private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options) { Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); int fAccess = ((_access & FileAccess.Read) == FileAccess.Read ? GENERIC_READ : 0) | ((_access & FileAccess.Write) == FileAccess.Write ? GENERIC_WRITE : 0); // Our Inheritable bit was stolen from Windows, but should be set in // the security attributes class. Don't leave this bit set. share &= ~FileShare.Inheritable; // Must use a valid Win32 constant here... if (mode == FileMode.Append) { mode = FileMode.OpenOrCreate; } int flagsAndAttributes = (int)options; // For mitigating local elevation of privilege attack through named pipes // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the // named pipe server can't impersonate a high privileged client security context flagsAndAttributes |= (Interop.Kernel32.SecurityOptions.SECURITY_SQOS_PRESENT | Interop.Kernel32.SecurityOptions.SECURITY_ANONYMOUS); // Don't pop up a dialog for reading from an empty floppy drive uint oldMode; bool success = Interop.Kernel32.SetThreadErrorMode(Interop.Kernel32.SEM_FAILCRITICALERRORS, out oldMode); try { SafeFileHandle fileHandle = Interop.Kernel32.CreateFile(_path, fAccess, share, ref secAttrs, mode, flagsAndAttributes, IntPtr.Zero); fileHandle.IsAsync = _useAsyncIO; if (fileHandle.IsInvalid) { // Return a meaningful exception with the full path. // NT5 oddity - when trying to open "C:\" as a Win32FileStream, // we usually get ERROR_PATH_NOT_FOUND from the OS. We should // probably be consistent w/ every other directory. int errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND && _path.Length == PathInternal.GetRootLength(_path)) { errorCode = Interop.Errors.ERROR_ACCESS_DENIED; } throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } int fileType = Interop.Kernel32.GetFileType(fileHandle); if (fileType != Interop.Kernel32.FileTypes.FILE_TYPE_DISK) { fileHandle.Dispose(); throw new NotSupportedException(SR.NotSupported_FileStreamOnNonFiles); } return(fileHandle); } finally { if (success) { Interop.Kernel32.SetThreadErrorMode(oldMode, out oldMode); } } }
public static string Combine(params string[] paths) { if (paths == null) { throw new ArgumentNullException("paths"); } Contract.EndContractBlock(); int finalSize = 0; int firstComponent = 0; // We have two passes, the first calcuates how large a buffer to allocate and does some precondition // checks on the paths passed in. The second actually does the combination. for (int i = 0; i < paths.Length; i++) { if (paths[i] == null) { throw new ArgumentNullException("paths"); } if (paths[i].Length == 0) { continue; } PathInternal.CheckInvalidPathChars(paths[i]); if (IsPathRooted(paths[i])) { firstComponent = i; finalSize = paths[i].Length; } else { finalSize += paths[i].Length; } char ch = paths[i][paths[i].Length - 1]; if (!IsDirectoryOrVolumeSeparator(ch)) { finalSize++; } } StringBuilder finalPath = StringBuilderCache.Acquire(finalSize); for (int i = firstComponent; i < paths.Length; i++) { if (paths[i].Length == 0) { continue; } if (finalPath.Length == 0) { finalPath.Append(paths[i]); } else { char ch = finalPath[finalPath.Length - 1]; if (!IsDirectoryOrVolumeSeparator(ch)) { finalPath.Append(DirectorySeparatorChar); } finalPath.Append(paths[i]); } } return(StringBuilderCache.GetStringAndRelease(finalPath)); }
public void MoveTo(String destDirName) { if (destDirName == null) { throw new ArgumentNullException("destDirName"); } if (destDirName.Length == 0) { throw new ArgumentException(SR.Argument_EmptyFileName, "destDirName"); } Contract.EndContractBlock(); String fullDestDirName = PathHelpers.GetFullPathInternal(destDirName); if (fullDestDirName[fullDestDirName.Length - 1] != Path.DirectorySeparatorChar) { fullDestDirName = fullDestDirName + PathHelpers.DirectorySeparatorCharAsString; } String fullSourcePath; if (FullPath.Length > 0 && FullPath[FullPath.Length - 1] == Path.DirectorySeparatorChar) { fullSourcePath = FullPath; } else { fullSourcePath = FullPath + PathHelpers.DirectorySeparatorCharAsString; } if (PathInternal.IsDirectoryTooLong(fullSourcePath)) { throw new PathTooLongException(SR.IO_PathTooLong); } if (PathInternal.IsDirectoryTooLong(fullDestDirName)) { throw new PathTooLongException(SR.IO_PathTooLong); } StringComparison pathComparison = PathInternal.StringComparison; if (String.Equals(fullSourcePath, fullDestDirName, pathComparison)) { throw new IOException(SR.IO_SourceDestMustBeDifferent); } String sourceRoot = Path.GetPathRoot(fullSourcePath); String destinationRoot = Path.GetPathRoot(fullDestDirName); if (!String.Equals(sourceRoot, destinationRoot, pathComparison)) { throw new IOException(SR.IO_SourceDestMustHaveSameRoot); } FileSystem.Current.MoveDirectory(FullPath, fullDestDirName); FullPath = fullDestDirName; OriginalPath = destDirName; DisplayPath = GetDisplayName(OriginalPath, FullPath); // Flush any cached information about the directory. Invalidate(); }
private static string CombineNoChecks(string path1, string path2, string path3, string path4) { if (path1.Length == 0) { return(CombineNoChecks(path2, path3, path4)); } if (path2.Length == 0) { return(CombineNoChecks(path1, path3, path4)); } if (path3.Length == 0) { return(CombineNoChecks(path1, path2, path4)); } if (path4.Length == 0) { return(CombineNoChecks(path1, path2, path3)); } if (IsPathRooted(path4)) { return(path4); } if (IsPathRooted(path3)) { return(CombineNoChecks(path3, path4)); } if (IsPathRooted(path2)) { return(CombineNoChecks(path2, path3, path4)); } bool hasSep1 = PathInternal.IsDirectoryOrVolumeSeparator(path1[path1.Length - 1]); bool hasSep2 = PathInternal.IsDirectoryOrVolumeSeparator(path2[path2.Length - 1]); bool hasSep3 = PathInternal.IsDirectoryOrVolumeSeparator(path3[path3.Length - 1]); if (hasSep1 && hasSep2 && hasSep3) { // Use string.Concat overload that takes four strings return(path1 + path2 + path3 + path4); } else { // string.Concat only has string-based overloads up to four arguments; after that requires allocating // a params string[]. Instead, try to use a cached StringBuilder. StringBuilder sb = StringBuilderCache.Acquire(path1.Length + path2.Length + path3.Length + path4.Length + 3); sb.Append(path1); if (!hasSep1) { sb.Append(DirectorySeparatorChar); } sb.Append(path2); if (!hasSep2) { sb.Append(DirectorySeparatorChar); } sb.Append(path3); if (!hasSep3) { sb.Append(DirectorySeparatorChar); } sb.Append(path4); return(StringBuilderCache.GetStringAndRelease(sb)); } }
private static bool IsDirectoryOrVolumeSeparator(char c) { return(PathInternal.IsDirectorySeparator(c) || VolumeSeparatorChar == c); }
/// <summary> /// Returns true if the character is a directory or volume separator. /// </summary> /// <param name="ch">The character to test.</param> internal static bool IsDirectoryOrVolumeSeparator(char ch) { return(PathInternal.IsDirectorySeparator(ch) || Path.VolumeSeparatorChar == ch); }
internal unsafe bool TryExpandShortFileName() { if (doNotTryExpandShortFileName) { return(false); } if (useStackAlloc) { NullTerminate(); char *buffer = UnsafeGetArrayPtr(); char *shortFileNameBuffer = stackalloc char[Path.MaxPath + 1]; int r = Interop.mincore.GetLongPathNameUnsafe(buffer, shortFileNameBuffer, Path.MaxPath); // If success, the return buffer length does not account for the terminating null character. // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. // If failure, the return buffer length is zero if (r >= Path.MaxPath) { throw new PathTooLongException(SR.IO_PathTooLong); } if (r == 0) { // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a // path like \\.\PHYSICALDEVICE0 - some device driver doesn't // support GetLongPathName on that string. This behavior is // by design, according to the Core File Services team. // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\ // We do not need to call GetLongPathName if we know it will fail becasue the path does not exist: int lastErr = Marshal.GetLastWin32Error(); if (lastErr == Interop.mincore.Errors.ERROR_FILE_NOT_FOUND || lastErr == Interop.mincore.Errors.ERROR_PATH_NOT_FOUND) { doNotTryExpandShortFileName = true; } return(false); } // Safe to copy as we have already done Path.MaxPath bound checking Wstrcpy(buffer, shortFileNameBuffer, r); Length = r; // We should explicitly null terminate as in some cases the long version of the path // might actually be shorter than what we started with because of Win32's normalization // Safe to write directly as bufferLength is guaranteed to be < Path.MaxPath NullTerminate(); return(true); } else { StringBuilder sb = GetStringBuilder(); String origName = sb.ToString(); String tempName = origName; bool addedPrefix = false; if (tempName.Length > Path.MaxPath) { tempName = PathInternal.EnsureExtendedPrefix(tempName); addedPrefix = true; } sb.Capacity = m_capacity; sb.Length = 0; int r = Interop.mincore.GetLongPathName(tempName, sb, m_capacity); if (r == 0) { // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a // path like \\.\PHYSICALDEVICE0 - some device driver doesn't // support GetLongPathName on that string. This behavior is // by design, according to the Core File Services team. // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\ // We do not need to call GetLongPathName if we know it will fail becasue the path does not exist: int lastErr = Marshal.GetLastWin32Error(); if (Interop.mincore.Errors.ERROR_FILE_NOT_FOUND == lastErr || Interop.mincore.Errors.ERROR_PATH_NOT_FOUND == lastErr) { doNotTryExpandShortFileName = true; } sb.Length = 0; sb.Append(origName); return(false); } if (addedPrefix) { r -= 4; sb = PathInternal.RemoveExtendedPrefix(sb); } // If success, the return buffer length does not account for the terminating null character. // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. // If failure, the return buffer length is zero if (r >= m_maxPath) { throw new PathTooLongException(SR.IO_PathTooLong); } Length = sb.Length; return(true); } }
private 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 like crazy 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. ValueStringBuilder 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; // Need to null terminate the input builder inputBuilder.Append('\0'); while (!success) { uint result = Interop.Kernel32.GetLongPathNameW(ref inputBuilder.GetPinnableReference(), 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)); result = Interop.Kernel32.GetLongPathNameW(ref inputBuilder.GetPinnableReference(), ref outputBuilder.GetPinnableReference(), (uint)outputBuilder.Capacity); } 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().Slice(foundIndex, inputBuilder.Length - foundIndex)); } } } // Need to trim out the trailing separator in the input builder inputBuilder.Length = inputBuilder.Length - 1; // 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);
/// <summary> /// Gets reparse point information associated to <paramref name="linkPath"/>. /// </summary> /// <returns>The immediate link target, absolute or relative or null if the file is not a supported link.</returns> internal static unsafe string?GetImmediateLinkTarget(string linkPath, bool isDirectory, bool throwOnError, bool returnFullPath) { using SafeFileHandle handle = OpenSafeFileHandle(linkPath, Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS | Interop.Kernel32.FileOperations.FILE_FLAG_OPEN_REPARSE_POINT); if (handle.IsInvalid) { if (!throwOnError) { return(null); } int error = Marshal.GetLastWin32Error(); // File not found doesn't make much sense coming from a directory. if (isDirectory && error == Interop.Errors.ERROR_FILE_NOT_FOUND) { error = Interop.Errors.ERROR_PATH_NOT_FOUND; } throw Win32Marshal.GetExceptionForWin32Error(error, linkPath); } byte[] buffer = ArrayPool <byte> .Shared.Rent(Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE); try { bool success = Interop.Kernel32.DeviceIoControl( handle, dwIoControlCode: Interop.Kernel32.FSCTL_GET_REPARSE_POINT, lpInBuffer: IntPtr.Zero, nInBufferSize: 0, lpOutBuffer: buffer, nOutBufferSize: Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE, out _, IntPtr.Zero); if (!success) { if (!throwOnError) { return(null); } int error = Marshal.GetLastWin32Error(); // The file or directory is not a reparse point. if (error == Interop.Errors.ERROR_NOT_A_REPARSE_POINT) { return(null); } throw Win32Marshal.GetExceptionForWin32Error(error, linkPath); } Span <byte> bufferSpan = new(buffer); success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.SymbolicLinkReparseBuffer rbSymlink); Debug.Assert(success); // We always use SubstituteName(Offset|Length) instead of PrintName(Offset|Length), // the latter is just the display name of the reparse point and it can show something completely unrelated to the target. if (rbSymlink.ReparseTag == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) { int offset = sizeof(Interop.Kernel32.SymbolicLinkReparseBuffer) + rbSymlink.SubstituteNameOffset; int length = rbSymlink.SubstituteNameLength; Span <char> targetPath = MemoryMarshal.Cast <byte, char>(bufferSpan.Slice(offset, length)); bool isRelative = (rbSymlink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) != 0; if (!isRelative) { // Absolute target is in NT format and we need to clean it up before return it to the user. if (targetPath.StartsWith(PathInternal.UncNTPathPrefix.AsSpan())) { // We need to prepend the Win32 equivalent of UNC NT prefix. return(Path.Join(PathInternal.UncPathPrefix.AsSpan(), targetPath.Slice(PathInternal.UncNTPathPrefix.Length))); } return(GetTargetPathWithoutNTPrefix(targetPath)); } else if (returnFullPath) { return(Path.Join(Path.GetDirectoryName(linkPath.AsSpan()), targetPath)); } else { return(targetPath.ToString()); } } else if (rbSymlink.ReparseTag == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.MountPointReparseBuffer rbMountPoint); Debug.Assert(success); int offset = sizeof(Interop.Kernel32.MountPointReparseBuffer) + rbMountPoint.SubstituteNameOffset; int length = rbMountPoint.SubstituteNameLength; Span <char> targetPath = MemoryMarshal.Cast <byte, char>(bufferSpan.Slice(offset, length)); // Unlike symbolic links, mount point paths cannot be relative. Debug.Assert(!PathInternal.IsPartiallyQualified(targetPath)); // Mount points cannot point to a remote location. Debug.Assert(!targetPath.StartsWith(PathInternal.UncNTPathPrefix.AsSpan())); return(GetTargetPathWithoutNTPrefix(targetPath)); } return(null); } finally { ArrayPool <byte> .Shared.Return(buffer); }
private string TryExpandShortFileName(StringBuilder outputBuffer) { // 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 char[] inputBuffer = null; int rootLength = PathInternal.GetRootLength(outputBuffer); bool isUnc = this.IsUnc(outputBuffer); int rootDifference = this.GetInputBuffer(outputBuffer, isUnc, out inputBuffer); rootLength += rootDifference; int inputLength = inputBuffer.Length; bool success = false; int foundIndex = inputBuffer.Length - 1; while (!success) { int result = Interop.mincore.GetLongPathNameW(inputBuffer, outputBuffer, outputBuffer.Capacity + 1); // 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.Capacity) { // Not enough space. The result count for this API does not include the null terminator. outputBuffer.EnsureCapacity(result); result = Interop.mincore.GetLongPathNameW(inputBuffer, outputBuffer, outputBuffer.Capacity + 1); } else { // Found the path success = true; 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 if (success) { if (isUnc) { // Need to go from \\?\UNC\ to \\?\UN\\ outputBuffer[PathInternal.UncExtendedPathPrefix.Length - 1] = '\\'; return(outputBuffer.ToString(rootDifference, outputBuffer.Length - rootDifference)); } else { return(outputBuffer.ToString(rootDifference, outputBuffer.Length - rootDifference)); } } else { // Failed to get and expanded path, clean up our input if (isUnc) { // Need to go from \\?\UNC\ to \\?\UN\\ inputBuffer[PathInternal.UncExtendedPathPrefix.Length - 1] = '\\'; return(new string(inputBuffer, rootDifference, inputBuffer.Length - rootDifference)); } else { return(new string(inputBuffer, rootDifference, inputBuffer.Length - rootDifference)); } } }
private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType) { if (string.IsNullOrEmpty(relativeTo)) { throw new ArgumentNullException(nameof(relativeTo)); } if (PathInternal.IsEffectivelyEmpty(path)) { throw new ArgumentNullException(nameof(path)); } Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase); relativeTo = GetFullPath(relativeTo); path = GetFullPath(path); // Need to check if the roots are different- if they are we need to return the "to" path. if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType)) { return(path); } int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase); // If there is nothing in common they can't share the same root, return the "to" path as is. if (commonLength == 0) { return(path); } // Trailing separators aren't significant for comparison int relativeToLength = relativeTo.Length; if (PathInternal.EndsInDirectorySeparator(relativeTo)) { relativeToLength--; } bool pathEndsInSeparator = PathInternal.EndsInDirectorySeparator(path); int pathLength = path.Length; if (pathEndsInSeparator) { pathLength--; } // If we have effectively the same path, return "." if (relativeToLength == pathLength && commonLength >= relativeToLength) { return("."); } // We have the same root, we need to calculate the difference now using the // common Length and Segment count past the length. // // Some examples: // // C:\Foo C:\Bar L3, S1 -> ..\Bar // C:\Foo C:\Foo\Bar L6, S0 -> Bar // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar StringBuilder sb = StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length)); // Add parent segments for segments past the common on the "from" path if (commonLength < relativeToLength) { sb.Append(PathInternal.ParentDirectoryPrefix); for (int i = commonLength; i < relativeToLength; i++) { if (PathInternal.IsDirectorySeparator(relativeTo[i])) { sb.Append(PathInternal.ParentDirectoryPrefix); } } } else if (PathInternal.IsDirectorySeparator(path[commonLength])) { // No parent segments and we need to eat the initial separator // (C:\Foo C:\Foo\Bar case) commonLength++; } // Now add the rest of the "to" path, adding back the trailing separator int count = pathLength - commonLength; if (pathEndsInSeparator) { count++; } sb.Append(path, commonLength, count); return(StringBuilderCache.GetStringAndRelease(sb)); }
public static bool IsPathFullyQualified(ReadOnlySpan <char> path) { return(!PathInternal.IsPartiallyQualified(path)); }
public static string Join(params string?[] paths) { ArgumentNullException.ThrowIfNull(paths); if (paths.Length == 0) { return(string.Empty); } int maxSize = 0; foreach (string?path in paths) { maxSize += path?.Length ?? 0; } maxSize += paths.Length - 1; var builder = new ValueStringBuilder(stackalloc char[260]); // MaxShortPath on Windows builder.EnsureCapacity(maxSize); for (int i = 0; i < paths.Length; i++) { string?path = paths[i]; if (string.IsNullOrEmpty(path)) { continue; } if (builder.Length == 0) { builder.Append(path); } else { if (!PathInternal.IsDirectorySeparator(builder[builder.Length - 1]) && !PathInternal.IsDirectorySeparator(path[0])) { builder.Append(PathInternal.DirectorySeparatorChar); } builder.Append(path); } } return(builder.ToString()); }
public static string Combine(params string[] paths) { ArgumentNullException.ThrowIfNull(paths); int maxSize = 0; int firstComponent = 0; // We have two passes, the first calculates how large a buffer to allocate and does some precondition // checks on the paths passed in. The second actually does the combination. for (int i = 0; i < paths.Length; i++) { ArgumentNullException.ThrowIfNull(paths[i], nameof(paths)); if (paths[i].Length == 0) { continue; } if (IsPathRooted(paths[i])) { firstComponent = i; maxSize = paths[i].Length; } else { maxSize += paths[i].Length; } char ch = paths[i][paths[i].Length - 1]; if (!PathInternal.IsDirectorySeparator(ch)) { maxSize++; } } var builder = new ValueStringBuilder(stackalloc char[260]); // MaxShortPath on Windows builder.EnsureCapacity(maxSize); for (int i = firstComponent; i < paths.Length; i++) { if (paths[i].Length == 0) { continue; } if (builder.Length == 0) { builder.Append(paths[i]); } else { char ch = builder[builder.Length - 1]; if (!PathInternal.IsDirectorySeparator(ch)) { builder.Append(PathInternal.DirectorySeparatorChar); } builder.Append(paths[i]); } } return(builder.ToString()); }
/// <summary> /// Try to remove relative segments from the given path (without combining with a root). /// </summary> /// <param name="rootLength">The length of the root of the given path</param> internal static string RemoveRelativeSegments(string path, int rootLength) { Debug.Assert(rootLength > 0); bool flippedSeparator = false; int skip = rootLength; // We treat "\.." , "\." and "\\" as a relative segment. We want to collapse the first separator past the root presuming // the root actually ends in a separator. Otherwise the first segment for RemoveRelativeSegments // in cases like "\\?\C:\.\" and "\\?\C:\..\", the first segment after the root will be ".\" and "..\" which is not considered as a relative segment and hence not be removed. if (PathInternal.IsDirectorySeparator(path[skip - 1])) { skip--; } Span <char> initialBuffer = stackalloc char[260 /* PathInternal.MaxShortPath */]; ValueStringBuilder sb = new ValueStringBuilder(initialBuffer); // Remove "//", "/./", and "/../" from the path by copying each character to the output, // except the ones we're removing, such that the builder contains the normalized path // at the end. if (skip > 0) { sb.Append(path.AsSpan(0, skip)); } for (int i = skip; i < path.Length; i++) { char c = path[i]; if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length) { // Skip this character if it's a directory separator and if the next character is, too, // e.g. "parent//child" => "parent/child" if (PathInternal.IsDirectorySeparator(path[i + 1])) { continue; } // Skip this character and the next if it's referring to the current directory, // e.g. "parent/./child" => "parent/child" if ((i + 2 == path.Length || PathInternal.IsDirectorySeparator(path[i + 2])) && path[i + 1] == '.') { i++; continue; } // Skip this character and the next two if it's referring to the parent directory, // e.g. "parent/child/../grandchild" => "parent/grandchild" if (i + 2 < path.Length && (i + 3 == path.Length || PathInternal.IsDirectorySeparator(path[i + 3])) && path[i + 1] == '.' && path[i + 2] == '.') { // Unwind back to the last slash (and if there isn't one, clear out everything). int s; for (s = sb.Length - 1; s >= skip; s--) { if (PathInternal.IsDirectorySeparator(sb[s])) { sb.Length = (i + 3 >= path.Length && s == skip) ? s + 1 : s; // to avoid removing the complete "\tmp\" segment in cases like \\?\C:\tmp\..\, C:\tmp\.. break; } } if (s < skip) { sb.Length = skip; } i += 2; continue; } } // Normalize the directory separator if needed if (c != PathInternal.DirectorySeparatorChar && c == PathInternal.AltDirectorySeparatorChar) { c = PathInternal.DirectorySeparatorChar; flippedSeparator = true; } sb.Append(c); } // If we haven't changed the source path, return the original if (!flippedSeparator && sb.Length == path.Length) { sb.Dispose(); return(path); } return(sb.Length < rootLength?path.Substring(0, rootLength) : sb.ToString()); }
[System.Security.SecurityCritical] // auto-generated internal static String GetDisplayablePath(String path, bool isInvalidPath) { if (String.IsNullOrEmpty(path)) { return(String.Empty); } // Is it a fully qualified path? bool isFullyQualified = false; if (path.Length < 2) { return(path); } if (PathInternal.IsDirectorySeparator(path[0]) && PathInternal.IsDirectorySeparator(path[1])) { isFullyQualified = true; } else if (path[1] == Path.VolumeSeparatorChar) { isFullyQualified = true; } if (!isFullyQualified && !isInvalidPath) { return(path); } bool safeToReturn = false; try { if (!isInvalidPath) { #if !FEATURE_CORECLR new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { path }, false, false).Demand(); #endif safeToReturn = true; } } catch (SecurityException) { } catch (ArgumentException) { // ? and * characters cause ArgumentException to be thrown from HasIllegalCharacters // inside FileIOPermission.AddPathList } catch (NotSupportedException) { // paths like "!Bogus\\dir:with/junk_.in it" can cause NotSupportedException to be thrown // from Security.Util.StringExpressionSet.CanonicalizePath when ':' is found in the path // beyond string index position 1. } if (!safeToReturn) { if (PathInternal.IsDirectorySeparator(path[path.Length - 1])) { path = Environment.GetResourceString("IO.IO_NoPermissionToDirectoryName"); } else { path = Path.GetFileName(path); } } return(path); }
public static void Move(string sourceDirName, string destDirName) { if (sourceDirName == null) { throw new ArgumentNullException(nameof(sourceDirName)); } if (sourceDirName.Length == 0) { throw new ArgumentException(SR.Argument_EmptyFileName, nameof(sourceDirName)); } if (destDirName == null) { throw new ArgumentNullException(nameof(destDirName)); } if (destDirName.Length == 0) { throw new ArgumentException(SR.Argument_EmptyFileName, nameof(destDirName)); } Contract.EndContractBlock(); string fullsourceDirName = Path.GetFullPath(sourceDirName); string sourcePath = EnsureTrailingDirectorySeparator(fullsourceDirName); if (PathInternal.IsDirectoryTooLong(sourcePath)) { throw new PathTooLongException(SR.IO_PathTooLong); } string fulldestDirName = Path.GetFullPath(destDirName); string destPath = EnsureTrailingDirectorySeparator(fulldestDirName); if (PathInternal.IsDirectoryTooLong(destPath)) { throw new PathTooLongException(SR.IO_PathTooLong); } StringComparison pathComparison = PathInternal.StringComparison; if (string.Equals(sourcePath, destPath, pathComparison)) { throw new IOException(SR.IO_SourceDestMustBeDifferent); } string sourceRoot = Path.GetPathRoot(sourcePath); string destinationRoot = Path.GetPathRoot(destPath); if (!string.Equals(sourceRoot, destinationRoot, pathComparison)) { throw new IOException(SR.IO_SourceDestMustHaveSameRoot); } // Windows will throw if the source file/directory doesn't exist, we preemptively check // to make sure our cross platform behavior matches NetFX behavior. if (!FileSystem.Current.DirectoryExists(fullsourceDirName) && !FileSystem.Current.FileExists(fullsourceDirName)) { throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, fullsourceDirName)); } if (FileSystem.Current.DirectoryExists(fulldestDirName)) { throw new IOException(SR.Format(SR.IO_AlreadyExists_Name, fulldestDirName)); } FileSystem.Current.MoveDirectory(fullsourceDirName, fulldestDirName); }
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 { return(NormalizeStandardPath(path, fullCheck, expandShortPaths)); } }
private static string NormalizePath( string path, bool fullCheck, int maxPathLength, bool expandShortPaths) // ignored on Unix { Debug.Assert(path != null); if (path.Length == 0) { throw new ArgumentException(SR.Arg_PathIllegal); } if (fullCheck) { PathInternal.CheckInvalidPathChars(path); // Expand with current directory if necessary if (!IsPathRooted(path)) { path = Combine(Interop.libc.getcwd(), path); } } // Remove "//", "/./", and "/../" from the path. We would ideally use realpath // to do this, but it resolves symlinks, requires that the file actually exist, // and turns it into a full path, which we only want if fullCheck is true. // Instead, we do the normalization manually, copying each character to the output, // except the ones we're removing, such that the builder contains the normalized path // at the end. var sb = StringBuilderCache.Acquire(path.Length); int componentCharCount = 0; for (int i = 0; i < path.Length; i++) { char c = path[i]; if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length) { componentCharCount = 0; // Skip this character if it's a directory separator and if the next character is, too, // e.g. "parent//child" => "parent/child" if (PathInternal.IsDirectorySeparator(path[i + 1])) { continue; } // Skip this character and the next if it's referring to the current directory, // e.g. "parent/./child" =? "parent/child" if ((i + 2 == path.Length || PathInternal.IsDirectorySeparator(path[i + 2])) && path[i + 1] == '.') { i++; continue; } // Skip this character and the next two if it's referring to the parent directory, // e.g. "parent/child/../grandchild" => "parent/grandchild" if (i + 2 < path.Length && (i + 3 == path.Length || PathInternal.IsDirectorySeparator(path[i + 3])) && path[i + 1] == '.' && path[i + 2] == '.') { // Unwind back to the last slash (and if there isn't one, clear out everything). int s; for (s = sb.Length - 1; s >= 0; s--) { if (PathInternal.IsDirectorySeparator(sb[s])) { sb.Length = s; break; } } if (s < 0) { sb.Length = 0; } i += 2; continue; } } if (++componentCharCount > MaxComponentLength) { throw new PathTooLongException(SR.IO_PathTooLong); } sb.Append(c); } Debug.Assert(sb.Length < path.Length || sb.ToString() == path, "Either we've removed characters, or the string should be unmodified from the input path."); if (sb.Length > MaxPath) { throw new PathTooLongException(SR.IO_PathTooLong); } string result = sb.Length == 0 ? (fullCheck ? DirectorySeparatorCharAsString : string.Empty) : sb.Length == path.Length ? path : sb.ToString(); StringBuilderCache.Release(sb); return(result); }
/// <summary> /// Checks for known bad extended paths (paths that start with \\?\) /// </summary> /// <param name="fullCheck">Check for invalid characters if true.</param> /// <returns>'true' if the path passes validity checks.</returns> private static bool ValidateExtendedPath(string path, bool fullCheck) { if (path.Length == PathInternal.ExtendedPathPrefix.Length) { // Effectively empty and therefore invalid return(false); } if (path.StartsWith(PathInternal.UncExtendedPathPrefix, StringComparison.Ordinal)) { // UNC specific checks if (path.Length == PathInternal.UncExtendedPathPrefix.Length || path[PathInternal.UncExtendedPathPrefix.Length] == DirectorySeparatorChar) { // Effectively empty and therefore invalid (\\?\UNC\ or \\?\UNC\\) return(false); } int serverShareSeparator = path.IndexOf(DirectorySeparatorChar, PathInternal.UncExtendedPathPrefix.Length); if (serverShareSeparator == -1 || serverShareSeparator == path.Length - 1) { // Need at least a Server\Share return(false); } } // Segments can't be empty "\\" or contain *just* "." or ".." char twoBack = '?'; char oneBack = DirectorySeparatorChar; char currentCharacter; bool periodSegment = false; for (int i = PathInternal.ExtendedPathPrefix.Length; i < path.Length; i++) { currentCharacter = path[i]; switch (currentCharacter) { case '\\': if (oneBack == DirectorySeparatorChar || periodSegment) { throw new ArgumentException(SR.Arg_PathIllegal); } periodSegment = false; break; case '.': periodSegment = (oneBack == DirectorySeparatorChar || (twoBack == DirectorySeparatorChar && oneBack == '.')); break; default: periodSegment = false; break; } twoBack = oneBack; oneBack = currentCharacter; } if (periodSegment) { return(false); } if (fullCheck) { // Look for illegal path characters. PathInternal.CheckInvalidPathChars(path); } return(true); }
private static bool IsDosUnc(StringBuffer buffer) { return(!PathInternal.IsDevice(buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\'); }
public static void CreateDirectory(string fullPath) { // NOTE: This logic is primarily just carried forward from Win32FileSystem.CreateDirectory. int length = fullPath.Length; // We need to trim the trailing slash or the code will try to create 2 directories of the same name. if (length >= 2 && PathInternal.EndsInDirectorySeparator(fullPath)) { length--; } // For paths that are only // or /// if (length == 2 && PathInternal.IsDirectorySeparator(fullPath[1])) { throw new IOException(SR.Format(SR.IO_CannotCreateDirectory, fullPath)); } // We can save a bunch of work if the directory we want to create already exists. if (DirectoryExists(fullPath)) { return; } // Attempt to figure out which directories don't exist, and only create the ones we need. bool somepathexists = false; Stack <string> stackDir = new Stack <string>(); int lengthRoot = PathInternal.GetRootLength(fullPath); if (length > lengthRoot) { int i = length - 1; while (i >= lengthRoot && !somepathexists) { string dir = fullPath.Substring(0, i + 1); if (!DirectoryExists(dir)) // Create only the ones missing { stackDir.Push(dir); } else { somepathexists = true; } while (i > lengthRoot && !PathInternal.IsDirectorySeparator(fullPath[i])) { i--; } i--; } } int count = stackDir.Count; if (count == 0 && !somepathexists) { string root = Directory.InternalGetDirectoryRoot(fullPath); if (!DirectoryExists(root)) { throw Interop.GetExceptionForIoErrno(Interop.Error.ENOENT.Info(), fullPath, isDirectory: true); } return; } // Create all the directories int result = 0; Interop.ErrorInfo firstError = default(Interop.ErrorInfo); string errorString = fullPath; while (stackDir.Count > 0) { string name = stackDir.Pop(); // The mkdir command uses 0777 by default (it'll be AND'd with the process umask internally). // We do the same. result = Interop.Sys.MkDir(name, (int)Interop.Sys.Permissions.Mask); if (result < 0 && firstError.Error == 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); // While we tried to avoid creating directories that don't // exist above, there are a few cases that can fail, e.g. // a race condition where another process or thread creates // the directory first, or there's a file at the location. if (errorInfo.Error != Interop.Error.EEXIST) { firstError = errorInfo; } else if (FileExists(name) || (!DirectoryExists(name, out errorInfo) && errorInfo.Error == Interop.Error.EACCES)) { // If there's a file in this directory's place, or if we have ERROR_ACCESS_DENIED when checking if the directory already exists throw. firstError = errorInfo; errorString = name; } } } // Only throw an exception if creating the exact directory we wanted failed to work correctly. if (result < 0 && firstError.Error != 0) { throw Interop.GetExceptionForIoErrno(firstError, errorString, isDirectory: true); } }
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.IsPartiallyQualified(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). 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 \\.\) inputBuffer = new StringBuffer(); inputBuffer.Append(outputBuffer); if (outputBuffer[2] == '.') { wasDotDevice = true; inputBuffer[2] = '?'; } } else { isDosUnc = IsDosUnc(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 = Interop.mincore.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 != 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(), 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; // 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); } inputBuffer.Dispose(); return(returnValue); }
public override void CreateDirectory(string fullPath) { // We can save a bunch of work if the directory we want to create already exists. This also // saves us in the case where sub paths are inaccessible (due to ERROR_ACCESS_DENIED) but the // final path is accessible and the directory already exists. For example, consider trying // to create c:\Foo\Bar\Baz, where everything already exists but ACLS prevent access to c:\Foo // and c:\Foo\Bar. In that case, this code will think it needs to create c:\Foo, and c:\Foo\Bar // and fail to due so, causing an exception to be thrown. This is not what we want. if (DirectoryExists(fullPath)) { return; } List <string> stackDir = new List <string>(); // Attempt to figure out which directories don't exist, and only // create the ones we need. Note that InternalExists may fail due // to Win32 ACL's preventing us from seeing a directory, and this // isn't threadsafe. bool somepathexists = false; int length = fullPath.Length; // We need to trim the trailing slash or the code will try to create 2 directories of the same name. if (length >= 2 && PathHelpers.EndsInDirectorySeparator(fullPath)) { length--; } int lengthRoot = PathInternal.GetRootLength(fullPath); if (length > lengthRoot) { // Special case root (fullpath = X:\\) int i = length - 1; while (i >= lengthRoot && !somepathexists) { string dir = fullPath.Substring(0, i + 1); if (!DirectoryExists(dir)) // Create only the ones missing { stackDir.Add(dir); } else { somepathexists = true; } while (i > lengthRoot && !PathInternal.IsDirectorySeparator(fullPath[i])) { i--; } i--; } } int count = stackDir.Count; // If we were passed a DirectorySecurity, convert it to a security // descriptor and set it in he call to CreateDirectory. Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default; bool r = true; int firstError = 0; string errorString = fullPath; // If all the security checks succeeded create all the directories while (stackDir.Count > 0) { string name = stackDir[stackDir.Count - 1]; stackDir.RemoveAt(stackDir.Count - 1); r = Interop.Kernel32.CreateDirectory(name, ref secAttrs); if (!r && (firstError == 0)) { int currentError = Marshal.GetLastWin32Error(); // While we tried to avoid creating directories that don't // exist above, there are at least two cases that will // cause us to see ERROR_ALREADY_EXISTS here. InternalExists // can fail because we didn't have permission to the // directory. Secondly, another thread or process could // create the directory between the time we check and the // time we try using the directory. Thirdly, it could // fail because the target does exist, but is a file. if (currentError != Interop.Errors.ERROR_ALREADY_EXISTS) { firstError = currentError; } else { // If there's a file in this directory's place, or if we have ERROR_ACCESS_DENIED when checking if the directory already exists throw. if (File.InternalExists(name) || (!DirectoryExists(name, out currentError) && currentError == Interop.Errors.ERROR_ACCESS_DENIED)) { firstError = currentError; errorString = name; } } } } // We need this check to mask OS differences // Handle CreateDirectory("X:\\") when X: doesn't exist. Similarly for n/w paths. if ((count == 0) && !somepathexists) { string root = Directory.InternalGetDirectoryRoot(fullPath); if (!DirectoryExists(root)) { throw Win32Marshal.GetExceptionForWin32Error(Interop.Errors.ERROR_PATH_NOT_FOUND, root); } return; } // Only throw an exception if creating the exact directory we // wanted failed to work correctly. if (!r && (firstError != 0)) { throw Win32Marshal.GetExceptionForWin32Error(firstError, errorString); } }
/// <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 = t_fullPathBuffer ?? (t_fullPathBuffer = new StringBuffer(PathInternal.MaxShortPath)); try { GetFullPathName(path, fullPath); // Trim whitespace off the end of the string. Win32 normalization trims only U+0020. fullPath.TrimEnd(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(fullPath); bool possibleBadUnc = specialPath && !isDevice; uint index = specialPath ? 2u : 0; uint lastSeparator = specialPath ? 1u : 0; uint segmentLength; char *start = fullPath.CharPointer; char current; while (index < fullPath.Length) { current = start[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 > (uint)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 > (uint)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(fullPath, originalPath: path)); } else { if (fullPath.Length == (uint)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(); } }
public static char[] GetInvalidPathChars() { return(PathInternal.GetInvalidPathChars()); }
// 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.Argument_PathFormatNotSupported); } } // Technically this doesn't matter but we used to throw for this case if (string.IsNullOrWhiteSpace(path)) { throw new ArgumentException(SR.Arg_PathIllegal); } // 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> /// Try to remove relative segments from the given path (without combining with a root). /// </summary> /// <param name="skip">Skip the specified number of characters before evaluating.</param> private static string RemoveRelativeSegments(string path, int skip = 0) { bool flippedSeparator = false; // Remove "//", "/./", and "/../" from the path by copying each character to the output, // except the ones we're removing, such that the builder contains the normalized path // at the end. var sb = StringBuilderCache.Acquire(path.Length); if (skip > 0) { sb.Append(path, 0, skip); } int componentCharCount = 0; for (int i = skip; i < path.Length; i++) { char c = path[i]; if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length) { componentCharCount = 0; // Skip this character if it's a directory separator and if the next character is, too, // e.g. "parent//child" => "parent/child" if (PathInternal.IsDirectorySeparator(path[i + 1])) { continue; } // Skip this character and the next if it's referring to the current directory, // e.g. "parent/./child" =? "parent/child" if ((i + 2 == path.Length || PathInternal.IsDirectorySeparator(path[i + 2])) && path[i + 1] == '.') { i++; continue; } // Skip this character and the next two if it's referring to the parent directory, // e.g. "parent/child/../grandchild" => "parent/grandchild" if (i + 2 < path.Length && (i + 3 == path.Length || PathInternal.IsDirectorySeparator(path[i + 3])) && path[i + 1] == '.' && path[i + 2] == '.') { // Unwind back to the last slash (and if there isn't one, clear out everything). int s; for (s = sb.Length - 1; s >= 0; s--) { if (PathInternal.IsDirectorySeparator(sb[s])) { sb.Length = s; break; } } if (s < 0) { sb.Length = 0; } i += 2; continue; } } if (++componentCharCount > PathInternal.MaxComponentLength) { throw new PathTooLongException(SR.IO_PathTooLong); } // Normalize the directory separator if needed if (c != Path.DirectorySeparatorChar && c == Path.AltDirectorySeparatorChar) { c = Path.DirectorySeparatorChar; flippedSeparator = true; } sb.Append(c); } if (flippedSeparator || sb.Length != path.Length) { return(StringBuilderCache.GetStringAndRelease(sb)); } else { // We haven't changed the source path, return the original StringBuilderCache.Release(sb); return(path); } }
private unsafe static void InternalCreateDirectory(string fullPath, string path, object dirSecurityObj) { DirectorySecurity directorySecurity = (DirectorySecurity)dirSecurityObj; int num = fullPath.Length; if (num >= 2 && Path.IsDirectorySeparator(fullPath[num - 1])) { num--; } int rootLength = LongPath.GetRootLength(fullPath); if (num == 2 && Path.IsDirectorySeparator(fullPath[1])) { throw new IOException(Environment.GetResourceString("IO.IO_CannotCreateDirectory", new object[] { path })); } List <string> list = new List <string>(); bool flag = false; if (num > rootLength) { int num2 = num - 1; while (num2 >= rootLength && !flag) { string text = fullPath.Substring(0, num2 + 1); if (!LongPathDirectory.InternalExists(text)) { list.Add(text); } else { flag = true; } while (num2 > rootLength && fullPath[num2] != Path.DirectorySeparatorChar && fullPath[num2] != Path.AltDirectorySeparatorChar) { num2--; } num2--; } } int count = list.Count; if (list.Count != 0 && !CodeAccessSecurityEngine.QuickCheckForAllDemands()) { string[] array = new string[list.Count]; list.CopyTo(array, 0); for (int i = 0; i < array.Length; i++) { string[] array2 = array; int num3 = i; array2[num3] += "\\."; } AccessControlActions control = (directorySecurity == null) ? AccessControlActions.None : AccessControlActions.Change; FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, control, array, false, false); } Win32Native.SECURITY_ATTRIBUTES security_ATTRIBUTES = null; if (directorySecurity != null) { security_ATTRIBUTES = new Win32Native.SECURITY_ATTRIBUTES(); security_ATTRIBUTES.nLength = Marshal.SizeOf <Win32Native.SECURITY_ATTRIBUTES>(security_ATTRIBUTES); byte[] securityDescriptorBinaryForm = directorySecurity.GetSecurityDescriptorBinaryForm(); byte * ptr = stackalloc byte[checked (unchecked ((UIntPtr)securityDescriptorBinaryForm.Length) * 1)]; Buffer.Memcpy(ptr, 0, securityDescriptorBinaryForm, 0, securityDescriptorBinaryForm.Length); security_ATTRIBUTES.pSecurityDescriptor = ptr; } bool flag2 = true; int num4 = 0; string maybeFullPath = path; while (list.Count > 0) { string text2 = list[list.Count - 1]; list.RemoveAt(list.Count - 1); if (text2.Length >= 32767) { throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); } flag2 = Win32Native.CreateDirectory(PathInternal.EnsureExtendedPrefix(text2), security_ATTRIBUTES); if (!flag2 && num4 == 0) { int lastWin32Error = Marshal.GetLastWin32Error(); if (lastWin32Error != 183) { num4 = lastWin32Error; } else if (LongPathFile.InternalExists(text2) || (!LongPathDirectory.InternalExists(text2, out lastWin32Error) && lastWin32Error == 5)) { num4 = lastWin32Error; try { FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, LongPathDirectory.GetDemandDir(text2, true), false, false); maybeFullPath = text2; } catch (SecurityException) { } } } } if (count == 0 && !flag) { string path2 = LongPathDirectory.InternalGetDirectoryRoot(fullPath); if (!LongPathDirectory.InternalExists(path2)) { __Error.WinIOError(3, LongPathDirectory.InternalGetDirectoryRoot(path)); } return; } if (!flag2 && num4 != 0) { __Error.WinIOError(num4, maybeFullPath); } }
InlineData(PathInternal.ExtendedPathPrefix, PathInternal.ExtendedPathPrefix)
/// <summary> /// Normalize the path and check for bad characters or other invalid syntax. /// </summary> /// <remarks> /// The legacy NormalizePath /// </remarks> private static string NormalizeAndValidatePath(string path) { Debug.Assert(path != null, "path can't be null"); // 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); bool isExtended = path.Length >= PathInternal.ExtendedPathPrefix.Length + startIndex && path.IndexOf(PathInternal.ExtendedPathPrefix, startIndex, PathInternal.ExtendedPathPrefix.Length, StringComparison.Ordinal) >= 0; if (isExtended) { startIndex += PathInternal.ExtendedPathPrefix.Length; } // Move past the colon startIndex += 2; 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) { // If the path is in extended syntax, we don't need to normalize, but we still do some basic validity checks if (!ValidateExtendedPath(path)) { 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); } // Look for illegal path characters. PathInternal.CheckInvalidPathChars(path); return(path); } 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, checkInvalidCharacters: true, expandShortPaths: true)); } }