Skip to content

Commit 0559d39

Browse files
authored
Fix form pipe parser #12381 (#12749)
1 parent 999d61b commit 0559d39

File tree

3 files changed

+131
-83
lines changed

3 files changed

+131
-83
lines changed

src/Http/WebUtilities/perf/Microsoft.AspNetCore.WebUtilities.Performance/FormPipeReaderInternalsBenchmark.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ namespace Microsoft.AspNetCore.WebUtilities.Performance
1313
public class FormPipeReaderInternalsBenchmark
1414
{
1515
private byte[] _singleUtf8 = Encoding.UTF8.GetBytes("foo=bar&baz=boo&haha=hehe&lol=temp");
16-
private byte[] _firstUtf8 = Encoding.UTF8.GetBytes("foo=bar&baz=boo");
17-
private byte[] _secondUtf8 = Encoding.UTF8.GetBytes("&haha=hehe&lol=temp");
16+
private byte[] _firstUtf8 = Encoding.UTF8.GetBytes("foo=bar&baz=bo");
17+
private byte[] _secondUtf8 = Encoding.UTF8.GetBytes("o&haha=hehe&lol=temp");
1818
private FormPipeReader _formPipeReader;
1919

2020
[IterationSetup]

src/Http/WebUtilities/src/FormPipeReader.cs

Lines changed: 86 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Buffers;
66
using System.Collections.Generic;
7+
using System.Diagnostics;
78
using System.IO;
89
using System.IO.Pipelines;
910
using System.Runtime.CompilerServices;
@@ -139,74 +140,78 @@ private void ParseFormValuesFast(ReadOnlySpan<byte> span,
139140
bool isFinalBlock,
140141
out int consumed)
141142
{
142-
ReadOnlySpan<byte> key = default;
143-
ReadOnlySpan<byte> value = default;
143+
ReadOnlySpan<byte> key;
144+
ReadOnlySpan<byte> value;
144145
consumed = 0;
145146
var equalsDelimiter = GetEqualsForEncoding();
146147
var andDelimiter = GetAndForEncoding();
147148

148149
while (span.Length > 0)
149150
{
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;
151156

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
153164
{
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)
155168
{
156-
ThrowKeyTooLargeException();
169+
// Don't buffer indefinately
170+
if (span.Length > KeyLengthLimit + ValueLengthLimit)
171+
{
172+
ThrowKeyOrValueTooLargeException();
173+
}
174+
return;
157175
}
158-
break;
159-
}
160176

161-
if (equals > KeyLengthLimit)
162-
{
163-
ThrowKeyTooLargeException();
177+
keyValuePair = span;
178+
span = default;
179+
consumed += keyValuePair.Length;
164180
}
165181

166-
key = span.Slice(0, equals);
167-
168-
span = span.Slice(key.Length + equalsDelimiter.Length);
169-
value = span;
182+
equals = keyValuePair.IndexOf(equalsDelimiter);
170183

171-
var ampersand = span.IndexOf(andDelimiter);
172-
173-
if (ampersand == -1)
184+
if (equals == -1)
174185
{
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)
182188
{
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();
186190
}
187191

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;
191195
}
192196
else
193197
{
194-
if (ampersand > ValueLengthLimit)
198+
key = keyValuePair.Slice(0, equals);
199+
if (key.Length > KeyLengthLimit)
195200
{
196-
ThrowValueTooLargeException();
201+
ThrowKeyTooLargeException();
197202
}
198203

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+
}
201209
}
202210

203211
var decodedKey = GetDecodedString(key);
204212
var decodedValue = GetDecodedString(value);
205213

206214
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);
210215
}
211216
}
212217

@@ -217,53 +222,68 @@ private void ParseValuesSlow(
217222
bool isFinalBlock)
218223
{
219224
var sequenceReader = new SequenceReader<byte>(buffer);
225+
ReadOnlySequence<byte> keyValuePair;
226+
220227
var consumed = sequenceReader.Position;
221228
var consumedBytes = default(long);
222229
var equalsDelimiter = GetEqualsForEncoding();
223230
var andDelimiter = GetAndForEncoding();
224231

225232
while (!sequenceReader.End)
226233
{
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))
230235
{
231-
if ((sequenceReader.Consumed - consumedBytes) > KeyLengthLimit)
236+
if (!isFinalBlock)
232237
{
233-
ThrowKeyTooLargeException();
238+
// Don't buffer indefinately
239+
if ((sequenceReader.Consumed - consumedBytes) > KeyLengthLimit + ValueLengthLimit)
240+
{
241+
ThrowKeyOrValueTooLargeException();
242+
}
243+
break;
234244
}
235245

236-
break;
246+
// This must be the final key=value pair
247+
keyValuePair = buffer.Slice(sequenceReader.Position);
248+
sequenceReader.Advance(keyValuePair.Length);
237249
}
238250

239-
if (key.Length > KeyLengthLimit)
251+
if (keyValuePair.IsSingleSegment)
240252
{
241-
ThrowKeyTooLargeException();
253+
ParseFormValuesFast(keyValuePair.FirstSpan, ref accumulator, isFinalBlock: true, out var segmentConsumed);
254+
Debug.Assert(segmentConsumed == keyValuePair.FirstSpan.Length);
255+
continue;
242256
}
243257

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))
246262
{
247-
if (!isFinalBlock)
263+
if (key.Length > KeyLengthLimit)
248264
{
249-
if ((sequenceReader.Consumed - consumedBytes - key.Length) > ValueLengthLimit)
250-
{
251-
ThrowValueTooLargeException();
252-
}
253-
break;
265+
ThrowKeyTooLargeException();
254266
}
255267

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+
}
259273
}
260-
261-
if (value.Length > ValueLengthLimit)
274+
else
262275
{
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;
264285
}
265286

266-
// Need to call ToArray if the key/value spans multiple segments
267287
var decodedKey = GetDecodedStringFromReadOnlySequence(key);
268288
var decodedValue = GetDecodedStringFromReadOnlySequence(value);
269289

@@ -276,6 +296,11 @@ private void ParseValuesSlow(
276296
buffer = buffer.Slice(consumed);
277297
}
278298

299+
private void ThrowKeyOrValueTooLargeException()
300+
{
301+
throw new InvalidDataException($"Form key length limit {KeyLengthLimit} or value length limit {ValueLengthLimit} exceeded.");
302+
}
303+
279304
private void ThrowKeyTooLargeException()
280305
{
281306
throw new InvalidDataException($"Form key length limit {KeyLengthLimit} exceeded.");

0 commit comments

Comments
 (0)