Implement MaxRequestBodySize feature for IIS inprocess (#9475)
This commit is contained in:
parent
93d82b0883
commit
aaaaf572fd
|
|
@ -13,6 +13,7 @@
|
|||
#define CS_WINDOWS_AUTHENTICATION_SECTION L"system.webServer/security/authentication/windowsAuthentication"
|
||||
#define CS_BASIC_AUTHENTICATION_SECTION L"system.webServer/security/authentication/basicAuthentication"
|
||||
#define CS_ANONYMOUS_AUTHENTICATION_SECTION L"system.webServer/security/authentication/anonymousAuthentication"
|
||||
#define CS_MAX_REQUEST_BODY_SIZE_SECTION L"system.webServer/security/requestFiltering"
|
||||
|
||||
class ConfigurationSource: NonCopyable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSourc
|
|||
m_fWindowsAuthEnabled(false),
|
||||
m_fBasicAuthEnabled(false),
|
||||
m_fAnonymousAuthEnabled(false),
|
||||
m_dwMaxRequestBodySize(INFINITE),
|
||||
m_dwStartupTimeLimitInMS(INFINITE),
|
||||
m_dwShutdownTimeLimitInMS(INFINITE)
|
||||
{
|
||||
|
|
@ -71,6 +72,24 @@ InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSourc
|
|||
const auto anonAuthSection = configurationSource.GetSection(CS_ANONYMOUS_AUTHENTICATION_SECTION);
|
||||
m_fAnonymousAuthEnabled = anonAuthSection && anonAuthSection->GetBool(CS_ENABLED).value_or(false);
|
||||
|
||||
const auto requestFilteringSection = configurationSource.GetSection(CS_MAX_REQUEST_BODY_SIZE_SECTION);
|
||||
if (requestFilteringSection != nullptr)
|
||||
{
|
||||
// The requestFiltering section is enabled by default in most scenarios. However, if the value
|
||||
// maxAllowedContentLength isn't set, it defaults to 30_000_000 in IIS.
|
||||
// The section element won't be defined if the feature is disabled, so the presence of the section tells
|
||||
// us whether there should be a default or not.
|
||||
auto requestLimitSection = requestFilteringSection->GetSection(L"requestLimits").value_or(nullptr);
|
||||
if (requestLimitSection != nullptr)
|
||||
{
|
||||
m_dwMaxRequestBodySize = requestLimitSection->GetLong(L"maxAllowedContentLength").value_or(30000000);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dwMaxRequestBodySize = 30000000;
|
||||
}
|
||||
}
|
||||
|
||||
if (pSite != nullptr)
|
||||
{
|
||||
m_bindingInformation = BindingInformation::Load(configurationSource, *pSite);
|
||||
|
|
|
|||
|
|
@ -94,6 +94,12 @@ public:
|
|||
return m_dwShutdownTimeLimitInMS;
|
||||
}
|
||||
|
||||
DWORD
|
||||
QueryMaxRequestBodySizeLimit() const
|
||||
{
|
||||
return m_dwMaxRequestBodySize;
|
||||
}
|
||||
|
||||
const std::map<std::wstring, std::wstring, ignore_case_comparer>&
|
||||
QueryEnvironmentVariables() const
|
||||
{
|
||||
|
|
@ -128,6 +134,7 @@ private:
|
|||
bool m_fAnonymousAuthEnabled;
|
||||
DWORD m_dwStartupTimeLimitInMS;
|
||||
DWORD m_dwShutdownTimeLimitInMS;
|
||||
DWORD m_dwMaxRequestBodySize;
|
||||
std::map<std::wstring, std::wstring, ignore_case_comparer> m_environmentVariables;
|
||||
std::vector<BindingInformation> m_bindingInformation;
|
||||
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
|
|||
}
|
||||
|
||||
// Used to make .NET Runtime always log to event log when there is an unhandled exception.
|
||||
LOG_LAST_ERROR_IF(SetEnvironmentVariable(L"COMPlus_UseEntryPointFilter", L"1"));
|
||||
LOG_LAST_ERROR_IF(!SetEnvironmentVariable(L"COMPlus_UseEntryPointFilter", L"1"));
|
||||
|
||||
bool clrThreadExited;
|
||||
{
|
||||
|
|
|
|||
|
|
@ -191,12 +191,13 @@ struct IISConfigurationData
|
|||
BOOL fBasicAuthEnabled;
|
||||
BOOL fAnonymousAuthEnable;
|
||||
BSTR pwzBindings;
|
||||
DWORD maxRequestBodySize;
|
||||
};
|
||||
|
||||
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
|
||||
HRESULT
|
||||
http_get_application_properties(
|
||||
_In_ IISConfigurationData* pIISCofigurationData
|
||||
_In_ IISConfigurationData* pIISConfigurationData
|
||||
)
|
||||
{
|
||||
auto pInProcessApplication = IN_PROCESS_APPLICATION::GetInstance();
|
||||
|
|
@ -207,15 +208,16 @@ http_get_application_properties(
|
|||
|
||||
const auto& pConfiguration = pInProcessApplication->QueryConfig();
|
||||
|
||||
pIISCofigurationData->pInProcessApplication = pInProcessApplication;
|
||||
pIISCofigurationData->pwzFullApplicationPath = SysAllocString(pInProcessApplication->QueryApplicationPhysicalPath().c_str());
|
||||
pIISCofigurationData->pwzVirtualApplicationPath = SysAllocString(pInProcessApplication->QueryApplicationVirtualPath().c_str());
|
||||
pIISCofigurationData->fWindowsAuthEnabled = pConfiguration.QueryWindowsAuthEnabled();
|
||||
pIISCofigurationData->fBasicAuthEnabled = pConfiguration.QueryBasicAuthEnabled();
|
||||
pIISCofigurationData->fAnonymousAuthEnable = pConfiguration.QueryAnonymousAuthEnabled();
|
||||
pIISConfigurationData->pInProcessApplication = pInProcessApplication;
|
||||
pIISConfigurationData->pwzFullApplicationPath = SysAllocString(pInProcessApplication->QueryApplicationPhysicalPath().c_str());
|
||||
pIISConfigurationData->pwzVirtualApplicationPath = SysAllocString(pInProcessApplication->QueryApplicationVirtualPath().c_str());
|
||||
pIISConfigurationData->fWindowsAuthEnabled = pConfiguration.QueryWindowsAuthEnabled();
|
||||
pIISConfigurationData->fBasicAuthEnabled = pConfiguration.QueryBasicAuthEnabled();
|
||||
pIISConfigurationData->fAnonymousAuthEnable = pConfiguration.QueryAnonymousAuthEnabled();
|
||||
|
||||
auto const serverAddresses = BindingInformation::Format(pConfiguration.QueryBindings(), pInProcessApplication->QueryApplicationVirtualPath());
|
||||
pIISCofigurationData->pwzBindings = SysAllocString(serverAddresses.c_str());
|
||||
pIISConfigurationData->pwzBindings = SysAllocString(serverAddresses.c_str());
|
||||
pIISConfigurationData->maxRequestBodySize = pInProcessApplication->QueryConfig().QueryMaxRequestBodySizeLimit();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Performance
|
|||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_server = TestServer.Create(builder => builder.UseMiddleware<PlaintextMiddleware>(), new LoggerFactory()).GetAwaiter().GetResult();
|
||||
_server = TestServer.Create(builder => builder.UseMiddleware<PlaintextMiddleware>(), new LoggerFactory(), new IISServerOptions()).GetAwaiter().GetResult();
|
||||
// Recreate client, TestServer.Client has additional logging that can hurt performance
|
||||
_client = new HttpClient()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
public string AuthenticationDisplayName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
public bool AutomaticAuthentication { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
public long? MaxRequestBodySize { get { throw null; } set { } }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Hosting
|
||||
|
|
@ -27,6 +28,11 @@ namespace Microsoft.AspNetCore.Http.Features
|
|||
}
|
||||
namespace Microsoft.AspNetCore.Server.IIS
|
||||
{
|
||||
public sealed partial class BadHttpRequestException : System.IO.IOException
|
||||
{
|
||||
internal BadHttpRequestException() { }
|
||||
public int StatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
public static partial class HttpContextExtensions
|
||||
{
|
||||
public static string GetIISServerVariable(this Microsoft.AspNetCore.Http.HttpContext context, string variableName) { throw null; }
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.IISIntegration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("IIS.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
// 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.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.IIS
|
||||
{
|
||||
public sealed class BadHttpRequestException : IOException
|
||||
{
|
||||
private BadHttpRequestException(string message, int statusCode, RequestRejectionReason reason)
|
||||
: base(message)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
Reason = reason;
|
||||
}
|
||||
|
||||
public int StatusCode { get; }
|
||||
|
||||
internal RequestRejectionReason Reason { get; }
|
||||
|
||||
internal static void Throw(RequestRejectionReason reason)
|
||||
{
|
||||
throw GetException(reason);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static BadHttpRequestException GetException(RequestRejectionReason reason)
|
||||
{
|
||||
BadHttpRequestException ex;
|
||||
switch (reason)
|
||||
{
|
||||
case RequestRejectionReason.RequestBodyTooLarge:
|
||||
ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestBodyTooLarge, StatusCodes.Status413PayloadTooLarge, reason);
|
||||
break;
|
||||
default:
|
||||
ex = new BadHttpRequestException(CoreStrings.BadRequest, StatusCodes.Status400BadRequest, reason);
|
||||
break;
|
||||
}
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,5 +19,6 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
public bool fAnonymousAuthEnable;
|
||||
[MarshalAs(UnmanagedType.BStr)]
|
||||
public string pwzBindings;
|
||||
public int maxRequestBodySize;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Http.Features;
|
|||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||
using Microsoft.AspNetCore.Server.IIS.Core.IO;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||
{
|
||||
|
|
@ -28,7 +29,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
IServerVariablesFeature,
|
||||
IHttpBufferingFeature,
|
||||
ITlsConnectionFeature,
|
||||
IHttpBodyControlFeature
|
||||
IHttpBodyControlFeature,
|
||||
IHttpMaxRequestBodySizeFeature
|
||||
{
|
||||
// NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation,
|
||||
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
|
||||
|
|
@ -277,7 +279,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
Debug.Assert(_readBodyTask == null || _readBodyTask.IsCompleted);
|
||||
|
||||
// Reset reading status to allow restarting with new IO
|
||||
_hasRequestReadingStarted = false;
|
||||
HasStartedConsumingRequestBody = false;
|
||||
|
||||
// Upgrade async will cause the stream processing to go into duplex mode
|
||||
AsyncIO = new WebSocketsAsyncIOEngine(_contextLock, _pInProcessHandler);
|
||||
|
|
@ -322,6 +324,35 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
|
||||
bool IHttpBodyControlFeature.AllowSynchronousIO { get; set; }
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (value > _options.IisMaxRequestSizeLimit)
|
||||
{
|
||||
_logger.LogWarning(CoreStrings.MaxRequestLimitWarning);
|
||||
}
|
||||
|
||||
MaxRequestBodySize = value;
|
||||
}
|
||||
}
|
||||
|
||||
void IHttpBufferingFeature.DisableRequestBuffering()
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
private static readonly Type IISHttpContextType = typeof(IISHttpContext);
|
||||
private static readonly Type IServerVariablesFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature);
|
||||
private static readonly Type IHttpBufferingFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBufferingFeature);
|
||||
private static readonly Type IHttpMaxRequestBodySizeFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature);
|
||||
|
||||
private object _currentIHttpRequestFeature;
|
||||
private object _currentIHttpResponseFeature;
|
||||
|
|
@ -48,6 +49,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
private object _currentIHttpSendFileFeature;
|
||||
private object _currentIServerVariablesFeature;
|
||||
private object _currentIHttpBufferingFeature;
|
||||
private object _currentIHttpMaxRequestBodySizeFeature;
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
|
|
@ -61,6 +63,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
_currentIHttpAuthenticationFeature = this;
|
||||
_currentIServerVariablesFeature = this;
|
||||
_currentIHttpBufferingFeature = this;
|
||||
_currentIHttpMaxRequestBodySizeFeature = this;
|
||||
_currentITlsConnectionFeature = this;
|
||||
}
|
||||
|
||||
|
|
@ -146,6 +149,10 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
{
|
||||
return _currentIHttpBufferingFeature;
|
||||
}
|
||||
if (key == IHttpMaxRequestBodySizeFeature)
|
||||
{
|
||||
return _currentIHttpMaxRequestBodySizeFeature;
|
||||
}
|
||||
|
||||
return ExtraFeatureGet(key);
|
||||
}
|
||||
|
|
@ -249,6 +256,10 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
_currentIHttpBufferingFeature = feature;
|
||||
return;
|
||||
}
|
||||
if (key == IHttpMaxRequestBodySizeFeature)
|
||||
{
|
||||
_currentIHttpMaxRequestBodySizeFeature = feature;
|
||||
}
|
||||
if (key == IISHttpContextType)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot set IISHttpContext in feature collection");
|
||||
|
|
@ -334,6 +345,10 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
{
|
||||
yield return new KeyValuePair<Type, object>(IHttpBufferingFeature, _currentIHttpBufferingFeature as global::Microsoft.AspNetCore.Http.Features.IHttpBufferingFeature);
|
||||
}
|
||||
if (_currentIHttpMaxRequestBodySizeFeature != null)
|
||||
{
|
||||
yield return new KeyValuePair<Type, object>(IHttpMaxRequestBodySizeFeature, _currentIHttpMaxRequestBodySizeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature);
|
||||
}
|
||||
|
||||
if (MaybeExtra != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
{
|
||||
internal partial class IISHttpContext
|
||||
{
|
||||
private long _consumedBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the Input pipe to the user.
|
||||
/// </summary>
|
||||
|
|
@ -22,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
/// <returns></returns>
|
||||
internal async ValueTask<int> ReadAsync(Memory<byte> memory, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_hasRequestReadingStarted)
|
||||
if (!HasStartedConsumingRequestBody)
|
||||
{
|
||||
InitializeRequestIO();
|
||||
}
|
||||
|
|
@ -105,9 +107,15 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
// Read was not canceled because of incoming write or IO stopping
|
||||
if (read != -1)
|
||||
{
|
||||
_consumedBytes += read;
|
||||
_bodyInputPipe.Writer.Advance(read);
|
||||
}
|
||||
|
||||
if (_consumedBytes > MaxRequestBodySize)
|
||||
{
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
|
||||
}
|
||||
|
||||
var result = await _bodyInputPipe.Writer.FlushAsync();
|
||||
|
||||
if (result.IsCompleted || result.IsCanceled)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
private static readonly Action<ILogger, string, string, Exception> _unexpectedError =
|
||||
LoggerMessage.Define<string, string>(LogLevel.Error, new EventId(3, "UnexpectedError"), @"Unexpected exception in ""{ClassName}.{MethodName}"".");
|
||||
|
||||
private static readonly Action<ILogger, string, string, Exception> _connectionBadRequest =
|
||||
LoggerMessage.Define<string, string>(LogLevel.Information, new EventId(4, nameof(ConnectionBadRequest)), @"Connection id ""{ConnectionId}"" bad request data: ""{message}""");
|
||||
|
||||
public static void ConnectionDisconnect(ILogger logger, string connectionId)
|
||||
{
|
||||
_connectionDisconnect(logger, connectionId, null);
|
||||
|
|
@ -34,6 +37,11 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
{
|
||||
_unexpectedError(logger, className, methodName, ex);
|
||||
}
|
||||
|
||||
public static void ConnectionBadRequest(ILogger logger, string connectionId, BadHttpRequestException ex)
|
||||
{
|
||||
_connectionBadRequest(logger, connectionId, ex.Message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
protected Streams _streams;
|
||||
|
||||
private volatile bool _hasResponseStarted;
|
||||
private volatile bool _hasRequestReadingStarted;
|
||||
|
||||
private int _statusCode;
|
||||
private string _reasonPhrase;
|
||||
|
|
@ -50,6 +49,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
protected Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted;
|
||||
|
||||
protected Exception _applicationException;
|
||||
protected BadHttpRequestException _requestRejectedException;
|
||||
|
||||
private readonly MemoryPool<byte> _memoryPool;
|
||||
private readonly IISHttpServer _server;
|
||||
|
||||
|
|
@ -112,6 +113,9 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
private HeaderCollection HttpResponseHeaders { get; set; }
|
||||
internal HttpApiTypes.HTTP_VERB KnownMethod { get; private set; }
|
||||
|
||||
private bool HasStartedConsumingRequestBody { get; set; }
|
||||
public long? MaxRequestBodySize { get; set; }
|
||||
|
||||
protected void InitializeContext()
|
||||
{
|
||||
_thisHandle = GCHandle.Alloc(this);
|
||||
|
|
@ -156,6 +160,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
}
|
||||
}
|
||||
|
||||
MaxRequestBodySize = _options.MaxRequestBodySize;
|
||||
|
||||
ResetFeatureCollection();
|
||||
|
||||
if (!_server.IsWebSocketAvailable(_pInProcessHandler))
|
||||
|
|
@ -282,9 +288,14 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
|
||||
private void InitializeRequestIO()
|
||||
{
|
||||
Debug.Assert(!_hasRequestReadingStarted);
|
||||
Debug.Assert(!HasStartedConsumingRequestBody);
|
||||
|
||||
_hasRequestReadingStarted = true;
|
||||
if (RequestHeaders.ContentLength > MaxRequestBodySize)
|
||||
{
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
|
||||
}
|
||||
|
||||
HasStartedConsumingRequestBody = true;
|
||||
|
||||
EnsureIOInitialized();
|
||||
|
||||
|
|
@ -308,7 +319,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
|
||||
protected Task ProduceEnd()
|
||||
{
|
||||
if (_applicationException != null)
|
||||
if (_requestRejectedException != null || _applicationException != null)
|
||||
{
|
||||
if (HasResponseStarted)
|
||||
{
|
||||
|
|
@ -318,6 +329,10 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
|
||||
// If the request was rejected, the error state has already been set by SetBadRequestState and
|
||||
// that should take precedence.
|
||||
if (_requestRejectedException != null)
|
||||
{
|
||||
SetErrorResponseException(_requestRejectedException);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 500 Internal Server Error
|
||||
|
|
@ -461,6 +476,23 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
}
|
||||
}
|
||||
|
||||
public void SetBadRequestState(BadHttpRequestException ex)
|
||||
{
|
||||
Log.ConnectionBadRequest(_logger, RequestConnectionId, ex);
|
||||
|
||||
if (!HasResponseStarted)
|
||||
{
|
||||
SetErrorResponseException(ex);
|
||||
}
|
||||
|
||||
_requestRejectedException = ex;
|
||||
}
|
||||
|
||||
private void SetErrorResponseException(BadHttpRequestException ex)
|
||||
{
|
||||
SetErrorResponseHeaders(ex.StatusCode);
|
||||
}
|
||||
|
||||
protected void ReportApplicationError(Exception ex)
|
||||
{
|
||||
if (_applicationException == null)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,12 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
|
||||
await _application.ProcessRequestAsync(context);
|
||||
}
|
||||
catch (BadHttpRequestException ex)
|
||||
{
|
||||
SetBadRequestState(ex);
|
||||
ReportApplicationError(ex);
|
||||
success = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ReportApplicationError(ex);
|
||||
|
|
@ -59,7 +65,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
{
|
||||
await ProduceEnd();
|
||||
}
|
||||
else if (!HasResponseStarted)
|
||||
else if (!HasResponseStarted && _requestRejectedException == null)
|
||||
{
|
||||
// If the request was aborted and no response was sent, there's no
|
||||
// meaningful status code to log.
|
||||
|
|
|
|||
|
|
@ -81,6 +81,11 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
}
|
||||
|
||||
Features.Set<IServerAddressesFeature>(_serverAddressesFeature);
|
||||
|
||||
if (_options.MaxRequestBodySize > _options.IisMaxRequestSizeLimit)
|
||||
{
|
||||
_logger.LogWarning(CoreStrings.MaxRequestLimitWarning);
|
||||
}
|
||||
}
|
||||
|
||||
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
|
||||
|
|
|
|||
|
|
@ -147,4 +147,22 @@
|
|||
<data name="ParameterReadOnlyAfterResponseStarted" xml:space="preserve">
|
||||
<value>{name} cannot be set because the response has already started.</value>
|
||||
</data>
|
||||
<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>
|
||||
<data name="NonNegativeNumberOrNullRequired" xml:space="preserve">
|
||||
<value>Value must be null or a non-negative number.</value>
|
||||
</data>
|
||||
<data name="BadRequest" xml:space="preserve">
|
||||
<value>Bad request.</value>
|
||||
</data>
|
||||
<data name="MaxRequestLimitWarning" xml:space="preserve">
|
||||
<value>Increasing the MaxRequestBodySize conflicts with the max value for IIS limit maxAllowedContentLength. HTTP requests that have a content length greater than maxAllowedContentLength will still be rejected by IIS. You can disable the limit by either removing or setting the maxAllowedContentLength value to a higher limit.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
// 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 Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.IIS;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
|
|
@ -34,5 +37,33 @@ namespace Microsoft.AspNetCore.Builder
|
|||
internal bool ForwardWindowsAuthentication { get; set; } = true;
|
||||
|
||||
internal string[] ServerAddresses { get; set; }
|
||||
|
||||
// Matches the default maxAllowedContentLength in IIS (~28.6 MB)
|
||||
// https://www.iis.net/configreference/system.webserver/security/requestfiltering/requestlimits#005
|
||||
private long? _maxRequestBodySize = 30000000;
|
||||
|
||||
internal long IisMaxRequestSizeLimit;
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,6 +150,90 @@ namespace Microsoft.AspNetCore.Server.IIS
|
|||
internal static string FormatParameterReadOnlyAfterResponseStarted(object name)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ParameterReadOnlyAfterResponseStarted", "name"), name);
|
||||
|
||||
/// <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 been upgraded.
|
||||
/// </summary>
|
||||
internal static string MaxRequestBodySizeCannotBeModifiedForUpgradedRequests
|
||||
{
|
||||
get => GetString("MaxRequestBodySizeCannotBeModifiedForUpgradedRequests");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum request body size cannot be modified after the request has been upgraded.
|
||||
/// </summary>
|
||||
internal static string FormatMaxRequestBodySizeCannotBeModifiedForUpgradedRequests()
|
||||
=> GetString("MaxRequestBodySizeCannotBeModifiedForUpgradedRequests");
|
||||
|
||||
/// <summary>
|
||||
/// Value must be null or a non-negative number.
|
||||
/// </summary>
|
||||
internal static string NonNegativeNumberOrNullRequired
|
||||
{
|
||||
get => GetString("NonNegativeNumberOrNullRequired");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value must be null or a non-negative number.
|
||||
/// </summary>
|
||||
internal static string FormatNonNegativeNumberOrNullRequired()
|
||||
=> GetString("NonNegativeNumberOrNullRequired");
|
||||
|
||||
/// <summary>
|
||||
/// Bad request.
|
||||
/// </summary>
|
||||
internal static string BadRequest
|
||||
{
|
||||
get => GetString("BadRequest");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bad request.
|
||||
/// </summary>
|
||||
internal static string FormatBadRequest()
|
||||
=> GetString("BadRequest");
|
||||
|
||||
/// <summary>
|
||||
/// Increasing the MaxRequestBodySize conflicts with the max value for IIS limit maxAllowedContentLength. HTTP requests that have a content length greater than maxAllowedContentLength will still be rejected by IIS. You can disable the limit by either removing or setting the maxAllowedContentLength value to a higher limit.
|
||||
/// </summary>
|
||||
internal static string MaxRequestLimitWarning
|
||||
{
|
||||
get => GetString("MaxRequestLimitWarning");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increasing the MaxRequestBodySize conflicts with the max value for IIS limit maxAllowedContentLength. HTTP requests that have a content length greater than maxAllowedContentLength will still be rejected by IIS. You can disable the limit by either removing or setting the maxAllowedContentLength value to a higher limit.
|
||||
/// </summary>
|
||||
internal static string FormatMaxRequestLimitWarning()
|
||||
=> GetString("MaxRequestLimitWarning");
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.IIS
|
||||
{
|
||||
internal enum RequestRejectionReason
|
||||
{
|
||||
RequestBodyTooLarge
|
||||
}
|
||||
}
|
||||
|
|
@ -46,6 +46,7 @@ namespace Microsoft.AspNetCore.Hosting
|
|||
options => {
|
||||
options.ServerAddresses = iisConfigData.pwzBindings.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
options.ForwardWindowsAuthentication = iisConfigData.fWindowsAuthEnabled || iisConfigData.fBasicAuthEnabled;
|
||||
options.IisMaxRequestSizeLimit = iisConfigData.maxRequestBodySize;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
// 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.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.IIS;
|
||||
using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||
{
|
||||
[Collection(PublishedSitesCollection.Name)]
|
||||
public class MaxRequestBodySizeTests : IISFunctionalTestBase
|
||||
{
|
||||
public MaxRequestBodySizeTests(PublishedSitesFixture fixture) : base(fixture)
|
||||
{
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[RequiresNewHandler]
|
||||
public async Task MaxRequestBodySizeE2EWorks()
|
||||
{
|
||||
var deploymentParameters = Fixture.GetBaseDeploymentParameters();
|
||||
deploymentParameters.TransformArguments((a, _) => $"{a} DecreaseRequestLimit");
|
||||
|
||||
var deploymentResult = await DeployAsync(deploymentParameters);
|
||||
|
||||
var result = await deploymentResult.HttpClient.PostAsync("/ReadRequestBody", new StringContent("test"));
|
||||
Assert.Equal(HttpStatusCode.RequestEntityTooLarge, result.StatusCode);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[RequiresNewHandler]
|
||||
public async Task SetIISLimitMaxRequestBodySizeE2EWorks()
|
||||
{
|
||||
var deploymentParameters = Fixture.GetBaseDeploymentParameters();
|
||||
deploymentParameters.ServerConfigActionList.Add(
|
||||
(config, _) => {
|
||||
config
|
||||
.RequiredElement("system.webServer")
|
||||
.GetOrAdd("security")
|
||||
.GetOrAdd("requestFiltering")
|
||||
.GetOrAdd("requestLimits", "maxAllowedContentLength", "1");
|
||||
});
|
||||
var deploymentResult = await DeployAsync(deploymentParameters);
|
||||
|
||||
var result = await deploymentResult.HttpClient.PostAsync("/ReadRequestBody", new StringContent("test"));
|
||||
|
||||
// IIS returns a 404 instead of a 413...
|
||||
Assert.Equal(HttpStatusCode.NotFound, result.StatusCode);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[RequiresNewHandler]
|
||||
public async Task IISRejectsContentLengthTooLargeByDefault()
|
||||
{
|
||||
var deploymentParameters = Fixture.GetBaseDeploymentParameters();
|
||||
var deploymentResult = await DeployAsync(deploymentParameters);
|
||||
|
||||
using (var connection = new TestConnection(deploymentResult.HttpClient.BaseAddress.Port))
|
||||
{
|
||||
await connection.Send(
|
||||
"POST /HelloWorld HTTP/1.1",
|
||||
$"Content-Length: 30000001",
|
||||
"Host: localhost",
|
||||
"",
|
||||
"A");
|
||||
await connection.Receive("HTTP/1.1 404 Not Found");
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[RequiresNewHandler]
|
||||
[RequiresIIS(IISCapability.PoolEnvironmentVariables)]
|
||||
public async Task SetIISLimitMaxRequestBodyLogsWarning()
|
||||
{
|
||||
var deploymentParameters = Fixture.GetBaseDeploymentParameters();
|
||||
deploymentParameters.ServerConfigActionList.Add(
|
||||
(config, _) => {
|
||||
config
|
||||
.RequiredElement("system.webServer")
|
||||
.GetOrAdd("security")
|
||||
.GetOrAdd("requestFiltering")
|
||||
.GetOrAdd("requestLimits", "maxAllowedContentLength", "1");
|
||||
});
|
||||
var deploymentResult = await DeployAsync(deploymentParameters);
|
||||
|
||||
var result = await deploymentResult.HttpClient.PostAsync("/DecreaseRequestLimit", new StringContent("1"));
|
||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||
if (deploymentParameters.ServerType == ServerType.IISExpress)
|
||||
{
|
||||
Assert.Single(TestSink.Writes, w => w.Message.Contains("Increasing the MaxRequestBodySize conflicts with the max value for IIS limit maxAllowedContentLength." +
|
||||
" HTTP requests that have a content length greater than maxAllowedContentLength will still be rejected by IIS." +
|
||||
" You can disable the limit by either removing or setting the maxAllowedContentLength value to a higher limit."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
|
||||
<PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,339 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.IIS;
|
||||
using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace IIS.Tests
|
||||
{
|
||||
[SkipIfHostableWebCoreNotAvailable]
|
||||
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
|
||||
public class MaxRequestBodySizeTests : LoggedTest
|
||||
{
|
||||
[ConditionalFact]
|
||||
public async Task RequestBodyTooLargeContentLengthExceedsGlobalLimit()
|
||||
{
|
||||
var globalMaxRequestBodySize = 0x100000000;
|
||||
|
||||
BadHttpRequestException exception = null;
|
||||
using (var testServer = await TestServer.Create(
|
||||
async ctx =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await ctx.Request.Body.ReadAsync(new byte[2000]);
|
||||
}
|
||||
catch (BadHttpRequestException ex)
|
||||
{
|
||||
exception = ex;
|
||||
throw ex;
|
||||
}
|
||||
}, LoggerFactory))
|
||||
{
|
||||
using (var connection = testServer.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
$"Content-Length: {globalMaxRequestBodySize + 1}",
|
||||
"Host: localhost",
|
||||
"",
|
||||
"");
|
||||
await connection.Receive("HTTP/1.1 413 Payload Too Large");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, exception.Message);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task RequestBodyTooLargeContentLengthExceedingPerRequestLimit()
|
||||
{
|
||||
var maxRequestSize = 0x10000;
|
||||
var perRequestMaxRequestBodySize = 0x100;
|
||||
|
||||
BadHttpRequestException exception = null;
|
||||
using (var testServer = await TestServer.Create(
|
||||
async ctx =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var feature = ctx.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.Equal(maxRequestSize, feature.MaxRequestBodySize);
|
||||
feature.MaxRequestBodySize = perRequestMaxRequestBodySize;
|
||||
|
||||
await ctx.Request.Body.ReadAsync(new byte[2000]);
|
||||
}
|
||||
catch (BadHttpRequestException ex)
|
||||
{
|
||||
exception = ex;
|
||||
throw ex;
|
||||
}
|
||||
}, LoggerFactory, new IISServerOptions { MaxRequestBodySize = maxRequestSize }))
|
||||
{
|
||||
using (var connection = testServer.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
$"Content-Length: {perRequestMaxRequestBodySize + 1}",
|
||||
"Host: localhost",
|
||||
"",
|
||||
"");
|
||||
await connection.Receive("HTTP/1.1 413 Payload Too Large");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, exception.Message);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task DoesNotRejectRequestWithContentLengthHeaderExceedingGlobalLimitIfLimitDisabledPerRequest()
|
||||
{
|
||||
using (var testServer = await TestServer.Create(
|
||||
async ctx =>
|
||||
{
|
||||
var feature = ctx.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.Equal(0, feature.MaxRequestBodySize);
|
||||
feature.MaxRequestBodySize = null;
|
||||
|
||||
await ctx.Request.Body.ReadAsync(new byte[2000]);
|
||||
|
||||
}, LoggerFactory, new IISServerOptions { MaxRequestBodySize = 0 }))
|
||||
{
|
||||
using (var connection = testServer.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
$"Content-Length: 1",
|
||||
"Host: localhost",
|
||||
"",
|
||||
"A");
|
||||
await connection.Receive("HTTP/1.1 200 OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task DoesNotRejectRequestWithChunkedExceedingGlobalLimitIfLimitDisabledPerRequest()
|
||||
{
|
||||
using (var testServer = await TestServer.Create(
|
||||
async ctx =>
|
||||
{
|
||||
var feature = ctx.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.Equal(0, feature.MaxRequestBodySize);
|
||||
feature.MaxRequestBodySize = null;
|
||||
|
||||
await ctx.Request.Body.ReadAsync(new byte[2000]);
|
||||
|
||||
}, LoggerFactory, new IISServerOptions { MaxRequestBodySize = 0 }))
|
||||
{
|
||||
using (var connection = testServer.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
$"Transfer-Encoding: chunked",
|
||||
"Host: localhost",
|
||||
"",
|
||||
"1",
|
||||
"a",
|
||||
"0",
|
||||
"");
|
||||
await connection.Receive("HTTP/1.1 200 OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task DoesNotRejectBodylessGetRequestWithZeroMaxRequestBodySize()
|
||||
{
|
||||
using (var testServer = await TestServer.Create(
|
||||
async ctx =>
|
||||
{
|
||||
await ctx.Request.Body.ReadAsync(new byte[2000]);
|
||||
|
||||
}, LoggerFactory, new IISServerOptions { MaxRequestBodySize = 0 }))
|
||||
{
|
||||
using (var connection = testServer.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"GET / HTTP/1.1",
|
||||
"Host: localhost",
|
||||
"",
|
||||
"");
|
||||
|
||||
await connection.Receive("HTTP/1.1 200 OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task DoesNotRejectBodylessPostWithZeroContentLengthRequestWithZeroMaxRequestBodySize()
|
||||
{
|
||||
using (var testServer = await TestServer.Create(
|
||||
async ctx =>
|
||||
{
|
||||
await ctx.Request.Body.ReadAsync(new byte[2000]);
|
||||
|
||||
}, LoggerFactory, new IISServerOptions { MaxRequestBodySize = 0 }))
|
||||
{
|
||||
using (var connection = testServer.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
$"Content-Length: 0",
|
||||
"Host: localhost",
|
||||
"",
|
||||
"");
|
||||
|
||||
await connection.Receive("HTTP/1.1 200 OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task DoesNotRejectBodylessPostWithEmptyChunksRequestWithZeroMaxRequestBodySize()
|
||||
{
|
||||
using (var testServer = await TestServer.Create(
|
||||
async ctx =>
|
||||
{
|
||||
await ctx.Request.Body.ReadAsync(new byte[2000]);
|
||||
|
||||
}, LoggerFactory, new IISServerOptions { MaxRequestBodySize = 0 }))
|
||||
{
|
||||
using (var connection = testServer.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
$"Transfer-Encoding: chunked",
|
||||
"Host: localhost",
|
||||
"",
|
||||
"0",
|
||||
"",
|
||||
"");
|
||||
|
||||
await connection.Receive("HTTP/1.1 200 OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task SettingMaxRequestBodySizeAfterReadingFromRequestBodyThrows()
|
||||
{
|
||||
var perRequestMaxRequestBodySize = 0x10;
|
||||
var payloadSize = perRequestMaxRequestBodySize + 1;
|
||||
var payload = new string('A', payloadSize);
|
||||
InvalidOperationException invalidOpEx = null;
|
||||
|
||||
using (var testServer = await TestServer.Create(
|
||||
async ctx =>
|
||||
{
|
||||
var buffer = new byte[1];
|
||||
Assert.Equal(1, await ctx.Request.Body.ReadAsync(buffer, 0, 1));
|
||||
|
||||
var feature = ctx.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||
Assert.True(feature.IsReadOnly);
|
||||
|
||||
invalidOpEx = Assert.Throws<InvalidOperationException>(() =>
|
||||
feature.MaxRequestBodySize = perRequestMaxRequestBodySize);
|
||||
throw invalidOpEx;
|
||||
}, LoggerFactory))
|
||||
{
|
||||
using (var connection = testServer.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Host: localhost",
|
||||
"Content-Length: " + payloadSize,
|
||||
"",
|
||||
payload);
|
||||
await connection.Receive(
|
||||
"HTTP/1.1 500 Internal Server Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task RequestBodyTooLargeChunked()
|
||||
{
|
||||
var maxRequestSize = 0x1000;
|
||||
|
||||
BadHttpRequestException exception = null;
|
||||
using (var testServer = await TestServer.Create(
|
||||
async ctx =>
|
||||
{
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var num = await ctx.Request.Body.ReadAsync(new byte[2000]);
|
||||
}
|
||||
}
|
||||
catch (BadHttpRequestException ex)
|
||||
{
|
||||
exception = ex;
|
||||
throw ex;
|
||||
}
|
||||
}, LoggerFactory, new IISServerOptions { MaxRequestBodySize = maxRequestSize }))
|
||||
{
|
||||
using (var connection = testServer.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Transfer-Encoding: chunked",
|
||||
"Host: localhost",
|
||||
"",
|
||||
"1001",
|
||||
new string('a', 4097),
|
||||
"0",
|
||||
"");
|
||||
await connection.Receive("HTTP/1.1 413 Payload Too Large");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotNull(exception);
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, exception.Message);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task EveryReadFailsWhenContentLengthHeaderExceedsGlobalLimit()
|
||||
{
|
||||
BadHttpRequestException requestRejectedEx1 = null;
|
||||
BadHttpRequestException requestRejectedEx2 = null;
|
||||
using (var testServer = await TestServer.Create(
|
||||
async ctx =>
|
||||
{
|
||||
var buffer = new byte[1];
|
||||
requestRejectedEx1 = await Assert.ThrowsAsync<BadHttpRequestException>(
|
||||
async () => await ctx.Request.Body.ReadAsync(buffer, 0, 1));
|
||||
requestRejectedEx2 = await Assert.ThrowsAsync<BadHttpRequestException>(
|
||||
async () => await ctx.Request.Body.ReadAsync(buffer, 0, 1));
|
||||
throw requestRejectedEx2;
|
||||
}, LoggerFactory, new IISServerOptions { MaxRequestBodySize = 0 }))
|
||||
{
|
||||
using (var connection = testServer.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Host: localhost",
|
||||
"Content-Length: " + (new IISServerOptions().MaxRequestBodySize + 1),
|
||||
"",
|
||||
"");
|
||||
await connection.Receive(
|
||||
"HTTP/1.1 413 Payload Too Large");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotNull(requestRejectedEx1);
|
||||
Assert.NotNull(requestRejectedEx2);
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, requestRejectedEx1.Message);
|
||||
Assert.Equal(CoreStrings.BadRequest_RequestBodyTooLarge, requestRejectedEx2.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ using System.IO;
|
|||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Loader;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
|
@ -15,7 +14,6 @@ using System.Xml.XPath;
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
|
||||
|
|
@ -51,6 +49,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
public HttpClient HttpClient { get; private set; }
|
||||
public TestConnection CreateConnection() => new TestConnection(_currentPort);
|
||||
|
||||
private static IISServerOptions _options;
|
||||
private IWebHost _host;
|
||||
|
||||
private string _appHostConfigPath;
|
||||
|
|
@ -63,9 +62,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
public static async Task<TestServer> Create(Action<IApplicationBuilder> appBuilder, ILoggerFactory loggerFactory)
|
||||
public static async Task<TestServer> Create(Action<IApplicationBuilder> appBuilder, ILoggerFactory loggerFactory, IISServerOptions options)
|
||||
{
|
||||
await WebCoreLock.WaitAsync();
|
||||
_options = options;
|
||||
var server = new TestServer(appBuilder, loggerFactory);
|
||||
server.Start();
|
||||
(await server.HttpClient.GetAsync("/start")).EnsureSuccessStatusCode();
|
||||
|
|
@ -75,7 +75,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
|
||||
public static Task<TestServer> Create(RequestDelegate app, ILoggerFactory loggerFactory)
|
||||
{
|
||||
return Create(builder => builder.Run(app), loggerFactory);
|
||||
return Create(builder => builder.Run(app), loggerFactory, new IISServerOptions());
|
||||
}
|
||||
|
||||
public static Task<TestServer> Create(RequestDelegate app, ILoggerFactory loggerFactory, IISServerOptions options)
|
||||
{
|
||||
return Create(builder => builder.Run(app), loggerFactory, options);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
|
|
@ -125,14 +130,16 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
|
||||
private int Main(IntPtr argc, IntPtr argv)
|
||||
{
|
||||
_host = new WebHostBuilder()
|
||||
var builder = new WebHostBuilder()
|
||||
.UseIIS()
|
||||
.ConfigureServices(services => {
|
||||
services.AddSingleton<IStartup>(this);
|
||||
services.AddSingleton(_loggerFactory);
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.Configure<IISServerOptions>(options => options.MaxRequestBodySize = _options.MaxRequestBodySize);
|
||||
services.AddSingleton<IStartup>(this);
|
||||
services.AddSingleton(_loggerFactory);
|
||||
})
|
||||
.UseSetting(WebHostDefaults.ApplicationKey, typeof(TestServer).GetTypeInfo().Assembly.FullName)
|
||||
.Build();
|
||||
.UseSetting(WebHostDefaults.ApplicationKey, typeof(TestServer).GetTypeInfo().Assembly.FullName);
|
||||
_host = builder.Build();
|
||||
|
||||
var doneEvent = new ManualResetEventSlim();
|
||||
var lifetime = _host.Services.GetService<IHostApplicationLifetime>();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Server.IIS.csproj" />
|
||||
<ProjectReference Include="..\Common.Tests\Common.Tests.csproj" />
|
||||
<ProjectReference Include="$(RepositoryRoot)src\Hosting\Server.IntegrationTesting\src\Microsoft.AspNetCore.Server.IntegrationTesting.csproj" />
|
||||
<ProjectReference Include="$(RepositoryRoot)src\Servers\IIS\IntegrationTesting.IIS\src\Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj" />
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<TestAssetOutputName>InProcessForwardsCompatWebSite</TestAssetOutputName>
|
||||
<DefineConstants>FORWARDCOMPAT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
|
||||
|
|
|
|||
|
|
@ -100,6 +100,27 @@ namespace TestSite
|
|||
case "ConsoleWriteStartServer":
|
||||
Console.WriteLine("TEST MESSAGE");
|
||||
return StartServer();
|
||||
#if !FORWARDCOMPAT
|
||||
case "DecreaseRequestLimit":
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.ConfigureLogging((_, factory) =>
|
||||
{
|
||||
factory.AddConsole();
|
||||
factory.AddFilter("Console", level => level >= LogLevel.Information);
|
||||
})
|
||||
.UseIIS()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.Configure<IISServerOptions>(options => options.MaxRequestBodySize = 2);
|
||||
})
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
return StartServer();
|
||||
|
||||
|
|
|
|||
|
|
@ -284,7 +284,6 @@ namespace TestSite
|
|||
{
|
||||
result = await ctx.Request.Body.ReadAsync(readBuffer, 0, 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int _requestsInFlight = 0;
|
||||
|
|
|
|||
|
|
@ -153,7 +153,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
|
|||
{
|
||||
try
|
||||
{
|
||||
|
||||
// Handle cases where debug file is redirected by test
|
||||
var debugLogLocations = new List<string>();
|
||||
if (IISDeploymentParameters.HandlerSettings.ContainsKey("debugFile"))
|
||||
|
|
@ -193,8 +192,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unable to find non-empty debug log files. Tried: {string.Join(", ", debugLogLocations)}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
|
|||
|
||||
public static string DecodeAndUnescapePath(Span<byte> rawUrlBytes)
|
||||
{
|
||||
Debug.Assert(rawUrlBytes.Length > 0, "Length of the URL cannot be zero.");
|
||||
Debug.Assert(rawUrlBytes.Length != 0, "Length of the URL cannot be zero.");
|
||||
var rawPath = RawUrlHelper.GetPath(rawUrlBytes);
|
||||
|
||||
if (rawPath.Length == 0)
|
||||
|
|
|
|||
Loading…
Reference in New Issue