Added RequestFormLimits filter.
[Fixes #5128] Overriding Request Form max upload limit
This commit is contained in:
parent
e114911d77
commit
23b7d8f62a
|
|
@ -199,10 +199,11 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>());
|
||||
|
||||
//
|
||||
// RequestSizeLimit filters
|
||||
// Request body limit filters
|
||||
//
|
||||
services.TryAddTransient<RequestSizeLimitFilter>();
|
||||
services.TryAddTransient<DisableRequestSizeLimitFilter>();
|
||||
services.TryAddTransient<RequestFormLimitsFilter>();
|
||||
|
||||
// Error description
|
||||
services.TryAddSingleton<IErrorDescriptionFactory, DefaultErrorDescriptorFactory>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// A marker interface for filters which define a policy for limits on a request's body read as a form.
|
||||
/// </summary>
|
||||
public interface IRequestFormLimitsPolicy : IFilterMetadata
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -77,6 +77,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
private static readonly Action<ILogger, string, Exception> _maxRequestBodySizeSet;
|
||||
private static readonly Action<ILogger, Exception> _requestBodySizeLimitDisabled;
|
||||
|
||||
private static readonly Action<ILogger, Exception> _cannotApplyRequestFormLimits;
|
||||
private static readonly Action<ILogger, Exception> _appliedRequestFormLimits;
|
||||
|
||||
|
||||
static MvcCoreLoggerExtensions()
|
||||
{
|
||||
_actionExecuting = LoggerMessage.Define<string>(
|
||||
|
|
@ -268,6 +272,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
LogLevel.Debug,
|
||||
3,
|
||||
"The request body size limit has been disabled.");
|
||||
|
||||
_cannotApplyRequestFormLimits = LoggerMessage.Define(
|
||||
LogLevel.Warning,
|
||||
1,
|
||||
"Unable to apply configured form options since the request form has already been read.");
|
||||
|
||||
_appliedRequestFormLimits = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
2,
|
||||
"Applied the configured form options on the current request.");
|
||||
}
|
||||
|
||||
public static IDisposable ActionScope(this ILogger logger, ActionDescriptor action)
|
||||
|
|
@ -568,6 +582,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
_requestBodySizeLimitDisabled(logger, null);
|
||||
}
|
||||
|
||||
public static void CannotApplyRequestFormLimits(this ILogger logger)
|
||||
{
|
||||
_cannotApplyRequestFormLimits(logger, null);
|
||||
}
|
||||
|
||||
public static void AppliedRequestFormLimits(this ILogger logger)
|
||||
{
|
||||
_appliedRequestFormLimits(logger, null);
|
||||
}
|
||||
|
||||
private class ActionLogScope : IReadOnlyList<KeyValuePair<string, object>>
|
||||
{
|
||||
private readonly ActionDescriptor _action;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter that configures <see cref="FormOptions"/> for the current request.
|
||||
/// </summary>
|
||||
public class RequestFormLimitsFilter : IAuthorizationFilter, IRequestFormLimitsPolicy
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RequestFormLimitsFilter(ILoggerFactory loggerFactory)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<RequestFormLimitsFilter>();
|
||||
}
|
||||
|
||||
public FormOptions FormOptions { get; set; }
|
||||
|
||||
public void OnAuthorization(AuthorizationFilterContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.IsEffectivePolicy<IRequestFormLimitsPolicy>(this))
|
||||
{
|
||||
var features = context.HttpContext.Features;
|
||||
var formFeature = features.Get<IFormFeature>();
|
||||
|
||||
if (formFeature == null || formFeature.Form == null)
|
||||
{
|
||||
// Request form has not been read yet, so set the limits
|
||||
features.Set<IFormFeature>(new FormFeature(context.HttpContext.Request, FormOptions));
|
||||
_logger.AppliedRequestFormLimits();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.CannotApplyRequestFormLimits();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the specified limits to the <see cref="HttpRequest.Form"/>.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class RequestFormLimitsAttribute : Attribute, IFilterFactory, IOrderedFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the order value for determining the order of execution of filters. Filters execute in
|
||||
/// ascending numeric value of the <see cref="Order"/> property.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Filters are executed in an ordering determined by an ascending sort of the <see cref="Order"/> property.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The default Order for this attribute is 900 because it must run before ValidateAntiForgeryTokenAttribute and
|
||||
/// after any filter which does authentication or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Look at <see cref="IOrderedFilter.Order"/> for more detailed info.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public int Order { get; set; } = 900;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReusable => true;
|
||||
|
||||
// Internal for unit testing
|
||||
internal FormOptions FormOptions { get; } = new FormOptions();
|
||||
|
||||
/// <summary>
|
||||
/// Enables full request body buffering. Use this if multiple components need to read the raw stream.
|
||||
/// The default value is false.
|
||||
/// </summary>
|
||||
public bool BufferBody
|
||||
{
|
||||
get => FormOptions.BufferBody;
|
||||
set => FormOptions.BufferBody = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="BufferBody"/> is enabled, this many bytes of the body will be buffered in memory.
|
||||
/// If this threshold is exceeded then the buffer will be moved to a temp file on disk instead.
|
||||
/// This also applies when buffering individual multipart section bodies.
|
||||
/// </summary>
|
||||
public int MemoryBufferThreshold
|
||||
{
|
||||
get => FormOptions.MemoryBufferThreshold;
|
||||
set => FormOptions.MemoryBufferThreshold = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="BufferBody"/> is enabled, this is the limit for the total number of bytes that will
|
||||
/// be buffered. Forms that exceed this limit will throw an <see cref="InvalidDataException"/> when parsed.
|
||||
/// </summary>
|
||||
public long BufferBodyLengthLimit
|
||||
{
|
||||
get => FormOptions.BufferBodyLengthLimit;
|
||||
set => FormOptions.BufferBodyLengthLimit = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A limit for the number of form entries to allow.
|
||||
/// Forms that exceed this limit will throw an <see cref="InvalidDataException"/> when parsed.
|
||||
/// </summary>
|
||||
public int ValueCountLimit
|
||||
{
|
||||
get => FormOptions.ValueCountLimit;
|
||||
set => FormOptions.ValueCountLimit = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A limit on the length of individual keys. Forms containing keys that exceed this limit will
|
||||
/// throw an <see cref="InvalidDataException"/> when parsed.
|
||||
/// </summary>
|
||||
public int KeyLengthLimit
|
||||
{
|
||||
get => FormOptions.KeyLengthLimit;
|
||||
set => FormOptions.KeyLengthLimit = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A limit on the length of individual form values. Forms containing values that exceed this
|
||||
/// limit will throw an <see cref="InvalidDataException"/> when parsed.
|
||||
/// </summary>
|
||||
public int ValueLengthLimit
|
||||
{
|
||||
get => FormOptions.ValueLengthLimit;
|
||||
set => FormOptions.ValueLengthLimit = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A limit for the length of the boundary identifier. Forms with boundaries that exceed this
|
||||
/// limit will throw an <see cref="InvalidDataException"/> when parsed.
|
||||
/// </summary>
|
||||
public int MultipartBoundaryLengthLimit
|
||||
{
|
||||
get => FormOptions.MultipartBoundaryLengthLimit;
|
||||
set => FormOptions.MultipartBoundaryLengthLimit = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A limit for the number of headers to allow in each multipart section. Headers with the same name will
|
||||
/// be combined. Form sections that exceed this limit will throw an <see cref="InvalidDataException"/>
|
||||
/// when parsed.
|
||||
/// </summary>
|
||||
public int MultipartHeadersCountLimit
|
||||
{
|
||||
get => FormOptions.MultipartHeadersCountLimit;
|
||||
set => FormOptions.MultipartHeadersCountLimit = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A limit for the total length of the header keys and values in each multipart section.
|
||||
/// Form sections that exceed this limit will throw an <see cref="InvalidDataException"/> when parsed.
|
||||
/// </summary>
|
||||
public int MultipartHeadersLengthLimit
|
||||
{
|
||||
get => FormOptions.MultipartHeadersLengthLimit;
|
||||
set => FormOptions.MultipartHeadersLengthLimit = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A limit for the length of each multipart body. Forms sections that exceed this limit will throw an
|
||||
/// <see cref="InvalidDataException"/> when parsed.
|
||||
/// </summary>
|
||||
public long MultipartBodyLengthLimit
|
||||
{
|
||||
get => FormOptions.MultipartBodyLengthLimit;
|
||||
set => FormOptions.MultipartBodyLengthLimit = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
|
||||
{
|
||||
var filter = serviceProvider.GetRequiredService<RequestFormLimitsFilter>();
|
||||
filter.FormOptions = FormOptions;
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
// 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 Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
{
|
||||
public class RequestFormLimitsFilterTest
|
||||
{
|
||||
[Fact]
|
||||
public void SetsRequestFormFeature_WhenFeatureIsNotPresent()
|
||||
{
|
||||
// Arrange
|
||||
var requestFormLimitsFilter = new RequestFormLimitsFilter(NullLoggerFactory.Instance);
|
||||
requestFormLimitsFilter.FormOptions = new FormOptions();
|
||||
var authorizationFilterContext = CreateauthorizationFilterContext(
|
||||
new IFilterMetadata[] { requestFormLimitsFilter });
|
||||
// Set to null explicitly as we want to make sure the filter adds one
|
||||
authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(null);
|
||||
|
||||
// Act
|
||||
requestFormLimitsFilter.OnAuthorization(authorizationFilterContext);
|
||||
|
||||
// Assert
|
||||
var formFeature = authorizationFilterContext.HttpContext.Features.Get<IFormFeature>();
|
||||
Assert.IsType<FormFeature>(formFeature);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetsRequestFormFeature_WhenFeatureIsPresent_ButFormIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var requestFormLimitsFilter = new RequestFormLimitsFilter(NullLoggerFactory.Instance);
|
||||
requestFormLimitsFilter.FormOptions = new FormOptions();
|
||||
var authorizationFilterContext = CreateauthorizationFilterContext(
|
||||
new IFilterMetadata[] { requestFormLimitsFilter });
|
||||
var oldFormFeature = new FormFeature(authorizationFilterContext.HttpContext.Request);
|
||||
// Set to null explicitly as we want to make sure the filter adds one
|
||||
authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(oldFormFeature);
|
||||
|
||||
// Act
|
||||
requestFormLimitsFilter.OnAuthorization(authorizationFilterContext);
|
||||
|
||||
// Assert
|
||||
var actualFormFeature = authorizationFilterContext.HttpContext.Features.Get<IFormFeature>();
|
||||
Assert.NotSame(oldFormFeature, actualFormFeature);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LogsCannotApplyRequestFormLimits()
|
||||
{
|
||||
// Arrange
|
||||
var sink = new TestSink();
|
||||
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||
|
||||
var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory);
|
||||
requestFormLimitsFilter.FormOptions = new FormOptions();
|
||||
var authorizationFilterContext = CreateauthorizationFilterContext(
|
||||
new IFilterMetadata[] { requestFormLimitsFilter });
|
||||
authorizationFilterContext.HttpContext.Request.Form = new FormCollection(null);
|
||||
|
||||
// Act
|
||||
requestFormLimitsFilter.OnAuthorization(authorizationFilterContext);
|
||||
|
||||
// Assert
|
||||
var write = Assert.Single(sink.Writes);
|
||||
Assert.Equal(LogLevel.Warning, write.LogLevel);
|
||||
Assert.Equal(
|
||||
"Unable to apply configured form options since the request form has already been read.",
|
||||
write.State.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LogsAppliedRequestFormLimits_WhenFormFeatureIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var sink = new TestSink();
|
||||
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||
|
||||
var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory);
|
||||
requestFormLimitsFilter.FormOptions = new FormOptions();
|
||||
var authorizationFilterContext = CreateauthorizationFilterContext(
|
||||
new IFilterMetadata[] { requestFormLimitsFilter });
|
||||
// Set to null explicitly as we want to make sure the filter adds one
|
||||
authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(null);
|
||||
|
||||
// Act
|
||||
requestFormLimitsFilter.OnAuthorization(authorizationFilterContext);
|
||||
|
||||
// Assert
|
||||
var write = Assert.Single(sink.Writes);
|
||||
Assert.Equal(LogLevel.Debug, write.LogLevel);
|
||||
Assert.Equal(
|
||||
"Applied the configured form options on the current request.",
|
||||
write.State.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LogsAppliedRequestFormLimits_WhenFormFeatureIsPresent_ButFormIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var sink = new TestSink();
|
||||
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||
|
||||
var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory);
|
||||
requestFormLimitsFilter.FormOptions = new FormOptions();
|
||||
var authorizationFilterContext = CreateauthorizationFilterContext(
|
||||
new IFilterMetadata[] { requestFormLimitsFilter });
|
||||
// Set to null explicitly as we want to make sure the filter adds one
|
||||
authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(
|
||||
new FormFeature(authorizationFilterContext.HttpContext.Request));
|
||||
|
||||
// Act
|
||||
requestFormLimitsFilter.OnAuthorization(authorizationFilterContext);
|
||||
|
||||
// Assert
|
||||
var write = Assert.Single(sink.Writes);
|
||||
Assert.Equal(LogLevel.Debug, write.LogLevel);
|
||||
Assert.Equal(
|
||||
"Applied the configured form options on the current request.",
|
||||
write.State.ToString());
|
||||
}
|
||||
|
||||
private static AuthorizationFilterContext CreateauthorizationFilterContext(IFilterMetadata[] filters)
|
||||
{
|
||||
return new AuthorizationFilterContext(CreateActionContext(), filters);
|
||||
}
|
||||
|
||||
private static ActionContext CreateActionContext()
|
||||
{
|
||||
return new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
// 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.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
public class RequestFormLimitsAttributeTest
|
||||
{
|
||||
[Fact]
|
||||
public void AllPublicProperties_OfFormOptions_AreExposed()
|
||||
{
|
||||
// Arrange
|
||||
var formOptionsProperties = GetProperties(typeof(FormOptions));
|
||||
var formLimitsAttributeProperties = GetProperties(typeof(RequestFormLimitsAttribute));
|
||||
|
||||
// Act & Assert
|
||||
foreach (var property in formOptionsProperties)
|
||||
{
|
||||
var formLimiAttributeProperty = formLimitsAttributeProperties
|
||||
.Where(pi => property.Name == pi.Name && pi.PropertyType == property.PropertyType)
|
||||
.SingleOrDefault();
|
||||
Assert.NotNull(formLimiAttributeProperty);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreatesFormOptions_WithDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var formOptionsProperties = GetProperties(typeof(FormOptions));
|
||||
var formLimitsAttributeProperties = GetProperties(typeof(RequestFormLimitsAttribute));
|
||||
var formOptions = new FormOptions();
|
||||
|
||||
// Act
|
||||
var requestFormLimitsAttribute = new RequestFormLimitsAttribute();
|
||||
|
||||
// Assert
|
||||
foreach (var formOptionsProperty in formOptionsProperties)
|
||||
{
|
||||
var formLimitsAttributeProperty = formLimitsAttributeProperties
|
||||
.Where(pi => pi.Name == formOptionsProperty.Name && pi.PropertyType == formOptionsProperty.PropertyType)
|
||||
.SingleOrDefault();
|
||||
|
||||
Assert.Equal(
|
||||
formOptionsProperty.GetValue(formOptions),
|
||||
formLimitsAttributeProperty.GetValue(requestFormLimitsAttribute));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdatesFormOptions_WithOverridenValues()
|
||||
{
|
||||
// Arrange
|
||||
var requestFormLimitsAttribute = new RequestFormLimitsAttribute();
|
||||
|
||||
// Act
|
||||
requestFormLimitsAttribute.BufferBody = true;
|
||||
requestFormLimitsAttribute.BufferBodyLengthLimit = 0;
|
||||
requestFormLimitsAttribute.KeyLengthLimit = 0;
|
||||
requestFormLimitsAttribute.MemoryBufferThreshold = 0;
|
||||
requestFormLimitsAttribute.MultipartBodyLengthLimit = 0;
|
||||
requestFormLimitsAttribute.MultipartBoundaryLengthLimit = 0;
|
||||
requestFormLimitsAttribute.MultipartHeadersCountLimit = 0;
|
||||
requestFormLimitsAttribute.MultipartHeadersLengthLimit = 0;
|
||||
requestFormLimitsAttribute.ValueCountLimit = 0;
|
||||
requestFormLimitsAttribute.ValueLengthLimit = 0;
|
||||
|
||||
// Assert
|
||||
Assert.True(requestFormLimitsAttribute.FormOptions.BufferBody);
|
||||
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.BufferBodyLengthLimit);
|
||||
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.KeyLengthLimit);
|
||||
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.MemoryBufferThreshold);
|
||||
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.MultipartBodyLengthLimit);
|
||||
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.MultipartBoundaryLengthLimit);
|
||||
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.MultipartHeadersCountLimit);
|
||||
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.MultipartHeadersLengthLimit);
|
||||
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.ValueCountLimit);
|
||||
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.ValueLengthLimit);
|
||||
}
|
||||
|
||||
private PropertyInfo[] GetProperties(Type type)
|
||||
{
|
||||
return type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RequestFormLimitsTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupRequestLimitSize>>
|
||||
{
|
||||
public RequestFormLimitsTest(MvcTestFixture<BasicWebSite.StartupRequestLimitSize> fixture)
|
||||
{
|
||||
Client = fixture.Client;
|
||||
}
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[Fact]
|
||||
public async Task RequestFormLimitCheckHappens_BeforeAntiforgeryTokenValidation()
|
||||
{
|
||||
// Arrange
|
||||
var request = new HttpRequestMessage();
|
||||
var kvps = new List<KeyValuePair<string, string>>();
|
||||
// Controller has value count limit of 2
|
||||
kvps.Add(new KeyValuePair<string, string>("key1", "value1"));
|
||||
kvps.Add(new KeyValuePair<string, string>("key2", "value2"));
|
||||
kvps.Add(new KeyValuePair<string, string>("key3", "value3"));
|
||||
kvps.Add(new KeyValuePair<string, string>("RequestVerificationToken", "invalid-data"));
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsync(
|
||||
"RequestFormLimits/RequestFormLimitsBeforeAntiforgeryValidation",
|
||||
new FormUrlEncodedContent(kvps));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
var result = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains(
|
||||
"InvalidDataException: Form value count limit 2 exceeded.",
|
||||
result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OverridesControllerLevelLimits()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "{\"sampleInt\":10,\"sampleString\":null}";
|
||||
var request = new HttpRequestMessage();
|
||||
var kvps = new List<KeyValuePair<string, string>>();
|
||||
// Controller has a value count limit of 2, but the action has a limit of 5
|
||||
kvps.Add(new KeyValuePair<string, string>("key1", "value1"));
|
||||
kvps.Add(new KeyValuePair<string, string>("key2", "value2"));
|
||||
kvps.Add(new KeyValuePair<string, string>("SampleInt", "10"));
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsync(
|
||||
"RequestFormLimits/OverrideControllerLevelLimits",
|
||||
new FormUrlEncodedContent(kvps));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var result = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OverrideControllerLevelLimits_UsingDefaultLimits()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "{\"sampleInt\":50,\"sampleString\":null}";
|
||||
var request = new HttpRequestMessage();
|
||||
var kvps = new List<KeyValuePair<string, string>>();
|
||||
// Controller has a key limit of 2, but the action has default limits
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
kvps.Add(new KeyValuePair<string, string>($"key{i}", $"value{i}"));
|
||||
}
|
||||
kvps.Add(new KeyValuePair<string, string>("SampleInt", "50"));
|
||||
kvps.Add(new KeyValuePair<string, string>("RequestVerificationToken", "invalid-data"));
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsync(
|
||||
"RequestFormLimits/OverrideControllerLevelLimitsUsingDefaultLimits",
|
||||
new FormUrlEncodedContent(kvps));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var result = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RequestSizeLimitCheckHappens_BeforeRequestFormLimits()
|
||||
{
|
||||
// Arrange
|
||||
var request = new HttpRequestMessage();
|
||||
var kvps = new List<KeyValuePair<string, string>>();
|
||||
// Request size has a limit of 100 bytes
|
||||
// Request form limits has a value count limit of 2
|
||||
// Antiforgery validation is also present
|
||||
kvps.Add(new KeyValuePair<string, string>("key1", new string('a', 1024)));
|
||||
kvps.Add(new KeyValuePair<string, string>("key2", "value2"));
|
||||
kvps.Add(new KeyValuePair<string, string>("RequestVerificationToken", "invalid-data"));
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsync(
|
||||
"RequestFormLimits/RequestSizeLimitBeforeRequestFormLimits",
|
||||
new FormUrlEncodedContent(kvps));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
var result = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains(
|
||||
"InvalidOperationException: Request content size is greater than the limit size",
|
||||
result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RequestFormLimitsCheckHappens_AfterRequestSizeLimit()
|
||||
{
|
||||
// Arrange
|
||||
var request = new HttpRequestMessage();
|
||||
var kvps = new List<KeyValuePair<string, string>>();
|
||||
// Request size has a limit of 100 bytes
|
||||
// Request form limits has a value count limit of 2
|
||||
// Antiforgery validation is also present
|
||||
kvps.Add(new KeyValuePair<string, string>("key1", "value1"));
|
||||
kvps.Add(new KeyValuePair<string, string>("key1", "value2"));
|
||||
kvps.Add(new KeyValuePair<string, string>("key1", "value3"));
|
||||
kvps.Add(new KeyValuePair<string, string>("RequestVerificationToken", "invalid-data"));
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsync(
|
||||
"RequestFormLimits/RequestSizeLimitBeforeRequestFormLimits",
|
||||
new FormUrlEncodedContent(kvps));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
var result = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains(
|
||||
"InvalidDataException: Form value count limit 2 exceeded.",
|
||||
result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AntiforgeryValidationHappens_AfterRequestFormAndSizeLimitCheck()
|
||||
{
|
||||
// Arrange
|
||||
var request = new HttpRequestMessage();
|
||||
var kvps = new List<KeyValuePair<string, string>>();
|
||||
// Request size has a limit of 100 bytes
|
||||
// Request form limits has a value count limit of 2
|
||||
// Antiforgery validation is also present
|
||||
kvps.Add(new KeyValuePair<string, string>("key1", "value1"));
|
||||
kvps.Add(new KeyValuePair<string, string>("RequestVerificationToken", "invalid-data"));
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsync(
|
||||
"RequestFormLimits/RequestSizeLimitBeforeRequestFormLimits",
|
||||
new FormUrlEncodedContent(kvps));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// 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 BasicWebSite.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BasicWebSite.Controllers
|
||||
{
|
||||
[RequestFormLimits(ValueCountLimit = 2)]
|
||||
public class RequestFormLimitsController : Controller
|
||||
{
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult RequestFormLimitsBeforeAntiforgeryValidation(Product product)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
return Json(product);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[RequestFormLimits(ValueCountLimit = 5)]
|
||||
public IActionResult OverrideControllerLevelLimits(Product product)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
return Json(product);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[RequestFormLimits]
|
||||
public IActionResult OverrideControllerLevelLimitsUsingDefaultLimits(Product product)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
return Json(product);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[RequestFormLimits(ValueCountLimit = 2)]
|
||||
[RequestSizeLimit(100)]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult RequestSizeLimitBeforeRequestFormLimits(Product product)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
return Json(product);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue