diff --git a/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsMiddleware.cs b/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsMiddleware.cs index cae0bbab1c..a5dd632d38 100644 --- a/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsMiddleware.cs +++ b/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsMiddleware.cs @@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure if (string.Equals( context.Request.Method, CorsConstants.PreflightHttpMethod, - StringComparison.Ordinal) && + StringComparison.OrdinalIgnoreCase) && !StringValues.IsNullOrEmpty(accessControlRequestMethod)) { // Since there is a policy which was identified, diff --git a/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsService.cs b/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsService.cs index 3f792e72af..6af9dafc7d 100644 --- a/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsService.cs +++ b/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsService.cs @@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure var corsResult = new CorsResult(); var accessControlRequestMethod = context.Request.Headers[CorsConstants.AccessControlRequestMethod]; - if (string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.Ordinal) && + if (string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.OrdinalIgnoreCase) && !StringValues.IsNullOrEmpty(accessControlRequestMethod)) { EvaluatePreflightRequest(context, policy, corsResult); @@ -109,9 +109,23 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure var requestHeaders = context.Request.Headers.GetCommaSeparatedValues(CorsConstants.AccessControlRequestHeaders); - if (!policy.AllowAnyMethod && !policy.Methods.Contains(accessControlRequestMethod)) + if (!policy.AllowAnyMethod) { - return; + var found = false; + for (var i = 0; i < policy.Methods.Count; i++) + { + var method = policy.Methods[i]; + if (string.Equals(method, accessControlRequestMethod, StringComparison.OrdinalIgnoreCase)) + { + found = true; + break; + } + } + + if (!found) + { + return; + } } if (!policy.AllowAnyHeader && diff --git a/test/Microsoft.AspNetCore.Cors.Test/CorsMiddlewareTests.cs b/test/Microsoft.AspNetCore.Cors.Test/CorsMiddlewareTests.cs index 7fc712aaef..d884293e81 100644 --- a/test/Microsoft.AspNetCore.Cors.Test/CorsMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.Cors.Test/CorsMiddlewareTests.cs @@ -16,6 +16,41 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure { public class CorsMiddlewareTests { + [Theory] + [InlineData("PuT")] + [InlineData("PUT")] + public async Task CorsRequest_MatchesPolicy_OnCaseInsensitiveAccessControlRequestMethod(string accessControlRequestMethod) + { + // Arrange + var hostBuilder = new WebHostBuilder() + .Configure(app => + { + app.UseCors(builder => + builder.WithOrigins("http://localhost:5001") + .WithMethods("PUT")); + app.Run(async context => + { + await context.Response.WriteAsync("Cross origin response"); + }); + }) + .ConfigureServices(services => services.AddCors()); + + using (var server = new TestServer(hostBuilder)) + { + // Act + // Actual request. + var response = await server.CreateRequest("/") + .AddHeader(CorsConstants.Origin, "http://localhost:5001") + .SendAsync(accessControlRequestMethod); + + // Assert + response.EnsureSuccessStatusCode(); + Assert.Equal(1, response.Headers.Count()); + Assert.Equal("Cross origin response", await response.Content.ReadAsStringAsync()); + Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault()); + } + } + [Fact] public async Task CorsRequest_MatchPolicy_SetsResponseHeaders() { @@ -52,6 +87,48 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure } } + [Theory] + [InlineData("OpTions")] + [InlineData("OPTIONS")] + public async Task PreFlight_MatchesPolicy_OnCaseInsensitiveOptionsMethod(string preflightMethod) + { + // Arrange + var policy = new CorsPolicy(); + policy.Origins.Add("http://localhost:5001"); + policy.Methods.Add("PUT"); + + var hostBuilder = new WebHostBuilder() + .Configure(app => + { + app.UseCors("customPolicy"); + app.Run(async context => + { + await context.Response.WriteAsync("Cross origin response"); + }); + }) + .ConfigureServices(services => + { + services.AddCors(options => + { + options.AddPolicy("customPolicy", policy); + }); + }); + + using (var server = new TestServer(hostBuilder)) + { + // Act + // Preflight request. + var response = await server.CreateRequest("/") + .AddHeader(CorsConstants.Origin, "http://localhost:5001") + .SendAsync(preflightMethod); + + // Assert + response.EnsureSuccessStatusCode(); + Assert.Equal(1, response.Headers.Count()); + Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault()); + } + } + [Fact] public async Task PreFlight_MatchesPolicy_SetsResponseHeaders() { diff --git a/test/Microsoft.AspNetCore.Cors.Test/CorsServiceTests.cs b/test/Microsoft.AspNetCore.Cors.Test/CorsServiceTests.cs index fbdfd25dc0..c88eccd6fa 100644 --- a/test/Microsoft.AspNetCore.Cors.Test/CorsServiceTests.cs +++ b/test/Microsoft.AspNetCore.Cors.Test/CorsServiceTests.cs @@ -227,12 +227,17 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure Assert.Contains("PUT", result.AllowedMethods); } - [Fact] - public void EvaluatePolicy_PreflightRequest_OriginAllowed_ReturnsOrigin() + [Theory] + [InlineData("OpTions")] + [InlineData("OPTIONS")] + public void EvaluatePolicy_CaseInsensitivePreflightRequest_OriginAllowed_ReturnsOrigin(string preflightMethod) { // Arrange var corsService = new CorsService(new TestCorsOptions()); - var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT"); + var requestContext = GetHttpContext( + method: preflightMethod, + origin: "http://example.com", + accessControlRequestMethod: "PUT"); var policy = new CorsPolicy(); policy.Origins.Add(CorsConstants.AnyOrigin); policy.Origins.Add("http://example.com"); @@ -323,12 +328,17 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure Assert.Contains("GET", result.AllowedMethods); } - [Fact] - public void EvaluatePolicy_PreflightRequest_ListedMethod_ReturnsSubsetOfListedMethods() + [Theory] + [InlineData("Put")] + [InlineData("PUT")] + public void EvaluatePolicy_CaseInsensitivePreflightRequest_ListedMethod_ReturnsSubsetOfListedMethods(string method) { // Arrange var corsService = new CorsService(new TestCorsOptions()); - var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT"); + var requestContext = GetHttpContext( + method: "OPTIONS", + origin: "http://example.com", + accessControlRequestMethod: method); var policy = new CorsPolicy(); policy.Origins.Add(CorsConstants.AnyOrigin); policy.Methods.Add("PUT"); @@ -339,7 +349,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure // Assert Assert.Equal(1, result.AllowedMethods.Count); - Assert.Contains("PUT", result.AllowedMethods); + Assert.Contains(method, result.AllowedMethods); } [Fact]