From 2cca2f348f370358fbe501e401f1102162eb4bcc Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 3 Feb 2026 06:30:15 +0100 Subject: [PATCH] Fix custom Uri parser edge-case when UriKind.Relative is used --- .../System.Private.Uri/src/System/UriExt.cs | 18 +++----- .../FunctionalTests/UriCreationOptionsTest.cs | 3 -- .../tests/FunctionalTests/UriParserTest.cs | 45 +++++++++++++++++++ 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/libraries/System.Private.Uri/src/System/UriExt.cs b/src/libraries/System.Private.Uri/src/System/UriExt.cs index 40c7c4958d018d..0b623579a9efb5 100644 --- a/src/libraries/System.Private.Uri/src/System/UriExt.cs +++ b/src/libraries/System.Private.Uri/src/System/UriExt.cs @@ -119,12 +119,6 @@ private void CreateThis(string? uri, bool dontEscape, UriKind uriKind, in UriCre return GetException(err); } - - if (uriKind == UriKind.Relative) - { - // Here we know that we can create an absolute Uri, but the user has requested only a relative one - return GetException(ParsingError.CannotCreateRelative); - } } else { @@ -148,12 +142,12 @@ private void CreateThis(string? uri, bool dontEscape, UriKind uriKind, in UriCre // we use = here to clear all parsing flags for a uri that we think is invalid. _flags = Flags.UserDrivenParsing | (_flags & Flags.UserEscaped); } - else if (uriKind == UriKind.Relative) - { - // Here we know that custom parser can create an absolute Uri, but the user has requested only a - // relative one - return GetException(ParsingError.CannotCreateRelative); - } + } + + if (uriKind == UriKind.Relative) + { + // Here we know that we can create an absolute Uri, but the user has requested only a relative one + return GetException(ParsingError.CannotCreateRelative); } if (hasUnicode) diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriCreationOptionsTest.cs b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriCreationOptionsTest.cs index 3f7438738f0bf9..d7722101f6c428 100644 --- a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriCreationOptionsTest.cs +++ b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriCreationOptionsTest.cs @@ -287,8 +287,5 @@ public void DisableCanonicalization_GetComponentsThrowsForPathAndQuery(UriFormat Assert.Throws(() => uri.GetComponents(UriComponents.PathAndQuery, format)); Assert.Throws(() => uri.GetComponents(UriComponents.AbsoluteUri, format)); } - - - private sealed class CustomUriParser : UriParser { } } } diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriParserTest.cs b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriParserTest.cs index c54e432067e5c8..b10d5387d6fc8b 100644 --- a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriParserTest.cs +++ b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriParserTest.cs @@ -576,5 +576,50 @@ public static void NetTcpStyleUriParser_ctor() new NetTcpStyleUriParser(); } #endregion UriParser template tests + + [Theory] + [InlineData(UriKind.Absolute, true)] + [InlineData(UriKind.Absolute, false)] + [InlineData(UriKind.RelativeOrAbsolute, true)] + [InlineData(UriKind.RelativeOrAbsolute, false)] + [InlineData(UriKind.Relative, true)] + [InlineData(UriKind.Relative, false)] + public static void CustomParserCanRecoverOnInvalidUri(UriKind uriKind, bool recover) + { + string scheme = $"custom-recover-on-invalid-{uriKind}-{recover}"; + UriParser.Register(new CustomParser_RecoversOnInvalidUri(recover), scheme, -1); + + var uriString = recover ? $"{scheme}:not a valid host" : $"{scheme}://host"; + + if (uriKind == UriKind.Relative) + { + Assert.Throws(() => new Uri(uriString, uriKind)); + Assert.False(Uri.TryCreate(uriString, uriKind, out _)); + } + else + { + var uri1 = new Uri(uriString, uriKind); + Assert.True(Uri.TryCreate(uriString, uriKind, out Uri? uri2)); + Assert.Same(uri1.OriginalString, uri2.OriginalString); + Assert.Equal("foo", uri1.Host); + Assert.Equal("foo", uri2.Host); + } + } + + private sealed class CustomParser_RecoversOnInvalidUri(bool recover) : GenericUriParser(GenericUriParserOptions.Default) + { + protected override void InitializeAndValidate(Uri uri, out UriFormatException? parsingError) + { + base.InitializeAndValidate(uri, out parsingError); + + if (recover) + { + Assert.NotNull(parsingError); + parsingError = null; + } + } + + protected override string GetComponents(Uri uri, UriComponents components, UriFormat format) => "foo"; + } } }