11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4- using System . Buffers ;
54using System . Diagnostics . CodeAnalysis ;
6- using System . Runtime . CompilerServices ;
75using System . Text ;
86
97namespace Microsoft . AspNetCore . Http . Extensions ;
@@ -17,7 +15,6 @@ public static class UriHelper
1715 private const char Hash = '#' ;
1816 private const char QuestionMark = '?' ;
1917 private static readonly string SchemeDelimiter = Uri . SchemeDelimiter ;
20- private static readonly SpanAction < char , ( string scheme , string host , string pathBase , string path , string query , string fragment ) > InitializeAbsoluteUriStringSpanAction = new ( InitializeAbsoluteUriString ) ;
2118
2219 /// <summary>
2320 /// Combines the given URI components into a string that is properly encoded for use in HTTP headers.
@@ -90,7 +87,59 @@ public static string BuildAbsolute(
9087 length -- ;
9188 }
9289
93- return string . Create ( length , ( scheme , hostText , pathBaseText , pathText , queryText , fragmentText ) , InitializeAbsoluteUriStringSpanAction ) ;
90+ return string . Create (
91+ length ,
92+ new UriComponents ( scheme , hostText , pathBaseText , pathText , queryText , fragmentText ) ,
93+ static ( buffer , uriComponents ) =>
94+ {
95+ if ( uriComponents . Scheme . Length > 0 )
96+ {
97+ uriComponents . Scheme . AsSpan ( ) . CopyTo ( buffer ) ;
98+ buffer = buffer . Slice ( uriComponents . Scheme . Length ) ;
99+ }
100+
101+ Uri . SchemeDelimiter . CopyTo ( buffer ) ;
102+ buffer = buffer . Slice ( Uri . SchemeDelimiter . Length ) ;
103+
104+ if ( uriComponents . Host . Length > 0 )
105+ {
106+ uriComponents . Host . AsSpan ( ) . CopyTo ( buffer ) ;
107+ buffer = buffer . Slice ( uriComponents . Host . Length ) ;
108+ }
109+
110+ var pathBaseSpan = uriComponents . PathBase . AsSpan ( ) ;
111+
112+ if ( uriComponents . Path . Length > 0 && pathBaseSpan . Length > 0 && pathBaseSpan [ ^ 1 ] == '/' )
113+ {
114+ // If the path string has a trailing slash and the other string has a leading slash, we need
115+ // to trim one of them.
116+ // Trim the last slash from pathBase. The total length was decremented before the call to string.Create.
117+ pathBaseSpan = pathBaseSpan [ ..^ 1 ] ;
118+ }
119+
120+ if ( pathBaseSpan . Length > 0 )
121+ {
122+ pathBaseSpan . CopyTo ( buffer ) ;
123+ buffer = buffer . Slice ( pathBaseSpan . Length ) ;
124+ }
125+
126+ if ( uriComponents . Path . Length > 0 )
127+ {
128+ uriComponents . Path . CopyTo ( buffer ) ;
129+ buffer = buffer . Slice ( uriComponents . Path . Length ) ;
130+ }
131+
132+ if ( uriComponents . Query . Length > 0 )
133+ {
134+ uriComponents . Query . CopyTo ( buffer ) ;
135+ buffer = buffer . Slice ( uriComponents . Query . Length ) ;
136+ }
137+
138+ if ( uriComponents . Fragment . Length > 0 )
139+ {
140+ uriComponents . Fragment . CopyTo ( buffer ) ;
141+ }
142+ } ) ;
94143 }
95144
96145 /// <summary>
@@ -224,45 +273,23 @@ public static string GetDisplayUrl(this HttpRequest request)
224273 . ToString ( ) ;
225274 }
226275
227- /// <summary>
228- /// Copies the specified <paramref name="text"/> to the specified <paramref name="buffer"/> starting at the specified <paramref name="index"/>.
229- /// </summary>
230- /// <param name="buffer">The buffer to copy text to.</param>
231- /// <param name="index">The buffer start index.</param>
232- /// <param name="text">The text to copy.</param>
233- /// <returns></returns>
234- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
235- private static int CopyTextToBuffer ( Span < char > buffer , int index , ReadOnlySpan < char > text )
236- {
237- text . CopyTo ( buffer . Slice ( index , text . Length ) ) ;
238- return index + text . Length ;
239- }
240-
241- /// <summary>
242- /// Initializes the URI <see cref="string"/> for <see cref="BuildAbsolute(string, HostString, PathString, PathString, QueryString, FragmentString)"/>.
243- /// </summary>
244- /// <param name="buffer">The URI <see cref="string"/>'s <see cref="char"/> buffer.</param>
245- /// <param name="uriParts">The URI parts.</param>
246- private static void InitializeAbsoluteUriString ( Span < char > buffer , ( string scheme , string host , string pathBase , string path , string query , string fragment ) uriParts )
276+ private readonly struct UriComponents
247277 {
248- var index = 0 ;
249-
250- var pathBaseSpan = uriParts . pathBase . AsSpan ( ) ;
278+ public readonly string Scheme ;
279+ public readonly string Host ;
280+ public readonly string PathBase ;
281+ public readonly string Path ;
282+ public readonly string Query ;
283+ public readonly string Fragment ;
251284
252- if ( uriParts . path . Length > 0 && pathBaseSpan . Length > 0 && pathBaseSpan [ ^ 1 ] == '/' )
285+ public UriComponents ( string scheme , string host , string pathBase , string path , string query , string fragment )
253286 {
254- // If the path string has a trailing slash and the other string has a leading slash, we need
255- // to trim one of them.
256- // Trim the last slahs from pathBase. The total length was decremented before the call to string.Create.
257- pathBaseSpan = pathBaseSpan [ ..^ 1 ] ;
287+ Scheme = scheme ;
288+ Host = host ;
289+ PathBase = pathBase ;
290+ Path = path ;
291+ Query = query ;
292+ Fragment = fragment ;
258293 }
259-
260- index = CopyTextToBuffer ( buffer , index , uriParts . scheme . AsSpan ( ) ) ;
261- index = CopyTextToBuffer ( buffer , index , Uri . SchemeDelimiter . AsSpan ( ) ) ;
262- index = CopyTextToBuffer ( buffer , index , uriParts . host . AsSpan ( ) ) ;
263- index = CopyTextToBuffer ( buffer , index , pathBaseSpan ) ;
264- index = CopyTextToBuffer ( buffer , index , uriParts . path . AsSpan ( ) ) ;
265- index = CopyTextToBuffer ( buffer , index , uriParts . query . AsSpan ( ) ) ;
266- _ = CopyTextToBuffer ( buffer , index , uriParts . fragment . AsSpan ( ) ) ;
267294 }
268295}
0 commit comments