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