1 //------------------------------------------------------------------------------
5 // Copyright (C) 2001 Moonlight Enterprises, All Rights Reserved
6 // Copyright (C) 2002 Ximian, Inc. (http://www.ximian.com)
7 // Copyright (C) 2003 Ben Maurer
8 // Copyright 2011 Xamarin Inc (http://www.xamarin.com).
10 // Author: Jim Richardson, develop@wtfo-guru.com
11 // Dan Lewis (dihlewis@yahoo.co.uk)
12 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
13 // Ben Maurer (bmaurer@users.sourceforge.net)
14 // Sebastien Pouliot <sebastien@ximian.com>
15 // Created: Saturday, August 11, 2001
17 //------------------------------------------------------------------------------
20 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
22 // Permission is hereby granted, free of charge, to any person obtaining
23 // a copy of this software and associated documentation files (the
24 // "Software"), to deal in the Software without restriction, including
25 // without limitation the rights to use, copy, modify, merge, publish,
26 // distribute, sublicense, and/or sell copies of the Software, and to
27 // permit persons to whom the Software is furnished to do so, subject to
28 // the following conditions:
30 // The above copyright notice and this permission notice shall be
31 // included in all copies or substantial portions of the Software.
33 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
34 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
35 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
36 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
37 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
38 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
39 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
42 using System.Globalization;
43 using System.Runtime.CompilerServices;
44 using System.Runtime.InteropServices;
45 using System.Security;
46 using System.Security.Cryptography;
47 using System.Security.Permissions;
53 public static class Path {
55 [Obsolete ("see GetInvalidPathChars and GetInvalidFileNameChars methods.")]
56 public static readonly char[] InvalidPathChars;
57 public static readonly char AltDirectorySeparatorChar;
58 public static readonly char DirectorySeparatorChar;
59 public static readonly char PathSeparator;
60 internal static readonly string DirectorySeparatorStr;
61 public static readonly char VolumeSeparatorChar;
63 internal static readonly char[] PathSeparatorChars;
64 private static readonly bool dirEqualsVolume;
67 public static string ChangeExtension (string path, string extension)
72 if (path.IndexOfAny (InvalidPathChars) != -1)
73 throw new ArgumentException ("Illegal characters in path.");
75 int iExt = findExtension (path);
77 if (extension == null)
78 return iExt < 0 ? path : path.Substring (0, iExt);
79 else if (extension.Length == 0)
80 return iExt < 0 ? path + '.' : path.Substring (0, iExt + 1);
82 else if (path.Length != 0) {
83 if (extension.Length > 0 && extension [0] != '.')
84 extension = "." + extension;
86 extension = String.Empty;
89 return path + extension;
90 } else if (iExt > 0) {
91 string temp = path.Substring (0, iExt);
92 return temp + extension;
98 public static string Combine (string path1, string path2)
101 throw new ArgumentNullException ("path1");
104 throw new ArgumentNullException ("path2");
106 if (path1.Length == 0)
109 if (path2.Length == 0)
112 if (path1.IndexOfAny (InvalidPathChars) != -1)
113 throw new ArgumentException ("Illegal characters in path.");
115 if (path2.IndexOfAny (InvalidPathChars) != -1)
116 throw new ArgumentException ("Illegal characters in path.");
119 if (IsPathRooted (path2))
122 char p1end = path1 [path1.Length - 1];
123 if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
124 return path1 + DirectorySeparatorStr + path2;
126 return path1 + path2;
131 // * Removes duplicat path separators from a string
132 // * If the string starts with \\, preserves the first two (hostname on Windows)
133 // * Removes the trailing path separator.
134 // * Returns the DirectorySeparatorChar for the single input DirectorySeparatorChar or AltDirectorySeparatorChar
136 // Unlike CanonicalizePath, this does not do any path resolution
137 // (which GetDirectoryName is not supposed to do).
139 internal static string CleanPath (string s)
148 if (l > 2 && s0 == '\\' && s [1] == '\\'){
152 // We are only left with root
153 if (l == 1 && (s0 == DirectorySeparatorChar || s0 == AltDirectorySeparatorChar))
157 for (int i = start; i < l; i++){
160 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
162 if (DirectorySeparatorChar != AltDirectorySeparatorChar && c == AltDirectorySeparatorChar)
168 if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar)
173 if (sub == 0 && alt == 0)
176 char [] copy = new char [l-sub];
181 for (int i = start, j = start; i < l && j < copy.Length; i++){
184 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar){
189 // For non-trailing cases.
190 if (j+1 != copy.Length){
191 copy [j++] = DirectorySeparatorChar;
194 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
199 return new String (copy);
202 public static string GetDirectoryName (string path)
204 // LAMESPEC: For empty string MS docs say both
205 // return null AND throw exception. Seems .NET throws.
206 if (path == String.Empty)
207 throw new ArgumentException("Invalid path");
209 if (path == null || GetPathRoot (path) == path)
212 if (path.Trim ().Length == 0)
213 throw new ArgumentException ("Argument string consists of whitespace characters only.");
215 if (path.IndexOfAny (System.IO.Path.InvalidPathChars) > -1)
216 throw new ArgumentException ("Path contains invalid characters");
218 int nLast = path.LastIndexOfAny (PathSeparatorChars);
223 string ret = path.Substring (0, nLast);
226 if (l >= 2 && DirectorySeparatorChar == '\\' && ret [l - 1] == VolumeSeparatorChar)
227 return ret + DirectorySeparatorChar;
228 else if (l == 1 && DirectorySeparatorChar == '\\' && path.Length >= 2 && path [nLast] == VolumeSeparatorChar)
229 return ret + VolumeSeparatorChar;
232 // Important: do not use CanonicalizePath here, use
233 // the custom CleanPath here, as this should not
234 // return absolute paths
236 return CleanPath (ret);
243 public static string GetExtension (string path)
248 if (path.IndexOfAny (InvalidPathChars) != -1)
249 throw new ArgumentException ("Illegal characters in path.");
251 int iExt = findExtension (path);
255 if (iExt < path.Length - 1)
256 return path.Substring (iExt);
261 public static string GetFileName (string path)
263 if (path == null || path.Length == 0)
266 if (path.IndexOfAny (InvalidPathChars) != -1)
267 throw new ArgumentException ("Illegal characters in path.");
269 int nLast = path.LastIndexOfAny (PathSeparatorChars);
271 return path.Substring (nLast + 1);
276 public static string GetFileNameWithoutExtension (string path)
278 return ChangeExtension (GetFileName (path), null);
281 public static string GetFullPath (string path)
283 string fullpath = InsecureGetFullPath (path);
285 SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
288 if (SecurityManager.SecurityEnabled) {
289 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
295 internal static String GetFullPathInternal(String path)
297 return InsecureGetFullPath (path);
301 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364963%28v=vs.85%29.aspx
302 [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
303 private static extern int GetFullPathName(string path, int numBufferChars, StringBuilder buffer, ref IntPtr lpFilePartOrNull);
305 internal static string GetFullPathName(string path)
307 const int MAX_PATH = 260;
308 StringBuilder buffer = new StringBuilder(MAX_PATH);
309 IntPtr ptr = IntPtr.Zero;
310 int length = GetFullPathName(path, MAX_PATH, buffer, ref ptr);
313 int error = Marshal.GetLastWin32Error();
314 throw new IOException("Windows API call to GetFullPathName failed, Windows error code: " + error);
316 else if (length > MAX_PATH)
318 buffer = new StringBuilder(length);
319 GetFullPathName(path, length, buffer, ref ptr);
321 return buffer.ToString();
324 internal static string WindowsDriveAdjustment (string path)
326 // three special cases to consider when a drive is specified
327 if (path.Length < 2) {
328 if (path.Length == 1 && (path[0] == '\\' || path[0] == '/'))
329 return Path.GetPathRoot(Directory.GetCurrentDirectory());
332 if ((path [1] != ':') || !Char.IsLetter (path [0]))
335 string current = Directory.InsecureGetCurrentDirectory ();
336 // first, only the drive is specified
337 if (path.Length == 2) {
338 // then if the current directory is on the same drive
339 if (current [0] == path [0])
340 path = current; // we return it
342 path = GetFullPathName(path); // we have to use the GetFullPathName Windows API
343 } else if ((path [2] != Path.DirectorySeparatorChar) && (path [2] != Path.AltDirectorySeparatorChar)) {
344 // second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
345 // If the current directory is on the specified drive...
346 if (current [0] == path [0]) {
347 // then specified directory is appended to the current drive directory
348 path = Path.Combine (current, path.Substring (2, path.Length - 2));
350 // we have to use the GetFullPathName Windows API
351 path = GetFullPathName(path);
358 // insecure - do not call directly
359 internal static string InsecureGetFullPath (string path)
362 throw new ArgumentNullException ("path");
364 if (path.Trim ().Length == 0) {
365 string msg = Locale.GetText ("The specified path is not of a legal form (empty).");
366 throw new ArgumentException (msg);
369 // adjust for drives, i.e. a special case for windows
370 if (Environment.IsRunningOnWindows)
371 path = WindowsDriveAdjustment (path);
373 // if the supplied path ends with a separator...
374 char end = path [path.Length - 1];
376 var canonicalize = true;
377 if (path.Length >= 2 &&
378 IsDirectorySeparator (path [0]) &&
379 IsDirectorySeparator (path [1])) {
380 if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
381 throw new ArgumentException ("UNC paths should be of the form \\\\server\\share.");
383 if (path [0] != DirectorySeparatorChar)
384 path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
387 if (!IsPathRooted (path)) {
389 // avoid calling expensive CanonicalizePath when possible
390 if (!Environment.IsRunningOnWindows) {
392 while ((start = path.IndexOf ('.', start)) != -1) {
393 if (++start == path.Length || path [start] == DirectorySeparatorChar || path [start] == AltDirectorySeparatorChar)
396 canonicalize = start > 0;
399 var cwd = Directory.InsecureGetCurrentDirectory();
400 if (cwd [cwd.Length - 1] == DirectorySeparatorChar)
403 path = cwd + DirectorySeparatorChar + path;
404 } else if (DirectorySeparatorChar == '\\' &&
406 IsDirectorySeparator (path [0]) &&
407 !IsDirectorySeparator (path [1])) { // like `\abc\def'
408 string current = Directory.InsecureGetCurrentDirectory();
409 if (current [1] == VolumeSeparatorChar)
410 path = current.Substring (0, 2) + path;
412 path = current.Substring (0, current.IndexOf ('\\', current.IndexOfUnchecked ("\\\\", 0, current.Length) + 1));
417 path = CanonicalizePath (path);
419 // if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
420 if (IsDirectorySeparator (end) && (path [path.Length - 1] != DirectorySeparatorChar))
421 path += DirectorySeparatorChar;
426 internal static bool IsDirectorySeparator (char c) {
427 return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
430 public static string GetPathRoot (string path)
435 if (path.Trim ().Length == 0)
436 throw new ArgumentException ("The specified path is not of a legal form.");
438 if (!IsPathRooted (path))
441 if (DirectorySeparatorChar == '/') {
443 return IsDirectorySeparator (path [0]) ? DirectorySeparatorStr : String.Empty;
448 if (path.Length == 1 && IsDirectorySeparator (path [0]))
449 return DirectorySeparatorStr;
450 else if (path.Length < 2)
453 if (IsDirectorySeparator (path [0]) && IsDirectorySeparator (path[1])) {
454 // UNC: \\server or \\server\share
456 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
459 if (len < path.Length) {
461 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
464 return DirectorySeparatorStr +
465 DirectorySeparatorStr +
466 path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
467 } else if (IsDirectorySeparator (path [0])) {
468 // path starts with '\' or '/'
469 return DirectorySeparatorStr;
470 } else if (path[1] == VolumeSeparatorChar) {
472 if (path.Length >= 3 && (IsDirectorySeparator (path [2]))) len++;
474 return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
475 return path.Substring (0, len);
479 // FIXME: Further limit the assertion when imperative Assert is implemented
480 [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
481 public static string GetTempFileName ()
489 SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
492 var tmp_path = GetTempPath ();
496 path = Path.Combine (tmp_path, "tmp" + num.ToString ("x", CultureInfo.InvariantCulture) + ".tmp");
499 f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
500 8192, false, (FileOptions) 1);
501 } catch (IOException ex){
502 if (ex._HResult != MonoIO.FileAlreadyExistsHResult || count ++ > 65536)
504 } catch (UnauthorizedAccessException ex) {
505 if (count ++ > 65536)
506 throw new IOException (ex.Message, ex);
514 [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
515 public static string GetTempPath ()
517 SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
519 string p = get_temp_path ();
520 if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
521 return p + DirectorySeparatorChar;
526 [MethodImplAttribute(MethodImplOptions.InternalCall)]
527 private static extern string get_temp_path ();
529 public static bool HasExtension (string path)
531 if (path == null || path.Trim ().Length == 0)
534 if (path.IndexOfAny (InvalidPathChars) != -1)
535 throw new ArgumentException ("Illegal characters in path.");
537 int pos = findExtension (path);
538 return 0 <= pos && pos < path.Length - 1;
541 public static bool IsPathRooted (string path)
543 if (path == null || path.Length == 0)
546 if (path.IndexOfAny (InvalidPathChars) != -1)
547 throw new ArgumentException ("Illegal characters in path.");
550 return (c == DirectorySeparatorChar ||
551 c == AltDirectorySeparatorChar ||
552 (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
555 public static char[] GetInvalidFileNameChars ()
557 // return a new array as we do not want anyone to be able to change the values
558 if (Environment.IsRunningOnWindows) {
559 return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
560 '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
561 '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
562 '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
564 return new char [2] { '\x00', '/' };
568 public static char[] GetInvalidPathChars ()
570 // return a new array as we do not want anyone to be able to change the values
571 if (Environment.IsRunningOnWindows) {
572 return new char [36] { '\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
573 '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
574 '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
577 return new char [1] { '\x00' };
581 public static string GetRandomFileName ()
583 // returns a 8.3 filename (total size 12)
584 StringBuilder sb = new StringBuilder (12);
585 // using strong crypto but without creating the file
586 RandomNumberGenerator rng = RandomNumberGenerator.Create ();
587 byte [] buffer = new byte [11];
588 rng.GetBytes (buffer);
590 for (int i = 0; i < buffer.Length; i++) {
594 // restrict to length of range [a..z0..9]
595 int b = (buffer [i] % 36);
596 char c = (char) (b < 26 ? (b + 'a') : (b - 26 + '0'));
600 return sb.ToString ();
603 // private class methods
605 private static int findExtension (string path)
607 // method should return the index of the path extension
608 // start or -1 if no valid extension
610 int iLastDot = path.LastIndexOf ('.');
611 int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
613 if (iLastDot > iLastSep)
621 VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
622 DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
623 AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
625 PathSeparator = MonoIO.PathSeparator;
626 // this copy will be modifiable ("by design")
627 InvalidPathChars = GetInvalidPathChars ();
630 DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
631 PathSeparatorChars = new char [] {
632 DirectorySeparatorChar,
633 AltDirectorySeparatorChar,
637 dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
640 // returns the server and share part of a UNC. Assumes "path" is a UNC.
641 static string GetServerAndShare (string path)
644 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
646 if (len < path.Length) {
648 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
651 return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
654 // assumes Environment.IsRunningOnWindows == true
655 static bool SameRoot (string root, string path)
657 // compare root - if enough details are available
658 if ((root.Length < 2) || (path.Length < 2))
662 if (IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1])) {
663 if (!(IsDirectorySeparator (path[0]) && IsDirectorySeparator (path[1])))
666 string rootShare = GetServerAndShare (root);
667 string pathShare = GetServerAndShare (path);
669 return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
673 if (!root [0].Equals (path [0]))
675 // presence of the separator
676 if (path[1] != Path.VolumeSeparatorChar)
678 if ((root.Length > 2) && (path.Length > 2)) {
679 // but don't directory compare the directory separator
680 return (IsDirectorySeparator (root[2]) && IsDirectorySeparator (path[2]));
685 static string CanonicalizePath (string path)
687 // STEP 1: Check for empty string
690 if (Environment.IsRunningOnWindows)
693 if (path.Length == 0)
696 // STEP 2: Check to see if this is only a root
697 string root = Path.GetPathRoot (path);
698 // it will return '\' for path '\', while it should return 'c:\' or so.
699 // Note: commenting this out makes the need for the (target == 1...) check in step 5
700 //if (root == path) return path;
702 // STEP 3: split the directories, this gets rid of consecutative "/"'s
703 string[] dirs = path.Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
704 // STEP 4: Get rid of directories containing . and ..
707 bool isUnc = Environment.IsRunningOnWindows &&
708 root.Length > 2 && IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1]);
710 // Set an overwrite limit for UNC paths since '\' + server + share
711 // must not be eliminated by the '..' elimination algorithm.
712 int limit = isUnc ? 3 : 0;
714 for (int i = 0; i < dirs.Length; i++) {
715 // WIN32 path components must be trimmed
716 if (Environment.IsRunningOnWindows)
717 dirs[i] = dirs[i].TrimEnd ();
719 if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
721 else if (dirs[i] == "..") {
722 // don't overwrite path segments below the limit
726 dirs[target++] = dirs[i];
729 // STEP 5: Combine everything.
730 if (target == 0 || (target == 1 && dirs[0] == ""))
733 string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
734 if (Environment.IsRunningOnWindows) {
736 // append leading '\' of the UNC path that was lost in STEP 3.
738 ret = Path.DirectorySeparatorStr + ret;
740 if (!SameRoot (root, ret))
745 } else if (!IsDirectorySeparator (path[0]) && SameRoot (root, path)) {
746 if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
747 ret += Path.DirectorySeparatorChar;
750 string current = Directory.GetCurrentDirectory ();
751 if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
752 // DOS local file path
753 if (ret.Length == 0 || IsDirectorySeparator (ret[0]))
755 return current.Substring (0, 2) + ret;
756 } else if (IsDirectorySeparator (current[current.Length - 1]) && IsDirectorySeparator (ret[0]))
757 return current + ret.Substring (1);
759 return current + ret;
763 if (root != "" && ret.Length > 0 && ret [0] != '/')
770 // required for FileIOPermission (and most proibably reusable elsewhere too)
771 // both path MUST be "full paths"
772 static internal bool IsPathSubsetOf (string subset, string path)
774 if (subset.Length > path.Length)
777 // check that everything up to the last separator match
778 int slast = subset.LastIndexOfAny (PathSeparatorChars);
779 if (String.Compare (subset, 0, path, 0, slast) != 0)
783 // then check if the last segment is identical
784 int plast = path.IndexOfAny (PathSeparatorChars, slast);
785 if (plast >= slast) {
786 return String.Compare (subset, slast, path, slast, path.Length - plast) == 0;
788 if (subset.Length != path.Length)
791 return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
795 static string Combine (params string [] paths)
798 throw new ArgumentNullException ("paths");
801 var ret = new StringBuilder ();
802 int pathsLen = paths.Length;
806 foreach (var s in paths) {
808 throw new ArgumentNullException ("One of the paths contains a null value", "paths");
811 if (s.IndexOfAny (InvalidPathChars) != -1)
812 throw new ArgumentException ("Illegal characters in path.");
816 ret.Append (DirectorySeparatorStr);
820 if (IsPathRooted (s))
825 if (slen > 0 && pathsLen > 0) {
826 char p1end = s [slen - 1];
827 if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
832 return ret.ToString ();
836 static string Combine (string path1, string path2, string path3)
839 throw new ArgumentNullException ("path1");
842 throw new ArgumentNullException ("path2");
845 throw new ArgumentNullException ("path3");
847 return Combine (new string [] { path1, path2, path3 });
851 static string Combine (string path1, string path2, string path3, string path4)
854 throw new ArgumentNullException ("path1");
857 throw new ArgumentNullException ("path2");
860 throw new ArgumentNullException ("path3");
863 throw new ArgumentNullException ("path4");
865 return Combine (new string [] { path1, path2, path3, path4 });
868 internal static void Validate (string path)
870 Validate (path, "path");
873 internal static void Validate (string path, string parameterName)
876 throw new ArgumentNullException (parameterName);
877 if (String.IsNullOrWhiteSpace (path))
878 throw new ArgumentException (Locale.GetText ("Path is empty"));
879 if (path.IndexOfAny (Path.InvalidPathChars) != -1)
880 throw new ArgumentException (Locale.GetText ("Path contains invalid chars"));
882 if (Environment.IsRunningOnWindows) {
883 int idx = path.IndexOf (':');
884 if (idx >= 0 && idx != 1)
885 throw new ArgumentException (parameterName);
890 internal static string DirectorySeparatorCharAsString {
892 return DirectorySeparatorStr;
896 internal const int MAX_PATH = 260; // From WinDef.h
898 #region Copied from referencesource
899 // this was copied from corefx since it's not available in referencesource
900 internal static readonly char[] trimEndCharsWindows = { (char)0x9, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20, (char)0x85, (char)0xA0 };
901 internal static readonly char[] trimEndCharsUnix = { };
903 internal static char[] TrimEndChars => Environment.IsRunningOnWindows ? trimEndCharsWindows : trimEndCharsUnix;
905 // ".." can only be used if it is specified as a part of a valid File/Directory name. We disallow
906 // the user being able to use it to move up directories. Here are some examples eg
907 // Valid: a..b abc..d
908 // Invalid: ..ab ab.. .. abc..d\abc..
910 internal static void CheckSearchPattern(String searchPattern)
913 while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) {
915 if (index + 2 == searchPattern.Length) // Terminal ".." . Files names cannot end in ".."
916 throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
918 if ((searchPattern[index+2] == DirectorySeparatorChar)
919 || (searchPattern[index+2] == AltDirectorySeparatorChar))
920 throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
922 searchPattern = searchPattern.Substring(index + 2);
926 internal static void CheckInvalidPathChars(string path, bool checkAdditional = false)
929 throw new ArgumentNullException("path");
931 if (PathInternal.HasIllegalCharacters(path, checkAdditional))
932 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
935 internal static String InternalCombine(String path1, String path2) {
936 if (path1==null || path2==null)
937 throw new ArgumentNullException((path1==null) ? "path1" : "path2");
938 CheckInvalidPathChars(path1);
939 CheckInvalidPathChars(path2);
941 if (path2.Length == 0)
942 throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), "path2");
943 if (IsPathRooted(path2))
944 throw new ArgumentException(Environment.GetResourceString("Arg_Path2IsRooted"), "path2");
945 int i = path1.Length;
946 if (i == 0) return path2;
947 char ch = path1[i - 1];
948 if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar)
949 return path1 + DirectorySeparatorCharAsString + path2;
950 return path1 + path2;