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 internal static String GetFullPathInternal(String path)
294 return InsecureGetFullPath (path);
298 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364963%28v=vs.85%29.aspx
299 [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
300 private static extern int GetFullPathName(string path, int numBufferChars, StringBuilder buffer, ref IntPtr lpFilePartOrNull);
302 internal static string GetFullPathName(string path)
304 const int MAX_PATH = 260;
305 StringBuilder buffer = new StringBuilder(MAX_PATH);
306 IntPtr ptr = IntPtr.Zero;
307 int length = GetFullPathName(path, MAX_PATH, buffer, ref ptr);
310 int error = Marshal.GetLastWin32Error();
311 throw new IOException("Windows API call to GetFullPathName failed, Windows error code: " + error);
313 else if (length > MAX_PATH)
315 buffer = new StringBuilder(length);
316 GetFullPathName(path, length, buffer, ref ptr);
318 return buffer.ToString();
321 internal static string WindowsDriveAdjustment (string path)
323 // three special cases to consider when a drive is specified
324 if (path.Length < 2) {
325 if (path.Length == 1 && (path[0] == '\\' || path[0] == '/'))
326 return Path.GetPathRoot(Directory.GetCurrentDirectory());
329 if ((path [1] != ':') || !Char.IsLetter (path [0]))
332 string current = Directory.InsecureGetCurrentDirectory ();
333 // first, only the drive is specified
334 if (path.Length == 2) {
335 // then if the current directory is on the same drive
336 if (current [0] == path [0])
337 path = current; // we return it
339 path = GetFullPathName(path); // we have to use the GetFullPathName Windows API
340 } else if ((path [2] != Path.DirectorySeparatorChar) && (path [2] != Path.AltDirectorySeparatorChar)) {
341 // second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
342 // If the current directory is on the specified drive...
343 if (current [0] == path [0]) {
344 // then specified directory is appended to the current drive directory
345 path = Path.Combine (current, path.Substring (2, path.Length - 2));
347 // we have to use the GetFullPathName Windows API
348 path = GetFullPathName(path);
355 // insecure - do not call directly
356 internal static string InsecureGetFullPath (string path)
359 throw new ArgumentNullException ("path");
361 if (path.Trim ().Length == 0) {
362 string msg = Locale.GetText ("The specified path is not of a legal form (empty).");
363 throw new ArgumentException (msg);
366 // adjust for drives, i.e. a special case for windows
367 if (Environment.IsRunningOnWindows)
368 path = WindowsDriveAdjustment (path);
370 // if the supplied path ends with a separator...
371 char end = path [path.Length - 1];
373 var canonicalize = true;
374 if (path.Length >= 2 &&
375 IsDirectorySeparator (path [0]) &&
376 IsDirectorySeparator (path [1])) {
377 if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
378 throw new ArgumentException ("UNC paths should be of the form \\\\server\\share.");
380 if (path [0] != DirectorySeparatorChar)
381 path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
384 if (!IsPathRooted (path)) {
386 // avoid calling expensive CanonicalizePath when possible
387 if (!Environment.IsRunningOnWindows) {
389 while ((start = path.IndexOf ('.', start)) != -1) {
390 if (++start == path.Length || path [start] == DirectorySeparatorChar || path [start] == AltDirectorySeparatorChar)
393 canonicalize = start > 0;
396 var cwd = Directory.InsecureGetCurrentDirectory();
397 if (cwd [cwd.Length - 1] == DirectorySeparatorChar)
400 path = cwd + DirectorySeparatorChar + path;
401 } else if (DirectorySeparatorChar == '\\' &&
403 IsDirectorySeparator (path [0]) &&
404 !IsDirectorySeparator (path [1])) { // like `\abc\def'
405 string current = Directory.InsecureGetCurrentDirectory();
406 if (current [1] == VolumeSeparatorChar)
407 path = current.Substring (0, 2) + path;
409 path = current.Substring (0, current.IndexOf ('\\', current.IndexOfUnchecked ("\\\\", 0, current.Length) + 1));
414 path = CanonicalizePath (path);
416 // if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
417 if (IsDirectorySeparator (end) && (path [path.Length - 1] != DirectorySeparatorChar))
418 path += DirectorySeparatorChar;
423 internal static bool IsDirectorySeparator (char c) {
424 return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
427 public static string GetPathRoot (string path)
432 if (path.Trim ().Length == 0)
433 throw new ArgumentException ("The specified path is not of a legal form.");
435 if (!IsPathRooted (path))
438 if (DirectorySeparatorChar == '/') {
440 return IsDirectorySeparator (path [0]) ? DirectorySeparatorStr : String.Empty;
445 if (path.Length == 1 && IsDirectorySeparator (path [0]))
446 return DirectorySeparatorStr;
447 else if (path.Length < 2)
450 if (IsDirectorySeparator (path [0]) && IsDirectorySeparator (path[1])) {
451 // UNC: \\server or \\server\share
453 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
456 if (len < path.Length) {
458 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
461 return DirectorySeparatorStr +
462 DirectorySeparatorStr +
463 path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
464 } else if (IsDirectorySeparator (path [0])) {
465 // path starts with '\' or '/'
466 return DirectorySeparatorStr;
467 } else if (path[1] == VolumeSeparatorChar) {
469 if (path.Length >= 3 && (IsDirectorySeparator (path [2]))) len++;
471 return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
472 return path.Substring (0, len);
476 // FIXME: Further limit the assertion when imperative Assert is implemented
477 [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
478 public static string GetTempFileName ()
486 SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
489 var tmp_path = GetTempPath ();
493 path = Path.Combine (tmp_path, "tmp" + num.ToString ("x", CultureInfo.InvariantCulture) + ".tmp");
496 f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
497 8192, false, (FileOptions) 1);
498 } catch (IOException ex){
499 if (ex._HResult != MonoIO.FileAlreadyExistsHResult || count ++ > 65536)
501 } catch (UnauthorizedAccessException ex) {
502 if (count ++ > 65536)
503 throw new IOException (ex.Message, ex);
511 [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
512 public static string GetTempPath ()
514 SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
516 string p = get_temp_path ();
517 if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
518 return p + DirectorySeparatorChar;
523 [MethodImplAttribute(MethodImplOptions.InternalCall)]
524 private static extern string get_temp_path ();
526 public static bool HasExtension (string path)
528 if (path == null || path.Trim ().Length == 0)
531 if (path.IndexOfAny (InvalidPathChars) != -1)
532 throw new ArgumentException ("Illegal characters in path.");
534 int pos = findExtension (path);
535 return 0 <= pos && pos < path.Length - 1;
538 public static bool IsPathRooted (string path)
540 if (path == null || path.Length == 0)
543 if (path.IndexOfAny (InvalidPathChars) != -1)
544 throw new ArgumentException ("Illegal characters in path.");
547 return (c == DirectorySeparatorChar ||
548 c == AltDirectorySeparatorChar ||
549 (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
552 public static char[] GetInvalidFileNameChars ()
554 // return a new array as we do not want anyone to be able to change the values
555 if (Environment.IsRunningOnWindows) {
556 return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
557 '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
558 '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
559 '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
561 return new char [2] { '\x00', '/' };
565 public static char[] GetInvalidPathChars ()
567 // return a new array as we do not want anyone to be able to change the values
568 if (Environment.IsRunningOnWindows) {
569 return new char [36] { '\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
570 '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
571 '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
574 return new char [1] { '\x00' };
578 public static string GetRandomFileName ()
580 // returns a 8.3 filename (total size 12)
581 StringBuilder sb = new StringBuilder (12);
582 // using strong crypto but without creating the file
583 RandomNumberGenerator rng = RandomNumberGenerator.Create ();
584 byte [] buffer = new byte [11];
585 rng.GetBytes (buffer);
587 for (int i = 0; i < buffer.Length; i++) {
591 // restrict to length of range [a..z0..9]
592 int b = (buffer [i] % 36);
593 char c = (char) (b < 26 ? (b + 'a') : (b - 26 + '0'));
597 return sb.ToString ();
600 // private class methods
602 private static int findExtension (string path)
604 // method should return the index of the path extension
605 // start or -1 if no valid extension
607 int iLastDot = path.LastIndexOf ('.');
608 int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
610 if (iLastDot > iLastSep)
618 VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
619 DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
620 AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
622 PathSeparator = MonoIO.PathSeparator;
623 // this copy will be modifiable ("by design")
624 InvalidPathChars = GetInvalidPathChars ();
627 DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
628 PathSeparatorChars = new char [] {
629 DirectorySeparatorChar,
630 AltDirectorySeparatorChar,
634 dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
637 // returns the server and share part of a UNC. Assumes "path" is a UNC.
638 static string GetServerAndShare (string path)
641 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
643 if (len < path.Length) {
645 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
648 return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
651 // assumes Environment.IsRunningOnWindows == true
652 static bool SameRoot (string root, string path)
654 // compare root - if enough details are available
655 if ((root.Length < 2) || (path.Length < 2))
659 if (IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1])) {
660 if (!(IsDirectorySeparator (path[0]) && IsDirectorySeparator (path[1])))
663 string rootShare = GetServerAndShare (root);
664 string pathShare = GetServerAndShare (path);
666 return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
670 if (!root [0].Equals (path [0]))
672 // presence of the separator
673 if (path[1] != Path.VolumeSeparatorChar)
675 if ((root.Length > 2) && (path.Length > 2)) {
676 // but don't directory compare the directory separator
677 return (IsDirectorySeparator (root[2]) && IsDirectorySeparator (path[2]));
682 static string CanonicalizePath (string path)
684 // STEP 1: Check for empty string
687 if (Environment.IsRunningOnWindows)
690 if (path.Length == 0)
693 // STEP 2: Check to see if this is only a root
694 string root = Path.GetPathRoot (path);
695 // it will return '\' for path '\', while it should return 'c:\' or so.
696 // Note: commenting this out makes the need for the (target == 1...) check in step 5
697 //if (root == path) return path;
699 // STEP 3: split the directories, this gets rid of consecutative "/"'s
700 string[] dirs = path.Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
701 // STEP 4: Get rid of directories containing . and ..
704 bool isUnc = Environment.IsRunningOnWindows &&
705 root.Length > 2 && IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1]);
707 // Set an overwrite limit for UNC paths since '\' + server + share
708 // must not be eliminated by the '..' elimination algorithm.
709 int limit = isUnc ? 3 : 0;
711 for (int i = 0; i < dirs.Length; i++) {
712 // WIN32 path components must be trimmed
713 if (Environment.IsRunningOnWindows)
714 dirs[i] = dirs[i].TrimEnd ();
716 if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
718 else if (dirs[i] == "..") {
719 // don't overwrite path segments below the limit
723 dirs[target++] = dirs[i];
726 // STEP 5: Combine everything.
727 if (target == 0 || (target == 1 && dirs[0] == ""))
730 string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
731 if (Environment.IsRunningOnWindows) {
732 // append leading '\' of the UNC path that was lost in STEP 3.
734 ret = Path.DirectorySeparatorStr + ret;
736 if (!SameRoot (root, ret))
741 } else if (!IsDirectorySeparator (path[0]) && SameRoot (root, path)) {
742 if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
743 ret += Path.DirectorySeparatorChar;
746 string current = Directory.GetCurrentDirectory ();
747 if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
748 // DOS local file path
749 if (ret.Length == 0 || IsDirectorySeparator (ret[0]))
751 return current.Substring (0, 2) + ret;
752 } else if (IsDirectorySeparator (current[current.Length - 1]) && IsDirectorySeparator (ret[0]))
753 return current + ret.Substring (1);
755 return current + ret;
758 if (root != "" && ret.Length > 0 && ret [0] != '/')
765 // required for FileIOPermission (and most proibably reusable elsewhere too)
766 // both path MUST be "full paths"
767 static internal bool IsPathSubsetOf (string subset, string path)
769 if (subset.Length > path.Length)
772 // check that everything up to the last separator match
773 int slast = subset.LastIndexOfAny (PathSeparatorChars);
774 if (String.Compare (subset, 0, path, 0, slast) != 0)
778 // then check if the last segment is identical
779 int plast = path.IndexOfAny (PathSeparatorChars, slast);
780 if (plast >= slast) {
781 return String.Compare (subset, slast, path, slast, path.Length - plast) == 0;
783 if (subset.Length != path.Length)
786 return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
790 static string Combine (params string [] paths)
793 throw new ArgumentNullException ("paths");
796 var ret = new StringBuilder ();
797 int pathsLen = paths.Length;
801 foreach (var s in paths) {
803 throw new ArgumentNullException ("One of the paths contains a null value", "paths");
806 if (s.IndexOfAny (InvalidPathChars) != -1)
807 throw new ArgumentException ("Illegal characters in path.");
811 ret.Append (DirectorySeparatorStr);
815 if (IsPathRooted (s))
820 if (slen > 0 && pathsLen > 0) {
821 char p1end = s [slen - 1];
822 if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
827 return ret.ToString ();
831 static string Combine (string path1, string path2, string path3)
834 throw new ArgumentNullException ("path1");
837 throw new ArgumentNullException ("path2");
840 throw new ArgumentNullException ("path3");
842 return Combine (new string [] { path1, path2, path3 });
846 static string Combine (string path1, string path2, string path3, string path4)
849 throw new ArgumentNullException ("path1");
852 throw new ArgumentNullException ("path2");
855 throw new ArgumentNullException ("path3");
858 throw new ArgumentNullException ("path4");
860 return Combine (new string [] { path1, path2, path3, path4 });
863 internal static void Validate (string path)
865 Validate (path, "path");
868 internal static void Validate (string path, string parameterName)
871 throw new ArgumentNullException (parameterName);
872 if (String.IsNullOrWhiteSpace (path))
873 throw new ArgumentException (Locale.GetText ("Path is empty"));
874 if (path.IndexOfAny (Path.InvalidPathChars) != -1)
875 throw new ArgumentException (Locale.GetText ("Path contains invalid chars"));
876 if (Environment.IsRunningOnWindows) {
877 int idx = path.IndexOf (':');
878 if (idx >= 0 && idx != 1)
879 throw new ArgumentException (parameterName);
883 internal static string DirectorySeparatorCharAsString {
885 return DirectorySeparatorStr;