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 ();
293 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364963%28v=vs.85%29.aspx
294 [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
295 private static extern int GetFullPathName(string path, int numBufferChars, StringBuilder buffer, ref IntPtr lpFilePartOrNull);
297 internal static string GetFullPathName(string path)
299 const int MAX_PATH = 260;
300 StringBuilder buffer = new StringBuilder(MAX_PATH);
301 IntPtr ptr = IntPtr.Zero;
302 int length = GetFullPathName(path, MAX_PATH, buffer, ref ptr);
305 int error = Marshal.GetLastWin32Error();
306 throw new IOException("Windows API call to GetFullPathName failed, Windows error code: " + error);
308 else if (length > MAX_PATH)
310 buffer = new StringBuilder(length);
311 GetFullPathName(path, length, buffer, ref ptr);
313 return buffer.ToString();
316 internal static string WindowsDriveAdjustment (string path)
318 // two special cases to consider when a drive is specified
321 if ((path [1] != ':') || !Char.IsLetter (path [0]))
324 string current = Directory.InsecureGetCurrentDirectory ();
325 // first, only the drive is specified
326 if (path.Length == 2) {
327 // then if the current directory is on the same drive
328 if (current [0] == path [0])
329 path = current; // we return it
331 path = GetFullPathName(path); // we have to use the GetFullPathName Windows API
332 } else if ((path [2] != Path.DirectorySeparatorChar) && (path [2] != Path.AltDirectorySeparatorChar)) {
333 // second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
334 // If the current directory is on the specified drive...
335 if (current [0] == path [0]) {
336 // then specified directory is appended to the current drive directory
337 path = Path.Combine (current, path.Substring (2, path.Length - 2));
339 // we have to use the GetFullPathName Windows API
340 path = GetFullPathName(path);
347 // insecure - do not call directly
348 internal static string InsecureGetFullPath (string path)
351 throw new ArgumentNullException ("path");
353 if (path.Trim ().Length == 0) {
354 string msg = Locale.GetText ("The specified path is not of a legal form (empty).");
355 throw new ArgumentException (msg);
358 // adjust for drives, i.e. a special case for windows
359 if (Environment.IsRunningOnWindows)
360 path = WindowsDriveAdjustment (path);
362 // if the supplied path ends with a separator...
363 char end = path [path.Length - 1];
365 var canonicalize = true;
366 if (path.Length >= 2 &&
367 IsDirectorySeparator (path [0]) &&
368 IsDirectorySeparator (path [1])) {
369 if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
370 throw new ArgumentException ("UNC paths should be of the form \\\\server\\share.");
372 if (path [0] != DirectorySeparatorChar)
373 path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
376 if (!IsPathRooted (path)) {
378 // avoid calling expensive CanonicalizePath when possible
379 if (!Environment.IsRunningOnWindows) {
381 while ((start = path.IndexOf ('.', start)) != -1) {
382 if (++start == path.Length || path [start] == DirectorySeparatorChar || path [start] == AltDirectorySeparatorChar)
385 canonicalize = start > 0;
388 var cwd = Directory.InsecureGetCurrentDirectory();
389 if (cwd [cwd.Length - 1] == DirectorySeparatorChar)
392 path = cwd + DirectorySeparatorChar + path;
393 } else if (DirectorySeparatorChar == '\\' &&
395 IsDirectorySeparator (path [0]) &&
396 !IsDirectorySeparator (path [1])) { // like `\abc\def'
397 string current = Directory.InsecureGetCurrentDirectory();
398 if (current [1] == VolumeSeparatorChar)
399 path = current.Substring (0, 2) + path;
401 path = current.Substring (0, current.IndexOf ('\\', current.IndexOfUnchecked ("\\\\", 0, current.Length) + 1));
406 path = CanonicalizePath (path);
408 // if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
409 if (IsDirectorySeparator (end) && (path [path.Length - 1] != DirectorySeparatorChar))
410 path += DirectorySeparatorChar;
415 internal static bool IsDirectorySeparator (char c) {
416 return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
419 public static string GetPathRoot (string path)
424 if (path.Trim ().Length == 0)
425 throw new ArgumentException ("The specified path is not of a legal form.");
427 if (!IsPathRooted (path))
430 if (DirectorySeparatorChar == '/') {
432 return IsDirectorySeparator (path [0]) ? DirectorySeparatorStr : String.Empty;
437 if (path.Length == 1 && IsDirectorySeparator (path [0]))
438 return DirectorySeparatorStr;
439 else if (path.Length < 2)
442 if (IsDirectorySeparator (path [0]) && IsDirectorySeparator (path[1])) {
443 // UNC: \\server or \\server\share
445 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
448 if (len < path.Length) {
450 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
453 return DirectorySeparatorStr +
454 DirectorySeparatorStr +
455 path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
456 } else if (IsDirectorySeparator (path [0])) {
457 // path starts with '\' or '/'
458 return DirectorySeparatorStr;
459 } else if (path[1] == VolumeSeparatorChar) {
461 if (path.Length >= 3 && (IsDirectorySeparator (path [2]))) len++;
463 return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
464 return path.Substring (0, len);
468 // FIXME: Further limit the assertion when imperative Assert is implemented
469 [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
470 public static string GetTempFileName ()
478 SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
481 var tmp_path = GetTempPath ();
485 path = Path.Combine (tmp_path, "tmp" + num.ToString ("x", CultureInfo.InvariantCulture) + ".tmp");
488 f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
489 8192, false, (FileOptions) 1);
490 } catch (IOException ex){
491 if (ex.hresult != MonoIO.FileAlreadyExistsHResult || count ++ > 65536)
493 } catch (UnauthorizedAccessException ex) {
494 if (count ++ > 65536)
495 throw new IOException (ex.Message, ex);
503 [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
504 public static string GetTempPath ()
506 SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
508 string p = get_temp_path ();
509 if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
510 return p + DirectorySeparatorChar;
515 [MethodImplAttribute(MethodImplOptions.InternalCall)]
516 private static extern string get_temp_path ();
518 public static bool HasExtension (string path)
520 if (path == null || path.Trim ().Length == 0)
523 if (path.IndexOfAny (InvalidPathChars) != -1)
524 throw new ArgumentException ("Illegal characters in path.");
526 int pos = findExtension (path);
527 return 0 <= pos && pos < path.Length - 1;
530 public static bool IsPathRooted (string path)
532 if (path == null || path.Length == 0)
535 if (path.IndexOfAny (InvalidPathChars) != -1)
536 throw new ArgumentException ("Illegal characters in path.");
539 return (c == DirectorySeparatorChar ||
540 c == AltDirectorySeparatorChar ||
541 (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
544 public static char[] GetInvalidFileNameChars ()
546 // return a new array as we do not want anyone to be able to change the values
547 if (Environment.IsRunningOnWindows) {
548 return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
549 '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
550 '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
551 '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
553 return new char [2] { '\x00', '/' };
557 public static char[] GetInvalidPathChars ()
559 // return a new array as we do not want anyone to be able to change the values
560 if (Environment.IsRunningOnWindows) {
561 return new char [36] { '\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
562 '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
563 '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
566 return new char [1] { '\x00' };
570 public static string GetRandomFileName ()
572 // returns a 8.3 filename (total size 12)
573 StringBuilder sb = new StringBuilder (12);
574 // using strong crypto but without creating the file
575 RandomNumberGenerator rng = RandomNumberGenerator.Create ();
576 byte [] buffer = new byte [11];
577 rng.GetBytes (buffer);
579 for (int i = 0; i < buffer.Length; i++) {
583 // restrict to length of range [a..z0..9]
584 int b = (buffer [i] % 36);
585 char c = (char) (b < 26 ? (b + 'a') : (b - 26 + '0'));
589 return sb.ToString ();
592 // private class methods
594 private static int findExtension (string path)
596 // method should return the index of the path extension
597 // start or -1 if no valid extension
599 int iLastDot = path.LastIndexOf ('.');
600 int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
602 if (iLastDot > iLastSep)
610 VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
611 DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
612 AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
614 PathSeparator = MonoIO.PathSeparator;
615 // this copy will be modifiable ("by design")
616 InvalidPathChars = GetInvalidPathChars ();
619 DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
620 PathSeparatorChars = new char [] {
621 DirectorySeparatorChar,
622 AltDirectorySeparatorChar,
626 dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
629 // returns the server and share part of a UNC. Assumes "path" is a UNC.
630 static string GetServerAndShare (string path)
633 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
635 if (len < path.Length) {
637 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
640 return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
643 // assumes Environment.IsRunningOnWindows == true
644 static bool SameRoot (string root, string path)
646 // compare root - if enough details are available
647 if ((root.Length < 2) || (path.Length < 2))
651 if (IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1])) {
652 if (!(IsDirectorySeparator (path[0]) && IsDirectorySeparator (path[1])))
655 string rootShare = GetServerAndShare (root);
656 string pathShare = GetServerAndShare (path);
658 return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
662 if (!root [0].Equals (path [0]))
664 // presence of the separator
665 if (path[1] != Path.VolumeSeparatorChar)
667 if ((root.Length > 2) && (path.Length > 2)) {
668 // but don't directory compare the directory separator
669 return (IsDirectorySeparator (root[2]) && IsDirectorySeparator (path[2]));
674 static string CanonicalizePath (string path)
676 // STEP 1: Check for empty string
679 if (Environment.IsRunningOnWindows)
682 if (path.Length == 0)
685 // STEP 2: Check to see if this is only a root
686 string root = Path.GetPathRoot (path);
687 // it will return '\' for path '\', while it should return 'c:\' or so.
688 // Note: commenting this out makes the need for the (target == 1...) check in step 5
689 //if (root == path) return path;
691 // STEP 3: split the directories, this gets rid of consecutative "/"'s
692 string[] dirs = path.Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
693 // STEP 4: Get rid of directories containing . and ..
696 bool isUnc = Environment.IsRunningOnWindows &&
697 root.Length > 2 && IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1]);
699 // Set an overwrite limit for UNC paths since '\' + server + share
700 // must not be eliminated by the '..' elimination algorithm.
701 int limit = isUnc ? 3 : 0;
703 for (int i = 0; i < dirs.Length; i++) {
704 // WIN32 path components must be trimmed
705 if (Environment.IsRunningOnWindows)
706 dirs[i] = dirs[i].TrimEnd ();
708 if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
710 else if (dirs[i] == "..") {
711 // don't overwrite path segments below the limit
715 dirs[target++] = dirs[i];
718 // STEP 5: Combine everything.
719 if (target == 0 || (target == 1 && dirs[0] == ""))
722 string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
723 if (Environment.IsRunningOnWindows) {
724 // append leading '\' of the UNC path that was lost in STEP 3.
726 ret = Path.DirectorySeparatorStr + ret;
728 if (!SameRoot (root, ret))
733 } else if (!IsDirectorySeparator (path[0]) && SameRoot (root, path)) {
734 if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
735 ret += Path.DirectorySeparatorChar;
738 string current = Directory.GetCurrentDirectory ();
739 if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
740 // DOS local file path
741 if (ret.Length == 0 || IsDirectorySeparator (ret[0]))
743 return current.Substring (0, 2) + ret;
744 } else if (IsDirectorySeparator (current[current.Length - 1]) && IsDirectorySeparator (ret[0]))
745 return current + ret.Substring (1);
747 return current + ret;
750 if (root != "" && ret.Length > 0 && ret [0] != '/')
757 // required for FileIOPermission (and most proibably reusable elsewhere too)
758 // both path MUST be "full paths"
759 static internal bool IsPathSubsetOf (string subset, string path)
761 if (subset.Length > path.Length)
764 // check that everything up to the last separator match
765 int slast = subset.LastIndexOfAny (PathSeparatorChars);
766 if (String.Compare (subset, 0, path, 0, slast) != 0)
770 // then check if the last segment is identical
771 int plast = path.IndexOfAny (PathSeparatorChars, slast);
772 if (plast >= slast) {
773 return String.Compare (subset, slast, path, slast, path.Length - plast) == 0;
775 if (subset.Length != path.Length)
778 return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
782 static string Combine (params string [] paths)
785 throw new ArgumentNullException ("paths");
788 var ret = new StringBuilder ();
789 int pathsLen = paths.Length;
793 foreach (var s in paths) {
795 throw new ArgumentNullException ("One of the paths contains a null value", "paths");
798 if (s.IndexOfAny (InvalidPathChars) != -1)
799 throw new ArgumentException ("Illegal characters in path.");
803 ret.Append (DirectorySeparatorStr);
807 if (IsPathRooted (s))
812 if (slen > 0 && pathsLen > 0) {
813 char p1end = s [slen - 1];
814 if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
819 return ret.ToString ();
823 static string Combine (string path1, string path2, string path3)
826 throw new ArgumentNullException ("path1");
829 throw new ArgumentNullException ("path2");
832 throw new ArgumentNullException ("path3");
834 return Combine (new string [] { path1, path2, path3 });
838 static string Combine (string path1, string path2, string path3, string path4)
841 throw new ArgumentNullException ("path1");
844 throw new ArgumentNullException ("path2");
847 throw new ArgumentNullException ("path3");
850 throw new ArgumentNullException ("path4");
852 return Combine (new string [] { path1, path2, path3, path4 });
855 internal static void Validate (string path)
857 Validate (path, "path");
860 internal static void Validate (string path, string parameterName)
863 throw new ArgumentNullException (parameterName);
864 if (String.IsNullOrWhiteSpace (path))
865 throw new ArgumentException (Locale.GetText ("Path is empty"));
866 if (path.IndexOfAny (Path.InvalidPathChars) != -1)
867 throw new ArgumentException (Locale.GetText ("Path contains invalid chars"));
868 if (Environment.IsRunningOnWindows) {
869 int idx = path.IndexOf (':');
870 if (idx >= 0 && idx != 1)
871 throw new ArgumentException (parameterName);