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