[coop] Temporarily restore MonoThreadInfo when TLS destructor runs. Fixes #43099
[mono.git] / mcs / class / referencesource / mscorlib / system / io / path.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 /*============================================================
7 **
8 ** Class:  Path
9 ** 
10 ** <OWNER>[....]</OWNER>
11 **
12 **
13 ** Purpose: A collection of path manipulation methods.
14 **
15 **
16 ===========================================================*/
17
18 using System;
19 using System.Security.Permissions;
20 using Win32Native = Microsoft.Win32.Win32Native;
21 using System.Text;
22 using System.Runtime.InteropServices;
23 using System.Security;
24 using System.Security.Cryptography;
25 using System.Runtime.CompilerServices;
26 using System.Globalization;
27 using System.Runtime.Versioning;
28 using System.Diagnostics.Contracts;
29
30 namespace System.IO {
31     // Provides methods for processing directory strings in an ideally
32     // cross-platform manner.  Most of the methods don't do a complete
33     // full parsing (such as examining a UNC hostname), but they will
34     // handle most string operations.  
35     // 
36     // File names cannot contain backslash (\), slash (/), colon (:),
37     // asterick (*), question mark (?), quote ("), less than (<;), 
38     // greater than (>;), or pipe (|).  The first three are used as directory
39     // separators on various platforms.  Asterick and question mark are treated
40     // as wild cards.  Less than, Greater than, and pipe all redirect input
41     // or output from a program to a file or some combination thereof.  Quotes
42     // are special.
43     // 
44     // We are guaranteeing that Path.SeparatorChar is the correct 
45     // directory separator on all platforms, and we will support 
46     // Path.AltSeparatorChar as well.  To write cross platform
47     // code with minimal pain, you can use slash (/) as a directory separator in
48     // your strings.
49      // Class contains only static data, no need to serialize
50     [ComVisible(true)]
51     public static class Path
52     {
53         // Platform specific directory separator character.  This is backslash
54         // ('\') on Windows, slash ('/') on Unix, and colon (':') on Mac.
55         // 
56         public static readonly char DirectorySeparatorChar = '\\';
57         internal const string DirectorySeparatorCharAsString = "\\";
58         
59         // Platform specific alternate directory separator character.  
60         // This is backslash ('\') on Unix, and slash ('/') on Windows 
61         // and MacOS.
62         // 
63         public static readonly char AltDirectorySeparatorChar = '/';
64     
65         // Platform specific volume separator character.  This is colon (':')
66         // on Windows and MacOS, and slash ('/') on Unix.  This is mostly
67         // useful for parsing paths like "c:\windows" or "MacVolume:System Folder".  
68         // 
69         public static readonly char VolumeSeparatorChar = ':';
70         
71         // Platform specific invalid list of characters in a path.
72         // See the "Naming a File" MSDN conceptual docs for more details on
73         // what is valid in a file name (which is slightly different from what
74         // is legal in a path name).
75         // Note: This list is duplicated in CheckInvalidPathChars
76         [Obsolete("Please use GetInvalidPathChars or GetInvalidFileNameChars instead.")]
77         public static readonly char[] InvalidPathChars = { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31 };
78
79         // Trim trailing white spaces, tabs etc but don't be aggressive in removing everything that has UnicodeCategory of trailing space.
80         // String.WhitespaceChars will trim aggressively than what the underlying FS does (for ex, NTFS, FAT).    
81         internal static readonly char[] TrimEndChars = { (char) 0x9, (char) 0xA, (char) 0xB, (char) 0xC, (char) 0xD, (char) 0x20,   (char) 0x85, (char) 0xA0};
82         
83         private static readonly char[] RealInvalidPathChars = { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31 };
84
85         // This is used by HasIllegalCharacters
86         private static readonly char[] InvalidPathCharsWithAdditionalChecks = { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31, '*', '?' };
87
88         private static readonly char[] InvalidFileNameChars = { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31, ':', '*', '?', '\\', '/' };
89
90         public static readonly char PathSeparator = ';';
91
92
93         // Make this public sometime.
94         // The max total path is 260, and the max individual component length is 255. 
95         // For example, D:\<256 char file name> isn't legal, even though it's under 260 chars.
96         internal static readonly int MaxPath = 260;
97         private static readonly int MaxDirectoryLength = 255;
98
99         // Windows API definitions
100         internal const int MAX_PATH = 260;  // From WinDef.h
101         internal const int MAX_DIRECTORY_PATH = 248;   // cannot create directories greater than 248 characters
102     
103         // Changes the extension of a file path. The path parameter
104         // specifies a file path, and the extension parameter
105         // specifies a file extension (with a leading period, such as
106         // ".exe" or ".cs").
107         //
108         // The function returns a file path with the same root, directory, and base
109         // name parts as path, but with the file extension changed to
110         // the specified extension. If path is null, the function
111         // returns null. If path does not contain a file extension,
112         // the new file extension is appended to the path. If extension
113         // is null, any exsiting extension is removed from path.
114         //
115         public static String ChangeExtension(String path, String extension) {
116             if (path != null) {
117                 CheckInvalidPathChars(path);
118     
119                 String s = path;
120                 for (int i = path.Length; --i >= 0;) {
121                     char ch = path[i];
122                     if (ch == '.') {
123                         s = path.Substring(0, i);
124                         break;
125                     }
126                     if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) break;
127                 }
128                 if (extension != null && path.Length != 0) {
129                     if (extension.Length == 0 || extension[0] != '.') {
130                         s = s + ".";
131                     }
132                     s = s + extension;
133                 }
134                 return s;
135             }
136             return null;
137         }
138
139        
140         // Returns the directory path of a file path. This method effectively
141         // removes the last element of the given file path, i.e. it returns a
142         // string consisting of all characters up to but not including the last
143         // backslash ("\") in the file path. The returned value is null if the file
144         // path is null or if the file path denotes a root (such as "\", "C:", or
145         // "\\server\share").
146         //
147         [ResourceExposure(ResourceScope.None)]
148         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
149         public static String GetDirectoryName(String path) {
150             if (path != null) {
151                 CheckInvalidPathChars(path);
152
153 #if FEATURE_LEGACYNETCF
154                 if (!CompatibilitySwitches.IsAppEarlierThanWindowsPhone8) {
155 #endif
156
157                 string normalizedPath = NormalizePath(path, false);
158
159                 // If there are no permissions for PathDiscovery to this path, we should NOT expand the short paths
160                 // as this would leak information about paths to which the user would not have access to.
161                 if (path.Length > 0)
162                 {
163                     try
164                     {
165                         // If we were passed in a path with \\?\ we need to remove it as FileIOPermission does not like it.
166                         string tempPath = Path.RemoveLongPathPrefix(path);
167
168                         // FileIOPermission cannot handle paths that contain ? or *
169                         // So we only pass to FileIOPermission the text up to them.
170                         int pos = 0;
171                         while (pos < tempPath.Length && (tempPath[pos] != '?' && tempPath[pos] != '*')) 
172                             pos++;
173
174                         // GetFullPath will Demand that we have the PathDiscovery FileIOPermission and thus throw 
175                         // SecurityException if we don't. 
176                         // While we don't use the result of this call we are using it as a consistent way of 
177                         // doing the security checks. 
178                         if (pos > 0)
179                             Path.GetFullPath(tempPath.Substring(0, pos));
180                     }
181                     catch (SecurityException) {
182                         // If the user did not have permissions to the path, make sure that we don't leak expanded short paths
183                         // Only re-normalize if the original path had a ~ in it.
184                         if (path.IndexOf("~", StringComparison.Ordinal) != -1)
185                         {
186                             normalizedPath = NormalizePath(path, /*fullCheck*/ false, /*expandShortPaths*/ false);
187                         }
188                     }
189                     catch (PathTooLongException) { }
190                     catch (NotSupportedException) { }  // Security can throw this on "c:\foo:"
191                     catch (IOException) { }
192                     catch (ArgumentException) { } // The normalizePath with fullCheck will throw this for file: and http:
193                 }
194
195                 path = normalizedPath;
196
197 #if FEATURE_LEGACYNETCF
198                 }
199 #endif
200
201                 int root = GetRootLength(path);
202                 int i = path.Length;
203                 if (i > root) {
204                     i = path.Length;
205                     if (i == root) return null;
206                     while (i > root && path[--i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar);                    
207                     String dir = path.Substring(0, i);
208 #if FEATURE_LEGACYNETCF
209                     if (CompatibilitySwitches.IsAppEarlierThanWindowsPhone8) {                        
210                         if (dir.Length >= MAX_PATH - 1)
211                             throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
212                     }                     
213 #endif
214                     return dir;
215                 }
216             }
217             return null;
218         }
219
220         // Gets the length of the root DirectoryInfo or whatever DirectoryInfo markers
221         // are specified for the first part of the DirectoryInfo name.
222         // 
223         internal static int GetRootLength(String path) {
224             CheckInvalidPathChars(path);
225             
226             int i = 0;
227             int length = path.Length;
228
229             if (length >= 1 && (IsDirectorySeparator(path[0]))) {
230                 // handles UNC names and directories off current drive's root.
231                 i = 1;
232                 if (length >= 2 && (IsDirectorySeparator(path[1]))) {
233                     i = 2;
234                     int n = 2;
235                     while (i < length && ((path[i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar) || --n > 0)) i++;
236                 }
237             }
238             else if (length >= 2 && path[1] == VolumeSeparatorChar) {
239                 // handles A:\foo.
240                 i = 2;
241                 if (length >= 3 && (IsDirectorySeparator(path[2]))) i++;
242             }
243             return i;
244         }
245
246         internal static bool IsDirectorySeparator(char c) {
247             return (c==DirectorySeparatorChar || c == AltDirectorySeparatorChar);
248         }
249
250
251         public static char[] GetInvalidPathChars()
252         {
253             return (char[]) RealInvalidPathChars.Clone();
254         }
255
256         public static char[] GetInvalidFileNameChars()
257         {
258             return (char[]) InvalidFileNameChars.Clone();
259         }
260
261         // Returns the extension of the given path. The returned value includes the
262         // period (".") character of the extension except when you have a terminal period when you get String.Empty, such as ".exe" or
263         // ".cpp". The returned value is null if the given path is
264         // null or if the given path does not include an extension.
265         //
266         [Pure]
267         public static String GetExtension(String path) {
268             if (path==null)
269                 return null;
270
271             CheckInvalidPathChars(path);
272             int length = path.Length;
273             for (int i = length; --i >= 0;) {
274                 char ch = path[i];
275                 if (ch == '.')
276                 {
277                     if (i != length - 1)
278                         return path.Substring(i, length - i);
279                     else
280                         return String.Empty;
281                 }
282                 if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
283                     break;
284             }
285             return String.Empty;
286         }
287
288         // Expands the given path to a fully qualified path. The resulting string
289         // consists of a drive letter, a colon, and a root relative path. This
290         // function does not verify that the resulting path 
291         // refers to an existing file or directory on the associated volume.
292         [Pure]
293         [System.Security.SecuritySafeCritical]
294         [ResourceExposure(ResourceScope.Machine)]
295         [ResourceConsumption(ResourceScope.Machine)]
296         public static String GetFullPath(String path) {
297             String fullPath = GetFullPathInternal(path);
298 #if FEATURE_CORECLR
299             FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, path, fullPath);
300             state.EnsureState();
301 #else
302             FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, fullPath, false, false);
303 #endif
304             return fullPath;
305         }
306
307         [System.Security.SecurityCritical]
308         [ResourceExposure(ResourceScope.Machine)]
309         [ResourceConsumption(ResourceScope.Machine)]
310         internal static String UnsafeGetFullPath(String path)
311         {
312             String fullPath = GetFullPathInternal(path);
313 #if !FEATURE_CORECLR
314             FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, fullPath, false, false);
315 #endif
316             return fullPath;
317         }
318
319         // This method is package access to let us quickly get a string name
320         // while avoiding a security check.  This also serves a slightly
321         // different purpose - when we open a file, we need to resolve the
322         // path into a fully qualified, non-relative path name.  This
323         // method does that, finding the current drive &; directory.  But
324         // as long as we don't return this info to the user, we're good.  However,
325         // the public GetFullPath does need to do a security check.
326         [ResourceExposure(ResourceScope.Machine)]
327         [ResourceConsumption(ResourceScope.Machine)]
328         internal static String GetFullPathInternal(String path) {
329             if (path == null)
330                 throw new ArgumentNullException("path");
331             Contract.EndContractBlock();
332
333             String newPath = NormalizePath(path, true);
334
335             return newPath;
336         }
337
338         [System.Security.SecuritySafeCritical]  // auto-generated
339         [ResourceExposure(ResourceScope.Machine)]
340         [ResourceConsumption(ResourceScope.Machine)]
341         internal unsafe static String NormalizePath(String path, bool fullCheck) {
342             return NormalizePath(path, fullCheck, MaxPath);
343         }
344
345         [System.Security.SecuritySafeCritical]  // auto-generated
346         [ResourceExposure(ResourceScope.Machine)]
347         [ResourceConsumption(ResourceScope.Machine)]
348         internal unsafe static String NormalizePath(String path, bool fullCheck, bool expandShortPaths)
349         {
350             return NormalizePath(path, fullCheck, MaxPath, expandShortPaths);
351         }
352
353         [System.Security.SecuritySafeCritical]  // auto-generated
354         [ResourceExposure(ResourceScope.Machine)]
355         [ResourceConsumption(ResourceScope.Machine)]
356         internal unsafe static String NormalizePath(String path, bool fullCheck, int maxPathLength) {
357             return NormalizePath(path, fullCheck, maxPathLength, true);
358         }
359
360         [System.Security.SecurityCritical]  // auto-generated
361         [ResourceExposure(ResourceScope.Machine)]
362         [ResourceConsumption(ResourceScope.Machine)]
363         internal unsafe static String NormalizePath(String path, bool fullCheck, int maxPathLength, bool expandShortPaths) {
364
365             Contract.Requires(path != null, "path can't be null");
366             // If we're doing a full path check, trim whitespace and look for
367             // illegal path characters.
368             if (fullCheck) {
369                 // Trim whitespace off the end of the string.
370                 // Win32 normalization trims only U+0020. 
371                 path = path.TrimEnd(TrimEndChars);
372
373                 // Look for illegal path characters.
374                 CheckInvalidPathChars(path);
375             }
376
377             int index = 0;
378             // We prefer to allocate on the stack for workingset/perf gain. If the 
379             // starting path is less than MaxPath then we can stackalloc; otherwise we'll
380             // use a StringBuilder (PathHelper does this under the hood). The latter may
381             // happen in 2 cases:
382             // 1. Starting path is greater than MaxPath but it normalizes down to MaxPath.
383             // This is relevant for paths containing escape sequences. In this case, we
384             // attempt to normalize down to MaxPath, but the caller pays a perf penalty 
385             // since StringBuilder is used. 
386             // 2. IsolatedStorage, which supports paths longer than MaxPath (value given 
387             // by maxPathLength.
388             PathHelper newBuffer;
389             if (path.Length + 1 <= MaxPath) {
390                 char* m_arrayPtr = stackalloc char[MaxPath];
391                 newBuffer = new PathHelper(m_arrayPtr, MaxPath);
392             } else {
393                 newBuffer = new PathHelper(path.Length + Path.MaxPath, maxPathLength);
394             }
395             
396             uint numSpaces = 0;
397             uint numDots = 0;
398             bool fixupDirectorySeparator = false;
399             // Number of significant chars other than potentially suppressible
400             // dots and spaces since the last directory or volume separator char
401             uint numSigChars = 0;
402             int lastSigChar = -1; // Index of last significant character.
403             // Whether this segment of the path (not the complete path) started
404             // with a volume separator char.  Reject "c:...".
405             bool startedWithVolumeSeparator = false;
406             bool firstSegment = true;
407             int lastDirectorySeparatorPos = 0;
408
409             bool mightBeShortFileName = false;
410
411             // LEGACY: This code is here for backwards compatibility reasons. It 
412             // ensures that \\foo.cs\bar.cs stays \\foo.cs\bar.cs instead of being
413             // turned into \foo.cs\bar.cs.
414             if (path.Length > 0 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) {
415                 newBuffer.Append('\\');
416                 index++;
417                 lastSigChar = 0;
418             }
419
420             // Normalize the string, stripping out redundant dots, spaces, and 
421             // slashes.
422             while (index < path.Length) {
423                 char currentChar = path[index];
424
425                 // We handle both directory separators and dots specially.  For 
426                 // directory separators, we consume consecutive appearances.  
427                 // For dots, we consume all dots beyond the second in 
428                 // succession.  All other characters are added as is.  In 
429                 // addition we consume all spaces after the last other char
430                 // in a directory name up until the directory separator.
431
432                 if (currentChar == DirectorySeparatorChar || currentChar == AltDirectorySeparatorChar) {
433                     // If we have a path like "123.../foo", remove the trailing dots.
434                     // However, if we found "c:\temp\..\bar" or "c:\temp\...\bar", don't.
435                     // Also remove trailing spaces from both files & directory names.
436                     // This was agreed on with the OS team to fix undeletable directory
437                     // names ending in spaces.
438
439                     // If we saw a '\' as the previous last significant character and
440                     // are simply going to write out dots, suppress them.
441                     // If we only contain dots and slashes though, only allow
442                     // a string like [dot]+ [space]*.  Ignore everything else.
443                     // Legal: "\.. \", "\...\", "\. \"
444                     // Illegal: "\.. .\", "\. .\", "\ .\"
445                     if (numSigChars == 0) {
446                         // Dot and space handling
447                         if (numDots > 0) {
448                             // Look for ".[space]*" or "..[space]*"
449                             int start = lastSigChar + 1;
450                             if (path[start] != '.')
451                                 throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
452
453                             // Only allow "[dot]+[space]*", and normalize the 
454                             // legal ones to "." or ".."
455                             if (numDots >= 2) {
456                                 // Reject "C:..."
457                                 if (startedWithVolumeSeparator && numDots > 2)
458
459                                     throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
460
461                                 if (path[start + 1] == '.') {
462                                     // Search for a space in the middle of the
463                                     // dots and throw
464                                     for(int i=start + 2; i < start + numDots; i++) {
465                                         if (path[i] != '.')
466                                             throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
467                                     }
468
469                                     numDots = 2;
470                                 }
471                                 else {
472                                     if (numDots > 1)
473                                         throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
474                                     numDots = 1;
475                                 }
476                             }
477                                     
478                             if (numDots == 2) {
479                                 newBuffer.Append('.');
480                             }
481
482                             newBuffer.Append('.');
483                             fixupDirectorySeparator = false;
484
485                             // Continue in this case, potentially writing out '\'.
486                         }
487
488                         if (numSpaces > 0 && firstSegment) {
489                             // Handle strings like " \\server\share".
490                             if (index + 1 < path.Length && 
491                                 (path[index + 1] == DirectorySeparatorChar || path[index + 1] == AltDirectorySeparatorChar))
492                             {
493                                 newBuffer.Append(DirectorySeparatorChar);
494                             }
495                         }
496                     }
497                     numDots = 0;
498                     numSpaces = 0;  // Suppress trailing spaces
499
500                     if (!fixupDirectorySeparator) {
501                         fixupDirectorySeparator = true;
502                         newBuffer.Append(DirectorySeparatorChar);
503                     }
504                     numSigChars = 0;
505                     lastSigChar = index;
506                     startedWithVolumeSeparator = false;
507                     firstSegment = false;
508
509                     // For short file names, we must try to expand each of them as
510                     // soon as possible.  We need to allow people to specify a file
511                     // name that doesn't exist using a path with short file names
512                     // in it, such as this for a temp file we're trying to create:
513                     // C:\DOCUME~1\USERNA~1.RED\LOCALS~1\Temp\bg3ylpzp
514                     // We could try doing this afterwards piece by piece, but it's
515                     // probably a lot simpler to do it here.
516                     if (mightBeShortFileName) {
517                         newBuffer.TryExpandShortFileName(); 
518                         mightBeShortFileName = false;
519                     }
520
521                     int thisPos = newBuffer.Length - 1;
522                     if (thisPos - lastDirectorySeparatorPos > MaxDirectoryLength)
523                     {
524                         throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
525                     }
526                     lastDirectorySeparatorPos = thisPos;
527                 } // if (Found directory separator)
528                 else if (currentChar == '.') {
529                     // Reduce only multiple .'s only after slash to 2 dots. For
530                     // instance a...b is a valid file name.
531                     numDots++;
532                     // Don't flush out non-terminal spaces here, because they may in
533                     // the end not be significant.  Turn "c:\ . .\foo" -> "c:\foo"
534                     // which is the conclusion of removing trailing dots & spaces,
535                     // as well as folding multiple '\' characters.
536                 }
537                 else if (currentChar == ' ') {
538                     numSpaces++;
539                 }
540                 else {  // Normal character logic
541                     if (currentChar == '~' && expandShortPaths)
542                         mightBeShortFileName = true;
543
544                     fixupDirectorySeparator = false;
545
546                     // To reject strings like "C:...\foo" and "C  :\foo"
547                     if (firstSegment && currentChar == VolumeSeparatorChar) {
548                         // Only accept "C:", not "c :" or ":"
549                         // Get a drive letter or ' ' if index is 0.
550                         char driveLetter = (index > 0) ? path[index-1] : ' ';
551                         bool validPath = ((numDots == 0) && (numSigChars >= 1) && (driveLetter != ' '));
552                         if (!validPath)
553                             throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
554
555                         startedWithVolumeSeparator = true;
556                         // We need special logic to make " c:" work, we should not fix paths like "  foo::$DATA"
557                         if (numSigChars > 1) { // Common case, simply do nothing
558                             int spaceCount = 0; // How many spaces did we write out, numSpaces has already been reset.
559                             while((spaceCount < newBuffer.Length) && newBuffer[spaceCount] == ' ') 
560                                 spaceCount++;
561                             if (numSigChars - spaceCount == 1) {
562                                 //Safe to update stack ptr directly
563                                 newBuffer.Length = 0;
564                                 newBuffer.Append(driveLetter); // Overwrite spaces, we need a special case to not break "  foo" as a relative path.
565                             }
566                         }
567                         numSigChars = 0;
568                     }
569                     else 
570                     {
571                         numSigChars += 1 + numDots + numSpaces;
572                     }
573
574                     // Copy any spaces & dots since the last significant character
575                     // to here.  Note we only counted the number of dots & spaces,
576                     // and don't know what order they're in.  Hence the copy.
577                     if (numDots > 0 || numSpaces > 0) {
578                         int numCharsToCopy = (lastSigChar >= 0) ? index - lastSigChar - 1 : index;
579                         if (numCharsToCopy > 0) {
580                             for (int i=0; i<numCharsToCopy; i++) {
581                                 newBuffer.Append(path[lastSigChar + 1 + i]);
582                             }
583                         }
584                         numDots = 0;
585                         numSpaces = 0;
586                     }
587
588                     newBuffer.Append(currentChar);
589                     lastSigChar = index;
590                 }
591                 
592                 index++;
593             } // end while
594
595             if (newBuffer.Length - 1 - lastDirectorySeparatorPos > MaxDirectoryLength)
596             {
597                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
598             }
599
600             // Drop any trailing dots and spaces from file & directory names, EXCEPT
601             // we MUST make sure that "C:\foo\.." is correctly handled.
602             // Also handle "C:\foo\." -> "C:\foo", while "C:\." -> "C:\"
603             if (numSigChars == 0) {
604                 if (numDots > 0) {
605                     // Look for ".[space]*" or "..[space]*"
606                     int start = lastSigChar + 1;
607                     if (path[start] != '.')
608                         throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
609
610                     // Only allow "[dot]+[space]*", and normalize the 
611                     // legal ones to "." or ".."
612                     if (numDots >= 2) {
613                         // Reject "C:..."
614                         if (startedWithVolumeSeparator && numDots > 2)
615                             throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
616
617                         if (path[start + 1] == '.') {
618                             // Search for a space in the middle of the
619                             // dots and throw
620                             for(int i=start + 2; i < start + numDots; i++) {
621                                 if (path[i] != '.')
622                                     throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
623                             }
624                             
625                             numDots = 2;
626                         }
627                         else {
628                             if (numDots > 1)
629                                 throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
630                             numDots = 1;
631                         }
632                     }
633
634                     if (numDots == 2) {
635                         newBuffer.Append('.');
636                     }
637
638                     newBuffer.Append('.');
639                 }
640             } // if (numSigChars == 0)
641
642             // If we ended up eating all the characters, bail out.
643             if (newBuffer.Length == 0)
644                 throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
645
646             // Disallow URL's here.  Some of our other Win32 API calls will reject
647             // them later, so we might be better off rejecting them here.
648             // Note we've probably turned them into "file:\D:\foo.tmp" by now.
649             // But for compatibility, ensure that callers that aren't doing a 
650             // full check aren't rejected here.
651             if (fullCheck) {
652                 if ( newBuffer.OrdinalStartsWith("http:", false) ||
653                      newBuffer.OrdinalStartsWith("file:", false))
654                 {
655                     throw new ArgumentException(Environment.GetResourceString("Argument_PathUriFormatNotSupported")); 
656                 }
657             }
658
659             // If the last part of the path (file or directory name) had a tilde,
660             // expand that too.
661             if (mightBeShortFileName) {
662                 newBuffer.TryExpandShortFileName(); 
663             }
664
665             // Call the Win32 API to do the final canonicalization step.
666             int result = 1;
667
668             if (fullCheck) {
669                 // NOTE: Win32 GetFullPathName requires the input buffer to be big enough to fit the initial 
670                 // path which is a concat of CWD and the relative path, this can be of an arbitrary 
671                 // size and could be > MAX_PATH (which becomes an artificial limit at this point), 
672                 // even though the final normalized path after fixing up the relative path syntax 
673                 // might be well within the MAX_PATH restriction. For ex,
674                 // "c:\SomeReallyLongDirName(thinkGreaterThan_MAXPATH)\..\foo.txt" which actually requires a
675                 // buffer well with in the MAX_PATH as the normalized path is just "c:\foo.txt"
676                 // This buffer requirement seems wrong, it could be a bug or a perf optimization  
677                 // like returning required buffer length quickly or avoid stratch buffer etc. 
678                 // Either way we need to workaround it here...
679                 
680                 // Ideally we would get the required buffer length first by calling GetFullPathName
681                 // once without the buffer and use that in the later call but this doesn't always work
682                 // due to Win32 GetFullPathName bug. For instance, in Win2k, when the path we are trying to
683                 // fully qualify is a single letter name (such as "a", "1", ",") GetFullPathName
684                 // fails to return the right buffer size (i.e, resulting in insufficient buffer). 
685                 // To workaround this bug we will start with MAX_PATH buffer and grow it once if the 
686                 // return value is > MAX_PATH. 
687
688                 result = newBuffer.GetFullPathName();
689
690                 // If we called GetFullPathName with something like "foo" and our
691                 // command window was in short file name mode (ie, by running edlin or
692                 // DOS versions of grep, etc), we might have gotten back a short file
693                 // name.  So, check to see if we need to expand it.
694                 mightBeShortFileName = false;
695                 for(int i=0; i < newBuffer.Length && !mightBeShortFileName; i++) {
696                     if (newBuffer[i] == '~' && expandShortPaths)
697                         mightBeShortFileName = true;
698                 }
699
700                 if (mightBeShortFileName) {
701                     bool r = newBuffer.TryExpandShortFileName();
702                     // Consider how the path "Doesn'tExist" would expand.  If
703                     // we add in the current directory, it too will need to be
704                     // fully expanded, which doesn't happen if we use a file
705                     // name that doesn't exist.
706                     if (!r) {
707                         int lastSlash = -1;
708
709                         for (int i = newBuffer.Length - 1; i >= 0; i--) { 
710                             if (newBuffer[i] == DirectorySeparatorChar) {
711                                 lastSlash = i;
712                                 break;
713                             }
714                         }
715
716                         if (lastSlash >= 0) {
717                             
718                             // This bounds check is for safe memcpy but we should never get this far 
719                             if (newBuffer.Length >= maxPathLength)
720                                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
721
722                             int lenSavedName = newBuffer.Length - lastSlash - 1;
723                             Contract.Assert(lastSlash < newBuffer.Length, "path unexpectedly ended in a '\'");
724
725                             newBuffer.Fixup(lenSavedName, lastSlash);
726                         }
727                     }
728                 }
729             }
730
731             if (result != 0) {
732                 /* Throw an ArgumentException for paths like \\, \\server, \\server\
733                    This check can only be properly done after normalizing, so
734                    \\foo\.. will be properly rejected.  Also, reject \\?\GLOBALROOT\
735                    (an internal kernel path) because it provides aliases for drives. */
736                 if (newBuffer.Length > 1 && newBuffer[0] == '\\' && newBuffer[1] == '\\') {
737                     int startIndex = 2;
738                     while (startIndex < result) {
739                         if (newBuffer[startIndex] == '\\') {
740                             startIndex++;
741                             break;
742                         }
743                         else {
744                             startIndex++;
745                         }
746                     }
747                     if (startIndex == result)
748                         throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC"));
749
750                     // Check for \\?\Globalroot, an internal mechanism to the kernel
751                     // that provides aliases for drives and other undocumented stuff.
752                     // The kernel team won't even describe the full set of what
753                     // is available here - we don't want managed apps mucking 
754                     // with this for security reasons.
755                     if ( newBuffer.OrdinalStartsWith("\\\\?\\globalroot", true))
756                         throw new ArgumentException(Environment.GetResourceString("Arg_PathGlobalRoot"));
757                 }
758             }
759
760             // Check our result and form the managed string as necessary.
761             if (newBuffer.Length >= maxPathLength)
762                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
763
764             if (result == 0) {
765                 int errorCode = Marshal.GetLastWin32Error();
766                 if (errorCode == 0)
767                     errorCode = Win32Native.ERROR_BAD_PATHNAME;
768                 __Error.WinIOError(errorCode, path);
769                 return null;  // Unreachable - silence a compiler error.
770             }
771
772             String returnVal = newBuffer.ToString();
773             if (String.Equals(returnVal, path, StringComparison.Ordinal))
774             {
775                 returnVal = path;
776             }
777             return returnVal;
778
779         }
780         internal const int MaxLongPath = 32000;
781
782         private const string LongPathPrefix = @"\\?\";
783         private const string UNCPathPrefix = @"\\";
784         private const string UNCLongPathPrefixToInsert = @"?\UNC\";
785         private const string UNCLongPathPrefix = @"\\?\UNC\";
786
787         internal unsafe static bool HasLongPathPrefix(String path)
788         {
789             return path.StartsWith(LongPathPrefix, StringComparison.Ordinal);
790         }
791
792         internal unsafe static String AddLongPathPrefix(String path)
793         {
794             if (path.StartsWith(LongPathPrefix, StringComparison.Ordinal))
795                 return path;
796
797             if (path.StartsWith(UNCPathPrefix, StringComparison.Ordinal))
798                 return path.Insert(2, UNCLongPathPrefixToInsert); // Given \\server\share in longpath becomes \\?\UNC\server\share  => UNCLongPathPrefix + path.SubString(2); => The actual command simply reduces the operation cost.
799
800             return LongPathPrefix + path;
801         }
802
803         internal unsafe static String RemoveLongPathPrefix(String path)
804         {
805             if (!path.StartsWith(LongPathPrefix, StringComparison.Ordinal))
806                 return path;
807
808             if (path.StartsWith(UNCLongPathPrefix, StringComparison.OrdinalIgnoreCase))
809                 return path.Remove(2, 6); // Given \\?\UNC\server\share we return \\server\share => @'\\' + path.SubString(UNCLongPathPrefix.Length) => The actual command simply reduces the operation cost.
810
811             return path.Substring(4);
812         }
813
814         internal unsafe static StringBuilder RemoveLongPathPrefix(StringBuilder pathSB)
815         {
816             string path = pathSB.ToString();
817             if (!path.StartsWith(LongPathPrefix, StringComparison.Ordinal))
818                 return pathSB;
819
820             if (path.StartsWith(UNCLongPathPrefix, StringComparison.OrdinalIgnoreCase))
821                 return pathSB.Remove(2, 6);  // Given \\?\UNC\server\share we return \\server\share => @'\\' + path.SubString(UNCLongPathPrefix.Length) => The actual command simply reduces the operation cost.
822
823             return pathSB.Remove(0, 4);
824         }
825
826         // Returns the name and extension parts of the given path. The resulting
827         // string contains the characters of path that follow the last
828         // backslash ("\"), slash ("/"), or colon (":") character in 
829         // path. The resulting string is the entire path if path 
830         // contains no backslash after removing trailing slashes, slash, or colon characters. The resulting 
831         // string is null if path is null.
832         //
833         [Pure]
834         public static String GetFileName(String path) {
835           if (path != null) {
836                 CheckInvalidPathChars(path);
837     
838                 int length = path.Length;
839                 for (int i = length; --i >= 0;) {
840                     char ch = path[i];
841                     if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
842                         return path.Substring(i + 1, length - i - 1);
843
844                 }
845             }
846             return path;
847         }
848
849         [Pure]
850         public static String GetFileNameWithoutExtension(String path) {
851             path = GetFileName(path);
852             if (path != null)
853             {
854                 int i;
855                 if ((i=path.LastIndexOf('.')) == -1)
856                     return path; // No path extension found
857                 else
858                     return path.Substring(0,i);
859             }
860             return null;
861          }
862
863
864
865         // Returns the root portion of the given path. The resulting string
866         // consists of those rightmost characters of the path that constitute the
867         // root of the path. Possible patterns for the resulting string are: An
868         // empty string (a relative path on the current drive), "\" (an absolute
869         // path on the current drive), "X:" (a relative path on a given drive,
870         // where X is the drive letter), "X:\" (an absolute path on a given drive),
871         // and "\\server\share" (a UNC path for a given server and share name).
872         // The resulting string is null if path is null.
873         //
874         [Pure]
875         [ResourceExposure(ResourceScope.Machine)]
876         [ResourceConsumption(ResourceScope.Machine)]
877         public static String GetPathRoot(String path) {
878             if (path == null) return null;
879             path = NormalizePath(path, false);
880             return path.Substring(0, GetRootLength(path));
881         }
882
883         [System.Security.SecuritySafeCritical]
884         [ResourceExposure(ResourceScope.Machine)]
885         [ResourceConsumption(ResourceScope.Machine)]
886         public static String GetTempPath()
887         {
888 #if !FEATURE_CORECLR
889             new EnvironmentPermission(PermissionState.Unrestricted).Demand();
890 #endif
891             StringBuilder sb = new StringBuilder(MAX_PATH);
892             uint r = Win32Native.GetTempPath(MAX_PATH, sb);
893             String path = sb.ToString();
894             if (r==0) __Error.WinIOError();
895             path = GetFullPathInternal(path);
896 #if FEATURE_CORECLR
897             FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, String.Empty, path);
898             state.EnsureState();
899 #endif
900             return path;
901         }
902
903         internal static bool IsRelative(string path)
904         {
905             Contract.Assert(path != null, "path can't be null");
906             if ((path.Length >= 3 && path[1] == VolumeSeparatorChar && path[2] == DirectorySeparatorChar && 
907                    ((path[0] >= 'a' && path[0] <= 'z') || (path[0] >= 'A' && path[0] <= 'Z'))) ||
908                   (path.Length >= 2 && path[0] == '\\' && path[1] == '\\'))
909                 return false;
910             else
911                 return true;
912         
913         }
914                 
915         // Returns a cryptographically strong random 8.3 string that can be 
916         // used as either a folder name or a file name.
917         public static String GetRandomFileName()
918         {
919             // 5 bytes == 40 bits == 40/5 == 8 chars in our encoding
920             // This gives us exactly 8 chars. We want to avoid the 8.3 short name issue
921             byte[] key = new byte[10];
922
923             // RNGCryptoServiceProvider is disposable in post-Orcas desktop mscorlibs, but not in CoreCLR's
924             // mscorlib, so we need to do a manual using block for it.
925             RNGCryptoServiceProvider rng = null;
926             try
927             {
928                 rng = new RNGCryptoServiceProvider();
929
930                 rng.GetBytes(key);
931                 // rndCharArray is expected to be 16 chars
932                 char[] rndCharArray = Path.ToBase32StringSuitableForDirName(key).ToCharArray();
933                 rndCharArray[8] = '.';
934                 return new String(rndCharArray, 0, 12);
935             }
936             finally
937             {
938                 if (rng != null)
939                 {
940                     rng.Dispose();
941                 }
942             }
943         }
944
945         // Returns a unique temporary file name, and creates a 0-byte file by that
946         // name on disk.
947         [System.Security.SecuritySafeCritical]
948         [ResourceExposure(ResourceScope.AppDomain)]
949         [ResourceConsumption(ResourceScope.AppDomain)]
950         public static String GetTempFileName()
951         {
952             return InternalGetTempFileName(true);
953         }
954
955         [System.Security.SecurityCritical]
956         [ResourceExposure(ResourceScope.AppDomain)]
957         [ResourceConsumption(ResourceScope.AppDomain)]
958         internal static String UnsafeGetTempFileName()
959         {
960             return InternalGetTempFileName(false);
961         }
962
963         [System.Security.SecurityCritical]  
964         [ResourceExposure(ResourceScope.AppDomain)]
965         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
966         private static String InternalGetTempFileName(bool checkHost) 
967         {
968             String path = GetTempPath();
969
970             // Since this can write to the temp directory and theoretically 
971             // cause a denial of service attack, demand FileIOPermission to 
972             // that directory.
973
974 #if FEATURE_CORECLR
975             if (checkHost)
976             {
977                 FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, String.Empty, path);
978                 state.EnsureState();
979             }
980 #else
981             new FileIOPermission(FileIOPermissionAccess.Write, path).Demand();
982 #endif
983             StringBuilder sb = new StringBuilder(MAX_PATH);
984             uint r = Win32Native.GetTempFileName(path, "tmp", 0, sb);
985             if (r==0) __Error.WinIOError();
986             return sb.ToString();
987         }
988     
989         // Tests if a path includes a file extension. The result is
990         // true if the characters that follow the last directory
991         // separator ('\\' or '/') or volume separator (':') in the path include 
992         // a period (".") other than a terminal period. The result is false otherwise.
993         //
994         [Pure]
995         public static bool HasExtension(String path) {
996             if (path != null) {
997                 CheckInvalidPathChars(path);
998                 
999                 for (int i = path.Length; --i >= 0;) {
1000                     char ch = path[i];
1001                     if (ch == '.') {
1002                         if ( i != path.Length - 1)
1003                             return true;
1004                         else
1005                             return false;
1006                     }
1007                     if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) break;
1008                 }
1009             }
1010             return false;
1011         }
1012     
1013     
1014         // Tests if the given path contains a root. A path is considered rooted
1015         // if it starts with a backslash ("\") or a drive letter and a colon (":").
1016         //
1017         [Pure]
1018         public static bool IsPathRooted(String path) {
1019             if (path != null) {
1020                 CheckInvalidPathChars(path);
1021     
1022                 int length = path.Length;
1023                 if ((length >= 1 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) || (length >= 2 && path[1] == VolumeSeparatorChar))
1024                     return true;
1025             }
1026             return false;
1027         }
1028
1029         public static String Combine(String path1, String path2) {
1030             if (path1==null || path2==null)
1031                 throw new ArgumentNullException((path1==null) ? "path1" : "path2");
1032             Contract.EndContractBlock();
1033             CheckInvalidPathChars(path1);
1034             CheckInvalidPathChars(path2);
1035
1036             return CombineNoChecks(path1, path2);
1037         }
1038
1039         public static String Combine(String path1, String path2, String path3) {
1040             if (path1 == null || path2 == null || path3 == null)
1041                 throw new ArgumentNullException((path1 == null) ? "path1" : (path2 == null) ? "path2" : "path3");
1042             Contract.EndContractBlock();
1043             CheckInvalidPathChars(path1);
1044             CheckInvalidPathChars(path2);
1045             CheckInvalidPathChars(path3);
1046
1047             return CombineNoChecks(CombineNoChecks(path1, path2), path3);
1048         }
1049
1050         public static String Combine(String path1, String path2, String path3, String path4) {
1051             if (path1 == null || path2 == null || path3 == null || path4 == null)
1052                 throw new ArgumentNullException((path1 == null) ? "path1" : (path2 == null) ? "path2" : (path3 == null) ? "path3" : "path4");
1053             Contract.EndContractBlock();
1054             CheckInvalidPathChars(path1);
1055             CheckInvalidPathChars(path2);
1056             CheckInvalidPathChars(path3);
1057             CheckInvalidPathChars(path4);
1058
1059             return CombineNoChecks(CombineNoChecks(CombineNoChecks(path1, path2), path3), path4);
1060         }
1061
1062         public static String Combine(params String[] paths) {
1063             if (paths == null) {
1064                 throw new ArgumentNullException("paths");
1065             }
1066             Contract.EndContractBlock();
1067
1068             int finalSize = 0;
1069             int firstComponent = 0;
1070
1071             // We have two passes, the first calcuates how large a buffer to allocate and does some precondition
1072             // checks on the paths passed in.  The second actually does the combination.
1073
1074             for (int i = 0; i < paths.Length; i++) {
1075                 if (paths[i] == null) {
1076                     throw new ArgumentNullException("paths");
1077                 }
1078
1079                 if (paths[i].Length  == 0) {
1080                     continue;
1081                 }
1082
1083                 CheckInvalidPathChars(paths[i]);
1084
1085                 if (Path.IsPathRooted(paths[i])) {
1086                     firstComponent = i;
1087                     finalSize = paths[i].Length;
1088                 } else {
1089                     finalSize += paths[i].Length;
1090                 }
1091
1092                 char ch = paths[i][paths[i].Length - 1];
1093                 if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) 
1094                     finalSize++;
1095                 }
1096
1097             StringBuilder finalPath = StringBuilderCache.Acquire(finalSize);
1098
1099             for (int i = firstComponent; i < paths.Length; i++) {
1100                 if (paths[i].Length == 0) {
1101                     continue;
1102                 }
1103
1104                 if (finalPath.Length == 0) {
1105                     finalPath.Append(paths[i]);
1106                 } else {
1107                     char ch = finalPath[finalPath.Length - 1];
1108                     if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) {
1109                         finalPath.Append(DirectorySeparatorChar);
1110                     }
1111
1112                     finalPath.Append(paths[i]);
1113                 }
1114             }
1115
1116             return StringBuilderCache.GetStringAndRelease(finalPath);
1117         }
1118
1119         private static String CombineNoChecks(String path1, String path2) {
1120             if (path2.Length == 0)
1121                 return path1;
1122
1123             if (path1.Length == 0)
1124                 return path2;
1125                 
1126             if (IsPathRooted(path2))
1127                 return path2;
1128
1129             char ch = path1[path1.Length - 1];
1130             if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) 
1131                 return path1 + DirectorySeparatorCharAsString + path2;
1132             return path1 + path2;
1133         }
1134
1135         private static readonly Char[] s_Base32Char   = {
1136                 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 
1137                 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
1138                 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 
1139                 'y', 'z', '0', '1', '2', '3', '4', '5'};
1140
1141         internal static String ToBase32StringSuitableForDirName(byte[] buff)
1142         {
1143             // This routine is optimised to be used with buffs of length 20
1144             Contract.Assert(((buff.Length % 5) == 0), "Unexpected hash length");
1145
1146             StringBuilder sb = StringBuilderCache.Acquire();
1147             byte b0, b1, b2, b3, b4;
1148             int  l, i;
1149     
1150             l = buff.Length;
1151             i = 0;
1152
1153             // Create l chars using the last 5 bits of each byte.  
1154             // Consume 3 MSB bits 5 bytes at a time.
1155
1156             do
1157             {
1158                 b0 = (i < l) ? buff[i++] : (byte)0;
1159                 b1 = (i < l) ? buff[i++] : (byte)0;
1160                 b2 = (i < l) ? buff[i++] : (byte)0;
1161                 b3 = (i < l) ? buff[i++] : (byte)0;
1162                 b4 = (i < l) ? buff[i++] : (byte)0;
1163
1164                 // Consume the 5 Least significant bits of each byte
1165                 sb.Append(s_Base32Char[b0 & 0x1F]);
1166                 sb.Append(s_Base32Char[b1 & 0x1F]);
1167                 sb.Append(s_Base32Char[b2 & 0x1F]);
1168                 sb.Append(s_Base32Char[b3 & 0x1F]);
1169                 sb.Append(s_Base32Char[b4 & 0x1F]);
1170     
1171                 // Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4
1172                 sb.Append(s_Base32Char[(
1173                         ((b0 & 0xE0) >> 5) | 
1174                         ((b3 & 0x60) >> 2))]);
1175     
1176                 sb.Append(s_Base32Char[(
1177                         ((b1 & 0xE0) >> 5) | 
1178                         ((b4 & 0x60) >> 2))]);
1179     
1180                 // Consume 3 MSB bits of b2, 1 MSB bit of b3, b4
1181                 
1182                 b2 >>= 5;
1183     
1184                 Contract.Assert(((b2 & 0xF8) == 0), "Unexpected set bits");
1185     
1186                 if ((b3 & 0x80) != 0)
1187                     b2 |= 0x08;
1188                 if ((b4 & 0x80) != 0)
1189                     b2 |= 0x10;
1190     
1191                 sb.Append(s_Base32Char[b2]);
1192
1193             } while (i < l);
1194
1195             return StringBuilderCache.GetStringAndRelease(sb);
1196         }
1197
1198         // ".." can only be used if it is specified as a part of a valid File/Directory name. We disallow
1199         //  the user being able to use it to move up directories. Here are some examples eg 
1200         //    Valid: a..b  abc..d
1201         //    Invalid: ..ab   ab..  ..   abc..d\abc..
1202         //
1203         internal static void CheckSearchPattern(String searchPattern)
1204         {
1205             int index;
1206             while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) {
1207                     
1208                  if (index + 2 == searchPattern.Length) // Terminal ".." . Files names cannot end in ".."
1209                     throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
1210                 
1211                  if ((searchPattern[index+2] ==  DirectorySeparatorChar)
1212                     || (searchPattern[index+2] == AltDirectorySeparatorChar))
1213                     throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
1214                 
1215                 searchPattern = searchPattern.Substring(index + 2);
1216             }
1217
1218         }
1219
1220         internal static bool HasIllegalCharacters(String path, bool checkAdditional)
1221         {
1222             Contract.Requires(path != null);
1223
1224             if (checkAdditional)
1225             {
1226                 return path.IndexOfAny(InvalidPathCharsWithAdditionalChecks) >= 0;
1227             }
1228
1229             return path.IndexOfAny(RealInvalidPathChars) >= 0;
1230         }
1231
1232         internal static void CheckInvalidPathChars(String path, bool checkAdditional = false)
1233         {
1234             if (path == null)
1235                 throw new ArgumentNullException("path");
1236
1237             if (Path.HasIllegalCharacters(path, checkAdditional))
1238                 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
1239         }
1240
1241         
1242         internal static String InternalCombine(String path1, String path2) {
1243             if (path1==null || path2==null)
1244                 throw new ArgumentNullException((path1==null) ? "path1" : "path2");
1245             Contract.EndContractBlock();
1246             CheckInvalidPathChars(path1);
1247             CheckInvalidPathChars(path2);
1248             
1249             if (path2.Length == 0)
1250                 throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), "path2");
1251             if (IsPathRooted(path2))
1252                 throw new ArgumentException(Environment.GetResourceString("Arg_Path2IsRooted"), "path2");
1253             int i = path1.Length;
1254             if (i == 0) return path2;
1255             char ch = path1[i - 1];
1256             if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) 
1257                 return path1 + DirectorySeparatorCharAsString + path2;
1258             return path1 + path2;
1259         }
1260             
1261     }
1262 }