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)
147 if (l > 2 && s0 == '\\' && s [1] == '\\'){
151 // We are only left with root
152 if (l == 1 && (s0 == DirectorySeparatorChar || s0 == AltDirectorySeparatorChar))
156 for (int i = start; i < l; i++){
159 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
165 if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar)
173 char [] copy = new char [l-sub];
178 for (int i = start, j = start; i < l && j < copy.Length; i++){
181 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar){
186 // For non-trailing cases.
187 if (j+1 != copy.Length){
188 copy [j++] = DirectorySeparatorChar;
191 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
196 return new String (copy);
199 public static string GetDirectoryName (string path)
201 // LAMESPEC: For empty string MS docs say both
202 // return null AND throw exception. Seems .NET throws.
203 if (path == String.Empty)
204 throw new ArgumentException("Invalid path");
206 if (path == null || GetPathRoot (path) == path)
209 if (path.Trim ().Length == 0)
210 throw new ArgumentException ("Argument string consists of whitespace characters only.");
212 if (path.IndexOfAny (System.IO.Path.InvalidPathChars) > -1)
213 throw new ArgumentException ("Path contains invalid characters");
215 int nLast = path.LastIndexOfAny (PathSeparatorChars);
220 string ret = path.Substring (0, nLast);
223 if (l >= 2 && DirectorySeparatorChar == '\\' && ret [l - 1] == VolumeSeparatorChar)
224 return ret + DirectorySeparatorChar;
225 else if (l == 1 && DirectorySeparatorChar == '\\' && path.Length >= 2 && path [nLast] == VolumeSeparatorChar)
226 return ret + VolumeSeparatorChar;
229 // Important: do not use CanonicalizePath here, use
230 // the custom CleanPath here, as this should not
231 // return absolute paths
233 return CleanPath (ret);
240 public static string GetExtension (string path)
245 if (path.IndexOfAny (InvalidPathChars) != -1)
246 throw new ArgumentException ("Illegal characters in path.");
248 int iExt = findExtension (path);
252 if (iExt < path.Length - 1)
253 return path.Substring (iExt);
258 public static string GetFileName (string path)
260 if (path == null || path.Length == 0)
263 if (path.IndexOfAny (InvalidPathChars) != -1)
264 throw new ArgumentException ("Illegal characters in path.");
266 int nLast = path.LastIndexOfAny (PathSeparatorChars);
268 return path.Substring (nLast + 1);
273 public static string GetFileNameWithoutExtension (string path)
275 return ChangeExtension (GetFileName (path), null);
278 public static string GetFullPath (string path)
280 string fullpath = InsecureGetFullPath (path);
282 SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
285 if (SecurityManager.SecurityEnabled) {
286 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
292 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364963%28v=vs.85%29.aspx
293 [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
294 private static extern int GetFullPathName(string path, int numBufferChars, StringBuilder buffer, ref IntPtr lpFilePartOrNull);
296 internal static string GetFullPathName(string path)
298 const int MAX_PATH = 260;
299 StringBuilder buffer = new StringBuilder(MAX_PATH);
300 IntPtr ptr = IntPtr.Zero;
301 int length = GetFullPathName(path, MAX_PATH, buffer, ref ptr);
304 int error = Marshal.GetLastWin32Error();
305 throw new IOException("Windows API call to GetFullPathName failed, Windows error code: " + error);
307 else if (length > MAX_PATH)
309 buffer = new StringBuilder(length);
310 GetFullPathName(path, length, buffer, ref ptr);
312 return buffer.ToString();
315 internal static string WindowsDriveAdjustment (string path)
317 // two special cases to consider when a drive is specified
320 if ((path [1] != ':') || !Char.IsLetter (path [0]))
323 string current = Directory.InsecureGetCurrentDirectory ();
324 // first, only the drive is specified
325 if (path.Length == 2) {
326 // then if the current directory is on the same drive
327 if (current [0] == path [0])
328 path = current; // we return it
330 path = GetFullPathName(path); // we have to use the GetFullPathName Windows API
331 } else if ((path [2] != Path.DirectorySeparatorChar) && (path [2] != Path.AltDirectorySeparatorChar)) {
332 // second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
333 // If the current directory is on the specified drive...
334 if (current [0] == path [0]) {
335 // then specified directory is appended to the current drive directory
336 path = Path.Combine (current, path.Substring (2, path.Length - 2));
338 // we have to use the GetFullPathName Windows API
339 path = GetFullPathName(path);
345 // insecure - do not call directly
346 internal static string InsecureGetFullPath (string path)
349 throw new ArgumentNullException ("path");
351 if (path.Trim ().Length == 0) {
352 string msg = Locale.GetText ("The specified path is not of a legal form (empty).");
353 throw new ArgumentException (msg);
356 // adjust for drives, i.e. a special case for windows
357 if (Environment.IsRunningOnWindows)
358 path = WindowsDriveAdjustment (path);
360 // if the supplied path ends with a separator...
361 char end = path [path.Length - 1];
363 var canonicalize = true;
364 if (path.Length >= 2 &&
365 IsDirectorySeparator (path [0]) &&
366 IsDirectorySeparator (path [1])) {
367 if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
368 throw new ArgumentException ("UNC paths should be of the form \\\\server\\share.");
370 if (path [0] != DirectorySeparatorChar)
371 path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
374 if (!IsPathRooted (path)) {
376 // avoid calling expensive CanonicalizePath when possible
377 if (!Environment.IsRunningOnWindows) {
379 while ((start = path.IndexOf ('.', start)) != -1) {
380 if (++start == path.Length || path [start] == DirectorySeparatorChar || path [start] == AltDirectorySeparatorChar)
383 canonicalize = start > 0;
386 var cwd = Directory.InsecureGetCurrentDirectory();
387 if (cwd [cwd.Length - 1] == DirectorySeparatorChar)
390 path = cwd + DirectorySeparatorChar + path;
391 } else if (DirectorySeparatorChar == '\\' &&
393 IsDirectorySeparator (path [0]) &&
394 !IsDirectorySeparator (path [1])) { // like `\abc\def'
395 string current = Directory.InsecureGetCurrentDirectory();
396 if (current [1] == VolumeSeparatorChar)
397 path = current.Substring (0, 2) + path;
399 path = current.Substring (0, current.IndexOf ('\\', current.IndexOfUnchecked ("\\\\", 0, current.Length) + 1));
404 path = CanonicalizePath (path);
406 // if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
407 if (IsDirectorySeparator (end) && (path [path.Length - 1] != DirectorySeparatorChar))
408 path += DirectorySeparatorChar;
413 internal static bool IsDirectorySeparator (char c) {
414 return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
417 public static string GetPathRoot (string path)
422 if (path.Trim ().Length == 0)
423 throw new ArgumentException ("The specified path is not of a legal form.");
425 if (!IsPathRooted (path))
428 if (DirectorySeparatorChar == '/') {
430 return IsDirectorySeparator (path [0]) ? DirectorySeparatorStr : String.Empty;
435 if (path.Length == 1 && IsDirectorySeparator (path [0]))
436 return DirectorySeparatorStr;
437 else if (path.Length < 2)
440 if (IsDirectorySeparator (path [0]) && IsDirectorySeparator (path[1])) {
441 // UNC: \\server or \\server\share
443 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
446 if (len < path.Length) {
448 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
451 return DirectorySeparatorStr +
452 DirectorySeparatorStr +
453 path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
454 } else if (IsDirectorySeparator (path [0])) {
455 // path starts with '\' or '/'
456 return DirectorySeparatorStr;
457 } else if (path[1] == VolumeSeparatorChar) {
459 if (path.Length >= 3 && (IsDirectorySeparator (path [2]))) len++;
461 return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
462 return path.Substring (0, len);
466 // FIXME: Further limit the assertion when imperative Assert is implemented
467 [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
468 public static string GetTempFileName ()
476 SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
479 var tmp_path = GetTempPath ();
483 path = Path.Combine (tmp_path, "tmp" + num.ToString ("x", CultureInfo.InvariantCulture) + ".tmp");
486 f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
487 8192, false, (FileOptions) 1);
488 } catch (IOException ex){
489 if (ex.hresult != MonoIO.FileAlreadyExistsHResult || count ++ > 65536)
491 } catch (UnauthorizedAccessException ex) {
492 if (count ++ > 65536)
493 throw new IOException (ex.Message, ex);
501 [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
502 public static string GetTempPath ()
504 SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
506 string p = get_temp_path ();
507 if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
508 return p + DirectorySeparatorChar;
513 [MethodImplAttribute(MethodImplOptions.InternalCall)]
514 private static extern string get_temp_path ();
516 public static bool HasExtension (string path)
518 if (path == null || path.Trim ().Length == 0)
521 if (path.IndexOfAny (InvalidPathChars) != -1)
522 throw new ArgumentException ("Illegal characters in path.");
524 int pos = findExtension (path);
525 return 0 <= pos && pos < path.Length - 1;
528 public static bool IsPathRooted (string path)
530 if (path == null || path.Length == 0)
533 if (path.IndexOfAny (InvalidPathChars) != -1)
534 throw new ArgumentException ("Illegal characters in path.");
537 return (c == DirectorySeparatorChar ||
538 c == AltDirectorySeparatorChar ||
539 (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
542 public static char[] GetInvalidFileNameChars ()
544 // return a new array as we do not want anyone to be able to change the values
545 if (Environment.IsRunningOnWindows) {
546 return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
547 '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
548 '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
549 '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
551 return new char [2] { '\x00', '/' };
555 public static char[] GetInvalidPathChars ()
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 [36] { '\x22', '\x3C', '\x3E', '\x7C', '\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',
564 return new char [1] { '\x00' };
568 public static string GetRandomFileName ()
570 // returns a 8.3 filename (total size 12)
571 StringBuilder sb = new StringBuilder (12);
572 // using strong crypto but without creating the file
573 RandomNumberGenerator rng = RandomNumberGenerator.Create ();
574 byte [] buffer = new byte [11];
575 rng.GetBytes (buffer);
577 for (int i = 0; i < buffer.Length; i++) {
581 // restrict to length of range [a..z0..9]
582 int b = (buffer [i] % 36);
583 char c = (char) (b < 26 ? (b + 'a') : (b - 26 + '0'));
587 return sb.ToString ();
590 // private class methods
592 private static int findExtension (string path)
594 // method should return the index of the path extension
595 // start or -1 if no valid extension
597 int iLastDot = path.LastIndexOf ('.');
598 int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
600 if (iLastDot > iLastSep)
608 VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
609 DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
610 AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
612 PathSeparator = MonoIO.PathSeparator;
613 // this copy will be modifiable ("by design")
614 InvalidPathChars = GetInvalidPathChars ();
617 DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
618 PathSeparatorChars = new char [] {
619 DirectorySeparatorChar,
620 AltDirectorySeparatorChar,
624 dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
627 // returns the server and share part of a UNC. Assumes "path" is a UNC.
628 static string GetServerAndShare (string path)
631 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
633 if (len < path.Length) {
635 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
638 return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
641 // assumes Environment.IsRunningOnWindows == true
642 static bool SameRoot (string root, string path)
644 // compare root - if enough details are available
645 if ((root.Length < 2) || (path.Length < 2))
649 if (IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1])) {
650 if (!(IsDirectorySeparator (path[0]) && IsDirectorySeparator (path[1])))
653 string rootShare = GetServerAndShare (root);
654 string pathShare = GetServerAndShare (path);
656 return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
660 if (!root [0].Equals (path [0]))
662 // presence of the separator
663 if (path[1] != Path.VolumeSeparatorChar)
665 if ((root.Length > 2) && (path.Length > 2)) {
666 // but don't directory compare the directory separator
667 return (IsDirectorySeparator (root[2]) && IsDirectorySeparator (path[2]));
672 static string CanonicalizePath (string path)
674 // STEP 1: Check for empty string
677 if (Environment.IsRunningOnWindows)
680 if (path.Length == 0)
683 // STEP 2: Check to see if this is only a root
684 string root = Path.GetPathRoot (path);
685 // it will return '\' for path '\', while it should return 'c:\' or so.
686 // Note: commenting this out makes the need for the (target == 1...) check in step 5
687 //if (root == path) return path;
689 // STEP 3: split the directories, this gets rid of consecutative "/"'s
690 string[] dirs = path.Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
691 // STEP 4: Get rid of directories containing . and ..
694 bool isUnc = Environment.IsRunningOnWindows &&
695 root.Length > 2 && IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1]);
697 // Set an overwrite limit for UNC paths since '\' + server + share
698 // must not be eliminated by the '..' elimination algorithm.
699 int limit = isUnc ? 3 : 0;
701 for (int i = 0; i < dirs.Length; i++) {
702 // WIN32 path components must be trimmed
703 if (Environment.IsRunningOnWindows)
704 dirs[i] = dirs[i].TrimEnd ();
706 if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
708 else if (dirs[i] == "..") {
709 // don't overwrite path segments below the limit
713 dirs[target++] = dirs[i];
716 // STEP 5: Combine everything.
717 if (target == 0 || (target == 1 && dirs[0] == ""))
720 string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
721 if (Environment.IsRunningOnWindows) {
722 // append leading '\' of the UNC path that was lost in STEP 3.
724 ret = Path.DirectorySeparatorStr + ret;
726 if (!SameRoot (root, ret))
731 } else if (!IsDirectorySeparator (path[0]) && SameRoot (root, path)) {
732 if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
733 ret += Path.DirectorySeparatorChar;
736 string current = Directory.GetCurrentDirectory ();
737 if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
738 // DOS local file path
739 if (ret.Length == 0 || IsDirectorySeparator (ret[0]))
741 return current.Substring (0, 2) + ret;
742 } else if (IsDirectorySeparator (current[current.Length - 1]) && IsDirectorySeparator (ret[0]))
743 return current + ret.Substring (1);
745 return current + ret;
748 if (root != "" && ret.Length > 0 && ret [0] != '/')
755 // required for FileIOPermission (and most proibably reusable elsewhere too)
756 // both path MUST be "full paths"
757 static internal bool IsPathSubsetOf (string subset, string path)
759 if (subset.Length > path.Length)
762 // check that everything up to the last separator match
763 int slast = subset.LastIndexOfAny (PathSeparatorChars);
764 if (String.Compare (subset, 0, path, 0, slast) != 0)
768 // then check if the last segment is identical
769 int plast = path.IndexOfAny (PathSeparatorChars, slast);
770 if (plast >= slast) {
771 return String.Compare (subset, slast, path, slast, path.Length - plast) == 0;
773 if (subset.Length != path.Length)
776 return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
780 static string Combine (params string [] paths)
783 throw new ArgumentNullException ("paths");
786 var ret = new StringBuilder ();
787 int pathsLen = paths.Length;
791 foreach (var s in paths) {
793 throw new ArgumentNullException ("One of the paths contains a null value", "paths");
796 if (s.IndexOfAny (InvalidPathChars) != -1)
797 throw new ArgumentException ("Illegal characters in path.");
801 ret.Append (DirectorySeparatorStr);
805 if (IsPathRooted (s))
810 if (slen > 0 && pathsLen > 0) {
811 char p1end = s [slen - 1];
812 if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
817 return ret.ToString ();
821 static string Combine (string path1, string path2, string path3)
824 throw new ArgumentNullException ("path1");
827 throw new ArgumentNullException ("path2");
830 throw new ArgumentNullException ("path3");
832 return Combine (new string [] { path1, path2, path3 });
836 static string Combine (string path1, string path2, string path3, string path4)
839 throw new ArgumentNullException ("path1");
842 throw new ArgumentNullException ("path2");
845 throw new ArgumentNullException ("path3");
848 throw new ArgumentNullException ("path4");
850 return Combine (new string [] { path1, path2, path3, path4 });
853 internal static void Validate (string path)
855 Validate (path, "path");
858 internal static void Validate (string path, string parameterName)
861 throw new ArgumentNullException (parameterName);
862 if (String.IsNullOrWhiteSpace (path))
863 throw new ArgumentException (Locale.GetText ("Path is empty"));
864 if (path.IndexOfAny (Path.InvalidPathChars) != -1)
865 throw new ArgumentException (Locale.GetText ("Path contains invalid chars"));
866 if (Environment.IsRunningOnWindows) {
867 int idx = path.IndexOf (':');
868 if (idx >= 0 && idx != 1)
869 throw new ArgumentException (parameterName);