// Expands the given path to a fully qualified path. public static string GetFullPath(string path) { if (path == null) { throw new ArgumentNullException(nameof(path)); } if (path.Length == 0) { throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); } if (path.Contains('\0')) { throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); } // Expand with current directory if necessary if (!IsPathRooted(path)) { path = Combine(Interop.Sys.GetCwd(), 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. string collapsedString = PathInternal.RemoveRelativeSegments(path, PathInternal.GetRootLength(path)); Debug.Assert(collapsedString.Length < path.Length || collapsedString.ToString() == path, "Either we've removed characters, or the string should be unmodified from the input path."); string result = collapsedString.Length == 0 ? PathInternal.DirectorySeparatorCharAsString : collapsedString; return(result); }
internal static unsafe string RemoveRelativeSegments(string path, int rootLength) { // ISSUE: untyped stack allocation ValueStringBuilder sb = new ValueStringBuilder(new Span <char>((void *)__untypedstackalloc(new IntPtr(520)), 260)); if (PathInternal.RemoveRelativeSegments(path.AsSpan(), rootLength, ref sb)) { path = sb.ToString(); } sb.Dispose(); return(path); }
// Gets the full path without argument validation private static string GetFullPathInternal(string path) { Debug.Assert(!string.IsNullOrEmpty(path)); Debug.Assert(!path.Contains('\0')); // Expand with current directory if necessary if (!IsPathRooted(path)) { path = Combine(Interop.Sys.GetCwd(), path); } // We would ideally use realpath to do this, but it resolves symlinks and requires that the file actually exist. string collapsedString = PathInternal.RemoveRelativeSegments(path, PathInternal.GetRootLength(path)); Debug.Assert(collapsedString.Length < path.Length || collapsedString.ToString() == path, "Either we've removed characters, or the string should be unmodified from the input path."); string result = collapsedString.Length == 0 ? PathInternal.DirectorySeparatorCharAsString : collapsedString; return(result); }
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)); }