Skip to content

Commit fbe90bb

Browse files
authored
Set form options for form-based request types (#50016)
* Set form options for form-based request types * Remove MediaTypeHeaderValue * Tweak
1 parent e74ec45 commit fbe90bb

File tree

7 files changed

+74
-16
lines changed

7 files changed

+74
-16
lines changed

src/Antiforgery/src/AntiforgeryMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public Task Invoke(HttpContext context)
2323
}
2424

2525
var method = context.Request.Method;
26-
if (!HttpMethodExtensions.IsValidHttpMethodForForm(method))
26+
if (!HttpExtensions.IsValidHttpMethodForForm(method))
2727
{
2828
return _next(context);
2929
}

src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
</ItemGroup>
2626

2727
<ItemGroup>
28-
<Compile Include="$(SharedSourceRoot)HttpMethodExtensions.cs" LinkBase="Shared"/>
28+
<Compile Include="$(SharedSourceRoot)HttpExtensions.cs" LinkBase="Shared"/>
2929
<Compile Include="$(SharedSourceRoot)Reroute.cs" LinkBase="Shared"/>
3030
</ItemGroup>
3131
</Project>

src/Http/Routing/src/EndpointRoutingMiddleware.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,14 @@ private Task SetRoutingAndContinue(HttpContext httpContext)
139139
// We do this during endpoint routing to ensure that successive middlewares in the pipeline
140140
// can access the feature with the correct value.
141141
SetMaxRequestBodySize(httpContext);
142-
// Map IFormOptionsMetadata to IFormFeature if present on the endpoint.
143-
SetFormOptions(httpContext, endpoint);
142+
// Map IFormOptionsMetadata to IFormFeature if present on the endpoint if the
143+
// request being processed is a form request. Note: we do a manual check for the
144+
// form content-types here to avoid prematurely accessing the form feature.
145+
if (HttpExtensions.IsValidHttpMethodForForm(httpContext.Request.Method) &&
146+
HttpExtensions.IsValidContentTypeForForm(httpContext.Request.ContentType))
147+
{
148+
SetFormOptions(httpContext, endpoint);
149+
}
144150

145151
var shortCircuitMetadata = endpoint.Metadata.GetMetadata<ShortCircuitMetadata>();
146152
if (shortCircuitMetadata is not null)
@@ -177,7 +183,7 @@ private Task ExecuteShortCircuit(ShortCircuitMetadata shortCircuitMetadata, Endp
177183

178184
if (endpoint.Metadata.GetMetadata<IAntiforgeryMetadata>() is { RequiresValidation: true } &&
179185
httpContext.Request.Method is {} method &&
180-
HttpMethodExtensions.IsValidHttpMethodForForm(method))
186+
HttpExtensions.IsValidHttpMethodForForm(method))
181187
{
182188
ThrowCannotShortCircuitAnAntiforgeryRouteException(endpoint);
183189
}

src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
<Compile Include="$(SharedSourceRoot)HttpParseResult.cs" LinkBase="Shared" />
3737
<Compile Include="$(SharedSourceRoot)Debugger\DebuggerHelpers.cs" LinkBase="Shared" />
3838
<Compile Include="$(SharedSourceRoot)AntiforgeryMetadata.cs" LinkBase="Shared" />
39-
<Compile Include="$(SharedSourceRoot)HttpMethodExtensions.cs" LinkBase="Shared" />
39+
<Compile Include="$(SharedSourceRoot)HttpExtensions.cs" LinkBase="Shared" />
4040
</ItemGroup>
4141

4242
<ItemGroup>

src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareFormOptionsTest.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,42 @@ public async Task SettingEndpointManuallyDoesNotOverwriteOptions()
186186
Assert.Equal(FormOptions.DefaultMemoryBufferThreshold, formOptions.MemoryBufferThreshold);
187187
}
188188

189+
[Fact]
190+
public async Task OptionsNotSetForNonFormRequests()
191+
{
192+
var sink = new TestSink(TestSink.EnableWithTypeName<EndpointRoutingMiddleware>);
193+
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
194+
var logger = new Logger<EndpointRoutingMiddleware>(loggerFactory);
195+
var httpContext = new DefaultHttpContext();
196+
var endpointMetadata = new FormOptionsMetadata(bufferBody: true, valueCountLimit: 70);
197+
var endpoint = new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(endpointMetadata), "myapp");
198+
199+
var formOptionsMetadata = new FormOptionsMetadata(bufferBody: false, valueCountLimit: 54);
200+
var middleware = CreateMiddleware(
201+
logger: logger,
202+
matcherFactory: new TestMatcherFactory(isHandled: true, setEndpointCallback: c =>
203+
{
204+
c.SetEndpoint(new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(formOptionsMetadata), "myapp"));
205+
}));
206+
207+
// Act
208+
await middleware.Invoke(httpContext);
209+
httpContext.SetEndpoint(endpoint);
210+
211+
var formFeature = httpContext.Features.Get<IFormFeature>();
212+
Assert.Null(formFeature);
213+
}
214+
189215
private HttpContext CreateHttpContext()
190216
{
191217
var httpContext = new DefaultHttpContext
192218
{
193-
RequestServices = new TestServiceProvider()
219+
RequestServices = new TestServiceProvider(),
220+
Request =
221+
{
222+
ContentType = "multipart/form-data; boundary=----WebKitFormBoundarymx2fSWqWSd0OxQqq",
223+
Method = "POST",
224+
}
194225
};
195226

196227
return httpContext;

src/Shared/HttpExtensions.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
using Microsoft.AspNetCore.Http;
4+
5+
internal static class HttpExtensions
6+
{
7+
internal const string UrlEncodedFormContentType = "application/x-www-form-urlencoded";
8+
internal const string MultipartFormContentType = "multipart/form-data";
9+
10+
internal static bool IsValidHttpMethodForForm(string method) =>
11+
HttpMethods.IsPost(method) || HttpMethods.IsPut(method) || HttpMethods.IsPatch(method);
12+
13+
internal static bool IsValidContentTypeForForm(string? contentType)
14+
{
15+
if (contentType == null)
16+
{
17+
return false;
18+
}
19+
20+
// Abort early if this doesn't look like it could be a form-related content-type
21+
22+
if (contentType.Length < MultipartFormContentType.Length)
23+
{
24+
return false;
25+
}
26+
27+
return contentType.Equals(UrlEncodedFormContentType, StringComparison.OrdinalIgnoreCase) ||
28+
contentType.StartsWith(MultipartFormContentType, StringComparison.OrdinalIgnoreCase);
29+
}
30+
}

src/Shared/HttpMethodExtensions.cs

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)