xbuild: use the AdditionalReferencePath items to locate assemblies
[mono.git] / mcs / class / corlib / System.IO / Path.cs
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 // Copyright 2011 Xamarin Inc (http://www.xamarin.com).
9 // 
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 
16 //
17 //------------------------------------------------------------------------------
18
19 //
20 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
21 //
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:
29 // 
30 // The above copyright notice and this permission notice shall be
31 // included in all copies or substantial portions of the Software.
32 // 
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.
40 //
41
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;
48 using System.Text;
49
50 namespace System.IO {
51
52         [ComVisible (true)]
53         public static class Path {
54
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;
62
63                 internal static readonly char[] PathSeparatorChars;
64                 private static readonly bool dirEqualsVolume;
65
66                 // class methods
67                 public static string ChangeExtension (string path, string extension)
68                 {
69                         if (path == null)
70                                 return null;
71
72                         if (path.IndexOfAny (InvalidPathChars) != -1)
73                                 throw new ArgumentException ("Illegal characters in path.");
74
75                         int iExt = findExtension (path);
76
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);
81
82                         else if (path.Length != 0) {
83                                 if (extension.Length > 0 && extension [0] != '.')
84                                         extension = "." + extension;
85                         } else
86                                 extension = String.Empty;
87                         
88                         if (iExt < 0) {
89                                 return path + extension;
90                         } else if (iExt > 0) {
91                                 string temp = path.Substring (0, iExt);
92                                 return temp + extension;
93                         }
94
95                         return extension;
96                 }
97
98                 public static string Combine (string path1, string path2)
99                 {
100                         if (path1 == null)
101                                 throw new ArgumentNullException ("path1");
102
103                         if (path2 == null)
104                                 throw new ArgumentNullException ("path2");
105
106                         if (path1.Length == 0)
107                                 return path2;
108
109                         if (path2.Length == 0)
110                                 return path1;
111
112                         if (path1.IndexOfAny (InvalidPathChars) != -1)
113                                 throw new ArgumentException ("Illegal characters in path.");
114
115                         if (path2.IndexOfAny (InvalidPathChars) != -1)
116                                 throw new ArgumentException ("Illegal characters in path.");
117
118                         //TODO???: UNC names
119                         if (IsPathRooted (path2))
120                                 return path2;
121                         
122                         char p1end = path1 [path1.Length - 1];
123                         if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
124                                 return path1 + DirectorySeparatorStr + path2;
125
126                         return path1 + path2;
127                 }
128         
129                 //
130                 // This routine:
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
135                 //
136                 // Unlike CanonicalizePath, this does not do any path resolution
137                 // (which GetDirectoryName is not supposed to do).
138                 //
139                 internal static string CleanPath (string s)
140                 {
141                         int l = s.Length;
142                         int sub = 0;
143                         int start = 0;
144
145                         // Host prefix?
146                         char s0 = s [0];
147                         if (l > 2 && s0 == '\\' && s [1] == '\\'){
148                                 start = 2;
149                         }
150
151                         // We are only left with root
152                         if (l == 1 && (s0 == DirectorySeparatorChar || s0 == AltDirectorySeparatorChar))
153                                 return s;
154
155                         // Cleanup
156                         for (int i = start; i < l; i++){
157                                 char c = s [i];
158                                 
159                                 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
160                                         continue;
161                                 if (i+1 == l)
162                                         sub++;
163                                 else {
164                                         c = s [i + 1];
165                                         if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar)
166                                                 sub++;
167                                 }
168                         }
169
170                         if (sub == 0)
171                                 return s;
172
173                         char [] copy = new char [l-sub];
174                         if (start != 0){
175                                 copy [0] = '\\';
176                                 copy [1] = '\\';
177                         }
178                         for (int i = start, j = start; i < l && j < copy.Length; i++){
179                                 char c = s [i];
180
181                                 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar){
182                                         copy [j++] = c;
183                                         continue;
184                                 }
185
186                                 // For non-trailing cases.
187                                 if (j+1 != copy.Length){
188                                         copy [j++] = DirectorySeparatorChar;
189                                         for (;i < l-1; i++){
190                                                 c = s [i+1];
191                                                 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
192                                                         break;
193                                         }
194                                 }
195                         }
196                         return new String (copy);
197                 }
198
199                 public static string GetDirectoryName (string path)
200                 {
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");
205
206                         if (path == null || GetPathRoot (path) == path)
207                                 return null;
208
209                         if (path.Trim ().Length == 0)
210                                 throw new ArgumentException ("Argument string consists of whitespace characters only.");
211
212                         if (path.IndexOfAny (System.IO.Path.InvalidPathChars) > -1)
213                                 throw new ArgumentException ("Path contains invalid characters");
214
215                         int nLast = path.LastIndexOfAny (PathSeparatorChars);
216                         if (nLast == 0)
217                                 nLast++;
218
219                         if (nLast > 0) {
220                                 string ret = path.Substring (0, nLast);
221                                 int l = ret.Length;
222
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;
227                                 else {
228                                         //
229                                         // Important: do not use CanonicalizePath here, use
230                                         // the custom CleanPath here, as this should not
231                                         // return absolute paths
232                                         //
233                                         return CleanPath (ret);
234                                 }
235                         }
236
237                         return String.Empty;
238                 }
239
240                 public static string GetExtension (string path)
241                 {
242                         if (path == null)
243                                 return null;
244
245                         if (path.IndexOfAny (InvalidPathChars) != -1)
246                                 throw new ArgumentException ("Illegal characters in path.");
247
248                         int iExt = findExtension (path);
249
250                         if (iExt > -1)
251                         {
252                                 if (iExt < path.Length - 1)
253                                         return path.Substring (iExt);
254                         }
255                         return string.Empty;
256                 }
257
258                 public static string GetFileName (string path)
259                 {
260                         if (path == null || path.Length == 0)
261                                 return path;
262
263                         if (path.IndexOfAny (InvalidPathChars) != -1)
264                                 throw new ArgumentException ("Illegal characters in path.");
265
266                         int nLast = path.LastIndexOfAny (PathSeparatorChars);
267                         if (nLast >= 0)
268                                 return path.Substring (nLast + 1);
269
270                         return path;
271                 }
272
273                 public static string GetFileNameWithoutExtension (string path)
274                 {
275                         return ChangeExtension (GetFileName (path), null);
276                 }
277
278                 public static string GetFullPath (string path)
279                 {
280                         string fullpath = InsecureGetFullPath (path);
281
282                         SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
283
284 #if !NET_2_1
285                         if (SecurityManager.SecurityEnabled) {
286                                 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
287                         }
288 #endif
289                         return fullpath;
290                 }
291
292                 internal static string WindowsDriveAdjustment (string path)
293                 {
294                         // two special cases to consider when a drive is specified
295                         if (path.Length < 2)
296                                 return path;
297                         if ((path [1] != ':') || !Char.IsLetter (path [0]))
298                                 return path;
299
300                         string current = Directory.InsecureGetCurrentDirectory ();
301                         // first, only the drive is specified
302                         if (path.Length == 2) {
303                                 // then if the current directory is on the same drive
304                                 if (current [0] == path [0])
305                                         path = current; // we return it
306                                 else
307                                         path += '\\';
308                         } else if ((path [2] != Path.DirectorySeparatorChar) && (path [2] != Path.AltDirectorySeparatorChar)) {
309                                 // second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
310                                 // If the current directory is on the specified drive...
311                                 if (current [0] == path [0]) {
312                                         // then specified directory is appended to the current drive directory
313                                         path = Path.Combine (current, path.Substring (2, path.Length - 2));
314                                 } else {
315                                         // if not, then just pretend there was a separator (Path.Combine won't work in this case)
316                                         path = String.Concat (path.Substring (0, 2), DirectorySeparatorStr, path.Substring (2, path.Length - 2));
317                                 }
318                         }
319                         return path;
320                 }
321
322                 // insecure - do not call directly
323                 internal static string InsecureGetFullPath (string path)
324                 {
325                         if (path == null)
326                                 throw new ArgumentNullException ("path");
327
328                         if (path.Trim ().Length == 0) {
329                                 string msg = Locale.GetText ("The specified path is not of a legal form (empty).");
330                                 throw new ArgumentException (msg);
331                         }
332
333                         // adjust for drives, i.e. a special case for windows
334                         if (Environment.IsRunningOnWindows)
335                                 path = WindowsDriveAdjustment (path);
336
337                         // if the supplied path ends with a separator...
338                         char end = path [path.Length - 1];
339
340                         var canonicalize = true;
341                         if (path.Length >= 2 &&
342                                 IsDsc (path [0]) &&
343                                 IsDsc (path [1])) {
344                                 if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
345                                         throw new ArgumentException ("UNC paths should be of the form \\\\server\\share.");
346
347                                 if (path [0] != DirectorySeparatorChar)
348                                         path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
349
350                         } else {
351                                 if (!IsPathRooted (path)) {
352                                         
353                                         // avoid calling expensive CanonicalizePath when possible
354                                         if (!Environment.IsRunningOnWindows) {
355                                                 var start = 0;
356                                                 while ((start = path.IndexOf ('.', start)) != -1) {
357                                                         if (++start == path.Length || path [start] == DirectorySeparatorChar || path [start] == AltDirectorySeparatorChar)
358                                                                 break;
359                                                 }
360                                                 canonicalize = start > 0;
361                                         }
362
363                                         path = Directory.InsecureGetCurrentDirectory() + DirectorySeparatorStr + path;
364                                 } else if (DirectorySeparatorChar == '\\' &&
365                                         path.Length >= 2 &&
366                                         IsDsc (path [0]) &&
367                                         !IsDsc (path [1])) { // like `\abc\def'
368                                         string current = Directory.InsecureGetCurrentDirectory();
369                                         if (current [1] == VolumeSeparatorChar)
370                                                 path = current.Substring (0, 2) + path;
371                                         else
372                                                 path = current.Substring (0, current.IndexOf ('\\', current.IndexOfOrdinalUnchecked ("\\\\") + 1));
373                                 }
374                         }
375                         
376                         if (canonicalize)
377                             path = CanonicalizePath (path);
378
379                         // if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
380                         if (IsDsc (end) && (path [path.Length - 1] != DirectorySeparatorChar))
381                                 path += DirectorySeparatorChar;
382
383                         return path;
384                 }
385
386                 static bool IsDsc (char c) {
387                         return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
388                 }
389                 
390                 public static string GetPathRoot (string path)
391                 {
392                         if (path == null)
393                                 return null;
394
395                         if (path.Trim ().Length == 0)
396                                 throw new ArgumentException ("The specified path is not of a legal form.");
397
398                         if (!IsPathRooted (path))
399                                 return String.Empty;
400                         
401                         if (DirectorySeparatorChar == '/') {
402                                 // UNIX
403                                 return IsDsc (path [0]) ? DirectorySeparatorStr : String.Empty;
404                         } else {
405                                 // Windows
406                                 int len = 2;
407
408                                 if (path.Length == 1 && IsDsc (path [0]))
409                                         return DirectorySeparatorStr;
410                                 else if (path.Length < 2)
411                                         return String.Empty;
412
413                                 if (IsDsc (path [0]) && IsDsc (path[1])) {
414                                         // UNC: \\server or \\server\share
415                                         // Get server
416                                         while (len < path.Length && !IsDsc (path [len])) len++;
417
418                                         // Get share
419                                         if (len < path.Length) {
420                                                 len++;
421                                                 while (len < path.Length && !IsDsc (path [len])) len++;
422                                         }
423
424                                         return DirectorySeparatorStr +
425                                                 DirectorySeparatorStr +
426                                                 path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
427                                 } else if (IsDsc (path [0])) {
428                                         // path starts with '\' or '/'
429                                         return DirectorySeparatorStr;
430                                 } else if (path[1] == VolumeSeparatorChar) {
431                                         // C:\folder
432                                         if (path.Length >= 3 && (IsDsc (path [2]))) len++;
433                                 } else
434                                         return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
435                                 return path.Substring (0, len);
436                         }
437                 }
438
439                 // FIXME: Further limit the assertion when imperative Assert is implemented
440                 [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
441                 public static string GetTempFileName ()
442                 {
443                         FileStream f = null;
444                         string path;
445                         Random rnd;
446                         int num = 0;
447                         int count = 0;
448
449                         SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
450
451                         rnd = new Random ();
452                         do {
453                                 num = rnd.Next ();
454                                 num++;
455                                 path = Path.Combine (GetTempPath(), "tmp" + num.ToString("x") + ".tmp");
456
457                                 try {
458                                         f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
459                                                             8192, false, (FileOptions) 1);
460                                 }
461                                 catch (IOException ex){
462                                         if (ex.hresult != MonoIO.FileAlreadyExistsHResult || count ++ > 65536)
463                                                 throw;
464                                 }
465                         } while (f == null);
466                         
467                         f.Close();
468                         return path;
469                 }
470
471                 [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
472                 public static string GetTempPath ()
473                 {
474                         SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
475
476                         string p = get_temp_path ();
477                         if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
478                                 return p + DirectorySeparatorChar;
479
480                         return p;
481                 }
482
483                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
484                 private static extern string get_temp_path ();
485
486                 public static bool HasExtension (string path)
487                 {
488                         if (path == null || path.Trim ().Length == 0)
489                                 return false;
490
491                         if (path.IndexOfAny (InvalidPathChars) != -1)
492                                 throw new ArgumentException ("Illegal characters in path.");
493
494                         int pos = findExtension (path);
495                         return 0 <= pos && pos < path.Length - 1;
496                 }
497
498                 public static bool IsPathRooted (string path)
499                 {
500                         if (path == null || path.Length == 0)
501                                 return false;
502
503                         if (path.IndexOfAny (InvalidPathChars) != -1)
504                                 throw new ArgumentException ("Illegal characters in path.");
505
506                         char c = path [0];
507                         return (c == DirectorySeparatorChar     ||
508                                 c == AltDirectorySeparatorChar  ||
509                                 (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
510                 }
511
512                 public static char[] GetInvalidFileNameChars ()
513                 {
514                         // return a new array as we do not want anyone to be able to change the values
515                         if (Environment.IsRunningOnWindows) {
516                                 return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
517                                         '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12', 
518                                         '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', 
519                                         '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
520                         } else {
521                                 return new char [2] { '\x00', '/' };
522                         }
523                 }
524
525                 public static char[] GetInvalidPathChars ()
526                 {
527                         // return a new array as we do not want anyone to be able to change the values
528                         if (Environment.IsRunningOnWindows) {
529                                 return new char [36] { '\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
530                                         '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12', 
531                                         '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', 
532                                         '\x1E', '\x1F' };
533                         } else {
534                                 return new char [1] { '\x00' };
535                         }
536                 }
537
538                 public static string GetRandomFileName ()
539                 {
540                         // returns a 8.3 filename (total size 12)
541                         StringBuilder sb = new StringBuilder (12);
542                         // using strong crypto but without creating the file
543                         RandomNumberGenerator rng = RandomNumberGenerator.Create ();
544                         byte [] buffer = new byte [11];
545                         rng.GetBytes (buffer);
546
547                         for (int i = 0; i < buffer.Length; i++) {
548                                 if (sb.Length == 8)
549                                         sb.Append ('.');
550
551                                 // restrict to length of range [a..z0..9]
552                                 int b = (buffer [i] % 36);
553                                 char c = (char) (b < 26 ? (b + 'a') : (b - 26 + '0'));
554                                 sb.Append (c);
555                         }
556
557                         return sb.ToString ();
558                 }
559
560                 // private class methods
561
562                 private static int findExtension (string path)
563                 {
564                         // method should return the index of the path extension
565                         // start or -1 if no valid extension
566                         if (path != null){
567                                 int iLastDot = path.LastIndexOf ('.');
568                                 int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
569
570                                 if (iLastDot > iLastSep)
571                                         return iLastDot;
572                         }
573                         return -1;
574                 }
575
576                 static Path ()
577                 {
578                         VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
579                         DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
580                         AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
581
582                         PathSeparator = MonoIO.PathSeparator;
583                         // this copy will be modifiable ("by design")
584                         InvalidPathChars = GetInvalidPathChars ();
585                         // internal fields
586
587                         DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
588                         PathSeparatorChars = new char [] {
589                                 DirectorySeparatorChar,
590                                 AltDirectorySeparatorChar,
591                                 VolumeSeparatorChar
592                         };
593
594                         dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
595                 }
596
597                 // returns the server and share part of a UNC. Assumes "path" is a UNC.
598                 static string GetServerAndShare (string path)
599                 {
600                         int len = 2;
601                         while (len < path.Length && !IsDsc (path [len])) len++;
602
603                         if (len < path.Length) {
604                                 len++;
605                                 while (len < path.Length && !IsDsc (path [len])) len++;
606                         }
607
608                         return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
609                 }
610
611                 // assumes Environment.IsRunningOnWindows == true
612                 static bool SameRoot (string root, string path)
613                 {
614                         // compare root - if enough details are available
615                         if ((root.Length < 2) || (path.Length < 2))
616                                 return false;
617
618                         // UNC handling
619                         if (IsDsc (root[0]) && IsDsc (root[1])) {
620                                 if (!(IsDsc (path[0]) && IsDsc (path[1])))
621                                         return false;
622
623                                 string rootShare = GetServerAndShare (root);
624                                 string pathShare = GetServerAndShare (path);
625
626                                 return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
627                         }
628                         
629                         // same volume/drive
630                         if (!root [0].Equals (path [0]))
631                                 return false;
632                         // presence of the separator
633                         if (path[1] != Path.VolumeSeparatorChar)
634                                 return false;
635                         if ((root.Length > 2) && (path.Length > 2)) {
636                                 // but don't directory compare the directory separator
637                                 return (IsDsc (root[2]) && IsDsc (path[2]));
638                         }
639                         return true;
640                 }
641
642                 static string CanonicalizePath (string path)
643                 {
644                         // STEP 1: Check for empty string
645                         if (path == null)
646                                 return path;
647                         if (Environment.IsRunningOnWindows)
648                                 path = path.Trim ();
649
650                         if (path.Length == 0)
651                                 return path;
652
653                         // STEP 2: Check to see if this is only a root
654                         string root = Path.GetPathRoot (path);
655                         // it will return '\' for path '\', while it should return 'c:\' or so.
656                         // Note: commenting this out makes the need for the (target == 1...) check in step 5
657                         //if (root == path) return path;
658
659                         // STEP 3: split the directories, this gets rid of consecutative "/"'s
660                         string[] dirs = path.Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
661                         // STEP 4: Get rid of directories containing . and ..
662                         int target = 0;
663
664                         bool isUnc = Environment.IsRunningOnWindows &&
665                                 root.Length > 2 && IsDsc (root[0]) && IsDsc (root[1]);
666
667                         // Set an overwrite limit for UNC paths since '\' + server + share
668                         // must not be eliminated by the '..' elimination algorithm.
669                         int limit = isUnc ? 3 : 0;
670
671                         for (int i = 0; i < dirs.Length; i++) {
672                                 // WIN32 path components must be trimmed
673                                 if (Environment.IsRunningOnWindows)
674                                         dirs[i] = dirs[i].TrimEnd ();
675                                 
676                                 if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
677                                         continue;
678                                 else if (dirs[i] == "..") {
679                                         // don't overwrite path segments below the limit
680                                         if (target > limit)
681                                                 target--;
682                                 } else
683                                         dirs[target++] = dirs[i];
684                         }
685
686                         // STEP 5: Combine everything.
687                         if (target == 0 || (target == 1 && dirs[0] == ""))
688                                 return root;
689                         else {
690                                 string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
691                                 if (Environment.IsRunningOnWindows) {
692                                         // append leading '\' of the UNC path that was lost in STEP 3.
693                                         if (isUnc)
694                                                 ret = Path.DirectorySeparatorStr + ret;
695
696                                         if (!SameRoot (root, ret))
697                                                 ret = root + ret;
698
699                                         if (isUnc) {
700                                                 return ret;
701                                         } else if (!IsDsc (path[0]) && SameRoot (root, path)) {
702                                                 if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
703                                                         ret += Path.DirectorySeparatorChar;
704                                                 return ret;
705                                         } else {
706                                                 string current = Directory.GetCurrentDirectory ();
707                                                 if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
708                                                         // DOS local file path
709                                                         if (ret.Length == 0 || IsDsc (ret[0]))
710                                                                 ret += '\\';
711                                                         return current.Substring (0, 2) + ret;
712                                                 } else if (IsDsc (current[current.Length - 1]) && IsDsc (ret[0]))
713                                                         return current + ret.Substring (1);
714                                                 else
715                                                         return current + ret;
716                                         }
717                                 }
718                                 return ret;
719                         }
720                 }
721
722                 // required for FileIOPermission (and most proibably reusable elsewhere too)
723                 // both path MUST be "full paths"
724                 static internal bool IsPathSubsetOf (string subset, string path)
725                 {
726                         if (subset.Length > path.Length)
727                                 return false;
728
729                         // check that everything up to the last separator match
730                         int slast = subset.LastIndexOfAny (PathSeparatorChars);
731                         if (String.Compare (subset, 0, path, 0, slast) != 0)
732                                 return false;
733
734                         slast++;
735                         // then check if the last segment is identical
736                         int plast = path.IndexOfAny (PathSeparatorChars, slast);
737                         if (plast >= slast) {
738                                 return String.Compare (subset, slast, path, slast, path.Length - plast) == 0;
739                         }
740                         if (subset.Length != path.Length)
741                                 return false;
742
743                         return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
744                 }
745
746 #if NET_4_0
747                 public
748 #else
749                 internal
750 #endif
751                 static string Combine (params string [] paths)
752                 {
753                         if (paths == null)
754                                 throw new ArgumentNullException ("paths");
755
756                         bool need_sep;
757                         var ret = new StringBuilder ();
758                         int pathsLen = paths.Length;
759                         int slen;
760                         foreach (var s in paths) {
761                                 need_sep = false;
762                                 if (s == null)
763                                         throw new ArgumentNullException ("One of the paths contains a null value", "paths");
764                                 if (s.IndexOfAny (InvalidPathChars) != -1)
765                                         throw new ArgumentException ("Illegal characters in path.");
766                                 
767                                 pathsLen--;
768                                 if (IsPathRooted (s))
769                                         ret.Length = 0;
770                                 
771                                 ret.Append (s);
772                                 slen = s.Length;
773                                 if (slen > 0 && pathsLen > 0) {
774                                         char p1end = s [slen - 1];
775                                         if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
776                                                 need_sep = true;
777                                 }
778                                 
779                                 if (need_sep)
780                                         ret.Append (DirectorySeparatorStr);
781                         }
782
783                         return ret.ToString ();
784                 }
785
786 #if NET_4_0
787                 public
788 #else
789                 internal
790 #endif
791                 static string Combine (string path1, string path2, string path3)
792                 {
793                         if (path1 == null)
794                                 throw new ArgumentNullException ("path1");
795
796                         if (path2 == null)
797                                 throw new ArgumentNullException ("path2");
798
799                         if (path3 == null)
800                                 throw new ArgumentNullException ("path3");
801                         
802                         return Combine (new string [] { path1, path2, path3 });
803                 }
804
805 #if NET_4_0
806                 public
807 #else
808                 internal
809 #endif
810                 static string Combine (string path1, string path2, string path3, string path4)
811                 {
812                         if (path1 == null)
813                                 throw new ArgumentNullException ("path1");
814
815                         if (path2 == null)
816                                 throw new ArgumentNullException ("path2");
817
818                         if (path3 == null)
819                                 throw new ArgumentNullException ("path3");
820
821                         if (path4 == null)
822                                 throw new ArgumentNullException ("path4");
823                         
824                         return Combine (new string [] { path1, path2, path3, path4 });
825                 }
826
827                 internal static void Validate (string path)
828                 {
829                         Validate (path, "path");
830                 }
831
832                 internal static void Validate (string path, string parameterName)
833                 {
834                         if (path == null)
835                                 throw new ArgumentNullException (parameterName);
836                         if (String.IsNullOrWhiteSpace (path))
837                                 throw new ArgumentException (Locale.GetText ("Path is empty"));
838                         if (path.IndexOfAny (Path.InvalidPathChars) != -1)
839                                 throw new ArgumentException (Locale.GetText ("Path contains invalid chars"));
840                         if (Environment.IsRunningOnWindows) {
841                                 int idx = path.IndexOf (':');
842                                 if (idx >= 0 && idx != 1)
843                                         throw new ArgumentException (parameterName);
844                         }
845                 }
846         }
847 }