# This is a combination of 2 commits.

# This is the 1st commit message:

#539 Implement request body size limit

# The commit message #2 will be skipped:

# Check exception messages
This commit is contained in:
Chris R 2017-06-23 14:11:51 -07:00
parent c13ba3ef0a
commit 7b15720a05
9 changed files with 609 additions and 76 deletions

View File

@ -28,7 +28,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
IHttpRequestLifetimeFeature,
IHttpAuthenticationFeature,
IHttpUpgradeFeature,
IHttpRequestIdentifierFeature
IHttpRequestIdentifierFeature,
IHttpMaxRequestBodySizeFeature
{
private RequestContext _requestContext;
private IFeatureCollection _features;
@ -62,11 +63,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
private bool _responseStarted;
private bool _completed;
internal FeatureContext(RequestContext requestContext, bool enableResponseCaching)
internal FeatureContext(RequestContext requestContext)
{
_requestContext = requestContext;
_features = new FeatureCollection(new StandardFeatureCollection(this));
_enableResponseCaching = enableResponseCaching;
_enableResponseCaching = _requestContext.Server.Options.EnableResponseCaching;
// Pre-initialize any fields that are not lazy at the lower level.
_requestHeaders = Request.Headers;
@ -78,7 +79,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
_scheme = Request.Scheme;
_user = _requestContext.User;
_responseStream = new ResponseStream(requestContext.Response.Body, OnStart);
_responseStream = new ResponseStream(requestContext.Response.Body, OnResponseStart);
_responseHeaders = Response.Headers;
}
@ -405,7 +406,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
async Task IHttpSendFileFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
{
await OnStart();
await OnResponseStart();
await Response.SendFileAsync(path, offset, length, cancellation);
}
@ -433,7 +434,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()
{
await OnStart();
await OnResponseStart();
return await _requestContext.UpgradeAsync();
}
@ -463,7 +464,15 @@ namespace Microsoft.AspNetCore.Server.HttpSys
}
}
internal async Task OnStart()
bool IHttpMaxRequestBodySizeFeature.IsReadOnly => Request.HasRequestBodyStarted;
long? IHttpMaxRequestBodySizeFeature.MaxRequestBodySize
{
get => Request.MaxRequestBodySize;
set => Request.MaxRequestBodySize = value;
}
internal async Task OnResponseStart()
{
if (_responseStarted)
{

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.HttpSys
{
@ -9,12 +10,16 @@ namespace Microsoft.AspNetCore.Server.HttpSys
{
private const long DefaultRequestQueueLength = 1000; // Http.sys default.
internal static readonly int DefaultMaxAccepts = 5 * Environment.ProcessorCount;
// Matches the default maxAllowedContentLength in IIS (~28.6 MB)
// https://www.iis.net/configreference/system.webserver/security/requestfiltering/requestlimits#005
private const long DefaultMaxRequestBodySize = 30000000;
// The native request queue
private long _requestQueueLength = DefaultRequestQueueLength;
private long? _maxConnections;
private RequestQueue _requestQueue;
private UrlGroup _urlGroup;
private long? _maxRequestBodySize = DefaultMaxRequestBodySize;
public HttpSysOptions()
{
@ -104,6 +109,28 @@ namespace Microsoft.AspNetCore.Server.HttpSys
}
}
/// <summary>
/// Gets or sets the maximum allowed size of any request body in bytes.
/// When set to null, the maximum request body size is unlimited.
/// This limit has no effect on upgraded connections which are always unlimited.
/// This can be overridden per-request via <see cref="IHttpMaxRequestBodySizeFeature"/>.
/// </summary>
/// <remarks>
/// Defaults to 30,000,000 bytes, which is approximately 28.6MB.
/// </remarks>
public long? MaxRequestBodySize
{
get => _maxRequestBodySize;
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, string.Empty);
}
_maxRequestBodySize = value;
}
}
internal void Apply(UrlGroup urlGroup, RequestQueue requestQueue)
{
_urlGroup = urlGroup;

View File

@ -56,14 +56,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
_processRequest = new Action<object>(ProcessRequestAsync);
_maxAccepts = _options.MaxAccepts;
EnableResponseCaching = _options.EnableResponseCaching;
_shutdownSignal = new TaskCompletionSource<object>();
}
internal HttpSysListener Listener { get; }
internal bool EnableResponseCaching { get; set; }
public IFeatureCollection Features { get; }
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
@ -199,12 +196,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys
Interlocked.Increment(ref _outstandingRequests);
try
{
var featureContext = new FeatureContext(requestContext, EnableResponseCaching);
var featureContext = new FeatureContext(requestContext);
context = _application.CreateContext(featureContext.Features);
try
{
await _application.ProcessRequestAsync(context).SupressContext();
await featureContext.OnStart();
await featureContext.OnResponseStart();
requestContext.Dispose();
_application.DisposeContext(context, null);
}

View File

@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
private BoundaryType _contentBoundaryType;
private long? _contentLength;
private Stream _nativeStream;
private RequestStream _nativeStream;
private SocketAddress _localEndPoint;
private SocketAddress _remoteEndPoint;
@ -143,15 +143,32 @@ namespace Microsoft.AspNetCore.Server.HttpSys
public string Method { get; }
public Stream Body
public Stream Body => EnsureRequestStream() ?? Stream.Null;
private RequestStream EnsureRequestStream()
{
get
if (_nativeStream == null && HasEntityBody)
{
if (_nativeStream == null)
_nativeStream = new RequestStream(RequestContext)
{
_nativeStream = HasEntityBody ? new RequestStream(RequestContext) : Stream.Null;
MaxSize = RequestContext.Server.Options.MaxRequestBodySize
};
}
return _nativeStream;
}
public bool HasRequestBodyStarted => _nativeStream?.HasStarted ?? false;
public long? MaxRequestBodySize
{
get => EnsureRequestStream()?.MaxSize;
set
{
EnsureRequestStream();
if (_nativeStream != null)
{
_nativeStream.MaxSize = value;
}
return _nativeStream;
}
}
@ -319,10 +336,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
internal void SwitchToOpaqueMode()
{
if (_nativeStream == null || _nativeStream == Stream.Null)
if (_nativeStream == null)
{
_nativeStream = new RequestStream(RequestContext);
}
_nativeStream.SwitchToOpaqueMode();
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
@ -17,6 +18,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
private RequestContext _requestContext;
private uint _dataChunkOffset;
private int _dataChunkIndex;
private long? _maxSize;
private long _totalRead;
private bool _closed;
internal RequestStream(RequestContext httpContext)
@ -35,68 +38,53 @@ namespace Microsoft.AspNetCore.Server.HttpSys
private ILogger Logger => RequestContext.Server.Logger;
public override bool CanSeek
public bool HasStarted { get; private set; }
public long? MaxSize
{
get
get => _maxSize;
set
{
return false;
if (HasStarted)
{
throw new InvalidOperationException("The maximum request size cannot be changed after the request body has started reading.");
}
if (value.HasValue && value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, string.Empty);
}
_maxSize = value;
}
}
public override bool CanWrite
{
get
{
return false;
}
}
public override bool CanSeek => false;
public override bool CanRead
{
get
{
return true;
}
}
public override bool CanWrite => false;
public override long Length
{
get
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
}
public override bool CanRead => true;
public override long Length => throw new NotSupportedException(Resources.Exception_NoSeek);
public override long Position
{
get
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
set
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
get => throw new NotSupportedException(Resources.Exception_NoSeek);
set => throw new NotSupportedException(Resources.Exception_NoSeek);
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
=> throw new NotSupportedException(Resources.Exception_NoSeek);
public override void SetLength(long value)
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
public override void SetLength(long value) => throw new NotSupportedException(Resources.Exception_NoSeek);
public override void Flush()
{
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
}
public override void Flush() => throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
public override Task FlushAsync(CancellationToken cancellationToken)
=> throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
internal void SwitchToOpaqueMode()
{
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
HasStarted = true;
_maxSize = null;
}
internal void Abort()
@ -124,6 +112,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
public override unsafe int Read([In, Out] byte[] buffer, int offset, int size)
{
ValidateReadBuffer(buffer, offset, size);
CheckSizeLimit();
if (_closed)
{
return 0;
@ -177,6 +166,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
}
UpdateAfterRead(statusCode, dataRead);
}
if (TryCheckSizeLimit((int)dataRead, out var ex))
{
throw ex;
}
// TODO: Verbose log dump data read
return (int)dataRead;
@ -193,6 +186,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
public override unsafe IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
{
ValidateReadBuffer(buffer, offset, size);
CheckSizeLimit();
if (_closed)
{
RequestStreamAsyncResult result = new RequestStreamAsyncResult(this, state, callback);
@ -295,7 +289,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
castedAsyncResult.EndCalled = true;
// wait & then check for errors
// Throws on failure
int dataRead = castedAsyncResult.Task.Result;
int dataRead = castedAsyncResult.Task.GetAwaiter().GetResult();
// TODO: Verbose log #dataRead.
return dataRead;
}
@ -303,6 +297,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
public override unsafe Task<int> ReadAsync(byte[] buffer, int offset, int size, CancellationToken cancellationToken)
{
ValidateReadBuffer(buffer, offset, size);
CheckSizeLimit();
if (_closed)
{
return Task.FromResult<int>(0);
@ -323,6 +318,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
if (_dataChunkIndex != -1 && dataRead == size)
{
UpdateAfterRead(UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS, dataRead);
if (TryCheckSizeLimit((int)dataRead, out var exception))
{
return Task.FromException<int>(exception);
}
// TODO: Verbose log #dataRead
return Task.FromResult<int>((int)dataRead);
}
@ -378,6 +377,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
{
uint totalRead = dataRead + bytesReturned;
UpdateAfterRead(statusCode, totalRead);
if (TryCheckSizeLimit((int)totalRead, out var exception))
{
return Task.FromException<int>(exception);
}
// TODO: Verbose log totalRead
return Task.FromResult<int>((int)totalRead);
}
@ -396,6 +399,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
asyncResult.Dispose();
uint totalRead = dataRead + bytesReturned;
UpdateAfterRead(statusCode, totalRead);
if (TryCheckSizeLimit((int)totalRead, out var exception))
{
return Task.FromException<int>(exception);
}
// TODO: Verbose log
return Task.FromResult<int>((int)totalRead);
}
@ -418,6 +425,40 @@ namespace Microsoft.AspNetCore.Server.HttpSys
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
}
// Called before each read
private void CheckSizeLimit()
{
// Note SwitchToOpaqueMode sets HasStarted and clears _maxSize, so these limits don't apply.
if (!HasStarted)
{
var contentLength = RequestContext.Request.ContentLength;
if (contentLength.HasValue && _maxSize.HasValue && contentLength.Value > _maxSize.Value)
{
throw new IOException(
$"The request's Content-Length {contentLength.Value} is larger than the request body size limit {_maxSize.Value}.");
}
HasStarted = true;
}
else if (TryCheckSizeLimit(0, out var exception))
{
throw exception;
}
}
// Called after each read.
internal bool TryCheckSizeLimit(int bytesRead, out Exception exception)
{
_totalRead += bytesRead;
if (_maxSize.HasValue && _totalRead > _maxSize.Value)
{
exception = new IOException($"The total number of bytes read {_totalRead} has exceeded the request body size limit {_maxSize.Value}.");
return true;
}
exception = null;
return false;
}
protected override void Dispose(bool disposing)
{
try

View File

@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
}
catch (Exception e)
{
asyncResult.Fail(e);
asyncResult.Fail(new IOException(string.Empty, e));
}
}
@ -112,7 +112,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
internal void Complete(int read, uint errorCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
if (_tcs.TrySetResult(read + (int)DataAlreadyRead))
if (_requestStream.TryCheckSizeLimit(read + (int)DataAlreadyRead, out var exception))
{
_tcs.TrySetException(exception);
}
else if (_tcs.TrySetResult(read + (int)DataAlreadyRead))
{
RequestStream.UpdateAfterRead((uint)errorCode, (uint)(read + DataAlreadyRead));
if (_callback != null)

View File

@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
{ typeof(IHttpAuthenticationFeature), _identityFunc },
{ typeof(IHttpRequestIdentifierFeature), _identityFunc },
{ typeof(RequestContext), ctx => ctx.RequestContext },
{ typeof(IHttpMaxRequestBodySizeFeature), _identityFunc },
};
private readonly FeatureContext _featureContext;

View File

@ -0,0 +1,422 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Server.HttpSys
{
public class RequestBodyLimitTests
{
[ConditionalFact]
public async Task ContentLengthEqualsLimit_ReadSync_Success()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Equal(11, httpContext.Request.ContentLength);
byte[] input = new byte[100];
int read = httpContext.Request.Body.Read(input, 0, input.Length);
httpContext.Response.ContentLength = read;
httpContext.Response.Body.Write(input, 0, read);
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address, "Hello World");
Assert.Equal("Hello World", response);
}
}
[ConditionalFact]
public async Task ContentLengthEqualsLimit_ReadAync_Success()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, async httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Equal(11, httpContext.Request.ContentLength);
byte[] input = new byte[100];
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
httpContext.Response.ContentLength = read;
await httpContext.Response.Body.WriteAsync(input, 0, read);
}))
{
var response = await SendRequestAsync(address, "Hello World");
Assert.Equal("Hello World", response);
}
}
[ConditionalFact]
public async Task ContentLengthEqualsLimit_ReadBeginEnd_Success()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Equal(11, httpContext.Request.ContentLength);
byte[] input = new byte[100];
int read = httpContext.Request.Body.EndRead(httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
httpContext.Response.ContentLength = read;
httpContext.Response.Body.EndWrite(httpContext.Response.Body.BeginWrite(input, 0, read, null, null));
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address, "Hello World");
Assert.Equal("Hello World", response);
}
}
[ConditionalFact]
public async Task ChunkedEqualsLimit_ReadSync_Success()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Null(httpContext.Request.ContentLength);
byte[] input = new byte[100];
int read = httpContext.Request.Body.Read(input, 0, input.Length);
httpContext.Response.ContentLength = read;
httpContext.Response.Body.Write(input, 0, read);
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address, "Hello World", chunked: true);
Assert.Equal("Hello World", response);
}
}
[ConditionalFact]
public async Task ChunkedEqualsLimit_ReadAync_Success()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, async httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Null(httpContext.Request.ContentLength);
byte[] input = new byte[100];
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
httpContext.Response.ContentLength = read;
await httpContext.Response.Body.WriteAsync(input, 0, read);
}))
{
var response = await SendRequestAsync(address, "Hello World", chunked: true);
Assert.Equal("Hello World", response);
}
}
[ConditionalFact]
public async Task ChunkedEqualsLimit_ReadBeginEnd_Success()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Null(httpContext.Request.ContentLength);
byte[] input = new byte[100];
int read = httpContext.Request.Body.EndRead(httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
httpContext.Response.ContentLength = read;
httpContext.Response.Body.EndWrite(httpContext.Response.Body.BeginWrite(input, 0, read, null, null));
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address, "Hello World", chunked: true);
Assert.Equal("Hello World", response);
}
}
[ConditionalFact]
public async Task ContentLengthExceedsLimit_ReadSync_ThrowsImmidately()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Equal(11, httpContext.Request.ContentLength);
byte[] input = new byte[100];
var ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address, "Hello World");
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact]
public async Task ContentLengthExceedsLimit_ReadAsync_ThrowsImmidately()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Equal(11, httpContext.Request.ContentLength);
byte[] input = new byte[100];
var ex = Assert.Throws<IOException>(() => { var t = httpContext.Request.Body.ReadAsync(input, 0, input.Length); });
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
ex = Assert.Throws<IOException>(() => { var t = httpContext.Request.Body.ReadAsync(input, 0, input.Length); });
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address, "Hello World");
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact]
public async Task ContentLengthExceedsLimit_ReadBeginEnd_ThrowsImmidately()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Equal(11, httpContext.Request.ContentLength);
byte[] input = new byte[100];
var ex = Assert.Throws<IOException>(() => httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
ex = Assert.Throws<IOException>(() => httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address, "Hello World");
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact]
public async Task ChunkedExceedsLimit_ReadSync_ThrowsAtLimit()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Null(httpContext.Request.ContentLength);
byte[] input = new byte[100];
var ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address, "Hello World", chunked: true);
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact]
public async Task ChunkedExceedsLimit_ReadAsync_ThrowsAtLimit()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, async httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Null(httpContext.Request.ContentLength);
byte[] input = new byte[100];
var ex = await Assert.ThrowsAsync<IOException>(() => httpContext.Request.Body.ReadAsync(input, 0, input.Length));
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
ex = await Assert.ThrowsAsync<IOException>(() => httpContext.Request.Body.ReadAsync(input, 0, input.Length));
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
}))
{
var response = await SendRequestAsync(address, "Hello World", chunked: true);
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact]
public async Task ChunkedExceedsLimit_ReadBeginEnd_ThrowsAtLimit()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Null(httpContext.Request.ContentLength);
byte[] input = new byte[100];
var body = httpContext.Request.Body;
var ex = Assert.Throws<IOException>(() => body.EndRead(body.BeginRead(input, 0, input.Length, null, null)));
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
ex = Assert.Throws<IOException>(() => body.EndRead(body.BeginRead(input, 0, input.Length, null, null)));
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address, "Hello World", chunked: true);
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact]
public async Task Chunked_ReadSyncPartialBodyUnderLimit_ThrowsAfterLimit()
{
var content = new StaggardContent();
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Null(httpContext.Request.ContentLength);
byte[] input = new byte[100];
int read = httpContext.Request.Body.Read(input, 0, input.Length);
Assert.Equal(10, read);
content.Block.Release();
var ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
Assert.Equal("The total number of bytes read 20 has exceeded the request body size limit 10.", ex.Message);
return Task.FromResult(0);
}))
{
string response = await SendRequestAsync(address, content, chunked: true);
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact]
public async Task Chunked_ReadAsyncPartialBodyUnderLimit_ThrowsAfterLimit()
{
var content = new StaggardContent();
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, async httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Null(httpContext.Request.ContentLength);
byte[] input = new byte[100];
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
Assert.Equal(10, read);
content.Block.Release();
var ex = await Assert.ThrowsAsync<IOException>(() => httpContext.Request.Body.ReadAsync(input, 0, input.Length));
Assert.Equal("The total number of bytes read 20 has exceeded the request body size limit 10.", ex.Message);
}))
{
string response = await SendRequestAsync(address, content, chunked: true);
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact]
public async Task AdjustLimitPerRequest_ContentLength_ReadAync_Success()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, async httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Equal(11, feature.MaxRequestBodySize);
feature.MaxRequestBodySize = 12;
Assert.Equal(12, httpContext.Request.ContentLength);
byte[] input = new byte[100];
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
Assert.True(feature.IsReadOnly);
httpContext.Response.ContentLength = read;
await httpContext.Response.Body.WriteAsync(input, 0, read);
}))
{
var response = await SendRequestAsync(address, "Hello World!");
Assert.Equal("Hello World!", response);
}
}
[ConditionalFact]
public async Task AdjustLimitPerRequest_Chunked_ReadAync_Success()
{
string address;
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, async httpContext =>
{
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
Assert.NotNull(feature);
Assert.False(feature.IsReadOnly);
Assert.Equal(11, feature.MaxRequestBodySize);
feature.MaxRequestBodySize = 12;
Assert.Null(httpContext.Request.ContentLength);
byte[] input = new byte[100];
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
Assert.True(feature.IsReadOnly);
httpContext.Response.ContentLength = read;
await httpContext.Response.Body.WriteAsync(input, 0, read);
}))
{
var response = await SendRequestAsync(address, "Hello World!", chunked: true);
Assert.Equal("Hello World!", response);
}
}
private Task<string> SendRequestAsync(string uri, string upload, bool chunked = false)
{
return SendRequestAsync(uri, new StringContent(upload), chunked);
}
private async Task<string> SendRequestAsync(string uri, HttpContent content, bool chunked = false)
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.TransferEncodingChunked = chunked;
HttpResponseMessage response = await client.PostAsync(uri, content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
private class StaggardContent : HttpContent
{
public StaggardContent()
{
Block = new SemaphoreSlim(0, 1);
}
public SemaphoreSlim Block { get; private set; }
protected async override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
await stream.WriteAsync(new byte[10], 0, 10);
Assert.True(await Block.WaitAsync(TimeSpan.FromSeconds(10)));
await stream.WriteAsync(new byte[10], 0, 10);
}
protected override bool TryComputeLength(out long length)
{
length = 10;
return true;
}
}
}
}

