Skip to content

Commit a56f65e

Browse files
authored
Fix duplicate Uri ToString for DOS-like custom schemes (#121930)
1 parent f8d762e commit a56f65e

File tree

2 files changed

+46
-14
lines changed

2 files changed

+46
-14
lines changed

src/libraries/System.Private.Uri/src/System/Uri.cs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2277,12 +2277,21 @@ private void CreateUriInfo(Flags cF)
22772277

22782278
if ((cF & Flags.AuthorityFound) != 0)
22792279
{
2280-
Debug.Assert(str[idx] is '/' or '\\' && str[idx + 1] is '/' or '\\');
2281-
2282-
if (str[idx] == '\\' || str[idx + 1] == '\\')
2283-
notCanonicalScheme = true;
2280+
if (str[idx] is '/' or '\\' && str[idx + 1] is '/' or '\\')
2281+
{
2282+
if (str[idx] == '\\' || str[idx + 1] == '\\')
2283+
{
2284+
notCanonicalScheme = true;
2285+
}
22842286

2285-
idx += 2;
2287+
idx += 2;
2288+
}
2289+
else
2290+
{
2291+
Debug.Assert(IsDosPath);
2292+
Debug.Assert(char.IsAsciiLetter(str.AsSpan(idx).TrimStart(['/', '\\'])[0]));
2293+
Debug.Assert(str.AsSpan(idx).TrimStart(['/', '\\'])[1] is ':' or '|');
2294+
}
22862295

22872296
if ((cF & (Flags.UncPath | Flags.DosPath)) != 0)
22882297
{
@@ -3249,21 +3258,23 @@ private void ParseRemaining()
32493258
DebugAssertInCtor();
32503259

32513260
// Dos/Unix paths have no host. Other schemes cleared/set _string with host information in PrivateParseMinimal.
3252-
if (IsFile && !IsUncPath)
3261+
if (InFact(Flags.DosPath | Flags.UnixPath))
32533262
{
3254-
if (IsImplicitFile)
3255-
{
3256-
_string = string.Empty;
3257-
}
3258-
else
3259-
{
3260-
_string = _syntax.SchemeName + SchemeDelimiter;
3261-
}
3263+
Debug.Assert(!InFact(Flags.HasUserInfo));
3264+
3265+
_string =
3266+
IsImplicitFile ? string.Empty :
3267+
InFact(Flags.AuthorityFound) ? _syntax.SchemeName + SchemeDelimiter :
3268+
_syntax.SchemeName + ':';
32623269

32633270
_info.Offset.Scheme = 0;
32643271
_info.Offset.User = _string.Length;
32653272
_info.Offset.Host = _string.Length;
32663273
}
3274+
else
3275+
{
3276+
Debug.Assert(!ReferenceEquals(_string, OriginalString));
3277+
}
32673278

32683279
_info.Offset.Path = _string.Length;
32693280
idx = _info.Offset.Path;

src/libraries/System.Private.Uri/tests/FunctionalTests/UriTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,12 +821,33 @@ public static IEnumerable<object[]> FilePathHandlesNonAscii_TestData()
821821
yield return new object[] { "file:///\u00FCri/", "file:///\u00FCri/", "/%C3%BCri/", "file:///%C3%BCri/", "/\u00FCri/" };
822822
yield return new object[] { "file:///a/b\uD83D\uDE1F/Foo.cs", "file:///a/b\uD83D\uDE1F/Foo.cs", "/a/b%F0%9F%98%9F/Foo.cs", "file:///a/b%F0%9F%98%9F/Foo.cs", "/a/b\uD83D\uDE1F/Foo.cs" };
823823

824+
// DOS with ':' or ':/' instead of '://'
825+
yield return new object[] { "file:C:/uri/", "file:///C:/uri/", "C:/uri/", "file:///C:/uri/", "C:\\uri\\" };
826+
yield return new object[] { "file:C:/\u00FCri/", "file:///C:/\u00FCri/", "C:/%C3%BCri/", "file:///C:/%C3%BCri/", "C:\\\u00FCri\\" };
827+
yield return new object[] { "file:/C:/uri/", "file:///C:/uri/", "C:/uri/", "file:///C:/uri/", "C:\\uri\\" };
828+
yield return new object[] { "file:/C:/\u00FCri/", "file:///C:/\u00FCri/", "C:/%C3%BCri/", "file:///C:/%C3%BCri/", "C:\\\u00FCri\\" };
829+
824830
// DOS
831+
yield return new object[] { "file://C:/uri/", "file:///C:/uri/", "C:/uri/", "file:///C:/uri/", "C:\\uri\\" };
825832
yield return new object[] { "file://C:/\u00FCri/", "file:///C:/\u00FCri/", "C:/%C3%BCri/", "file:///C:/%C3%BCri/", "C:\\\u00FCri\\" };
833+
yield return new object[] { "file:///C:/uri/", "file:///C:/uri/", "C:/uri/", "file:///C:/uri/", "C:\\uri\\" };
826834
yield return new object[] { "file:///C:/\u00FCri/", "file:///C:/\u00FCri/", "C:/%C3%BCri/", "file:///C:/%C3%BCri/", "C:\\\u00FCri\\" };
827835
yield return new object[] { "C:/\u00FCri/", "file:///C:/\u00FCri/", "C:/%C3%BCri/", "file:///C:/%C3%BCri/", "C:\\\u00FCri\\" };
828836
yield return new object[] { "\tC:/\u00FCri/", "file:///C:/\u00FCri/", "C:/%C3%BCri/", "file:///C:/%C3%BCri/", "C:\\\u00FCri\\" };
829837

838+
// DOS in custom scheme (differs in whether ':' vs '://' is used since it doesn't have MustHaveAuthority)
839+
yield return new object[] { "custom:C:/uri/", "custom:C:/uri/", "C:/uri/", "custom:C:/uri/", "C:\\uri\\" };
840+
yield return new object[] { "custom:C:/\u00FCri/", "custom:C:/\u00FCri/", "C:/%C3%BCri/", "custom:C:/%C3%BCri/", "C:\\\u00FCri\\" };
841+
yield return new object[] { "custom:/C:/uri/", "custom:/C:/uri/", "C:/uri/", "custom:/C:/uri/", "C:\\uri\\" };
842+
yield return new object[] { "custom:/C:/\u00FCri/", "custom:/C:/\u00FCri/", "C:/%C3%BCri/", "custom:/C:/%C3%BCri/", "C:\\\u00FCri\\" };
843+
yield return new object[] { "custom://C:/uri/", "custom:///C:/uri/", "C:/uri/", "custom:///C:/uri/", "C:\\uri\\" };
844+
yield return new object[] { "custom://C:/\u00FCri/", "custom:///C:/\u00FCri/", "C:/%C3%BCri/", "custom:///C:/%C3%BCri/", "C:\\\u00FCri\\" };
845+
yield return new object[] { "custom:///C:/uri/", "custom:///C:/uri/", "C:/uri/", "custom:///C:/uri/", "C:\\uri\\" };
846+
yield return new object[] { "custom:///C:/\u00FCri/", "custom:///C:/\u00FCri/", "C:/%C3%BCri/", "custom:///C:/%C3%BCri/", "C:\\\u00FCri\\" };
847+
848+
yield return new object[] { "vsmacros://C:/\u00FCri/", "vsmacros://C:/\u00FCri/", "C:/%C3%BCri/", "vsmacros://C:/%C3%BCri/", "C:\\\u00FCri\\" };
849+
yield return new object[] { "vsmacros://test/\u00FCri/", "vsmacros://test/\u00FCri/", "/%C3%BCri/", "vsmacros://test/%C3%BCri/", "\\\\test\\\u00FCri\\" };
850+
830851
// UNC
831852
yield return new object[] { "\\\\\u00FCri/", "file://\u00FCri/", "/", "file://\u00FCri/", "\\\\\u00FCri\\" };
832853
yield return new object[] { "file://\u00FCri/", "file://\u00FCri/", "/", "file://\u00FCri/", "\\\\\u00FCri\\" };

0 commit comments

Comments
 (0)