/// <summary> /// Returns the volume name for dos, UNC and device paths. /// </summary> internal static ReadOnlySpan <char> GetVolumeName(ReadOnlySpan <char> path) { // 3 cases: UNC ("\\server\share"), Device ("\\?\C:\"), or Dos ("C:\") ReadOnlySpan <char> root = GetPathRoot(path); if (root.Length == 0) { return(root); } // Cut from "\\?\UNC\Server\Share" to "Server\Share" // Cut from "\\Server\Share" to "Server\Share" int startOffset = GetUncRootLength(path); if (startOffset == -1) { if (PathInternal.IsDevice(path)) { startOffset = 4; // Cut from "\\?\C:\" to "C:" } else { startOffset = 0; // e.g. "C:" } } ReadOnlySpan <char> pathToTrim = root.Slice(startOffset); return(Path.EndsInDirectorySeparator(pathToTrim) ? pathToTrim.Slice(0, pathToTrim.Length - 1) : pathToTrim); }
internal unsafe static bool HasWildCardCharacters(string path) { // Question mark is part of dos device syntax so we have to skip if we are int startIndex = PathInternal.IsDevice(path) ? ExtendedPathPrefix.Length : 0; return(path.IndexOfAny(s_wildcardChars, startIndex) >= 0); }
/// <summary> /// Returns the volume name for dos, UNC and device paths. /// </summary> internal static ReadOnlySpan <char> GetVolumeName(ReadOnlySpan <char> path) { // 3 cases: UNC ("\\server\share"), Device ("\\?\C:\"), or Dos ("C:\") ReadOnlySpan <char> root = GetPathRoot(path); if (root.Length == 0) { return(root); } int offset = GetUncRootLength(path); if (offset >= 0) { // Cut from "\\?\UNC\Server\Share" to "Server\Share" // Cut from "\\Server\Share" to "Server\Share" return(TrimEndingDirectorySeparator(root.Slice(offset))); } else if (PathInternal.IsDevice(path)) { return(TrimEndingDirectorySeparator(root.Slice(4))); // Cut from "\\?\C:\" to "C:" } return(TrimEndingDirectorySeparator(root)); // e.g. "C:" }
internal static string EnsureExtendedPrefix(string path) { if (PathInternal.IsPartiallyQualified(path.AsSpan()) || PathInternal.IsDevice(path.AsSpan())) { return(path); } return(path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase) ? path.Insert(2, "?\\UNC\\") : "\\\\?\\" + path); }
private static void VerifyPath(string path) { if (path != null) { path = path.Trim(); #if !PLATFORM_UNIX if (!PathInternal.IsDevice(path) && PathInternal.HasInvalidVolumeSeparator(path)) { throw new ArgumentException(Environment.GetResourceString("Argument_PathFormatNotSupported")); } #endif PathInternal.CheckInvalidPathChars(path); } }
/// <summary> /// Returns offset as -1 if the path is not in Unc format, otherwise returns the root length. /// </summary> /// <param name="path"></param> /// <returns></returns> internal static int GetUncRootLength(ReadOnlySpan <char> path) { bool isDevice = PathInternal.IsDevice(path); if (!isDevice && StringSpanHelpers.Equals(path.Slice(0, 2), @"\\")) { return(2); } else if (isDevice && path.Length >= 8 && (StringSpanHelpers.Equals(path.Slice(0, 8), PathInternal.UncExtendedPathPrefix) || StringSpanHelpers.Equals(path.Slice(5, 4), @"UNC\"))) { return(8); } return(-1); }
/// <summary> /// Returns offset as -1 if the path is not in Unc format, otherwise returns the root length. /// </summary> /// <param name="path"></param> /// <returns></returns> internal static int GetUncRootLength(ReadOnlySpan <char> path) { bool isDevice = PathInternal.IsDevice(path); if (!isDevice && path.Slice(0, 2).EqualsOrdinal(@"\\")) { return(2); } else if (isDevice && path.Length >= 8 && (path.Slice(0, 8).EqualsOrdinal(PathInternal.UncExtendedPathPrefix) || path.Slice(5, 4).EqualsOrdinal(@"UNC\"))) { return(8); } return(-1); }
internal static int GetRootLength(ReadOnlySpan <char> path) { int length = path.Length; int index = 0; bool flag1 = PathInternal.IsDevice(path); bool flag2 = flag1 && PathInternal.IsDeviceUNC(path); if (!flag1 | flag2 && length > 0 && PathInternal.IsDirectorySeparator(path[0])) { if (flag2 || length > 1 && PathInternal.IsDirectorySeparator(path[1])) { index = flag2 ? 8 : 2; int num = 2; while (index < length && (!PathInternal.IsDirectorySeparator(path[index]) || --num > 0)) { ++index; } } else { index = 1; } } else if (flag1) { index = 4; while (index < length && !PathInternal.IsDirectorySeparator(path[index])) { ++index; } if (index < length && index > 4 && PathInternal.IsDirectorySeparator(path[index])) { ++index; } } else if (length >= 2 && path[1] == ':' && PathInternal.IsValidDriveChar(path[0])) { index = 2; if (length > 2 && PathInternal.IsDirectorySeparator(path[2])) { ++index; } } return(index); }
public static string GetFullPath(string path, string basePath) { if (path == null) { throw new ArgumentNullException(nameof(path)); } if (basePath == null) { throw new ArgumentNullException(nameof(basePath)); } if (!IsPathFullyQualified(basePath)) { throw new ArgumentException(SR.Arg_BasePathNotFullyQualified, nameof(basePath)); } if (basePath.Contains('\0') || path.Contains('\0')) { throw new ArgumentException(SR.Argument_InvalidPathChars); } if (IsPathFullyQualified(path)) { return(GetFullPath(path)); } int length = path.Length; string combinedPath = null; if ((length >= 1 && PathInternal.IsDirectorySeparator(path[0]))) { // Path is current drive rooted i.e. starts with \: // "\Foo" and "C:\Bar" => "C:\Foo" // "\Foo" and "\\?\C:\Bar" => "\\?\C:\Foo" combinedPath = Join(GetPathRoot(basePath.AsSpan()), path); } else if (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar) { // Drive relative paths Debug.Assert(length == 2 || !PathInternal.IsDirectorySeparator(path[2])); if (StringSpanHelpers.Equals(GetVolumeName(path), GetVolumeName(basePath))) { // Matching root // "C:Foo" and "C:\Bar" => "C:\Bar\Foo" // "C:Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo" combinedPath = Join(basePath, path.AsSpan().Slice(2)); } else { // No matching root, root to specified drive // "D:Foo" and "C:\Bar" => "D:Foo" // "D:Foo" and "\\?\C:\Bar" => "\\?\D:\Foo" combinedPath = !PathInternal.IsDevice(basePath) ? path.Insert(2, @"\") : length == 2 ? JoinInternal(basePath.AsSpan().Slice(0, 4), path, @"\") : JoinInternal(basePath.AsSpan().Slice(0, 4), path.AsSpan().Slice(0, 2), @"\", path.AsSpan().Slice(2)); } } else { // "Simple" relative path // "Foo" and "C:\Bar" => "C:\Bar\Foo" // "Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo" combinedPath = JoinInternal(basePath, path); } // Device paths are normalized by definition, so passing something of this format // to GetFullPath() won't do anything by design. Additionally, GetFullPathName() in // Windows doesn't root them properly. As such we need to manually remove segments. return(PathInternal.IsDevice(combinedPath) ? RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath)) : GetFullPath(combinedPath)); }
// 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); }
/// <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(); } }
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); }
private static bool IsDosUnc(ref StringBuffer buffer) { return(!PathInternal.IsDevice(ref buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\'); }
internal static string TryExpandShortFileName(ref ValueStringBuilder outputBuilder, 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 a lot we'll create only one input array and modify the contents with embedded nulls. Debug.Assert(!PathInternal.IsPartiallyQualified(outputBuilder.AsSpan()), "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 (for long path support and speed), 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(outputBuilder.AsSpan()); bool isDevice = PathInternal.IsDevice(outputBuilder.AsSpan()); // As this is a corner case we're not going to add a stackalloc here to keep the stack pressure down. var inputBuilder = new ValueStringBuilder(); 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 \\.\) inputBuilder.Append(outputBuilder.AsSpan()); if (outputBuilder[2] == '.') { wasDotDevice = true; inputBuilder[2] = '?'; } } else { isDosUnc = !PathInternal.IsDevice(outputBuilder.AsSpan()) && outputBuilder.Length > 1 && outputBuilder[0] == '\\' && outputBuilder[1] == '\\'; rootDifference = PrependDevicePathChars(ref outputBuilder, isDosUnc, ref inputBuilder); } rootLength += rootDifference; int inputLength = inputBuilder.Length; bool success = false; int foundIndex = inputBuilder.Length - 1; while (!success) { uint result = Interop.Kernel32.GetLongPathNameW( ref inputBuilder.GetPinnableReference(terminate: true), ref outputBuilder.GetPinnableReference(), (uint)outputBuilder.Capacity); // Replace any temporary null we added if (inputBuilder[foundIndex] == '\0') { inputBuilder[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 && inputBuilder[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 inputBuilder[foundIndex] = '\0'; } } else if (result > outputBuilder.Capacity) { // Not enough space. The result count for this API does not include the null terminator. outputBuilder.EnsureCapacity(checked ((int)result)); } else { // Found the path success = true; outputBuilder.Length = checked ((int)result); if (foundIndex < inputLength - 1) { // It was a partial find, put the non-existent part of the path back outputBuilder.Append(inputBuilder.AsSpan(foundIndex, inputBuilder.Length - foundIndex)); } } } // If we were able to expand the path, use it, otherwise use the original full path result ref ValueStringBuilder builderToUse = ref (success ? ref outputBuilder : ref inputBuilder);
internal unsafe static string Normalize(string path, uint maxPathLength, bool checkInvalidCharacters, bool expandShortPaths) { StringBuffer stringBuffer; if ((stringBuffer = LongPathHelper.t_fullPathBuffer) == null) { stringBuffer = (LongPathHelper.t_fullPathBuffer = new StringBuffer(260U)); } StringBuffer stringBuffer2 = stringBuffer; string result; try { LongPathHelper.GetFullPathName(path, stringBuffer2); stringBuffer2.TrimEnd(LongPathHelper.s_trimEndChars); if (stringBuffer2.Length >= maxPathLength) { throw new PathTooLongException(); } bool flag = false; bool flag2 = false; bool flag3 = stringBuffer2.Length > 1U && stringBuffer2[0U] == '\\' && stringBuffer2[1U] == '\\'; bool flag4 = PathInternal.IsDevice(stringBuffer2); bool flag5 = flag3 && !flag4; uint num = flag3 ? 2U : 0U; uint num2 = flag3 ? 1U : 0U; char *charPointer = stringBuffer2.CharPointer; uint num3; while (num < stringBuffer2.Length) { char c = charPointer[(ulong)num * 2UL / 2UL]; if (c < '?' || c == '\\' || c == '|' || c == '~') { if (c <= '>') { if (c != '"' && c != '<' && c != '>') { goto IL_155; } } else if (c != '\\') { if (c != '|') { if (c != '~') { goto IL_155; } flag2 = true; goto IL_16E; } } else { num3 = num - num2 - 1U; if (num3 > (uint)PathInternal.MaxComponentLength) { throw new PathTooLongException(); } num2 = num; if (flag2) { if (num3 <= 12U) { flag = true; } flag2 = false; } if (!flag5) { goto IL_16E; } if (num == stringBuffer2.Length - 1U) { throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC")); } flag5 = false; goto IL_16E; } if (checkInvalidCharacters) { throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); } flag2 = false; goto IL_16E; IL_155: if (checkInvalidCharacters && c < ' ') { throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); } } IL_16E: num += 1U; } if (flag5) { throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC")); } num3 = stringBuffer2.Length - num2 - 1U; if (num3 > (uint)PathInternal.MaxComponentLength) { throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); } if (flag2 && num3 <= 12U) { flag = true; } if (expandShortPaths && flag) { result = LongPathHelper.TryExpandShortFileName(stringBuffer2, path); } else if (stringBuffer2.Length == (uint)path.Length && stringBuffer2.StartsWith(path)) { result = path; } else { result = stringBuffer2.ToString(); } } finally { stringBuffer2.Free(); } return(result); }
internal static bool IsDeviceUNC(ReadOnlySpan <char> path) { return(path.Length >= 8 && PathInternal.IsDevice(path) && (PathInternal.IsDirectorySeparator(path[7]) && path[4] == 'U') && path[5] == 'N' && path[6] == 'C'); }
// Token: 0x06001953 RID: 6483 RVA: 0x0005437C File Offset: 0x0005257C internal static bool HasWildCardCharacters(string path) { int startIndex = AppContextSwitches.UseLegacyPathHandling ? 0 : (PathInternal.IsDevice(path) ? "\\\\?\\".Length : 0); return(PathInternal.AnyPathHasWildCardCharacters(path, startIndex)); }
// Token: 0x06001951 RID: 6481 RVA: 0x00054344 File Offset: 0x00052544 internal static bool HasIllegalCharacters(string path, bool checkAdditional = false) { return((AppContextSwitches.UseLegacyPathHandling || !PathInternal.IsDevice(path)) && PathInternal.AnyPathHasIllegalCharacters(path, checkAdditional)); }
public static string GetFullPath(string path, string basePath) { if (path == null) { throw new ArgumentNullException(nameof(path)); } if (basePath == null) { throw new ArgumentNullException(nameof(basePath)); } if (!IsPathFullyQualified(basePath)) { throw new ArgumentException(SR.Arg_BasePathNotFullyQualified, nameof(basePath)); } if (basePath.Contains('\0') || path.Contains('\0')) { throw new ArgumentException(SR.Argument_InvalidPathChars); } if (IsPathFullyQualified(path)) { return(GetFullPathInternal(path)); } if (PathInternal.IsEffectivelyEmpty(path.AsSpan())) { return(basePath); } int length = path.Length; string combinedPath; if (length >= 1 && PathInternal.IsDirectorySeparator(path[0])) { // Path is current drive rooted i.e. starts with \: // "\Foo" and "C:\Bar" => "C:\Foo" // "\Foo" and "\\?\C:\Bar" => "\\?\C:\Foo" combinedPath = Join(GetPathRoot(basePath.AsSpan()), path.AsSpan(1)); // Cut the separator to ensure we don't end up with two separators when joining with the root. } else if (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar) { // Drive relative paths Debug.Assert(length == 2 || !PathInternal.IsDirectorySeparator(path[2])); if (GetVolumeName(path.AsSpan()).EqualsOrdinal(GetVolumeName(basePath.AsSpan()))) { // Matching root // "C:Foo" and "C:\Bar" => "C:\Bar\Foo" // "C:Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo" combinedPath = Join(basePath.AsSpan(), path.AsSpan(2)); } else { // No matching root, root to specified drive // "D:Foo" and "C:\Bar" => "D:Foo" // "D:Foo" and "\\?\C:\Bar" => "\\?\D:\Foo" combinedPath = !PathInternal.IsDevice(basePath.AsSpan()) ? path.Insert(2, @"\") : length == 2 ? JoinInternal(basePath.AsSpan(0, 4), path.AsSpan(), @"\".AsSpan()) : JoinInternal(basePath.AsSpan(0, 4), path.AsSpan(0, 2), @"\".AsSpan(), path.AsSpan(2)); } } else { // "Simple" relative path // "Foo" and "C:\Bar" => "C:\Bar\Foo" // "Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo" combinedPath = JoinInternal(basePath.AsSpan(), path.AsSpan()); } // Device paths are normalized by definition, so passing something of this format (i.e. \\?\C:\.\tmp, \\.\C:\foo) // to Windows APIs won't do anything by design. Additionally, GetFullPathName() in Windows doesn't root // them properly. As such we need to manually remove segments and not use GetFullPath(). return(PathInternal.IsDevice(combinedPath.AsSpan()) ? PathInternal.RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath.AsSpan())) : GetFullPathInternal(combinedPath)); }
private static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath) { // 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 \\.\. uint rootLength = PathInternal.GetRootLength(outputBuffer); bool isDevice = PathInternal.IsDevice(outputBuffer); StringBuffer inputBuffer = null; bool isDosUnc = false; uint 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 \\.\) // We will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is). inputBuffer = new StringBuffer(); inputBuffer.Append(outputBuffer); if (outputBuffer[2] == '.') { wasDotDevice = true; inputBuffer[2] = '?'; } } else { // \\Server\Share, but not \\.\ or \\?\. // We need to know this to be able to push \\?\UNC\ on if required isDosUnc = outputBuffer.Length > 1 && outputBuffer[0] == '\\' && outputBuffer[1] == '\\' && !PathInternal.IsDevice(outputBuffer); rootDifference = GetInputBuffer(outputBuffer, isDosUnc, out inputBuffer); } rootLength += rootDifference; uint inputLength = inputBuffer.Length; bool success = false; uint foundIndex = inputBuffer.Length - 1; while (!success) { uint result = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), 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 != Win32Native.ERROR_FILE_NOT_FOUND && error != Win32Native.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 = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity); } else { // Found the path success = true; outputBuffer.Length = result; if (foundIndex < inputLength - 1) { // It was a partial find, put the non-existent 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; if (wasDotDevice) { bufferToUse[2] = '.'; } string returnValue = null; int newLength = (int)(bufferToUse.Length - rootDifference); if (isDosUnc) { // Need to go from \\?\UNC\ to \\?\UN\\ bufferToUse[(uint)PathInternal.UncExtendedPathPrefix.Length - 1] = '\\'; } // 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); } inputBuffer.Dispose(); return(returnValue); }