diff --git a/src/CORS/.gitignore b/src/CORS/.gitignore
new file mode 100644
index 0000000000..a435689c0a
--- /dev/null
+++ b/src/CORS/.gitignore
@@ -0,0 +1,41 @@
+[Oo]bj/
+[Bb]in/
+TestResults/
+.nuget/
+*.sln.ide/
+_ReSharper.*/
+packages/
+artifacts/
+PublishProfiles/
+.vs/
+bower_components/
+node_modules/
+**/wwwroot/lib/
+debugSettings.json
+project.lock.json
+*.user
+*.suo
+*.cache
+*.docstates
+_ReSharper.*
+nuget.exe
+*net45.csproj
+*net451.csproj
+*k10.csproj
+*.psess
+*.vsp
+*.pidb
+*.userprefs
+*DS_Store
+*.ncrunchsolution
+*.*sdf
+*.ipch
+.settings
+*.sln.ide
+node_modules
+**/[Cc]ompiler/[Rr]esources/**/*.js
+*launchSettings.json
+.build/
+.testPublish/
+.vscode
+global.json
diff --git a/src/CORS/CORS.sln b/src/CORS/CORS.sln
new file mode 100644
index 0000000000..afdbf1fdc0
--- /dev/null
+++ b/src/CORS/CORS.sln
@@ -0,0 +1,89 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26817.0
+MinimumVisualStudioVersion = 15.0.26730.03
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{84FE6872-A610-4CEC-855F-A84CBF1F40FC}"
+ ProjectSection(SolutionItems) = preProject
+ src\Directory.Build.props = src\Directory.Build.props
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F32074C7-087C-46CC-A913-422BFD2D6E0A}"
+ ProjectSection(SolutionItems) = preProject
+ test\Directory.Build.props = test\Directory.Build.props
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cors", "src\Microsoft.AspNetCore.Cors\Microsoft.AspNetCore.Cors.csproj", "{41349FCD-D1C4-47A6-82D0-D16D00A8D59D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cors.Test", "test\Microsoft.AspNetCore.Cors.Test\Microsoft.AspNetCore.Cors.Test.csproj", "{F05BE96F-F869-4408-A480-96935B4835EE}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebSites", "WebSites", "{538380BF-0D4C-4E30-8F41-E75C4B1C01FA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CorsMiddlewareWebSite", "test\WebSites\CorsMiddlewareWebSite\CorsMiddlewareWebSite.csproj", "{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{960E0703-A8A5-44DF-AA87-B7C614683B3C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleDestination", "samples\SampleDestination\SampleDestination.csproj", "{F6675DC1-AA21-453B-89B6-DA425FB9C3A5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleOrigin", "samples\SampleOrigin\SampleOrigin.csproj", "{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0CCC5C1B-F548-4A17-96F8-14C700093FA0}"
+ ProjectSection(SolutionItems) = preProject
+ .appveyor.yml = .appveyor.yml
+ .gitattributes = .gitattributes
+ .gitignore = .gitignore
+ .travis.yml = .travis.yml
+ build.cmd = build.cmd
+ build.ps1 = build.ps1
+ build.sh = build.sh
+ CONTRIBUTING.md = CONTRIBUTING.md
+ Directory.Build.props = Directory.Build.props
+ Directory.Build.targets = Directory.Build.targets
+ LICENSE.txt = LICENSE.txt
+ NuGet.config = NuGet.config
+ NuGetPackageVerifier.json = NuGetPackageVerifier.json
+ README.md = README.md
+ version.xml = version.xml
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {41349FCD-D1C4-47A6-82D0-D16D00A8D59D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {41349FCD-D1C4-47A6-82D0-D16D00A8D59D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {41349FCD-D1C4-47A6-82D0-D16D00A8D59D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {41349FCD-D1C4-47A6-82D0-D16D00A8D59D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F05BE96F-F869-4408-A480-96935B4835EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F05BE96F-F869-4408-A480-96935B4835EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F05BE96F-F869-4408-A480-96935B4835EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F05BE96F-F869-4408-A480-96935B4835EE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F6675DC1-AA21-453B-89B6-DA425FB9C3A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F6675DC1-AA21-453B-89B6-DA425FB9C3A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F6675DC1-AA21-453B-89B6-DA425FB9C3A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F6675DC1-AA21-453B-89B6-DA425FB9C3A5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {41349FCD-D1C4-47A6-82D0-D16D00A8D59D} = {84FE6872-A610-4CEC-855F-A84CBF1F40FC}
+ {F05BE96F-F869-4408-A480-96935B4835EE} = {F32074C7-087C-46CC-A913-422BFD2D6E0A}
+ {538380BF-0D4C-4E30-8F41-E75C4B1C01FA} = {F32074C7-087C-46CC-A913-422BFD2D6E0A}
+ {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB} = {538380BF-0D4C-4E30-8F41-E75C4B1C01FA}
+ {F6675DC1-AA21-453B-89B6-DA425FB9C3A5} = {960E0703-A8A5-44DF-AA87-B7C614683B3C}
+ {99460370-AE5D-4DC9-8DBF-04DF66D6B21D} = {960E0703-A8A5-44DF-AA87-B7C614683B3C}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {F9ED9C53-44CD-4853-9621-D028B7B6A431}
+ EndGlobalSection
+EndGlobal
diff --git a/src/CORS/Directory.Build.props b/src/CORS/Directory.Build.props
new file mode 100644
index 0000000000..c81115d310
--- /dev/null
+++ b/src/CORS/Directory.Build.props
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+ Microsoft ASP.NET Core
+ https://github.com/aspnet/CORS
+ git
+ $(MSBuildThisFileDirectory)
+ $(MSBuildThisFileDirectory)build\Key.snk
+ true
+ true
+ true
+
+
+
diff --git a/src/CORS/Directory.Build.targets b/src/CORS/Directory.Build.targets
new file mode 100644
index 0000000000..53b3f6e1da
--- /dev/null
+++ b/src/CORS/Directory.Build.targets
@@ -0,0 +1,7 @@
+
+
+ $(MicrosoftNETCoreApp20PackageVersion)
+ $(MicrosoftNETCoreApp21PackageVersion)
+ $(NETStandardLibrary20PackageVersion)
+
+
diff --git a/src/CORS/NuGetPackageVerifier.json b/src/CORS/NuGetPackageVerifier.json
new file mode 100644
index 0000000000..b153ab1515
--- /dev/null
+++ b/src/CORS/NuGetPackageVerifier.json
@@ -0,0 +1,7 @@
+{
+ "Default": {
+ "rules": [
+ "DefaultCompositeRule"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/README.md b/src/CORS/README.md
new file mode 100644
index 0000000000..795d311da9
--- /dev/null
+++ b/src/CORS/README.md
@@ -0,0 +1,9 @@
+CORS
+===
+AppVeyor: [](https://ci.appveyor.com/project/aspnetci/CORS/branch/dev)
+
+Travis: [](https://travis-ci.org/aspnet/CORS)
+
+CORS repository includes the core implementation for CORS policy, utilized by the CORS middleware and MVC.
+
+This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.
diff --git a/src/CORS/build/Key.snk b/src/CORS/build/Key.snk
new file mode 100644
index 0000000000..e10e4889c1
Binary files /dev/null and b/src/CORS/build/Key.snk differ
diff --git a/src/CORS/build/buildpipeline/linux.groovy b/src/CORS/build/buildpipeline/linux.groovy
new file mode 100644
index 0000000000..903f218bb8
--- /dev/null
+++ b/src/CORS/build/buildpipeline/linux.groovy
@@ -0,0 +1,10 @@
+@Library('dotnet-ci') _
+
+simpleNode('Ubuntu16.04', 'latest-or-auto-docker') {
+ stage ('Checking out source') {
+ checkout scm
+ }
+ stage ('Build') {
+ sh './build.sh --ci'
+ }
+}
diff --git a/src/CORS/build/buildpipeline/osx.groovy b/src/CORS/build/buildpipeline/osx.groovy
new file mode 100644
index 0000000000..aaac63686b
--- /dev/null
+++ b/src/CORS/build/buildpipeline/osx.groovy
@@ -0,0 +1,10 @@
+@Library('dotnet-ci') _
+
+simpleNode('OSX10.12','latest') {
+ stage ('Checking out source') {
+ checkout scm
+ }
+ stage ('Build') {
+ sh './build.sh --ci'
+ }
+}
diff --git a/src/CORS/build/buildpipeline/pipeline.groovy b/src/CORS/build/buildpipeline/pipeline.groovy
new file mode 100644
index 0000000000..e915cadae1
--- /dev/null
+++ b/src/CORS/build/buildpipeline/pipeline.groovy
@@ -0,0 +1,18 @@
+import org.dotnet.ci.pipelines.Pipeline
+
+def windowsPipeline = Pipeline.createPipeline(this, 'build/buildpipeline/windows.groovy')
+def linuxPipeline = Pipeline.createPipeline(this, 'build/buildpipeline/linux.groovy')
+def osxPipeline = Pipeline.createPipeline(this, 'build/buildpipeline/osx.groovy')
+String configuration = 'Release'
+def parameters = [
+ 'Configuration': configuration
+]
+
+windowsPipeline.triggerPipelineOnEveryGithubPR("Windows ${configuration} x64 Build", parameters)
+windowsPipeline.triggerPipelineOnGithubPush(parameters)
+
+linuxPipeline.triggerPipelineOnEveryGithubPR("Ubuntu 16.04 ${configuration} Build", parameters)
+linuxPipeline.triggerPipelineOnGithubPush(parameters)
+
+osxPipeline.triggerPipelineOnEveryGithubPR("OSX 10.12 ${configuration} Build", parameters)
+osxPipeline.triggerPipelineOnGithubPush(parameters)
diff --git a/src/CORS/build/buildpipeline/windows.groovy b/src/CORS/build/buildpipeline/windows.groovy
new file mode 100644
index 0000000000..8d26f313d4
--- /dev/null
+++ b/src/CORS/build/buildpipeline/windows.groovy
@@ -0,0 +1,12 @@
+@Library('dotnet-ci') _
+
+// 'node' indicates to Jenkins that the enclosed block runs on a node that matches
+// the label 'windows-with-vs'
+simpleNode('Windows_NT','latest') {
+ stage ('Checking out source') {
+ checkout scm
+ }
+ stage ('Build') {
+ bat '.\\run.cmd -CI default-build'
+ }
+}
diff --git a/src/CORS/build/dependencies.props b/src/CORS/build/dependencies.props
new file mode 100644
index 0000000000..36fe73426a
--- /dev/null
+++ b/src/CORS/build/dependencies.props
@@ -0,0 +1,35 @@
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+
+ 2.1.3-rtm-15802
+ 2.0.0
+ 2.1.2
+ 15.6.1
+ 4.7.49
+ 2.0.3
+ 0.8.0
+ 2.3.1
+ 2.4.0-beta.1.build3945
+
+
+
+
+
+
+
+ 2.1.1
+ 2.1.1
+ 2.1.2
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+
+
\ No newline at end of file
diff --git a/src/CORS/build/repo.props b/src/CORS/build/repo.props
new file mode 100644
index 0000000000..dab1601c88
--- /dev/null
+++ b/src/CORS/build/repo.props
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Internal.AspNetCore.Universe.Lineup
+ 2.1.0-rc1-*
+ https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json
+
+
+
+
+
+
+
diff --git a/src/CORS/build/sources.props b/src/CORS/build/sources.props
new file mode 100644
index 0000000000..9215df9751
--- /dev/null
+++ b/src/CORS/build/sources.props
@@ -0,0 +1,17 @@
+
+
+
+
+ $(DotNetRestoreSources)
+
+ $(RestoreSources);
+ https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
+ https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json;
+ https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
+
+
+ $(RestoreSources);
+ https://api.nuget.org/v3/index.json;
+
+
+
diff --git a/src/CORS/samples/README.md b/src/CORS/samples/README.md
new file mode 100644
index 0000000000..0224188d71
--- /dev/null
+++ b/src/CORS/samples/README.md
@@ -0,0 +1,38 @@
+# CORS Sample
+
+This sample consists of a request origin (SampleOrigin) and a request destination (SampleDestination). Both have different domain names, to simulate a CORS request.
+
+## Modify Hosts File
+To run this CORS sample, modify the hosts file to register the hostnames `destination.example.com` and `origin.example.com`.
+### Windows:
+Run a text editor (e.g. Notepad) as an Administrator. Open the hosts file on the path: "C:\Windows\System32\drivers\etc\hosts".
+
+### Linux:
+On a Terminal window, type "sudo nano /etc/hosts" and enter your admin password when prompted.
+
+In the hosts file, add the following to the bottom of the file:
+
+```
+127.0.0.1 destination.example.com
+127.0.0.1 origin.example.com
+```
+
+Save the file and close it. Then clear your browser history.
+
+## Run the sample
+The SampleOrigin application will use port 5001, and SampleDestination will use 5000. Please ensure there are no other processes using those ports before running the CORS sample.
+
+* In a command prompt window, open the directory where you cloned the repository, and open the SampleDestination directory. Run the command: dotnet run
+* Repeat the above step in the SampleOrigin directory
+* Open a browser window and go to `http://origin.example.com:5001`
+* Input a method and header to create a CORS request or use one of the example buttons to see CORS in action
+
+As an example, apart from `GET`, `HEAD` and `POST` requests, `PUT` requests are allowed in the CORS policy on SampleDestination. Any others, like `DELETE`, `OPTIONS` etc. are not allowed and throw an error.
+`Cache-Control` has been added as an allowed header to the sample. Any other headers are not allowed and throw an error. You may leave the header name and value blank.
+
+To edit the policy, please see `app.UseCors()` method in the `Startup.cs` file of SampleDestination.
+
+**If using Visual Studio to launch the request origin:**
+Open Visual Studio and in the `launchSettings.json` file for the SampleOrigin project, change the `launchUrl` under SampleOrigin to `http://origin.example.com:5001`.
+Using the dropdown near the Start button, choose SampleOrigin before pressing Start to ensure that it uses Kestrel and not IIS Express.
+
diff --git a/src/CORS/samples/SampleDestination/Program.cs b/src/CORS/samples/SampleDestination/Program.cs
new file mode 100644
index 0000000000..0c2bf0968f
--- /dev/null
+++ b/src/CORS/samples/SampleDestination/Program.cs
@@ -0,0 +1,25 @@
+// 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 Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace SampleDestination
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var host = new WebHostBuilder()
+ .UseKestrel()
+ .UseUrls("http://*:5000")
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .ConfigureLogging(factory => factory.AddConsole())
+ .UseStartup()
+ .Build();
+
+ host.Run();
+ }
+ }
+}
diff --git a/src/CORS/samples/SampleDestination/SampleDestination.csproj b/src/CORS/samples/SampleDestination/SampleDestination.csproj
new file mode 100644
index 0000000000..b8e59ede3a
--- /dev/null
+++ b/src/CORS/samples/SampleDestination/SampleDestination.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netcoreapp2.1;net461
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CORS/samples/SampleDestination/Startup.cs b/src/CORS/samples/SampleDestination/Startup.cs
new file mode 100644
index 0000000000..0664d84572
--- /dev/null
+++ b/src/CORS/samples/SampleDestination/Startup.cs
@@ -0,0 +1,39 @@
+// 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.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace SampleDestination
+{
+ public class Startup
+ {
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddCors();
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ app.UseCors(policy => policy
+ .WithOrigins("http://origin.example.com:5001")
+ .WithMethods("PUT")
+ .WithHeaders("Cache-Control"));
+
+ app.Run(async context =>
+ {
+ var responseHeaders = context.Response.Headers;
+ context.Response.ContentType = "text/plain";
+ foreach (var responseHeader in responseHeaders)
+ {
+ await context.Response.WriteAsync("\n" + responseHeader.Key + ": " + responseHeader.Value);
+ }
+
+ await context.Response.WriteAsync("\nStatus code of your request: " + context.Response.StatusCode.ToString());
+ });
+ }
+ }
+}
diff --git a/src/CORS/samples/SampleOrigin/Program.cs b/src/CORS/samples/SampleOrigin/Program.cs
new file mode 100644
index 0000000000..278c09f48a
--- /dev/null
+++ b/src/CORS/samples/SampleOrigin/Program.cs
@@ -0,0 +1,25 @@
+// 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 Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace SampleOrigin
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var host = new WebHostBuilder()
+ .UseKestrel()
+ .UseUrls("http://*:5001")
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .ConfigureLogging(factory => factory.AddConsole())
+ .UseStartup()
+ .Build();
+
+ host.Run();
+ }
+ }
+}
diff --git a/src/CORS/samples/SampleOrigin/SampleOrigin.csproj b/src/CORS/samples/SampleOrigin/SampleOrigin.csproj
new file mode 100644
index 0000000000..b43f880f9c
--- /dev/null
+++ b/src/CORS/samples/SampleOrigin/SampleOrigin.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netcoreapp2.1;net461
+
+
+
+
+
+
+
+
diff --git a/src/CORS/samples/SampleOrigin/Startup.cs b/src/CORS/samples/SampleOrigin/Startup.cs
new file mode 100644
index 0000000000..a442b98d68
--- /dev/null
+++ b/src/CORS/samples/SampleOrigin/Startup.cs
@@ -0,0 +1,29 @@
+// 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.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace SampleOrigin
+{
+ public class Startup
+ {
+ public void ConfigureServices(IServiceCollection services)
+ {
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ app.Run(context =>
+ {
+ var fileInfoProvider = env.WebRootFileProvider;
+ var fileInfo = fileInfoProvider.GetFileInfo("/Index.html");
+ context.Response.ContentType = "text/html";
+ return context.Response.SendFileAsync(fileInfo);
+ });
+ }
+ }
+}
diff --git a/src/CORS/samples/SampleOrigin/wwwroot/Index.html b/src/CORS/samples/SampleOrigin/wwwroot/Index.html
new file mode 100644
index 0000000000..fad335b83f
--- /dev/null
+++ b/src/CORS/samples/SampleOrigin/wwwroot/Index.html
@@ -0,0 +1,89 @@
+
+
+
+
+
+ CORS Sample
+
+
+
+
+ CORS Sample
+ Method:
+ Header Name: Header Value:
+
+
+
+
+
+ Method DELETE is not allowed:
+ Method PUT is allowed:
+
+ Header 'Max-Forwards' not supported:
+ Header 'Cache-Control' is supported:
+
+
\ No newline at end of file
diff --git a/src/CORS/src/Directory.Build.props b/src/CORS/src/Directory.Build.props
new file mode 100644
index 0000000000..4f07cbc45d
--- /dev/null
+++ b/src/CORS/src/Directory.Build.props
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/CorsServiceCollectionExtensions.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/CorsServiceCollectionExtensions.cs
new file mode 100644
index 0000000000..430621d2fd
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/CorsServiceCollectionExtensions.cs
@@ -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 System;
+using Microsoft.AspNetCore.Cors.Infrastructure;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Extension methods for setting up cross-origin resource sharing services in an .
+ ///
+ public static class CorsServiceCollectionExtensions
+ {
+ ///
+ /// Adds cross-origin resource sharing services to the specified .
+ ///
+ /// The to add services to.
+ /// The so that additional calls can be chained.
+ public static IServiceCollection AddCors(this IServiceCollection services)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ services.AddOptions();
+
+ services.TryAdd(ServiceDescriptor.Transient());
+ services.TryAdd(ServiceDescriptor.Transient());
+
+ return services;
+ }
+
+ ///
+ /// Adds cross-origin resource sharing services to the specified .
+ ///
+ /// The to add services to.
+ /// An to configure the provided .
+ /// The so that additional calls can be chained.
+ public static IServiceCollection AddCors(this IServiceCollection services, Action setupAction)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (setupAction == null)
+ {
+ throw new ArgumentNullException(nameof(setupAction));
+ }
+
+ services.AddCors();
+ services.Configure(setupAction);
+
+ return services;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/DisableCorsAttribute.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/DisableCorsAttribute.cs
new file mode 100644
index 0000000000..ab4c755066
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/DisableCorsAttribute.cs
@@ -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 System;
+using Microsoft.AspNetCore.Cors.Infrastructure;
+
+namespace Microsoft.AspNetCore.Cors
+{
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+ public class DisableCorsAttribute : Attribute, IDisableCorsAttribute
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/EnableCorsAttribute.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/EnableCorsAttribute.cs
new file mode 100644
index 0000000000..b19851e39a
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/EnableCorsAttribute.cs
@@ -0,0 +1,34 @@
+// 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.Cors.Infrastructure;
+
+namespace Microsoft.AspNetCore.Cors
+{
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class EnableCorsAttribute : Attribute, IEnableCorsAttribute
+ {
+ ///
+ /// Creates a new instance of the with the default policy
+ /// name defined by .
+ ///
+ public EnableCorsAttribute()
+ : this(policyName: null)
+ {
+ }
+
+ ///
+ /// Creates a new instance of the with the supplied policy name.
+ ///
+ /// The name of the policy to be applied.
+ public EnableCorsAttribute(string policyName)
+ {
+ PolicyName = policyName;
+ }
+
+ ///
+ public string PolicyName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsConstants.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsConstants.cs
new file mode 100644
index 0000000000..22110b2e41
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsConstants.cs
@@ -0,0 +1,95 @@
+// 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.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ ///
+ /// CORS-related constants.
+ ///
+ public static class CorsConstants
+ {
+ ///
+ /// The HTTP method for the CORS preflight request.
+ ///
+ public static readonly string PreflightHttpMethod = HttpMethods.Options;
+
+ ///
+ /// The Origin request header.
+ ///
+ public static readonly string Origin = HeaderNames.Origin;
+
+ ///
+ /// The value for the Access-Control-Allow-Origin response header to allow all origins.
+ ///
+ public static readonly string AnyOrigin = "*";
+
+ ///
+ /// The Access-Control-Request-Method request header.
+ ///
+ public static readonly string AccessControlRequestMethod = HeaderNames.AccessControlRequestMethod;
+
+ ///
+ /// The Access-Control-Request-Headers request header.
+ ///
+ public static readonly string AccessControlRequestHeaders = HeaderNames.AccessControlRequestHeaders;
+
+ ///
+ /// The Access-Control-Allow-Origin response header.
+ ///
+ public static readonly string AccessControlAllowOrigin = HeaderNames.AccessControlAllowOrigin;
+
+ ///
+ /// The Access-Control-Allow-Headers response header.
+ ///
+ public static readonly string AccessControlAllowHeaders = HeaderNames.AccessControlAllowHeaders;
+
+ ///
+ /// The Access-Control-Expose-Headers response header.
+ ///
+ public static readonly string AccessControlExposeHeaders = HeaderNames.AccessControlExposeHeaders;
+
+ ///
+ /// The Access-Control-Allow-Methods response header.
+ ///
+ public static readonly string AccessControlAllowMethods = HeaderNames.AccessControlAllowMethods;
+
+ ///
+ /// The Access-Control-Allow-Credentials response header.
+ ///
+ public static readonly string AccessControlAllowCredentials = HeaderNames.AccessControlAllowCredentials;
+
+ ///
+ /// The Access-Control-Max-Age response header.
+ ///
+ public static readonly string AccessControlMaxAge = HeaderNames.AccessControlMaxAge;
+
+
+ internal static readonly string[] SimpleRequestHeaders =
+ {
+ HeaderNames.Origin,
+ HeaderNames.Accept,
+ HeaderNames.AcceptLanguage,
+ HeaderNames.ContentLanguage,
+ };
+
+ internal static readonly string[] SimpleResponseHeaders =
+ {
+ HeaderNames.CacheControl,
+ HeaderNames.ContentLanguage,
+ HeaderNames.ContentType,
+ HeaderNames.Expires,
+ HeaderNames.LastModified,
+ HeaderNames.Pragma
+ };
+
+ internal static readonly string[] SimpleMethods =
+ {
+ HttpMethods.Get,
+ HttpMethods.Head,
+ HttpMethods.Post
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsMiddleware.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsMiddleware.cs
new file mode 100644
index 0000000000..eb773f5abf
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsMiddleware.cs
@@ -0,0 +1,131 @@
+// 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.Http;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ ///
+ /// An ASP.NET middleware for handling CORS.
+ ///
+ public class CorsMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly ICorsService _corsService;
+ private readonly ICorsPolicyProvider _corsPolicyProvider;
+ private readonly CorsPolicy _policy;
+ private readonly string _corsPolicyName;
+
+ ///
+ /// Instantiates a new .
+ ///
+ /// The next middleware in the pipeline.
+ /// An instance of .
+ /// A policy provider which can get an .
+ public CorsMiddleware(
+ RequestDelegate next,
+ ICorsService corsService,
+ ICorsPolicyProvider policyProvider)
+ : this(next, corsService, policyProvider, policyName: null)
+ {
+ }
+
+ ///
+ /// Instantiates a new .
+ ///
+ /// The next middleware in the pipeline.
+ /// An instance of .
+ /// A policy provider which can get an .
+ /// An optional name of the policy to be fetched.
+ public CorsMiddleware(
+ RequestDelegate next,
+ ICorsService corsService,
+ ICorsPolicyProvider policyProvider,
+ string policyName)
+ {
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
+
+ if (corsService == null)
+ {
+ throw new ArgumentNullException(nameof(corsService));
+ }
+
+ if (policyProvider == null)
+ {
+ throw new ArgumentNullException(nameof(policyProvider));
+ }
+
+ _next = next;
+ _corsService = corsService;
+ _corsPolicyProvider = policyProvider;
+ _corsPolicyName = policyName;
+ }
+
+ ///
+ /// Instantiates a new .
+ ///
+ /// The next middleware in the pipeline.
+ /// An instance of .
+ /// An instance of the which can be applied.
+ public CorsMiddleware(
+ RequestDelegate next,
+ ICorsService corsService,
+ CorsPolicy policy)
+ {
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
+
+ if (corsService == null)
+ {
+ throw new ArgumentNullException(nameof(corsService));
+ }
+
+ if (policy == null)
+ {
+ throw new ArgumentNullException(nameof(policy));
+ }
+
+ _next = next;
+ _corsService = corsService;
+ _policy = policy;
+ }
+
+ ///
+ public async Task Invoke(HttpContext context)
+ {
+ if (context.Request.Headers.ContainsKey(CorsConstants.Origin))
+ {
+ var corsPolicy = _policy ?? await _corsPolicyProvider?.GetPolicyAsync(context, _corsPolicyName);
+ if (corsPolicy != null)
+ {
+ var corsResult = _corsService.EvaluatePolicy(context, corsPolicy);
+ _corsService.ApplyResult(corsResult, context.Response);
+
+ var accessControlRequestMethod =
+ context.Request.Headers[CorsConstants.AccessControlRequestMethod];
+ if (string.Equals(
+ context.Request.Method,
+ CorsConstants.PreflightHttpMethod,
+ StringComparison.OrdinalIgnoreCase) &&
+ !StringValues.IsNullOrEmpty(accessControlRequestMethod))
+ {
+ // Since there is a policy which was identified,
+ // always respond to preflight requests.
+ context.Response.StatusCode = StatusCodes.Status204NoContent;
+ return;
+ }
+ }
+ }
+
+ await _next(context);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsMiddlewareExtensions.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsMiddlewareExtensions.cs
new file mode 100644
index 0000000000..dea1ad01d9
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsMiddlewareExtensions.cs
@@ -0,0 +1,70 @@
+// 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.Cors.Infrastructure;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// The extensions for adding CORS middleware support.
+ ///
+ public static class CorsMiddlewareExtensions
+ {
+ ///
+ /// Adds a CORS middleware to your web application pipeline to allow cross domain requests.
+ ///
+ /// The IApplicationBuilder passed to your Configure method
+ /// The original app parameter
+ public static IApplicationBuilder UseCors(this IApplicationBuilder app)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseMiddleware();
+ }
+
+ ///
+ /// Adds a CORS middleware to your web application pipeline to allow cross domain requests.
+ ///
+ /// The IApplicationBuilder passed to your Configure method
+ /// The policy name of a configured policy.
+ /// The original app parameter
+ public static IApplicationBuilder UseCors(this IApplicationBuilder app, string policyName)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseMiddleware(policyName);
+ }
+
+ ///
+ /// Adds a CORS middleware to your web application pipeline to allow cross domain requests.
+ ///
+ /// The IApplicationBuilder passed to your Configure method.
+ /// A delegate which can use a policy builder to build a policy.
+ /// The original app parameter
+ public static IApplicationBuilder UseCors(
+ this IApplicationBuilder app,
+ Action configurePolicy)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ if (configurePolicy == null)
+ {
+ throw new ArgumentNullException(nameof(configurePolicy));
+ }
+
+ var policyBuilder = new CorsPolicyBuilder();
+ configurePolicy(policyBuilder);
+ return app.UseMiddleware(policyBuilder.Build());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsOptions.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsOptions.cs
new file mode 100644
index 0000000000..92e1f775ba
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsOptions.cs
@@ -0,0 +1,119 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ ///
+ /// Provides programmatic configuration for Cors.
+ ///
+ public class CorsOptions
+ {
+ private string _defaultPolicyName = "__DefaultCorsPolicy";
+ private IDictionary PolicyMap { get; } = new Dictionary();
+
+ public string DefaultPolicyName
+ {
+ get
+ {
+ return _defaultPolicyName;
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _defaultPolicyName = value;
+ }
+ }
+
+ ///
+ /// Adds a new policy and sets it as the default.
+ ///
+ /// The policy to be added.
+ public void AddDefaultPolicy(CorsPolicy policy)
+ {
+ if (policy == null)
+ {
+ throw new ArgumentNullException(nameof(policy));
+ }
+
+ AddPolicy(DefaultPolicyName, policy);
+ }
+
+ ///
+ /// Adds a new policy and sets it as the default.
+ ///
+ /// A delegate which can use a policy builder to build a policy.
+ public void AddDefaultPolicy(Action configurePolicy)
+ {
+ if (configurePolicy == null)
+ {
+ throw new ArgumentNullException(nameof(configurePolicy));
+ }
+
+ AddPolicy(DefaultPolicyName, configurePolicy);
+ }
+
+ ///
+ /// Adds a new policy.
+ ///
+ /// The name of the policy.
+ /// The policy to be added.
+ public void AddPolicy(string name, CorsPolicy policy)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ if (policy == null)
+ {
+ throw new ArgumentNullException(nameof(policy));
+ }
+
+ PolicyMap[name] = policy;
+ }
+
+ ///
+ /// Adds a new policy.
+ ///
+ /// The name of the policy.
+ /// A delegate which can use a policy builder to build a policy.
+ public void AddPolicy(string name, Action configurePolicy)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ if (configurePolicy == null)
+ {
+ throw new ArgumentNullException(nameof(configurePolicy));
+ }
+
+ var policyBuilder = new CorsPolicyBuilder();
+ configurePolicy(policyBuilder);
+ PolicyMap[name] = policyBuilder.Build();
+ }
+
+ ///
+ /// Gets the policy based on the
+ ///
+ /// The name of the policy to lookup.
+ /// The if the policy was added.null otherwise.
+ public CorsPolicy GetPolicy(string name)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsPolicy.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsPolicy.cs
new file mode 100644
index 0000000000..f541863c69
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsPolicy.cs
@@ -0,0 +1,164 @@
+// 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.Linq;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ ///
+ /// Defines the policy for Cross-Origin requests based on the CORS specifications.
+ ///
+ public class CorsPolicy
+ {
+ private TimeSpan? _preflightMaxAge;
+
+ ///
+ /// Default constructor for a CorsPolicy.
+ ///
+ public CorsPolicy()
+ {
+ IsOriginAllowed = DefaultIsOriginAllowed;
+ }
+
+ ///
+ /// Gets a value indicating if all headers are allowed.
+ ///
+ public bool AllowAnyHeader
+ {
+ get
+ {
+ if (Headers == null || Headers.Count != 1 || Headers.Count == 1 && Headers[0] != "*")
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ ///
+ /// Gets a value indicating if all methods are allowed.
+ ///
+ public bool AllowAnyMethod
+ {
+ get
+ {
+ if (Methods == null || Methods.Count != 1 || Methods.Count == 1 && Methods[0] != "*")
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ ///
+ /// Gets a value indicating if all origins are allowed.
+ ///
+ public bool AllowAnyOrigin
+ {
+ get
+ {
+ if (Origins == null || Origins.Count != 1 || Origins.Count == 1 && Origins[0] != "*")
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ ///
+ /// Gets or sets a function which evaluates whether an origin is allowed.
+ ///
+ public Func IsOriginAllowed { get; set; }
+
+ ///
+ /// Gets the headers that the resource might use and can be exposed.
+ ///
+ public IList ExposedHeaders { get; } = new List();
+
+ ///
+ /// Gets the headers that are supported by the resource.
+ ///
+ public IList Headers { get; } = new List();
+
+ ///
+ /// Gets the methods that are supported by the resource.
+ ///
+ public IList Methods { get; } = new List();
+
+ ///
+ /// Gets the origins that are allowed to access the resource.
+ ///
+ public IList Origins { get; } = new List();
+
+ ///
+ /// Gets or sets the for which the results of a preflight request can be cached.
+ ///
+ public TimeSpan? PreflightMaxAge
+ {
+ get
+ {
+ return _preflightMaxAge;
+ }
+ set
+ {
+ if (value < TimeSpan.Zero)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Resources.PreflightMaxAgeOutOfRange);
+ }
+
+ _preflightMaxAge = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the resource supports user credentials in the request.
+ ///
+ public bool SupportsCredentials { get; set; }
+
+ ///
+ /// Returns a that represents this instance.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ public override string ToString()
+ {
+ var builder = new StringBuilder();
+ builder.Append("AllowAnyHeader: ");
+ builder.Append(AllowAnyHeader);
+ builder.Append(", AllowAnyMethod: ");
+ builder.Append(AllowAnyMethod);
+ builder.Append(", AllowAnyOrigin: ");
+ builder.Append(AllowAnyOrigin);
+ builder.Append(", PreflightMaxAge: ");
+ builder.Append(PreflightMaxAge.HasValue ?
+ PreflightMaxAge.Value.TotalSeconds.ToString() : "null");
+ builder.Append(", SupportsCredentials: ");
+ builder.Append(SupportsCredentials);
+ builder.Append(", Origins: {");
+ builder.Append(string.Join(",", Origins));
+ builder.Append("}");
+ builder.Append(", Methods: {");
+ builder.Append(string.Join(",", Methods));
+ builder.Append("}");
+ builder.Append(", Headers: {");
+ builder.Append(string.Join(",", Headers));
+ builder.Append("}");
+ builder.Append(", ExposedHeaders: {");
+ builder.Append(string.Join(",", ExposedHeaders));
+ builder.Append("}");
+ return builder.ToString();
+ }
+
+ private bool DefaultIsOriginAllowed(string origin)
+ {
+ return Origins.Contains(origin, StringComparer.Ordinal);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsPolicyBuilder.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsPolicyBuilder.cs
new file mode 100644
index 0000000000..88e16fa0fa
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsPolicyBuilder.cs
@@ -0,0 +1,225 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ ///
+ /// Exposes methods to build a policy.
+ ///
+ public class CorsPolicyBuilder
+ {
+ private readonly CorsPolicy _policy = new CorsPolicy();
+
+ ///
+ /// Creates a new instance of the .
+ ///
+ /// list of origins which can be added.
+ public CorsPolicyBuilder(params string[] origins)
+ {
+ WithOrigins(origins);
+ }
+
+ ///
+ /// Creates a new instance of the .
+ ///
+ /// The policy which will be used to intialize the builder.
+ public CorsPolicyBuilder(CorsPolicy policy)
+ {
+ Combine(policy);
+ }
+
+ ///
+ /// Adds the specified to the policy.
+ ///
+ /// The origins that are allowed.
+ /// The current policy builder.
+ public CorsPolicyBuilder WithOrigins(params string[] origins)
+ {
+ foreach (var req in origins)
+ {
+ _policy.Origins.Add(req);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Adds the specified to the policy.
+ ///
+ /// The headers which need to be allowed in the request.
+ /// The current policy builder.
+ public CorsPolicyBuilder WithHeaders(params string[] headers)
+ {
+ foreach (var req in headers)
+ {
+ _policy.Headers.Add(req);
+ }
+ return this;
+ }
+
+ ///
+ /// Adds the specified to the policy.
+ ///
+ /// The headers which need to be exposed to the client.
+ /// The current policy builder.
+ public CorsPolicyBuilder WithExposedHeaders(params string[] exposedHeaders)
+ {
+ foreach (var req in exposedHeaders)
+ {
+ _policy.ExposedHeaders.Add(req);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Adds the specified to the policy.
+ ///
+ /// The methods which need to be added to the policy.
+ /// The current policy builder.
+ public CorsPolicyBuilder WithMethods(params string[] methods)
+ {
+ foreach (var req in methods)
+ {
+ _policy.Methods.Add(req);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Sets the policy to allow credentials.
+ ///
+ /// The current policy builder.
+ public CorsPolicyBuilder AllowCredentials()
+ {
+ _policy.SupportsCredentials = true;
+ return this;
+ }
+
+ ///
+ /// Sets the policy to not allow credentials.
+ ///
+ /// The current policy builder.
+ public CorsPolicyBuilder DisallowCredentials()
+ {
+ _policy.SupportsCredentials = false;
+ return this;
+ }
+
+ ///
+ /// Ensures that the policy allows any origin.
+ ///
+ /// The current policy builder.
+ public CorsPolicyBuilder AllowAnyOrigin()
+ {
+ _policy.Origins.Clear();
+ _policy.Origins.Add(CorsConstants.AnyOrigin);
+ return this;
+ }
+
+ ///
+ /// Ensures that the policy allows any method.
+ ///
+ /// The current policy builder.
+ public CorsPolicyBuilder AllowAnyMethod()
+ {
+ _policy.Methods.Clear();
+ _policy.Methods.Add("*");
+ return this;
+ }
+
+ ///
+ /// Ensures that the policy allows any header.
+ ///
+ /// The current policy builder.
+ public CorsPolicyBuilder AllowAnyHeader()
+ {
+ _policy.Headers.Clear();
+ _policy.Headers.Add("*");
+ return this;
+ }
+
+ ///
+ /// Sets the preflightMaxAge for the underlying policy.
+ ///
+ /// A positive indicating the time a preflight
+ /// request can be cached.
+ /// The current policy builder.
+ public CorsPolicyBuilder SetPreflightMaxAge(TimeSpan preflightMaxAge)
+ {
+ _policy.PreflightMaxAge = preflightMaxAge;
+ return this;
+ }
+
+ ///
+ /// Sets the specified for the underlying policy.
+ ///
+ /// The function used by the policy to evaluate if an origin is allowed.
+ /// The current policy builder.
+ public CorsPolicyBuilder SetIsOriginAllowed(Func isOriginAllowed)
+ {
+ _policy.IsOriginAllowed = isOriginAllowed;
+ return this;
+ }
+
+ ///
+ /// Sets the property of the policy to be a function
+ /// that allows origins to match a configured wildcarded domain when evaluating if the
+ /// origin is allowed.
+ ///
+ /// The current policy builder.
+ public CorsPolicyBuilder SetIsOriginAllowedToAllowWildcardSubdomains()
+ {
+ _policy.IsOriginAllowed = _policy.IsOriginAnAllowedSubdomain;
+ return this;
+ }
+
+ ///
+ /// Builds a new using the entries added.
+ ///
+ /// The constructed .
+ public CorsPolicy Build()
+ {
+ return _policy;
+ }
+
+ ///
+ /// Combines the given to the existing properties in the builder.
+ ///
+ /// The policy which needs to be combined.
+ /// The current policy builder.
+ private CorsPolicyBuilder Combine(CorsPolicy policy)
+ {
+ if (policy == null)
+ {
+ throw new ArgumentNullException(nameof(policy));
+ }
+
+ WithOrigins(policy.Origins.ToArray());
+ WithHeaders(policy.Headers.ToArray());
+ WithExposedHeaders(policy.ExposedHeaders.ToArray());
+ WithMethods(policy.Methods.ToArray());
+ SetIsOriginAllowed(policy.IsOriginAllowed);
+
+ if (policy.PreflightMaxAge.HasValue)
+ {
+ SetPreflightMaxAge(policy.PreflightMaxAge.Value);
+ }
+
+ if (policy.SupportsCredentials)
+ {
+ AllowCredentials();
+ }
+ else
+ {
+ DisallowCredentials();
+ }
+
+ return this;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsPolicyExtensions.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsPolicyExtensions.cs
new file mode 100644
index 0000000000..312f772994
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsPolicyExtensions.cs
@@ -0,0 +1,36 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ internal static class CorsPolicyExtensions
+ {
+ private const string _WildcardSubdomain = "*.";
+
+ public static bool IsOriginAnAllowedSubdomain(this CorsPolicy policy, string origin)
+ {
+ if (policy.Origins.Contains(origin))
+ {
+ return true;
+ }
+
+ if (Uri.TryCreate(origin, UriKind.Absolute, out var originUri))
+ {
+ return policy.Origins
+ .Where(o => o.Contains($"://{_WildcardSubdomain}"))
+ .Select(CreateDomainUri)
+ .Any(domain => UriHelpers.IsSubdomainOf(originUri, domain));
+ }
+
+ return false;
+ }
+
+ private static Uri CreateDomainUri(string origin)
+ {
+ return new Uri(origin.Replace(_WildcardSubdomain, string.Empty), UriKind.Absolute);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsResult.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsResult.cs
new file mode 100644
index 0000000000..99d38b9fd1
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsResult.cs
@@ -0,0 +1,94 @@
+// 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.Text;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ ///
+ /// Results returned by .
+ ///
+ public class CorsResult
+ {
+ private TimeSpan? _preflightMaxAge;
+
+ ///
+ /// Gets or sets the allowed origin.
+ ///
+ public string AllowedOrigin { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the resource supports user credentials.
+ ///
+ public bool SupportsCredentials { get; set; }
+
+ ///
+ /// Gets the allowed methods.
+ ///
+ public IList AllowedMethods { get; } = new List();
+
+ ///
+ /// Gets the allowed headers.
+ ///
+ public IList AllowedHeaders { get; } = new List();
+
+ ///
+ /// Gets the allowed headers that can be exposed on the response.
+ ///
+ public IList AllowedExposedHeaders { get; } = new List();
+
+ ///
+ /// Gets or sets a value indicating if a 'Vary' header with the value 'Origin' is required.
+ ///
+ public bool VaryByOrigin { get; set; }
+
+ ///
+ /// Gets or sets the for which the results of a preflight request can be cached.
+ ///
+ public TimeSpan? PreflightMaxAge
+ {
+ get
+ {
+ return _preflightMaxAge;
+ }
+ set
+ {
+ if (value < TimeSpan.Zero)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), Resources.PreflightMaxAgeOutOfRange);
+ }
+ _preflightMaxAge = value;
+ }
+ }
+
+ ///
+ /// Returns a that represents this instance.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ public override string ToString()
+ {
+ var builder = new StringBuilder();
+ builder.Append("AllowCredentials: ");
+ builder.Append(SupportsCredentials);
+ builder.Append(", PreflightMaxAge: ");
+ builder.Append(PreflightMaxAge.HasValue ?
+ PreflightMaxAge.Value.TotalSeconds.ToString() : "null");
+ builder.Append(", AllowOrigin: ");
+ builder.Append(AllowedOrigin);
+ builder.Append(", AllowExposedHeaders: {");
+ builder.Append(string.Join(",", AllowedExposedHeaders));
+ builder.Append("}");
+ builder.Append(", AllowHeaders: {");
+ builder.Append(string.Join(",", AllowedHeaders));
+ builder.Append("}");
+ builder.Append(", AllowMethods: {");
+ builder.Append(string.Join(",", AllowedMethods));
+ builder.Append("}");
+ return builder.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsService.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsService.cs
new file mode 100644
index 0000000000..5060ddf205
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/CorsService.cs
@@ -0,0 +1,313 @@
+// 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.Globalization;
+using System.Linq;
+using Microsoft.AspNetCore.Cors.Internal;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ ///
+ /// Default implementation of .
+ ///
+ public class CorsService : ICorsService
+ {
+ private readonly CorsOptions _options;
+ private readonly ILogger _logger;
+
+ ///
+ /// Creates a new instance of the .
+ ///
+ /// The option model representing .
+ public CorsService(IOptions options)
+ : this(options, loggerFactory: null)
+ {
+ }
+
+ ///
+ /// Creates a new instance of the .
+ ///
+ /// The option model representing .
+ /// The .
+ public CorsService(IOptions options, ILoggerFactory loggerFactory)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ _options = options.Value;
+ _logger = loggerFactory?.CreateLogger();
+ }
+
+ ///
+ /// Looks up a policy using the and then evaluates the policy using the passed in
+ /// .
+ ///
+ ///
+ ///
+ /// A which contains the result of policy evaluation and can be
+ /// used by the caller to set appropriate response headers.
+ public CorsResult EvaluatePolicy(HttpContext context, string policyName)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ var policy = _options.GetPolicy(policyName);
+ return EvaluatePolicy(context, policy);
+ }
+
+ ///
+ public CorsResult EvaluatePolicy(HttpContext context, CorsPolicy policy)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (policy == null)
+ {
+ throw new ArgumentNullException(nameof(policy));
+ }
+
+ var corsResult = new CorsResult();
+ var accessControlRequestMethod = context.Request.Headers[CorsConstants.AccessControlRequestMethod];
+ if (string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.OrdinalIgnoreCase) &&
+ !StringValues.IsNullOrEmpty(accessControlRequestMethod))
+ {
+ _logger?.IsPreflightRequest();
+ EvaluatePreflightRequest(context, policy, corsResult);
+ }
+ else
+ {
+ EvaluateRequest(context, policy, corsResult);
+ }
+
+ return corsResult;
+ }
+
+ public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result)
+ {
+ var origin = context.Request.Headers[CorsConstants.Origin];
+ if (!IsOriginAllowed(policy, origin))
+ {
+ return;
+ }
+
+ AddOriginToResult(origin, policy, result);
+ result.SupportsCredentials = policy.SupportsCredentials;
+ AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders);
+ _logger?.PolicySuccess();
+ }
+
+ public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result)
+ {
+ var origin = context.Request.Headers[CorsConstants.Origin];
+ if (!IsOriginAllowed(policy, origin))
+ {
+ return;
+ }
+
+ var accessControlRequestMethod = context.Request.Headers[CorsConstants.AccessControlRequestMethod];
+ if (StringValues.IsNullOrEmpty(accessControlRequestMethod))
+ {
+ return;
+ }
+
+ var requestHeaders =
+ context.Request.Headers.GetCommaSeparatedValues(CorsConstants.AccessControlRequestHeaders);
+
+ if (!policy.AllowAnyMethod)
+ {
+ 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)
+ {
+ _logger?.PolicyFailure();
+ _logger?.AccessControlMethodNotAllowed(accessControlRequestMethod);
+ return;
+ }
+ }
+
+ if (!policy.AllowAnyHeader &&
+ requestHeaders != null)
+ {
+ foreach (var requestHeader in requestHeaders)
+ {
+ if (!CorsConstants.SimpleRequestHeaders.Contains(requestHeader, StringComparer.OrdinalIgnoreCase) &&
+ !policy.Headers.Contains(requestHeader, StringComparer.OrdinalIgnoreCase))
+ {
+ _logger?.PolicyFailure();
+ _logger?.RequestHeaderNotAllowed(requestHeader);
+ return;
+ }
+ }
+ }
+
+ AddOriginToResult(origin, policy, result);
+ result.SupportsCredentials = policy.SupportsCredentials;
+ result.PreflightMaxAge = policy.PreflightMaxAge;
+ result.AllowedMethods.Add(accessControlRequestMethod);
+ AddHeaderValues(result.AllowedHeaders, requestHeaders);
+ _logger?.PolicySuccess();
+ }
+
+ ///
+ public virtual void ApplyResult(CorsResult result, HttpResponse response)
+ {
+ if (result == null)
+ {
+ throw new ArgumentNullException(nameof(result));
+ }
+
+ if (response == null)
+ {
+ throw new ArgumentNullException(nameof(response));
+ }
+
+ var headers = response.Headers;
+
+ if (result.AllowedOrigin != null)
+ {
+ headers[CorsConstants.AccessControlAllowOrigin] = result.AllowedOrigin;
+ }
+
+ if (result.VaryByOrigin)
+ {
+ headers["Vary"] = "Origin";
+ }
+
+ if (result.SupportsCredentials)
+ {
+ headers[CorsConstants.AccessControlAllowCredentials] = "true";
+ }
+
+ if (result.AllowedMethods.Count > 0)
+ {
+ // Filter out simple methods
+ var nonSimpleAllowMethods = result.AllowedMethods
+ .Where(m =>
+ !CorsConstants.SimpleMethods.Contains(m, StringComparer.OrdinalIgnoreCase))
+ .ToArray();
+
+ if (nonSimpleAllowMethods.Length > 0)
+ {
+ headers.SetCommaSeparatedValues(
+ CorsConstants.AccessControlAllowMethods,
+ nonSimpleAllowMethods);
+ }
+ }
+
+ if (result.AllowedHeaders.Count > 0)
+ {
+ // Filter out simple request headers
+ var nonSimpleAllowRequestHeaders = result.AllowedHeaders
+ .Where(header =>
+ !CorsConstants.SimpleRequestHeaders.Contains(header, StringComparer.OrdinalIgnoreCase))
+ .ToArray();
+
+ if (nonSimpleAllowRequestHeaders.Length > 0)
+ {
+ headers.SetCommaSeparatedValues(
+ CorsConstants.AccessControlAllowHeaders,
+ nonSimpleAllowRequestHeaders);
+ }
+ }
+
+ if (result.AllowedExposedHeaders.Count > 0)
+ {
+ // Filter out simple response headers
+ var nonSimpleAllowResponseHeaders = result.AllowedExposedHeaders
+ .Where(header =>
+ !CorsConstants.SimpleResponseHeaders.Contains(header, StringComparer.OrdinalIgnoreCase))
+ .ToArray();
+
+ if (nonSimpleAllowResponseHeaders.Length > 0)
+ {
+ headers.SetCommaSeparatedValues(
+ CorsConstants.AccessControlExposeHeaders,
+ nonSimpleAllowResponseHeaders);
+ }
+ }
+
+ if (result.PreflightMaxAge.HasValue)
+ {
+ headers[CorsConstants.AccessControlMaxAge]
+ = result.PreflightMaxAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture);
+ }
+ }
+
+ private void AddOriginToResult(string origin, CorsPolicy policy, CorsResult result)
+ {
+ if (policy.AllowAnyOrigin)
+ {
+ if (policy.SupportsCredentials)
+ {
+ result.AllowedOrigin = origin;
+ result.VaryByOrigin = true;
+ }
+ else
+ {
+ result.AllowedOrigin = CorsConstants.AnyOrigin;
+ }
+ }
+ else if (policy.IsOriginAllowed(origin))
+ {
+ result.AllowedOrigin = origin;
+
+ if(policy.Origins.Count > 1)
+ {
+ result.VaryByOrigin = true;
+ }
+ }
+ }
+
+ private static void AddHeaderValues(IList target, IEnumerable headerValues)
+ {
+ if (headerValues == null)
+ {
+ return;
+ }
+
+ foreach (var current in headerValues)
+ {
+ target.Add(current);
+ }
+ }
+
+ private bool IsOriginAllowed(CorsPolicy policy, StringValues origin)
+ {
+ if (StringValues.IsNullOrEmpty(origin))
+ {
+ _logger?.RequestDoesNotHaveOriginHeader();
+ return false;
+ }
+
+ _logger?.RequestHasOriginHeader(origin);
+ if (policy.AllowAnyOrigin || policy.IsOriginAllowed(origin))
+ {
+ return true;
+ }
+ _logger?.PolicyFailure();
+ _logger?.OriginNotAllowed(origin);
+ return false;
+ }
+ }
+}
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/DefaultCorsPolicyProvider.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/DefaultCorsPolicyProvider.cs
new file mode 100644
index 0000000000..4841d60f75
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/DefaultCorsPolicyProvider.cs
@@ -0,0 +1,36 @@
+// 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.Http;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ ///
+ public class DefaultCorsPolicyProvider : ICorsPolicyProvider
+ {
+ private readonly CorsOptions _options;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The options configured for the application.
+ public DefaultCorsPolicyProvider(IOptions options)
+ {
+ _options = options.Value;
+ }
+
+ ///
+ public Task GetPolicyAsync(HttpContext context, string policyName)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return Task.FromResult(_options.GetPolicy(policyName ?? _options.DefaultPolicyName));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/ICorsPolicyProvider.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/ICorsPolicyProvider.cs
new file mode 100644
index 0000000000..7785e3de9c
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/ICorsPolicyProvider.cs
@@ -0,0 +1,22 @@
+// 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.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ ///
+ /// A type which can provide a for a particular .
+ ///
+ public interface ICorsPolicyProvider
+ {
+ ///
+ /// Gets a from the given
+ ///
+ /// The associated with this call.
+ /// An optional policy name to look for.
+ /// A
+ Task GetPolicyAsync(HttpContext context, string policyName);
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/ICorsService.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/ICorsService.cs
new file mode 100644
index 0000000000..ab8176a961
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/ICorsService.cs
@@ -0,0 +1,31 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ ///
+ /// A type which can evaluate a policy for a particular .
+ ///
+ public interface ICorsService
+ {
+ ///
+ /// Evaluates the given using the passed in .
+ ///
+ /// The associated with the call.
+ /// The which needs to be evaluated.
+ /// A which contains the result of policy evaluation and can be
+ /// used by the caller to set appropriate response headers.
+ CorsResult EvaluatePolicy(HttpContext context, CorsPolicy policy);
+
+
+ ///
+ /// Adds CORS-specific response headers to the given .
+ ///
+ /// The used to read the allowed values.
+ /// The associated with the current call.
+ void ApplyResult(CorsResult result, HttpResponse response);
+
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/IDisableCorsAttribute.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/IDisableCorsAttribute.cs
new file mode 100644
index 0000000000..1e69ba3da3
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/IDisableCorsAttribute.cs
@@ -0,0 +1,12 @@
+// 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.Cors.Infrastructure
+{
+ ///
+ /// An interface which can be used to identify a type which provides metdata to disable cors for a resource.
+ ///
+ public interface IDisableCorsAttribute
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/IEnableCorsAttribute.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/IEnableCorsAttribute.cs
new file mode 100644
index 0000000000..c58e2a1d96
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/IEnableCorsAttribute.cs
@@ -0,0 +1,16 @@
+// 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.Cors.Infrastructure
+{
+ ///
+ /// An interface which can be used to identify a type which provides metadata needed for enabling CORS support.
+ ///
+ public interface IEnableCorsAttribute
+ {
+ ///
+ /// The name of the policy which needs to be applied.
+ ///
+ string PolicyName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/UriHelpers.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/UriHelpers.cs
new file mode 100644
index 0000000000..6c420e9260
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Infrastructure/UriHelpers.cs
@@ -0,0 +1,19 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ internal static class UriHelpers
+ {
+ public static bool IsSubdomainOf(Uri subdomain, Uri domain)
+ {
+ return subdomain.IsAbsoluteUri
+ && domain.IsAbsoluteUri
+ && subdomain.Scheme == domain.Scheme
+ && subdomain.Port == domain.Port
+ && subdomain.Host.EndsWith($".{domain.Host}", StringComparison.Ordinal);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Internal/CORSLoggerExtensions.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Internal/CORSLoggerExtensions.cs
new file mode 100644
index 0000000000..727d19a4ea
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Internal/CORSLoggerExtensions.cs
@@ -0,0 +1,103 @@
+// 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.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Cors.Internal
+{
+ internal static class CORSLoggerExtensions
+ {
+ private static readonly Action _isPreflightRequest;
+ private static readonly Action _requestHasOriginHeader;
+ private static readonly Action _requestDoesNotHaveOriginHeader;
+ private static readonly Action _policySuccess;
+ private static readonly Action _policyFailure;
+ private static readonly Action _originNotAllowed;
+ private static readonly Action _accessControlMethodNotAllowed;
+ private static readonly Action _requestHeaderNotAllowed;
+
+ static CORSLoggerExtensions()
+ {
+ _isPreflightRequest = LoggerMessage.Define(
+ LogLevel.Debug,
+ 1,
+ "The request is a preflight request.");
+
+ _requestHasOriginHeader = LoggerMessage.Define(
+ LogLevel.Debug,
+ 2,
+ "The request has an origin header: '{origin}'.");
+
+ _requestDoesNotHaveOriginHeader = LoggerMessage.Define(
+ LogLevel.Debug,
+ 3,
+ "The request does not have an origin header.");
+
+ _policySuccess = LoggerMessage.Define(
+ LogLevel.Information,
+ 4,
+ "Policy execution successful.");
+
+ _policyFailure = LoggerMessage.Define(
+ LogLevel.Information,
+ 5,
+ "Policy execution failed.");
+
+ _originNotAllowed = LoggerMessage.Define(
+ LogLevel.Information,
+ 6,
+ "Request origin {origin} does not have permission to access the resource.");
+
+ _accessControlMethodNotAllowed = LoggerMessage.Define(
+ LogLevel.Information,
+ 7,
+ "Request method {accessControlRequestMethod} not allowed in CORS policy.");
+
+ _requestHeaderNotAllowed = LoggerMessage.Define(
+ LogLevel.Information,
+ 8,
+ "Request header '{requestHeader}' not allowed in CORS policy.");
+ }
+
+ public static void IsPreflightRequest(this ILogger logger)
+ {
+ _isPreflightRequest(logger, null);
+ }
+
+ public static void RequestHasOriginHeader(this ILogger logger, string origin)
+ {
+ _requestHasOriginHeader(logger, origin, null);
+ }
+
+ public static void RequestDoesNotHaveOriginHeader(this ILogger logger)
+ {
+ _requestDoesNotHaveOriginHeader(logger, null);
+ }
+
+ public static void PolicySuccess(this ILogger logger)
+ {
+ _policySuccess(logger, null);
+ }
+
+ public static void PolicyFailure(this ILogger logger)
+ {
+ _policyFailure(logger, null);
+ }
+
+ public static void OriginNotAllowed(this ILogger logger, string origin)
+ {
+ _originNotAllowed(logger, origin, null);
+ }
+
+ public static void AccessControlMethodNotAllowed(this ILogger logger, string accessControlMethod)
+ {
+ _accessControlMethodNotAllowed(logger, accessControlMethod, null);
+ }
+
+ public static void RequestHeaderNotAllowed(this ILogger logger, string requestHeader)
+ {
+ _requestHeaderNotAllowed(logger, requestHeader, null);
+ }
+ }
+}
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Microsoft.AspNetCore.Cors.csproj b/src/CORS/src/Microsoft.AspNetCore.Cors/Microsoft.AspNetCore.Cors.csproj
new file mode 100644
index 0000000000..10d8530a2e
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Microsoft.AspNetCore.Cors.csproj
@@ -0,0 +1,22 @@
+
+
+
+ CORS middleware and policy for ASP.NET Core to enable cross-origin resource sharing.
+Commonly used types:
+Microsoft.AspNetCore.Cors.DisableCorsAttribute
+Microsoft.AspNetCore.Cors.EnableCorsAttribute
+ netstandard2.0
+ $(NoWarn);CS1591
+ true
+ aspnetcore;cors
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Properties/AssemblyInfo.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..b3d086e2e5
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Properties/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+// 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.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cors.Test,PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Resources.Designer.cs b/src/CORS/src/Microsoft.AspNetCore.Cors/Resources.Designer.cs
new file mode 100644
index 0000000000..0bc258074c
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.AspNetCore.Cors {
+ using System;
+ using System.Reflection;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNetCore.Cors.Resources", typeof(Resources).GetTypeInfo().Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to PreflightMaxAge must be greater than or equal to 0..
+ ///
+ internal static string PreflightMaxAgeOutOfRange {
+ get {
+ return ResourceManager.GetString("PreflightMaxAgeOutOfRange", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/Resources.resx b/src/CORS/src/Microsoft.AspNetCore.Cors/Resources.resx
new file mode 100644
index 0000000000..6b9ebaad31
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/Resources.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ PreflightMaxAge must be greater than or equal to 0.
+
+
\ No newline at end of file
diff --git a/src/CORS/src/Microsoft.AspNetCore.Cors/baseline.netcore.json b/src/CORS/src/Microsoft.AspNetCore.Cors/baseline.netcore.json
new file mode 100644
index 0000000000..ef96cf2eed
--- /dev/null
+++ b/src/CORS/src/Microsoft.AspNetCore.Cors/baseline.netcore.json
@@ -0,0 +1,1250 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.Cors, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.Builder.CorsMiddlewareExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "UseCors",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "UseCors",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ },
+ {
+ "Name": "policyName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "UseCors",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ },
+ {
+ "Name": "configurePolicy",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.DisableCorsAttribute",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "System.Attribute",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Cors.Infrastructure.IDisableCorsAttribute"
+ ],
+ "Members": [
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.EnableCorsAttribute",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "System.Attribute",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Cors.Infrastructure.IEnableCorsAttribute"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_PolicyName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Cors.Infrastructure.IEnableCorsAttribute",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_PolicyName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Cors.Infrastructure.IEnableCorsAttribute",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "policyName",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.Infrastructure.CorsConstants",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Field",
+ "Name": "PreflightHttpMethod",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "Origin",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "AnyOrigin",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "AccessControlRequestMethod",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "AccessControlRequestHeaders",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "AccessControlAllowOrigin",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "AccessControlAllowHeaders",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "AccessControlExposeHeaders",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "AccessControlAllowMethods",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "AccessControlAllowCredentials",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "AccessControlMaxAge",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Invoke",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "next",
+ "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+ },
+ {
+ "Name": "corsService",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsService"
+ },
+ {
+ "Name": "policyProvider",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsPolicyProvider"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "next",
+ "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+ },
+ {
+ "Name": "corsService",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsService"
+ },
+ {
+ "Name": "policyProvider",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsPolicyProvider"
+ },
+ {
+ "Name": "policyName",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "next",
+ "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+ },
+ {
+ "Name": "corsService",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsService"
+ },
+ {
+ "Name": "policy",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_DefaultPolicyName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_DefaultPolicyName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AddDefaultPolicy",
+ "Parameters": [
+ {
+ "Name": "policy",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AddDefaultPolicy",
+ "Parameters": [
+ {
+ "Name": "configurePolicy",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AddPolicy",
+ "Parameters": [
+ {
+ "Name": "name",
+ "Type": "System.String"
+ },
+ {
+ "Name": "policy",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AddPolicy",
+ "Parameters": [
+ {
+ "Name": "name",
+ "Type": "System.String"
+ },
+ {
+ "Name": "configurePolicy",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetPolicy",
+ "Parameters": [
+ {
+ "Name": "name",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_AllowAnyHeader",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_AllowAnyMethod",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_AllowAnyOrigin",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_IsOriginAllowed",
+ "Parameters": [],
+ "ReturnType": "System.Func",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_IsOriginAllowed",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Func"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ExposedHeaders",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IList",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Headers",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IList",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Methods",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IList",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Origins",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IList",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_PreflightMaxAge",
+ "Parameters": [],
+ "ReturnType": "System.Nullable",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_PreflightMaxAge",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Nullable"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_SupportsCredentials",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_SupportsCredentials",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Boolean"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ToString",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "WithOrigins",
+ "Parameters": [
+ {
+ "Name": "origins",
+ "Type": "System.String[]",
+ "IsParams": true
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "WithHeaders",
+ "Parameters": [
+ {
+ "Name": "headers",
+ "Type": "System.String[]",
+ "IsParams": true
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "WithExposedHeaders",
+ "Parameters": [
+ {
+ "Name": "exposedHeaders",
+ "Type": "System.String[]",
+ "IsParams": true
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "WithMethods",
+ "Parameters": [
+ {
+ "Name": "methods",
+ "Type": "System.String[]",
+ "IsParams": true
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AllowCredentials",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "DisallowCredentials",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AllowAnyOrigin",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AllowAnyMethod",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AllowAnyHeader",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SetPreflightMaxAge",
+ "Parameters": [
+ {
+ "Name": "preflightMaxAge",
+ "Type": "System.TimeSpan"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SetIsOriginAllowed",
+ "Parameters": [
+ {
+ "Name": "isOriginAllowed",
+ "Type": "System.Func"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SetIsOriginAllowedToAllowWildcardSubdomains",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Build",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "origins",
+ "Type": "System.String[]",
+ "IsParams": true
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "policy",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.Infrastructure.CorsResult",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_AllowedOrigin",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_AllowedOrigin",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_SupportsCredentials",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_SupportsCredentials",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Boolean"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_AllowedMethods",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IList",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_AllowedHeaders",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IList",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_AllowedExposedHeaders",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IList",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_VaryByOrigin",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_VaryByOrigin",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Boolean"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_PreflightMaxAge",
+ "Parameters": [],
+ "ReturnType": "System.Nullable",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_PreflightMaxAge",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Nullable"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ToString",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.Infrastructure.CorsService",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Cors.Infrastructure.ICorsService"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "EvaluatePolicy",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "policyName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsResult",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "EvaluatePolicy",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "policy",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsResult",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsService",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "EvaluateRequest",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "policy",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy"
+ },
+ {
+ "Name": "result",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.CorsResult"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "EvaluatePreflightRequest",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "policy",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy"
+ },
+ {
+ "Name": "result",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.CorsResult"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyResult",
+ "Parameters": [
+ {
+ "Name": "result",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.CorsResult"
+ },
+ {
+ "Name": "response",
+ "Type": "Microsoft.AspNetCore.Http.HttpResponse"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsService",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "options",
+ "Type": "Microsoft.Extensions.Options.IOptions"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "options",
+ "Type": "Microsoft.Extensions.Options.IOptions"
+ },
+ {
+ "Name": "loggerFactory",
+ "Type": "Microsoft.Extensions.Logging.ILoggerFactory"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.Infrastructure.DefaultCorsPolicyProvider",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Cors.Infrastructure.ICorsPolicyProvider"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "GetPolicyAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "policyName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsPolicyProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "options",
+ "Type": "Microsoft.Extensions.Options.IOptions"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsPolicyProvider",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "GetPolicyAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "policyName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsService",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "EvaluatePolicy",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "policy",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Cors.Infrastructure.CorsResult",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyResult",
+ "Parameters": [
+ {
+ "Name": "result",
+ "Type": "Microsoft.AspNetCore.Cors.Infrastructure.CorsResult"
+ },
+ {
+ "Name": "response",
+ "Type": "Microsoft.AspNetCore.Http.HttpResponse"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.Infrastructure.IDisableCorsAttribute",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cors.Infrastructure.IEnableCorsAttribute",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_PolicyName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_PolicyName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.Extensions.DependencyInjection.CorsServiceCollectionExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "AddCors",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+ }
+ ],
+ "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AddCors",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+ },
+ {
+ "Name": "setupAction",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/CORS/test/Directory.Build.props b/src/CORS/test/Directory.Build.props
new file mode 100644
index 0000000000..4244624987
--- /dev/null
+++ b/src/CORS/test/Directory.Build.props
@@ -0,0 +1,15 @@
+
+
+
+
+ netcoreapp2.1
+ $(DeveloperBuildTestTfms)
+ $(StandardTestTfms);netcoreapp2.0
+ $(StandardTestTfms);net461
+
+
+
+
+
+
+
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsMiddlewareFunctionalTest.cs b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsMiddlewareFunctionalTest.cs
new file mode 100644
index 0000000000..008a4926d9
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsMiddlewareFunctionalTest.cs
@@ -0,0 +1,98 @@
+// 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.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ public class CorsMiddlewareFunctionalTests : IClassFixture>
+ {
+ public CorsMiddlewareFunctionalTests(CorsTestFixture fixture)
+ {
+ Client = fixture.Client;
+ }
+
+ public HttpClient Client { get; }
+
+ [Theory]
+ [InlineData("GET")]
+ [InlineData("HEAD")]
+ [InlineData("POST")]
+ public async Task ResourceWithSimpleRequestPolicy_Allows_SimpleRequests(string method)
+ {
+ // Arrange
+ var path = "/CorsMiddleware/EC6AA70D-BA3E-4B71-A87F-18625ADDB2BD";
+ var origin = "http://example.com";
+ var request = new HttpRequestMessage(new HttpMethod(method), path);
+ request.Headers.Add(CorsConstants.Origin, origin);
+
+ // Act
+ var response = await Client.SendAsync(request);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var content = await response.Content.ReadAsStringAsync();
+ Assert.Equal(path, content);
+ var responseHeaders = response.Headers;
+ var header = Assert.Single(response.Headers);
+ Assert.Equal(CorsConstants.AccessControlAllowOrigin, header.Key);
+ Assert.Equal(new[] { "http://example.com" }, header.Value.ToArray());
+ }
+
+ [Theory]
+ [InlineData("GET")]
+ [InlineData("HEAD")]
+ [InlineData("POST")]
+ [InlineData("PUT")]
+ public async Task PolicyFailed_Disallows_PreFlightRequest(string method)
+ {
+ // Arrange
+ var path = "/CorsMiddleware/9B8BB9C6-5BF2-4255-A636-DCB450D51AAE";
+ var request = new HttpRequestMessage(new HttpMethod(CorsConstants.PreflightHttpMethod), path);
+
+ // Adding a custom header makes it a non-simple request.
+ request.Headers.Add(CorsConstants.Origin, "http://example.com");
+ request.Headers.Add(CorsConstants.AccessControlRequestMethod, method);
+ request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom");
+
+ // Act
+ var response = await Client.SendAsync(request);
+
+ // Assert
+ // Middleware applied the policy and since that did not pass, there were no access control headers.
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+ Assert.Empty(response.Headers);
+
+ // It should short circuit and hence no result.
+ var content = await response.Content.ReadAsStringAsync();
+ Assert.Equal(string.Empty, content);
+ }
+
+ [Fact]
+ public async Task PolicyFailed_Allows_ActualRequest_WithMissingResponseHeaders()
+ {
+ // Arrange
+ var path = "/CorsMiddleware/1E6C6F4D-1E1C-450E-8BD0-73DBF089A78F";
+ var request = new HttpRequestMessage(HttpMethod.Put, path);
+
+ // Adding a custom header makes it a non simple request.
+ request.Headers.Add(CorsConstants.Origin, "http://example2.com");
+
+ // Act
+ var response = await Client.SendAsync(request);
+
+ // Assert
+ // Middleware applied the policy and since that did not pass, there were no access control headers.
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Empty(response.Headers);
+
+ // It still has executed the action.
+ var content = await response.Content.ReadAsStringAsync();
+ Assert.Equal(path, content);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsMiddlewareTests.cs b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsMiddlewareTests.cs
new file mode 100644
index 0000000000..d74c020eae
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsMiddlewareTests.cs
@@ -0,0 +1,353 @@
+// 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.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Moq;
+using Xunit;
+
+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.Single(response.Headers);
+ 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()
+ {
+ // Arrange
+ var hostBuilder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseCors(builder =>
+ builder.WithOrigins("http://localhost:5001")
+ .WithMethods("PUT")
+ .WithHeaders("Header1")
+ .WithExposedHeaders("AllowedHeader"));
+ 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("PUT");
+
+ // Assert
+ response.EnsureSuccessStatusCode();
+ Assert.Equal(2, response.Headers.Count());
+ Assert.Equal("Cross origin response", await response.Content.ReadAsStringAsync());
+ Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
+ Assert.Equal("AllowedHeader", response.Headers.GetValues(CorsConstants.AccessControlExposeHeaders).FirstOrDefault());
+ }
+ }
+
+ [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.Single(response.Headers);
+ Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
+ }
+ }
+
+ [Fact]
+ public async Task PreFlight_MatchesPolicy_SetsResponseHeaders()
+ {
+ // Arrange
+ var policy = new CorsPolicy();
+ policy.Origins.Add("http://localhost:5001");
+ policy.Methods.Add("PUT");
+ policy.Headers.Add("Header1");
+ policy.ExposedHeaders.Add("AllowedHeader");
+
+ 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")
+ .AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
+ .SendAsync(CorsConstants.PreflightHttpMethod);
+
+ // Assert
+ response.EnsureSuccessStatusCode();
+ Assert.Equal(2, response.Headers.Count());
+ Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
+ Assert.Equal("PUT", response.Headers.GetValues(CorsConstants.AccessControlAllowMethods).FirstOrDefault());
+ }
+ }
+
+ [Fact]
+ public async Task PreFlightRequest_DoesNotMatchPolicy_DoesNotSetHeaders()
+ {
+ // Arrange
+ var hostBuilder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseCors(builder =>
+ builder.WithOrigins("http://localhost:5001")
+ .WithMethods("PUT")
+ .WithHeaders("Header1")
+ .WithExposedHeaders("AllowedHeader"));
+ app.Run(async context =>
+ {
+ await context.Response.WriteAsync("Cross origin response");
+ });
+ })
+ .ConfigureServices(services => services.AddCors());
+
+ using (var server = new TestServer(hostBuilder))
+ {
+ // Act
+ // Preflight request.
+ var response = await server.CreateRequest("/")
+ .AddHeader(CorsConstants.Origin, "http://localhost:5002")
+ .AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
+ .SendAsync(CorsConstants.PreflightHttpMethod);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+ Assert.Empty(response.Headers);
+ }
+ }
+
+ [Fact]
+ public async Task CorsRequest_DoesNotMatchPolicy_DoesNotSetHeaders()
+ {
+ // Arrange
+ var hostBuilder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseCors(builder =>
+ builder.WithOrigins("http://localhost:5001")
+ .WithMethods("PUT")
+ .WithHeaders("Header1")
+ .WithExposedHeaders("AllowedHeader"));
+ 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:5002")
+ .SendAsync("PUT");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Empty(response.Headers);
+ }
+ }
+
+ [Fact]
+ public async Task Uses_PolicyProvider_AsFallback()
+ {
+ // Arrange
+ var corsService = Mock.Of();
+ var mockProvider = new Mock();
+ mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny(), It.IsAny()))
+ .Returns(Task.FromResult(null))
+ .Verifiable();
+
+ var middleware = new CorsMiddleware(
+ Mock.Of(),
+ corsService,
+ mockProvider.Object,
+ policyName: null);
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Headers.Add(CorsConstants.Origin, new[] { "http://example.com" });
+
+ // Act
+ await middleware.Invoke(httpContext);
+
+ // Assert
+ mockProvider.Verify(
+ o => o.GetPolicyAsync(It.IsAny(), It.IsAny()),
+ Times.Once);
+ }
+
+ [Fact]
+ public async Task DoesNotSetHeaders_ForNoPolicy()
+ {
+ // Arrange
+ var corsService = Mock.Of();
+ var mockProvider = new Mock();
+ mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny(), It.IsAny()))
+ .Returns(Task.FromResult(null))
+ .Verifiable();
+
+ var middleware = new CorsMiddleware(
+ Mock.Of(),
+ corsService,
+ mockProvider.Object,
+ policyName: null);
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Headers.Add(CorsConstants.Origin, new[] { "http://example.com" });
+
+ // Act
+ await middleware.Invoke(httpContext);
+
+ // Assert
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.Empty(httpContext.Response.Headers);
+ mockProvider.Verify(
+ o => o.GetPolicyAsync(It.IsAny(), It.IsAny()),
+ Times.Once);
+ }
+
+ [Fact]
+ public async Task PreFlight_MatchesDefaultPolicy_SetsResponseHeaders()
+ {
+ // Arrange
+ var hostBuilder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseCors();
+ app.Run(async context =>
+ {
+ await context.Response.WriteAsync("Cross origin response");
+ });
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddCors(options =>
+ {
+ options.AddDefaultPolicy(policyBuilder =>
+ {
+ policyBuilder
+ .WithOrigins("http://localhost:5001")
+ .WithMethods("PUT")
+ .WithHeaders("Header1")
+ .WithExposedHeaders("AllowedHeader")
+ .Build();
+ });
+ options.AddPolicy("policy2", policyBuilder =>
+ {
+ policyBuilder
+ .WithOrigins("http://localhost:5002")
+ .Build();
+ });
+ });
+ });
+
+ using (var server = new TestServer(hostBuilder))
+ {
+ // Act
+ // Preflight request.
+ var response = await server.CreateRequest("/")
+ .AddHeader(CorsConstants.Origin, "http://localhost:5001")
+ .AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
+ .SendAsync(CorsConstants.PreflightHttpMethod);
+
+ // Assert
+ response.EnsureSuccessStatusCode();
+ Assert.Equal(2, response.Headers.Count());
+ Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
+ Assert.Equal("PUT", response.Headers.GetValues(CorsConstants.AccessControlAllowMethods).FirstOrDefault());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsOptionsTest.cs b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsOptionsTest.cs
new file mode 100644
index 0000000000..360231d38b
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsOptionsTest.cs
@@ -0,0 +1,67 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ public class CorsOptionsTest
+ {
+ [Fact]
+ public void AddDefaultPolicy_SetsDefaultPolicyName()
+ {
+ // Arrange
+ var corsOptions = new CorsOptions();
+ var expectedPolicy = new CorsPolicy();
+
+ // Act
+ corsOptions.AddPolicy("policy1", new CorsPolicy());
+ corsOptions.AddDefaultPolicy(expectedPolicy);
+ corsOptions.AddPolicy("policy3", new CorsPolicy());
+
+ // Assert
+ var actualPolicy = corsOptions.GetPolicy(corsOptions.DefaultPolicyName);
+ Assert.Same(expectedPolicy, actualPolicy);
+ }
+
+ [Fact]
+ public void AddDefaultPolicy_OverridesDefaultPolicyName()
+ {
+ // Arrange
+ var corsOptions = new CorsOptions();
+ var expectedPolicy = new CorsPolicy();
+
+ // Act
+ corsOptions.AddDefaultPolicy(new CorsPolicy());
+ corsOptions.AddDefaultPolicy(expectedPolicy);
+
+ // Assert
+ var actualPolicy = corsOptions.GetPolicy(corsOptions.DefaultPolicyName);
+ Assert.Same(expectedPolicy, actualPolicy);
+ }
+
+ [Fact]
+ public void AddDefaultPolicy_UsingPolicyBuilder_SetsDefaultPolicyName()
+ {
+ // Arrange
+ var corsOptions = new CorsOptions();
+ CorsPolicy expectedPolicy = null;
+
+ // Act
+ corsOptions.AddPolicy("policy1", policyBuilder =>
+ {
+ policyBuilder.AllowAnyOrigin().Build();
+ });
+ corsOptions.AddDefaultPolicy(policyBuilder =>
+ {
+ expectedPolicy = policyBuilder.AllowAnyOrigin().Build();
+ });
+ corsOptions.AddPolicy("policy3", new CorsPolicy());
+
+ // Assert
+ var actualPolicy = corsOptions.GetPolicy(corsOptions.DefaultPolicyName);
+ Assert.Same(expectedPolicy, actualPolicy);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsPolicyBuilderTests.cs b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsPolicyBuilderTests.cs
new file mode 100644
index 0000000000..8a223ad225
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsPolicyBuilderTests.cs
@@ -0,0 +1,292 @@
+// 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.Linq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ public class CorsPolicyBuilderTests
+ {
+ [Fact]
+ public void Constructor_WithPolicy_AddsTheGivenPolicy()
+ {
+ // Arrange
+ Func isOriginAllowed = origin => true;
+ var originalPolicy = new CorsPolicy();
+ originalPolicy.Origins.Add("http://existing.com");
+ originalPolicy.Headers.Add("Existing");
+ originalPolicy.Methods.Add("GET");
+ originalPolicy.ExposedHeaders.Add("ExistingExposed");
+ originalPolicy.SupportsCredentials = true;
+ originalPolicy.PreflightMaxAge = TimeSpan.FromSeconds(12);
+ originalPolicy.IsOriginAllowed = isOriginAllowed;
+
+ // Act
+ var builder = new CorsPolicyBuilder(originalPolicy);
+
+ // Assert
+ var corsPolicy = builder.Build();
+
+ Assert.False(corsPolicy.AllowAnyHeader);
+ Assert.False(corsPolicy.AllowAnyMethod);
+ Assert.False(corsPolicy.AllowAnyOrigin);
+ Assert.True(corsPolicy.SupportsCredentials);
+ Assert.NotSame(originalPolicy.Headers, corsPolicy.Headers);
+ Assert.Equal(originalPolicy.Headers, corsPolicy.Headers);
+ Assert.NotSame(originalPolicy.Methods, corsPolicy.Methods);
+ Assert.Equal(originalPolicy.Methods, corsPolicy.Methods);
+ Assert.NotSame(originalPolicy.Origins, corsPolicy.Origins);
+ Assert.Equal(originalPolicy.Origins, corsPolicy.Origins);
+ Assert.NotSame(originalPolicy.ExposedHeaders, corsPolicy.ExposedHeaders);
+ Assert.Equal(originalPolicy.ExposedHeaders, corsPolicy.ExposedHeaders);
+ Assert.Equal(TimeSpan.FromSeconds(12), corsPolicy.PreflightMaxAge);
+ Assert.Same(originalPolicy.IsOriginAllowed, corsPolicy.IsOriginAllowed);
+ }
+
+ [Fact]
+ public void ConstructorWithPolicy_HavingNullPreflightMaxAge_AddsTheGivenPolicy()
+ {
+ // Arrange
+ var originalPolicy = new CorsPolicy();
+ originalPolicy.Origins.Add("http://existing.com");
+
+ // Act
+ var builder = new CorsPolicyBuilder(originalPolicy);
+
+ // Assert
+ var corsPolicy = builder.Build();
+
+ Assert.Null(corsPolicy.PreflightMaxAge);
+ Assert.False(corsPolicy.AllowAnyHeader);
+ Assert.False(corsPolicy.AllowAnyMethod);
+ Assert.False(corsPolicy.AllowAnyOrigin);
+ Assert.NotSame(originalPolicy.Origins, corsPolicy.Origins);
+ Assert.Equal(originalPolicy.Origins, corsPolicy.Origins);
+ Assert.Empty(corsPolicy.Headers);
+ Assert.Empty(corsPolicy.Methods);
+ Assert.Empty(corsPolicy.ExposedHeaders);
+ }
+
+ [Fact]
+ public void Constructor_WithNoOrigin()
+ {
+ // Arrange & Act
+ var builder = new CorsPolicyBuilder();
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.False(corsPolicy.AllowAnyHeader);
+ Assert.False(corsPolicy.AllowAnyMethod);
+ Assert.False(corsPolicy.AllowAnyOrigin);
+ Assert.False(corsPolicy.SupportsCredentials);
+ Assert.Empty(corsPolicy.ExposedHeaders);
+ Assert.Empty(corsPolicy.Headers);
+ Assert.Empty(corsPolicy.Methods);
+ Assert.Empty(corsPolicy.Origins);
+ Assert.Null(corsPolicy.PreflightMaxAge);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("http://example.com,http://example2.com")]
+ public void Constructor_WithParamsOrigin_InitializesOrigin(string origin)
+ {
+ // Arrange
+ var origins = origin.Split(',');
+
+ // Act
+ var builder = new CorsPolicyBuilder(origins);
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.False(corsPolicy.AllowAnyHeader);
+ Assert.False(corsPolicy.AllowAnyMethod);
+ Assert.False(corsPolicy.AllowAnyOrigin);
+ Assert.False(corsPolicy.SupportsCredentials);
+ Assert.Empty(corsPolicy.ExposedHeaders);
+ Assert.Empty(corsPolicy.Headers);
+ Assert.Empty(corsPolicy.Methods);
+ Assert.Equal(origins.ToList(), corsPolicy.Origins);
+ Assert.Null(corsPolicy.PreflightMaxAge);
+ }
+
+ [Fact]
+ public void WithOrigins_AddsOrigins()
+ {
+ // Arrange
+ var builder = new CorsPolicyBuilder();
+
+ // Act
+ builder.WithOrigins("http://example.com", "http://example2.com");
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.False(corsPolicy.AllowAnyOrigin);
+ Assert.Equal(new List() { "http://example.com", "http://example2.com" }, corsPolicy.Origins);
+ }
+
+ [Fact]
+ public void AllowAnyOrigin_AllowsAny()
+ {
+ // Arrange
+ var builder = new CorsPolicyBuilder();
+
+ // Act
+ builder.AllowAnyOrigin();
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.True(corsPolicy.AllowAnyOrigin);
+ Assert.Equal(new List() { "*" }, corsPolicy.Origins);
+ }
+
+ [Fact]
+ public void SetIsOriginAllowed_AddsIsOriginAllowed()
+ {
+ // Arrange
+ var builder = new CorsPolicyBuilder();
+ Func isOriginAllowed = origin => true;
+
+ // Act
+ builder.SetIsOriginAllowed(isOriginAllowed);
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.Same(corsPolicy.IsOriginAllowed, isOriginAllowed);
+ }
+
+ [Fact]
+ public void SetIsOriginAllowedToAllowWildcardSubdomains_AllowsWildcardSubdomains()
+ {
+ // Arrange
+ var builder = new CorsPolicyBuilder("http://*.example.com");
+
+ // Act
+ builder.SetIsOriginAllowedToAllowWildcardSubdomains();
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.True(corsPolicy.IsOriginAllowed("http://test.example.com"));
+ }
+
+ [Fact]
+ public void WithMethods_AddsMethods()
+ {
+ // Arrange
+ var builder = new CorsPolicyBuilder();
+
+ // Act
+ builder.WithMethods("PUT", "GET");
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.False(corsPolicy.AllowAnyOrigin);
+ Assert.Equal(new List() { "PUT", "GET" }, corsPolicy.Methods);
+ }
+
+ [Fact]
+ public void AllowAnyMethod_AllowsAny()
+ {
+ // Arrange
+ var builder = new CorsPolicyBuilder();
+
+ // Act
+ builder.AllowAnyMethod();
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.True(corsPolicy.AllowAnyMethod);
+ Assert.Equal(new List() { "*" }, corsPolicy.Methods);
+ }
+
+ [Fact]
+ public void WithHeaders_AddsHeaders()
+ {
+ // Arrange
+ var builder = new CorsPolicyBuilder();
+
+ // Act
+ builder.WithHeaders("example1", "example2");
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.False(corsPolicy.AllowAnyHeader);
+ Assert.Equal(new List() { "example1", "example2" }, corsPolicy.Headers);
+ }
+
+ [Fact]
+ public void AllowAnyHeaders_AllowsAny()
+ {
+ // Arrange
+ var builder = new CorsPolicyBuilder();
+
+ // Act
+ builder.AllowAnyHeader();
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.True(corsPolicy.AllowAnyHeader);
+ Assert.Equal(new List() { "*" }, corsPolicy.Headers);
+ }
+
+ [Fact]
+ public void WithExposedHeaders_AddsExposedHeaders()
+ {
+ // Arrange
+ var builder = new CorsPolicyBuilder();
+
+ // Act
+ builder.WithExposedHeaders("exposed1", "exposed2");
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.Equal(new List() { "exposed1", "exposed2" }, corsPolicy.ExposedHeaders);
+ }
+
+ [Fact]
+ public void SetPreFlightMaxAge_SetsThePreFlightAge()
+ {
+ // Arrange
+ var builder = new CorsPolicyBuilder();
+
+ // Act
+ builder.SetPreflightMaxAge(TimeSpan.FromSeconds(12));
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.Equal(TimeSpan.FromSeconds(12), corsPolicy.PreflightMaxAge);
+ }
+
+ [Fact]
+ public void AllowCredential_SetsSupportsCredentials_ToTrue()
+ {
+ // Arrange
+ var builder = new CorsPolicyBuilder();
+
+ // Act
+ builder.AllowCredentials();
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.True(corsPolicy.SupportsCredentials);
+ }
+
+
+ [Fact]
+ public void DisallowCredential_SetsSupportsCredentials_ToFalse()
+ {
+ // Arrange
+ var builder = new CorsPolicyBuilder();
+
+ // Act
+ builder.DisallowCredentials();
+
+ // Assert
+ var corsPolicy = builder.Build();
+ Assert.False(corsPolicy.SupportsCredentials);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsPolicyExtensionsTests.cs b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsPolicyExtensionsTests.cs
new file mode 100644
index 0000000000..74dd67db0b
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsPolicyExtensionsTests.cs
@@ -0,0 +1,85 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ public sealed class CorsPolicyExtensionsTest
+ {
+ [Fact]
+ public void IsOriginAnAllowedSubdomain_ReturnsTrueIfPolicyContainsOrigin()
+ {
+ // Arrange
+ const string origin = "http://sub.domain";
+ var policy = new CorsPolicy();
+ policy.Origins.Add(origin);
+
+ // Act
+ var actual = policy.IsOriginAnAllowedSubdomain(origin);
+
+ // Assert
+ Assert.True(actual);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("null")]
+ [InlineData("http://")]
+ [InlineData("http://*")]
+ [InlineData("http://.domain")]
+ [InlineData("http://.domain/hello")]
+ public void IsOriginAnAllowedSubdomain_ReturnsFalseIfOriginIsMalformedUri(string malformedOrigin)
+ {
+ // Arrange
+ var policy = new CorsPolicy();
+ policy.Origins.Add("http://*.domain");
+
+ // Act
+ var actual = policy.IsOriginAnAllowedSubdomain(malformedOrigin);
+
+ // Assert
+ Assert.False(actual);
+ }
+
+ [Theory]
+ [InlineData("http://sub.domain", "http://*.domain")]
+ [InlineData("http://sub.sub.domain", "http://*.domain")]
+ [InlineData("http://sub.sub.domain", "http://*.sub.domain")]
+ [InlineData("http://sub.domain:4567", "http://*.domain:4567")]
+ public void IsOriginAnAllowedSubdomain_ReturnsTrue_WhenASubdomain(string origin, string allowedOrigin)
+ {
+ // Arrange
+ var policy = new CorsPolicy();
+ policy.Origins.Add(allowedOrigin);
+
+ // Act
+ var isAllowed = policy.IsOriginAnAllowedSubdomain(origin);
+
+ // Assert
+ Assert.True(isAllowed);
+ }
+
+ [Theory]
+ [InlineData("http://domain", "http://*.domain")]
+ [InlineData("http://sub.domain", "http://domain")]
+ [InlineData("http://sub.domain:1234", "http://*.domain:5678")]
+ [InlineData("http://sub.domain", "http://domain.*")]
+ [InlineData("http://sub.sub.domain", "http://sub.*.domain")]
+ [InlineData("http://sub.domain.hacker", "http://*.domain")]
+ [InlineData("https://sub.domain", "http://*.domain")]
+ public void IsOriginAnAllowedSubdomain_ReturnsFalse_WhenNotASubdomain(string origin, string allowedOrigin)
+ {
+ // Arrange
+ var policy = new CorsPolicy();
+ policy.Origins.Add(allowedOrigin);
+
+ // Act
+ var isAllowed = policy.IsOriginAnAllowedSubdomain(origin);
+
+ // Assert
+ Assert.False(isAllowed);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsPolicyTests.cs b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsPolicyTests.cs
new file mode 100644
index 0000000000..f49e30fabc
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsPolicyTests.cs
@@ -0,0 +1,74 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ public class CorsPolicyTest
+ {
+ [Fact]
+ public void Default_Constructor()
+ {
+ // Arrange & Act
+ var corsPolicy = new CorsPolicy();
+
+ // Assert
+ Assert.False(corsPolicy.AllowAnyHeader);
+ Assert.False(corsPolicy.AllowAnyMethod);
+ Assert.False(corsPolicy.AllowAnyOrigin);
+ Assert.False(corsPolicy.SupportsCredentials);
+ Assert.Empty(corsPolicy.ExposedHeaders);
+ Assert.Empty(corsPolicy.Headers);
+ Assert.Empty(corsPolicy.Methods);
+ Assert.Empty(corsPolicy.Origins);
+ Assert.Null(corsPolicy.PreflightMaxAge);
+ Assert.NotNull(corsPolicy.IsOriginAllowed);
+ }
+
+ [Fact]
+ public void SettingNegativePreflightMaxAge_Throws()
+ {
+ // Arrange
+ var policy = new CorsPolicy();
+
+ // Act
+ var exception = Assert.Throws(() =>
+ {
+ policy.PreflightMaxAge = TimeSpan.FromSeconds(-12);
+ });
+
+ // Assert
+ Assert.Equal(
+ $"PreflightMaxAge must be greater than or equal to 0.{Environment.NewLine}Parameter name: value",
+ exception.Message);
+ }
+
+ [Fact]
+ public void ToString_ReturnsThePropertyValues()
+ {
+ // Arrange
+ var corsPolicy = new CorsPolicy
+ {
+ PreflightMaxAge = TimeSpan.FromSeconds(12),
+ SupportsCredentials = true
+ };
+ corsPolicy.Headers.Add("foo");
+ corsPolicy.Headers.Add("bar");
+ corsPolicy.Origins.Add("http://example.com");
+ corsPolicy.Origins.Add("http://example.org");
+ corsPolicy.Methods.Add("GET");
+
+ // Act
+ var policyString = corsPolicy.ToString();
+
+ // Assert
+ Assert.Equal(
+ @"AllowAnyHeader: False, AllowAnyMethod: False, AllowAnyOrigin: False, PreflightMaxAge: 12,"+
+ " SupportsCredentials: True, Origins: {http://example.com,http://example.org}, Methods: {GET},"+
+ " Headers: {foo,bar}, ExposedHeaders: {}",
+ policyString);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsResultTests.cs b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsResultTests.cs
new file mode 100644
index 0000000000..bc6e4a2d3a
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsResultTests.cs
@@ -0,0 +1,69 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ public class CorsResultTest
+ {
+ [Fact]
+ public void Default_Constructor()
+ {
+ // Arrange & Act
+ var result = new CorsResult();
+
+ // Assert
+ Assert.Empty(result.AllowedHeaders);
+ Assert.Empty(result.AllowedExposedHeaders);
+ Assert.Empty(result.AllowedMethods);
+ Assert.False(result.SupportsCredentials);
+ Assert.Null(result.AllowedOrigin);
+ Assert.Null(result.PreflightMaxAge);
+ }
+
+ [Fact]
+ public void SettingNegativePreflightMaxAge_Throws()
+ {
+ // Arrange
+ var result = new CorsResult();
+
+ // Act
+ var exception = Assert.Throws(() =>
+ {
+ result.PreflightMaxAge = TimeSpan.FromSeconds(-1);
+ });
+
+ // Assert
+ Assert.Equal(
+ $"PreflightMaxAge must be greater than or equal to 0.{Environment.NewLine}Parameter name: value",
+ exception.Message);
+ }
+
+ [Fact]
+ public void ToString_ReturnsThePropertyValues()
+ {
+ // Arrange
+ var corsResult = new CorsResult
+ {
+ SupportsCredentials = true,
+ PreflightMaxAge = TimeSpan.FromSeconds(30),
+ AllowedOrigin = "*"
+ };
+ corsResult.AllowedExposedHeaders.Add("foo");
+ corsResult.AllowedHeaders.Add("bar");
+ corsResult.AllowedHeaders.Add("baz");
+ corsResult.AllowedMethods.Add("GET");
+
+ // Act
+ var result = corsResult.ToString();
+
+ // Assert
+ Assert.Equal(
+ @"AllowCredentials: True, PreflightMaxAge: 30, AllowOrigin: *," +
+ " AllowExposedHeaders: {foo}, AllowHeaders: {bar,baz}, AllowMethods: {GET}",
+ result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsServiceTests.cs b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsServiceTests.cs
new file mode 100644
index 0000000000..8a71ce7b42
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsServiceTests.cs
@@ -0,0 +1,1167 @@
+// 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 Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging.Testing;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ public class CorsServiceTests
+ {
+ [Fact]
+ public void EvaluatePolicy_NoOrigin_ReturnsInvalidResult()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext("GET", origin: null);
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, new CorsPolicy());
+
+ // Assert
+ Assert.Null(result.AllowedOrigin);
+ Assert.False(result.VaryByOrigin);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_NoMatchingOrigin_ReturnsInvalidResult()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(origin: "http://example.com");
+ var policy = new CorsPolicy();
+ policy.Origins.Add("bar");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Null(result.AllowedOrigin);
+ Assert.False(result.VaryByOrigin);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_EmptyOriginsPolicy_ReturnsInvalidResult()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(origin: "http://example.com");
+ var policy = new CorsPolicy();
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Null(result.AllowedOrigin);
+ Assert.False(result.VaryByOrigin);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_IsOriginAllowedReturnsFalse_ReturnsInvalidResult()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(origin: "http://example.com");
+ var policy = new CorsPolicy()
+ {
+ IsOriginAllowed = origin => false
+ };
+ policy.Origins.Add("example.com");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Null(result.AllowedOrigin);
+ Assert.False(result.VaryByOrigin);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_AllowAnyOrigin_DoesNotSupportCredentials_EmitsWildcardForOrigin()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(origin: "http://example.com");
+
+ var policy = new CorsPolicy
+ {
+ SupportsCredentials = false
+ };
+
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Equal("*", result.AllowedOrigin);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_AllowAnyOrigin_SupportsCredentials_AddsSpecificOrigin()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(origin: "http://example.com");
+ var policy = new CorsPolicy
+ {
+ SupportsCredentials = true
+ };
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Equal("http://example.com", result.AllowedOrigin);
+ Assert.True(result.VaryByOrigin);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_DoesNotSupportCredentials_AllowCredentialsReturnsFalse()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(origin: "http://example.com");
+ var policy = new CorsPolicy
+ {
+ SupportsCredentials = false
+ };
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.False(result.SupportsCredentials);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_SupportsCredentials_AllowCredentialsReturnsTrue()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(origin: "http://example.com");
+ var policy = new CorsPolicy
+ {
+ SupportsCredentials = true
+ };
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.True(result.SupportsCredentials);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_NoExposedHeaders_NoAllowExposedHeaders()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(origin: "http://example.com");
+ var policy = new CorsPolicy();
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Empty(result.AllowedExposedHeaders);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_OneExposedHeaders_HeadersAllowed()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(origin: "http://example.com");
+ var policy = new CorsPolicy();
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.ExposedHeaders.Add("foo");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Equal(1, result.AllowedExposedHeaders.Count);
+ Assert.Contains("foo", result.AllowedExposedHeaders);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_ManyExposedHeaders_HeadersAllowed()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(origin: "http://example.com");
+ var policy = new CorsPolicy();
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.ExposedHeaders.Add("foo");
+ policy.ExposedHeaders.Add("bar");
+ policy.ExposedHeaders.Add("baz");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Equal(3, result.AllowedExposedHeaders.Count);
+ Assert.Contains("foo", result.AllowedExposedHeaders);
+ Assert.Contains("bar", result.AllowedExposedHeaders);
+ Assert.Contains("baz", result.AllowedExposedHeaders);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_PreflightRequest_MethodNotAllowed_ReturnsInvalidResult()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
+ var policy = new CorsPolicy();
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.Methods.Add("GET");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Empty(result.AllowedMethods);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_PreflightRequest_MethodAllowed_ReturnsAllowMethods()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
+ var policy = new CorsPolicy();
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.Methods.Add("PUT");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Contains("PUT", result.AllowedMethods);
+ }
+
+ public static TheoryData PreflightRequests_LoggingData
+ {
+ get
+ {
+ return new TheoryData
+ {
+ {
+ new LogData {
+ Origin = "http://example.com",
+ Method = "PUT",
+ Headers = null,
+ OriginLogMessage = "The request has an origin header: 'http://example.com'.",
+ PolicyLogMessage = "Policy execution failed.",
+ FailureReason = "Request origin http://example.com does not have permission to access the resource."
+ }
+ },
+ {
+ new LogData {
+ Origin = "http://allowed.example.com",
+ Method = "DELETE",
+ Headers = null,
+ OriginLogMessage = "The request has an origin header: 'http://allowed.example.com'.",
+ PolicyLogMessage = "Policy execution failed.",
+ FailureReason = "Request method DELETE not allowed in CORS policy."
+ }
+ },
+ {
+ new LogData {
+ Origin = "http://allowed.example.com",
+ Method = "PUT",
+ Headers = new[] { "test" },
+ OriginLogMessage = "The request has an origin header: 'http://allowed.example.com'.",
+ PolicyLogMessage = "Policy execution failed.",
+ FailureReason = "Request header 'test' not allowed in CORS policy."
+ }
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(PreflightRequests_LoggingData))]
+ public void EvaluatePolicy_LoggingForPreflightRequests_HasOriginHeader_PolicyFailed(LogData logData)
+ {
+ var sink = new TestSink();
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+
+ var corsService = new CorsService(new TestCorsOptions(), loggerFactory);
+ var requestContext = GetHttpContext(method: "OPTIONS", origin: logData.Origin, accessControlRequestMethod: logData.Method, accessControlRequestHeaders: logData.Headers);
+ var policy = new CorsPolicy();
+ policy.Origins.Add("http://allowed.example.com");
+ policy.Methods.Add("PUT");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ var writeList = sink.Writes.ToList();
+ Assert.Equal("The request is a preflight request.", writeList[0].State.ToString());
+ Assert.Equal(logData.OriginLogMessage, writeList[1].State.ToString());
+ Assert.Equal(logData.PolicyLogMessage, writeList[2].State.ToString());
+ Assert.Equal(logData.FailureReason, writeList[3].State.ToString());
+ }
+
+ [Fact]
+ public void EvaluatePolicy_LoggingForPreflightRequests_HasOriginHeader_PolicySucceeded()
+ {
+ var sink = new TestSink();
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+
+ var corsService = new CorsService(new TestCorsOptions(), loggerFactory);
+ var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://allowed.example.com", accessControlRequestMethod: "PUT");
+ var policy = new CorsPolicy();
+ policy.Origins.Add("http://allowed.example.com");
+ policy.Methods.Add("PUT");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ var writeList = sink.Writes.ToList();
+ Assert.Equal("The request is a preflight request.", writeList[0].State.ToString());
+ Assert.Equal("The request has an origin header: 'http://allowed.example.com'.", writeList[1].State.ToString());
+ Assert.Equal("Policy execution successful.", writeList[2].State.ToString());
+ }
+
+ [Fact]
+ public void EvaluatePolicy_LoggingForPreflightRequests_DoesNotHaveOriginHeader()
+ {
+ var sink = new TestSink();
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+
+ var corsService = new CorsService(new TestCorsOptions(), loggerFactory);
+ var requestContext = GetHttpContext(method: "OPTIONS", origin: null, accessControlRequestMethod: "PUT");
+ var policy = new CorsPolicy();
+ policy.Origins.Add("http://allowed.example.com");
+ policy.Methods.Add("PUT");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ var writeList = sink.Writes.ToList();
+ Assert.Equal("The request is a preflight request.", writeList[0].State.ToString());
+ Assert.Equal("The request does not have an origin header.", writeList[1].State.ToString());
+ }
+
+ [Fact]
+ public void EvaluatePolicy_LoggingForNonPreflightRequests_HasOriginHeader_PolicyFailed()
+ {
+ var sink = new TestSink();
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+
+ var corsService = new CorsService(new TestCorsOptions(), loggerFactory);
+ var requestContext = GetHttpContext(origin: "http://example.com");
+ var policy = new CorsPolicy();
+ policy.Origins.Add("http://allowed.example.com");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ var writeList = sink.Writes.ToList();
+ Assert.Equal("The request has an origin header: 'http://example.com'.", writeList[0].State.ToString());
+ Assert.Equal("Policy execution failed.", writeList[1].State.ToString());
+ Assert.Equal("Request origin http://example.com does not have permission to access the resource.", writeList[2].State.ToString());
+ }
+
+ [Fact]
+ public void EvaluatePolicy_LoggingForNonPreflightRequests_HasOriginHeader_PolicySucceeded()
+ {
+ var sink = new TestSink();
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+
+ var corsService = new CorsService(new TestCorsOptions(), loggerFactory);
+ var requestContext = GetHttpContext(origin: "http://allowed.example.com");
+ var policy = new CorsPolicy();
+ policy.Origins.Add("http://allowed.example.com");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ var writeList = sink.Writes.ToList();
+ Assert.Equal("The request has an origin header: 'http://allowed.example.com'.", writeList[0].State.ToString());
+ Assert.Equal("Policy execution successful.", writeList[1].State.ToString());
+ }
+
+ [Fact]
+ public void EvaluatePolicy_LoggingForNonPreflightRequests_DoesNotHaveOriginHeader()
+ {
+ var sink = new TestSink();
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+
+ var corsService = new CorsService(new TestCorsOptions(), loggerFactory);
+ var requestContext = GetHttpContext(origin: null);
+ var policy = new CorsPolicy();
+ policy.Origins.Add("http://allowed.example.com");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ var logMessage = Assert.Single(sink.Writes);
+ Assert.Equal("The request does not have an origin header.", logMessage.State.ToString());
+ }
+
+ [Theory]
+ [InlineData("OpTions")]
+ [InlineData("OPTIONS")]
+ public void EvaluatePolicy_CaseInsensitivePreflightRequest_OriginAllowed_ReturnsOrigin(string preflightMethod)
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ 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");
+ policy.Methods.Add("*");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Equal("http://example.com", result.AllowedOrigin);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_PreflightRequest_IsOriginAllowedReturnsTrue_ReturnsOrigin()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(
+ method: "OPTIONS",
+ origin: "http://example.com",
+ accessControlRequestMethod: "PUT");
+ var policy = new CorsPolicy
+ {
+ IsOriginAllowed = origin => true
+ };
+ policy.Methods.Add("*");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Equal("http://example.com", result.AllowedOrigin);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_PreflightRequest_SupportsCredentials_AllowCredentialsReturnsTrue()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
+ var policy = new CorsPolicy
+ {
+ SupportsCredentials = true
+ };
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.Methods.Add("*");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.True(result.SupportsCredentials);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_PreflightRequest_NoPreflightMaxAge_NoPreflightMaxAgeSet()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
+ var policy = new CorsPolicy
+ {
+ PreflightMaxAge = null
+ };
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.Methods.Add("*");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Null(result.PreflightMaxAge);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_PreflightRequest_PreflightMaxAge_PreflightMaxAgeSet()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
+ var policy = new CorsPolicy
+ {
+ PreflightMaxAge = TimeSpan.FromSeconds(10)
+ };
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.Methods.Add("*");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Equal(TimeSpan.FromSeconds(10), result.PreflightMaxAge);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_PreflightRequest_AnyMethod_ReturnsRequestMethod()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "GET");
+ var policy = new CorsPolicy();
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.Methods.Add("*");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Equal(1, result.AllowedMethods.Count);
+ Assert.Contains("GET", result.AllowedMethods);
+ }
+
+ [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: method);
+ var policy = new CorsPolicy();
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.Methods.Add("PUT");
+ policy.Methods.Add("DELETE");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Equal(1, result.AllowedMethods.Count);
+ Assert.Contains(method, result.AllowedMethods);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_PreflightRequest_NoHeadersRequested_AllowedAllHeaders_ReturnsEmptyHeaders()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
+ var policy = new CorsPolicy();
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.Methods.Add("*");
+ policy.Headers.Add("*");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Empty(result.AllowedHeaders);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_PreflightRequest_HeadersRequested_AllowAllHeaders_ReturnsRequestedHeaders()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(
+ method: "OPTIONS",
+ origin: "http://example.com",
+ accessControlRequestMethod: "PUT",
+ accessControlRequestHeaders: new[] { "foo", "bar" });
+ var policy = new CorsPolicy();
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.Methods.Add("*");
+ policy.Headers.Add("*");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Equal(2, result.AllowedHeaders.Count);
+ Assert.Contains("foo", result.AllowedHeaders);
+ Assert.Contains("bar", result.AllowedHeaders);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_PreflightRequest_HeadersRequested_AllowSomeHeaders_ReturnsSubsetOfListedHeaders()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(
+ method: "OPTIONS",
+ origin: "http://example.com",
+ accessControlRequestMethod: "PUT",
+ accessControlRequestHeaders: new[] { "content-type", "accept" });
+ var policy = new CorsPolicy();
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.Methods.Add("*");
+ policy.Headers.Add("foo");
+ policy.Headers.Add("bar");
+ policy.Headers.Add("Content-Type");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Equal(2, result.AllowedHeaders.Count);
+ Assert.Contains("Content-Type", result.AllowedHeaders, StringComparer.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_PreflightRequest_HeadersRequested_NotAllHeaderMatches_ReturnsInvalidResult()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(
+ method: "OPTIONS",
+ origin: "http://example.com",
+ accessControlRequestMethod: "PUT",
+ accessControlRequestHeaders: new[] { "match", "noMatch" });
+ var policy = new CorsPolicy();
+ policy.Origins.Add(CorsConstants.AnyOrigin);
+ policy.Methods.Add("*");
+ policy.Headers.Add("match");
+ policy.Headers.Add("foo");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Empty(result.AllowedHeaders);
+ Assert.Empty(result.AllowedMethods);
+ Assert.Empty(result.AllowedExposedHeaders);
+ Assert.Null(result.AllowedOrigin);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_DoesCaseSensitiveComparison()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+
+ var policy = new CorsPolicy();
+ policy.Methods.Add("POST");
+ var httpContext = GetHttpContext(origin: null, accessControlRequestMethod: "post");
+
+ // Act
+ var result = corsService.EvaluatePolicy(httpContext, policy);
+
+ // Assert
+ Assert.Empty(result.AllowedHeaders);
+ Assert.Empty(result.AllowedMethods);
+ Assert.Empty(result.AllowedExposedHeaders);
+ Assert.Null(result.AllowedOrigin);
+ }
+
+ [Fact]
+ public void TryValidateOrigin_DoesCaseSensitiveComparison()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+
+ var policy = new CorsPolicy();
+ policy.Origins.Add("http://Example.com");
+ var httpContext = GetHttpContext(origin: "http://example.com");
+
+ // Act
+ var result = corsService.EvaluatePolicy(httpContext, policy);
+
+ // Assert
+ Assert.Empty(result.AllowedHeaders);
+ Assert.Empty(result.AllowedMethods);
+ Assert.Empty(result.AllowedExposedHeaders);
+ Assert.Null(result.AllowedOrigin);
+ }
+
+
+ [Fact]
+ public void ApplyResult_ReturnsNoHeaders_ByDefault()
+ {
+ // Arrange
+ var result = new CorsResult();
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.Empty(httpContext.Response.Headers);
+ }
+
+ [Fact]
+ public void ApplyResult_AllowOrigin_AllowOriginHeaderAdded()
+ {
+ // Arrange
+ var result = new CorsResult
+ {
+ AllowedOrigin = "http://example.com"
+ };
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.Equal("http://example.com", httpContext.Response.Headers["Access-Control-Allow-Origin"]);
+ }
+
+ [Fact]
+ public void ApplyResult_NoAllowOrigin_AllowOriginHeaderNotAdded()
+ {
+ // Arrange
+ var result = new CorsResult
+ {
+ AllowedOrigin = null
+ };
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.DoesNotContain("Access-Control-Allow-Origin", httpContext.Response.Headers.Keys);
+ }
+
+ [Fact]
+ public void ApplyResult_AllowCredentials_AllowCredentialsHeaderAdded()
+ {
+ // Arrange
+ var result = new CorsResult
+ {
+ SupportsCredentials = true
+ };
+
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ var httpContext = new DefaultHttpContext();
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.Equal("true", httpContext.Response.Headers["Access-Control-Allow-Credentials"]);
+ }
+
+ [Fact]
+ public void ApplyResult_AddVaryHeader_VaryHeaderAdded()
+ {
+ // Arrange
+ var result = new CorsResult
+ {
+ VaryByOrigin = true
+ };
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.Equal("Origin", httpContext.Response.Headers["Vary"]);
+ }
+
+ [Fact]
+ public void ApplyResult_NoAllowCredentials_AllowCredentialsHeaderNotAdded()
+ {
+ // Arrange
+ var result = new CorsResult
+ {
+ SupportsCredentials = false
+ };
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.DoesNotContain("Access-Control-Allow-Credentials", httpContext.Response.Headers.Keys);
+ }
+
+ [Fact]
+ public void ApplyResult_NoAllowMethods_AllowMethodsHeaderNotAdded()
+ {
+ // Arrange
+ var result = new CorsResult
+ {
+ // AllowMethods is empty by default
+ };
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.DoesNotContain("Access-Control-Allow-Methods", httpContext.Response.Headers.Keys);
+ }
+
+ [Fact]
+ public void ApplyResult_OneAllowMethods_AllowMethodsHeaderAdded()
+ {
+ // Arrange
+ var result = new CorsResult();
+ result.AllowedMethods.Add("PUT");
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.Equal("PUT", httpContext.Response.Headers["Access-Control-Allow-Methods"]);
+ }
+
+ [Fact]
+ public void ApplyResult_SomeSimpleAllowMethods_AllowMethodsHeaderAddedForNonSimpleMethods()
+ {
+ // Arrange
+ var result = new CorsResult();
+ result.AllowedMethods.Add("PUT");
+ result.AllowedMethods.Add("get");
+ result.AllowedMethods.Add("DELETE");
+ result.AllowedMethods.Add("POST");
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.Contains("Access-Control-Allow-Methods", httpContext.Response.Headers.Keys);
+ var value = Assert.Single(httpContext.Response.Headers.Values);
+ Assert.Equal(new[] { "PUT,DELETE" }, value);
+ string[] methods = httpContext.Response.Headers.GetCommaSeparatedValues("Access-Control-Allow-Methods");
+ Assert.Equal(2, methods.Length);
+ Assert.Contains("PUT", methods);
+ Assert.Contains("DELETE", methods);
+ }
+
+ [Fact]
+ public void ApplyResult_SimpleAllowMethods_AllowMethodsHeaderNotAdded()
+ {
+ // Arrange
+ var result = new CorsResult();
+ result.AllowedMethods.Add("GET");
+ result.AllowedMethods.Add("HEAD");
+ result.AllowedMethods.Add("POST");
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.DoesNotContain("Access-Control-Allow-Methods", httpContext.Response.Headers.Keys);
+ }
+
+ [Fact]
+ public void ApplyResult_NoAllowHeaders_AllowHeadersHeaderNotAdded()
+ {
+ // Arrange
+ var result = new CorsResult
+ {
+ // AllowHeaders is empty by default
+ };
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.DoesNotContain("Access-Control-Allow-Headers", httpContext.Response.Headers.Keys);
+ }
+
+ [Fact]
+ public void ApplyResult_OneAllowHeaders_AllowHeadersHeaderAdded()
+ {
+ // Arrange
+ var result = new CorsResult();
+ result.AllowedHeaders.Add("foo");
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.Equal("foo", httpContext.Response.Headers["Access-Control-Allow-Headers"]);
+ }
+
+ [Fact]
+ public void ApplyResult_ManyAllowHeaders_AllowHeadersHeaderAdded()
+ {
+ // Arrange
+ var result = new CorsResult();
+ result.AllowedHeaders.Add("foo");
+ result.AllowedHeaders.Add("bar");
+ result.AllowedHeaders.Add("baz");
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.Contains("Access-Control-Allow-Headers", httpContext.Response.Headers.Keys);
+ var value = Assert.Single(httpContext.Response.Headers.Values);
+ Assert.Equal(new[] { "foo,bar,baz" }, value);
+ string[] headerValues = httpContext.Response.Headers.GetCommaSeparatedValues("Access-Control-Allow-Headers");
+ Assert.Equal(3, headerValues.Length);
+ Assert.Contains("foo", headerValues);
+ Assert.Contains("bar", headerValues);
+ Assert.Contains("baz", headerValues);
+ }
+
+ [Fact]
+ public void ApplyResult_SomeSimpleAllowHeaders_AllowHeadersHeaderAddedForNonSimpleHeaders()
+ {
+ // Arrange
+ var result = new CorsResult();
+ result.AllowedHeaders.Add("Content-Language");
+ result.AllowedHeaders.Add("foo");
+ result.AllowedHeaders.Add("bar");
+ result.AllowedHeaders.Add("Accept");
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.Contains("Access-Control-Allow-Headers", httpContext.Response.Headers.Keys);
+ string[] headerValues = httpContext.Response.Headers.GetCommaSeparatedValues("Access-Control-Allow-Headers");
+ Assert.Equal(2, headerValues.Length);
+ Assert.Contains("foo", headerValues);
+ Assert.Contains("bar", headerValues);
+ }
+
+ [Fact]
+ public void ApplyResult_SimpleAllowHeaders_AllowHeadersHeaderNotAdded()
+ {
+ // Arrange
+ var result = new CorsResult();
+ result.AllowedHeaders.Add("Accept");
+ result.AllowedHeaders.Add("Accept-Language");
+ result.AllowedHeaders.Add("Content-Language");
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.DoesNotContain("Access-Control-Allow-Headers", httpContext.Response.Headers.Keys);
+ }
+
+ [Fact]
+ public void ApplyResult_NoAllowExposedHeaders_ExposedHeadersHeaderNotAdded()
+ {
+ // Arrange
+ var result = new CorsResult
+ {
+ // AllowExposedHeaders is empty by default
+ };
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.DoesNotContain("Access-Control-Expose-Headers", httpContext.Response.Headers.Keys);
+ }
+
+ [Fact]
+ public void ApplyResult_OneAllowExposedHeaders_ExposedHeadersHeaderAdded()
+ {
+ // Arrange
+ var result = new CorsResult();
+ result.AllowedExposedHeaders.Add("foo");
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.Equal("foo", httpContext.Response.Headers["Access-Control-Expose-Headers"]);
+ }
+
+ [Fact]
+ public void ApplyResult_ManyAllowExposedHeaders_ExposedHeadersHeaderAdded()
+ {
+ // Arrange
+ var result = new CorsResult();
+ result.AllowedExposedHeaders.Add("foo");
+ result.AllowedExposedHeaders.Add("bar");
+ result.AllowedExposedHeaders.Add("baz");
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.Contains("Access-Control-Expose-Headers", httpContext.Response.Headers.Keys);
+ var value = Assert.Single(httpContext.Response.Headers.Values);
+ Assert.Equal(new[] { "foo,bar,baz" }, value);
+ string[] exposedHeaderValues = httpContext.Response.Headers.GetCommaSeparatedValues("Access-Control-Expose-Headers");
+ Assert.Equal(3, exposedHeaderValues.Length);
+ Assert.Contains("foo", exposedHeaderValues);
+ Assert.Contains("bar", exposedHeaderValues);
+ Assert.Contains("baz", exposedHeaderValues);
+ }
+
+ [Fact]
+ public void ApplyResult_NoPreflightMaxAge_MaxAgeHeaderNotAdded()
+ {
+ // Arrange
+ var result = new CorsResult
+ {
+ PreflightMaxAge = null
+ };
+
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.DoesNotContain("Access-Control-Max-Age", httpContext.Response.Headers.Keys);
+ }
+
+ [Fact]
+ public void ApplyResult_PreflightMaxAge_MaxAgeHeaderAdded()
+ {
+ // Arrange
+ var result = new CorsResult
+ {
+ PreflightMaxAge = TimeSpan.FromSeconds(30)
+ };
+ var httpContext = new DefaultHttpContext();
+ var service = new CorsService(new TestCorsOptions());
+
+ // Act
+ service.ApplyResult(result, httpContext.Response);
+
+ // Assert
+ Assert.Equal("30", httpContext.Response.Headers["Access-Control-Max-Age"]);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_MultiOriginsPolicy_ReturnsVaryByOriginHeader()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(origin: "http://example.com");
+ var policy = new CorsPolicy();
+ policy.Origins.Add("http://example.com");
+ policy.Origins.Add("http://example-two.com");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.NotNull(result.AllowedOrigin);
+ Assert.True(result.VaryByOrigin);
+ }
+
+ [Fact]
+ public void EvaluatePolicy_MultiOriginsPolicy_NoMatchingOrigin_ReturnsInvalidResult()
+ {
+ // Arrange
+ var corsService = new CorsService(new TestCorsOptions());
+ var requestContext = GetHttpContext(origin: "http://example.com");
+ var policy = new CorsPolicy();
+ policy.Origins.Add("http://example-two.com");
+ policy.Origins.Add("http://example-three.com");
+
+ // Act
+ var result = corsService.EvaluatePolicy(requestContext, policy);
+
+ // Assert
+ Assert.Null(result.AllowedOrigin);
+ Assert.False(result.VaryByOrigin);
+ }
+
+
+ private static HttpContext GetHttpContext(
+ string method = null,
+ string origin = null,
+ string accessControlRequestMethod = null,
+ string[] accessControlRequestHeaders = null)
+ {
+ var context = new DefaultHttpContext();
+
+ if (method != null)
+ {
+ context.Request.Method = method;
+ }
+
+ if (origin != null)
+ {
+ context.Request.Headers.Add(CorsConstants.Origin, new[] { origin });
+ }
+
+ if (accessControlRequestMethod != null)
+ {
+ context.Request.Headers.Add(CorsConstants.AccessControlRequestMethod, new[] { accessControlRequestMethod });
+ }
+
+ if (accessControlRequestHeaders != null)
+ {
+ context.Request.Headers.Add(CorsConstants.AccessControlRequestHeaders, accessControlRequestHeaders);
+ }
+
+ return context;
+ }
+
+ public class LogData
+ {
+ public string Origin { get; set; }
+ public string Method { get; set; }
+ public string[] Headers { get; set; }
+ public string OriginLogMessage { get; set; }
+ public string PolicyLogMessage { get; set; }
+ public string FailureReason { get; set; }
+ }
+ }
+}
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsTestFixtureOfT.cs b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsTestFixtureOfT.cs
new file mode 100644
index 0000000000..aa698c62f2
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/CorsTestFixtureOfT.cs
@@ -0,0 +1,33 @@
+// 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.Net.Http;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ public class CorsTestFixture : IDisposable
+ where TStartup : class
+ {
+ private readonly TestServer _server;
+
+ public CorsTestFixture()
+ {
+ var builder = new WebHostBuilder().UseStartup();
+ _server = new TestServer(builder);
+
+ Client = _server.CreateClient();
+ Client.BaseAddress = new Uri("http://localhost");
+ }
+
+ public HttpClient Client { get; }
+
+ public void Dispose()
+ {
+ Client.Dispose();
+ _server.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/DefaultCorsPolicyProviderTests.cs b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/DefaultCorsPolicyProviderTests.cs
new file mode 100644
index 0000000000..d66b0e5653
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/DefaultCorsPolicyProviderTests.cs
@@ -0,0 +1,56 @@
+// 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.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ public class DefaultPolicyProviderTests
+ {
+ [Fact]
+ public async Task UsesTheDefaultPolicyName()
+ {
+ // Arrange
+ var options = new CorsOptions();
+ var policy = new CorsPolicy();
+ options.AddPolicy(options.DefaultPolicyName, policy);
+
+ var corsOptions = new TestCorsOptions
+ {
+ Value = options
+ };
+ var policyProvider = new DefaultCorsPolicyProvider(corsOptions);
+
+ // Act
+ var actualPolicy = await policyProvider.GetPolicyAsync(new DefaultHttpContext(), policyName: null);
+
+ // Assert
+ Assert.Same(policy, actualPolicy);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("policyName")]
+ public async Task GetsNamedPolicy(string policyName)
+ {
+ // Arrange
+ var options = new CorsOptions();
+ var policy = new CorsPolicy();
+ options.AddPolicy(policyName, policy);
+
+ var corsOptions = new TestCorsOptions
+ {
+ Value = options
+ };
+ var policyProvider = new DefaultCorsPolicyProvider(corsOptions);
+
+ // Act
+ var actualPolicy = await policyProvider.GetPolicyAsync(new DefaultHttpContext(), policyName);
+
+ // Assert
+ Assert.Same(policy, actualPolicy);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/Microsoft.AspNetCore.Cors.Test.csproj b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/Microsoft.AspNetCore.Cors.Test.csproj
new file mode 100644
index 0000000000..ff45928446
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/Microsoft.AspNetCore.Cors.Test.csproj
@@ -0,0 +1,22 @@
+
+
+
+ $(StandardTestTfms)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/TestCorsOptions.cs b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/TestCorsOptions.cs
new file mode 100644
index 0000000000..782ebb2db7
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/TestCorsOptions.cs
@@ -0,0 +1,12 @@
+// 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.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ public class TestCorsOptions : IOptions
+ {
+ public CorsOptions Value { get; set; }
+ }
+}
diff --git a/src/CORS/test/Microsoft.AspNetCore.Cors.Test/UriHelpersTests.cs b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/UriHelpersTests.cs
new file mode 100644
index 0000000000..ac04cfc3fd
--- /dev/null
+++ b/src/CORS/test/Microsoft.AspNetCore.Cors.Test/UriHelpersTests.cs
@@ -0,0 +1,66 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Cors.Infrastructure
+{
+ public sealed class UriHelpersTests
+ {
+ [Theory]
+ [MemberData(nameof(IsSubdomainOfTestData))]
+ public void TestIsSubdomainOf(Uri subdomain, Uri domain)
+ {
+ // Act
+ bool isSubdomain = UriHelpers.IsSubdomainOf(subdomain, domain);
+
+ // Assert
+ Assert.True(isSubdomain);
+ }
+
+ [Theory]
+ [MemberData(nameof(IsNotSubdomainOfTestData))]
+ public void TestIsSubdomainOf_ReturnsFalse_WhenNotSubdomain(Uri subdomain, Uri domain)
+ {
+ // Act
+ bool isSubdomain = UriHelpers.IsSubdomainOf(subdomain, domain);
+
+ // Assert
+ Assert.False(isSubdomain);
+ }
+
+ public static IEnumerable