internal static void ValidateFileTypeForNonExtendedPaths(SafeFileHandle handle, string originalPath) { if (!PathInternal.IsExtended(originalPath)) { // To help avoid stumbling into opening COM/LPT ports by accident, we will block on non file handles unless // we were explicitly passed a path that has \\?\. GetFullPath() will turn paths like C:\foo\con.txt into // \\.\CON, so we'll only allow the \\?\ syntax. int fileType = Interop.Kernel32.GetFileType(handle); if (fileType != Interop.Kernel32.FileTypes.FILE_TYPE_DISK) { int errorCode = fileType == Interop.Kernel32.FileTypes.FILE_TYPE_UNKNOWN ? Marshal.GetLastWin32Error() : Interop.Errors.ERROR_SUCCESS; handle.Dispose(); if (errorCode != Interop.Errors.ERROR_SUCCESS) { throw Win32Marshal.GetExceptionForWin32Error(errorCode); } throw new NotSupportedException(SR.NotSupported_FileStreamOnNonFiles); } } }
private static void GetFullPathName(ReadOnlySpan <char> path, ref ValueStringBuilder builder) { // 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)); uint result; while ((result = Interop.Kernel32.GetFullPathNameW(ref MemoryMarshal.GetReference(path), (uint)builder.Capacity, ref builder.GetPinnableReference(), IntPtr.Zero)) > builder.Capacity) { // Reported size is greater than the buffer size. Increase the capacity. builder.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.ToString()); } builder.Length = (int)result; }
unsafe private static void GetFullPathName(string path, 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. Contract.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 = Win32Native.GetFullPathNameW(pathStart + startIndex, fullPath.CharCapacity, fullPath.GetHandle(), IntPtr.Zero)) > fullPath.CharCapacity) { // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity. fullPath.EnsureCharCapacity(result); } if (result == 0) { // Failure, get the error and throw int errorCode = Marshal.GetLastWin32Error(); if (errorCode == 0) { errorCode = Win32Native.ERROR_BAD_PATHNAME; } __Error.WinIOError(errorCode, path); } fullPath.Length = result; } }
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); } }
// Expands the given path to a fully qualified path. public static string GetFullPath(string path) { if (path == null) { throw new ArgumentNullException(nameof(path)); } // If the path would normalize to string empty, we'll consider it empty if (PathInternal.IsEffectivelyEmpty(path)) { throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); } // Embedded null characters are the only invalid character case we trully care about. // This is because the nulls will signal the end of the string to Win32 and therefore have // unpredictable results. if (path.IndexOf('\0') != -1) { throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); } if (PathInternal.IsExtended(path)) { // \\?\ paths are considered normalized by definition. Windows doesn't normalize \\?\ // paths and neither should we. Even if we wanted to GetFullPathName does not work // properly with device paths. If one wants to pass a \\?\ path through normalization // one can chop off the prefix, pass it to GetFullPath and add it again. return(path); } return(PathHelper.Normalize(path)); }
internal static bool IsDevice(ReadOnlySpan <char> path) { if (PathInternal.IsExtended(path)) { return(true); } return(path.Length >= 4 && PathInternal.IsDirectorySeparator(path[0]) && PathInternal.IsDirectorySeparator(path[1]) && (path[2] == '.' || path[2] == '?') && PathInternal.IsDirectorySeparator(path[3])); }
/// <summary> /// Returns a value indicating if the given path contains invalid characters (", <, >, | /// NUL, or any ASCII char whose integer representation is in the range of 1 through 31), /// optionally checking for ? and *. /// </summary> internal static bool HasIllegalCharacters(string path, bool checkAdditional = false) { Debug.Assert(path != null); // See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx // Question mark is a normal part of extended path syntax (\\?\) int startIndex = PathInternal.IsExtended(path) ? ExtendedPathPrefix.Length : 0; return(path.IndexOfAny(checkAdditional ? InvalidPathCharsWithAdditionalChecks : InvalidPathChars, startIndex) >= 0); }
// Token: 0x06001948 RID: 6472 RVA: 0x00054082 File Offset: 0x00052282 internal static string RemoveExtendedPrefix(string path) { if (!PathInternal.IsExtended(path)) { return(path); } if (PathInternal.IsExtendedUnc(path)) { return(path.Remove(2, 6)); } return(path.Substring(4)); }
// Token: 0x06001949 RID: 6473 RVA: 0x000540A6 File Offset: 0x000522A6 internal static StringBuilder RemoveExtendedPrefix(StringBuilder path) { if (!PathInternal.IsExtended(path)) { return(path); } if (PathInternal.IsExtendedUnc(path)) { return(path.Remove(2, 6)); } return(path.Remove(0, 4)); }
private static string NormalizePath(string path, bool fullCheck = true, bool expandShortPaths = true) { Debug.Assert(path != null, "path can't be null"); bool isExtended = PathInternal.IsExtended(path); if (fullCheck) { // Embedded null characters are the only invalid character case we want to check up front. // This is because the nulls will signal the end of the string to Win32 and therefore have // unpredictable results. Other invalid characters we give a chance to be normalized out. if (path.IndexOf('\0') != -1) { throw new ArgumentException(SR.Argument_InvalidPathChars, "path"); } // Toss out paths with colons that aren't a valid drive specifier. // Cannot start with a colon and can only be of the form "C:" or "\\?\C:". // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.) int startIndex = PathInternal.PathStartSkip(path) + 2; if (isExtended) { startIndex += PathInternal.ExtendedPathPrefix.Length; } if ((path.Length > 0 && path[0] == VolumeSeparatorChar) || (path.Length >= startIndex && path[startIndex - 1] == VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2])) || (path.Length > startIndex && path.IndexOf(VolumeSeparatorChar, startIndex) != -1)) { throw new NotSupportedException(SR.Argument_PathFormatNotSupported); } } if (isExtended) { return(NormalizeExtendedPath(path, fullCheck)); } else { // Technically this doesn't matter but we used to throw for this case if (String.IsNullOrWhiteSpace(path)) { throw new ArgumentException(SR.Arg_PathIllegal); } return(PathHelper.Normalize(path, fullCheck, expandShortPaths)); } }
public static string GetFullPath(string path) { string fullPath = GetFullPathInternal(path); // Emulate FileIOPermissions checks, retained for compatibility PathInternal.CheckInvalidPathChars(fullPath, true); int startIndex = PathInternal.IsExtended(fullPath) ? PathInternal.ExtendedPathPrefix.Length + 2 : 2; if (fullPath.Length > startIndex && fullPath.IndexOf(':', startIndex) != -1) { throw new NotSupportedException(SR.Argument_PathFormatNotSupported); } return(fullPath); }
internal unsafe static bool HasAdditionalIllegalCharacters(string path) { int startIndex = PathInternal.IsExtended(path) ? ExtendedPathPrefix.Length : 0; char currentChar; for (int i = startIndex; i < path.Length; i++) { currentChar = path[i]; if (currentChar == '*' || currentChar == '?') { return(true); } } return(false); }
// Gets the full path without argument validation private static string GetFullPathInternal(string path) { Debug.Assert(!string.IsNullOrEmpty(path)); Debug.Assert(!path.Contains('\0')); if (PathInternal.IsExtended(path.AsSpan())) { // \\?\ paths are considered normalized by definition. Windows doesn't normalize \\?\ // paths and neither should we. Even if we wanted to GetFullPathName does not work // properly with device paths. If one wants to pass a \\?\ path through normalization // one can chop off the prefix, pass it to GetFullPath and add it again. return(path); } return(PathHelper.Normalize(path)); }
internal unsafe static bool HasWildCardCharacters(string path) { // Question mark is part of extended syntax so we have to skip if we're extended int startIndex = PathInternal.IsExtended(path) ? ExtendedPathPrefix.Length : 0; char currentChar; for (int i = startIndex; i < path.Length; i++) { currentChar = path[i]; if (currentChar == '*' || currentChar == '?') { return(true); } } return(false); }
private StringBuilder GetFullPathName(string path) { // Historically we would skip leading spaces *only* if the path started with a drive " C:" or a UNC " \\" int startIndex = PathInternal.PathStartSkip(path); int capacity = path.Length; if (PathInternal.IsRelative(path)) { // If the initial path is relative the final path will likely be no more than the current directory length (which can only // be MaxPath) so we'll pick that as a reasonable start. capacity += PathInternal.MaxShortPath; } else { // 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.IsExtended(path)); } StringBuilder outputBuffer = this.GetOutputBuffer(capacity); fixed(char *pathStart = path) { int result = 0; while ((result = Interop.mincore.GetFullPathNameW(pathStart + startIndex, outputBuffer.Capacity + 1, outputBuffer, IntPtr.Zero)) > outputBuffer.Capacity) { // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity. outputBuffer.Capacity = result; } if (result == 0) { // Failure, get the error and throw int errorCode = Marshal.GetLastWin32Error(); if (errorCode == 0) { errorCode = Interop.mincore.Errors.ERROR_BAD_PATHNAME; } throw Win32Marshal.GetExceptionForWin32Error(errorCode, path); } } return(outputBuffer); }
Guid.NewGuid().ToString("N").Substring(0, 8)); // randomness to avoid collisions between derived test classes using same base method concurrently // Some Windows versions like Windows Nano Server have the %TEMP% environment variable set to "C:\TEMP" but the // actual folder name is "C:\Temp", which prevents asserting path values using Assert.Equal due to case sensitiveness. // So instead of using TestDirectory directly, we retrieve the real path with proper casing of the initial folder path. private unsafe string GetTestDirectoryActualCasing() { if (!PlatformDetection.IsWindows) { return(TestDirectory); } try { using SafeFileHandle handle = Interop.Kernel32.CreateFile( TestDirectory, dwDesiredAccess: 0, dwShareMode: FileShare.ReadWrite | FileShare.Delete, dwCreationDisposition: FileMode.Open, dwFlagsAndAttributes: Interop.Kernel32.FileOperations.OPEN_EXISTING | Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS // Necessary to obtain a handle to a directory ); if (!handle.IsInvalid) { const int InitialBufferSize = 4096; char[]? buffer = ArrayPool <char> .Shared.Rent(InitialBufferSize); uint result = GetFinalPathNameByHandle(handle, buffer); // Remove extended prefix int skip = PathInternal.IsExtended(buffer) ? 4 : 0; return(new string( buffer, skip, (int)result - skip)); } } catch { } return(TestDirectory); }
// Expands the given path to a fully qualified path. public static string GetFullPath(string path) { if (path == null) { throw new ArgumentNullException(nameof(path)); } // Embedded null characters are the only invalid character case we want to check up front. // This is because the nulls will signal the end of the string to Win32 and therefore have // unpredictable results. Other invalid characters we give a chance to be normalized out. if (path.IndexOf('\0') != -1) { throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); } if (PathInternal.IsExtended(path)) { // We can't really know what is valid for all cases of extended paths. // // - object names can include other characters as well (':', '/', etc.) // - even file objects have different rules (pipe names can contain most characters) // // As such we will do no further analysis of extended paths to avoid blocking known and unknown // scenarios as well as minimizing compat breaks should we block now and need to unblock later. return(path); } bool isDevice = PathInternal.IsDevice(path); if (!isDevice) { // Toss out paths with colons that aren't a valid drive specifier. // Cannot start with a colon and can only be of the form "C:". // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.) int startIndex = PathInternal.PathStartSkip(path); // Move past the colon startIndex += 2; if ((path.Length > 0 && path[0] == PathInternal.VolumeSeparatorChar) || (path.Length >= startIndex && path[startIndex - 1] == PathInternal.VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2])) || (path.Length > startIndex && path.IndexOf(PathInternal.VolumeSeparatorChar, startIndex) != -1)) { throw new NotSupportedException(SR.Format(SR.Argument_PathFormatNotSupported_Path, path)); } } // Technically this doesn't matter but we used to throw for this case if (PathInternal.IsEffectivelyEmpty(path)) { throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); } // We don't want to check invalid characters for device format- see comments for extended above string fullPath = PathHelper.Normalize(path, checkInvalidCharacters: !isDevice, expandShortPaths: true); if (!isDevice) { // Emulate FileIOPermissions checks, retained for compatibility (normal invalid characters have already been checked) if (PathInternal.HasWildCardCharacters(fullPath)) { throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); } } return(fullPath); }
[System.Security.SecurityCritical] // auto-generated private unsafe static string NormalizePath(string path, bool fullCheck, int maxPathLength, bool expandShortPaths) { Contract.Requires(path != null, "path can't be null"); // If the path is in extended syntax, we don't need to normalize, but we still do some basic validity checks if (PathInternal.IsExtended(path)) { if (!ValidateExtendedPath(path, fullCheck)) { throw new ArgumentException(SR.Arg_PathIllegal); } // \\?\GLOBALROOT gives access to devices out of the scope of the current user, we // don't want to allow this for security reasons. // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx#nt_namespaces if (path.StartsWith(@"\\?\globalroot", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException(SR.Arg_PathGlobalRoot); } return(path); } // If we're doing a full path check, trim whitespace and look for // illegal path characters. if (fullCheck) { // Trim whitespace off the end of the string. // Win32 normalization trims only U+0020. path = path.TrimEnd(TrimEndChars); // Look for illegal path characters. PathInternal.CheckInvalidPathChars(path); } int index = 0; // We prefer to allocate on the stack for workingset/perf gain. If the // starting path is less than MaxPath then we can stackalloc; otherwise we'll // use a StringBuilder (PathHelper does this under the hood). The latter may // happen in 2 cases: // 1. Starting path is greater than MaxPath but it normalizes down to MaxPath. // This is relevant for paths containing escape sequences. In this case, we // attempt to normalize down to MaxPath, but the caller pays a perf penalty // since StringBuilder is used. // 2. IsolatedStorage, which supports paths longer than MaxPath (value given // by maxPathLength. PathHelper newBuffer = null; if (path.Length + 1 <= MaxPath) { char *m_arrayPtr = stackalloc char[MaxPath]; newBuffer = new PathHelper(m_arrayPtr, MaxPath); } else { newBuffer = new PathHelper(path.Length + MaxPath, maxPathLength); } uint numSpaces = 0; uint numDots = 0; bool fixupDirectorySeparator = false; // Number of significant chars other than potentially suppressible // dots and spaces since the last directory or volume separator char uint numSigChars = 0; int lastSigChar = -1; // Index of last significant character. // Whether this segment of the path (not the complete path) started // with a volume separator char. Reject "c:...". bool startedWithVolumeSeparator = false; bool firstSegment = true; int lastDirectorySeparatorPos = 0; bool mightBeShortFileName = false; // LEGACY: This code is here for backwards compatibility reasons. It // ensures that \\foo.cs\bar.cs stays \\foo.cs\bar.cs instead of being // turned into \foo.cs\bar.cs. if (path.Length > 0 && PathInternal.IsDirectorySeparator(path[0])) { newBuffer.Append('\\'); index++; lastSigChar = 0; } // Normalize the string, stripping out redundant dots, spaces, and // slashes. while (index < path.Length) { char currentChar = path[index]; // We handle both directory separators and dots specially. For // directory separators, we consume consecutive appearances. // For dots, we consume all dots beyond the second in // succession. All other characters are added as is. In // addition we consume all spaces after the last other char // in a directory name up until the directory separator. if (PathInternal.IsDirectorySeparator(currentChar)) { // If we have a path like "123.../foo", remove the trailing dots. // However, if we found "c:\temp\..\bar" or "c:\temp\...\bar", don't. // Also remove trailing spaces from both files & directory names. // This was agreed on with the OS team to fix undeletable directory // names ending in spaces. // If we saw a '\' as the previous last significant character and // are simply going to write out dots, suppress them. // If we only contain dots and slashes though, only allow // a string like [dot]+ [space]*. Ignore everything else. // Legal: "\.. \", "\...\", "\. \" // Illegal: "\.. .\", "\. .\", "\ .\" if (numSigChars == 0) { // Dot and space handling if (numDots > 0) { // Look for ".[space]*" or "..[space]*" int start = lastSigChar + 1; if (path[start] != '.') { throw new ArgumentException(SR.Arg_PathIllegal); } // Only allow "[dot]+[space]*", and normalize the // legal ones to "." or ".." if (numDots >= 2) { // Reject "C:..." if (startedWithVolumeSeparator && numDots > 2) { throw new ArgumentException(SR.Arg_PathIllegal); } if (path[start + 1] == '.') { // Search for a space in the middle of the // dots and throw for (int i = start + 2; i < start + numDots; i++) { if (path[i] != '.') { throw new ArgumentException(SR.Arg_PathIllegal); } } numDots = 2; } else { if (numDots > 1) { throw new ArgumentException(SR.Arg_PathIllegal); } numDots = 1; } } if (numDots == 2) { newBuffer.Append('.'); } newBuffer.Append('.'); fixupDirectorySeparator = false; // Continue in this case, potentially writing out '\'. } if (numSpaces > 0 && firstSegment) { // Handle strings like " \\server\share". if (index + 1 < path.Length && PathInternal.IsDirectorySeparator(path[index + 1])) { newBuffer.Append(DirectorySeparatorChar); } } } numDots = 0; numSpaces = 0; // Suppress trailing spaces if (!fixupDirectorySeparator) { fixupDirectorySeparator = true; newBuffer.Append(DirectorySeparatorChar); } numSigChars = 0; lastSigChar = index; startedWithVolumeSeparator = false; firstSegment = false; // For short file names, we must try to expand each of them as // soon as possible. We need to allow people to specify a file // name that doesn't exist using a path with short file names // in it, such as this for a temp file we're trying to create: // C:\DOCUME~1\USERNA~1.RED\LOCALS~1\Temp\bg3ylpzp // We could try doing this afterwards piece by piece, but it's // probably a lot simpler to do it here. if (mightBeShortFileName) { newBuffer.TryExpandShortFileName(); mightBeShortFileName = false; } int thisPos = newBuffer.Length - 1; if (thisPos - lastDirectorySeparatorPos > MaxComponentLength) { throw new PathTooLongException(SR.IO_PathTooLong); } lastDirectorySeparatorPos = thisPos; } // if (Found directory separator) else if (currentChar == '.') { // Reduce only multiple .'s only after slash to 2 dots. For // instance a...b is a valid file name. numDots++; // Don't flush out non-terminal spaces here, because they may in // the end not be significant. Turn "c:\ . .\foo" -> "c:\foo" // which is the conclusion of removing trailing dots & spaces, // as well as folding multiple '\' characters. } else if (currentChar == ' ') { numSpaces++; } else { // Normal character logic if (currentChar == '~' && expandShortPaths) { mightBeShortFileName = true; } fixupDirectorySeparator = false; // To reject strings like "C:...\foo" and "C :\foo" if (firstSegment && currentChar == VolumeSeparatorChar) { // Only accept "C:", not "c :" or ":" // Get a drive letter or ' ' if index is 0. char driveLetter = (index > 0) ? path[index - 1] : ' '; bool validPath = ((numDots == 0) && (numSigChars >= 1) && (driveLetter != ' ')); if (!validPath) { throw new ArgumentException(SR.Arg_PathIllegal); } startedWithVolumeSeparator = true; // We need special logic to make " c:" work, we should not fix paths like " foo::$DATA" if (numSigChars > 1) { // Common case, simply do nothing int spaceCount = 0; // How many spaces did we write out, numSpaces has already been reset. while ((spaceCount < newBuffer.Length) && newBuffer[spaceCount] == ' ') { spaceCount++; } if (numSigChars - spaceCount == 1) { //Safe to update stack ptr directly newBuffer.Length = 0; newBuffer.Append(driveLetter); // Overwrite spaces, we need a special case to not break " foo" as a relative path. } } numSigChars = 0; } else { numSigChars += 1 + numDots + numSpaces; } // Copy any spaces & dots since the last significant character // to here. Note we only counted the number of dots & spaces, // and don't know what order they're in. Hence the copy. if (numDots > 0 || numSpaces > 0) { int numCharsToCopy = (lastSigChar >= 0) ? index - lastSigChar - 1 : index; if (numCharsToCopy > 0) { for (int i = 0; i < numCharsToCopy; i++) { newBuffer.Append(path[lastSigChar + 1 + i]); } } numDots = 0; numSpaces = 0; } newBuffer.Append(currentChar); lastSigChar = index; } index++; } // end while if (newBuffer.Length - 1 - lastDirectorySeparatorPos > MaxComponentLength) { throw new PathTooLongException(SR.IO_PathTooLong); } // Drop any trailing dots and spaces from file & directory names, EXCEPT // we MUST make sure that "C:\foo\.." is correctly handled. // Also handle "C:\foo\." -> "C:\foo", while "C:\." -> "C:\" if (numSigChars == 0) { if (numDots > 0) { // Look for ".[space]*" or "..[space]*" int start = lastSigChar + 1; if (path[start] != '.') { throw new ArgumentException(SR.Arg_PathIllegal); } // Only allow "[dot]+[space]*", and normalize the // legal ones to "." or ".." if (numDots >= 2) { // Reject "C:..." if (startedWithVolumeSeparator && numDots > 2) { throw new ArgumentException(SR.Arg_PathIllegal); } if (path[start + 1] == '.') { // Search for a space in the middle of the // dots and throw for (int i = start + 2; i < start + numDots; i++) { if (path[i] != '.') { throw new ArgumentException(SR.Arg_PathIllegal); } } numDots = 2; } else { if (numDots > 1) { throw new ArgumentException(SR.Arg_PathIllegal); } numDots = 1; } } if (numDots == 2) { newBuffer.Append('.'); } newBuffer.Append('.'); } } // if (numSigChars == 0) // If we ended up eating all the characters, bail out. if (newBuffer.Length == 0) { throw new ArgumentException(SR.Arg_PathIllegal); } // Disallow URL's here. Some of our other Win32 API calls will reject // them later, so we might be better off rejecting them here. // Note we've probably turned them into "file:\D:\foo.tmp" by now. // But for compatibility, ensure that callers that aren't doing a // full check aren't rejected here. if (fullCheck) { if (newBuffer.OrdinalStartsWith("http:", false) || newBuffer.OrdinalStartsWith("file:", false)) { throw new ArgumentException(SR.Argument_PathUriFormatNotSupported); } } // If the last part of the path (file or directory name) had a tilde, // expand that too. if (mightBeShortFileName) { newBuffer.TryExpandShortFileName(); } // Call the Win32 API to do the final canonicalization step. int result = 1; if (fullCheck) { // NOTE: Win32 GetFullPathName requires the input buffer to be big enough to fit the initial // path which is a concat of CWD and the relative path, this can be of an arbitrary // size and could be > MaxPath (which becomes an artificial limit at this point), // even though the final normalized path after fixing up the relative path syntax // might be well within the MaxPath restriction. For ex, // "c:\SomeReallyLongDirName(thinkGreaterThan_MAXPATH)\..\foo.txt" which actually requires a // buffer well with in the MaxPath as the normalized path is just "c:\foo.txt" // This buffer requirement seems wrong, it could be a bug or a perf optimization // like returning required buffer length quickly or avoid stratch buffer etc. // Either way we need to workaround it here... // Ideally we would get the required buffer length first by calling GetFullPathName // once without the buffer and use that in the later call but this doesn't always work // due to Win32 GetFullPathName bug. For instance, in Win2k, when the path we are trying to // fully qualify is a single letter name (such as "a", "1", ",") GetFullPathName // fails to return the right buffer size (i.e, resulting in insufficient buffer). // To workaround this bug we will start with MaxPath buffer and grow it once if the // return value is > MaxPath. result = newBuffer.GetFullPathName(); // If we called GetFullPathName with something like "foo" and our // command window was in short file name mode (ie, by running edlin or // DOS versions of grep, etc), we might have gotten back a short file // name. So, check to see if we need to expand it. mightBeShortFileName = false; for (int i = 0; i < newBuffer.Length && !mightBeShortFileName; i++) { if (newBuffer[i] == '~' && expandShortPaths) { mightBeShortFileName = true; } } if (mightBeShortFileName) { bool r = newBuffer.TryExpandShortFileName(); // Consider how the path "Doesn'tExist" would expand. If // we add in the current directory, it too will need to be // fully expanded, which doesn't happen if we use a file // name that doesn't exist. if (!r) { int lastSlash = -1; for (int i = newBuffer.Length - 1; i >= 0; i--) { if (newBuffer[i] == DirectorySeparatorChar) { lastSlash = i; break; } } if (lastSlash >= 0) { // This bounds check is for safe memcpy but we should never get this far if (newBuffer.Length >= maxPathLength) { throw new PathTooLongException(SR.IO_PathTooLong); } int lenSavedName = newBuffer.Length - lastSlash - 1; Debug.Assert(lastSlash < newBuffer.Length, "path unexpectedly ended in a '\'"); newBuffer.Fixup(lenSavedName, lastSlash); } } } } if (result != 0) { /* Throw an ArgumentException for paths like \\, \\server, \\server\ * This check can only be properly done after normalizing, so \\foo\.. will be properly rejected. */ if (newBuffer.Length > 1 && newBuffer[0] == '\\' && newBuffer[1] == '\\') { int startIndex = 2; while (startIndex < result) { if (newBuffer[startIndex] == '\\') { startIndex++; break; } else { startIndex++; } } if (startIndex == result) { throw new ArgumentException(SR.Arg_PathIllegalUNC); } } } // Check our result and form the managed string as necessary. if (newBuffer.Length >= maxPathLength) { throw new PathTooLongException(SR.IO_PathTooLong); } if (result == 0) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode == 0) { errorCode = Interop.mincore.Errors.ERROR_BAD_PATHNAME; } throw Win32Marshal.GetExceptionForWin32Error(errorCode, path); } string returnVal = newBuffer.ToString(); if (string.Equals(returnVal, path, StringComparison.Ordinal)) { returnVal = path; } return(returnVal); }
private static string TryExpandShortFileName(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.IsRelative(outputBuffer), "should have resolved by now"); Debug.Assert(!PathInternal.IsExtended(outputBuffer), "expanding short names expects normal paths"); // Add the extended prefix before expanding to allow growth over MAX_PATH StringBuffer inputBuffer = null; ulong rootLength = PathInternal.GetRootLength(outputBuffer); bool isUnc = IsUnc(outputBuffer); ulong rootDifference = GetInputBuffer(outputBuffer, isUnc, out inputBuffer); rootLength += rootDifference; ulong inputLength = inputBuffer.Length; bool success = false; ulong foundIndex = inputBuffer.Length - 1; while (!success) { uint result = Interop.mincore.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), (uint)outputBuffer.CharCapacity); // 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.mincore.Errors.ERROR_FILE_NOT_FOUND && error != Interop.mincore.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.CharCapacity) { // Not enough space. The result count for this API does not include the null terminator. outputBuffer.EnsureCharCapacity(result); result = Interop.mincore.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), (uint)outputBuffer.CharCapacity); } else { // Found the path success = true; outputBuffer.Length = result; if (foundIndex < inputLength - 1) { // It was a partial find, put the non-existant part of the path back outputBuffer.Append(inputBuffer, foundIndex, inputBuffer.Length - foundIndex); } } } // Strip out the prefix and return the string StringBuffer bufferToUse = success ? outputBuffer : inputBuffer; string returnValue = null; int newLength = (int)(bufferToUse.Length - rootDifference); if (isUnc) { // Need to go from \\?\UNC\ to \\?\UN\\ bufferToUse[(ulong)PathInternal.UncExtendedPathPrefix.Length - 1] = '\\'; } if (bufferToUse.SubstringEquals(originalPath, rootDifference, newLength)) { // Use the original path to avoid allocating returnValue = originalPath; } else { returnValue = bufferToUse.Substring(rootDifference, newLength); } inputBuffer.Dispose(); return(returnValue); }
// Token: 0x06001946 RID: 6470 RVA: 0x00054019 File Offset: 0x00052219 internal static bool IsDirectoryTooLong(string fullPath) { if (AppContextSwitches.BlockLongPaths && (AppContextSwitches.UseLegacyPathHandling || !PathInternal.IsExtended(fullPath))) { return(fullPath.Length >= 248); } return(PathInternal.IsPathTooLong(fullPath)); }
// Token: 0x06001950 RID: 6480 RVA: 0x000542E0 File Offset: 0x000524E0 internal static bool IsExtendedUnc(StringBuilder path) { return(path.Length >= "\\\\?\\UNC\\".Length && PathInternal.IsExtended(path) && char.ToUpper(path[4]) == 'U' && char.ToUpper(path[5]) == 'N' && char.ToUpper(path[6]) == 'C' && path[7] == '\\'); }
// Token: 0x0600194B RID: 6475 RVA: 0x0005412C File Offset: 0x0005232C internal static bool IsDevice(StringBuffer path) { return(PathInternal.IsExtended(path) || (path.Length >= 4U && PathInternal.IsDirectorySeparator(path[0U]) && PathInternal.IsDirectorySeparator(path[1U]) && (path[2U] == '.' || path[2U] == '?') && PathInternal.IsDirectorySeparator(path[3U]))); }
// Token: 0x0600194A RID: 6474 RVA: 0x000540CC File Offset: 0x000522CC internal static bool IsDevice(string path) { return(PathInternal.IsExtended(path) || (path.Length >= 4 && PathInternal.IsDirectorySeparator(path[0]) && PathInternal.IsDirectorySeparator(path[1]) && (path[2] == '.' || path[2] == '?') && PathInternal.IsDirectorySeparator(path[3]))); }
// Token: 0x06001941 RID: 6465 RVA: 0x00053E6C File Offset: 0x0005206C internal static bool HasInvalidVolumeSeparator(string path) { int num = (!AppContextSwitches.UseLegacyPathHandling && PathInternal.IsExtended(path)) ? "\\\\?\\".Length : PathInternal.PathStartSkip(path); return((path.Length > num && path[num] == Path.VolumeSeparatorChar) || (path.Length >= num + 2 && path[num + 1] == Path.VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[num])) || (path.Length > num + 2 && path.IndexOf(Path.VolumeSeparatorChar, num + 2) != -1)); }
/// <summary> /// Only check for ? and *. /// </summary> internal static bool HasAdditionalIllegalCharacters(string path) { int startIndex = PathInternal.IsExtended(path) ? ExtendedPathPrefix.Length : 0; return(path.IndexOfAny(AdditionalInvalidPathChars, startIndex) >= 0); }