Drop more obsolete defines
[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 #if !NET_2_1
279                         if (SecurityManager.SecurityEnabled) {
280                                 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
281                         }
282 #endif
283                         return fullpath;
284                 }
285
286                 internal static string WindowsDriveAdjustment (string path)
287                 {
288                         // two special cases to consider when a drive is specified
289                         if (path.Length < 2)
290                                 return path;
291                         if ((path [1] != ':') || !Char.IsLetter (path [0]))
292                                 return path;
293
294                         string current = Directory.GetCurrentDirectory ();
295                         // first, only the drive is specified
296                         if (path.Length == 2) {
297                                 // then if the current directory is on the same drive
298                                 if (current [0] == path [0])
299                                         path = current; // we return it
300                                 else
301                                         path += '\\';
302                         } else if ((path [2] != Path.DirectorySeparatorChar) && (path [2] != Path.AltDirectorySeparatorChar)) {
303                                 // second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
304                                 // If the current directory is on the specified drive...
305                                 if (current [0] == path [0]) {
306                                         // then specified directory is appended to the current drive directory
307                                         path = Path.Combine (current, path.Substring (2, path.Length - 2));
308                                 } else {
309                                         // if not, then just pretend there was a separator (Path.Combine won't work in this case)
310                                         path = String.Concat (path.Substring (0, 2), DirectorySeparatorStr, path.Substring (2, path.Length - 2));
311                                 }
312                         }
313                         return path;
314                 }
315
316                 // insecure - do not call directly
317                 internal static string InsecureGetFullPath (string path)
318                 {
319                         if (path == null)
320                                 throw new ArgumentNullException ("path");
321
322                         if (path.Trim ().Length == 0) {
323                                 string msg = Locale.GetText ("The specified path is not of a legal form (empty).");
324                                 throw new ArgumentException (msg);
325                         }
326
327                         // adjust for drives, i.e. a special case for windows
328                         if (Environment.IsRunningOnWindows)
329                                 path = WindowsDriveAdjustment (path);
330
331                         // if the supplied path ends with a separator...
332                         char end = path [path.Length - 1];
333
334                         if (path.Length >= 2 &&
335                                 IsDsc (path [0]) &&
336                                 IsDsc (path [1])) {
337                                 if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
338                                         throw new ArgumentException ("UNC pass should be of the form \\\\server\\share.");
339
340                                 if (path [0] != DirectorySeparatorChar)
341                                         path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
342
343                                 path = CanonicalizePath (path);
344                         } else {
345                                 if (!IsPathRooted (path))
346                                         path = Directory.GetCurrentDirectory () + DirectorySeparatorStr + path;
347                                 else if (DirectorySeparatorChar == '\\' &&
348                                         path.Length >= 2 &&
349                                         IsDsc (path [0]) &&
350                                         !IsDsc (path [1])) { // like `\abc\def'
351                                         string current = Directory.GetCurrentDirectory ();
352                                         if (current [1] == VolumeSeparatorChar)
353                                                 path = current.Substring (0, 2) + path;
354                                         else
355                                                 path = current.Substring (0, current.IndexOf ('\\', current.IndexOf ("\\\\") + 1));
356                                 }
357                                 path = CanonicalizePath (path);
358                         }
359
360                         // if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
361                         if (IsDsc (end) && (path [path.Length - 1] != DirectorySeparatorChar))
362                                 path += DirectorySeparatorChar;
363
364                         return path;
365                 }
366
367                 static bool IsDsc (char c) {
368                         return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
369                 }
370                 
371                 public static string GetPathRoot (string path)
372                 {
373                         if (path == null)
374                                 return null;
375
376                         if (path.Trim ().Length == 0)
377                                 throw new ArgumentException ("The specified path is not of a legal form.");
378
379                         if (!IsPathRooted (path))
380                                 return String.Empty;
381                         
382                         if (DirectorySeparatorChar == '/') {
383                                 // UNIX
384                                 return IsDsc (path [0]) ? DirectorySeparatorStr : String.Empty;
385                         } else {
386                                 // Windows
387                                 int len = 2;
388
389                                 if (path.Length == 1 && IsDsc (path [0]))
390                                         return DirectorySeparatorStr;
391                                 else if (path.Length < 2)
392                                         return String.Empty;
393
394                                 if (IsDsc (path [0]) && IsDsc (path[1])) {
395                                         // UNC: \\server or \\server\share
396                                         // Get server
397                                         while (len < path.Length && !IsDsc (path [len])) len++;
398
399                                         // Get share
400                                         if (len < path.Length) {
401                                                 len++;
402                                                 while (len < path.Length && !IsDsc (path [len])) len++;
403                                         }
404
405                                         return DirectorySeparatorStr +
406                                                 DirectorySeparatorStr +
407                                                 path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
408                                 } else if (IsDsc (path [0])) {
409                                         // path starts with '\' or '/'
410                                         return DirectorySeparatorStr;
411                                 } else if (path[1] == VolumeSeparatorChar) {
412                                         // C:\folder
413                                         if (path.Length >= 3 && (IsDsc (path [2]))) len++;
414                                 } else
415                                         return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
416                                 return path.Substring (0, len);
417                         }
418                 }
419
420                 // FIXME: Further limit the assertion when imperative Assert is implemented
421                 [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
422                 public static string GetTempFileName ()
423                 {
424                         FileStream f = null;
425                         string path;
426                         Random rnd;
427                         int num = 0;
428
429                         rnd = new Random ();
430                         do {
431                                 num = rnd.Next ();
432                                 num++;
433                                 path = Path.Combine (GetTempPath(), "tmp" + num.ToString("x") + ".tmp");
434
435                                 try {
436                                         f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
437                                                             8192, false, (FileOptions) 1);
438                                 }
439                                 catch (SecurityException) {
440                                         // avoid an endless loop
441                                         throw;
442                                 }
443                                 catch {
444                                 }
445                         } while (f == null);
446                         
447                         f.Close();
448                         return path;
449                 }
450
451                 [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
452                 public static string GetTempPath ()
453                 {
454                         string p = get_temp_path ();
455                         if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
456                                 return p + DirectorySeparatorChar;
457
458                         return p;
459                 }
460
461                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
462                 private static extern string get_temp_path ();
463
464                 public static bool HasExtension (string path)
465                 {
466                         if (path == null || path.Trim ().Length == 0)
467                                 return false;
468
469                         if (path.IndexOfAny (InvalidPathChars) != -1)
470                                 throw new ArgumentException ("Illegal characters in path.");
471
472                         int pos = findExtension (path);
473                         return 0 <= pos && pos < path.Length - 1;
474                 }
475
476                 public static bool IsPathRooted (string path)
477                 {
478                         if (path == null || path.Length == 0)
479                                 return false;
480
481                         if (path.IndexOfAny (InvalidPathChars) != -1)
482                                 throw new ArgumentException ("Illegal characters in path.");
483
484                         char c = path [0];
485                         return (c == DirectorySeparatorChar     ||
486                                 c == AltDirectorySeparatorChar  ||
487                                 (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
488                 }
489
490                 public static char[] GetInvalidFileNameChars ()
491                 {
492                         // return a new array as we do not want anyone to be able to change the values
493                         if (Environment.IsRunningOnWindows) {
494                                 return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
495                                         '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12', 
496                                         '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', 
497                                         '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
498                         } else {
499                                 return new char [2] { '\x00', '/' };
500                         }
501                 }
502
503                 public static char[] GetInvalidPathChars ()
504                 {
505                         // return a new array as we do not want anyone to be able to change the values
506                         if (Environment.IsRunningOnWindows) {
507                                 return new char [36] { '\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
508                                         '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12', 
509                                         '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', 
510                                         '\x1E', '\x1F' };
511                         } else {
512                                 return new char [1] { '\x00' };
513                         }
514                 }
515
516                 public static string GetRandomFileName ()
517                 {
518                         // returns a 8.3 filename (total size 12)
519                         StringBuilder sb = new StringBuilder (12);
520                         // using strong crypto but without creating the file
521                         RandomNumberGenerator rng = RandomNumberGenerator.Create ();
522                         byte [] buffer = new byte [11];
523                         rng.GetBytes (buffer);
524
525                         for (int i = 0; i < buffer.Length; i++) {
526                                 if (sb.Length == 8)
527                                         sb.Append ('.');
528
529                                 // restrict to length of range [a..z0..9]
530                                 int b = (buffer [i] % 36);
531                                 char c = (char) (b < 26 ? (b + 'a') : (b - 26 + '0'));
532                                 sb.Append (c);
533                         }
534
535                         return sb.ToString ();
536                 }
537
538                 // private class methods
539
540                 private static int findExtension (string path)
541                 {
542                         // method should return the index of the path extension
543                         // start or -1 if no valid extension
544                         if (path != null){
545                                 int iLastDot = path.LastIndexOf ('.');
546                                 int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
547
548                                 if (iLastDot > iLastSep)
549                                         return iLastDot;
550                         }
551                         return -1;
552                 }
553
554                 static Path ()
555                 {
556                         VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
557                         DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
558                         AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
559
560                         PathSeparator = MonoIO.PathSeparator;
561                         // this copy will be modifiable ("by design")
562                         InvalidPathChars = GetInvalidPathChars ();
563                         // internal fields
564
565                         DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
566                         PathSeparatorChars = new char [] {
567                                 DirectorySeparatorChar,
568                                 AltDirectorySeparatorChar,
569                                 VolumeSeparatorChar
570                         };
571
572                         dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
573                 }
574
575                 // returns the server and share part of a UNC. Assumes "path" is a UNC.
576                 static string GetServerAndShare (string path)
577                 {
578                         int len = 2;
579                         while (len < path.Length && !IsDsc (path [len])) len++;
580
581                         if (len < path.Length) {
582                                 len++;
583                                 while (len < path.Length && !IsDsc (path [len])) len++;
584                         }
585
586                         return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
587                 }
588
589                 // assumes Environment.IsRunningOnWindows == true
590                 static bool SameRoot (string root, string path)
591                 {
592                         // compare root - if enough details are available
593                         if ((root.Length < 2) || (path.Length < 2))
594                                 return false;
595
596                         // UNC handling
597                         if (IsDsc (root[0]) && IsDsc (root[1])) {
598                                 if (!(IsDsc (path[0]) && IsDsc (path[1])))
599                                         return false;
600
601                                 string rootShare = GetServerAndShare (root);
602                                 string pathShare = GetServerAndShare (path);
603
604                                 return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
605                         }
606                         
607                         // same volume/drive
608                         if (!root [0].Equals (path [0]))
609                                 return false;
610                         // presence of the separator
611                         if (path[1] != Path.VolumeSeparatorChar)
612                                 return false;
613                         if ((root.Length > 2) && (path.Length > 2)) {
614                                 // but don't directory compare the directory separator
615                                 return (IsDsc (root[2]) && IsDsc (path[2]));
616                         }
617                         return true;
618                 }
619
620                 static string CanonicalizePath (string path)
621                 {
622                         // STEP 1: Check for empty string
623                         if (path == null)
624                                 return path;
625                         if (Environment.IsRunningOnWindows)
626                                 path = path.Trim ();
627
628                         if (path.Length == 0)
629                                 return path;
630
631                         // STEP 2: Check to see if this is only a root
632                         string root = Path.GetPathRoot (path);
633                         // it will return '\' for path '\', while it should return 'c:\' or so.
634                         // Note: commenting this out makes the need for the (target == 1...) check in step 5
635                         //if (root == path) return path;
636
637                         // STEP 3: split the directories, this gets rid of consecutative "/"'s
638                         string[] dirs = path.Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
639                         // STEP 4: Get rid of directories containing . and ..
640                         int target = 0;
641
642                         bool isUnc = Environment.IsRunningOnWindows &&
643                                 root.Length > 2 && IsDsc (root[0]) && IsDsc (root[1]);
644
645                         // Set an overwrite limit for UNC paths since '\' + server + share
646                         // must not be eliminated by the '..' elimination algorithm.
647                         int limit = isUnc ? 3 : 0;
648
649                         for (int i = 0; i < dirs.Length; i++) {
650                                 // WIN32 path components must be trimmed
651                                 if (Environment.IsRunningOnWindows)
652                                         dirs[i] = dirs[i].TrimEnd ();
653                                 
654                                 if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
655                                         continue;
656                                 else if (dirs[i] == "..") {
657                                         // don't overwrite path segments below the limit
658                                         if (target > limit)
659                                                 target--;
660                                 } else
661                                         dirs[target++] = dirs[i];
662                         }
663
664                         // STEP 5: Combine everything.
665                         if (target == 0 || (target == 1 && dirs[0] == ""))
666                                 return root;
667                         else {
668                                 string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
669                                 if (Environment.IsRunningOnWindows) {
670                                         // append leading '\' of the UNC path that was lost in STEP 3.
671                                         if (isUnc)
672                                                 ret = Path.DirectorySeparatorStr + ret;
673
674                                         if (!SameRoot (root, ret))
675                                                 ret = root + ret;
676
677                                         if (isUnc) {
678                                                 return ret;
679                                         } else if (!IsDsc (path[0]) && SameRoot (root, path)) {
680                                                 if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
681                                                         ret += Path.DirectorySeparatorChar;
682                                                 return ret;
683                                         } else {
684                                                 string current = Directory.GetCurrentDirectory ();
685                                                 if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
686                                                         // DOS local file path
687                                                         if (ret.Length == 0 || IsDsc (ret[0]))
688                                                                 ret += '\\';
689                                                         return current.Substring (0, 2) + ret;
690                                                 } else if (IsDsc (current[current.Length - 1]) && IsDsc (ret[0]))
691                                                         return current + ret.Substring (1);
692                                                 else
693                                                         return current + ret;
694                                         }
695                                 }
696                                 return ret;
697                         }
698                 }
699
700                 // required for FileIOPermission (and most proibably reusable elsewhere too)
701                 // both path MUST be "full paths"
702                 static internal bool IsPathSubsetOf (string subset, string path)
703                 {
704                         if (subset.Length > path.Length)
705                                 return false;
706
707                         // check that everything up to the last separator match
708                         int slast = subset.LastIndexOfAny (PathSeparatorChars);
709                         if (String.Compare (subset, 0, path, 0, slast) != 0)
710                                 return false;
711
712                         slast++;
713                         // then check if the last segment is identical
714                         int plast = path.IndexOfAny (PathSeparatorChars, slast);
715                         if (plast >= slast) {
716                                 return String.Compare (subset, slast, path, slast, path.Length - plast) == 0;
717                         }
718                         if (subset.Length != path.Length)
719                                 return false;
720
721                         return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
722                 }
723         }
724 }