Merge branch 'rel/2.0.0-preview2' into dev
This commit is contained in:
commit
c6df7eb644
|
|
@ -71,6 +71,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
case RequestRejectionReason.TooManyHeaders:
|
||||
ex = new BadHttpRequestException(CoreStrings.BadRequest_TooManyHeaders, StatusCodes.Status431RequestHeaderFieldsTooLarge);
|
||||
break;
|
||||
case RequestRejectionReason.RequestBodyTooLarge:
|
||||
ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestBodyTooLarge, StatusCodes.Status413PayloadTooLarge);
|
||||
break;
|
||||
case RequestRejectionReason.RequestTimeout:
|
||||
ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestTimeout, StatusCodes.Status408RequestTimeout);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -318,4 +318,13 @@
|
|||
<data name="UpgradeCannotBeCalledMultipleTimes" xml:space="preserve">
|
||||
<value>IHttpUpgradeFeature.UpgradeAsync was already called and can only be called once per connection.</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="BadRequest_RequestBodyTooLarge" xml:space="preserve">
|
||||
<value>Request body too large.</value>
|
||||
</data>
|
||||
<data name="MaxRequestBodySizeCannotBeModifiedAfterRead" xml:space="preserve">
|
||||
<value>The maximum request body size cannot be modified after the app has already started reading from the request body.</value>
|
||||
</data>
|
||||
<data name="MaxRequestBodySizeCannotBeModifiedForUpgradedRequests" xml:space="preserve">
|
||||
<value>The maximum request body size cannot be modified after the request has been upgraded.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -20,7 +20,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
IHttpUpgradeFeature,
|
||||
IHttpConnectionFeature,
|
||||
IHttpRequestLifetimeFeature,
|
||||
IHttpRequestIdentifierFeature
|
||||
IHttpRequestIdentifierFeature,
|
||||
IHttpMaxRequestBodySizeFeature
|
||||
{
|
||||
// NOTE: When feature interfaces are added to or removed from this Frame class implementation,
|
||||
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
|
||||
|
|
@ -202,6 +203,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
set => TraceIdentifier = value;
|
||||
}
|
||||
|
||||
bool IHttpMaxRequestBodySizeFeature.IsReadOnly => HasStartedConsumingRequestBody || _wasUpgraded;
|
||||
|
||||
long? IHttpMaxRequestBodySizeFeature.MaxRequestBodySize
|
||||
{
|
||||
get => MaxRequestBodySize;
|
||||
set
|
||||
{
|
||||
if (HasStartedConsumingRequestBody)
|
||||
{
|
||||
throw new InvalidOperationException(CoreStrings.MaxRequestBodySizeCannotBeModifiedAfterRead);
|
||||
}
|
||||
if (_wasUpgraded)
|
||||
{
|
||||
throw new InvalidOperationException(CoreStrings.MaxRequestBodySizeCannotBeModifiedForUpgradedRequests);
|
||||
}
|
||||
if (value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.NonNegativeNumberOrNullRequired);
|
||||
}
|
||||
|
||||
MaxRequestBodySize = value;
|
||||
}
|
||||
}
|
||||
|
||||
object IFeatureCollection.this[Type key]
|
||||
{
|
||||
get => FastFeatureGet(key);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
private static readonly Type ITlsConnectionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature);
|
||||
private static readonly Type IHttpWebSocketFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature);
|
||||
private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
|
||||
private static readonly Type IHttpMaxRequestBodySizeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature);
|
||||
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
||||
|
||||
private object _currentIHttpRequestFeature;
|
||||
|
|
@ -40,6 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
private object _currentITlsConnectionFeature;
|
||||
private object _currentIHttpWebSocketFeature;
|
||||
private object _currentISessionFeature;
|
||||
private object _currentIHttpMaxRequestBodySizeFeature;
|
||||
private object _currentIHttpSendFileFeature;
|
||||
|
||||
private void FastReset()
|
||||
|
|
@ -50,6 +52,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_currentIHttpRequestIdentifierFeature = this;
|
||||
_currentIHttpRequestLifetimeFeature = this;
|
||||
_currentIHttpConnectionFeature = this;
|
||||
_currentIHttpMaxRequestBodySizeFeature = this;
|
||||
|
||||
_currentIServiceProvidersFeature = null;
|
||||
_currentIHttpAuthenticationFeature = null;
|
||||
|
|
@ -125,6 +128,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
return _currentISessionFeature;
|
||||
}
|
||||
if (key == IHttpMaxRequestBodySizeFeatureType)
|
||||
{
|
||||
return _currentIHttpMaxRequestBodySizeFeature;
|
||||
}
|
||||
if (key == IHttpSendFileFeatureType)
|
||||
{
|
||||
return _currentIHttpSendFileFeature;
|
||||
|
|
@ -211,6 +218,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_currentISessionFeature = feature;
|
||||
return;
|
||||
}
|
||||
if (key == IHttpMaxRequestBodySizeFeatureType)
|
||||
{
|
||||
_currentIHttpMaxRequestBodySizeFeature = feature;
|
||||
return;
|
||||
}
|
||||
if (key == IHttpSendFileFeatureType)
|
||||
{
|
||||
_currentIHttpSendFileFeature = feature;
|
||||
|
|
@ -281,6 +293,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
yield return new KeyValuePair<Type, object>(ISessionFeatureType, _currentISessionFeature as global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
|
||||
}
|
||||
if (_currentIHttpMaxRequestBodySizeFeature != null)
|
||||
{
|
||||
yield return new KeyValuePair<Type, object>(IHttpMaxRequestBodySizeFeatureType, _currentIHttpMaxRequestBodySizeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature);
|
||||
}
|
||||
if (_currentIHttpSendFileFeature != null)
|
||||
{
|
||||
yield return new KeyValuePair<Type, object>(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
protected string ConnectionId => _frameContext.ConnectionId;
|
||||
|
||||
public string ConnectionIdFeature { get; set; }
|
||||
public bool HasStartedConsumingRequestBody { get; set; }
|
||||
public long? MaxRequestBodySize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The request id. <seealso cref="HttpContext.TraceIdentifier"/>
|
||||
|
|
@ -310,8 +312,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
public void PauseStreams() => _frameStreams.Pause();
|
||||
|
||||
public void ResumeStreams() => _frameStreams.Resume();
|
||||
|
||||
public void StopStreams() => _frameStreams.Stop();
|
||||
|
||||
public void Reset()
|
||||
|
|
@ -326,6 +326,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
ResetFeatureCollection();
|
||||
|
||||
HasStartedConsumingRequestBody = false;
|
||||
MaxRequestBodySize = ServerOptions.Limits.MaxRequestBodySize;
|
||||
TraceIdentifier = null;
|
||||
Scheme = null;
|
||||
Method = null;
|
||||
|
|
@ -368,7 +370,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_manuallySetRequestAbortToken = null;
|
||||
_abortedCts = null;
|
||||
|
||||
// Allow to bytes for \r\n after headers
|
||||
// Allow two bytes for \r\n after headers
|
||||
_remainingRequestHeadersBytesAllowed = ServerOptions.Limits.MaxRequestHeadersTotalSize + 2;
|
||||
_requestHeadersParsed = 0;
|
||||
|
||||
|
|
@ -949,7 +951,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
var result = _parser.ParseRequestLine(new FrameAdapter(this), buffer, out consumed, out examined);
|
||||
if (!result && overLength)
|
||||
{
|
||||
RejectRequest(RequestRejectionReason.RequestLineTooLong);
|
||||
ThrowRequestRejected(RequestRejectionReason.RequestLineTooLong);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -973,7 +975,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
if (!result && overLength)
|
||||
{
|
||||
RejectRequest(RequestRejectionReason.HeadersExceedMaxTotalSize);
|
||||
ThrowRequestRejected(RequestRejectionReason.HeadersExceedMaxTotalSize);
|
||||
}
|
||||
if (result)
|
||||
{
|
||||
|
|
@ -1059,13 +1061,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
throw new ObjectDisposedException(CoreStrings.UnhandledApplicationException, _applicationException);
|
||||
}
|
||||
|
||||
public void RejectRequest(RequestRejectionReason reason)
|
||||
public void ThrowRequestRejected(RequestRejectionReason reason)
|
||||
=> throw BadHttpRequestException.GetException(reason);
|
||||
|
||||
public void RejectRequest(RequestRejectionReason reason, string detail)
|
||||
public void ThrowRequestRejected(RequestRejectionReason reason, string detail)
|
||||
=> throw BadHttpRequestException.GetException(reason, detail);
|
||||
|
||||
private void RejectRequestTarget(Span<byte> target)
|
||||
private void ThrowRequestTargetRejected(Span<byte> target)
|
||||
=> throw GetInvalidRequestTargetException(target);
|
||||
|
||||
private BadHttpRequestException GetInvalidRequestTargetException(Span<byte> target)
|
||||
|
|
@ -1207,7 +1209,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
RejectRequestTarget(target);
|
||||
ThrowRequestTargetRejected(target);
|
||||
}
|
||||
|
||||
QueryString = query.GetAsciiStringNonNullCharacters();
|
||||
|
|
@ -1226,7 +1228,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
var ch = target[i];
|
||||
if (!UriUtilities.IsValidAuthorityCharacter(ch))
|
||||
{
|
||||
RejectRequestTarget(target);
|
||||
ThrowRequestTargetRejected(target);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1234,7 +1236,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
// requests (https://tools.ietf.org/html/rfc7231#section-4.3.6).
|
||||
if (method != HttpMethod.Connect)
|
||||
{
|
||||
RejectRequest(RequestRejectionReason.ConnectMethodRequired);
|
||||
ThrowRequestRejected(RequestRejectionReason.ConnectMethodRequired);
|
||||
}
|
||||
|
||||
// When making a CONNECT request to establish a tunnel through one or
|
||||
|
|
@ -1259,7 +1261,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
// OPTIONS request (https://tools.ietf.org/html/rfc7231#section-4.3.7).
|
||||
if (method != HttpMethod.Options)
|
||||
{
|
||||
RejectRequest(RequestRejectionReason.OptionsMethodRequired);
|
||||
ThrowRequestRejected(RequestRejectionReason.OptionsMethodRequired);
|
||||
}
|
||||
|
||||
RawTarget = Asterisk;
|
||||
|
|
@ -1288,7 +1290,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
if (!Uri.TryCreate(RawTarget, UriKind.Absolute, out var uri))
|
||||
{
|
||||
RejectRequestTarget(target);
|
||||
ThrowRequestTargetRejected(target);
|
||||
}
|
||||
|
||||
_absoluteRequestTarget = uri;
|
||||
|
|
@ -1312,7 +1314,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_requestHeadersParsed++;
|
||||
if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount)
|
||||
{
|
||||
RejectRequest(RequestRejectionReason.TooManyHeaders);
|
||||
ThrowRequestRejected(RequestRejectionReason.TooManyHeaders);
|
||||
}
|
||||
var valueString = value.GetAsciiStringNonNullCharacters();
|
||||
|
||||
|
|
@ -1335,17 +1337,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
var host = FrameRequestHeaders.HeaderHost;
|
||||
if (host.Count <= 0)
|
||||
{
|
||||
RejectRequest(RequestRejectionReason.MissingHostHeader);
|
||||
ThrowRequestRejected(RequestRejectionReason.MissingHostHeader);
|
||||
}
|
||||
else if (host.Count > 1)
|
||||
{
|
||||
RejectRequest(RequestRejectionReason.MultipleHostHeaders);
|
||||
ThrowRequestRejected(RequestRejectionReason.MultipleHostHeaders);
|
||||
}
|
||||
else if (_requestTargetForm == HttpRequestTarget.AuthorityForm)
|
||||
{
|
||||
if (!host.Equals(RawTarget))
|
||||
{
|
||||
RejectRequest(RequestRejectionReason.InvalidHostHeader, host.ToString());
|
||||
ThrowRequestRejected(RequestRejectionReason.InvalidHostHeader, host.ToString());
|
||||
}
|
||||
}
|
||||
else if (_requestTargetForm == HttpRequestTarget.AbsoluteForm)
|
||||
|
|
@ -1361,7 +1363,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
if ((host != _absoluteRequestTarget.Authority || !_absoluteRequestTarget.IsDefaultPort)
|
||||
&& host != authorityAndPort)
|
||||
{
|
||||
RejectRequest(RequestRejectionReason.InvalidHostHeader, host.ToString());
|
||||
ThrowRequestRejected(RequestRejectionReason.InvalidHostHeader, host.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1370,9 +1372,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
=> ConnectionInformation.PipeFactory.Create(new PipeOptions
|
||||
{
|
||||
ReaderScheduler = ServiceContext.ThreadPool,
|
||||
WriterScheduler = ConnectionInformation.InputWriterScheduler,
|
||||
MaximumSizeHigh = ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0,
|
||||
MaximumSizeLow = ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0
|
||||
WriterScheduler = InlineScheduler.Default,
|
||||
MaximumSizeHigh = 1,
|
||||
MaximumSizeLow = 1
|
||||
});
|
||||
|
||||
private enum HttpRequestTarget
|
||||
|
|
|
|||
|
|
@ -93,8 +93,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
InitializeStreams(messageBody);
|
||||
|
||||
var messageBodyTask = messageBody.StartAsync();
|
||||
|
||||
var context = _application.CreateContext(this);
|
||||
try
|
||||
{
|
||||
|
|
@ -142,8 +140,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
// If _requestAbort is set, the connection has already been closed.
|
||||
if (Volatile.Read(ref _requestAborted) == 0)
|
||||
{
|
||||
ResumeStreams();
|
||||
|
||||
if (HasResponseStarted)
|
||||
{
|
||||
// If the response has already started, call ProduceEnd() before
|
||||
|
|
@ -158,21 +154,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
await ProduceEnd();
|
||||
}
|
||||
|
||||
if (!_keepAlive)
|
||||
// ForZeroContentLength does not complete the reader nor the writer
|
||||
if (!messageBody.IsEmpty)
|
||||
{
|
||||
messageBody.Cancel();
|
||||
if (_keepAlive)
|
||||
{
|
||||
// Finish reading the request body in case the app did not.
|
||||
await messageBody.ConsumeAsync();
|
||||
// At this point both the request body pipe reader and writer should be completed.
|
||||
RequestBodyPipe.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
RequestBodyPipe.Reader.Complete();
|
||||
messageBody.Cancel();
|
||||
Input.CancelPendingRead();
|
||||
}
|
||||
}
|
||||
|
||||
// An upgraded request has no defined request body length.
|
||||
// Cancel any pending read so the read loop ends.
|
||||
if (_upgradeAvailable)
|
||||
{
|
||||
Input.CancelPendingRead();
|
||||
}
|
||||
|
||||
// Finish reading the request body in case the app did not.
|
||||
await messageBody.ConsumeAsync();
|
||||
|
||||
if (!HasResponseStarted)
|
||||
{
|
||||
await ProduceEnd();
|
||||
|
|
@ -200,15 +199,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
// to ensure InitializeStreams has been called.
|
||||
StopStreams();
|
||||
}
|
||||
|
||||
// At this point both the request body pipe reader and writer should be completed.
|
||||
await messageBodyTask;
|
||||
|
||||
// ForZeroContentLength does not complete the reader nor the writer
|
||||
if (_keepAlive && !messageBody.IsEmpty)
|
||||
{
|
||||
RequestBodyPipe.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (!_keepAlive)
|
||||
|
|
|
|||
|
|
@ -169,14 +169,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_state = FrameStreamState.Closed;
|
||||
}
|
||||
|
||||
public void ResumeAcceptingReads()
|
||||
{
|
||||
if (_state == FrameStreamState.Closed)
|
||||
{
|
||||
_state = FrameStreamState.Open;
|
||||
}
|
||||
}
|
||||
|
||||
public void StopAcceptingReads()
|
||||
{
|
||||
// Can't use dispose (or close) as can be disposed too early by user code
|
||||
|
|
|
|||
|
|
@ -123,14 +123,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_state = FrameStreamState.Closed;
|
||||
}
|
||||
|
||||
public void ResumeAcceptingWrites()
|
||||
{
|
||||
if (_state == FrameStreamState.Closed)
|
||||
{
|
||||
_state = FrameStreamState.Open;
|
||||
}
|
||||
}
|
||||
|
||||
public void StopAcceptingWrites()
|
||||
{
|
||||
// Can't use dispose (or close) as can be disposed too early by user code
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||
{
|
||||
|
|
@ -33,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
public virtual bool IsEmpty => false;
|
||||
|
||||
public virtual async Task StartAsync()
|
||||
private async Task PumpAsync()
|
||||
{
|
||||
Exception error = null;
|
||||
|
||||
|
|
@ -83,7 +82,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
else if (result.IsCompleted)
|
||||
{
|
||||
_context.RejectRequest(RequestRejectionReason.UnexpectedEndOfRequestContent);
|
||||
_context.ThrowRequestRejected(RequestRejectionReason.UnexpectedEndOfRequestContent);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
|
@ -109,6 +108,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
public virtual async Task<int> ReadAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
TryInit();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var result = await _context.RequestBodyPipe.Reader.ReadAsync();
|
||||
|
|
@ -139,6 +140,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
public virtual async Task CopyToAsync(Stream destination, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
TryInit();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var result = await _context.RequestBodyPipe.Reader.ReadAsync();
|
||||
|
|
@ -169,7 +172,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
public virtual async Task ConsumeAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
Exception error = null;
|
||||
TryInit();
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -180,14 +183,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_context.RequestBodyPipe.Reader.Advance(result.Buffer.End);
|
||||
} while (!result.IsCompleted);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex;
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_context.RequestBodyPipe.Reader.Complete(error);
|
||||
_context.RequestBodyPipe.Reader.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +213,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
protected abstract bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined);
|
||||
private void TryInit()
|
||||
{
|
||||
if (!_context.HasStartedConsumingRequestBody)
|
||||
{
|
||||
OnReadStart();
|
||||
_context.HasStartedConsumingRequestBody = true;
|
||||
_ = PumpAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected virtual void OnReadStart()
|
||||
{
|
||||
}
|
||||
|
||||
public static MessageBody For(
|
||||
HttpVersion httpVersion,
|
||||
|
|
@ -248,15 +263,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
// status code and then close the connection.
|
||||
if (transferCoding != TransferCoding.Chunked)
|
||||
{
|
||||
context.RejectRequest(RequestRejectionReason.FinalTransferCodingNotChunked, transferEncoding.ToString());
|
||||
context.ThrowRequestRejected(RequestRejectionReason.FinalTransferCodingNotChunked, transferEncoding.ToString());
|
||||
}
|
||||
|
||||
if (upgrade)
|
||||
{
|
||||
context.RejectRequest(RequestRejectionReason.UpgradeRequestCannotHavePayload);
|
||||
context.ThrowRequestRejected(RequestRejectionReason.UpgradeRequestCannotHavePayload);
|
||||
}
|
||||
|
||||
return new ForChunkedEncoding(keepAlive, headers, context);
|
||||
return new ForChunkedEncoding(keepAlive, context);
|
||||
}
|
||||
|
||||
if (headers.ContentLength.HasValue)
|
||||
|
|
@ -269,7 +284,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
else if (upgrade)
|
||||
{
|
||||
context.RejectRequest(RequestRejectionReason.UpgradeRequestCannotHavePayload);
|
||||
context.ThrowRequestRejected(RequestRejectionReason.UpgradeRequestCannotHavePayload);
|
||||
}
|
||||
|
||||
return new ForContentLength(keepAlive, contentLength, context);
|
||||
|
|
@ -283,7 +298,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
if (HttpMethods.IsPost(context.Method) || HttpMethods.IsPut(context.Method))
|
||||
{
|
||||
var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10;
|
||||
context.RejectRequest(requestRejectionReason, context.Method);
|
||||
context.ThrowRequestRejected(requestRejectionReason, context.Method);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -322,11 +337,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
public override bool IsEmpty => true;
|
||||
|
||||
public override Task StartAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
|
|
@ -341,11 +351,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class ForContentLength : MessageBody
|
||||
|
|
@ -378,6 +383,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
return _inputLength == 0;
|
||||
}
|
||||
|
||||
protected override void OnReadStart()
|
||||
{
|
||||
if (_contentLength > _context.MaxRequestBodySize)
|
||||
{
|
||||
_context.ThrowRequestRejected(RequestRejectionReason.RequestBodyTooLarge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -387,19 +400,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
// byte consts don't have a data type annotation so we pre-cast it
|
||||
private const byte ByteCR = (byte)'\r';
|
||||
// "7FFFFFFF\r\n" is the largest chunk size that could be returned as an int.
|
||||
private const int MaxChunkPrefixBytes = 10;
|
||||
|
||||
private readonly IPipeReader _input;
|
||||
private readonly FrameRequestHeaders _requestHeaders;
|
||||
private int _inputLength;
|
||||
private long _consumedBytes;
|
||||
|
||||
private Mode _mode = Mode.Prefix;
|
||||
|
||||
public ForChunkedEncoding(bool keepAlive, FrameRequestHeaders headers, Frame context)
|
||||
public ForChunkedEncoding(bool keepAlive, Frame context)
|
||||
: base(context)
|
||||
{
|
||||
RequestKeepAlive = keepAlive;
|
||||
_input = _context.Input;
|
||||
_requestHeaders = headers;
|
||||
}
|
||||
|
||||
protected override bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined)
|
||||
|
|
@ -471,6 +483,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
readableBuffer = readableBuffer.Slice(consumed);
|
||||
}
|
||||
|
||||
// _consumedBytes aren't tracked for trailer headers, since headers have seperate limits.
|
||||
if (_mode == Mode.TrailerHeaders)
|
||||
{
|
||||
if (_context.TakeMessageHeaders(readableBuffer, out consumed, out examined))
|
||||
|
|
@ -482,6 +495,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
return _mode == Mode.Complete;
|
||||
}
|
||||
|
||||
private void AddAndCheckConsumedBytes(int consumedBytes)
|
||||
{
|
||||
_consumedBytes += consumedBytes;
|
||||
|
||||
if (_consumedBytes > _context.MaxRequestBodySize)
|
||||
{
|
||||
_context.ThrowRequestRejected(RequestRejectionReason.RequestBodyTooLarge);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseChunkedPrefix(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
|
||||
{
|
||||
consumed = buffer.Start;
|
||||
|
|
@ -499,13 +522,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
var chunkSize = CalculateChunkSize(ch1, 0);
|
||||
ch1 = ch2;
|
||||
|
||||
do
|
||||
while (reader.ConsumedBytes < MaxChunkPrefixBytes)
|
||||
{
|
||||
if (ch1 == ';')
|
||||
{
|
||||
consumed = reader.Cursor;
|
||||
examined = reader.Cursor;
|
||||
|
||||
AddAndCheckConsumedBytes(reader.ConsumedBytes);
|
||||
_inputLength = chunkSize;
|
||||
_mode = Mode.Extension;
|
||||
return;
|
||||
|
|
@ -523,23 +547,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
consumed = reader.Cursor;
|
||||
examined = reader.Cursor;
|
||||
|
||||
AddAndCheckConsumedBytes(reader.ConsumedBytes);
|
||||
_inputLength = chunkSize;
|
||||
|
||||
if (chunkSize > 0)
|
||||
{
|
||||
_mode = Mode.Data;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mode = Mode.Trailer;
|
||||
}
|
||||
|
||||
_mode = chunkSize > 0 ? Mode.Data : Mode.Trailer;
|
||||
return;
|
||||
}
|
||||
|
||||
chunkSize = CalculateChunkSize(ch1, chunkSize);
|
||||
ch1 = ch2;
|
||||
} while (ch1 != -1);
|
||||
}
|
||||
|
||||
// At this point, 10 bytes have been consumed which is enough to parse the max value "7FFFFFFF\r\n".
|
||||
_context.ThrowRequestRejected(RequestRejectionReason.BadChunkSizeData);
|
||||
}
|
||||
|
||||
private void ParseExtension(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
|
||||
|
|
@ -548,39 +567,48 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
// Just drain the data
|
||||
consumed = buffer.Start;
|
||||
examined = buffer.Start;
|
||||
|
||||
do
|
||||
{
|
||||
ReadCursor extensionCursor;
|
||||
if (ReadCursorOperations.Seek(buffer.Start, buffer.End, out extensionCursor, ByteCR) == -1)
|
||||
{
|
||||
// End marker not found yet
|
||||
consumed = buffer.End;
|
||||
examined = buffer.End;
|
||||
AddAndCheckConsumedBytes(buffer.Length);
|
||||
return;
|
||||
};
|
||||
|
||||
var charsToByteCRExclusive = buffer.Slice(0, extensionCursor).Length;
|
||||
|
||||
var sufixBuffer = buffer.Slice(extensionCursor);
|
||||
if (sufixBuffer.Length < 2)
|
||||
{
|
||||
consumed = extensionCursor;
|
||||
examined = buffer.End;
|
||||
AddAndCheckConsumedBytes(charsToByteCRExclusive);
|
||||
return;
|
||||
}
|
||||
|
||||
sufixBuffer = sufixBuffer.Slice(0, 2);
|
||||
var sufixSpan = sufixBuffer.ToSpan();
|
||||
|
||||
|
||||
if (sufixSpan[1] == '\n')
|
||||
{
|
||||
// We consumed the \r\n at the end of the extension, so switch modes.
|
||||
_mode = _inputLength > 0 ? Mode.Data : Mode.Trailer;
|
||||
|
||||
consumed = sufixBuffer.End;
|
||||
examined = sufixBuffer.End;
|
||||
if (_inputLength > 0)
|
||||
{
|
||||
_mode = Mode.Data;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mode = Mode.Trailer;
|
||||
}
|
||||
AddAndCheckConsumedBytes(charsToByteCRExclusive + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't consume suffixSpan[1] in case it is also a \r.
|
||||
buffer = buffer.Slice(charsToByteCRExclusive + 1);
|
||||
consumed = extensionCursor;
|
||||
AddAndCheckConsumedBytes(charsToByteCRExclusive + 1);
|
||||
}
|
||||
} while (_mode == Mode.Extension);
|
||||
}
|
||||
|
|
@ -594,6 +622,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
Copy(buffer.Slice(0, actual), writableBuffer);
|
||||
|
||||
_inputLength -= actual;
|
||||
AddAndCheckConsumedBytes(actual);
|
||||
|
||||
if (_inputLength == 0)
|
||||
{
|
||||
|
|
@ -618,11 +647,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
consumed = suffixBuffer.End;
|
||||
examined = suffixBuffer.End;
|
||||
AddAndCheckConsumedBytes(2);
|
||||
_mode = Mode.Prefix;
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.RejectRequest(RequestRejectionReason.BadChunkSuffix);
|
||||
_context.ThrowRequestRejected(RequestRejectionReason.BadChunkSuffix);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -644,6 +674,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
consumed = trailerBuffer.End;
|
||||
examined = trailerBuffer.End;
|
||||
AddAndCheckConsumedBytes(2);
|
||||
_mode = Mode.Complete;
|
||||
}
|
||||
else
|
||||
|
|
@ -654,23 +685,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
private int CalculateChunkSize(int extraHexDigit, int currentParsedSize)
|
||||
{
|
||||
checked
|
||||
try
|
||||
{
|
||||
if (extraHexDigit >= '0' && extraHexDigit <= '9')
|
||||
checked
|
||||
{
|
||||
return currentParsedSize * 0x10 + (extraHexDigit - '0');
|
||||
}
|
||||
else if (extraHexDigit >= 'A' && extraHexDigit <= 'F')
|
||||
{
|
||||
return currentParsedSize * 0x10 + (extraHexDigit - ('A' - 10));
|
||||
}
|
||||
else if (extraHexDigit >= 'a' && extraHexDigit <= 'f')
|
||||
{
|
||||
return currentParsedSize * 0x10 + (extraHexDigit - ('a' - 10));
|
||||
if (extraHexDigit >= '0' && extraHexDigit <= '9')
|
||||
{
|
||||
return currentParsedSize * 0x10 + (extraHexDigit - '0');
|
||||
}
|
||||
else if (extraHexDigit >= 'A' && extraHexDigit <= 'F')
|
||||
{
|
||||
return currentParsedSize * 0x10 + (extraHexDigit - ('A' - 10));
|
||||
}
|
||||
else if (extraHexDigit >= 'a' && extraHexDigit <= 'f')
|
||||
{
|
||||
return currentParsedSize * 0x10 + (extraHexDigit - ('a' - 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OverflowException ex)
|
||||
{
|
||||
throw new IOException(CoreStrings.BadRequest_BadChunkSizeData, ex);
|
||||
}
|
||||
|
||||
_context.RejectRequest(RequestRejectionReason.BadChunkSizeData);
|
||||
_context.ThrowRequestRejected(RequestRejectionReason.BadChunkSizeData);
|
||||
return -1; // can't happen, but compiler complains
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
RequestLineTooLong,
|
||||
HeadersExceedMaxTotalSize,
|
||||
TooManyHeaders,
|
||||
RequestBodyTooLarge,
|
||||
RequestTimeout,
|
||||
FinalTransferCodingNotChunked,
|
||||
LengthRequired,
|
||||
|
|
|
|||
|
|
@ -60,13 +60,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
_response.PauseAcceptingWrites();
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
_request.ResumeAcceptingReads();
|
||||
_emptyRequest.ResumeAcceptingReads();
|
||||
_response.ResumeAcceptingWrites();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_request.StopAcceptingReads();
|
||||
|
|
|
|||
|
|
@ -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.Kestrel.Core
|
||||
{
|
||||
|
|
@ -20,6 +21,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
// Matches the default large_client_header_buffers in nginx.
|
||||
private int _maxRequestHeadersTotalSize = 32 * 1024;
|
||||
|
||||
// Matches the default maxAllowedContentLength in IIS (~28.6 MB)
|
||||
// https://www.iis.net/configreference/system.webserver/security/requestfiltering/requestlimits#005
|
||||
private long? _maxRequestBodySize = 30000000;
|
||||
|
||||
// Matches the default LimitRequestFields in Apache httpd.
|
||||
private int _maxRequestHeaderCount = 100;
|
||||
|
||||
|
|
@ -133,6 +138,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
}
|
||||
}
|
||||
|
||||
/// <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 null (unlimited).
|
||||
/// </remarks>
|
||||
public long? MaxRequestBodySize
|
||||
{
|
||||
get => _maxRequestBodySize;
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.NonNegativeNumberOrNullRequired);
|
||||
}
|
||||
_maxRequestBodySize = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the keep-alive timeout.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -948,6 +948,48 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
internal static string FormatUpgradeCannotBeCalledMultipleTimes()
|
||||
=> GetString("UpgradeCannotBeCalledMultipleTimes");
|
||||
|
||||
/// <summary>
|
||||
/// Request body too large.
|
||||
/// </summary>
|
||||
internal static string BadRequest_RequestBodyTooLarge
|
||||
{
|
||||
get => GetString("BadRequest_RequestBodyTooLarge");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request body too large.
|
||||
/// </summary>
|
||||
internal static string FormatBadRequest_RequestBodyTooLarge()
|
||||
=> GetString("BadRequest_RequestBodyTooLarge");
|
||||
|
||||
/// <summary>
|
||||
/// The maximum request body size cannot be modified after the app has already started reading from the request body.
|
||||
/// </summary>
|
||||
internal static string MaxRequestBodySizeCannotBeModifiedAfterRead
|
||||
{
|
||||
get => GetString("MaxRequestBodySizeCannotBeModifiedAfterRead");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum request body size cannot be modified after the app has already started reading from the request body.
|
||||
/// </summary>
|
||||
internal static string FormatMaxRequestBodySizeCannotBeModifiedAfterRead()
|
||||
=> GetString("MaxRequestBodySizeCannotBeModifiedAfterRead");
|
||||
|
||||
/// <summary>
|
||||
/// The maximum request body size cannot be modified after the request has be upgraded.
|
||||
/// </summary>
|
||||
internal static string MaxRequestBodySizeCannotBeModifiedForUpgradedRequests
|
||||
{
|
||||
get => GetString("MaxRequestBodySizeCannotBeModifiedForUpgradedRequests");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum request body size cannot be modified after the request has be upgraded.
|
||||
/// </summary>
|
||||
internal static string FormatMaxRequestBodySizeCannotBeModifiedForUpgradedRequests()
|
||||
=> GetString("MaxRequestBodySizeCannotBeModifiedForUpgradedRequests");
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -17,7 +16,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
|||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -733,6 +731,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsWhenMaxRequestBodySizeIsSetAfterReadingFromRequestBody()
|
||||
{
|
||||
// Act
|
||||
// This would normally be set by the MessageBody during the first read.
|
||||
_frame.HasStartedConsumingRequestBody = true;
|
||||
|
||||
// Assert
|
||||
Assert.True(((IHttpMaxRequestBodySizeFeature)_frame).IsReadOnly);
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => ((IHttpMaxRequestBodySizeFeature)_frame).MaxRequestBodySize = 1);
|
||||
Assert.Equal(CoreStrings.MaxRequestBodySizeCannotBeModifiedAfterRead, ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsWhenMaxRequestBodySizeIsSetToANegativeValue()
|
||||
{
|
||||
// Assert
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => ((IHttpMaxRequestBodySizeFeature)_frame).MaxRequestBodySize = -1);
|
||||
Assert.StartsWith(CoreStrings.NonNegativeNumberOrNullRequired, ex.Message);
|
||||
}
|
||||
|
||||
private static async Task WaitForCondition(TimeSpan timeout, Func<bool> condition)
|
||||
{
|
||||
const int MaxWaitLoop = 150;
|
||||
|
|
|
|||
|
|
@ -241,5 +241,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new KestrelServerLimits().MaxConcurrentUpgradedConnections = value);
|
||||
Assert.StartsWith(CoreStrings.NonNegativeNumberOrNullRequired, ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MaxRequestBodySizeDefault()
|
||||
{
|
||||
// ~28.6 MB (https://www.iis.net/configreference/system.webserver/security/requestfiltering/requestlimits#005)
|
||||
Assert.Equal(30000000, new KestrelServerLimits().MaxRequestBodySize);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(long.MaxValue)]
|
||||
public void MaxRequestBodySizeValid(long? value)
|
||||
{
|
||||
var limits = new KestrelServerLimits
|
||||
{
|
||||
MaxRequestBodySize = value
|
||||
};
|
||||
|
||||
Assert.Equal(value, limits.MaxRequestBodySize);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(long.MinValue)]
|
||||
[InlineData(-1)]
|
||||
public void MaxRequestBodySizeInvalid(long value)
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new KestrelServerLimits().MaxRequestBodySize = value);
|
||||
Assert.StartsWith(CoreStrings.NonNegativeNumberOrNullRequired, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[Theory]
|
||||
[InlineData(HttpVersion.Http10)]
|
||||
[InlineData(HttpVersion.Http11)]
|
||||
public async Task CanReadFromContentLength(HttpVersion httpVersion)
|
||||
public void CanReadFromContentLength(HttpVersion httpVersion)
|
||||
{
|
||||
using (var input = new TestInput())
|
||||
{
|
||||
|
|
@ -34,8 +34,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
input.Add("Hello");
|
||||
|
||||
var buffer = new byte[1024];
|
||||
|
|
@ -46,8 +44,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
count = stream.Read(buffer, 0, buffer.Length);
|
||||
Assert.Equal(0, count);
|
||||
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,8 +58,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
input.Add("Hello");
|
||||
|
||||
var buffer = new byte[1024];
|
||||
|
|
@ -74,13 +68,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
count = await stream.ReadAsync(buffer, 0, buffer.Length);
|
||||
Assert.Equal(0, count);
|
||||
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadFromChunkedEncoding()
|
||||
public void CanReadFromChunkedEncoding()
|
||||
{
|
||||
using (var input = new TestInput())
|
||||
{
|
||||
|
|
@ -88,8 +80,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
input.Add("5\r\nHello\r\n");
|
||||
|
||||
var buffer = new byte[1024];
|
||||
|
|
@ -102,8 +92,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
count = stream.Read(buffer, 0, buffer.Length);
|
||||
Assert.Equal(0, count);
|
||||
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,8 +104,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
input.Add("5\r\nHello\r\n");
|
||||
|
||||
var buffer = new byte[1024];
|
||||
|
|
@ -130,15 +116,74 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
count = await stream.ReadAsync(buffer, 0, buffer.Length);
|
||||
Assert.Equal(0, count);
|
||||
}
|
||||
}
|
||||
|
||||
await bodyTask;
|
||||
[Fact]
|
||||
public async Task ReadExitsGivenIncompleteChunkedExtension()
|
||||
{
|
||||
using (var input = new TestInput())
|
||||
{
|
||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.FrameContext);
|
||||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
input.Add("5;\r\0");
|
||||
|
||||
var buffer = new byte[1024];
|
||||
var readTask = stream.ReadAsync(buffer, 0, buffer.Length);
|
||||
|
||||
Assert.False(readTask.IsCompleted);
|
||||
|
||||
input.Add("\r\r\r\nHello\r\n0\r\n\r\n");
|
||||
|
||||
Assert.Equal(5, await readTask.TimeoutAfter(TimeSpan.FromSeconds(10)));
|
||||
Assert.Equal(0, await stream.ReadAsync(buffer, 0, buffer.Length));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadThrowsGivenChunkPrefixGreaterThanMaxInt()
|
||||
{
|
||||
using (var input = new TestInput())
|
||||
{
|
||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.FrameContext);
|
||||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
input.Add("80000000\r\n");
|
||||
|
||||
var buffer = new byte[1024];
|
||||
var ex = await Assert.ThrowsAsync<IOException>(async () =>
|
||||
await stream.ReadAsync(buffer, 0, buffer.Length));
|
||||
Assert.IsType<OverflowException>(ex.InnerException);
|
||||
Assert.Equal(CoreStrings.BadRequest_BadChunkSizeData, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadThrowsGivenChunkPrefixGreaterThan8Bytes()
|
||||
{
|
||||
using (var input = new TestInput())
|
||||
{
|
||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.FrameContext);
|
||||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
input.Add("012345678\r");
|
||||
|
||||
var buffer = new byte[1024];
|
||||
var ex = await Assert.ThrowsAsync<BadHttpRequestException>(async () =>
|
||||
await stream.ReadAsync(buffer, 0, buffer.Length));
|
||||
|
||||
Assert.Equal(CoreStrings.BadRequest_BadChunkSizeData, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpVersion.Http10)]
|
||||
[InlineData(HttpVersion.Http11)]
|
||||
public async Task CanReadFromRemainingData(HttpVersion httpVersion)
|
||||
public void CanReadFromRemainingData(HttpVersion httpVersion)
|
||||
{
|
||||
using (var input = new TestInput())
|
||||
{
|
||||
|
|
@ -146,8 +191,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
input.Add("Hello");
|
||||
|
||||
var buffer = new byte[1024];
|
||||
|
|
@ -157,8 +200,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
AssertASCII("Hello", new ArraySegment<byte>(buffer, 0, count));
|
||||
|
||||
input.Fin();
|
||||
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -173,8 +214,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
input.Add("Hello");
|
||||
|
||||
var buffer = new byte[1024];
|
||||
|
|
@ -184,15 +223,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
AssertASCII("Hello", new ArraySegment<byte>(buffer, 0, count));
|
||||
|
||||
input.Fin();
|
||||
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpVersion.Http10)]
|
||||
[InlineData(HttpVersion.Http11)]
|
||||
public async Task ReadFromNoContentLengthReturnsZero(HttpVersion httpVersion)
|
||||
public void ReadFromNoContentLengthReturnsZero(HttpVersion httpVersion)
|
||||
{
|
||||
using (var input = new TestInput())
|
||||
{
|
||||
|
|
@ -200,14 +237,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
input.Add("Hello");
|
||||
|
||||
var buffer = new byte[1024];
|
||||
Assert.Equal(0, stream.Read(buffer, 0, buffer.Length));
|
||||
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -222,14 +255,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
input.Add("Hello");
|
||||
|
||||
var buffer = new byte[1024];
|
||||
Assert.Equal(0, await stream.ReadAsync(buffer, 0, buffer.Length));
|
||||
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -242,8 +271,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
// Input needs to be greater than 4032 bytes to allocate a block not backed by a slab.
|
||||
var largeInput = new string('a', 8192);
|
||||
|
||||
|
|
@ -258,8 +285,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var requestArray = ms.ToArray();
|
||||
Assert.Equal(8197, requestArray.Length);
|
||||
AssertASCII(largeInput + "Hello", new ArraySegment<byte>(requestArray, 0, requestArray.Length));
|
||||
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -314,8 +339,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
using (var input = new TestInput())
|
||||
{
|
||||
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "5" }, input.FrameContext);
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
input.Add("Hello");
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
|
|
@ -324,8 +347,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
}
|
||||
|
||||
Assert.Equal(0, await body.ReadAsync(new ArraySegment<byte>(new byte[1])));
|
||||
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,15 +356,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
using (var input = new TestInput())
|
||||
{
|
||||
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "5" }, input.FrameContext);
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
input.Add("Hello");
|
||||
|
||||
await body.ConsumeAsync();
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await body.ReadAsync(new ArraySegment<byte>(new byte[1])));
|
||||
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -386,8 +403,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
using (var input = new TestInput())
|
||||
{
|
||||
var body = MessageBody.For(HttpVersion.Http11, headers, input.FrameContext);
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
var copyToAsyncTask = body.CopyToAsync(mockDestination.Object);
|
||||
|
||||
// The block returned by IncomingStart always has at least 2048 available bytes,
|
||||
|
|
@ -421,8 +436,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await copyToAsyncTask;
|
||||
|
||||
Assert.Equal(2, writeCount);
|
||||
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -431,7 +444,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[InlineData("Keep-Alive, Upgrade")]
|
||||
[InlineData("upgrade, keep-alive")]
|
||||
[InlineData("Upgrade, Keep-Alive")]
|
||||
public async Task ConnectionUpgradeKeepAlive(string headerConnection)
|
||||
public void ConnectionUpgradeKeepAlive(string headerConnection)
|
||||
{
|
||||
using (var input = new TestInput())
|
||||
{
|
||||
|
|
@ -439,8 +452,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
input.Add("Hello");
|
||||
|
||||
var buffer = new byte[1024];
|
||||
|
|
@ -448,8 +459,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
AssertASCII("Hello", new ArraySegment<byte>(buffer, 0, 5));
|
||||
|
||||
input.Fin();
|
||||
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -462,8 +471,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
// Add some input and consume it to ensure StartAsync is in the loop
|
||||
input.Add("a");
|
||||
Assert.Equal(1, await stream.ReadAsync(new byte[1], 0, 1));
|
||||
|
|
@ -474,8 +481,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
input.Add("b");
|
||||
Assert.Equal(1, await stream.ReadAsync(new byte[1], 0, 1));
|
||||
|
||||
// All input was read, body task should complete
|
||||
await bodyTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -488,8 +493,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
var bodyTask = body.StartAsync();
|
||||
|
||||
// Add some input and consume it to ensure StartAsync is in the loop
|
||||
input.Add("a");
|
||||
Assert.Equal(1, await stream.ReadAsync(new byte[1], 0, 1));
|
||||
|
|
@ -503,8 +506,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
// Unblock the loop
|
||||
input.Pipe.Reader.CancelPendingRead();
|
||||
|
||||
await bodyTask.TimeoutAfter(TimeSpan.FromSeconds(10));
|
||||
|
||||
// There shouldn't be any additional data available
|
||||
Assert.Equal(0, await stream.ReadAsync(new byte[1], 0, 1));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,13 +80,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
RequestUpgrade = upgradeable;
|
||||
}
|
||||
|
||||
protected override bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined)
|
||||
{
|
||||
consumed = default(ReadCursor);
|
||||
examined = default(ReadCursor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,494 @@
|
|||
// 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.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||
{
|
||||
public class MaxRequestBodySizeTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task RejectsRequestWithContentLengthHeaderExceedingGlobalLimit()
|
||||
{
|
||||
// 4 GiB
|
||||
var globalMaxRequestBodySize = 0x100000000;
|
||||
BadHttpRequestException requestRejectedEx = null;
|
||||
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
var buffer = new byte[1];
|
||||
requestRejectedEx = await Assert.ThrowsAsync<BadHttpRequestException>(
|
||||
async () => await context.Request.Body.ReadAsync(buffer, 0, 1));
|
||||
throw requestRejectedEx;
|
||||
},
|
||||
new TestServiceContext { ServerOptions = { Limits = { MaxRequestBodySize = globalMaxRequestBodySize } } }))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Content-Length: " + (globalMaxRequestBodySize + 1),
|
||||
"",
|
||||
"");
|
||||
await connection.ReceiveForcedEnd(
|
||||
"HTTP/1.1 413 Payload Too Large",
|
||||
"Connection: close",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotNull(requestRejectedEx);
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, requestRejectedEx.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RejectsRequestWithContentLengthHeaderExceedingPerRequestLimit()
|
||||
{
|
||||
// 8 GiB
|
||||
var globalMaxRequestBodySize = 0x200000000;
|
||||
// 4 GiB
|
||||
var perRequestMaxRequestBodySize = 0x100000000;
|
||||
BadHttpRequestException requestRejectedEx = null;
|
||||
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
var feature = context.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.Equal(globalMaxRequestBodySize, feature.MaxRequestBodySize);
|
||||
|
||||
// Disable the MaxRequestBodySize prior to calling Request.Body.ReadAsync();
|
||||
feature.MaxRequestBodySize = perRequestMaxRequestBodySize;
|
||||
|
||||
var buffer = new byte[1];
|
||||
requestRejectedEx = await Assert.ThrowsAsync<BadHttpRequestException>(
|
||||
async () => await context.Request.Body.ReadAsync(buffer, 0, 1));
|
||||
throw requestRejectedEx;
|
||||
},
|
||||
new TestServiceContext { ServerOptions = { Limits = { MaxRequestBodySize = globalMaxRequestBodySize } } }))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Content-Length: " + (perRequestMaxRequestBodySize + 1),
|
||||
"",
|
||||
"");
|
||||
await connection.ReceiveForcedEnd(
|
||||
"HTTP/1.1 413 Payload Too Large",
|
||||
"Connection: close",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotNull(requestRejectedEx);
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, requestRejectedEx.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DoesNotRejectRequestWithContentLengthHeaderExceedingGlobalLimitIfLimitDisabledPerRequest()
|
||||
{
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
var feature = context.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.Equal(0, feature.MaxRequestBodySize);
|
||||
|
||||
// Disable the MaxRequestBodySize prior to calling Request.Body.ReadAsync();
|
||||
feature.MaxRequestBodySize = null;
|
||||
|
||||
var buffer = new byte[1];
|
||||
|
||||
Assert.Equal(1, await context.Request.Body.ReadAsync(buffer, 0, 1));
|
||||
Assert.Equal(buffer[0], (byte)'A');
|
||||
Assert.Equal(0, await context.Request.Body.ReadAsync(buffer, 0, 1));
|
||||
|
||||
context.Response.ContentLength = 1;
|
||||
await context.Response.Body.WriteAsync(buffer, 0, 1);
|
||||
},
|
||||
new TestServiceContext { ServerOptions = { Limits = { MaxRequestBodySize = 0 } } }))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Content-Length: 1",
|
||||
"",
|
||||
"A");
|
||||
await connection.Receive(
|
||||
"HTTP/1.1 200 OK",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 1",
|
||||
"",
|
||||
"A");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DoesNotRejectBodylessGetRequestWithZeroMaxRequestBodySize()
|
||||
{
|
||||
using (var server = new TestServer(context => Task.CompletedTask,
|
||||
new TestServiceContext { ServerOptions = { Limits = { MaxRequestBodySize = 0 } } }))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"GET / HTTP/1.1",
|
||||
"Host:",
|
||||
"",
|
||||
"POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Content-Length: 1",
|
||||
"",
|
||||
"A");
|
||||
await connection.ReceiveForcedEnd(
|
||||
"HTTP/1.1 200 OK",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"HTTP/1.1 413 Payload Too Large",
|
||||
"Connection: close",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SettingMaxRequestBodySizeAfterReadingFromRequestBodyThrows()
|
||||
{
|
||||
var perRequestMaxRequestBodySize = 0x10;
|
||||
var payloadSize = perRequestMaxRequestBodySize + 1;
|
||||
var payload = new string('A', payloadSize);
|
||||
InvalidOperationException invalidOpEx = null;
|
||||
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
var buffer = new byte[1];
|
||||
Assert.Equal(1, await context.Request.Body.ReadAsync(buffer, 0, 1));
|
||||
|
||||
var feature = context.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.Equal(new KestrelServerLimits().MaxRequestBodySize, feature.MaxRequestBodySize);
|
||||
Assert.True(feature.IsReadOnly);
|
||||
|
||||
invalidOpEx = Assert.Throws<InvalidOperationException>(() =>
|
||||
feature.MaxRequestBodySize = perRequestMaxRequestBodySize);
|
||||
throw invalidOpEx;
|
||||
}))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Content-Length: " + payloadSize,
|
||||
"",
|
||||
payload);
|
||||
await connection.Receive(
|
||||
"HTTP/1.1 500 Internal Server Error",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotNull(invalidOpEx);
|
||||
Assert.Equal(CoreStrings.MaxRequestBodySizeCannotBeModifiedAfterRead, invalidOpEx.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SettingMaxRequestBodySizeAfterUpgradingRequestThrows()
|
||||
{
|
||||
InvalidOperationException invalidOpEx = null;
|
||||
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
var upgradeFeature = context.Features.Get<IHttpUpgradeFeature>();
|
||||
var stream = await upgradeFeature.UpgradeAsync();
|
||||
|
||||
var feature = context.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.Equal(new KestrelServerLimits().MaxRequestBodySize, feature.MaxRequestBodySize);
|
||||
Assert.True(feature.IsReadOnly);
|
||||
|
||||
invalidOpEx = Assert.Throws<InvalidOperationException>(() =>
|
||||
feature.MaxRequestBodySize = 0x10);
|
||||
throw invalidOpEx;
|
||||
}))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send("GET / HTTP/1.1",
|
||||
"Host:",
|
||||
"Connection: Upgrade",
|
||||
"",
|
||||
"");
|
||||
await connection.Receive("HTTP/1.1 101 Switching Protocols",
|
||||
"Connection: Upgrade",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"",
|
||||
"");
|
||||
await connection.ReceiveForcedEnd();
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotNull(invalidOpEx);
|
||||
Assert.Equal(CoreStrings.MaxRequestBodySizeCannotBeModifiedForUpgradedRequests, invalidOpEx.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EveryReadFailsWhenContentLengthHeaderExceedsGlobalLimit()
|
||||
{
|
||||
BadHttpRequestException requestRejectedEx1 = null;
|
||||
BadHttpRequestException requestRejectedEx2 = null;
|
||||
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
var buffer = new byte[1];
|
||||
requestRejectedEx1 = await Assert.ThrowsAsync<BadHttpRequestException>(
|
||||
async () => await context.Request.Body.ReadAsync(buffer, 0, 1));
|
||||
requestRejectedEx2 = await Assert.ThrowsAsync<BadHttpRequestException>(
|
||||
async () => await context.Request.Body.ReadAsync(buffer, 0, 1));
|
||||
throw requestRejectedEx2;
|
||||
},
|
||||
new TestServiceContext { ServerOptions = { Limits = { MaxRequestBodySize = 0 } } }))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Content-Length: " + (new KestrelServerLimits().MaxRequestBodySize + 1),
|
||||
"",
|
||||
"");
|
||||
await connection.ReceiveForcedEnd(
|
||||
"HTTP/1.1 413 Payload Too Large",
|
||||
"Connection: close",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotNull(requestRejectedEx1);
|
||||
Assert.NotNull(requestRejectedEx2);
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, requestRejectedEx1.Message);
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, requestRejectedEx2.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChunkFramingAndExtensionsCountTowardsRequestBodySize()
|
||||
{
|
||||
var chunkedPayload = "5;random chunk extension\r\nHello\r\n6\r\n World\r\n0\r\n\r\n";
|
||||
var globalMaxRequestBodySize = chunkedPayload.Length - 1;
|
||||
BadHttpRequestException requestRejectedEx = null;
|
||||
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
var buffer = new byte[11];
|
||||
requestRejectedEx = await Assert.ThrowsAsync<BadHttpRequestException>(async () =>
|
||||
{
|
||||
var count = 0;
|
||||
do
|
||||
{
|
||||
count = await context.Request.Body.ReadAsync(buffer, 0, 11);
|
||||
} while (count != 0);
|
||||
});
|
||||
|
||||
throw requestRejectedEx;
|
||||
},
|
||||
new TestServiceContext { ServerOptions = { Limits = { MaxRequestBodySize = globalMaxRequestBodySize } } }))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Transfer-Encoding: chunked",
|
||||
"",
|
||||
chunkedPayload);
|
||||
await connection.ReceiveForcedEnd(
|
||||
"HTTP/1.1 413 Payload Too Large",
|
||||
"Connection: close",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotNull(requestRejectedEx);
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, requestRejectedEx.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TrailingHeadersDoNotCountTowardsRequestBodySize()
|
||||
{
|
||||
var chunkedPayload = $"5;random chunk extension\r\nHello\r\n6\r\n World\r\n0\r\n";
|
||||
var trailingHeaders = "Trailing-Header: trailing-value\r\n\r\n";
|
||||
var globalMaxRequestBodySize = chunkedPayload.Length;
|
||||
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
var offset = 0;
|
||||
var count = 0;
|
||||
var buffer = new byte[11];
|
||||
|
||||
do
|
||||
{
|
||||
count = await context.Request.Body.ReadAsync(buffer, offset, 11 - offset);
|
||||
offset += count;
|
||||
} while (count != 0);
|
||||
|
||||
Assert.Equal("Hello World", Encoding.ASCII.GetString(buffer));
|
||||
Assert.Equal("trailing-value", context.Request.Headers["Trailing-Header"].ToString());
|
||||
},
|
||||
new TestServiceContext { ServerOptions = { Limits = { MaxRequestBodySize = globalMaxRequestBodySize } } }))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Transfer-Encoding: chunked",
|
||||
"",
|
||||
chunkedPayload + trailingHeaders);
|
||||
await connection.Receive(
|
||||
"HTTP/1.1 200 OK",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PerRequestMaxRequestBodySizeGetsReset()
|
||||
{
|
||||
var chunkedPayload = "5;random chunk extension\r\nHello\r\n6\r\n World\r\n0\r\n\r\n";
|
||||
var globalMaxRequestBodySize = chunkedPayload.Length - 1;
|
||||
var firstRequest = true;
|
||||
BadHttpRequestException requestRejectedEx = null;
|
||||
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
var feature = context.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.Equal(globalMaxRequestBodySize, feature.MaxRequestBodySize);
|
||||
|
||||
var buffer = new byte[11];
|
||||
var count = 0;
|
||||
|
||||
if (firstRequest)
|
||||
{
|
||||
firstRequest = false;
|
||||
feature.MaxRequestBodySize = chunkedPayload.Length;
|
||||
|
||||
do
|
||||
{
|
||||
count = await context.Request.Body.ReadAsync(buffer, 0, 11);
|
||||
} while (count != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
requestRejectedEx = await Assert.ThrowsAsync<BadHttpRequestException>(async () =>
|
||||
{
|
||||
do
|
||||
{
|
||||
count = await context.Request.Body.ReadAsync(buffer, 0, 11);
|
||||
} while (count != 0);
|
||||
});
|
||||
|
||||
throw requestRejectedEx;
|
||||
}
|
||||
},
|
||||
new TestServiceContext { ServerOptions = { Limits = { MaxRequestBodySize = globalMaxRequestBodySize } } }))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Transfer-Encoding: chunked",
|
||||
"",
|
||||
chunkedPayload + "POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Transfer-Encoding: chunked",
|
||||
"",
|
||||
chunkedPayload);
|
||||
await connection.ReceiveForcedEnd(
|
||||
"HTTP/1.1 200 OK",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"HTTP/1.1 413 Payload Too Large",
|
||||
"Connection: close",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotNull(requestRejectedEx);
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, requestRejectedEx.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EveryReadFailsWhenChunkedPayloadExceedsGlobalLimit()
|
||||
{
|
||||
BadHttpRequestException requestRejectedEx1 = null;
|
||||
BadHttpRequestException requestRejectedEx2 = null;
|
||||
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
var buffer = new byte[1];
|
||||
requestRejectedEx1 = await Assert.ThrowsAsync<BadHttpRequestException>(
|
||||
async () => await context.Request.Body.ReadAsync(buffer, 0, 1));
|
||||
requestRejectedEx2 = await Assert.ThrowsAsync<BadHttpRequestException>(
|
||||
async () => await context.Request.Body.ReadAsync(buffer, 0, 1));
|
||||
throw requestRejectedEx2;
|
||||
},
|
||||
new TestServiceContext { ServerOptions = { Limits = { MaxRequestBodySize = 0 } } }))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Host:",
|
||||
"Transfer-Encoding: chunked",
|
||||
"",
|
||||
"1\r\n");
|
||||
await connection.ReceiveForcedEnd(
|
||||
"HTTP/1.1 413 Payload Too Large",
|
||||
"Connection: close",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotNull(requestRejectedEx1);
|
||||
Assert.NotNull(requestRejectedEx2);
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, requestRejectedEx1.Message);
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, requestRejectedEx2.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,7 +59,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
Assert.True(bufferLength % 256 == 0, $"{nameof(bufferLength)} must be evenly divisible by 256");
|
||||
|
||||
var builder = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseKestrel(o =>
|
||||
{
|
||||
o.Limits.MaxRequestBodySize = contentLength;
|
||||
})
|
||||
.UseUrls("http://127.0.0.1:0/")
|
||||
.Configure(app =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -117,20 +117,31 @@ namespace Microsoft.AspNetCore.Testing
|
|||
var expected = string.Join("\r\n", lines);
|
||||
var actual = new char[expected.Length];
|
||||
var offset = 0;
|
||||
while (offset < expected.Length)
|
||||
|
||||
try
|
||||
{
|
||||
var data = new byte[expected.Length];
|
||||
var task = _reader.ReadAsync(actual, offset, actual.Length - offset);
|
||||
if (!Debugger.IsAttached)
|
||||
while (offset < expected.Length)
|
||||
{
|
||||
task = task.TimeoutAfter(Timeout);
|
||||
var data = new byte[expected.Length];
|
||||
var task = _reader.ReadAsync(actual, offset, actual.Length - offset);
|
||||
if (!Debugger.IsAttached)
|
||||
{
|
||||
task = task.TimeoutAfter(Timeout);
|
||||
}
|
||||
var count = await task.ConfigureAwait(false);
|
||||
if (count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
offset += count;
|
||||
}
|
||||
var count = await task.ConfigureAwait(false);
|
||||
if (count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
offset += count;
|
||||
}
|
||||
catch (TimeoutException ex) when (offset != 0)
|
||||
{
|
||||
throw new TimeoutException($"Did not receive a complete response within {Timeout}.{Environment.NewLine}{Environment.NewLine}" +
|
||||
$"Expected:{Environment.NewLine}{expected}{Environment.NewLine}{Environment.NewLine}" +
|
||||
$"Actual:{Environment.NewLine}{new string(actual, 0, offset)}{Environment.NewLine}",
|
||||
ex);
|
||||
}
|
||||
|
||||
Assert.Equal(expected, new string(actual, 0, offset));
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ namespace CodeGenerator
|
|||
typeof(IItemsFeature),
|
||||
typeof(ITlsConnectionFeature),
|
||||
typeof(IHttpWebSocketFeature),
|
||||
typeof(ISessionFeature)
|
||||
typeof(ISessionFeature),
|
||||
typeof(IHttpMaxRequestBodySizeFeature)
|
||||
};
|
||||
|
||||
var rareFeatures = new[]
|
||||
|
|
@ -64,6 +65,7 @@ namespace CodeGenerator
|
|||
typeof(IHttpRequestIdentifierFeature),
|
||||
typeof(IHttpRequestLifetimeFeature),
|
||||
typeof(IHttpConnectionFeature),
|
||||
typeof(IHttpMaxRequestBodySizeFeature)
|
||||
};
|
||||
|
||||
return $@"// Copyright (c) .NET Foundation. All rights reserved.
|
||||
|
|
|
|||
Loading…
Reference in New Issue