From 73033dee728a145bab5e6d313fa3555f7aac87ea Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 15 Feb 2026 00:23:02 +0100 Subject: [PATCH 1/4] Use SearchValues to find the end of the Uri UserInfo --- .../System.Private.Uri/src/System/Uri.cs | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index e188918eabb7ff..1529c3c89a5f48 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -3672,6 +3672,9 @@ private static int ParseSchemeCheckImplicitFile(string uriString, ref ParsingErr return UriParser.FindOrFetchAsUnknownV1Syntax(UriHelper.SpanToLowerInvariantString(scheme)); } + private static readonly SearchValues s_userInfoEndChars = + SearchValues.Create(@"@?#\/"); + // Checks the syntax of an authority component. It may also get a userInfo if present // Returns an error if no/mailformed authority found // Does not NOT touch _info @@ -3718,33 +3721,24 @@ private int CheckAuthorityHelper(ReadOnlySpan str, int startOffset, out Pa if ((syntaxFlags & UriSyntaxFlags.MayHaveUserInfo) != 0) { - for (; (uint)i < (uint)str.Length; i++) + ReadOnlySpan slice = str.Slice(i); + int userInfoLength = slice.IndexOfAny(s_userInfoEndChars); + + // Check if the first delimiter is '@' and there's at least one character after it. + if ((uint)userInfoLength < (uint)slice.Length && slice[userInfoLength] == '@' && (uint)(++userInfoLength) < (uint)slice.Length) { - ch = str[i]; + ch = slice[userInfoLength]; + i += userInfoLength; - if ((uint)(i + 1) >= (uint)str.Length || ch == '?' || ch == '#' || ch == '\\' || ch == '/') - { - ch = str[startOffset]; - i = startOffset; - break; - } + flags |= Flags.HasUserInfo; - if (ch == '@') + // Iri'ze userinfo + if (hasUnicode) { - flags |= Flags.HasUserInfo; - - // Iri'ze userinfo - if (hasUnicode) - { - var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]); - IriHelper.EscapeUnescapeIri(ref vsb, str.Slice(startOffset, i - startOffset + 1), isQuery: false); - newHost = string.Concat(newHost, vsb.AsSpan()); - vsb.Dispose(); - } - - ch = str[i + 1]; - i++; - break; + var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]); + IriHelper.EscapeUnescapeIri(ref vsb, str.Slice(startOffset, userInfoLength), isQuery: false); + newHost = string.Concat(newHost, vsb.AsSpan()); + vsb.Dispose(); } } } From 2690f4074a3f68cf08fa3a3298d6eb0a8b11da7b Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 15 Feb 2026 00:29:51 +0100 Subject: [PATCH 2/4] Replace AllowAnyOtherHost loop with IndexOfAny --- src/libraries/System.Private.Uri/src/System/Uri.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index 1529c3c89a5f48..132ad8cd8c9513 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -3919,13 +3919,8 @@ str[delimiterIdx] is not ('/' or '\\') && if ((syntaxFlags & UriSyntaxFlags.AllowAnyOtherHost) != 0) { flags |= Flags.BasicHostType; - for (endOfHost = i; (uint)endOfHost < (uint)str.Length; endOfHost++) - { - if (str[endOfHost] == '/' || str[endOfHost] == '?' || str[endOfHost] == '#') - { - break; - } - } + int basicHostEnd = str.Slice(i).IndexOfAny('/', '?', '#'); + endOfHost = basicHostEnd >= 0 ? i + basicHostEnd : str.Length; if (hasUnicode) { From 416f639748753386ee9288bb59ce4dbd26b5e6ef Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 15 Feb 2026 00:36:14 +0100 Subject: [PATCH 3/4] Replace the loop in GetCombinedString --- .../System.Private.Uri/src/System/Uri.cs | 53 ++++++++----------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index 132ad8cd8c9513..9fe33fbee84778 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -641,47 +641,36 @@ private static void GetCombinedString(Uri baseUri, string relativeStr, // This parser will allow the relativeStr to be an absolute Uri with the different scheme // In fact this is strict violation of RFC2396 // - for (int i = 0; i < relativeStr.Length; ++i) - { - if (relativeStr[i] == '/' || relativeStr[i] == '\\' || relativeStr[i] == '?' || relativeStr[i] == '#') - { - break; - } - else if (relativeStr[i] == ':') - { - if (i < 2) - { - // Note we don't support one-letter Uri schemes. - // Hence anything like x:sdsd is a relative path and be added to the baseUri Path - break; - } + int i = relativeStr.IndexOfAny(s_segmentSeparatorChars); - ParsingError error = ParsingError.None; - UriParser? syntax = CheckSchemeSyntax(relativeStr.AsSpan(0, i), ref error); + // Note we don't support one-letter Uri schemes (i > 1). + // Hence anything like x:sdsd is a relative path and be added to the baseUri Path + if ((uint)i < (uint)relativeStr.Length && relativeStr[i] == ':' && i > 1) + { + ParsingError error = ParsingError.None; + UriParser? syntax = CheckSchemeSyntax(relativeStr.AsSpan(0, i), ref error); - if (error == ParsingError.None) + if (error == ParsingError.None) + { + if (baseUri.Syntax == syntax) { - if (baseUri.Syntax == syntax) + //Remove the scheme for backward Uri parsers compatibility + if (i + 1 < relativeStr.Length) { - //Remove the scheme for backward Uri parsers compatibility - if (i + 1 < relativeStr.Length) - { - relativeStr = relativeStr.Substring(i + 1); - } - else - { - relativeStr = string.Empty; - } + relativeStr = relativeStr.Substring(i + 1); } else { - // This is the place where we switch the scheme. - // Return relative part as the result Uri. - result = relativeStr; - return; + relativeStr = string.Empty; } } - break; + else + { + // This is the place where we switch the scheme. + // Return relative part as the result Uri. + result = relativeStr; + return; + } } } From d7e05db9ba8db0129e44dd9de3c293d6411ead79 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 15 Feb 2026 03:01:35 +0100 Subject: [PATCH 4/4] Simplify a slice --- src/libraries/System.Private.Uri/src/System/Uri.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index 9fe33fbee84778..a9395b312c3784 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -3725,7 +3725,7 @@ private int CheckAuthorityHelper(ReadOnlySpan str, int startOffset, out Pa if (hasUnicode) { var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]); - IriHelper.EscapeUnescapeIri(ref vsb, str.Slice(startOffset, userInfoLength), isQuery: false); + IriHelper.EscapeUnescapeIri(ref vsb, slice.Slice(0, userInfoLength), isQuery: false); newHost = string.Concat(newHost, vsb.AsSpan()); vsb.Dispose(); }