Skip to content

Commit e0683a9

Browse files
authored
Fix non-ascii absolute file path handling in Uri (#36429)
* Fix non-ascii absolute file path Uri handling * Fix Unix tests * Revert unrelated style change * Add asserts to FilePathHandlesNonAscii to test AbsoluteUri roundtrips properties
1 parent 4eaccd6 commit e0683a9

File tree

2 files changed

+71
-41
lines changed

2 files changed

+71
-41
lines changed

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

Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3219,8 +3219,10 @@ private unsafe void ParseRemaining()
32193219
// so both '?' and '#' will work as delimiters
32203220
if (buildIriStringFromPath)
32213221
{
3222-
// Dos paths have no host. Other schemes cleared/set _string with host information in PrivateParseMinimal.
3223-
if (IsDosPath)
3222+
DebugAssertInCtor();
3223+
3224+
// Dos/Unix paths have no host. Other schemes cleared/set _string with host information in PrivateParseMinimal.
3225+
if (IsFile && !IsUncPath)
32243226
{
32253227
if (IsImplicitFile)
32263228
{
@@ -3263,19 +3265,7 @@ private unsafe void ParseRemaining()
32633265
origIdx = index == -1 ? _originalUnicodeString.Length : (index + origIdx);
32643266
}
32653267

3266-
// Correctly escape unescape
3267-
string escapedPath = EscapeUnescapeIri(_originalUnicodeString, offset, origIdx, UriComponents.Path);
3268-
3269-
// Normalize path
3270-
try
3271-
{
3272-
_string += escapedPath;
3273-
}
3274-
catch (ArgumentException)
3275-
{
3276-
UriFormatException e = GetException(ParsingError.BadFormat)!;
3277-
throw e;
3278-
}
3268+
_string += EscapeUnescapeIri(_originalUnicodeString, offset, origIdx, UriComponents.Path);
32793269

32803270
if (_string.Length > ushort.MaxValue)
32813271
{
@@ -3394,6 +3384,8 @@ private unsafe void ParseRemaining()
33943384
//
33953385
if (buildIriStringFromPath)
33963386
{
3387+
DebugAssertInCtor();
3388+
33973389
int offset = origIdx;
33983390

33993391
if (origIdx < _originalUnicodeString.Length && _originalUnicodeString[origIdx] == '?')
@@ -3409,19 +3401,7 @@ private unsafe void ParseRemaining()
34093401
origIdx = _originalUnicodeString.Length;
34103402
}
34113403

3412-
// Correctly escape unescape
3413-
string escapedPath = EscapeUnescapeIri(_originalUnicodeString, offset, origIdx, UriComponents.Query);
3414-
3415-
// Normalize path
3416-
try
3417-
{
3418-
_string += escapedPath;
3419-
}
3420-
catch (ArgumentException)
3421-
{
3422-
UriFormatException e = GetException(ParsingError.BadFormat)!;
3423-
throw e;
3424-
}
3404+
_string += EscapeUnescapeIri(_originalUnicodeString, offset, origIdx, UriComponents.Query);
34253405

34263406
if (_string.Length > ushort.MaxValue)
34273407
{
@@ -3470,25 +3450,15 @@ private unsafe void ParseRemaining()
34703450
//
34713451
if (buildIriStringFromPath)
34723452
{
3453+
DebugAssertInCtor();
3454+
34733455
int offset = origIdx;
34743456

34753457
if (origIdx < _originalUnicodeString.Length && _originalUnicodeString[origIdx] == '#')
34763458
{
34773459
origIdx = _originalUnicodeString.Length;
34783460

3479-
// Correctly escape unescape
3480-
string escapedPath = EscapeUnescapeIri(_originalUnicodeString, offset, origIdx, UriComponents.Fragment);
3481-
3482-
// Normalize path
3483-
try
3484-
{
3485-
_string += escapedPath;
3486-
}
3487-
catch (ArgumentException)
3488-
{
3489-
UriFormatException e = GetException(ParsingError.BadFormat)!;
3490-
throw e;
3491-
}
3461+
_string += EscapeUnescapeIri(_originalUnicodeString, offset, origIdx, UriComponents.Fragment);
34923462

34933463
if (_string.Length > ushort.MaxValue)
34943464
{

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,5 +759,65 @@ public static void Uri_DoesNotLockOnString()
759759
Assert.True(Monitor.TryEnter(uriString, TimeSpan.FromSeconds(10)));
760760
Assert.False(timedOut);
761761
}
762+
763+
public static IEnumerable<object[]> FilePathHandlesNonAscii_TestData()
764+
{
765+
if (PlatformDetection.IsNotWindows)
766+
{
767+
// Unix absolute file path
768+
yield return new object[] { "/\u00FCri/", "file:///\u00FCri/", "/%C3%BCri/", "file:///%C3%BCri/", "/\u00FCri/" };
769+
yield return new object[] { "/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" };
770+
}
771+
772+
// Absolute fie path
773+
yield return new object[] { "file:///\u00FCri/", "file:///\u00FCri/", "/%C3%BCri/", "file:///%C3%BCri/", "/\u00FCri/" };
774+
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" };
775+
776+
// DOS
777+
yield return new object[] { "file://C:/\u00FCri/", "file:///C:/\u00FCri/", "C:/%C3%BCri/", "file:///C:/%C3%BCri/", "C:\\\u00FCri\\" };
778+
yield return new object[] { "file:///C:/\u00FCri/", "file:///C:/\u00FCri/", "C:/%C3%BCri/", "file:///C:/%C3%BCri/", "C:\\\u00FCri\\" };
779+
yield return new object[] { "C:/\u00FCri/", "file:///C:/\u00FCri/", "C:/%C3%BCri/", "file:///C:/%C3%BCri/", "C:\\\u00FCri\\" };
780+
781+
// UNC
782+
yield return new object[] { "\\\\\u00FCri/", "file://\u00FCri/", "/", "file://\u00FCri/", "\\\\\u00FCri\\" };
783+
yield return new object[] { "file://\u00FCri/", "file://\u00FCri/", "/", "file://\u00FCri/", "\\\\\u00FCri\\" };
784+
785+
// ? and # handling
786+
if (PlatformDetection.IsWindows)
787+
{
788+
yield return new object[] { "file:///a/?b/c\u00FC/", "file:///a/?b/c\u00FC/", "/a/", "file:///a/?b/c%C3%BC/", "/a/" };
789+
yield return new object[] { "file:///a/#b/c\u00FC/", "file:///a/#b/c\u00FC/", "/a/", "file:///a/#b/c%C3%BC/", "/a/" };
790+
yield return new object[] { "file:///a/?b/#c/d\u00FC/", "file:///a/?b/#c/d\u00FC/", "/a/", "file:///a/?b/#c/d%C3%BC/", "/a/" };
791+
}
792+
else
793+
{
794+
yield return new object[] { "/a/?b/c\u00FC/", "file:///a/%3Fb/c\u00FC/", "/a/%3Fb/c%C3%BC/", "file:///a/%3Fb/c%C3%BC/", "/a/?b/c\u00FC/" };
795+
yield return new object[] { "/a/#b/c\u00FC/", "file:///a/%23b/c\u00FC/", "/a/%23b/c%C3%BC/", "file:///a/%23b/c%C3%BC/", "/a/#b/c\u00FC/" };
796+
yield return new object[] { "/a/?b/#c/d\u00FC/", "file:///a/%3Fb/%23c/d\u00FC/", "/a/%3Fb/%23c/d%C3%BC/", "file:///a/%3Fb/%23c/d%C3%BC/", "/a/?b/#c/d\u00FC/" };
797+
798+
yield return new object[] { "file:///a/?b/c\u00FC/", "file:///a/?b/c\u00FC/", "/a/", "file:///a/?b/c%C3%BC/", "/a/" };
799+
yield return new object[] { "file:///a/#b/c\u00FC/", "file:///a/#b/c\u00FC/", "/a/", "file:///a/#b/c%C3%BC/", "/a/" };
800+
yield return new object[] { "file:///a/?b/#c/d\u00FC/", "file:///a/?b/#c/d\u00FC/", "/a/", "file:///a/?b/#c/d%C3%BC/", "/a/" };
801+
}
802+
}
803+
804+
[Theory]
805+
[MemberData(nameof(FilePathHandlesNonAscii_TestData))]
806+
public static void FilePathHandlesNonAscii(string uriString, string toString, string absolutePath, string absoluteUri, string localPath)
807+
{
808+
var uri = new Uri(uriString);
809+
810+
Assert.Equal(toString, uri.ToString());
811+
Assert.Equal(absolutePath, uri.AbsolutePath);
812+
Assert.Equal(absoluteUri, uri.AbsoluteUri);
813+
Assert.Equal(localPath, uri.LocalPath);
814+
815+
var uri2 = new Uri(uri.AbsoluteUri);
816+
817+
Assert.Equal(toString, uri2.ToString());
818+
Assert.Equal(absolutePath, uri2.AbsolutePath);
819+
Assert.Equal(absoluteUri, uri2.AbsoluteUri);
820+
Assert.Equal(localPath, uri2.LocalPath);
821+
}
762822
}
763823
}

0 commit comments

Comments
 (0)