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