Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Commit 8b3c308

Browse files
committed
Limit value number instead of key number in form reader
1 parent 8212694 commit 8b3c308

File tree

7 files changed

+111
-47
lines changed

7 files changed

+111
-47
lines changed

src/Microsoft.AspNetCore.Http/Features/FormFeature.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancell
148148
var encoding = FilterEncoding(contentType.Encoding);
149149
using (var formReader = new FormReader(_request.Body, encoding)
150150
{
151-
KeyCountLimit = _options.KeyCountLimit,
151+
ValueCountLimit = _options.ValueCountLimit,
152152
KeyLengthLimit = _options.KeyLengthLimit,
153153
ValueLengthLimit = _options.ValueLengthLimit,
154154
})
@@ -200,9 +200,9 @@ private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancell
200200
{
201201
files = new FormFileCollection();
202202
}
203-
if (files.Count >= _options.KeyCountLimit)
203+
if (files.Count >= _options.ValueCountLimit)
204204
{
205-
throw new InvalidDataException($"Form key count limit {_options.KeyCountLimit} exceeded.");
205+
throw new InvalidDataException($"Form value count limit {_options.ValueCountLimit} exceeded.");
206206
}
207207
files.Add(file);
208208
}
@@ -222,9 +222,9 @@ private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancell
222222
// The value length limit is enforced by MultipartBodyLengthLimit
223223
var value = await reader.ReadToEndAsync();
224224
formAccumulator.Append(key, value);
225-
if (formAccumulator.Count > _options.KeyCountLimit)
225+
if (formAccumulator.ValueCount > _options.ValueCountLimit)
226226
{
227-
throw new InvalidDataException($"Form key count limit {_options.KeyCountLimit} exceeded.");
227+
throw new InvalidDataException($"Form value count limit {_options.ValueCountLimit} exceeded.");
228228
}
229229
}
230230
}

src/Microsoft.AspNetCore.Http/Features/FormOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ public class FormOptions
3333
public long BufferBodyLengthLimit { get; set; } = DefaultBufferBodyLengthLimit;
3434

3535
/// <summary>
36-
/// A limit for the number of form entries to allow. Entries with the same key will be combined.
36+
/// A limit for the number of form entries to allow.
3737
/// Forms that exceed this limit will throw an <see cref="InvalidDataException"/> when parsed.
3838
/// </summary>
39-
public int KeyCountLimit { get; set; } = FormReader.DefaultKeyCountLimit;
39+
public int ValueCountLimit { get; set; } = FormReader.DefaultValueCountLimit;
4040

4141
/// <summary>
4242
/// A limit on the length of individual keys. Forms containing keys that exceed this limit will

src/Microsoft.AspNetCore.WebUtilities/FormReader.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.WebUtilities
1717
/// </summary>
1818
public class FormReader : IDisposable
1919
{
20-
public const int DefaultKeyCountLimit = 1024;
20+
public const int DefaultValueCountLimit = 1024;
2121
public const int DefaultKeyLengthLimit = 1024 * 2;
2222
public const int DefaultValueLengthLimit = 1024 * 1024 * 4;
2323

@@ -78,9 +78,9 @@ public FormReader(Stream stream, Encoding encoding, ArrayPool<char> charPool)
7878
}
7979

8080
/// <summary>
81-
/// The limit on the number of form keys to allow in ReadForm or ReadFormAsync.
81+
/// The limit on the number of form values to allow in ReadForm or ReadFormAsync.
8282
/// </summary>
83-
public int KeyCountLimit { get; set; } = DefaultKeyCountLimit;
83+
public int ValueCountLimit { get; set; } = DefaultValueCountLimit;
8484

8585
/// <summary>
8686
/// The limit on the length of form keys.
@@ -293,9 +293,9 @@ private void Append(ref KeyValueAccumulator accumulator)
293293
if (ReadSucceded())
294294
{
295295
accumulator.Append(_currentKey, _currentValue);
296-
if (accumulator.Count > KeyCountLimit)
296+
if (accumulator.ValueCount > ValueCountLimit)
297297
{
298-
throw new InvalidDataException($"Form key count limit {KeyCountLimit} exceeded.");
298+
throw new InvalidDataException($"Form value count limit {ValueCountLimit} exceeded.");
299299
}
300300
}
301301
}

src/Microsoft.AspNetCore.WebUtilities/KeyValueAccumulator.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,15 @@ public void Append(string key, string value)
5959
// First value for this key
6060
_accumulator[key] = new StringValues(value);
6161
}
62+
63+
ValueCount++;
6264
}
6365

