/// <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); }
/// <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:" }
public void IsDeviceTest(string path, bool expected) { StringBuffer sb = new StringBuffer(); sb.Append(path); Assert.Equal(expected, PathInternal.IsDevice(sb)); Assert.Equal(expected, PathInternal.IsDevice(path)); }
internal static int GetUncRootLength(ReadOnlySpan <char> path) { bool isDevice = PathInternal.IsDevice(path); if (!isDevice && path.Slice(0, 2).EqualsOrdinal(@"\\".AsSpan())) { return(2); } else if (isDevice && path.Length >= 8 && (path.Slice(0, 8).EqualsOrdinal(PathInternal.UncExtendedPathPrefix.AsSpan()) || path.Slice(5, 4).EqualsOrdinal(@"UNC\".AsSpan()))) { return(8); } return(-1); }
/// <summary> /// Perform the additional path checks that would normally happen when creating a FileIOPermission object. /// </summary> /// <param name="fullPath">A path that has already gone through GetFullPath or Normalize</param> internal static void EmulateFileIOPermissionChecks(string fullPath) { // Callers should have already made checks for invalid path format via normalization. This method will only make the // additional checks needed to throw the same exceptions that would normally throw when using FileIOPermission. // These checks are done via CheckIllegalCharacters() and StringExpressionSet in AddPathList() above. #if !PLATFORM_UNIX // Checking for colon / invalid characters on device paths blocks legitimate access to objects such as named pipes. if (!PathInternal.IsDevice(fullPath)) { // GetFullPath already checks normal invalid path characters. We need to just check additional (wildcard) characters here. // (By calling the standard helper we can allow extended paths \\?\ through when the support is enabled.) if (PathInternal.HasWildCardCharacters(fullPath)) { throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); } if (PathInternal.HasInvalidVolumeSeparator(fullPath)) { throw new NotSupportedException(Environment.GetResourceString("Argument_PathFormatNotSupported")); } } #endif // !PLATFORM_UNIX }
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)); } 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())) : GetFullPath(combinedPath)); }
public void IsDeviceTest(string path, bool expected) { Assert.Equal(expected, PathInternal.IsDevice(path)); }