public DirectoryInfo CreateSubdirectory(string path) { if (path == null) { throw new ArgumentNullException(nameof(path)); } if (PathInternal.IsEffectivelyEmpty(path)) { throw new ArgumentException(SR.Argument_PathEmpty, nameof(path)); } if (Path.IsPathRooted(path)) { throw new ArgumentException(SR.Arg_Path2IsRooted, nameof(path)); } string fullPath = Path.GetFullPath(Path.Combine(FullPath, path)); if (fullPath.Length < FullPath.Length || (fullPath.Length > FullPath.Length && !PathInternal.IsDirectorySeparator(fullPath[FullPath.Length])) || string.Compare(FullPath, 0, fullPath, 0, FullPath.Length, PathInternal.StringComparison) != 0) { throw new ArgumentException(SR.Format(SR.Argument_InvalidSubPath, path, FullPath), nameof(path)); } FileSystem.CreateDirectory(fullPath); return(new DirectoryInfo(fullPath)); }
// Returns the directory path of a file path. This method effectively // removes the last element of the given file path, i.e. it returns a // string consisting of all characters up to but not including the last // backslash ("\") in the file path. The returned value is null if the file // path is null or if the file path denotes a root (such as "\", "C:", or // "\\server\share"). public static string GetDirectoryName(string path) { if (path == null) { return(null); } if (PathInternal.IsEffectivelyEmpty(path)) { throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); } PathInternal.CheckInvalidPathChars(path); path = PathInternal.NormalizeDirectorySeparators(path); int root = PathInternal.GetRootLength(path); int i = path.Length; if (i > root) { while (i > root && !PathInternal.IsDirectorySeparator(path[--i])) { ; } return(path.Substring(0, i)); } return(null); }
public DirectoryInfo CreateSubdirectory(string path) { ArgumentNullException.ThrowIfNull(path); if (PathInternal.IsEffectivelyEmpty(path.AsSpan())) { throw new ArgumentException(SR.Argument_PathEmpty, nameof(path)); } if (Path.IsPathRooted(path)) { throw new ArgumentException(SR.Arg_Path2IsRooted, nameof(path)); } string newPath = Path.GetFullPath(Path.Combine(FullPath, path)); ReadOnlySpan <char> trimmedNewPath = Path.TrimEndingDirectorySeparator(newPath.AsSpan()); ReadOnlySpan <char> trimmedCurrentPath = Path.TrimEndingDirectorySeparator(FullPath.AsSpan()); // We want to make sure the requested directory is actually under the subdirectory. if (trimmedNewPath.StartsWith(trimmedCurrentPath, PathInternal.StringComparison) // Allow the exact same path, but prevent allowing "..\FooBar" through when the directory is "Foo" && ((trimmedNewPath.Length == trimmedCurrentPath.Length) || PathInternal.IsDirectorySeparator(newPath[trimmedCurrentPath.Length]))) { FileSystem.CreateDirectory(newPath); return(new DirectoryInfo(newPath)); } // We weren't nested throw new ArgumentException(SR.Format(SR.Argument_InvalidSubPath, path, FullPath), nameof(path)); }
// Expands the given path to a fully qualified path. public static string GetFullPath(string path) { if (path == null) { throw new ArgumentNullException(nameof(path)); } // If the path would normalize to string empty, we'll consider it empty if (PathInternal.IsEffectivelyEmpty(path)) { throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); } // Embedded null characters are the only invalid character case we trully care about. // This is because the nulls will signal the end of the string to Win32 and therefore have // unpredictable results. if (path.IndexOf('\0') != -1) { throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); } if (PathInternal.IsExtended(path)) { // \\?\ paths are considered normalized by definition. Windows doesn't normalize \\?\ // paths and neither should we. Even if we wanted to GetFullPathName does not work // properly with device paths. If one wants to pass a \\?\ path through normalization // one can chop off the prefix, pass it to GetFullPath and add it again. return(path); } return(PathHelper.Normalize(path)); }
/// <summary> /// Returns the path root or null if path is empty or null. /// </summary> public static string?GetPathRoot(string?path) { if (PathInternal.IsEffectivelyEmpty(path)) { return(null); } return(IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString : string.Empty); }
public static ReadOnlySpan <char> GetDirectoryName(ReadOnlySpan <char> path) { if (PathInternal.IsEffectivelyEmpty(path)) { return(ReadOnlySpan <char> .Empty); } int directoryNameOffset = Path.GetDirectoryNameOffset(path); return(directoryNameOffset < 0 ? ReadOnlySpan <char> .Empty : path.Slice(0, directoryNameOffset)); }
public static string GetDirectoryName(string path) { if (path == null || PathInternal.IsEffectivelyEmpty(path.AsSpan())) { return((string)null); } int directoryNameOffset = Path.GetDirectoryNameOffset(path.AsSpan()); return(directoryNameOffset < 0 ? (string)null : PathInternal.NormalizeDirectorySeparators(path.Substring(0, directoryNameOffset))); }
/// <summary> /// Returns the directory portion of a file path. This method effectively /// removes the last segment of the given file path, i.e. it returns a /// string consisting of all characters up to but not including the last /// backslash ("\") in the file path. The returned value is null if the /// specified path is null, empty, or a root (such as "\", "C:", or /// "\\server\share"). /// </summary> /// <remarks> /// Directory separators are normalized in the returned string. /// </remarks> public static string?GetDirectoryName(string?path) { if (path == null || PathInternal.IsEffectivelyEmpty(path.AsSpan())) { return(null); } int end = GetDirectoryNameOffset(path.AsSpan()); return(end >= 0 ? PathInternal.NormalizeDirectorySeparators(path.Substring(0, end)) : null); }
/// <summary> /// Returns the directory portion of a file path. The returned value is empty /// if the specified path is null, empty, or a root (such as "\", "C:", or /// "\\server\share"). /// </summary> /// <remarks> /// Unlike the string overload, this method will not normalize directory separators. /// </remarks> public static ReadOnlySpan <char> GetDirectoryName(ReadOnlySpan <char> path) { if (PathInternal.IsEffectivelyEmpty(path)) { return(ReadOnlySpan <char> .Empty); } int end = GetDirectoryNameOffset(path); return(end >= 0 ? path.Slice(0, end) : ReadOnlySpan <char> .Empty); }
/// <remarks> /// Unlike the string overload, this method will not normalize directory separators. /// </remarks> public static ReadOnlySpan <char> GetPathRoot(ReadOnlySpan <char> path) { if (PathInternal.IsEffectivelyEmpty(path)) { return(ReadOnlySpan <char> .Empty); } int pathRoot = PathInternal.GetRootLength(path); return(pathRoot <= 0 ? ReadOnlySpan <char> .Empty : path.Slice(0, pathRoot)); }
// The resulting string is null if path is null. If the path is empty or // only contains whitespace characters an ArgumentException gets thrown. public static string GetPathRoot(string path) { if (path == null) { return(null); } if (PathInternal.IsEffectivelyEmpty(path)) { throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); } return(IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString : String.Empty); }
// Returns the root portion of the given path. The resulting string // consists of those rightmost characters of the path that constitute the // root of the path. Possible patterns for the resulting string are: An // empty string (a relative path on the current drive), "\" (an absolute // path on the current drive), "X:" (a relative path on a given drive, // where X is the drive letter), "X:\" (an absolute path on a given drive), // and "\\server\share" (a UNC path for a given server and share name). // The resulting string is null if path is null. If the path is empty or // only contains whitespace characters an ArgumentException gets thrown. public static string GetPathRoot(string path) { if (PathInternal.IsEffectivelyEmpty(path)) { return(null); } ReadOnlySpan <char> result = GetPathRoot(path.AsSpan()); if (path.Length == result.Length) { return(PathInternal.NormalizeDirectorySeparators(path)); } return(PathInternal.NormalizeDirectorySeparators(new string(result))); }
// Returns the directory path of a file path. This method effectively // removes the last element of the given file path, i.e. it returns a // string consisting of all characters up to but not including the last // backslash ("\") in the file path. The returned value is null if the file // path is null or if the file path denotes a root (such as "\", "C:", or // "\\server\share"). public static string GetDirectoryName(string path) { if (path == null) { return(null); } if (PathInternal.IsEffectivelyEmpty(path)) { throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); } path = PathInternal.NormalizeDirectorySeparators(path); int end = PathInternal.GetDirectoryNameOffset(path); return(end >= 0 ? path.Substring(0, end) : null); }
// Returns the root portion of the given path. The resulting string // consists of those rightmost characters of the path that constitute the // root of the path. Possible patterns for the resulting string are: An // empty string (a relative path on the current drive), "\" (an absolute // path on the current drive), "X:" (a relative path on a given drive, // where X is the drive letter), "X:\" (an absolute path on a given drive), // and "\\server\share" (a UNC path for a given server and share name). // The resulting string is null if path is null. If the path is empty or // only contains whitespace characters an ArgumentException gets thrown. public static string GetPathRoot(string path) { if (path == null) { return(null); } if (PathInternal.IsEffectivelyEmpty(path)) { throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); } // Need to return the normalized directory separator path = PathInternal.NormalizeDirectorySeparators(path); int pathRoot = PathInternal.GetRootLength(path); return(pathRoot <= 0 ? string.Empty : path.Substring(0, pathRoot)); }
// Expands the given path to a fully qualified path. public static string GetFullPath(string path) { ArgumentNullException.ThrowIfNull(path); // If the path would normalize to string empty, we'll consider it empty if (PathInternal.IsEffectivelyEmpty(path.AsSpan())) { throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); } // Embedded null characters are the only invalid character case we trully care about. // This is because the nulls will signal the end of the string to Win32 and therefore have // unpredictable results. if (path.Contains('\0')) { throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); } return(GetFullPathInternal(path)); }
// Returns the root portion of the given path. The resulting string // consists of those rightmost characters of the path that constitute the // root of the path. Possible patterns for the resulting string are: An // empty string (a relative path on the current drive), "\" (an absolute // path on the current drive), "X:" (a relative path on a given drive, // where X is the drive letter), "X:\" (an absolute path on a given drive), // and "\\server\share" (a UNC path for a given server and share name). // The resulting string is null if path is null. If the path is empty or // only contains whitespace characters an ArgumentException gets thrown. public static string GetPathRoot(string path) { if (path == null) { return(null); } if (PathInternal.IsEffectivelyEmpty(path)) { throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); } ReadOnlySpan <char> result = GetPathRoot(path.AsReadOnlySpan()); if (path.Length == result.Length) { return(PathInternal.NormalizeDirectorySeparators(path)); } return(PathInternal.NormalizeDirectorySeparators(new string(result))); }
// 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); }
public static ReadOnlySpan <char> GetPathRoot(ReadOnlySpan <char> path) { return(PathInternal.IsEffectivelyEmpty(path) && IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString.AsSpan() : ReadOnlySpan <char> .Empty); }
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 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)) { return(basePath); } 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.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).EqualsOrdinal(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(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(0, 4), path, @"\") : JoinInternal(basePath.AsSpan(0, 4), path.AsSpan(0, 2), @"\", path.AsSpan(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 (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) ? PathInternal.RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath)) : GetFullPath(combinedPath)); }