64-
public bool HasValues => _accumulator != null;
66+
public bool HasValues => ValueCount > 0;
67+
68+
public int KeyCount => _accumulator?.Count ?? 0;
6569

66-
public int Count => _accumulator?.Count ?? 0;
70+
public int ValueCount { get; private set; }
6771

6872
public Dictionary<string, StringValues> GetResults()
6973
{

src/Microsoft.AspNetCore.WebUtilities/MultipartReader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private async Task<Dictionary<string, StringValues>> ReadHeadersAsync(Cancellati
104104
var name = line.Substring(0, splitIndex);
105105
var value = line.Substring(splitIndex + 1, line.Length - splitIndex - 1).Trim();
106106
accumulator.Append(name, value);
107-
if (accumulator.Count > HeadersCountLimit)
107+
if (accumulator.KeyCount > HeadersCountLimit)
108108
{
109109
throw new InvalidDataException($"Multipart headers count limit {HeadersCountLimit} exceeded.");
110110
}

test/Microsoft.AspNetCore.Http.Tests/Features/FormFeatureTests.cs

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.IO;
67
using System.Text;
78
using System.Threading.Tasks;
8-
using Microsoft.AspNetCore.Http.Internal;
99
using Xunit;
1010

1111
namespace Microsoft.AspNetCore.Http.Features
@@ -48,33 +48,35 @@ public async Task ReadFormAsync_SimpleData_ReturnsParsedFormCollection(bool buff
4848
}
4949

5050
private const string MultipartContentType = "multipart/form-data; boundary=WebKitFormBoundary5pDRpGheQXaM8k3T";
51-
private const string EmptyMultipartForm =
52-
"--WebKitFormBoundary5pDRpGheQXaM8k3T--";
51+
52+
private const string EmptyMultipartForm = "--WebKitFormBoundary5pDRpGheQXaM8k3T--";
53+
5354
// Note that CRLF (\r\n) is required. You can't use multi-line C# strings here because the line breaks on Linux are just LF.
54-
private const string MultipartFormWithField =
55-
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
55+
private const string MultipartFormEnd = "--WebKitFormBoundary5pDRpGheQXaM8k3T--\r\n";
56+
57+
private const string MultipartFormField = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
5658
"Content-Disposition: form-data; name=\"description\"\r\n" +
5759
"\r\n" +
58-
"Foo\r\n" +
59-
"--WebKitFormBoundary5pDRpGheQXaM8k3T--";
60-
private const string MultipartFormWithFile =
61-
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
60+
"Foo\r\n";
61+
62+
private const string MultipartFormFile = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
6263
"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
6364
"Content-Type: text/html\r\n" +
6465
"\r\n" +
65-
"<html><body>Hello World</body></html>\r\n" +
66-
"--WebKitFormBoundary5pDRpGheQXaM8k3T--";
66+
"<html><body>Hello World</body></html>\r\n";
67+
68+
private const string MultipartFormWithField =
69+
MultipartFormField +
70+
MultipartFormEnd;
71+
72+
private const string MultipartFormWithFile =
73+
MultipartFormFile +
74+
MultipartFormEnd;
75+
6776
private const string MultipartFormWithFieldAndFile =
68-
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
69-
"Content-Disposition: form-data; name=\"description\"\r\n" +
70-
"\r\n" +
71-
"Foo\r\n" +
72-
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
73-
"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
74-
"Content-Type: text/html\r\n" +
75-
"\r\n" +
76-
"<html><body>Hello World</body></html>\r\n" +
77-
"--WebKitFormBoundary5pDRpGheQXaM8k3T--";
77+
MultipartFormField +
78+
MultipartFormFile +
79+
MultipartFormEnd;
7880

7981
[Theory]
8082
[InlineData(true)]
@@ -243,6 +245,55 @@ public async Task ReadFormAsync_MultipartWithFieldAndFile_ReturnsParsedFormColle
243245
await responseFeature.CompleteAsync();
244246
}
245247

248+
[Theory]
249+
[InlineData(true)]
250+
[InlineData(false)]
251+
public async Task ReadFormAsync_ValueCountLimitExceeded_Throw(bool bufferRequest)
252+
{
253+
var formContent = new List<byte>();
254+
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormField));
255+
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormField));
256+
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormField));
257+
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormEnd));
258+
259+
var context = new DefaultHttpContext();
260+
var responseFeature = new FakeResponseFeature();
261+
context.Features.Set<IHttpResponseFeature>(responseFeature);
262+
context.Request.ContentType = MultipartContentType;
263+
context.Request.Body = new NonSeekableReadStream(formContent.ToArray());
264+
265+
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 });
266+
context.Features.Set<IFormFeature>(formFeature);
267+
268+
var exception = await Assert.ThrowsAsync<InvalidDataException> (() => context.Request.ReadFormAsync());
269+
Assert.Equal(exception.Message, "Form value count limit 2 exceeded.");
270+
}
271+
272+
[Theory]
273+
[InlineData(true)]
274+
[InlineData(false)]
275+
public async Task ReadFormAsync_ValueCountLimitExceededWithFiles_Throw(bool bufferRequest)
276+
{
277+
var formContent = new List<byte>();
278+
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFile));
279+
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFile));
280+
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFile));
281+
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormEnd));
282+
283+
284+
var context = new DefaultHttpContext();
285+
var responseFeature = new FakeResponseFeature();
286+
context.Features.Set<IHttpResponseFeature>(responseFeature);
287+
context.Request.ContentType = MultipartContentType;
288+
context.Request.Body = new NonSeekableReadStream(formContent.ToArray());
289+
290+
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 });
291+
context.Features.Set<IFormFeature>(formFeature);
292+
293+
var exception = await Assert.ThrowsAsync<InvalidDataException> (() => context.Request.ReadFormAsync());
294+
Assert.Equal(exception.Message, "Form value count limit 2 exceeded.");
295+
}
296+
246297
[Theory]
247298
// FileBufferingReadStream transitions to disk storage after 30kb, and stops pooling buffers at 1mb.
248299
[InlineData(true, 1024)]
@@ -313,10 +364,7 @@ private Stream CreateMultipartWithFormAndFile(Stream fileContents)
313364
{
314365
var stream = new MemoryStream();
315366
var header =
316-
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
317-
"Content-Disposition: form-data; name=\"description\"\r\n" +
318-
"\r\n" +
319-
"Foo\r\n" +
367+
MultipartFormField +
320368
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
321369
"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
322370
"Content-Type: text/html\r\n" +

test/Microsoft.AspNetCore.WebUtilities.Tests/FormReaderTests.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,28 +65,40 @@ public async Task ReadFormAsync_EmptyValuedWithAdditionalEntryAllowed(bool buffe
6565
[Theory]
6666
[InlineData(true)]
6767
[InlineData(false)]
68-
public async Task ReadFormAsync_KeyCountLimitMet_Success(bool bufferRequest)
68+
public async Task ReadFormAsync_ValueCountLimitMet_Success(bool bufferRequest)
6969
{
70-
var body = MakeStream(bufferRequest, "foo=1&bar=2&baz=3&baz=4");
70+
var body = MakeStream(bufferRequest, "foo=1&bar=2&baz=3");
7171

72-
var formCollection = await ReadFormAsync(new FormReader(body) { KeyCountLimit = 3 });
72+
var formCollection = await ReadFormAsync(new FormReader(body) { ValueCountLimit = 3 });
7373

7474
Assert.Equal("1", formCollection["foo"].ToString());
7575
Assert.Equal("2", formCollection["bar"].ToString());
76-
Assert.Equal("3,4", formCollection["baz"].ToString());
76+
Assert.Equal("3", formCollection["baz"].ToString());
7777
Assert.Equal(3, formCollection.Count);
7878
}
7979

8080
[Theory]
8181
[InlineData(true)]
8282
[InlineData(false)]
83-
public async Task ReadFormAsync_KeyCountLimitExceeded_Throw(bool bufferRequest)
83+
public async Task ReadFormAsync_ValueCountLimitExceeded_Throw(bool bufferRequest)
8484
{
8585
var body = MakeStream(bufferRequest, "foo=1&baz=2&bar=3&baz=4&baf=5");
8686

8787
var exception = await Assert.ThrowsAsync<InvalidDataException>(
88-
() => ReadFormAsync(new FormReader(body) { KeyCountLimit = 3 }));
89-
Assert.Equal("Form key count limit 3 exceeded.", exception.Message);
88+
() => ReadFormAsync(new FormReader(body) { ValueCountLimit = 3 }));
89+
Assert.Equal("Form value count limit 3 exceeded.", exception.Message);
90+
}
91+
92+
[Theory]
93+
[InlineData(true)]
94+
[InlineData(false)]
95+
public async Task ReadFormAsync_ValueCountLimitExceededSameKey_Throw(bool bufferRequest)
96+
{
97+
var body = MakeStream(bufferRequest, "baz=1&baz=2&baz=3&baz=4");
98+
99+
var exception = await Assert.ThrowsAsync<InvalidDataException>(
100+
() => ReadFormAsync(new FormReader(body) { ValueCountLimit = 3 }));
101+
Assert.Equal("Form value count limit 3 exceeded.", exception.Message);
90102
}
91103

92104
[Theory]

0 commit comments

Comments
 (0)