2010-04-06 Jb Evain <jbevain@novell.com>
[mcs.git] / class / corlib / System.IO / Path.cs
blob471d37c227fd58c3e47f2f543b4d2612c8103b33
1 //------------------------------------------------------------------------------
2 //
3 // System.IO.Path.cs
4 //
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 //
9 // Author: Jim Richardson, develop@wtfo-guru.com
10 // Dan Lewis (dihlewis@yahoo.co.uk)
11 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
12 // Ben Maurer (bmaurer@users.sourceforge.net)
13 // Sebastien Pouliot <sebastien@ximian.com>
14 // Created: Saturday, August 11, 2001
16 //------------------------------------------------------------------------------
19 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
21 // Permission is hereby granted, free of charge, to any person obtaining
22 // a copy of this software and associated documentation files (the
23 // "Software"), to deal in the Software without restriction, including
24 // without limitation the rights to use, copy, modify, merge, publish,
25 // distribute, sublicense, and/or sell copies of the Software, and to
26 // permit persons to whom the Software is furnished to do so, subject to
27 // the following conditions:
28 //
29 // The above copyright notice and this permission notice shall be
30 // included in all copies or substantial portions of the Software.
31 //
32 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
33 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
34 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
35 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
36 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
37 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
38 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41 using System.Globalization;
42 using System.Runtime.CompilerServices;
43 using System.Runtime.InteropServices;
44 using System.Security;
45 using System.Security.Cryptography;
46 using System.Security.Permissions;
47 using System.Text;
49 namespace System.IO {
51 [ComVisible (true)]
52 public static class Path {
54 [Obsolete ("see GetInvalidPathChars and GetInvalidFileNameChars methods.")]
55 public static readonly char[] InvalidPathChars;
56 public static readonly char AltDirectorySeparatorChar;
57 public static readonly char DirectorySeparatorChar;
58 public static readonly char PathSeparator;
59 internal static readonly string DirectorySeparatorStr;
60 public static readonly char VolumeSeparatorChar;
62 internal static readonly char[] PathSeparatorChars;
63 private static readonly bool dirEqualsVolume;
65 // class methods
66 public static string ChangeExtension (string path, string extension)
68 if (path == null)
69 return null;
71 if (path.IndexOfAny (InvalidPathChars) != -1)
72 throw new ArgumentException ("Illegal characters in path.");
74 int iExt = findExtension (path);
76 if (extension == null)
77 return iExt < 0 ? path : path.Substring (0, iExt);
78 else if (extension.Length == 0)
79 return iExt < 0 ? path + '.' : path.Substring (0, iExt + 1);
81 else if (path.Length != 0) {
82 if (extension.Length > 0 && extension [0] != '.')
83 extension = "." + extension;
84 } else
85 extension = String.Empty;
87 if (iExt < 0) {
88 return path + extension;
89 } else if (iExt > 0) {
90 string temp = path.Substring (0, iExt);
91 return temp + extension;
94 return extension;
97 public static string Combine (string path1, string path2)
99 if (path1 == null)
100 throw new ArgumentNullException ("path1");
102 if (path2 == null)
103 throw new ArgumentNullException ("path2");
105 if (path1.Length == 0)
106 return path2;
108 if (path2.Length == 0)
109 return path1;
111 if (path1.IndexOfAny (InvalidPathChars) != -1)
112 throw new ArgumentException ("Illegal characters in path.");
114 if (path2.IndexOfAny (InvalidPathChars) != -1)
115 throw new ArgumentException ("Illegal characters in path.");
117 //TODO???: UNC names
118 if (IsPathRooted (path2))
119 return path2;
121 char p1end = path1 [path1.Length - 1];
122 if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
123 return path1 + DirectorySeparatorStr + path2;
125 return path1 + path2;
129 // This routine:
130 // * Removes duplicat path separators from a string
131 // * If the string starts with \\, preserves the first two (hostname on Windows)
132 // * Removes the trailing path separator.
133 // * Returns the DirectorySeparatorChar for the single input DirectorySeparatorChar or AltDirectorySeparatorChar
135 // Unlike CanonicalizePath, this does not do any path resolution
136 // (which GetDirectoryName is not supposed to do).
138 internal static string CleanPath (string s)
140 int l = s.Length;
141 int sub = 0;
142 int start = 0;
144 // Host prefix?
145 char s0 = s [0];
146 if (l > 2 && s0 == '\\' && s [1] == '\\'){
147 start = 2;
150 // We are only left with root
151 if (l == 1 && (s0 == DirectorySeparatorChar || s0 == AltDirectorySeparatorChar))
152 return s;
154 // Cleanup
155 for (int i = start; i < l; i++){
156 char c = s [i];
158 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
159 continue;
160 if (i+1 == l)
161 sub++;
162 else {
163 c = s [i + 1];
164 if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar)
165 sub++;
169 if (sub == 0)
170 return s;
172 char [] copy = new char [l-sub];
173 if (start != 0){
174 copy [0] = '\\';
175 copy [1] = '\\';
177 for (int i = start, j = start; i < l && j < copy.Length; i++){
178 char c = s [i];
180 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar){
181 copy [j++] = c;
182 continue;
185 // For non-trailing cases.
186 if (j+1 != copy.Length){
187 copy [j++] = DirectorySeparatorChar;
188 for (;i < l-1; i++){
189 c = s [i+1];
190 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
191 break;
195 return new String (copy);
198 public static string GetDirectoryName (string path)
200 // LAMESPEC: For empty string MS docs say both
201 // return null AND throw exception. Seems .NET throws.
202 if (path == String.Empty)
203 throw new ArgumentException("Invalid path");
205 if (path == null || GetPathRoot (path) == path)
206 return null;
208 if (path.Trim ().Length == 0)
209 throw new ArgumentException ("Argument string consists of whitespace characters only.");
211 if (path.IndexOfAny (System.IO.Path.InvalidPathChars) > -1)
212 throw new ArgumentException ("Path contains invalid characters");
214 int nLast = path.LastIndexOfAny (PathSeparatorChars);
215 if (nLast == 0)
216 nLast++;
218 if (nLast > 0) {
219 string ret = path.Substring (0, nLast);
220 int l = ret.Length;
222 if (l >= 2 && DirectorySeparatorChar == '\\' && ret [l - 1] == VolumeSeparatorChar)
223 return ret + DirectorySeparatorChar;
224 else {
226 // Important: do not use CanonicalizePath here, use
227 // the custom CleanPath here, as this should not
228 // return absolute paths
230 return CleanPath (ret);
234 return String.Empty;
237 public static string GetExtension (string path)
239 if (path == null)
240 return null;
242 if (path.IndexOfAny (InvalidPathChars) != -1)
243 throw new ArgumentException ("Illegal characters in path.");
245 int iExt = findExtension (path);
247 if (iExt > -1)
249 if (iExt < path.Length - 1)
250 return path.Substring (iExt);
252 return string.Empty;
255 public static string GetFileName (string path)
257 if (path == null || path.Length == 0)
258 return path;
260 if (path.IndexOfAny (InvalidPathChars) != -1)
261 throw new ArgumentException ("Illegal characters in path.");
263 int nLast = path.LastIndexOfAny (PathSeparatorChars);
264 if (nLast >= 0)
265 return path.Substring (nLast + 1);
267 return path;
270 public static string GetFileNameWithoutExtension (string path)
272 return ChangeExtension (GetFileName (path), null);
275 public static string GetFullPath (string path)
277 string fullpath = InsecureGetFullPath (path);
278 #if !NET_2_1
279 if (SecurityManager.SecurityEnabled) {
280 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
282 #endif
283 return fullpath;
286 internal static string WindowsDriveAdjustment (string path)
288 // two special cases to consider when a drive is specified
289 if (path.Length < 2)
290 return path;
291 if ((path [1] != ':') || !Char.IsLetter (path [0]))
292 return path;
294 string current = Directory.GetCurrentDirectory ();
295 // first, only the drive is specified
296 if (path.Length == 2) {
297 // then if the current directory is on the same drive
298 if (current [0] == path [0])
299 path = current; // we return it
300 else
301 path += '\\';
302 } else if ((path [2] != Path.DirectorySeparatorChar) && (path [2] != Path.AltDirectorySeparatorChar)) {
303 // second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
304 // If the current directory is on the specified drive...
305 if (current [0] == path [0]) {
306 // then specified directory is appended to the current drive directory
307 path = Path.Combine (current, path.Substring (2, path.Length - 2));
308 } else {
309 // if not, then just pretend there was a separator (Path.Combine won't work in this case)
310 path = String.Concat (path.Substring (0, 2), DirectorySeparatorStr, path.Substring (2, path.Length - 2));
313 return path;
316 // insecure - do not call directly
317 internal static string InsecureGetFullPath (string path)
319 if (path == null)
320 throw new ArgumentNullException ("path");
322 if (path.Trim ().Length == 0) {
323 string msg = Locale.GetText ("The specified path is not of a legal form (empty).");
324 throw new ArgumentException (msg);
327 // adjust for drives, i.e. a special case for windows
328 if (Environment.IsRunningOnWindows)
329 path = WindowsDriveAdjustment (path);
331 // if the supplied path ends with a separator...
332 char end = path [path.Length - 1];
334 var canonicalize = true;
335 if (path.Length >= 2 &&
336 IsDsc (path [0]) &&
337 IsDsc (path [1])) {
338 if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
339 throw new ArgumentException ("UNC pass should be of the form \\\\server\\share.");
341 if (path [0] != DirectorySeparatorChar)
342 path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
344 } else {
345 if (!IsPathRooted (path)) {
347 // avoid calling expensive CanonicalizePath when possible
348 var start = 0;
349 while ((start = path.IndexOf ('.', start)) != -1) {
350 if (++start == path.Length || path [start] == DirectorySeparatorChar || path [start] == AltDirectorySeparatorChar)
351 break;
353 canonicalize = start > 0;
355 path = Directory.GetCurrentDirectory () + DirectorySeparatorStr + path;
356 } else if (DirectorySeparatorChar == '\\' &&
357 path.Length >= 2 &&
358 IsDsc (path [0]) &&
359 !IsDsc (path [1])) { // like `\abc\def'
360 string current = Directory.GetCurrentDirectory ();
361 if (current [1] == VolumeSeparatorChar)
362 path = current.Substring (0, 2) + path;
363 else
364 path = current.Substring (0, current.IndexOf ('\\', current.IndexOf ("\\\\") + 1));
368 if (canonicalize)
369 path = CanonicalizePath (path);
371 // if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
372 if (IsDsc (end) && (path [path.Length - 1] != DirectorySeparatorChar))
373 path += DirectorySeparatorChar;
375 return path;
378 static bool IsDsc (char c) {
379 return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
382 public static string GetPathRoot (string path)
384 if (path == null)
385 return null;
387 if (path.Trim ().Length == 0)
388 throw new ArgumentException ("The specified path is not of a legal form.");
390 if (!IsPathRooted (path))
391 return String.Empty;
393 if (DirectorySeparatorChar == '/') {
394 // UNIX
395 return IsDsc (path [0]) ? DirectorySeparatorStr : String.Empty;
396 } else {
397 // Windows
398 int len = 2;
400 if (path.Length == 1 && IsDsc (path [0]))
401 return DirectorySeparatorStr;
402 else if (path.Length < 2)
403 return String.Empty;
405 if (IsDsc (path [0]) && IsDsc (path[1])) {
406 // UNC: \\server or \\server\share
407 // Get server
408 while (len < path.Length && !IsDsc (path [len])) len++;
410 // Get share
411 if (len < path.Length) {
412 len++;
413 while (len < path.Length && !IsDsc (path [len])) len++;
416 return DirectorySeparatorStr +
417 DirectorySeparatorStr +
418 path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
419 } else if (IsDsc (path [0])) {
420 // path starts with '\' or '/'
421 return DirectorySeparatorStr;
422 } else if (path[1] == VolumeSeparatorChar) {
423 // C:\folder
424 if (path.Length >= 3 && (IsDsc (path [2]))) len++;
425 } else
426 return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
427 return path.Substring (0, len);
431 // FIXME: Further limit the assertion when imperative Assert is implemented
432 [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
433 public static string GetTempFileName ()
435 FileStream f = null;
436 string path;
437 Random rnd;
438 int num = 0;
440 rnd = new Random ();
441 do {
442 num = rnd.Next ();
443 num++;
444 path = Path.Combine (GetTempPath(), "tmp" + num.ToString("x") + ".tmp");
446 try {
447 f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
448 8192, false, (FileOptions) 1);
450 catch (SecurityException) {
451 // avoid an endless loop
452 throw;
454 catch (UnauthorizedAccessException) {
455 // This can happen if we don't have write permission to /tmp
456 throw;
458 catch {
460 } while (f == null);
462 f.Close();
463 return path;
466 [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
467 public static string GetTempPath ()
469 string p = get_temp_path ();
470 if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
471 return p + DirectorySeparatorChar;
473 return p;
476 [MethodImplAttribute(MethodImplOptions.InternalCall)]
477 private static extern string get_temp_path ();
479 public static bool HasExtension (string path)
481 if (path == null || path.Trim ().Length == 0)
482 return false;
484 if (path.IndexOfAny (InvalidPathChars) != -1)
485 throw new ArgumentException ("Illegal characters in path.");
487 int pos = findExtension (path);
488 return 0 <= pos && pos < path.Length - 1;
491 public static bool IsPathRooted (string path)
493 if (path == null || path.Length == 0)
494 return false;
496 if (path.IndexOfAny (InvalidPathChars) != -1)
497 throw new ArgumentException ("Illegal characters in path.");
499 char c = path [0];
500 return (c == DirectorySeparatorChar ||
501 c == AltDirectorySeparatorChar ||
502 (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
505 public static char[] GetInvalidFileNameChars ()
507 // return a new array as we do not want anyone to be able to change the values
508 if (Environment.IsRunningOnWindows) {
509 return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
510 '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
511 '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
512 '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
513 } else {
514 return new char [2] { '\x00', '/' };
518 public static char[] GetInvalidPathChars ()
520 // return a new array as we do not want anyone to be able to change the values
521 if (Environment.IsRunningOnWindows) {
522 return new char [36] { '\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
523 '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
524 '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
525 '\x1E', '\x1F' };
526 } else {
527 return new char [1] { '\x00' };
531 public static string GetRandomFileName ()
533 // returns a 8.3 filename (total size 12)
534 StringBuilder sb = new StringBuilder (12);
535 // using strong crypto but without creating the file
536 RandomNumberGenerator rng = RandomNumberGenerator.Create ();
537 byte [] buffer = new byte [11];
538 rng.GetBytes (buffer);
540 for (int i = 0; i < buffer.Length; i++) {
541 if (sb.Length == 8)
542 sb.Append ('.');
544 // restrict to length of range [a..z0..9]
545 int b = (buffer [i] % 36);
546 char c = (char) (b < 26 ? (b + 'a') : (b - 26 + '0'));
547 sb.Append (c);
550 return sb.ToString ();
553 // private class methods
555 private static int findExtension (string path)
557 // method should return the index of the path extension
558 // start or -1 if no valid extension
559 if (path != null){
560 int iLastDot = path.LastIndexOf ('.');
561 int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
563 if (iLastDot > iLastSep)
564 return iLastDot;
566 return -1;
569 static Path ()
571 VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
572 DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
573 AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
575 PathSeparator = MonoIO.PathSeparator;
576 // this copy will be modifiable ("by design")
577 InvalidPathChars = GetInvalidPathChars ();
578 // internal fields
580 DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
581 PathSeparatorChars = new char [] {
582 DirectorySeparatorChar,
583 AltDirectorySeparatorChar,
584 VolumeSeparatorChar
587 dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
590 // returns the server and share part of a UNC. Assumes "path" is a UNC.
591 static string GetServerAndShare (string path)
593 int len = 2;
594 while (len < path.Length && !IsDsc (path [len])) len++;
596 if (len < path.Length) {
597 len++;
598 while (len < path.Length && !IsDsc (path [len])) len++;
601 return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
604 // assumes Environment.IsRunningOnWindows == true
605 static bool SameRoot (string root, string path)
607 // compare root - if enough details are available
608 if ((root.Length < 2) || (path.Length < 2))
609 return false;
611 // UNC handling
612 if (IsDsc (root[0]) && IsDsc (root[1])) {
613 if (!(IsDsc (path[0]) && IsDsc (path[1])))
614 return false;
616 string rootShare = GetServerAndShare (root);
617 string pathShare = GetServerAndShare (path);
619 return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
622 // same volume/drive
623 if (!root [0].Equals (path [0]))
624 return false;
625 // presence of the separator
626 if (path[1] != Path.VolumeSeparatorChar)
627 return false;
628 if ((root.Length > 2) && (path.Length > 2)) {
629 // but don't directory compare the directory separator
630 return (IsDsc (root[2]) && IsDsc (path[2]));
632 return true;
635 static string CanonicalizePath (string path)
637 // STEP 1: Check for empty string
638 if (path == null)
639 return path;
640 if (Environment.IsRunningOnWindows)
641 path = path.Trim ();
643 if (path.Length == 0)
644 return path;
646 // STEP 2: Check to see if this is only a root
647 string root = Path.GetPathRoot (path);
648 // it will return '\' for path '\', while it should return 'c:\' or so.
649 // Note: commenting this out makes the need for the (target == 1...) check in step 5
650 //if (root == path) return path;
652 // STEP 3: split the directories, this gets rid of consecutative "/"'s
653 string[] dirs = path.Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
654 // STEP 4: Get rid of directories containing . and ..
655 int target = 0;
657 bool isUnc = Environment.IsRunningOnWindows &&
658 root.Length > 2 && IsDsc (root[0]) && IsDsc (root[1]);
660 // Set an overwrite limit for UNC paths since '\' + server + share
661 // must not be eliminated by the '..' elimination algorithm.
662 int limit = isUnc ? 3 : 0;
664 for (int i = 0; i < dirs.Length; i++) {
665 // WIN32 path components must be trimmed
666 if (Environment.IsRunningOnWindows)
667 dirs[i] = dirs[i].TrimEnd ();
669 if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
670 continue;
671 else if (dirs[i] == "..") {
672 // don't overwrite path segments below the limit
673 if (target > limit)
674 target--;
675 } else
676 dirs[target++] = dirs[i];
679 // STEP 5: Combine everything.
680 if (target == 0 || (target == 1 && dirs[0] == ""))
681 return root;
682 else {
683 string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
684 if (Environment.IsRunningOnWindows) {
685 // append leading '\' of the UNC path that was lost in STEP 3.
686 if (isUnc)
687 ret = Path.DirectorySeparatorStr + ret;
689 if (!SameRoot (root, ret))
690 ret = root + ret;
692 if (isUnc) {
693 return ret;
694 } else if (!IsDsc (path[0]) && SameRoot (root, path)) {
695 if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
696 ret += Path.DirectorySeparatorChar;
697 return ret;
698 } else {
699 string current = Directory.GetCurrentDirectory ();
700 if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
701 // DOS local file path
702 if (ret.Length == 0 || IsDsc (ret[0]))
703 ret += '\\';
704 return current.Substring (0, 2) + ret;
705 } else if (IsDsc (current[current.Length - 1]) && IsDsc (ret[0]))
706 return current + ret.Substring (1);
707 else
708 return current + ret;
711 return ret;
715 // required for FileIOPermission (and most proibably reusable elsewhere too)
716 // both path MUST be "full paths"
717 static internal bool IsPathSubsetOf (string subset, string path)
719 if (subset.Length > path.Length)
720 return false;
722 // check that everything up to the last separator match
723 int slast = subset.LastIndexOfAny (PathSeparatorChars);
724 if (String.Compare (subset, 0, path, 0, slast) != 0)
725 return false;
727 slast++;
728 // then check if the last segment is identical
729 int plast = path.IndexOfAny (PathSeparatorChars, slast);
730 if (plast >= slast) {
731 return String.Compare (subset, slast, path, slast, path.Length - plast) == 0;
733 if (subset.Length != path.Length)
734 return false;
736 return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
739 #if NET_4_0 || MOONLIGHT
740 public static string Combine (params string [] paths)
742 if (paths == null)
743 throw new ArgumentNullException ("paths");
745 int l = 0;
746 bool need_sep = false;
747 foreach (var s in paths){
748 if (s == null)
749 throw new ArgumentNullException ("One of the paths contains a null value", "paths");
750 if (s.IndexOfAny (InvalidPathChars) != -1)
751 throw new ArgumentException ("Illegal characters in path.");
752 if (l == 0 && s.Length > 0){
753 char p1end = s [s.Length - 1];
754 if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar){
755 need_sep = true;
756 l += DirectorySeparatorStr.Length;
760 var ret = new StringBuilder (l);
761 l = 0;
762 foreach (var s in paths){
763 if (IsPathRooted (s))
764 ret.Length = l = 0;
765 ret.Append (s);
766 if (l == 0 && need_sep)
767 ret.Append (DirectorySeparatorStr);
768 l = 1;
771 return ret.ToString ();
774 public static string Combine (string path1, string path2, string path3)
776 return Combine (new string [] { path1, path2, path3 });
779 public static string Combine (string path1, string path2, string path3, string path4)
781 return Combine (new string [] { path1, path2, path3, path4 });
783 #endif