2
2
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
3
4
4
using System ;
5
- using Microsoft . AspNetCore . WebUtilities ;
5
+ using System . Buffers ;
6
+ using System . Collections . Generic ;
7
+ using System . Runtime . CompilerServices ;
8
+ using System . Runtime . InteropServices ;
9
+ using System . Runtime . Intrinsics ;
10
+ using System . Runtime . Intrinsics . X86 ;
11
+ using Microsoft . AspNetCore . Internal ;
12
+ using Microsoft . Extensions . Primitives ;
6
13
7
14
namespace Microsoft . AspNetCore . Http . Features
8
15
{
@@ -55,30 +62,21 @@ public IQueryCollection Query
55
62
{
56
63
get
57
64
{
58
- if ( _features . Collection == null )
65
+ if ( _features . Collection is null )
59
66
{
60
- if ( _parsedValues == null )
61
- {
62
- _parsedValues = QueryCollection . Empty ;
63
- }
64
- return _parsedValues ;
67
+ return _parsedValues ?? QueryCollection . Empty ;
65
68
}
66
69
67
70
var current = HttpRequestFeature . QueryString ;
68
71
if ( _parsedValues == null || ! string . Equals ( _original , current , StringComparison . Ordinal ) )
69
72
{
70
73
_original = current ;
71
74
72
- var result = QueryHelpers . ParseNullableQuery ( current ) ;
75
+ var result = ParseNullableQueryInternal ( current ) ;
73
76
74
- if ( result == null )
75
- {
76
- _parsedValues = QueryCollection . Empty ;
77
- }
78
- else
79
- {
80
- _parsedValues = new QueryCollection ( result ) ;
81
- }
77
+ _parsedValues = result is not null
78
+ ? new QueryCollectionInternal ( result )
79
+ : QueryCollection . Empty ;
82
80
}
83
81
return _parsedValues ;
84
82
}
@@ -100,5 +98,221 @@ public IQueryCollection Query
100
98
}
101
99
}
102
100
}
101
+
102
+ /// <summary>
103
+ /// Parse a query string into its component key and value parts.
104
+ /// </summary>
105
+ /// <param name="queryString">The raw query string value, with or without the leading '?'.</param>
106
+ /// <returns>A collection of parsed keys and values, null if there are no entries.</returns>
107
+ [ SkipLocalsInit ]
108
+ internal static AdaptiveCapacityDictionary < string , StringValues > ? ParseNullableQueryInternal ( string ? queryString )
109
+ {
110
+ if ( string . IsNullOrEmpty ( queryString ) || ( queryString . Length == 1 && queryString [ 0 ] == '?' ) )
111
+ {
112
+ return null ;
113
+ }
114
+
115
+ var accumulator = new KvpAccumulator ( ) ;
116
+ var query = queryString . AsSpan ( ) ;
117
+
118
+ if ( query [ 0 ] == '?' )
119
+ {
120
+ query = query [ 1 ..] ;
121
+ }
122
+
123
+ while ( ! query . IsEmpty )
124
+ {
125
+ var delimiterIndex = query . IndexOf ( '&' ) ;
126
+
127
+ var querySegment = delimiterIndex >= 0
128
+ ? query . Slice ( 0 , delimiterIndex )
129
+ : query ;
130
+
131
+ var equalIndex = querySegment . IndexOf ( '=' ) ;
132
+
133
+ if ( equalIndex >= 0 )
134
+ {
135
+ var name = SpanHelper . ReplacePlusWithSpace ( querySegment . Slice ( 0 , equalIndex ) ) ;
136
+ var value = SpanHelper . ReplacePlusWithSpace ( querySegment . Slice ( equalIndex + 1 ) ) ;
137
+
138
+ accumulator . Append (
139
+ Uri . UnescapeDataString ( name ) ,
140
+ Uri . UnescapeDataString ( value ) ) ;
141
+ }
142
+ else
143
+ {
144
+ if ( ! querySegment . IsEmpty )
145
+ {
146
+ accumulator . Append ( querySegment ) ;
147
+ }
148
+ }
149
+
150
+ if ( delimiterIndex < 0 )
151
+ {
152
+ break ;
153
+ }
154
+
155
+ query = query . Slice ( delimiterIndex + 1 ) ;
156
+ }
157
+
158
+ return accumulator . HasValues
159
+ ? accumulator . GetResults ( )
160
+ : null ;
161
+ }
162
+
163
+ internal struct KvpAccumulator
164
+ {
165
+ /// <summary>
166
+ /// This API supports infrastructure and is not intended to be used
167
+ /// directly from your code. This API may change or be removed in future releases.
168
+ /// </summary>
169
+ private AdaptiveCapacityDictionary < string , StringValues > _accumulator ;
170
+ private AdaptiveCapacityDictionary < string , List < string > > _expandingAccumulator ;
171
+
172
+ public void Append ( ReadOnlySpan < char > key , ReadOnlySpan < char > value = default )
173
+ => Append ( key . ToString ( ) , value . IsEmpty ? string . Empty : value . ToString ( ) ) ;
174
+
175
+ /// <summary>
176
+ /// This API supports infrastructure and is not intended to be used
177
+ /// directly from your code. This API may change or be removed in future releases.
178
+ /// </summary>
179
+ public void Append ( string key , string value )
180
+ {
181
+ if ( _accumulator is null )
182
+ {
183
+ _accumulator = new AdaptiveCapacityDictionary < string , StringValues > ( StringComparer . OrdinalIgnoreCase ) ;
184
+ }
185
+
186
+ if ( ! _accumulator . TryGetValue ( key , out var values ) )
187
+ {
188
+ // First value for this key
189
+ _accumulator [ key ] = new StringValues ( value ) ;
190
+ }
191
+ else
192
+ {
193
+ AppendToExpandingAccumulator ( key , value , values ) ;
194
+ }
195
+
196
+ ValueCount ++ ;
197
+ }
198
+
199
+ private void AppendToExpandingAccumulator ( string key , string value , StringValues values )
200
+ {
201
+ // When there are some values for the same key, so switch to expanding accumulator, and
202
+ // add a zero count marker in the accumulator to indicate that switch.
203
+
204
+ if ( values . Count != 0 )
205
+ {
206
+ _accumulator [ key ] = default ;
207
+
208
+ if ( _expandingAccumulator is null )
209
+ {
210
+ _expandingAccumulator = new AdaptiveCapacityDictionary < string , List < string > > ( capacity : 5 , StringComparer . OrdinalIgnoreCase ) ;
211
+ }
212
+
213
+ // Already 2 (1 existing + the new one) entries so use List's expansion mechanism for more
214
+ var list = new List < string > ( ) ;
215
+
216
+ list . AddRange ( values ) ;
217
+ list . Add ( value ) ;
218
+
219
+ _expandingAccumulator [ key ] = list ;
220
+ }
221
+ else
222
+ {
223
+ // The marker indicates we are in the expanding accumulator, so just append to the list.
224
+ _expandingAccumulator [ key ] . Add ( value ) ;
225
+ }
226
+ }
227
+
228
+ /// <summary>
229
+ /// This API supports infrastructure and is not intended to be used
230
+ /// directly from your code. This API may change or be removed in future releases.
231
+ /// </summary>
232
+ public bool HasValues => ValueCount > 0 ;
233
+
234
+ /// <summary>
235
+ /// This API supports infrastructure and is not intended to be used
236
+ /// directly from your code. This API may change or be removed in future releases.
237
+ /// </summary>
238
+ public int KeyCount => _accumulator ? . Count ?? 0 ;
239
+
240
+ /// <summary>
241
+ /// This API supports infrastructure and is not intended to be used
242
+ /// directly from your code. This API may change or be removed in future releases.
243
+ /// </summary>
244
+ public int ValueCount { get ; private set ; }
245
+
246
+ /// <summary>
247
+ /// This API supports infrastructure and is not intended to be used
248
+ /// directly from your code. This API may change or be removed in future releases.
249
+ /// </summary>
250
+ public AdaptiveCapacityDictionary < string , StringValues > GetResults ( )
251
+ {
252
+ if ( _expandingAccumulator != null )
253
+ {
254
+ // Coalesce count 3+ multi-value entries into _accumulator dictionary
255
+ foreach ( var entry in _expandingAccumulator )
256
+ {
257
+ _accumulator [ entry . Key ] = new StringValues ( entry . Value . ToArray ( ) ) ;
258
+ }
259
+ }
260
+
261
+ return _accumulator ?? new AdaptiveCapacityDictionary < string , StringValues > ( 0 , StringComparer . OrdinalIgnoreCase ) ;
262
+ }
263
+ }
264
+
265
+ private static class SpanHelper
266
+ {
267
+ private static readonly SpanAction < char , IntPtr > s_replacePlusWithSpace = ReplacePlusWithSpaceCore ;
268
+
269
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
270
+ public static unsafe string ReplacePlusWithSpace ( ReadOnlySpan < char > span )
271
+ {
272
+ fixed ( char * ptr = & MemoryMarshal . GetReference ( span ) )
273
+ {
274
+ return string . Create ( span . Length , ( IntPtr ) ptr , s_replacePlusWithSpace ) ;
275
+ }
276
+ }
277
+
278
+ private static unsafe void ReplacePlusWithSpaceCore ( Span < char > buffer , IntPtr state )
279
+ {
280
+ fixed ( char * ptr = & MemoryMarshal . GetReference ( buffer ) )
281
+ {
282
+ var input = ( ushort * ) state . ToPointer ( ) ;
283
+ var output = ( ushort * ) ptr ;
284
+
285
+ var i = ( nint ) 0 ;
286
+ var n = ( nint ) ( uint ) buffer . Length ;
287
+
288
+ if ( Sse41 . IsSupported && n >= Vector128 < ushort > . Count )
289
+ {
290
+ var vecPlus = Vector128 . Create ( ( ushort ) '+' ) ;
291
+ var vecSpace = Vector128 . Create ( ( ushort ) ' ' ) ;
292
+
293
+ do
294
+ {
295
+ var vec = Sse2 . LoadVector128 ( input + i ) ;
296
+ var mask = Sse2 . CompareEqual ( vec , vecPlus ) ;
297
+ var res = Sse41 . BlendVariable ( vec , vecSpace , mask ) ;
298
+ Sse2 . Store ( output + i , res ) ;
299
+ i += Vector128 < ushort > . Count ;
300
+ } while ( i <= n - Vector128 < ushort > . Count ) ;
301
+ }
302
+
303
+ for ( ; i < n ; ++ i )
304
+ {
305
+ if ( input [ i ] != '+' )
306
+ {
307
+ output [ i ] = input [ i ] ;
308
+ }
309
+ else
310
+ {
311
+ output [ i ] = ' ' ;
312
+ }
313
+ }
314
+ }
315
+ }
316
+ }
103
317
}
104
318
}
0 commit comments