// 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);
+ 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.");
combinedPath = JoinInternal(basePath, path);
}
- // Device paths are normalized by definition, so passing something of this format
- // to GetFullPath() won't do anything by design. Additionally, GetFullPathName() in
- // Windows doesn't root them properly. As such we need to manually remove segments.
+ // 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)
- // Paths at this point are in the form of \\?\C:\.\tmp we skip to the last character of the root when calling RemoveRelativeSegments to remove relative paths in such cases.
- ? PathInternal.RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath) - 1)
+ ? PathInternal.RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath))
: GetFullPath(combinedPath);
}
/// <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>
- internal static string RemoveRelativeSegments(string path, int skip = 0)
+ /// <param name="rootLength">The length of the root of the given path</param>
+ internal static string RemoveRelativeSegments(string path, int rootLength)
{
- Debug.Assert(skip >= 0);
+ 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 (path[skip - 1] == '\\')
+ skip--;
+
Span<char> initialBuffer = stackalloc char[260 /* PathInternal.MaxShortPath */];
ValueStringBuilder sb = new ValueStringBuilder(initialBuffer);
return path;
}
- return sb.ToString();
+ return sb.Length < rootLength ? path.Substring(0, rootLength) : sb.ToString();
}
}
}