The Labs \ Source Viewer \ SSCLI \ System.IO \ Path

  1. // ==++==
  2. //
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. //
  14. // ==--==
  15. /*============================================================
  16. **
  17. ** Class:  Path
  18. **
  19. **
  20. ** Purpose: A collection of path manipulation methods.
  21. **
  22. **
  23. ===========================================================*/
  24. using System;
  25. using System.Security.Permissions;
  26. using Win32Native = Microsoft.Win32.Win32Native;
  27. using System.Text;
  28. using System.Runtime.InteropServices;
  29. using System.Security;
  30. using System.Security.Cryptography;
  31. using System.Runtime.CompilerServices;
  32. using System.Globalization;
  33. using System.Runtime.Versioning;
  34. namespace System.IO
  35. {
  36.     // Provides methods for processing directory strings in an ideally
  37.     // cross-platform manner. Most of the methods don't do a complete
  38.     // full parsing (such as examining a UNC hostname), but they will
  39.     // handle most string operations.
  40.     //
  41.     // File names cannot contain backslash (\), slash (/), colon (:),
  42.     // asterick (*), question mark (?), quote ("), less than (<;),
  43.     // greater than (>;), or pipe (|). The first three are used as directory
  44.     // separators on various platforms. Asterick and question mark are treated
  45.     // as wild cards. Less than, Greater than, and pipe all redirect input
  46.     // or output from a program to a file or some combination thereof. Quotes
  47.     // are special.
  48.     //
  49.     // We are guaranteeing that Path.SeparatorChar is the correct
  50.     // directory separator on all platforms, and we will support
  51.     // Path.AltSeparatorChar as well. To write cross platform
  52.     // code with minimal pain, you can use slash (/) as a directory separator in
  53.     // your strings.
  54.     // Class contains only static data, no need to serialize
  55.     [ComVisible(true)]
  56.     public static class Path
  57.     {
  58.         // Platform specific directory separator character. This is backslash
  59.         // ('\') on Windows, slash ('/') on Unix, and colon (':') on Mac.
  60.         //
  61.         #if !PLATFORM_UNIX
  62.         public static readonly char DirectorySeparatorChar = '\\';
  63.         #else
  64.         public static readonly char DirectorySeparatorChar = '/';
  65.         #endif // !PLATFORM_UNIX
  66.        
  67.         // Platform specific alternate directory separator character.
  68.         // This is backslash ('\') on Unix, and slash ('/') on Windows
  69.         // and MacOS.
  70.         //
  71.         #if !PLATFORM_UNIX
  72.         public static readonly char AltDirectorySeparatorChar = '/';
  73.         #else
  74.         public static readonly char AltDirectorySeparatorChar = '\\';
  75.         #endif // !PLATFORM_UNIX
  76.        
  77.         // Platform specific volume separator character. This is colon (':')
  78.         // on Windows and MacOS, and slash ('/') on Unix. This is mostly
  79.         // useful for parsing paths like "c:\windows" or "MacVolume:System Folder".
  80.         //
  81.         #if !PLATFORM_UNIX
  82.         public static readonly char VolumeSeparatorChar = ':';
  83.         #else
  84.         public static readonly char VolumeSeparatorChar = '/';
  85.         #endif // !PLATFORM_UNIX
  86.        
  87.         // Platform specific invalid list of characters in a path.
  88.         // See the "Naming a File" MSDN conceptual docs for more details on
  89.         // what is valid in a file name (which is slightly different from what
  90.         // is legal in a path name).
  91.         // Note: This list is duplicated in CheckInvalidPathChars
  92.         [Obsolete("Please use GetInvalidPathChars or GetInvalidFileNameChars instead.")]
  93.         public static readonly char[] InvalidPathChars = {'"', '<', '>', '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5,
  94.         (char)6, (char)7, (char)8, (char)9, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15,
  95.         (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25,
  96.         (char)26, (char)27, (char)28, (char)29, (char)30, (char)31};
  97.        
  98.        
  99.         private static readonly char[] RealInvalidPathChars = {'"', '<', '>', '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5,
  100.         (char)6, (char)7, (char)8, (char)9, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15,
  101.         (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25,
  102.         (char)26, (char)27, (char)28, (char)29, (char)30, (char)31};
  103.        
  104.         private static readonly char[] InvalidFileNameChars = {'"', '<', '>', '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5,
  105.         (char)6, (char)7, (char)8, (char)9, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15,
  106.         (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25,
  107.         (char)26, (char)27, (char)28, (char)29, (char)30, (char)31, ':', '*', '?', '\\',
  108.         '/'};
  109.        
  110.         #if !PLATFORM_UNIX
  111.         public static readonly char PathSeparator = ';';
  112.         #else
  113.         public static readonly char PathSeparator = ':';
  114.         #endif // !PLATFORM_UNIX
  115.        
  116.         // Make this public sometime.
  117.         // MaxPath accounts for the null-terminating character, for example, the maximum path on the D drive is "D:<256 chars>\0".
  118.         static internal readonly int MaxPath = 260;
  119.        
  120.         // Changes the extension of a file path. The path parameter
  121.         // specifies a file path, and the extension parameter
  122.         // specifies a file extension (with a leading period, such as
  123.         // ".exe" or ".cs").
  124.         //
  125.         // The function returns a file path with the same root, directory, and base
  126.         // name parts as path, but with the file extension changed to
  127.         // the specified extension. If path is null, the function
  128.         // returns null. If path does not contain a file extension,
  129.         // the new file extension is appended to the path. If extension
  130.         // is null, any exsiting extension is removed from path.
  131.         //
  132.         public static string ChangeExtension(string path, string extension)
  133.         {
  134.             if (path != null) {
  135.                 CheckInvalidPathChars(path);
  136.                
  137.                 string s = path;
  138.                 for (int i = path.Length; --i >= 0;) {
  139.                     char ch = path[i];
  140.                     if (ch == '.') {
  141.                         s = path.Substring(0, i);
  142.                         break;
  143.                     }
  144.                     if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
  145.                         break;
  146.                 }
  147.                 if (extension != null && path.Length != 0) {
  148.                     if (extension.Length == 0 || extension[0] != '.') {
  149.                         s = s + ".";
  150.                     }
  151.                     s = s + extension;
  152.                 }
  153.                 return s;
  154.             }
  155.             return null;
  156.         }
  157.        
  158.        
  159.         // Returns the directory path of a file path. This method effectively
  160.         // removes the last element of the given file path, i.e. it returns a
  161.         // string consisting of all characters up to but not including the last
  162.         // backslash ("\") in the file path. The returned value is null if the file
  163.         // path is null or if the file path denotes a root (such as "\", "C:", or
  164.         // "\\server\share").
  165.         //
  166.         public static string GetDirectoryName(string path)
  167.         {
  168.             if (path != null) {
  169.                 CheckInvalidPathChars(path);
  170.                 path = FixupPath(path);
  171.                 int root = GetRootLength(path);
  172.                 int i = path.Length;
  173.                 if (i > root) {
  174.                     i = path.Length;
  175.                     if (i == root)
  176.                         return null;
  177.                     while (i > root && path[--i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar)
  178.                         ;
  179.                     return path.Substring(0, i);
  180.                 }
  181.             }
  182.             return null;
  183.         }
  184.        
  185.         // Gets the length of the root DirectoryInfo or whatever DirectoryInfo markers
  186.         // are specified for the first part of the DirectoryInfo name.
  187.         //
  188.         static internal int GetRootLength(string path)
  189.         {
  190.             CheckInvalidPathChars(path);
  191.            
  192.             int i = 0;
  193.             int length = path.Length;
  194.            
  195.             #if !PLATFORM_UNIX
  196.             if (length >= 1 && (IsDirectorySeparator(path[0]))) {
  197.                 // handles UNC names and directories off current drive's root.
  198.                 i = 1;
  199.                 if (length >= 2 && (IsDirectorySeparator(path[1]))) {
  200.                     i = 2;
  201.                     int n = 2;
  202.                     while (i < length && ((path[i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar) || --n > 0))
  203.                         i++;
  204.                 }
  205.             }
  206.             else if (length >= 2 && path[1] == VolumeSeparatorChar) {
  207.                 // handles A:\foo.
  208.                 i = 2;
  209.                 if (length >= 3 && (IsDirectorySeparator(path[2])))
  210.                     i++;
  211.             }
  212.             return i;
  213.             #else
  214.             if (length >= 1 && (IsDirectorySeparator(path[0]))) {
  215.                 i = 1;
  216.             }
  217.             return i;
  218.             #endif // !PLATFORM_UNIX
  219.         }
  220.        
  221.         static internal bool IsDirectorySeparator(char c)
  222.         {
  223.             return (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar);
  224.         }
  225.        
  226.        
  227.         public static char[] GetInvalidPathChars()
  228.         {
  229.             return (char[])RealInvalidPathChars.Clone();
  230.         }
  231.        
  232.         public static char[] GetInvalidFileNameChars()
  233.         {
  234.             return (char[])InvalidFileNameChars.Clone();
  235.         }
  236.        
  237.         // Returns the extension of the given path. The returned value includes the
  238.         // period (".") character of the extension except when you have a terminal period when you get String.Empty, such as ".exe" or
  239.         // ".cpp". The returned value is null if the given path is
  240.         // null or if the given path does not include an extension.
  241.         //
  242.         public static string GetExtension(string path)
  243.         {
  244.             if (path == null)
  245.                 return null;
  246.            
  247.             CheckInvalidPathChars(path);
  248.             int length = path.Length;
  249.             for (int i = length; --i >= 0;) {
  250.                 char ch = path[i];
  251.                 if (ch == '.') {
  252.                     if (i != length - 1)
  253.                         return path.Substring(i, length - i);
  254.                     else
  255.                         return String.Empty;
  256.                 }
  257.                 if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
  258.                     break;
  259.             }
  260.             return String.Empty;
  261.         }
  262.        
  263.         // Expands the given path to a fully qualified path. The resulting string
  264.         // consists of a drive letter, a colon, and a root relative path. This
  265.         // function does not verify that the resulting path
  266.         // refers to an existing file or directory on the associated volume.
  267.         [ResourceExposure(ResourceScope.Machine)]
  268.         [ResourceConsumption(ResourceScope.Machine)]
  269.         public static string GetFullPath(string path)
  270.         {
  271.             string fullPath = GetFullPathInternal(path);
  272.             new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new string[] {fullPath}, false, false).Demand();
  273.             return fullPath;
  274.         }
  275.        
  276.         // This method is package access to let us quickly get a string name
  277.         // while avoiding a security check. This also serves a slightly
  278.         // different purpose - when we open a file, we need to resolve the
  279.         // path into a fully qualified, non-relative path name. This
  280.         // method does that, finding the current drive &; directory. But
  281.         // as long as we don't return this info to the user, we're good. However,
  282.         // the public GetFullPath does need to do a security check.
  283.         [ResourceExposure(ResourceScope.Machine)]
  284.         [ResourceConsumption(ResourceScope.Machine)]
  285.         static internal string GetFullPathInternal(string path)
  286.         {
  287.             if (path == null)
  288.                 throw new ArgumentNullException("path");
  289.            
  290.             string newPath = NormalizePath(path, true);
  291.            
  292.             return newPath;
  293.         }
  294.        
  295.         [ResourceExposure(ResourceScope.Machine)]
  296.         [ResourceConsumption(ResourceScope.Machine)]
  297.         static internal string NormalizePath(string path, bool fullCheck)
  298.         {
  299.             return NormalizePathFast(path, fullCheck);
  300.         }
  301.        
  302.         [ResourceExposure(ResourceScope.Machine)]
  303.         [ResourceConsumption(ResourceScope.Machine)]
  304.         static internal string NormalizePathSlow(string path, bool fullCheck)
  305.         {
  306.             BCLDebug.Assert(path != null, "path can't be null");
  307.             // If we're doing a full path check, trim whitespace and look for
  308.             // illegal path characters.
  309.            
  310.             if (fullCheck) {
  311.                 // Trim whitespace off the end of the string.
  312.                 path = path.TrimEnd();
  313.                
  314.                 // Look for illegal path characters.
  315.                 CheckInvalidPathChars(path);
  316.             }
  317.            
  318.             int index = 0;
  319.             char[] newBuffer = new char[MaxPath];
  320.             int newBufferIndex = 0;
  321.             char[] finalBuffer = null;
  322.             uint numSpaces = 0;
  323.             uint numDots = 0;
  324.             bool fixupDirectorySeparator = false;
  325.             // Number of significant chars other than potentially suppressible
  326.             // dots and spaces since the last directory or volume separator char
  327.             uint numSigChars = 0;
  328.             int lastSigChar = -1;
  329.             // Index of last significant character.
  330.             // Whether this segment of the path (not the complete path) started
  331.             // with a volume separator char. Reject "c:...".
  332.             bool startedWithVolumeSeparator = false;
  333.             bool firstSegment = true;
  334.             bool mightBeShortFileName = false;
  335.            
  336.             #if !PLATFORM_UNIX
  337.             // Win9x fixup - //server/share becomes c://server/share.
  338.             // This prevents our code from turning "\\server" into "\server".
  339.             // On Win9x, //server/share becomes c://server/share
  340.             if (path.Length > 0 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) {
  341.                 newBuffer[newBufferIndex++] = '\\';
  342.                 index++;
  343.                 lastSigChar = 0;
  344.             }
  345.             #endif
  346.            
  347.             // Normalize the string, stripping out redundant dots, spaces, and
  348.             // slashes.
  349.             while (index < path.Length) {
  350.                 char currentChar = path[index];
  351.                
  352.                 // We handle both directory separators and dots specially. For
  353.                 // directory separators, we consume consecutive appearances.
  354.                 // For dots, we consume all dots beyond the second in
  355.                 // succession. All other characters are added as is. In
  356.                 // addition we consume all spaces after the last other char
  357.                 // in a directory name up until the directory separator.
  358.                
  359.                 if (currentChar == DirectorySeparatorChar || currentChar == AltDirectorySeparatorChar) {
  360.                     // If we have a path like "123.../foo", remove the trailing dots.
  361.                     // However, if we found "c:\temp\..\bar" or "c:\temp\...\bar", don't.
  362.                     // Also remove trailing spaces from both files & directory names.
  363.                     // This was agreed on with the OS team to fix undeletable directory
  364.                     // names ending in spaces.
  365.                    
  366.                     // If we saw a '\' as the previous last significant character and
  367.                     // are simply going to write out dots, suppress them.
  368.                     // If we only contain dots and slashes though, only allow
  369.                     // a string like [dot]+ [space]*. Ignore everything else.
  370.                     // Legal: "\.. \", "\...\", "\. \"
  371.                     // Illegal: "\.. .\", "\. .\", "\ .\"
  372.                     if (numSigChars == 0) {
  373.                         // Dot and space handling
  374.                         if (numDots > 0) {
  375.                             // Look for ".[space]*" or "..[space]*"
  376.                             int start = lastSigChar + 1;
  377.                             if (path[start] != '.')
  378.                                 throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  379.                            
  380.                             // Only allow "[dot]+[space]*", and normalize the
  381.                             // legal ones to "." or ".."
  382.                             if (numDots >= 2) {
  383.                                 // Reject "C:..."
  384.                                 if (startedWithVolumeSeparator && numDots > 2)
  385.                                    
  386.                                     throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  387.                                
  388.                                 if (path[start + 1] == '.') {
  389.                                     // Search for a space in the middle of the
  390.                                     // dots and throw
  391.                                     for (int i = start + 2; i < start + numDots; i++) {
  392.                                         if (path[i] != '.')
  393.                                             throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  394.                                     }
  395.                                    
  396.                                     numDots = 2;
  397.                                 }
  398.                                 else {
  399.                                     if (numDots > 1)
  400.                                         throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  401.                                     numDots = 1;
  402.                                 }
  403.                             }
  404.                            
  405.                             if (newBufferIndex + numDots + 1 >= MaxPath)
  406.                                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  407.                            
  408.                             if (numDots == 2) {
  409.                                 newBuffer[newBufferIndex++] = '.';
  410.                             }
  411.                            
  412.                             newBuffer[newBufferIndex++] = '.';
  413.                             fixupDirectorySeparator = false;
  414.                            
  415.                             // Continue in this case, potentially writing out '\'.
  416.                         }
  417.                        
  418.                         if (numSpaces > 0 && firstSegment) {
  419.                             // Handle strings like " \\server\share".
  420.                             if (index + 1 < path.Length && (path[index + 1] == DirectorySeparatorChar || path[index + 1] == AltDirectorySeparatorChar)) {
  421.                                 newBuffer[newBufferIndex++] = DirectorySeparatorChar;
  422.                             }
  423.                         }
  424.                     }
  425.                     numDots = 0;
  426.                     numSpaces = 0;
  427.                     // Suppress trailing spaces
  428.                     if (!fixupDirectorySeparator) {
  429.                         fixupDirectorySeparator = true;
  430.                        
  431.                         if (newBufferIndex + 1 >= MaxPath)
  432.                             throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  433.                        
  434.                         newBuffer[newBufferIndex++] = DirectorySeparatorChar;
  435.                     }
  436.                     numSigChars = 0;
  437.                     lastSigChar = index;
  438.                     startedWithVolumeSeparator = false;
  439.                     firstSegment = false;
  440.                    
  441.                     #if !PLATFORM_UNIX
  442.                     // For short file names, we must try to expand each of them as
  443.                     // soon as possible. We need to allow users to specify a file
  444.                     // name that doesn't exist using a path with short file names
  445.                     // in it, such as this for a temp file we're trying to create:
  446.                     // C:\DOCUME~1\USERNA~1.RED\LOCALS~1\Temp\bg3ylpzp
  447.                     // We could try doing this afterwards piece by piece, but it's
  448.                     // probably a lot simpler to do it here.
  449.                     if (mightBeShortFileName) {
  450.                         newBuffer[newBufferIndex] = '\0';
  451.                         TryExpandShortFileName(newBuffer, ref newBufferIndex, MAX_PATH);
  452.                         mightBeShortFileName = false;
  453.                     }
  454.                     #endif
  455.                 }
  456.                 // if (Found directory separator)
  457.                 else if (currentChar == '.') {
  458.                     // Reduce only multiple .'s only after slash to 2 dots. For
  459.                     // instance a...b is a valid file name.
  460.                     numDots++;
  461.                     // Don't flush out non-terminal spaces here, because they may in
  462.                     // the end not be significant. Turn "c:\ . .\foo" -> "c:\foo"
  463.                     // which is the conclusion of removing trailing dots & spaces,
  464.                     // as well as folding multiple '\' characters.
  465.                 }
  466.                 else if (currentChar == ' ') {
  467.                     numSpaces++;
  468.                 }
  469.                 else {
  470.                     // Normal character logic
  471.                     #if !PLATFORM_UNIX
  472.                     if (currentChar == '~')
  473.                         mightBeShortFileName = true;
  474.                     #endif
  475.                    
  476.                     fixupDirectorySeparator = false;
  477.                    
  478.                     #if !PLATFORM_UNIX
  479.                     // To reject strings like "C:...\foo" and "C :\foo"
  480.                     if (firstSegment && currentChar == VolumeSeparatorChar) {
  481.                         // Only accept "C:", not "c :" or ":"
  482.                         // Get a drive letter or ' ' if index is 0.
  483.                         char driveLetter = (index > 0) ? path[index - 1] : ' ';
  484.                         bool validPath = ((numDots == 0) && (numSigChars >= 1) && (driveLetter != ' '));
  485.                         if (!validPath)
  486.                             throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  487.                        
  488.                         startedWithVolumeSeparator = true;
  489.                         // We need special logic to make " c:" work, we should not fix paths like " foo::$DATA"
  490.                         if (numSigChars > 1) {
  491.                             // Common case, simply do nothing
  492.                             uint spaceCount = 0;
  493.                             // How many spaces did we write out, numSpaces has already been reset.
  494.                             while ((spaceCount < newBufferIndex) && newBuffer[spaceCount] == ' ')
  495.                                 spaceCount++;
  496.                             if (numSigChars - spaceCount == 1) {
  497.                                 newBuffer[0] = driveLetter;
  498.                                 // Overwrite spaces, we need a special case to not break " foo" as a relative path.
  499.                                 newBufferIndex = 1;
  500.                             }
  501.                         }
  502.                         numSigChars = 0;
  503.                     }
  504.                     #endif // !PLATFORM_UNIX
  505.                     else {
  506.                         numSigChars += 1 + numDots + numSpaces;
  507.                     }
  508.                    
  509.                     // Copy any spaces & dots since the last significant character
  510.                     // to here. Note we only counted the number of dots & spaces,
  511.                     // and don't know what order they're in. Hence the copy.
  512.                     if (numDots > 0 || numSpaces > 0) {
  513.                         int numCharsToCopy = (lastSigChar >= 0) ? index - lastSigChar - 1 : index;
  514.                         if (numCharsToCopy > 0) {
  515.                             path.CopyTo(lastSigChar + 1, newBuffer, newBufferIndex, numCharsToCopy);
  516.                             newBufferIndex += numCharsToCopy;
  517.                         }
  518.                         numDots = 0;
  519.                         numSpaces = 0;
  520.                     }
  521.                    
  522.                     if (newBufferIndex + 1 >= MaxPath)
  523.                         throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  524.                    
  525.                     newBuffer[newBufferIndex++] = currentChar;
  526.                     lastSigChar = index;
  527.                 }
  528.                
  529.                 index++;
  530.             }
  531.             // end while
  532.             // Drop any trailing dots and spaces from file & directory names, EXCEPT
  533.             // we MUST make sure that "C:\foo\.." is correctly handled.
  534.             // Also handle "C:\foo\." -> "C:\foo", while "C:\." -> "C:\"
  535.             if (numSigChars == 0) {
  536.                 if (numDots > 0) {
  537.                     // Look for ".[space]*" or "..[space]*"
  538.                     int start = lastSigChar + 1;
  539.                     if (path[start] != '.')
  540.                         throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  541.                    
  542.                     // Only allow "[dot]+[space]*", and normalize the
  543.                     // legal ones to "." or ".."
  544.                     if (numDots >= 2) {
  545.                         // Reject "C:..."
  546.                         if (startedWithVolumeSeparator && numDots > 2)
  547.                             throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  548.                        
  549.                         if (path[start + 1] == '.') {
  550.                             // Search for a space in the middle of the
  551.                             // dots and throw
  552.                             for (int i = start + 2; i < start + numDots; i++) {
  553.                                 if (path[i] != '.')
  554.                                     throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  555.                             }
  556.                            
  557.                             numDots = 2;
  558.                         }
  559.                         else {
  560.                             if (numDots > 1)
  561.                                 throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  562.                             numDots = 1;
  563.                         }
  564.                     }
  565.                    
  566.                     if (newBufferIndex + numDots >= MaxPath)
  567.                         throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  568.                    
  569.                     if (numDots == 2) {
  570.                         newBuffer[newBufferIndex++] = '.';
  571.                     }
  572.                    
  573.                     newBuffer[newBufferIndex++] = '.';
  574.                 }
  575.             }
  576.             // if (numSigChars == 0)
  577.             // If we ended up eating all the characters, bail out.
  578.             if (newBufferIndex == 0)
  579.                 throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  580.            
  581.             BCLDebug.Assert(newBufferIndex <= MaxPath, "Overflowed temporary path buffer");
  582.             newBuffer[newBufferIndex] = '\0';
  583.            
  584.             // Disallow URL's here. Some of our other Win32 API calls will reject
  585.             // them later, so we might be better off rejecting them here.
  586.             // Note we've probably turned them into "file:\D:\foo.tmp" by now.
  587.             // But for compatibility, ensure that callers that aren't doing a
  588.             // full check aren't rejected here.
  589.             if (fullCheck && (CharArrayStartsWithOrdinal(newBuffer, newBufferIndex, "http:", false) || CharArrayStartsWithOrdinal(newBuffer, newBufferIndex, "file:", false)))
  590.                 throw new ArgumentException(Environment.GetResourceString("Argument_PathUriFormatNotSupported"));
  591.            
  592.             #if !PLATFORM_UNIX
  593.             // If the last part of the path (file or directory name) had a tilde,
  594.             // expand that too.
  595.             if (mightBeShortFileName) {
  596.                 TryExpandShortFileName(newBuffer, ref newBufferIndex, MaxPath);
  597.             }
  598.             #endif
  599.            
  600.             // Call the Win32 API to do the final canonicalization step.
  601.             int result = 1;
  602.             char[] pFinal;
  603.             int len;
  604.            
  605.             if (fullCheck) {
  606.                
  607.                 finalBuffer = new char[MaxPath + 1];
  608.                 result = Win32Native.GetFullPathName(newBuffer, MaxPath + 1, finalBuffer, IntPtr.Zero);
  609.                
  610.                 // If success, the return buffer length does not account for the terminating null character.
  611.                 // If failure, the return buffer length does account for the path + the terminating null character.
  612.                 if (result > MaxPath) {
  613.                     finalBuffer = new char[result];
  614.                     result = Win32Native.GetFullPathName(newBuffer, result, finalBuffer, IntPtr.Zero);
  615.                    
  616.                     // Fullpath is genuinely long
  617.                     if (result > MaxPath)
  618.                         throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  619.                 }
  620.                
  621.                 BCLDebug.Assert(result <= MaxPath, "did we accidently remove a PathTooLongException check?");
  622.                 if (result == 0 && newBuffer[0] != '\0') {
  623.                     __Error.WinIOError();
  624.                 }
  625.                 else if (result < MaxPath)
  626.                     // May be necessary for empty strings
  627.                     finalBuffer[result] = '\0';
  628.                 pFinal = finalBuffer;
  629.                 len = result;
  630.                
  631.                 #if !PLATFORM_UNIX
  632.                 // If we called GetFullPathName with something like "foo" and our
  633.                 // command window was in short file name mode (ie, by running edlin or
  634.                 // DOS versions of grep, etc), we might have gotten back a short file
  635.                 // name. So, check to see if we need to expand it.
  636.                 mightBeShortFileName = false;
  637.                 for (uint i = 0; i < len && !mightBeShortFileName; i++) {
  638.                     if (finalBuffer[i] == '~')
  639.                         mightBeShortFileName = true;
  640.                 }
  641.                 if (mightBeShortFileName) {
  642.                     bool r = TryExpandShortFileName(finalBuffer, ref len, MaxPath);
  643.                     if (!r) {
  644.                         int lastSlash = Array.LastIndexOf(finalBuffer, DirectorySeparatorChar, len - 1, len);
  645.                         if (lastSlash >= 0) {
  646.                             BCLDebug.Assert(lastSlash < len, "path unexpectedly ended in a ''");
  647.                             char[] savedName = new char[len - lastSlash - 1];
  648.                             Array.Copy(finalBuffer, lastSlash + 1, savedName, 0, len - lastSlash - 1);
  649.                             finalBuffer[lastSlash] = '\0';
  650.                             r = TryExpandShortFileName(finalBuffer, ref lastSlash, MaxPath);
  651.                            
  652.                             // Clean up changes made to the finalBuffer.
  653.                             finalBuffer[lastSlash] = DirectorySeparatorChar;
  654.                            
  655.                             Array.Copy(savedName, 0, finalBuffer, lastSlash + 1, savedName.Length);
  656.                             if (r)
  657.                                 len = lastSlash + 1 + savedName.Length;
  658.                         }
  659.                     }
  660.                 }
  661.                 #endif
  662.             }
  663.             else {
  664.                 pFinal = newBuffer;
  665.                 len = newBufferIndex;
  666.             }
  667.            
  668.             if (result != 0) {
  669.                 /* Throw an ArgumentException for paths like \\, \\server, \\server\
  670.                   This check can only be properly done after normalizing, so
  671.                   \\foo\.. will be properly rejected.  Also, reject \\?\GLOBALROOT\
  672.                   (an internal kernel path) because it provides aliases for drives. */               
  673. if (pFinal[0] == '\\' && pFinal[1] == '\\') {
  674.                     int startIndex = 2;
  675.                     while (startIndex < result) {
  676.                         if (pFinal[startIndex] == '\\') {
  677.                             startIndex++;
  678.                             break;
  679.                         }
  680.                         else {
  681.                             startIndex++;
  682.                         }
  683.                     }
  684.                     if (startIndex == result)
  685.                         throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC"));
  686.                    
  687.                     // Check for \\?\Globalroot, an internal mechanism to the kernel
  688.                     // that provides aliases for drives and other undocumented stuff.
  689.                     // The kernel team won't even describe the full set of what
  690.                     // is available here - we don't want managed apps mucking
  691.                     // with this for security reasons.
  692.                     if (CharArrayStartsWithOrdinal(pFinal, len, "\\\\?\\globalroot", true))
  693.                         throw new ArgumentException(Environment.GetResourceString("Arg_PathGlobalRoot"));
  694.                 }
  695.             }
  696.            
  697.             // Check our result and form the managed string as necessary.
  698.            
  699.             if (len >= MaxPath)
  700.                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  701.            
  702.             if (result == 0) {
  703.                 int errorCode = Marshal.GetLastWin32Error();
  704.                 if (errorCode == 0)
  705.                     errorCode = Win32Native.ERROR_BAD_PATHNAME;
  706.                 __Error.WinIOError(errorCode, path);
  707.                 return null;
  708.                 // Unreachable - silence a compiler error.
  709.             }
  710.            
  711.             return new string(pFinal, 0, len);
  712.         }
  713.        
  714.         private static bool CharArrayStartsWithOrdinal(char[] array, int numChars, string compareTo, bool ignoreCase)
  715.         {
  716.             if (numChars < compareTo.Length)
  717.                 return false;
  718.            
  719.             if (ignoreCase) {
  720.                 string s = new string(array, 0, compareTo.Length);
  721.                 return compareTo.Equals(s, StringComparison.OrdinalIgnoreCase);
  722.             }
  723.             else {
  724.                 for (int i = 0; i < compareTo.Length; i++) {
  725.                     if (array[i] != compareTo[i]) {
  726.                         return false;
  727.                     }
  728.                 }
  729.                 return true;
  730.             }
  731.         }
  732.        
  733.         [ResourceExposure(ResourceScope.Machine)]
  734.         [ResourceConsumption(ResourceScope.Machine)]
  735.         private static bool TryExpandShortFileName(char[] buffer, ref int bufferLength, int maxBufferSize)
  736.         {
  737.             BCLDebug.Assert(buffer != null, "buffer can't be null");
  738.            
  739.             // Allocate on the heap instead of the stack to simplify Win9x code path.
  740.             char[] shortFileNameBuffer = new char[MaxPath + 1];
  741.            
  742.             int r = Win32Native.GetLongPathName(buffer, shortFileNameBuffer, MaxPath);
  743.            
  744.             if (r >= MaxPath)
  745.                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  746.             if (r == 0) {
  747.                 // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a
  748.                 // path like \\.\PHYSICALDEVICE0 - some device driver doesn't
  749.                 // support GetLongPathName on that string. This behavior is
  750.                 // by design, according to the Core File Services team.
  751.                 // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs
  752.                 // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\
  753.                 return false;
  754.             }
  755.            
  756.            
  757.             Buffer.BlockCopy(shortFileNameBuffer, 0, buffer, 0, 2 * r);
  758.             bufferLength = r;
  759.             buffer[bufferLength] = '\0';
  760.             return true;
  761.         }
  762.        
  763.         // This will update the path stack pointer after checking to ensure the index is bounded with in MaxPath
  764.         unsafe private static void SafeSetStackPointerValue(char* buffer, int index, char value)
  765.         {
  766.             if (index >= MaxPath)
  767.                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  768.            
  769.             buffer[index] = value;
  770.         }
  771.        
  772.         [ResourceExposure(ResourceScope.Machine)]
  773.         [ResourceConsumption(ResourceScope.Machine)]
  774.         // This version allocates the path buffer on the stack instead of heap for perf
  775.         unsafe static internal string NormalizePathFast(string path, bool fullCheck)
  776.         {
  777.             BCLDebug.Assert(path != null, "path can't be null");
  778.             // If we're doing a full path check, trim whitespace and look for
  779.             // illegal path characters.
  780.            
  781.             // Win9x: to fixup path to replace multiple slashes with a single slash
  782.             if (fullCheck) {
  783.                 // Trim whitespace off the end of the string.
  784.                 path = path.TrimEnd();
  785.                
  786.                 // Look for illegal path characters.
  787.                 CheckInvalidPathChars(path);
  788.             }
  789.            
  790.             int index = 0;
  791.             // Allocating this on the stack instead of heap for workingset/perf gain.
  792.             // We need to be careful when we are indexing on the pointer as the operations
  793.             // are unsafe and not protected by bounds check.
  794.             char* newBuffer = stackalloc char[MaxPath];
  795.             int newBufferIndex = 0;
  796.             uint numSpaces = 0;
  797.             uint numDots = 0;
  798.             bool fixupDirectorySeparator = false;
  799.             // Number of significant chars other than potentially suppressible
  800.             // dots and spaces since the last directory or volume separator char
  801.             uint numSigChars = 0;
  802.             int lastSigChar = -1;
  803.             // Index of last significant character.
  804.             // Whether this segment of the path (not the complete path) started
  805.             // with a volume separator char. Reject "c:...".
  806.             bool startedWithVolumeSeparator = false;
  807.             bool firstSegment = true;
  808.             bool mightBeShortFileName = false;
  809.            
  810.             #if !PLATFORM_UNIX
  811.             // Win9x fixup - //server/share becomes c://server/share.
  812.             // This prevents our code from turning "\\server" into "\server".
  813.             // On Win9x, //server/share becomes c://server/share
  814.             if (path.Length > 0 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) {
  815.                 SafeSetStackPointerValue(newBuffer, newBufferIndex++, '\\');
  816.                 index++;
  817.                 lastSigChar = 0;
  818.             }
  819.             #endif
  820.            
  821.             // Normalize the string, stripping out redundant dots, spaces, and
  822.             // slashes.
  823.             while (index < path.Length) {
  824.                 char currentChar = path[index];
  825.                
  826.                 // We handle both directory separators and dots specially. For
  827.                 // directory separators, we consume consecutive appearances.
  828.                 // For dots, we consume all dots beyond the second in
  829.                 // succession. All other characters are added as is. In
  830.                 // addition we consume all spaces after the last other char
  831.                 // in a directory name up until the directory separator.
  832.                
  833.                 if (currentChar == DirectorySeparatorChar || currentChar == AltDirectorySeparatorChar) {
  834.                     // If we have a path like "123.../foo", remove the trailing dots.
  835.                     // However, if we found "c:\temp\..\bar" or "c:\temp\...\bar", don't.
  836.                     // Also remove trailing spaces from both files & directory names.
  837.                     // This was agreed on with the OS team to fix undeletable directory
  838.                     // names ending in spaces.
  839.                    
  840.                     // If we saw a '\' as the previous last significant character and
  841.                     // are simply going to write out dots, suppress them.
  842.                     // If we only contain dots and slashes though, only allow
  843.                     // a string like [dot]+ [space]*. Ignore everything else.
  844.                     // Legal: "\.. \", "\...\", "\. \"
  845.                     // Illegal: "\.. .\", "\. .\", "\ .\"
  846.                     if (numSigChars == 0) {
  847.                         // Dot and space handling
  848.                         if (numDots > 0) {
  849.                             // Look for ".[space]*" or "..[space]*"
  850.                             int start = lastSigChar + 1;
  851.                             if (path[start] != '.')
  852.                                 throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  853.                            
  854.                             // Only allow "[dot]+[space]*", and normalize the
  855.                             // legal ones to "." or ".."
  856.                             if (numDots >= 2) {
  857.                                 // Reject "C:..."
  858.                                 if (startedWithVolumeSeparator && numDots > 2)
  859.                                    
  860.                                     throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  861.                                
  862.                                 if (path[start + 1] == '.') {
  863.                                     // Search for a space in the middle of the
  864.                                     // dots and throw
  865.                                     for (int i = start + 2; i < start + numDots; i++) {
  866.                                         if (path[i] != '.')
  867.                                             throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  868.                                     }
  869.                                    
  870.                                     numDots = 2;
  871.                                 }
  872.                                 else {
  873.                                     if (numDots > 1)
  874.                                         throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  875.                                     numDots = 1;
  876.                                 }
  877.                             }
  878.                            
  879.                             if (numDots == 2) {
  880.                                 SafeSetStackPointerValue(newBuffer, newBufferIndex++, '.');
  881.                             }
  882.                            
  883.                             SafeSetStackPointerValue(newBuffer, newBufferIndex++, '.');
  884.                             fixupDirectorySeparator = false;
  885.                            
  886.                             // Continue in this case, potentially writing out '\'.
  887.                         }
  888.                        
  889.                         if (numSpaces > 0 && firstSegment) {
  890.                             // Handle strings like " \\server\share".
  891.                             if (index + 1 < path.Length && (path[index + 1] == DirectorySeparatorChar || path[index + 1] == AltDirectorySeparatorChar)) {
  892.                                 SafeSetStackPointerValue(newBuffer, newBufferIndex++, DirectorySeparatorChar);
  893.                             }
  894.                         }
  895.                     }
  896.                     numDots = 0;
  897.                     numSpaces = 0;
  898.                     // Suppress trailing spaces
  899.                     if (!fixupDirectorySeparator) {
  900.                         fixupDirectorySeparator = true;
  901.                         SafeSetStackPointerValue(newBuffer, newBufferIndex++, DirectorySeparatorChar);
  902.                     }
  903.                     numSigChars = 0;
  904.                     lastSigChar = index;
  905.                     startedWithVolumeSeparator = false;
  906.                     firstSegment = false;
  907.                    
  908.                     #if !PLATFORM_UNIX
  909.                     // For short file names, we must try to expand each of them as
  910.                     // soon as possible. We need to allow people to specify a file
  911.                     // name that doesn't exist using a path with short file names
  912.                     // in it, such as this for a temp file we're trying to create:
  913.                     // C:\DOCUME~1\USERNA~1.RED\LOCALS~1\Temp\bg3ylpzp
  914.                     // We could try doing this afterwards piece by piece, but it's
  915.                     // probably a lot simpler to do it here.
  916.                     if (mightBeShortFileName) {
  917.                         SafeSetStackPointerValue(newBuffer, newBufferIndex, '\0');
  918.                         TryExpandShortFileName(newBuffer, ref newBufferIndex, MAX_PATH);
  919.                         mightBeShortFileName = false;
  920.                     }
  921.                     #endif
  922.                 }
  923.                 // if (Found directory separator)
  924.                 else if (currentChar == '.') {
  925.                     // Reduce only multiple .'s only after slash to 2 dots. For
  926.                     // instance a...b is a valid file name.
  927.                     numDots++;
  928.                     // Don't flush out non-terminal spaces here, because they may in
  929.                     // the end not be significant. Turn "c:\ . .\foo" -> "c:\foo"
  930.                     // which is the conclusion of removing trailing dots & spaces,
  931.                     // as well as folding multiple '\' characters.
  932.                 }
  933.                 else if (currentChar == ' ') {
  934.                     numSpaces++;
  935.                 }
  936.                 else {
  937.                     // Normal character logic
  938.                     #if !PLATFORM_UNIX
  939.                     if (currentChar == '~')
  940.                         mightBeShortFileName = true;
  941.                     #endif
  942.                    
  943.                     fixupDirectorySeparator = false;
  944.                    
  945.                     #if !PLATFORM_UNIX
  946.                     // To reject strings like "C:...\foo" and "C :\foo"
  947.                     if (firstSegment && currentChar == VolumeSeparatorChar) {
  948.                         // Only accept "C:", not "c :" or ":"
  949.                         // Get a drive letter or ' ' if index is 0.
  950.                         char driveLetter = (index > 0) ? path[index - 1] : ' ';
  951.                         bool validPath = ((numDots == 0) && (numSigChars >= 1) && (driveLetter != ' '));
  952.                         if (!validPath)
  953.                             throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  954.                        
  955.                         startedWithVolumeSeparator = true;
  956.                         // We need special logic to make " c:" work, we should not fix paths like " foo::$DATA"
  957.                         if (numSigChars > 1) {
  958.                             // Common case, simply do nothing
  959.                             uint spaceCount = 0;
  960.                             // How many spaces did we write out, numSpaces has already been reset.
  961.                             while ((spaceCount < newBufferIndex) && newBuffer[spaceCount] == ' ')
  962.                                 spaceCount++;
  963.                             if (numSigChars - spaceCount == 1) {
  964.                                 //Safe to update stack ptr directly
  965.                                 newBuffer[0] = driveLetter;
  966.                                 // Overwrite spaces, we need a special case to not break " foo" as a relative path.
  967.                                 newBufferIndex = 1;
  968.                             }
  969.                         }
  970.                         numSigChars = 0;
  971.                     }
  972.                     #endif // !PLATFORM_UNIX
  973.                     else {
  974.                         numSigChars += 1 + numDots + numSpaces;
  975.                     }
  976.                    
  977.                     // Copy any spaces & dots since the last significant character
  978.                     // to here. Note we only counted the number of dots & spaces,
  979.                     // and don't know what order they're in. Hence the copy.
  980.                     if (numDots > 0 || numSpaces > 0) {
  981.                         int numCharsToCopy = (lastSigChar >= 0) ? index - lastSigChar - 1 : index;
  982.                         if (numCharsToCopy > 0) {
  983.                             for (int i = 0; i < numCharsToCopy; i++) {
  984.                                 SafeSetStackPointerValue(newBuffer, newBufferIndex++, path[lastSigChar + 1 + i]);
  985.                             }
  986.                         }
  987.                         numDots = 0;
  988.                         numSpaces = 0;
  989.                     }
  990.                    
  991.                     SafeSetStackPointerValue(newBuffer, newBufferIndex++, currentChar);
  992.                     lastSigChar = index;
  993.                 }
  994.                
  995.                 index++;
  996.             }
  997.             // end while
  998.             // Drop any trailing dots and spaces from file & directory names, EXCEPT
  999.             // we MUST make sure that "C:\foo\.." is correctly handled.
  1000.             // Also handle "C:\foo\." -> "C:\foo", while "C:\." -> "C:\"
  1001.             if (numSigChars == 0) {
  1002.                 if (numDots > 0) {
  1003.                     // Look for ".[space]*" or "..[space]*"
  1004.                     int start = lastSigChar + 1;
  1005.                     if (path[start] != '.')
  1006.                         throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  1007.                    
  1008.                     // Only allow "[dot]+[space]*", and normalize the
  1009.                     // legal ones to "." or ".."
  1010.                     if (numDots >= 2) {
  1011.                         // Reject "C:..."
  1012.                         if (startedWithVolumeSeparator && numDots > 2)
  1013.                             throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  1014.                        
  1015.                         if (path[start + 1] == '.') {
  1016.                             // Search for a space in the middle of the
  1017.                             // dots and throw
  1018.                             for (int i = start + 2; i < start + numDots; i++) {
  1019.                                 if (path[i] != '.')
  1020.                                     throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  1021.                             }
  1022.                            
  1023.                             numDots = 2;
  1024.                         }
  1025.                         else {
  1026.                             if (numDots > 1)
  1027.                                 throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  1028.                             numDots = 1;
  1029.                         }
  1030.                     }
  1031.                    
  1032.                     if (numDots == 2) {
  1033.                         SafeSetStackPointerValue(newBuffer, newBufferIndex++, '.');
  1034.                     }
  1035.                    
  1036.                     SafeSetStackPointerValue(newBuffer, newBufferIndex++, '.');
  1037.                 }
  1038.             }
  1039.             // if (numSigChars == 0)
  1040.             // If we ended up eating all the characters, bail out.
  1041.             if (newBufferIndex == 0)
  1042.                 throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
  1043.            
  1044.             // Null terminate the buffer. We will allow at most a path of length MaxPath-1 (accounting for the null terminating char).
  1045.             SafeSetStackPointerValue(newBuffer, newBufferIndex, '\0');
  1046.            
  1047.             // Disallow URL's here. Some of our other Win32 API calls will reject
  1048.             // them later, so we might be better off rejecting them here.
  1049.             // Note we've probably turned them into "file:\D:\foo.tmp" by now.
  1050.             // But for compatibility, ensure that callers that aren't doing a
  1051.             // full check aren't rejected here.
  1052.             if (fullCheck && (CharArrayStartsWithOrdinal(newBuffer, newBufferIndex, "http:", false) || CharArrayStartsWithOrdinal(newBuffer, newBufferIndex, "file:", false)))
  1053.                 throw new ArgumentException(Environment.GetResourceString("Argument_PathUriFormatNotSupported"));
  1054.            
  1055.             #if !PLATFORM_UNIX
  1056.             // If the last part of the path (file or directory name) had a tilde,
  1057.             // expand that too.
  1058.             if (mightBeShortFileName) {
  1059.                 TryExpandShortFileName(newBuffer, ref newBufferIndex, MaxPath);
  1060.             }
  1061.             #endif
  1062.            
  1063.             // Call the Win32 API to do the final canonicalization step.
  1064.             int result = 1;
  1065.             char* pFinal;
  1066.             int len;
  1067.            
  1068.             if (fullCheck) {
  1069.                
  1070.                
  1071.                 char* finalBuffer = stackalloc char[MaxPath + 1];
  1072.                 result = Win32Native.GetFullPathName(newBuffer, MaxPath + 1, finalBuffer, IntPtr.Zero);
  1073.                
  1074.                 // If success, the return buffer length does not account for the terminating null character.
  1075.                 // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character.
  1076.                 // If failure, the return buffer length is zero
  1077.                 if (result > MaxPath) {
  1078.                     char* tempBuffer = stackalloc char[result];
  1079.                     finalBuffer = tempBuffer;
  1080.                     result = Win32Native.GetFullPathName(newBuffer, result, finalBuffer, IntPtr.Zero);
  1081.                 }
  1082.                
  1083.                 // Fullpath is genuinely long
  1084.                 if (result >= MaxPath)
  1085.                     throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  1086.                
  1087.                 BCLDebug.Assert(result < MaxPath, "did we accidently remove a PathTooLongException check?");
  1088.                 if (result == 0 && newBuffer[0] != '\0') {
  1089.                     __Error.WinIOError();
  1090.                 }
  1091.                 else if (result < MaxPath) {
  1092.                     // Null terminate explicitly (may be only needed for some cases such as empty strings)
  1093.                     // GetFullPathName return length doesn't account for null terminating char...
  1094.                     finalBuffer[result] = '\0';
  1095.                     // Safe to write directly as result is < MaxPath
  1096.                 }
  1097.                
  1098.                 pFinal = finalBuffer;
  1099.                 // Doesn't account for null terminating char. Think of this as the last
  1100.                 // valid index into the buffer but not the length of the buffer
  1101.                 len = result;
  1102.                
  1103.                 #if !PLATFORM_UNIX
  1104.                 // If we called GetFullPathName with something like "foo" and our
  1105.                 // command window was in short file name mode (ie, by running edlin or
  1106.                 // DOS versions of grep, etc), we might have gotten back a short file
  1107.                 // name. So, check to see if we need to expand it.
  1108.                 mightBeShortFileName = false;
  1109.                 for (uint i = 0; i < len && !mightBeShortFileName; i++) {
  1110.                     if (finalBuffer[i] == '~')
  1111.                         mightBeShortFileName = true;
  1112.                 }
  1113.                
  1114.                 if (mightBeShortFileName) {
  1115.                     bool r = TryExpandShortFileName(finalBuffer, ref len, MaxPath);
  1116.                     if (!r) {
  1117.                         int lastSlash = -1;
  1118.                        
  1119.                         for (int i = len - 1; i >= 0; i--) {
  1120.                             if (finalBuffer[i] == DirectorySeparatorChar) {
  1121.                                 lastSlash = i;
  1122.                                 break;
  1123.                             }
  1124.                         }
  1125.                        
  1126.                         if (lastSlash >= 0) {
  1127.                            
  1128.                             // This bounds check is for safe memcpy but we should never get this far
  1129.                             if (len >= MaxPath)
  1130.                                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  1131.                            
  1132.                             int lenSavedName = len - lastSlash - 1;
  1133.                             BCLDebug.Assert(lastSlash < len, "path unexpectedly ended in a ''");
  1134.                             char* savedName = stackalloc char[lenSavedName];
  1135.                            
  1136.                             Buffer.memcpy(finalBuffer, lastSlash + 1, savedName, 0, lenSavedName);
  1137.                            
  1138.                             SafeSetStackPointerValue(finalBuffer, lastSlash, '\0');
  1139.                             r = TryExpandShortFileName(finalBuffer, ref lastSlash, MaxPath);
  1140.                            
  1141.                             // Clean up changes made to the finalBuffer.
  1142.                             SafeSetStackPointerValue(finalBuffer, lastSlash, DirectorySeparatorChar);
  1143.                            
  1144.                             if (lastSlash + 1 + lenSavedName >= MaxPath)
  1145.                                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  1146.                            
  1147.                             Buffer.memcpy(savedName, 0, finalBuffer, lastSlash + 1, lenSavedName);
  1148.                            
  1149.                             if (r)
  1150.                                 len = lastSlash + 1 + lenSavedName;
  1151.                         }
  1152.                     }
  1153.                 }
  1154.                 #endif
  1155.             }
  1156.             else {
  1157.                 pFinal = newBuffer;
  1158.                 len = newBufferIndex;
  1159.             }
  1160.            
  1161.             if (result != 0) {
  1162.                 /* Throw an ArgumentException for paths like \\, \\server, \\server\
  1163.                   This check can only be properly done after normalizing, so
  1164.                   \\foo\.. will be properly rejected.  Also, reject \\?\GLOBALROOT\
  1165.                   (an internal kernel path) because it provides aliases for drives. */               
  1166. if (pFinal[0] == '\\' && pFinal[1] == '\\') {
  1167.                     int startIndex = 2;
  1168.                     while (startIndex < result) {
  1169.                         if (pFinal[startIndex] == '\\') {
  1170.                             startIndex++;
  1171.                             break;
  1172.                         }
  1173.                         else {
  1174.                             startIndex++;
  1175.                         }
  1176.                     }
  1177.                     if (startIndex == result)
  1178.                         throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC"));
  1179.                    
  1180.                     // Check for \\?\Globalroot, an internal mechanism to the kernel
  1181.                     // that provides aliases for drives and other undocumented stuff.
  1182.                     // The kernel team won't even describe the full set of what
  1183.                     // is available here - we don't want managed apps mucking
  1184.                     // with this for security reasons.
  1185.                     if (CharArrayStartsWithOrdinal(pFinal, len, "\\\\?\\globalroot", true))
  1186.                         throw new ArgumentException(Environment.GetResourceString("Arg_PathGlobalRoot"));
  1187.                 }
  1188.             }
  1189.            
  1190.             // Check our result and form the managed string as necessary.
  1191.             if (len >= MaxPath)
  1192.                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  1193.            
  1194.             if (result == 0) {
  1195.                 int errorCode = Marshal.GetLastWin32Error();
  1196.                 if (errorCode == 0)
  1197.                     errorCode = Win32Native.ERROR_BAD_PATHNAME;
  1198.                 __Error.WinIOError(errorCode, path);
  1199.                 return null;
  1200.                 // Unreachable - silence a compiler error.
  1201.             }
  1202.            
  1203.             return new string(pFinal, 0, len);
  1204.         }
  1205.        
  1206.         unsafe private static bool CharArrayStartsWithOrdinal(char* array, int numChars, string compareTo, bool ignoreCase)
  1207.         {
  1208.             if (numChars < compareTo.Length)
  1209.                 return false;
  1210.             if (ignoreCase) {
  1211.                 string s = new string(array, 0, compareTo.Length);
  1212.                 return compareTo.Equals(s, StringComparison.OrdinalIgnoreCase);
  1213.             }
  1214.             else {
  1215.                 for (int i = 0; i < compareTo.Length; i++) {
  1216.                     if (array[i] != compareTo[i]) {
  1217.                         return false;
  1218.                     }
  1219.                 }
  1220.                 return true;
  1221.             }
  1222.         }
  1223.        
  1224.         [ResourceExposure(ResourceScope.Machine)]
  1225.         [ResourceConsumption(ResourceScope.Machine)]
  1226.         unsafe private static bool TryExpandShortFileName(char* buffer, ref int bufferLength, int maxBufferSize)
  1227.         {
  1228.             BCLDebug.Assert(buffer != null, "buffer can't be null");
  1229.            
  1230.             char* shortFileNameBuffer = stackalloc char[MaxPath + 1];
  1231.            
  1232.             int r = Win32Native.GetLongPathName(buffer, shortFileNameBuffer, MaxPath);
  1233.            
  1234.             // If success, the return buffer length does not account for the terminating null character.
  1235.             // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character.
  1236.             // If failure, the return buffer length is zero
  1237.             if (r >= MaxPath)
  1238.                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
  1239.             if (r == 0) {
  1240.                 // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a
  1241.                 // path like \\.\PHYSICALDEVICE0 - some device driver doesn't
  1242.                 // support GetLongPathName on that string. This behavior is
  1243.                 // by design, according to the Core File Services team.
  1244.                 // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs
  1245.                 // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\
  1246.                 return false;
  1247.             }
  1248.            
  1249.             // Safe to copy as we have already done MaxPath bound checking
  1250.             Buffer.memcpy(shortFileNameBuffer, 0, buffer, 0, r);
  1251.             bufferLength = r;
  1252.             // We should explicitly null terminate as in some cases the long version of the path
  1253.             // might actually be shorter than what we started with because of Win32's normailization
  1254.             // Safe to write directly as bufferLength is guaranteed to be < MaxPath
  1255.             buffer[bufferLength] = '\0';
  1256.             return true;
  1257.         }
  1258.        
  1259.         // Win9x: to fixup path to replace multiple slashes with a single slash
  1260.         // This is marked as exposing no resources because it calls
  1261.         // NormalizePath and passes in false for fullCheck. This is mostly
  1262.         // used to simply clean up the format of the string, but not to fully
  1263.         // qualify it.
  1264.         [ResourceExposure(ResourceScope.None)]
  1265.         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
  1266.         static internal string FixupPath(string path)
  1267.         {
  1268.             string newPath = NormalizePath(path, false);
  1269.             return newPath;
  1270.         }
  1271.        
  1272.         // Returns the name and extension parts of the given path. The resulting
  1273.         // string contains the characters of path that follow the last
  1274.         // backslash ("\"), slash ("/"), or colon (":") character in
  1275.         // path. The resulting string is the entire path if path
  1276.         // contains no backslash after removing trailing slashes, slash, or colon characters. The resulting
  1277.         // string is null if path is null.
  1278.         //
  1279.         public static string GetFileName(string path)
  1280.         {
  1281.             if (path != null) {
  1282.                 CheckInvalidPathChars(path);
  1283.                
  1284.                 int length = path.Length;
  1285.                 for (int i = length; --i >= 0;) {
  1286.                     char ch = path[i];
  1287.                     if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
  1288.                         return path.Substring(i + 1, length - i - 1);
  1289.                    
  1290.                 }
  1291.             }
  1292.             return path;
  1293.         }
  1294.        
  1295.         public static string GetFileNameWithoutExtension(string path)
  1296.         {
  1297.             path = GetFileName(path);
  1298.             if (path != null) {
  1299.                 int i;
  1300.                 if ((i = path.LastIndexOf('.')) == -1)
  1301.                     return path;
  1302.                 else
  1303.                     // No path extension found
  1304.                     return path.Substring(0, i);
  1305.             }
  1306.             return null;
  1307.         }
  1308.        
  1309.        
  1310.        
  1311.         // Returns the root portion of the given path. The resulting string
  1312.         // consists of those rightmost characters of the path that constitute the
  1313.         // root of the path. Possible patterns for the resulting string are: An
  1314.         // empty string (a relative path on the current drive), "\" (an absolute
  1315.         // path on the current drive), "X:" (a relative path on a given drive,
  1316.         // where X is the drive letter), "X:\" (an absolute path on a given drive),
  1317.         // and "\\server\share" (a UNC path for a given server and share name).
  1318.         // The resulting string is null if path is null.
  1319.         //
  1320.         public static string GetPathRoot(string path)
  1321.         {
  1322.             if (path == null)
  1323.                 return null;
  1324.             path = FixupPath(path);
  1325.             return path.Substring(0, GetRootLength(path));
  1326.         }
  1327.        
  1328.         [ResourceExposure(ResourceScope.Machine)]
  1329.         [ResourceConsumption(ResourceScope.Machine)]
  1330.         public static string GetTempPath()
  1331.         {
  1332.             new EnvironmentPermission(PermissionState.Unrestricted).Demand();
  1333.             StringBuilder sb = new StringBuilder(MAX_PATH);
  1334.             uint r = Win32Native.GetTempPath(MAX_PATH, sb);
  1335.             string path = sb.ToString();
  1336.             if (r == 0)
  1337.                 __Error.WinIOError();
  1338.             path = GetFullPathInternal(path);
  1339.             return path;
  1340.         }
  1341.        
  1342.         // Returns a cryptographically strong random 8.3 string that can be
  1343.         // used as either a folder name or a file name.
  1344.         public static string GetRandomFileName()
  1345.         {
  1346.             // 5 bytes == 40 bits == 40/5 == 8 chars in our encoding
  1347.             // This gives us exactly 8 chars. We want to avoid the 8.3 short name issue
  1348.             byte[] key = new byte[10];
  1349.             RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
  1350.             rng.GetBytes(key);
  1351.             // rndCharArray is expected to be 16 chars
  1352.             char[] rndCharArray = System.IO.IsolatedStorage.IsolatedStorage.ToBase32StringSuitableForDirName(key).ToCharArray();
  1353.             rndCharArray[8] = '.';
  1354.             return new string(rndCharArray, 0, 12);
  1355.         }
  1356.        
  1357.         // Returns a unique temporary file name, and creates a 0-byte file by that
  1358.         // name on disk.
  1359.         [ResourceExposure(ResourceScope.AppDomain)]
  1360.         [ResourceConsumption(ResourceScope.Machine)]
  1361.         public static string GetTempFileName()
  1362.         {
  1363.             string path = GetTempPath();
  1364.            
  1365.             // Since this can write to the temp directory and theoretically
  1366.             // cause a denial of service attack, demand FileIOPermission to
  1367.             // that directory.
  1368.             new FileIOPermission(FileIOPermissionAccess.Write, path).Demand();
  1369.            
  1370.             StringBuilder sb = new StringBuilder(MAX_PATH);
  1371.             uint r = Win32Native.GetTempFileName(path, "tmp", 0, sb);
  1372.             if (r == 0)
  1373.                 __Error.WinIOError();
  1374.             return sb.ToString();
  1375.         }
  1376.        
  1377.         // Tests if a path includes a file extension. The result is
  1378.         // true if the characters that follow the last directory
  1379.         // separator ('\\' or '/') or volume separator (':') in the path include
  1380.         // a period (".") other than a terminal period. The result is false otherwise.
  1381.         //
  1382.         public static bool HasExtension(string path)
  1383.         {
  1384.             if (path != null) {
  1385.                 CheckInvalidPathChars(path);
  1386.                
  1387.                 for (int i = path.Length; --i >= 0;) {
  1388.                     char ch = path[i];
  1389.                     if (ch == '.') {
  1390.                         if (i != path.Length - 1)
  1391.                             return true;
  1392.                         else
  1393.                             return false;
  1394.                     }
  1395.                     if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
  1396.                         break;
  1397.                 }
  1398.             }
  1399.             return false;
  1400.         }
  1401.        
  1402.        
  1403.         // Tests if the given path contains a root. A path is considered rooted
  1404.         // if it starts with a backslash ("\") or a drive letter and a colon (":").
  1405.         //
  1406.         public static bool IsPathRooted(string path)
  1407.         {
  1408.             if (path != null) {
  1409.                 CheckInvalidPathChars(path);
  1410.                
  1411.                 int length = path.Length;
  1412.                 if ((length >= 1 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) || (length >= 2 && path[1] == VolumeSeparatorChar))
  1413.                     #if !PLATFORM_UNIX
  1414.                     #endif
  1415.                     return true;
  1416.             }
  1417.             return false;
  1418.         }
  1419.        
  1420.         public static string Combine(string path1, string path2)
  1421.         {
  1422.             if (path1 == null || path2 == null)
  1423.                 throw new ArgumentNullException((path1 == null) ? "path1" : "path2");
  1424.             CheckInvalidPathChars(path1);
  1425.             CheckInvalidPathChars(path2);
  1426.            
  1427.             if (path2.Length == 0)
  1428.                 return path1;
  1429.            
  1430.             if (path1.Length == 0)
  1431.                 return path2;
  1432.            
  1433.             if (IsPathRooted(path2))
  1434.                 return path2;
  1435.            
  1436.             char ch = path1[path1.Length - 1];
  1437.             if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar)
  1438.                 return path1 + DirectorySeparatorChar + path2;
  1439.             return path1 + path2;
  1440.         }
  1441.        
  1442.        
  1443.         // ".." can only be used if it is specified as a part of a valid File/Directory name. We disallow
  1444.         // the user being able to use it to move up directories. Here are some examples eg
  1445.         // Valid: a..b abc..d
  1446.         // Invalid: ..ab ab.. .. abc..d\abc..
  1447.         //
  1448.         static internal void CheckSearchPattern(string searchPattern)
  1449.         {
  1450.             int index;
  1451.             while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) {
  1452.                
  1453.                 if (index + 2 == searchPattern.Length)
  1454.                     // Terminal ".." . Files names cannot end in ".."
  1455.                     throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
  1456.                
  1457.                 if ((searchPattern[index + 2] == DirectorySeparatorChar) || (searchPattern[index + 2] == AltDirectorySeparatorChar))
  1458.                     throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
  1459.                
  1460.                 searchPattern = searchPattern.Substring(index + 2);
  1461.             }
  1462.            
  1463.         }
  1464.        
  1465.         static internal void CheckInvalidPathChars(string path)
  1466.         {
  1467.             #if PLATFORM_UNIX
  1468.             if (path.StartsWith("\\\\"))
  1469.                 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
  1470.             #endif // PLATFORM_UNIX
  1471.            
  1472.             for (int i = 0; i < path.Length; i++) {
  1473.                 int c = path[i];
  1474.                
  1475.                 // Note: This list is duplicated in static char[] InvalidPathChars
  1476.                 if (c == '"' || c == '<' || c == '>' || c == '|' || c < 32)
  1477.                     throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
  1478.             }
  1479.         }
  1480.        
  1481.        
  1482.         static internal string InternalCombine(string path1, string path2)
  1483.         {
  1484.             if (path1 == null || path2 == null)
  1485.                 throw new ArgumentNullException((path1 == null) ? "path1" : "path2");
  1486.             CheckInvalidPathChars(path1);
  1487.             CheckInvalidPathChars(path2);
  1488.            
  1489.             if (path2.Length == 0)
  1490.                 throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), "path2");
  1491.             if (IsPathRooted(path2))
  1492.                 throw new ArgumentException(Environment.GetResourceString("Arg_Path2IsRooted"), "path2");
  1493.             int i = path1.Length;
  1494.             if (i == 0)
  1495.                 return path2;
  1496.             char ch = path1[i - 1];
  1497.             if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar)
  1498.                 return path1 + DirectorySeparatorChar + path2;
  1499.             return path1 + path2;
  1500.         }
  1501.        
  1502.        
  1503.         // Windows API definitions
  1504.         internal const int MAX_PATH = 260;
  1505.         // From WinDef.h
  1506.         internal const int MAX_DIRECTORY_PATH = 248;
  1507.         // cannot create directories greater than 248 characters
  1508.     }
  1509. }

Developer Fusion