// 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 = RemoveRelativeSegments(path);
+ string collapsedString = PathInternal.RemoveRelativeSegments(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.");
// Windows doesn't root them properly. As such we need to manually remove segments.
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.
- ? RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath) - 1)
+ ? PathInternal.RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath) - 1)
: 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>
- 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.
- StringBuilder sb = StringBuilderCache.Acquire(path.Length);
- if (skip > 0)
- {
- sb.Append(path, 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 >= 0; 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 < 0)
- {
- sb.Length = 0;
- }
-
- 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 (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;
- }
- }
-
- /// <summary>
/// Create a relative path from one path to another. Paths will be resolved before calculating the difference.
/// Default path comparison for the active platform will be used (OrdinalIgnoreCase for Windows or Mac, Ordinal for Unix).
/// </summary>
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Diagnostics;
+using System.Text;
+
namespace System.IO
{
/// <summary>Contains internal path helpers that are shared between many projects.</summary>
length: firstRootLength,
comparisonType: comparisonType) == 0;
}
+
+ /// <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)
+ {
+ Debug.Assert(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.
+ StringBuilder sb = StringBuilderCache.Acquire(path.Length);
+ if (skip > 0)
+ {
+ sb.Append(path, 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 (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;
+ }
+ }
}
}