diff --git a/src/Http/Http.Extensions/src/UriHelper.cs b/src/Http/Http.Extensions/src/UriHelper.cs index 6c98c4d4890b..9986ba14092b 100644 --- a/src/Http/Http.Extensions/src/UriHelper.cs +++ b/src/Http/Http.Extensions/src/UriHelper.cs @@ -1,9 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Buffers; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; using System.Text; namespace Microsoft.AspNetCore.Http.Extensions; @@ -17,7 +15,6 @@ public static class UriHelper private const char Hash = '#'; private const char QuestionMark = '?'; private static readonly string SchemeDelimiter = Uri.SchemeDelimiter; - private static readonly SpanAction InitializeAbsoluteUriStringSpanAction = new(InitializeAbsoluteUriString); /// /// Combines the given URI components into a string that is properly encoded for use in HTTP headers. @@ -90,7 +87,41 @@ public static string BuildAbsolute( length--; } - return string.Create(length, (scheme, hostText, pathBaseText, pathText, queryText, fragmentText), InitializeAbsoluteUriStringSpanAction); + return string.Create( + length, + new UriComponents(scheme, hostText, pathBaseText, pathText, queryText, fragmentText), + static (buffer, uriComponents) => + { + uriComponents.Scheme.AsSpan().CopyTo(buffer); + buffer = buffer.Slice(uriComponents.Scheme.Length); + + Uri.SchemeDelimiter.CopyTo(buffer); + buffer = buffer.Slice(Uri.SchemeDelimiter.Length); + + uriComponents.Host.AsSpan().CopyTo(buffer); + buffer = buffer.Slice(uriComponents.Host.Length); + + var pathBaseSpan = uriComponents.PathBase.AsSpan(); + + if (uriComponents.Path.Length > 0 && pathBaseSpan.Length > 0 && pathBaseSpan[^1] == '/') + { + // If the path string has a trailing slash and the other string has a leading slash, we need + // to trim one of them. + // Trim the last slash from pathBase. The total length was decremented before the call to string.Create. + pathBaseSpan = pathBaseSpan[..^1]; + } + + pathBaseSpan.CopyTo(buffer); + buffer = buffer.Slice(pathBaseSpan.Length); + + uriComponents.Path.CopyTo(buffer); + buffer = buffer.Slice(uriComponents.Path.Length); + + uriComponents.Query.CopyTo(buffer); + buffer = buffer.Slice(uriComponents.Query.Length); + + uriComponents.Fragment.CopyTo(buffer); + }); } /// @@ -171,7 +202,7 @@ public static string Encode(Uri uri) } else { - return uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); + return uri.GetComponents(System.UriComponents.SerializationInfoString, UriFormat.UriEscaped); } } @@ -224,45 +255,23 @@ public static string GetDisplayUrl(this HttpRequest request) .ToString(); } - /// - /// Copies the specified to the specified starting at the specified . - /// - /// The buffer to copy text to. - /// The buffer start index. - /// The text to copy. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int CopyTextToBuffer(Span buffer, int index, ReadOnlySpan text) - { - text.CopyTo(buffer.Slice(index, text.Length)); - return index + text.Length; - } - - /// - /// Initializes the URI for . - /// - /// The URI 's buffer. - /// The URI parts. - private static void InitializeAbsoluteUriString(Span buffer, (string scheme, string host, string pathBase, string path, string query, string fragment) uriParts) + private readonly struct UriComponents { - var index = 0; - - var pathBaseSpan = uriParts.pathBase.AsSpan(); + public readonly string Scheme; + public readonly string Host; + public readonly string PathBase; + public readonly string Path; + public readonly string Query; + public readonly string Fragment; - if (uriParts.path.Length > 0 && pathBaseSpan.Length > 0 && pathBaseSpan[^1] == '/') + public UriComponents(string scheme, string host, string pathBase, string path, string query, string fragment) { - // If the path string has a trailing slash and the other string has a leading slash, we need - // to trim one of them. - // Trim the last slahs from pathBase. The total length was decremented before the call to string.Create. - pathBaseSpan = pathBaseSpan[..^1]; + Scheme = scheme; + Host = host; + PathBase = pathBase; + Path = path; + Query = query; + Fragment = fragment; } - - index = CopyTextToBuffer(buffer, index, uriParts.scheme.AsSpan()); - index = CopyTextToBuffer(buffer, index, Uri.SchemeDelimiter.AsSpan()); - index = CopyTextToBuffer(buffer, index, uriParts.host.AsSpan()); - index = CopyTextToBuffer(buffer, index, pathBaseSpan); - index = CopyTextToBuffer(buffer, index, uriParts.path.AsSpan()); - index = CopyTextToBuffer(buffer, index, uriParts.query.AsSpan()); - _ = CopyTextToBuffer(buffer, index, uriParts.fragment.AsSpan()); } }