4
4
using System ;
5
5
using System . Buffers ;
6
6
using System . Collections . Generic ;
7
+ using System . Diagnostics ;
7
8
using System . IO ;
8
9
using System . IO . Pipelines ;
9
10
using System . Runtime . CompilerServices ;
@@ -139,74 +140,78 @@ private void ParseFormValuesFast(ReadOnlySpan<byte> span,
139
140
bool isFinalBlock ,
140
141
out int consumed )
141
142
{
142
- ReadOnlySpan < byte > key = default ;
143
- ReadOnlySpan < byte > value = default ;
143
+ ReadOnlySpan < byte > key ;
144
+ ReadOnlySpan < byte > value ;
144
145
consumed = 0 ;
145
146
var equalsDelimiter = GetEqualsForEncoding ( ) ;
146
147
var andDelimiter = GetAndForEncoding ( ) ;
147
148
148
149
while ( span . Length > 0 )
149
150
{
150
- var equals = span . IndexOf ( equalsDelimiter ) ;
151
+ // Find the end of the key=value pair.
152
+ var ampersand = span . IndexOf ( andDelimiter ) ;
153
+ ReadOnlySpan < byte > keyValuePair ;
154
+ int equals ;
155
+ var foundAmpersand = ampersand != - 1 ;
151
156
152
- if ( equals == - 1 )
157
+ if ( foundAmpersand )
158
+ {
159
+ keyValuePair = span . Slice ( 0 , ampersand ) ;
160
+ span = span . Slice ( keyValuePair . Length + andDelimiter . Length ) ;
161
+ consumed += keyValuePair . Length + andDelimiter . Length ;
162
+ }
163
+ else
153
164
{
154
- if ( span . Length > KeyLengthLimit )
165
+ // We can't know that what is currently read is the end of the form value, that's only the case if this is the final block
166
+ // If we're not in the final block, then consume nothing
167
+ if ( ! isFinalBlock )
155
168
{
156
- ThrowKeyTooLargeException ( ) ;
169
+ // Don't buffer indefinately
170
+ if ( span . Length > KeyLengthLimit + ValueLengthLimit )
171
+ {
172
+ ThrowKeyOrValueTooLargeException ( ) ;
173
+ }
174
+ return ;
157
175
}
158
- break ;
159
- }
160
176
161
- if ( equals > KeyLengthLimit )
162
- {
163
- ThrowKeyTooLargeException ( ) ;
177
+ keyValuePair = span ;
178
+ span = default ;
179
+ consumed += keyValuePair . Length ;
164
180
}
165
181
166
- key = span . Slice ( 0 , equals ) ;
167
-
168
- span = span . Slice ( key . Length + equalsDelimiter . Length ) ;
169
- value = span ;
182
+ equals = keyValuePair . IndexOf ( equalsDelimiter ) ;
170
183
171
- var ampersand = span . IndexOf ( andDelimiter ) ;
172
-
173
- if ( ampersand == - 1 )
184
+ if ( equals == - 1 )
174
185
{
175
- if ( span . Length > ValueLengthLimit )
176
- {
177
- ThrowValueTooLargeException ( ) ;
178
- return ;
179
- }
180
-
181
- if ( ! isFinalBlock )
186
+ // Too long for the whole segment to be a key.
187
+ if ( keyValuePair . Length > KeyLengthLimit )
182
188
{
183
- // We can't know that what is currently read is the end of the form value, that's only the case if this is the final block
184
- // If we're not in the final block, then consume nothing
185
- break ;
189
+ ThrowKeyTooLargeException ( ) ;
186
190
}
187
191
188
- // If we are on the final block, the remaining content in value is what we want to add to the KVAccumulator .
189
- // Clear out the remaining span such that the loop will exit.
190
- span = Span < byte > . Empty ;
192
+ // There is no more data, this segment must be "key" with no equals or value .
193
+ key = keyValuePair ;
194
+ value = default ;
191
195
}
192
196
else
193
197
{
194
- if ( ampersand > ValueLengthLimit )
198
+ key = keyValuePair . Slice ( 0 , equals ) ;
199
+ if ( key . Length > KeyLengthLimit )
195
200
{
196
- ThrowValueTooLargeException ( ) ;
201
+ ThrowKeyTooLargeException ( ) ;
197
202
}
198
203
199
- value = span . Slice ( 0 , ampersand ) ;
200
- span = span . Slice ( ampersand + andDelimiter . Length ) ;
204
+ value = keyValuePair . Slice ( equals + equalsDelimiter . Length ) ;
205
+ if ( value . Length > ValueLengthLimit )
206
+ {
207
+ ThrowValueTooLargeException ( ) ;
208
+ }
201
209
}
202
210
203
211
var decodedKey = GetDecodedString ( key ) ;
204
212
var decodedValue = GetDecodedString ( value ) ;
205
213
206
214
AppendAndVerify ( ref accumulator , decodedKey , decodedValue ) ;
207
-
208
- // Cover case where we don't have an ampersand at the end.
209
- consumed += key . Length + value . Length + ( ampersand == - 1 ? equalsDelimiter . Length : equalsDelimiter . Length + andDelimiter . Length ) ;
210
215
}
211
216
}
212
217
@@ -217,53 +222,68 @@ private void ParseValuesSlow(
217
222
bool isFinalBlock )
218
223
{
219
224
var sequenceReader = new SequenceReader < byte > ( buffer ) ;
225
+ ReadOnlySequence < byte > keyValuePair ;
226
+
220
227
var consumed = sequenceReader . Position ;
221
228
var consumedBytes = default ( long ) ;
222
229
var equalsDelimiter = GetEqualsForEncoding ( ) ;
223
230
var andDelimiter = GetAndForEncoding ( ) ;
224
231
225
232
while ( ! sequenceReader . End )
226
233
{
227
- // TODO seems there is a bug with TryReadTo (advancePastDelimiter: true). It isn't advancing past the delimiter on second read.
228
- if ( ! sequenceReader . TryReadTo ( out ReadOnlySequence < byte > key , equalsDelimiter , advancePastDelimiter : false ) ||
229
- ! sequenceReader . IsNext ( equalsDelimiter , true ) )
234
+ if ( ! sequenceReader . TryReadTo ( out keyValuePair , andDelimiter ) )
230
235
{
231
- if ( ( sequenceReader . Consumed - consumedBytes ) > KeyLengthLimit )
236
+ if ( ! isFinalBlock )
232
237
{
233
- ThrowKeyTooLargeException ( ) ;
238
+ // Don't buffer indefinately
239
+ if ( ( sequenceReader . Consumed - consumedBytes ) > KeyLengthLimit + ValueLengthLimit )
240
+ {
241
+ ThrowKeyOrValueTooLargeException ( ) ;
242
+ }
243
+ break ;
234
244
}
235
245
236
- break ;
246
+ // This must be the final key=value pair
247
+ keyValuePair = buffer . Slice ( sequenceReader . Position ) ;
248
+ sequenceReader . Advance ( keyValuePair . Length ) ;
237
249
}
238
250
239
- if ( key . Length > KeyLengthLimit )
251
+ if ( keyValuePair . IsSingleSegment )
240
252
{
241
- ThrowKeyTooLargeException ( ) ;
253
+ ParseFormValuesFast ( keyValuePair . FirstSpan , ref accumulator , isFinalBlock : true , out var segmentConsumed ) ;
254
+ Debug . Assert ( segmentConsumed == keyValuePair . FirstSpan . Length ) ;
255
+ continue ;
242
256
}
243
257
244
- if ( ! sequenceReader . TryReadTo ( out ReadOnlySequence < byte > value , andDelimiter , false ) ||
245
- ! sequenceReader . IsNext ( andDelimiter , true ) )
258
+ var keyValueReader = new SequenceReader < byte > ( keyValuePair ) ;
259
+ ReadOnlySequence < byte > value ;
260
+
261
+ if ( keyValueReader . TryReadTo ( out var key , equalsDelimiter ) )
246
262
{
247
- if ( ! isFinalBlock )
263
+ if ( key . Length > KeyLengthLimit )
248
264
{
249
- if ( ( sequenceReader . Consumed - consumedBytes - key . Length ) > ValueLengthLimit )
250
- {
251
- ThrowValueTooLargeException ( ) ;
252
- }
253
- break ;
265
+ ThrowKeyTooLargeException ( ) ;
254
266
}
255
267
256
- value = buffer . Slice ( sequenceReader . Position ) ;
257
-
258
- sequenceReader . Advance ( value . Length ) ;
268
+ value = keyValuePair . Slice ( keyValueReader . Position ) ;
269
+ if ( value . Length > ValueLengthLimit )
270
+ {
271
+ ThrowValueTooLargeException ( ) ;
272
+ }
259
273
}
260
-
261
- if ( value . Length > ValueLengthLimit )
274
+ else
262
275
{
263
- ThrowValueTooLargeException ( ) ;
276
+ // Too long for the whole segment to be a key.
277
+ if ( keyValuePair . Length > KeyLengthLimit )
278
+ {
279
+ ThrowKeyTooLargeException ( ) ;
280
+ }
281
+
282
+ // There is no more data, this segment must be "key" with no equals or value.
283
+ key = keyValuePair ;
284
+ value = default ;
264
285
}
265
286
266
- // Need to call ToArray if the key/value spans multiple segments
267
287
var decodedKey = GetDecodedStringFromReadOnlySequence ( key ) ;
268
288
var decodedValue = GetDecodedStringFromReadOnlySequence ( value ) ;
269
289
@@ -276,6 +296,11 @@ private void ParseValuesSlow(
276
296
buffer = buffer . Slice ( consumed ) ;
277
297
}
278
298
299
+ private void ThrowKeyOrValueTooLargeException ( )
300
+ {
301
+ throw new InvalidDataException ( $ "Form key length limit { KeyLengthLimit } or value length limit { ValueLengthLimit } exceeded.") ;
302
+ }
303
+
279
304
private void ThrowKeyTooLargeException ( )
280
305
{
281
306
throw new InvalidDataException ( $ "Form key length limit { KeyLengthLimit } exceeded.") ;
0 commit comments