View File

@ -27,25 +27,41 @@ namespace Microsoft.AspNetCore.Server.HttpSys
internal static IServer CreateHttpServer(out string baseAddress, RequestDelegate app)
{
string root;
return CreateDynamicHttpServer(string.Empty, AuthenticationSchemes.None, true, out root, out baseAddress, app);
return CreateDynamicHttpServer(string.Empty, out root, out baseAddress, options => { }, app);
}
internal static IServer CreateHttpServer(out string baseAddress, Action<HttpSysOptions> configureOptions, RequestDelegate app)
{
string root;
return CreateDynamicHttpServer(string.Empty, out root, out baseAddress, configureOptions, app);
}
internal static IServer CreateHttpServerReturnRoot(string path, out string root, RequestDelegate app)
{
string baseAddress;
return CreateDynamicHttpServer(path, AuthenticationSchemes.None, true, out root, out baseAddress, app);
return CreateDynamicHttpServer(path, out root, out baseAddress, options => { }, app);
}
internal static IServer CreateHttpAuthServer(AuthenticationSchemes authType, bool allowAnonymous, out string baseAddress, RequestDelegate app)
{
string root;
return CreateDynamicHttpServer(string.Empty, authType, allowAnonymous, out root, out baseAddress, app);
return CreateDynamicHttpServer(string.Empty, out root, out baseAddress, options =>
{
options.Authentication.Schemes = authType;
options.Authentication.AllowAnonymous = allowAnonymous;
}, app);
}
internal static IWebHost CreateDynamicHost(AuthenticationSchemes authType, bool allowAnonymous, out string root, RequestDelegate app)
=> CreateDynamicHost(string.Empty, authType, allowAnonymous, out root, out var baseAddress, app);
{
return CreateDynamicHost(string.Empty, out root, out var baseAddress, options =>
{
options.Authentication.Schemes = authType;
options.Authentication.AllowAnonymous = allowAnonymous;
}, app);
}
internal static IWebHost CreateDynamicHost(string basePath, AuthenticationSchemes authType, bool allowAnonymous, out string root, out string baseAddress, RequestDelegate app)
internal static IWebHost CreateDynamicHost(string basePath, out string root, out string baseAddress, Action<HttpSysOptions> configureOptions, RequestDelegate app)
{
lock (PortLock)
{
@ -60,8 +76,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
.UseHttpSys(options =>
{
options.UrlPrefixes.Add(prefix);
options.Authentication.Schemes = authType;
options.Authentication.AllowAnonymous = allowAnonymous;
configureOptions(options);
})
.Configure(appBuilder => appBuilder.Run(app));
@ -86,7 +101,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
internal static MessagePump CreatePump()
=> new MessagePump(Options.Create(new HttpSysOptions()), new LoggerFactory(), new AuthenticationSchemeProvider(Options.Create(new AuthenticationOptions())));
internal static IServer CreateDynamicHttpServer(string basePath, AuthenticationSchemes authType, bool allowAnonymous, out string root, out string baseAddress, RequestDelegate app)
internal static IServer CreateDynamicHttpServer(string basePath, out string root, out string baseAddress, Action<HttpSysOptions> configureOptions, RequestDelegate app)
{
lock (PortLock)
{
@ -100,8 +115,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
var server = CreatePump();
server.Features.Get<IServerAddressesFeature>().Addresses.Add(baseAddress);
server.Listener.Options.Authentication.Schemes = authType;
server.Listener.Options.Authentication.AllowAnonymous = allowAnonymous;
configureOptions(server.Listener.Options);
try
{
server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait();