Missing Case In Remove Relative Segments (#16869)
authorAnirudh Agnihotry <anirudhagnihotry098@gmail.com>
Mon, 12 Mar 2018 23:47:47 +0000 (16:47 -0700)
committerGitHub <noreply@github.com>
Mon, 12 Mar 2018 23:47:47 +0000 (16:47 -0700)
src/mscorlib/shared/System/IO/Path.Unix.cs
src/mscorlib/shared/System/IO/Path.Windows.cs
src/mscorlib/shared/System/IO/PathInternal.cs

index 10d295f..f364b84 100644 (file)
@@ -34,7 +34,7 @@ namespace System.IO
 
             // 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.");
index 945bba3..1934e8d 100644 (file)
@@ -117,12 +117,12 @@ namespace System.IO
                 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);
         }
 
index 059f0cf..c5cce00 100644 (file)
@@ -113,12 +113,19 @@ namespace System.IO
         /// <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);
 
@@ -195,7 +202,7 @@ namespace System.IO
                 return path;
             }
 
-            return sb.ToString();
+            return sb.Length < rootLength ? path.Substring(0, rootLength) : sb.ToString();
         }
     }
 }