Limit value number instead of key number in form reader
This commit is contained in:
parent
8212694874
commit
8b3c308c22
|
|
@ -148,7 +148,7 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
var encoding = FilterEncoding(contentType.Encoding);
|
var encoding = FilterEncoding(contentType.Encoding);
|
||||||
using (var formReader = new FormReader(_request.Body, encoding)
|
using (var formReader = new FormReader(_request.Body, encoding)
|
||||||
{
|
{
|
||||||
KeyCountLimit = _options.KeyCountLimit,
|
ValueCountLimit = _options.ValueCountLimit,
|
||||||
KeyLengthLimit = _options.KeyLengthLimit,
|
KeyLengthLimit = _options.KeyLengthLimit,
|
||||||
ValueLengthLimit = _options.ValueLengthLimit,
|
ValueLengthLimit = _options.ValueLengthLimit,
|
||||||
})
|
})
|
||||||
|
|
@ -200,9 +200,9 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
{
|
{
|
||||||
files = new FormFileCollection();
|
files = new FormFileCollection();
|
||||||
}
|
}
|
||||||
if (files.Count >= _options.KeyCountLimit)
|
if (files.Count >= _options.ValueCountLimit)
|
||||||
{
|
{
|
||||||
throw new InvalidDataException($"Form key count limit {_options.KeyCountLimit} exceeded.");
|
throw new InvalidDataException($"Form value count limit {_options.ValueCountLimit} exceeded.");
|
||||||
}
|
}
|
||||||
files.Add(file);
|
files.Add(file);
|
||||||
}
|
}
|
||||||
|
|
@ -222,9 +222,9 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
// The value length limit is enforced by MultipartBodyLengthLimit
|
// The value length limit is enforced by MultipartBodyLengthLimit
|
||||||
var value = await reader.ReadToEndAsync();
|
var value = await reader.ReadToEndAsync();
|
||||||
formAccumulator.Append(key, value);
|
formAccumulator.Append(key, value);
|
||||||
if (formAccumulator.Count > _options.KeyCountLimit)
|
if (formAccumulator.ValueCount > _options.ValueCountLimit)
|
||||||
{
|
{
|
||||||
throw new InvalidDataException($"Form key count limit {_options.KeyCountLimit} exceeded.");
|
throw new InvalidDataException($"Form value count limit {_options.ValueCountLimit} exceeded.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
public long BufferBodyLengthLimit { get; set; } = DefaultBufferBodyLengthLimit;
|
public long BufferBodyLengthLimit { get; set; } = DefaultBufferBodyLengthLimit;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A limit for the number of form entries to allow. Entries with the same key will be combined.
|
/// A limit for the number of form entries to allow.
|
||||||
/// Forms that exceed this limit will throw an <see cref="InvalidDataException"/> when parsed.
|
/// Forms that exceed this limit will throw an <see cref="InvalidDataException"/> when parsed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int KeyCountLimit { get; set; } = FormReader.DefaultKeyCountLimit;
|
public int ValueCountLimit { get; set; } = FormReader.DefaultValueCountLimit;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A limit on the length of individual keys. Forms containing keys that exceed this limit will
|
/// A limit on the length of individual keys. Forms containing keys that exceed this limit will
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FormReader : IDisposable
|
public class FormReader : IDisposable
|
||||||
{
|
{
|
||||||
public const int DefaultKeyCountLimit = 1024;
|
public const int DefaultValueCountLimit = 1024;
|
||||||
public const int DefaultKeyLengthLimit = 1024 * 2;
|
public const int DefaultKeyLengthLimit = 1024 * 2;
|
||||||
public const int DefaultValueLengthLimit = 1024 * 1024 * 4;
|
public const int DefaultValueLengthLimit = 1024 * 1024 * 4;
|
||||||
|
|
||||||
|
|
@ -78,9 +78,9 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The limit on the number of form keys to allow in ReadForm or ReadFormAsync.
|
/// The limit on the number of form values to allow in ReadForm or ReadFormAsync.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int KeyCountLimit { get; set; } = DefaultKeyCountLimit;
|
public int ValueCountLimit { get; set; } = DefaultValueCountLimit;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The limit on the length of form keys.
|
/// The limit on the length of form keys.
|
||||||
|
|
@ -293,9 +293,9 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
if (ReadSucceded())
|
if (ReadSucceded())
|
||||||
{
|
{
|
||||||
accumulator.Append(_currentKey, _currentValue);
|
accumulator.Append(_currentKey, _currentValue);
|
||||||
if (accumulator.Count > KeyCountLimit)
|
if (accumulator.ValueCount > ValueCountLimit)
|
||||||
{
|
{
|
||||||
throw new InvalidDataException($"Form key count limit {KeyCountLimit} exceeded.");
|
throw new InvalidDataException($"Form value count limit {ValueCountLimit} exceeded.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,11 +59,15 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
// First value for this key
|
// First value for this key
|
||||||
_accumulator[key] = new StringValues(value);
|
_accumulator[key] = new StringValues(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ValueCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasValues => _accumulator != null;
|
public bool HasValues => ValueCount > 0;
|
||||||
|
|
||||||
public int Count => _accumulator?.Count ?? 0;
|
public int KeyCount => _accumulator?.Count ?? 0;
|
||||||
|
|
||||||
|
public int ValueCount { get; private set; }
|
||||||
|
|
||||||
public Dictionary<string, StringValues> GetResults()
|
public Dictionary<string, StringValues> GetResults()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
var name = line.Substring(0, splitIndex);
|
var name = line.Substring(0, splitIndex);
|
||||||
var value = line.Substring(splitIndex + 1, line.Length - splitIndex - 1).Trim();
|
var value = line.Substring(splitIndex + 1, line.Length - splitIndex - 1).Trim();
|
||||||
accumulator.Append(name, value);
|
accumulator.Append(name, value);
|
||||||
if (accumulator.Count > HeadersCountLimit)
|
if (accumulator.KeyCount > HeadersCountLimit)
|
||||||
{
|
{
|
||||||
throw new InvalidDataException($"Multipart headers count limit {HeadersCountLimit} exceeded.");
|
throw new InvalidDataException($"Multipart headers count limit {HeadersCountLimit} exceeded.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http.Features
|
namespace Microsoft.AspNetCore.Http.Features
|
||||||
|
|
@ -48,33 +48,35 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string MultipartContentType = "multipart/form-data; boundary=WebKitFormBoundary5pDRpGheQXaM8k3T";
|
private const string MultipartContentType = "multipart/form-data; boundary=WebKitFormBoundary5pDRpGheQXaM8k3T";
|
||||||
private const string EmptyMultipartForm =
|
|
||||||
"--WebKitFormBoundary5pDRpGheQXaM8k3T--";
|
private const string EmptyMultipartForm = "--WebKitFormBoundary5pDRpGheQXaM8k3T--";
|
||||||
|
|
||||||
// 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.
|
// 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.
|
||||||
|
private const string MultipartFormEnd = "--WebKitFormBoundary5pDRpGheQXaM8k3T--\r\n";
|
||||||
|
|
||||||
|
private const string MultipartFormField = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
||||||
|
"Content-Disposition: form-data; name=\"description\"\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"Foo\r\n";
|
||||||
|
|
||||||
|
private const string MultipartFormFile = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
||||||
|
"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
|
||||||
|
"Content-Type: text/html\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"<html><body>Hello World</body></html>\r\n";
|
||||||
|
|
||||||
private const string MultipartFormWithField =
|
private const string MultipartFormWithField =
|
||||||
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
MultipartFormField +
|
||||||
"Content-Disposition: form-data; name=\"description\"\r\n" +
|
MultipartFormEnd;
|
||||||
"\r\n" +
|
|
||||||
"Foo\r\n" +
|
|
||||||
"--WebKitFormBoundary5pDRpGheQXaM8k3T--";
|
|
||||||
private const string MultipartFormWithFile =
|
private const string MultipartFormWithFile =
|
||||||
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
MultipartFormFile +
|
||||||
"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
|
MultipartFormEnd;
|
||||||
"Content-Type: text/html\r\n" +
|
|
||||||
"\r\n" +
|
|
||||||
"<html><body>Hello World</body></html>\r\n" +
|
|
||||||
"--WebKitFormBoundary5pDRpGheQXaM8k3T--";
|
|
||||||
private const string MultipartFormWithFieldAndFile =
|
private const string MultipartFormWithFieldAndFile =
|
||||||
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
MultipartFormField +
|
||||||
"Content-Disposition: form-data; name=\"description\"\r\n" +
|
MultipartFormFile +
|
||||||
"\r\n" +
|
MultipartFormEnd;
|
||||||
"Foo\r\n" +
|
|
||||||
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
|
||||||
"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
|
|
||||||
"Content-Type: text/html\r\n" +
|
|
||||||
"\r\n" +
|
|
||||||
"<html><body>Hello World</body></html>\r\n" +
|
|
||||||
"--WebKitFormBoundary5pDRpGheQXaM8k3T--";
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
|
|
@ -243,6 +245,55 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
await responseFeature.CompleteAsync();
|
await responseFeature.CompleteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public async Task ReadFormAsync_ValueCountLimitExceeded_Throw(bool bufferRequest)
|
||||||
|
{
|
||||||
|
var formContent = new List<byte>();
|
||||||
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormField));
|
||||||
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormField));
|
||||||
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormField));
|
||||||
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormEnd));
|
||||||
|
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
var responseFeature = new FakeResponseFeature();
|
||||||
|
context.Features.Set<IHttpResponseFeature>(responseFeature);
|
||||||
|
context.Request.ContentType = MultipartContentType;
|
||||||
|
context.Request.Body = new NonSeekableReadStream(formContent.ToArray());
|
||||||
|
|
||||||
|
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 });
|
||||||
|
context.Features.Set<IFormFeature>(formFeature);
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<InvalidDataException> (() => context.Request.ReadFormAsync());
|
||||||
|
Assert.Equal(exception.Message, "Form value count limit 2 exceeded.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public async Task ReadFormAsync_ValueCountLimitExceededWithFiles_Throw(bool bufferRequest)
|
||||||
|
{
|
||||||
|
var formContent = new List<byte>();
|
||||||
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFile));
|
||||||
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFile));
|
||||||
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFile));
|
||||||
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormEnd));
|
||||||
|
|
||||||
|
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
var responseFeature = new FakeResponseFeature();
|
||||||
|
context.Features.Set<IHttpResponseFeature>(responseFeature);
|
||||||
|
context.Request.ContentType = MultipartContentType;
|
||||||
|
context.Request.Body = new NonSeekableReadStream(formContent.ToArray());
|
||||||
|
|
||||||
|
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 });
|
||||||
|
context.Features.Set<IFormFeature>(formFeature);
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<InvalidDataException> (() => context.Request.ReadFormAsync());
|
||||||
|
Assert.Equal(exception.Message, "Form value count limit 2 exceeded.");
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
// FileBufferingReadStream transitions to disk storage after 30kb, and stops pooling buffers at 1mb.
|
// FileBufferingReadStream transitions to disk storage after 30kb, and stops pooling buffers at 1mb.
|
||||||
[InlineData(true, 1024)]
|
[InlineData(true, 1024)]
|
||||||
|
|
@ -313,10 +364,7 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
{
|
{
|
||||||
var stream = new MemoryStream();
|
var stream = new MemoryStream();
|
||||||
var header =
|
var header =
|
||||||
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
MultipartFormField +
|
||||||
"Content-Disposition: form-data; name=\"description\"\r\n" +
|
|
||||||
"\r\n" +
|
|
||||||
"Foo\r\n" +
|
|
||||||
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
||||||
"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
|
"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
|
||||||
"Content-Type: text/html\r\n" +
|
"Content-Type: text/html\r\n" +
|
||||||
|
|
|
||||||
|
|
@ -65,28 +65,40 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
[InlineData(false)]
|
[InlineData(false)]
|
||||||
public async Task ReadFormAsync_KeyCountLimitMet_Success(bool bufferRequest)
|
public async Task ReadFormAsync_ValueCountLimitMet_Success(bool bufferRequest)
|
||||||
{
|
{
|
||||||
var body = MakeStream(bufferRequest, "foo=1&bar=2&baz=3&baz=4");
|
var body = MakeStream(bufferRequest, "foo=1&bar=2&baz=3");
|
||||||
|
|
||||||
var formCollection = await ReadFormAsync(new FormReader(body) { KeyCountLimit = 3 });
|
var formCollection = await ReadFormAsync(new FormReader(body) { ValueCountLimit = 3 });
|
||||||
|
|
||||||
Assert.Equal("1", formCollection["foo"].ToString());
|
Assert.Equal("1", formCollection["foo"].ToString());
|
||||||
Assert.Equal("2", formCollection["bar"].ToString());
|
Assert.Equal("2", formCollection["bar"].ToString());
|
||||||
Assert.Equal("3,4", formCollection["baz"].ToString());
|
Assert.Equal("3", formCollection["baz"].ToString());
|
||||||
Assert.Equal(3, formCollection.Count);
|
Assert.Equal(3, formCollection.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
[InlineData(false)]
|
[InlineData(false)]
|
||||||
public async Task ReadFormAsync_KeyCountLimitExceeded_Throw(bool bufferRequest)
|
public async Task ReadFormAsync_ValueCountLimitExceeded_Throw(bool bufferRequest)
|
||||||
{
|
{
|
||||||
var body = MakeStream(bufferRequest, "foo=1&baz=2&bar=3&baz=4&baf=5");
|
var body = MakeStream(bufferRequest, "foo=1&baz=2&bar=3&baz=4&baf=5");
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<InvalidDataException>(
|
var exception = await Assert.ThrowsAsync<InvalidDataException>(
|
||||||
() => ReadFormAsync(new FormReader(body) { KeyCountLimit = 3 }));
|
() => ReadFormAsync(new FormReader(body) { ValueCountLimit = 3 }));
|
||||||
Assert.Equal("Form key count limit 3 exceeded.", exception.Message);
|
Assert.Equal("Form value count limit 3 exceeded.", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public async Task ReadFormAsync_ValueCountLimitExceededSameKey_Throw(bool bufferRequest)
|
||||||
|
{
|
||||||
|
var body = MakeStream(bufferRequest, "baz=1&baz=2&baz=3&baz=4");
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<InvalidDataException>(
|
||||||
|
() => ReadFormAsync(new FormReader(body) { ValueCountLimit = 3 }));
|
||||||
|
Assert.Equal("Form value count limit 3 exceeded.", exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue