diff options
Diffstat (limited to 'src/mscorlib/corefx/System/IO/PathHelper.Windows.cs')
-rw-r--r-- | src/mscorlib/corefx/System/IO/PathHelper.Windows.cs | 398 |
1 files changed, 0 insertions, 398 deletions
diff --git a/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs b/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs deleted file mode 100644 index e2ead93185..0000000000 --- a/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs +++ /dev/null @@ -1,398 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace System.IO -{ - /// <summary> - /// Wrapper to help with path normalization. - /// </summary> - internal class PathHelper - { - // Can't be over 8.3 and be a short name - private const int MaxShortName = 12; - - private const char LastAnsi = (char)255; - private const char Delete = (char)127; - - /// <summary> - /// Normalize the given path. - /// </summary> - /// <remarks> - /// Normalizes via Win32 GetFullPathName(). It will also trim all "typical" whitespace at the end of the path (see s_trimEndChars). Will also trim initial - /// spaces if the path is determined to be rooted. - /// - /// Note that invalid characters will be checked after the path is normalized, which could remove bad characters. (C:\|\..\a.txt -- C:\a.txt) - /// </remarks> - /// <param name="path">Path to normalize</param> - /// <param name="checkInvalidCharacters">True to check for invalid characters</param> - /// <param name="expandShortPaths">Attempt to expand short paths if true</param> - /// <exception cref="ArgumentException">Thrown if the path is an illegal UNC (does not contain a full server/share) or contains illegal characters.</exception> - /// <exception cref="PathTooLongException">Thrown if the path or a path segment exceeds the filesystem limits.</exception> - /// <exception cref="FileNotFoundException">Thrown if Windows returns ERROR_FILE_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception> - /// <exception cref="DirectoryNotFoundException">Thrown if Windows returns ERROR_PATH_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception> - /// <exception cref="UnauthorizedAccessException">Thrown if Windows returns ERROR_ACCESS_DENIED. (See Win32Marshal.GetExceptionForWin32Error)</exception> - /// <exception cref="IOException">Thrown if Windows returns an error that doesn't map to the above. (See Win32Marshal.GetExceptionForWin32Error)</exception> - /// <returns>Normalized path</returns> - internal static string Normalize(string path, bool checkInvalidCharacters, bool expandShortPaths) - { - // Get the full path - StringBuffer fullPath = new StringBuffer(PathInternal.MaxShortPath); - - try - { - GetFullPathName(path, ref fullPath); - - // Trim whitespace off the end of the string. Win32 normalization trims only U+0020. - fullPath.TrimEnd(PathInternal.s_trimEndChars); - - if (fullPath.Length >= PathInternal.MaxLongPath) - { - // Fullpath is genuinely too long - throw new PathTooLongException(SR.IO_PathTooLong); - } - - // Checking path validity used to happen before getting the full path name. To avoid additional input allocation - // (to trim trailing whitespace) we now do it after the Win32 call. This will allow legitimate paths through that - // used to get kicked back (notably segments with invalid characters might get removed via ".."). - // - // There is no way that GetLongPath can invalidate the path so we'll do this (cheaper) check before we attempt to - // expand short file names. - - // Scan the path for: - // - // - Illegal path characters. - // - Invalid UNC paths like \\, \\server, \\server\. - // - Segments that are too long (over MaxComponentLength) - - // As the path could be > 30K, we'll combine the validity scan. None of these checks are performed by the Win32 - // GetFullPathName() API. - - bool possibleShortPath = false; - bool foundTilde = false; - - // We can get UNCs as device paths through this code (e.g. \\.\UNC\), we won't validate them as there isn't - // an easy way to normalize without extensive cost (we'd have to hunt down the canonical name for any device - // path that contains UNC or to see if the path was doing something like \\.\GLOBALROOT\Device\Mup\, - // \\.\GLOBAL\UNC\, \\.\GLOBALROOT\GLOBAL??\UNC\, etc. - bool specialPath = fullPath.Length > 1 && fullPath[0] == '\\' && fullPath[1] == '\\'; - bool isDevice = PathInternal.IsDevice(ref fullPath); - bool possibleBadUnc = specialPath && !isDevice; - int index = specialPath ? 2 : 0; - int lastSeparator = specialPath ? 1 : 0; - int segmentLength; - char current; - - while (index < fullPath.Length) - { - current = fullPath[index]; - - // Try to skip deeper analysis. '?' and higher are valid/ignorable except for '\', '|', and '~' - if (current < '?' || current == '\\' || current == '|' || current == '~') - { - switch (current) - { - case '|': - case '>': - case '<': - case '\"': - if (checkInvalidCharacters) throw new ArgumentException(SR.Argument_InvalidPathChars); - foundTilde = false; - break; - case '~': - foundTilde = true; - break; - case '\\': - segmentLength = index - lastSeparator - 1; - if (segmentLength > PathInternal.MaxComponentLength) - throw new PathTooLongException(SR.IO_PathTooLong + fullPath.ToString()); - lastSeparator = index; - - if (foundTilde) - { - if (segmentLength <= MaxShortName) - { - // Possibly a short path. - possibleShortPath = true; - } - - foundTilde = false; - } - - if (possibleBadUnc) - { - // If we're at the end of the path and this is the first separator, we're missing the share. - // Otherwise we're good, so ignore UNC tracking from here. - if (index == fullPath.Length - 1) - throw new ArgumentException(SR.Arg_PathIllegalUNC); - else - possibleBadUnc = false; - } - - break; - - default: - if (checkInvalidCharacters && current < ' ') throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); - break; - } - } - - index++; - } - - if (possibleBadUnc) - throw new ArgumentException(SR.Arg_PathIllegalUNC); - - segmentLength = fullPath.Length - lastSeparator - 1; - if (segmentLength > PathInternal.MaxComponentLength) - throw new PathTooLongException(SR.IO_PathTooLong); - - if (foundTilde && segmentLength <= MaxShortName) - possibleShortPath = true; - - // Check for a short filename path and try and expand it. Technically you don't need to have a tilde for a short name, but - // this is how we've always done this. This expansion is costly so we'll continue to let other short paths slide. - if (expandShortPaths && possibleShortPath) - { - return TryExpandShortFileName(ref fullPath, originalPath: path); - } - else - { - if (fullPath.Length == path.Length && fullPath.StartsWith(path)) - { - // If we have the exact same string we were passed in, don't bother to allocate another string from the StringBuilder. - return path; - } - else - { - return fullPath.ToString(); - } - } - } - finally - { - // Clear the buffer - fullPath.Free(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsDosUnc(ref StringBuffer buffer) - { - return !PathInternal.IsDevice(ref buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\'; - } - - private static unsafe void GetFullPathName(string path, ref StringBuffer fullPath) - { - // If the string starts with an extended prefix we would need to remove it from the path before we call GetFullPathName as - // it doesn't root extended paths correctly. We don't currently resolve extended paths, so we'll just assert here. - Debug.Assert(PathInternal.IsPartiallyQualified(path) || !PathInternal.IsExtended(path)); - - // Historically we would skip leading spaces *only* if the path started with a drive " C:" or a UNC " \\" - int startIndex = PathInternal.PathStartSkip(path); - - fixed (char* pathStart = path) - { - uint result = 0; - while ((result = Interop.Kernel32.GetFullPathNameW(pathStart + startIndex, (uint)fullPath.Capacity, fullPath.UnderlyingArray, IntPtr.Zero)) > fullPath.Capacity) - { - // Reported size is greater than the buffer size. Increase the capacity. - fullPath.EnsureCapacity(checked((int)result)); - } - - if (result == 0) - { - // Failure, get the error and throw - int errorCode = Marshal.GetLastWin32Error(); - if (errorCode == 0) - errorCode = Interop.Errors.ERROR_BAD_PATHNAME; - throw Win32Marshal.GetExceptionForWin32Error(errorCode, path); - } - - fullPath.Length = checked((int)result); - } - } - - private static int GetInputBuffer(ref StringBuffer content, bool isDosUnc, ref StringBuffer buffer) - { - int length = content.Length; - - length += isDosUnc - ? PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength - : PathInternal.DevicePrefixLength; - - buffer.EnsureCapacity(length + 1); - - if (isDosUnc) - { - // Put the extended UNC prefix (\\?\UNC\) in front of the path - buffer.CopyFrom(bufferIndex: 0, source: PathInternal.UncExtendedPathPrefix); - - // Copy the source buffer over after the existing UNC prefix - content.CopyTo( - bufferIndex: PathInternal.UncPrefixLength, - destination: ref buffer, - destinationIndex: PathInternal.UncExtendedPrefixLength, - count: content.Length - PathInternal.UncPrefixLength); - - // Return the prefix difference - return PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength; - } - else - { - int prefixSize = PathInternal.ExtendedPathPrefix.Length; - buffer.CopyFrom(bufferIndex: 0, source: PathInternal.ExtendedPathPrefix); - content.CopyTo(bufferIndex: 0, destination: ref buffer, destinationIndex: prefixSize, count: content.Length); - return prefixSize; - } - } - - private static string TryExpandShortFileName(ref StringBuffer outputBuffer, string originalPath) - { - // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To - // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls. - - Debug.Assert(!PathInternal.IsPartiallyQualified(ref outputBuffer), "should have resolved by now"); - - // We'll have one of a few cases by now (the normalized path will have already: - // - // 1. Dos path (C:\) - // 2. Dos UNC (\\Server\Share) - // 3. Dos device path (\\.\C:\, \\?\C:\) - // - // We want to put the extended syntax on the front if it doesn't already have it, which may mean switching from \\.\. - // - // Note that we will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is). - - int rootLength = PathInternal.GetRootLength(ref outputBuffer); - bool isDevice = PathInternal.IsDevice(ref outputBuffer); - - StringBuffer inputBuffer = new StringBuffer(0); - try - { - bool isDosUnc = false; - int rootDifference = 0; - bool wasDotDevice = false; - - // Add the extended prefix before expanding to allow growth over MAX_PATH - if (isDevice) - { - // We have one of the following (\\?\ or \\.\) - inputBuffer.Append(ref outputBuffer); - - if (outputBuffer[2] == '.') - { - wasDotDevice = true; - inputBuffer[2] = '?'; - } - } - else - { - isDosUnc = IsDosUnc(ref outputBuffer); - rootDifference = GetInputBuffer(ref outputBuffer, isDosUnc, ref inputBuffer); - } - - rootLength += rootDifference; - int inputLength = inputBuffer.Length; - - bool success = false; - int foundIndex = inputBuffer.Length - 1; - - while (!success) - { - uint result = Interop.Kernel32.GetLongPathNameW(inputBuffer.UnderlyingArray, outputBuffer.UnderlyingArray, (uint)outputBuffer.Capacity); - - // Replace any temporary null we added - if (inputBuffer[foundIndex] == '\0') inputBuffer[foundIndex] = '\\'; - - if (result == 0) - { - // Look to see if we couldn't find the file - int error = Marshal.GetLastWin32Error(); - if (error != Interop.Errors.ERROR_FILE_NOT_FOUND && error != Interop.Errors.ERROR_PATH_NOT_FOUND) - { - // Some other failure, give up - break; - } - - // We couldn't find the path at the given index, start looking further back in the string. - foundIndex--; - - for (; foundIndex > rootLength && inputBuffer[foundIndex] != '\\'; foundIndex--) ; - if (foundIndex == rootLength) - { - // Can't trim the path back any further - break; - } - else - { - // Temporarily set a null in the string to get Windows to look further up the path - inputBuffer[foundIndex] = '\0'; - } - } - else if (result > outputBuffer.Capacity) - { - // Not enough space. The result count for this API does not include the null terminator. - outputBuffer.EnsureCapacity(checked((int)result)); - result = Interop.Kernel32.GetLongPathNameW(inputBuffer.UnderlyingArray, outputBuffer.UnderlyingArray, (uint)outputBuffer.Capacity); - } - else - { - // Found the path - success = true; - outputBuffer.Length = checked((int)result); - if (foundIndex < inputLength - 1) - { - // It was a partial find, put the non-existent part of the path back - outputBuffer.Append(ref inputBuffer, foundIndex, inputBuffer.Length - foundIndex); - } - } - } - - // Strip out the prefix and return the string - ref StringBuffer bufferToUse = ref Choose(success, ref outputBuffer, ref inputBuffer); - - // Switch back from \\?\ to \\.\ if necessary - if (wasDotDevice) - bufferToUse[2] = '.'; - - string returnValue = null; - - int newLength = (int)(bufferToUse.Length - rootDifference); - if (isDosUnc) - { - // Need to go from \\?\UNC\ to \\?\UN\\ - bufferToUse[PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength] = '\\'; - } - - // We now need to strip out any added characters at the front of the string - if (bufferToUse.SubstringEquals(originalPath, rootDifference, newLength)) - { - // Use the original path to avoid allocating - returnValue = originalPath; - } - else - { - returnValue = bufferToUse.Substring(rootDifference, newLength); - } - - return returnValue; - } - finally - { - inputBuffer.Free(); - } - } - - // Helper method to workaround lack of operator ? support for ref values - private static ref StringBuffer Choose(bool condition, ref StringBuffer s1, ref StringBuffer s2) - { - if (condition) return ref s1; - else return ref s2; - } - } -} |