Skip to content
Merged
42 changes: 38 additions & 4 deletions src/Http/Http/src/Features/FormFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ namespace Microsoft.AspNetCore.Http.Features;
/// </summary>
public class FormFeature : IFormFeature
{
private readonly HttpRequest _request;
private readonly HttpRequest? _request;
private readonly Endpoint? _endpoint;
private FormOptions _options;
private Task<IFormCollection>? _parsedFormTask;
private IFormCollection? _form;
private MediaTypeHeaderValue? _formContentType; // null iff _form is null

/// <summary>
/// Initializes a new instance of <see cref="FormFeature"/>.
Expand All @@ -31,7 +32,7 @@ public FormFeature(IFormCollection form)
ArgumentNullException.ThrowIfNull(form);

Form = form;
_request = default!;
_formContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
_options = FormOptions.Default;
}

Expand Down Expand Up @@ -71,8 +72,19 @@ private MediaTypeHeaderValue? ContentType
{
get
{
_ = MediaTypeHeaderValue.TryParse(_request.ContentType, out var mt);
return mt;
MediaTypeHeaderValue? mt = null;

if (_request is not null)
{
_ = MediaTypeHeaderValue.TryParse(_request.ContentType, out mt);
}

if (_form is not null && mt is null)
{
mt = _formContentType;
}

return mt;
}
}

Expand All @@ -87,6 +99,11 @@ public bool HasFormContentType
return true;
}

if (_request is null)
{
return false;
}

var contentType = ContentType;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I think it would be clearer to check whether contentType is null (rather than testing _request) - the fact that a null _request yields a null ContentType seems like an implementation detail.

return HasApplicationFormContentType(contentType) || HasMultipartFormContentType(contentType);
}
Expand All @@ -106,6 +123,14 @@ public IFormCollection? Form
{
_parsedFormTask = null;
_form = value;
if (_form is null)
{
_formContentType = null;
}
else
{
_formContentType ??= new MediaTypeHeaderValue("application/x-www-form-urlencoded");
}
}
}

Expand Down Expand Up @@ -151,6 +176,11 @@ public Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken)

private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancellationToken)
{
if (_request is null)
{
throw new InvalidOperationException("Cannot read form from this request. Request is 'null'.");
}

HandleUncheckedAntiforgeryValidationFeature();
_options = _endpoint is null ? _options : GetFormOptionsFromMetadata(_options, _endpoint);

Expand Down Expand Up @@ -326,6 +356,10 @@ private static bool HasMultipartFormContentType([NotNullWhen(true)] MediaTypeHea

private bool ResolveHasInvalidAntiforgeryValidationFeature()
{
if (_request is null)
{
return false;
}
var hasInvokedMiddleware = _request.HttpContext.Items.ContainsKey("__AntiforgeryMiddlewareWithEndpointInvoked");
var hasInvalidToken = _request.HttpContext.Features.Get<IAntiforgeryValidationFeature>() is { IsValid: false };
return hasInvokedMiddleware && hasInvalidToken;
Expand Down