| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177 |
- commit 2b80c905549110c312644400b7eea32119009710
- Author: Chris Ross (ASP.NET) <[email protected]>
- Date: Mon Feb 5 15:38:55 2018 -0800
- Add host filtering middleware
- diff --git a/BasicMiddleware.sln b/BasicMiddleware.sln
- index a4452cd103a..8810201f5ae 100644
- --- a/BasicMiddleware.sln
- +++ b/BasicMiddleware.sln
- @@ -1,6 +1,6 @@
- Microsoft Visual Studio Solution File, Format Version 12.00
- # Visual Studio 15
- -VisualStudioVersion = 15.0.26817.0
- +VisualStudioVersion = 15.0.27130.2027
- MinimumVisualStudioVersion = 15.0.26730.03
- Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpOverrides", "src\Microsoft.AspNetCore.HttpOverrides\Microsoft.AspNetCore.HttpOverrides.csproj", "{517308C3-B477-4B01-B461-CAB9C10B6928}"
- EndProject
- @@ -63,6 +63,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpsPolicySample", "sample
- EndProject
- Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpsPolicy.Tests", "test\Microsoft.AspNetCore.HttpsPolicy.Tests\Microsoft.AspNetCore.HttpsPolicy.Tests.csproj", "{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}"
- EndProject
- +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostFilteringSample", "samples\HostFilteringSample\HostFilteringSample.csproj", "{368B00A2-992A-4B0E-9085-A8136A22922D}"
- +EndProject
- +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{5CEA6F31-A829-4A02-8CD5-EC3DDD4CC1EA}"
- + ProjectSection(SolutionItems) = preProject
- + build\dependencies.props = build\dependencies.props
- + build\repo.props = build\repo.props
- + build\sources.props = build\sources.props
- + EndProjectSection
- +EndProject
- +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HostFiltering.Tests", "test\Microsoft.AspNetCore.HostFiltering.Tests\Microsoft.AspNetCore.HostFiltering.Tests.csproj", "{4BC947ED-13B8-4BE6-82A4-96A48D86980B}"
- +EndProject
- +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HostFiltering", "src\Microsoft.AspNetCore.HostFiltering\Microsoft.AspNetCore.HostFiltering.csproj", "{762F7276-C916-4111-A6C0-41668ABB3823}"
- +EndProject
- Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- @@ -129,6 +142,18 @@ Global
- {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.Build.0 = Release|Any CPU
- + {368B00A2-992A-4B0E-9085-A8136A22922D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- + {368B00A2-992A-4B0E-9085-A8136A22922D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- + {368B00A2-992A-4B0E-9085-A8136A22922D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- + {368B00A2-992A-4B0E-9085-A8136A22922D}.Release|Any CPU.Build.0 = Release|Any CPU
- + {4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- + {4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- + {4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- + {4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Release|Any CPU.Build.0 = Release|Any CPU
- + {762F7276-C916-4111-A6C0-41668ABB3823}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- + {762F7276-C916-4111-A6C0-41668ABB3823}.Debug|Any CPU.Build.0 = Debug|Any CPU
- + {762F7276-C916-4111-A6C0-41668ABB3823}.Release|Any CPU.ActiveCfg = Release|Any CPU
- + {762F7276-C916-4111-A6C0-41668ABB3823}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- @@ -149,6 +174,10 @@ Global
- {4D39C29B-4EC8-497C-B411-922DA494D71B} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
- {AC424AEE-4883-49C6-945F-2FC916B8CA1C} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
- {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE} = {8437B0F3-3894-4828-A945-A9187F37631D}
- + {368B00A2-992A-4B0E-9085-A8136A22922D} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
- + {5CEA6F31-A829-4A02-8CD5-EC3DDD4CC1EA} = {59A9B64C-E9BE-409E-89A2-58D72E2918F5}
- + {4BC947ED-13B8-4BE6-82A4-96A48D86980B} = {8437B0F3-3894-4828-A945-A9187F37631D}
- + {762F7276-C916-4111-A6C0-41668ABB3823} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {4518E9CE-3680-4E05-9259-B64EA7807158}
- diff --git a/build/dependencies.props b/build/dependencies.props
- index 97d18628138..4dadaa5ffab 100644
- --- a/build/dependencies.props
- +++ b/build/dependencies.props
- @@ -14,6 +14,7 @@
- <MicrosoftAspNetCoreTestHostPackageVersion>2.1.0-preview2-30281</MicrosoftAspNetCoreTestHostPackageVersion>
- <MicrosoftExtensionsConfigurationAbstractionsPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
- <MicrosoftExtensionsConfigurationBinderPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsConfigurationBinderPackageVersion>
- + <MicrosoftExtensionsConfigurationJsonPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsConfigurationJsonPackageVersion>
- <MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
- <MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
- <MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsLoggingConsolePackageVersion>
- diff --git a/samples/HostFilteringSample/HostFilteringSample.csproj b/samples/HostFilteringSample/HostFilteringSample.csproj
- new file mode 100644
- index 00000000000..7818be1fb04
- --- /dev/null
- +++ b/samples/HostFilteringSample/HostFilteringSample.csproj
- @@ -0,0 +1,29 @@
- +<Project Sdk="Microsoft.NET.Sdk.Web">
- +
- + <PropertyGroup>
- + <TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
- + </PropertyGroup>
- +
- + <ItemGroup>
- + <ProjectReference Include="..\..\src\Microsoft.AspNetCore.HostFiltering\Microsoft.AspNetCore.HostFiltering.csproj" />
- + </ItemGroup>
- +
- + <ItemGroup>
- + <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
- + <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsConfigurationJsonPackageVersion)" />
- + <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
- + </ItemGroup>
- +
- + <ItemGroup>
- + <Content Update="appsettings.Development.json">
- + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- + </Content>
- + <Content Update="appsettings.json">
- + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- + </Content>
- + <Content Update="appsettings.Production.json">
- + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- + </Content>
- + </ItemGroup>
- +
- +</Project>
- diff --git a/samples/HostFilteringSample/Program.cs b/samples/HostFilteringSample/Program.cs
- new file mode 100644
- index 00000000000..0d4ffa9324c
- --- /dev/null
- +++ b/samples/HostFilteringSample/Program.cs
- @@ -0,0 +1,37 @@
- +// 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.Hosting;
- +using Microsoft.Extensions.Configuration;
- +using Microsoft.Extensions.Logging;
- +
- +namespace HostFilteringSample
- +{
- + public class Program
- + {
- + public static void Main(string[] args)
- + {
- + BuildWebHost(args).Run();
- + }
- +
- + public static IWebHost BuildWebHost(string[] args)
- + {
- + var hostBuilder = new WebHostBuilder()
- + .ConfigureLogging((_, factory) =>
- + {
- + factory.SetMinimumLevel(LogLevel.Debug);
- + factory.AddConsole();
- + })
- + .ConfigureAppConfiguration((hostingContext, config) =>
- + {
- + var env = hostingContext.HostingEnvironment;
- + config.AddJsonFile("appsettings.json", optional: true)
- + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
- + })
- + .UseKestrel()
- + .UseStartup<Startup>();
- +
- + return hostBuilder.Build();
- + }
- + }
- +}
- diff --git a/samples/HostFilteringSample/Properties/launchSettings.json b/samples/HostFilteringSample/Properties/launchSettings.json
- new file mode 100644
- index 00000000000..a0ee1d227c4
- --- /dev/null
- +++ b/samples/HostFilteringSample/Properties/launchSettings.json
- @@ -0,0 +1,27 @@
- +{
- + "iisSettings": {
- + "windowsAuthentication": false,
- + "anonymousAuthentication": true,
- + "iisExpress": {
- + "applicationUrl": "http://localhost:14124/",
- + "sslPort": 0
- + }
- + },
- + "profiles": {
- + "IIS Express": {
- + "commandName": "IISExpress",
- + "launchBrowser": true,
- + "environmentVariables": {
- + "ASPNETCORE_ENVIRONMENT": "Development"
- + }
- + },
- + "HostFilteringSample": {
- + "commandName": "Project",
- + "launchBrowser": true,
- + "environmentVariables": {
- + "ASPNETCORE_ENVIRONMENT": "Development"
- + },
- + "applicationUrl": "http://localhost:14125/"
- + }
- + }
- +}
- \ No newline at end of file
- diff --git a/samples/HostFilteringSample/Startup.cs b/samples/HostFilteringSample/Startup.cs
- new file mode 100644
- index 00000000000..93c217b71c4
- --- /dev/null
- +++ b/samples/HostFilteringSample/Startup.cs
- @@ -0,0 +1,41 @@
- +// Copyright (c) .NET Foundation. All rights reserved.
- +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
- +
- +using System.Collections.Generic;
- +using Microsoft.AspNetCore.Builder;
- +using Microsoft.AspNetCore.Hosting;
- +using Microsoft.AspNetCore.Http;
- +using Microsoft.Extensions.Configuration;
- +using Microsoft.Extensions.DependencyInjection;
- +
- +namespace HostFilteringSample
- +{
- + public class Startup
- + {
- + public IConfiguration Config { get; }
- +
- + public Startup(IConfiguration config)
- + {
- + Config = config;
- + }
- +
- + public void ConfigureServices(IServiceCollection services)
- + {
- + services.AddHostFiltering(options =>
- + {
- + // If this is excluded then it will fall back to the server's addresses
- + options.AllowedHosts = Config.GetSection("AllowedHosts").Get<List<string>>();
- + });
- + }
- +
- + public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- + {
- + app.UseHostFiltering();
- +
- + app.Run(context =>
- + {
- + return context.Response.WriteAsync("Hello World! " + context.Request.Host);
- + });
- + }
- + }
- +}
- diff --git a/samples/HostFilteringSample/appsettings.Development.json b/samples/HostFilteringSample/appsettings.Development.json
- new file mode 100644
- index 00000000000..7c2d8e26dc2
- --- /dev/null
- +++ b/samples/HostFilteringSample/appsettings.Development.json
- @@ -0,0 +1,3 @@
- +{
- + "AllowedHosts": [ "localhost", "127.0.0.1", "[::1]" ]
- +}
- diff --git a/samples/HostFilteringSample/appsettings.Production.json b/samples/HostFilteringSample/appsettings.Production.json
- new file mode 100644
- index 00000000000..f2fc90c3900
- --- /dev/null
- +++ b/samples/HostFilteringSample/appsettings.Production.json
- @@ -0,0 +1,3 @@
- +{
- + "AllowedHosts": [ "example.com", "localhost" ]
- +}
- diff --git a/samples/HostFilteringSample/appsettings.json b/samples/HostFilteringSample/appsettings.json
- new file mode 100644
- index 00000000000..29091fa5825
- --- /dev/null
- +++ b/samples/HostFilteringSample/appsettings.json
- @@ -0,0 +1,3 @@
- +{
- +
- +}
- \ No newline at end of file
- diff --git a/src/Microsoft.AspNetCore.HostFiltering/HostFilteringBuilderExtensions.cs b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringBuilderExtensions.cs
- new file mode 100644
- index 00000000000..b8722de1c7f
- --- /dev/null
- +++ b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringBuilderExtensions.cs
- @@ -0,0 +1,32 @@
- +// 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.HostFiltering;
- +
- +namespace Microsoft.AspNetCore.Builder
- +{
- + /// <summary>
- + /// Extension methods for the HostFiltering middleware.
- + /// </summary>
- + public static class HostFilteringBuilderExtensions
- + {
- + /// <summary>
- + /// Adds middleware for filtering requests by allowed host headers. Invalid requests will be rejected with a
- + /// 400 status code.
- + /// </summary>
- + /// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
- + /// <returns>The original <see cref="IApplicationBuilder"/>.</returns>
- + public static IApplicationBuilder UseHostFiltering(this IApplicationBuilder app)
- + {
- + if (app == null)
- + {
- + throw new ArgumentNullException(nameof(app));
- + }
- +
- + app.UseMiddleware<HostFilteringMiddleware>();
- +
- + return app;
- + }
- + }
- +}
- diff --git a/src/Microsoft.AspNetCore.HostFiltering/HostFilteringMiddleware.cs b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringMiddleware.cs
- new file mode 100644
- index 00000000000..e1e38eca378
- --- /dev/null
- +++ b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringMiddleware.cs
- @@ -0,0 +1,166 @@
- +// 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;
- +using System.Threading.Tasks;
- +using Microsoft.AspNetCore.Http;
- +using Microsoft.Extensions.Logging;
- +using Microsoft.Extensions.Options;
- +using Microsoft.Extensions.Primitives;
- +using Microsoft.Net.Http.Headers;
- +
- +namespace Microsoft.AspNetCore.HostFiltering
- +{
- + /// <summary>
- + /// A middleware used to filter requests by their Host header.
- + /// </summary>
- + public class HostFilteringMiddleware
- + {
- + // Matches Http.Sys.
- + private static readonly byte[] DefaultResponse = Encoding.ASCII.GetBytes(
- + "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\"http://www.w3.org/TR/html4/strict.dtd\">\r\n"
- + + "<HTML><HEAD><TITLE>Bad Request</TITLE>\r\n"
- + + "<META HTTP-EQUIV=\"Content-Type\" Content=\"text/html; charset=us-ascii\"></ HEAD >\r\n"
- + + "<BODY><h2>Bad Request - Invalid Hostname</h2>\r\n"
- + + "<hr><p>HTTP Error 400. The request hostname is invalid.</p>\r\n"
- + + "</BODY></HTML>");
- +
- + private readonly RequestDelegate _next;
- + private readonly ILogger<HostFilteringMiddleware> _logger;
- + private readonly HostFilteringOptions _options;
- + private IList<StringSegment> _allowedHosts;
- + private bool? _allowAnyNonEmptyHost;
- +
- + /// <summary>
- + /// A middleware used to filter requests by their Host header.
- + /// </summary>
- + /// <param name="next"></param>
- + /// <param name="logger"></param>
- + /// <param name="options"></param>
- + public HostFilteringMiddleware(RequestDelegate next, ILogger<HostFilteringMiddleware> logger,
- + IOptions<HostFilteringOptions> options)
- + {
- + _next = next ?? throw new ArgumentNullException(nameof(next));
- + _logger = logger ?? throw new ArgumentNullException(nameof(logger));
- + _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
- + }
- +
- + /// <summary>
- + /// Processes requests
- + /// </summary>
- + /// <param name="context"></param>
- + /// <returns></returns>
- + public Task Invoke(HttpContext context)
- + {
- + EnsureConfigured();
- +
- + if (!CheckHost(context))
- + {
- + context.Response.StatusCode = 400;
- + if (_options.IncludeFailureMessage)
- + {
- + context.Response.ContentLength = DefaultResponse.Length;
- + context.Response.ContentType = "text/html";
- + return context.Response.Body.WriteAsync(DefaultResponse, 0, DefaultResponse.Length);
- + }
- + return Task.CompletedTask;
- + }
- +
- + return _next(context);
- + }
- +
- + private void EnsureConfigured()
- + {
- + if (_allowAnyNonEmptyHost == true || _allowedHosts?.Count > 0)
- + {
- + return;
- + }
- +
- + var allowedHosts = new List<StringSegment>();
- + if (_options.AllowedHosts?.Count > 0 && !TryProcessHosts(_options.AllowedHosts, allowedHosts))
- + {
- + _logger.LogDebug("Wildcard detected, all requests with hosts will be allowed.");
- + _allowAnyNonEmptyHost = true;
- + return;
- + }
- +
- + if (allowedHosts.Count == 0)
- + {
- + throw new InvalidOperationException("No allowed hosts were configured.");
- + }
- +
- + _logger.LogDebug("Allowed hosts: " + string.Join("; ", allowedHosts));
- + _allowedHosts = allowedHosts;
- + }
- +
- + // returns false if any wildcards were found
- + private bool TryProcessHosts(IEnumerable<string> incoming, IList<StringSegment> results)
- + {
- + foreach (var entry in incoming)
- + {
- + // Punycode. Http.Sys requires you to register Unicode hosts, but the headers contain punycode.
- + var host = new HostString(entry).ToUriComponent();
- +
- + if (IsTopLevelWildcard(host))
- + {
- + // Disable filtering
- + return false;
- + }
- +
- + if (!results.Contains(host, StringSegmentComparer.OrdinalIgnoreCase))
- + {
- + results.Add(host);
- + }
- + }
- +
- + return true;
- + }
- +
- + private bool IsTopLevelWildcard(string host)
- + {
- + return (string.Equals("*", host, StringComparison.Ordinal) // HttpSys wildcard
- + || string.Equals("[::]", host, StringComparison.Ordinal) // Kestrel wildcard, IPv6 Any
- + || string.Equals("0.0.0.0", host, StringComparison.Ordinal)); // IPv4 Any
- + }
- +
- + // This does not duplicate format validations that are expected to be performed by the host.
- + private bool CheckHost(HttpContext context)
- + {
- + var host = new StringSegment(context.Request.Headers[HeaderNames.Host].ToString()).Trim();
- +
- + if (StringSegment.IsNullOrEmpty(host))
- + {
- + // Http/1.0 does not require the host header.
- + // Http/1.1 requires the header but the value may be empty.
- + if (!_options.AllowEmptyHosts)
- + {
- + _logger.LogInformation($"{context.Request.Protocol} request rejected due to missing or empty host header.");
- + return false;
- + }
- + if (_logger.IsEnabled(LogLevel.Debug))
- + {
- + _logger.LogDebug($"{context.Request.Protocol} request allowed with missing or empty host header.");
- + }
- + return true;
- + }
- +
- + if (_allowAnyNonEmptyHost == true)
- + {
- + _logger.LogTrace($"All hosts are allowed.");
- + return true;
- + }
- +
- + if (HostString.MatchesAny(host, _allowedHosts))
- + {
- + _logger.LogTrace($"The host '{host}' matches an allowed host.");
- + return true;
- + }
- +
- + _logger.LogInformation($"The host '{host}' does not match an allowed host.");
- + return false;
- + }
- + }
- +}
- diff --git a/src/Microsoft.AspNetCore.HostFiltering/HostFilteringOptions.cs b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringOptions.cs
- new file mode 100644
- index 00000000000..1645591579e
- --- /dev/null
- +++ b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringOptions.cs
- @@ -0,0 +1,44 @@
- +// Copyright (c) .NET Foundation. All rights reserved.
- +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
- +
- +using System.Collections.Generic;
- +
- +namespace Microsoft.AspNetCore.HostFiltering
- +{
- + /// <summary>
- + /// Options for the HostFiltering middleware
- + /// </summary>
- + public class HostFilteringOptions
- + {
- + /// <summary>
- + /// The hosts headers that are allowed to access this site. At least one value is required.
- + /// </summary>
- + /// <remarks>
- + /// <list type="bullet">
- + /// <item><description>Port numbers must be excluded.</description></item>
- + /// <item><description>A top level wildcard "*" allows all non-empty hosts.</description></item>
- + /// <item><description>Subdomain wildcards are permitted. E.g. "*.example.com" matches subdomains like foo.example.com,
- + /// but not the parent domain example.com.</description></item>
- + /// <item><description>Unicode host names are allowed but will be converted to punycode for matching.</description></item>
- + /// <item><description>IPv6 addresses must include their bounding brackets and be in their normalized form.</description></item>
- + /// </list>
- + /// </remarks>
- + public IList<string> AllowedHosts { get; set; } = new List<string>();
- +
- + /// <summary>
- + /// Indicates if requests without hosts are allowed. The default is true.
- + /// </summary>
- + /// <remarks>
- + /// HTTP/1.0 does not require a host header.
- + /// Http/1.1 requires a host header, but says the value may be empty.
- + /// </remarks>
- + public bool AllowEmptyHosts { get; set; } = true;
- +
- + // Note if this were disabled then things like the status code middleware may try to re-execute
- + // the request. This is a low level protocol violation, pretty error pages should not be required.
- + /// <summary>
- + /// Indicates if the 400 response should include a default message or be empty. This is enabled by default.
- + /// </summary>
- + public bool IncludeFailureMessage { get; set; } = true;
- + }
- +}
- diff --git a/src/Microsoft.AspNetCore.HostFiltering/HostFilteringServicesExtensions.cs b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringServicesExtensions.cs
- new file mode 100644
- index 00000000000..81b48444a8e
- --- /dev/null
- +++ b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringServicesExtensions.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 Microsoft.AspNetCore.HostFiltering;
- +using Microsoft.Extensions.DependencyInjection;
- +
- +namespace Microsoft.AspNetCore.Builder
- +{
- + /// <summary>
- + /// Extension methods for the host filtering middleware.
- + /// </summary>
- + public static class HostFilteringServicesExtensions
- + {
- + /// <summary>
- + /// Adds services and options for the host filtering middleware.
- + /// </summary>
- + /// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
- + /// <param name="configureOptions">A delegate to configure the <see cref="HostFilteringOptions"/>.</param>
- + /// <returns></returns>
- + public static IServiceCollection AddHostFiltering(this IServiceCollection services, Action<HostFilteringOptions> configureOptions)
- + {
- + if (services == null)
- + {
- + throw new ArgumentNullException(nameof(services));
- + }
- + if (configureOptions == null)
- + {
- + throw new ArgumentNullException(nameof(configureOptions));
- + }
- +
- + services.Configure(configureOptions);
- + return services;
- + }
- + }
- +}
- diff --git a/src/Microsoft.AspNetCore.HostFiltering/Microsoft.AspNetCore.HostFiltering.csproj b/src/Microsoft.AspNetCore.HostFiltering/Microsoft.AspNetCore.HostFiltering.csproj
- new file mode 100644
- index 00000000000..ec6d6423407
- --- /dev/null
- +++ b/src/Microsoft.AspNetCore.HostFiltering/Microsoft.AspNetCore.HostFiltering.csproj
- @@ -0,0 +1,18 @@
- +<Project Sdk="Microsoft.NET.Sdk">
- +
- + <PropertyGroup>
- + <Description>
- + ASP.NET Core middleware for filtering out requests with unknown HTTP host headers.
- + </Description>
- + <TargetFramework>netstandard2.0</TargetFramework>
- + <GenerateDocumentationFile>true</GenerateDocumentationFile>
- + <PackageTags>aspnetcore</PackageTags>
- + </PropertyGroup>
- +
- + <ItemGroup>
- + <PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpAbstractionsPackageVersion)" />
- + <PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(MicrosoftAspNetCoreHttpExtensionsPackageVersion)" />
- + <PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
- + <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractionsPackageVersion)" />
- + </ItemGroup>
- +</Project>
- diff --git a/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs
- index d5e074de57d..decda81d58e 100644
- --- a/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs
- +++ b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs
- @@ -2,6 +2,7 @@
- // 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.Net;
- using System.Runtime.CompilerServices;
- @@ -11,6 +12,7 @@ using Microsoft.AspNetCore.Http;
- using Microsoft.AspNetCore.HttpOverrides.Internal;
- using Microsoft.Extensions.Logging;
- using Microsoft.Extensions.Options;
- +using Microsoft.Extensions.Primitives;
-
- namespace Microsoft.AspNetCore.HttpOverrides
- {
- @@ -22,6 +24,8 @@ namespace Microsoft.AspNetCore.HttpOverrides
- private readonly ForwardedHeadersOptions _options;
- private readonly RequestDelegate _next;
- private readonly ILogger _logger;
- + private bool _allowAllHosts;
- + private IList<StringSegment> _allowedHosts;
-
- static ForwardedHeadersMiddleware()
- {
- @@ -85,6 +89,8 @@ namespace Microsoft.AspNetCore.HttpOverrides
- _options = options.Value;
- _logger = loggerFactory.CreateLogger<ForwardedHeadersMiddleware>();
- _next = next;
- +
- + PreProcessHosts();
- }
-
- private static void EnsureOptionNotNullorWhitespace(string value, string propertyName)
- @@ -95,6 +101,43 @@ namespace Microsoft.AspNetCore.HttpOverrides
- }
- }
-
- + private void PreProcessHosts()
- + {
- + if (_options.AllowedHosts == null || _options.AllowedHosts.Count == 0)
- + {
- + _allowAllHosts = true;
- + return;
- + }
- +
- + var allowedHosts = new List<StringSegment>();
- + foreach (var entry in _options.AllowedHosts)
- + {
- + // Punycode. Http.Sys requires you to register Unicode hosts, but the headers contain punycode.
- + var host = new HostString(entry).ToUriComponent();
- +
- + if (IsTopLevelWildcard(host))
- + {
- + // Disable filtering
- + _allowAllHosts = true;
- + return;
- + }
- +
- + if (!allowedHosts.Contains(host, StringSegmentComparer.OrdinalIgnoreCase))
- + {
- + allowedHosts.Add(host);
- + }
- + }
- +
- + _allowedHosts = allowedHosts;
- + }
- +
- + private bool IsTopLevelWildcard(string host)
- + {
- + return (string.Equals("*", host, StringComparison.Ordinal) // HttpSys wildcard
- + || string.Equals("[::]", host, StringComparison.Ordinal) // Kestrel wildcard, IPv6 Any
- + || string.Equals("0.0.0.0", host, StringComparison.Ordinal)); // IPv4 Any
- + }
- +
- public Task Invoke(HttpContext context)
- {
- ApplyForwarders(context);
- @@ -231,7 +274,8 @@ namespace Microsoft.AspNetCore.HttpOverrides
-
- if (checkHost)
- {
- - if (!string.IsNullOrEmpty(set.Host) && TryValidateHost(set.Host))
- + if (!string.IsNullOrEmpty(set.Host) && TryValidateHost(set.Host)
- + && (_allowAllHosts || HostString.MatchesAny(set.Host, _allowedHosts)))
- {
- applyChanges = true;
- currentValues.Host = set.Host;
- diff --git a/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs
- index cbe00baaa19..3507d9bea55 100644
- --- a/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs
- +++ b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs
- @@ -67,6 +67,22 @@ namespace Microsoft.AspNetCore.Builder
- /// </summary>
- public IList<IPNetwork> KnownNetworks { get; } = new List<IPNetwork>() { new IPNetwork(IPAddress.Loopback, 8) };
-
- + /// <summary>
- + /// The allowed values from x-forwarded-host. If the list is empty then all hosts are allowed.
- + /// Failing to restrict this these values may allow an attacker to spoof links generated by your service.
- + /// </summary>
- + /// <remarks>
- + /// <list type="bullet">
- + /// <item><description>Port numbers must be excluded.</description></item>
- + /// <item><description>A top level wildcard "*" allows all non-empty hosts.</description></item>
- + /// <item><description>Subdomain wildcards are permitted. E.g. "*.example.com" matches subdomains like foo.example.com,
- + /// but not the parent domain example.com.</description></item>
- + /// <item><description>Unicode host names are allowed but will be converted to punycode for matching.</description></item>
- + /// <item><description>IPv6 addresses must include their bounding brackets and be in their normalized form.</description></item>
- + /// </list>
- + /// </remarks>
- + public IList<string> AllowedHosts { get; set; } = new List<string>();
- +
- /// <summary>
- /// Require the number of header values to be in sync between the different headers being processed.
- /// The default is 'false'.
- diff --git a/src/Microsoft.AspNetCore.HttpsPolicy/HttpsRedirectionBuilderExtensions.cs b/src/Microsoft.AspNetCore.HttpsPolicy/HttpsRedirectionBuilderExtensions.cs
- index 429f0f0ee22..c6f804763ff 100644
- --- a/src/Microsoft.AspNetCore.HttpsPolicy/HttpsRedirectionBuilderExtensions.cs
- +++ b/src/Microsoft.AspNetCore.HttpsPolicy/HttpsRedirectionBuilderExtensions.cs
- @@ -2,11 +2,9 @@
- // 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 Microsoft.AspNetCore.Hosting.Server.Features;
- using Microsoft.AspNetCore.HttpsPolicy;
- -using Microsoft.Extensions.Configuration;
- -using Microsoft.Extensions.DependencyInjection;
- -using Microsoft.Extensions.Options;
-
- namespace Microsoft.AspNetCore.Builder
- {
- @@ -20,15 +18,13 @@ namespace Microsoft.AspNetCore.Builder
- /// </summary>
- /// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
- /// <returns>The <see cref="IApplicationBuilder"/> for HttpsRedirection.</returns>
- - /// <remarks>
- - /// HTTPS Enforcement interanlly uses the UrlRewrite middleware to redirect HTTP requests to HTTPS.
- - /// </remarks>
- public static IApplicationBuilder UseHttpsRedirection(this IApplicationBuilder app)
- {
- if (app == null)
- {
- throw new ArgumentNullException(nameof(app));
- }
- +
- var serverAddressFeature = app.ServerFeatures.Get<IServerAddressesFeature>();
- if (serverAddressFeature != null)
- {
- diff --git a/test/Microsoft.AspNetCore.HostFiltering.Tests/HostFilteringMiddlewareTests.cs b/test/Microsoft.AspNetCore.HostFiltering.Tests/HostFilteringMiddlewareTests.cs
- new file mode 100644
- index 00000000000..2fc7b8c7137
- --- /dev/null
- +++ b/test/Microsoft.AspNetCore.HostFiltering.Tests/HostFilteringMiddlewareTests.cs
- @@ -0,0 +1,183 @@
- +// 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.Builder;
- +using Microsoft.AspNetCore.Hosting;
- +using Microsoft.AspNetCore.TestHost;
- +using Microsoft.Extensions.Primitives;
- +using Microsoft.Net.Http.Headers;
- +using Xunit;
- +
- +namespace Microsoft.AspNetCore.HostFiltering
- +{
- + public class HostFilteringMiddlewareTests
- + {
- + [Fact]
- + public async Task MissingConfigThrows()
- + {
- + var builder = new WebHostBuilder()
- + .Configure(app =>
- + {
- + app.UseHostFiltering();
- + });
- + await Assert.ThrowsAsync<InvalidOperationException>(() => new TestServer(builder).SendAsync(_ => { }));
- + }
- +
- + [Theory]
- + [InlineData(true, 200)]
- + [InlineData(false, 400)]
- + public async Task AllowsMissingHost(bool allowed, int status)
- + {
- + var builder = new WebHostBuilder()
- + .ConfigureServices(services =>
- + {
- + services.AddHostFiltering(options =>
- + {
- + options.AllowEmptyHosts = allowed;
- + options.AllowedHosts.Add("Localhost");
- + });
- + })
- + .Configure(app =>
- + {
- + app.Use((ctx, next) =>
- + {
- + ctx.Request.Headers.Remove(HeaderNames.Host);
- + return next();
- + });
- + app.UseHostFiltering();
- + app.Run(c =>
- + {
- + Assert.False(c.Request.Headers.TryGetValue(HeaderNames.Host, out var host));
- + return Task.CompletedTask;
- + });
- + });
- + var server = new TestServer(builder);
- + var response = await server.CreateClient().GetAsync("/");
- + Assert.Equal(status, (int)response.StatusCode);
- + }
- +
- + [Theory]
- + [InlineData(true, 200)]
- + [InlineData(false, 400)]
- + public async Task AllowsEmptyHost(bool allowed, int status)
- + {
- + var builder = new WebHostBuilder()
- + .ConfigureServices(services =>
- + {
- + services.AddHostFiltering(options =>
- + {
- + options.AllowEmptyHosts = allowed;
- + options.AllowedHosts.Add("Localhost");
- + });
- + })
- + .Configure(app =>
- + {
- + app.Use((ctx, next) =>
- + {
- + ctx.Request.Headers[HeaderNames.Host] = " ";
- + return next();
- + });
- + app.UseHostFiltering();
- + app.Run(c =>
- + {
- + Assert.True(c.Request.Headers.TryGetValue(HeaderNames.Host, out var host));
- + Assert.True(StringValues.Equals(" ", host));
- + return Task.CompletedTask;
- + });
- + app.Run(c => Task.CompletedTask);
- + });
- + var server = new TestServer(builder);
- + var response = await server.CreateClient().GetAsync("/");
- + Assert.Equal(status, (int)response.StatusCode);
- + }
- +
- + [Theory]
- + [InlineData("localHost", "localhost")]
- + [InlineData("localHost", "*")] // Any - Used by HttpSys
- + [InlineData("localHost", "[::]")] // IPv6 Any - This is what Kestrel reports when binding to *
- + [InlineData("localHost", "0.0.0.0")] // IPv4 Any
- + [InlineData("localhost:9090", "example.com;localHost")]
- + [InlineData("example.com:443", "example.com;localhost")]
- + [InlineData("localHost:80", "localhost;")]
- + [InlineData("foo.eXample.com:443", "*.exampLe.com")]
- + [InlineData("f.eXample.com:443", "*.exampLe.com")]
- + [InlineData("127.0.0.1", "127.0.0.1")]
- + [InlineData("127.0.0.1:443", "127.0.0.1")]
- + [InlineData("xn--c1yn36f:443", "xn--c1yn36f")]
- + [InlineData("xn--c1yn36f:443", "點看")]
- + [InlineData("[::ABC]", "[::aBc]")]
- + [InlineData("[::1]:80", "[::1]")]
- + public async Task AllowsSpecifiedHost(string host, string allowedHost)
- + {
- + var builder = new WebHostBuilder()
- + .ConfigureServices(services =>
- + {
- + services.AddHostFiltering(options =>
- + {
- + options.AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
- + });
- + })
- + .Configure(app =>
- + {
- + app.Use((ctx, next) =>
- + {
- + // TestHost's ClientHandler doesn't let you set the host header, only the host in the URI
- + // and that would over-normalize some of our test conditions like casing.
- + ctx.Request.Headers[HeaderNames.Host] = host;
- + return next();
- + });
- + app.UseHostFiltering();
- + app.Run(c => Task.CompletedTask);
- + });
- + var server = new TestServer(builder);
- + var response = await server.CreateRequest("/").GetAsync();
- + Assert.Equal(200, (int)response.StatusCode);
- + }
- +
- + [Theory]
- + [InlineData("example.com", "localhost")]
- + [InlineData("localhost:9090", "example.com;")]
- + [InlineData(";", "example.com;localhost")]
- + [InlineData(";:80", "example.com;localhost")]
- + [InlineData(":80", "localhost")]
- + [InlineData(":", "localhost")]
- + [InlineData("example.com:443", "*.example.com")]
- + [InlineData(".example.com:443", "*.example.com")]
- + [InlineData("foo.com:443", "*.example.com")]
- + [InlineData("foo.example.com.bar:443", "*.example.com")]
- + [InlineData(".com:443", "*.com")]
- + // Unicode in the host shouldn't be allowed without punycode anyways. This match fails because the middleware converts
- + // its input to punycode.
- + [InlineData("點看", "點看")]
- + [InlineData("[::1", "[::1]")]
- + [InlineData("[::1:80", "[::1]")]
- + public async Task RejectsMismatchedHosts(string host, string allowedHost)
- + {
- + var builder = new WebHostBuilder()
- + .ConfigureServices(services =>
- + {
- + services.AddHostFiltering(options =>
- + {
- + options.AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
- + });
- + })
- + .Configure(app =>
- + {
- + app.Use((ctx, next) =>
- + {
- + // TestHost's ClientHandler doesn't let you set the host header, only the host in the URI
- + // and that would reject some of our test conditions.
- + ctx.Request.Headers[HeaderNames.Host] = host;
- + return next();
- + });
- + app.UseHostFiltering();
- + app.Run(c => throw new NotImplementedException("App"));
- + });
- + var server = new TestServer(builder);
- + var response = await server.CreateRequest("/").GetAsync();
- + Assert.Equal(400, (int)response.StatusCode);
- + }
- + }
- +}
- diff --git a/test/Microsoft.AspNetCore.HostFiltering.Tests/Microsoft.AspNetCore.HostFiltering.Tests.csproj b/test/Microsoft.AspNetCore.HostFiltering.Tests/Microsoft.AspNetCore.HostFiltering.Tests.csproj
- new file mode 100644
- index 00000000000..d71805c949d
- --- /dev/null
- +++ b/test/Microsoft.AspNetCore.HostFiltering.Tests/Microsoft.AspNetCore.HostFiltering.Tests.csproj
- @@ -0,0 +1,11 @@
- +<Project Sdk="Microsoft.NET.Sdk">
- +
- + <PropertyGroup>
- + <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
- + </PropertyGroup>
- +
- + <ItemGroup>
- + <ProjectReference Include="..\..\src\Microsoft.AspNetCore.HostFiltering\Microsoft.AspNetCore.HostFiltering.csproj" />
- + </ItemGroup>
- +
- +</Project>
- diff --git a/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs b/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs
- index 81d6ccf15a0..f88243c9e77 100644
- --- a/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs
- +++ b/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs
- @@ -1,12 +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 System.Linq;
- using System.Net;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.Builder;
- using Microsoft.AspNetCore.Hosting;
- using Microsoft.AspNetCore.TestHost;
- +using Microsoft.Net.Http.Headers;
- using Xunit;
-
- namespace Microsoft.AspNetCore.HttpOverrides
- @@ -392,6 +394,119 @@ namespace Microsoft.AspNetCore.HttpOverrides
- Assert.True(assertsExecuted);
- }
-
- + [Theory]
- + [InlineData("localHost", "localhost")]
- + [InlineData("localHost", "*")] // Any - Used by HttpSys
- + [InlineData("localHost", "[::]")] // IPv6 Any - This is what Kestrel reports when binding to *
- + [InlineData("localHost", "0.0.0.0")] // IPv4 Any
- + [InlineData("localhost:9090", "example.com;localHost")]
- + [InlineData("example.com:443", "example.com;localhost")]
- + [InlineData("localHost:80", "localhost;")]
- + [InlineData("foo.eXample.com:443", "*.exampLe.com")]
- + [InlineData("f.eXample.com:443", "*.exampLe.com")]
- + [InlineData("127.0.0.1", "127.0.0.1")]
- + [InlineData("127.0.0.1:443", "127.0.0.1")]
- + [InlineData("xn--c1yn36f:443", "xn--c1yn36f")]
- + [InlineData("xn--c1yn36f:443", "點看")]
- + [InlineData("[::ABC]", "[::aBc]")]
- + [InlineData("[::1]:80", "[::1]")]
- + public async Task XForwardedHostAllowsSpecifiedHost(string host, string allowedHost)
- + {
- + bool assertsExecuted = false;
- + var builder = new WebHostBuilder()
- + .Configure(app =>
- + {
- + app.UseForwardedHeaders(new ForwardedHeadersOptions
- + {
- + ForwardedHeaders = ForwardedHeaders.XForwardedHost,
- + AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
- + });
- + app.Run(context =>
- + {
- + Assert.Equal(host, context.Request.Headers[HeaderNames.Host]);
- + assertsExecuted = true;
- + return Task.FromResult(0);
- + });
- + });
- + var server = new TestServer(builder);
- + var response = await server.SendAsync(ctx =>
- + {
- + ctx.Request.Headers["X-forwarded-Host"] = host;
- + });
- + Assert.True(assertsExecuted);
- + }
- +
- + [Theory]
- + [InlineData("example.com", "localhost")]
- + [InlineData("localhost:9090", "example.com;")]
- + [InlineData(";", "example.com;localhost")]
- + [InlineData(";:80", "example.com;localhost")]
- + [InlineData(":80", "localhost")]
- + [InlineData(":", "localhost")]
- + [InlineData("example.com:443", "*.example.com")]
- + [InlineData(".example.com:443", "*.example.com")]
- + [InlineData("foo.com:443", "*.example.com")]
- + [InlineData("foo.example.com.bar:443", "*.example.com")]
- + [InlineData(".com:443", "*.com")]
- + // Unicode in the host shouldn't be allowed without punycode anyways. This match fails because the middleware converts
- + // its input to punycode.
- + [InlineData("點看", "點看")]
- + [InlineData("[::1", "[::1]")]
- + [InlineData("[::1:80", "[::1]")]
- + public async Task XForwardedHostFailsMismatchedHosts(string host, string allowedHost)
- + {
- + bool assertsExecuted = false;
- + var builder = new WebHostBuilder()
- + .Configure(app =>
- + {
- + app.UseForwardedHeaders(new ForwardedHeadersOptions
- + {
- + ForwardedHeaders = ForwardedHeaders.XForwardedHost,
- + AllowedHosts = new[] { allowedHost }
- + });
- + app.Run(context =>
- + {
- + Assert.NotEqual<string>(host, context.Request.Headers[HeaderNames.Host]);
- + assertsExecuted = true;
- + return Task.FromResult(0);
- + });
- + });
- + var server = new TestServer(builder);
- + var response = await server.SendAsync(ctx =>
- + {
- + ctx.Request.Headers["X-forwarded-Host"] = host;
- + });
- + Assert.True(assertsExecuted);
- + }
- +
- + [Fact]
- + public async Task XForwardedHostStopsAtFirstUnspecifiedHost()
- + {
- + bool assertsExecuted = false;
- + var builder = new WebHostBuilder()
- + .Configure(app =>
- + {
- + app.UseForwardedHeaders(new ForwardedHeadersOptions
- + {
- + ForwardedHeaders = ForwardedHeaders.XForwardedHost,
- + ForwardLimit = 10,
- + AllowedHosts = new[] { "bar.com", "*.foo.com" }
- + });
- + app.Run(context =>
- + {
- + Assert.Equal("bar.foo.com:432", context.Request.Headers[HeaderNames.Host]);
- + assertsExecuted = true;
- + return Task.FromResult(0);
- + });
- + });
- + var server = new TestServer(builder);
- + var response = await server.SendAsync(ctx =>
- + {
- + ctx.Request.Headers["X-forwarded-Host"] = "stuff:523, bar.foo.com:432, bar.com:80";
- + });
- + Assert.True(assertsExecuted);
- + }
- +
- [Theory]
- [InlineData(0, "h1", "http")]
- [InlineData(1, "", "http")]
- diff --git a/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HttpsRedirectionMiddlewareTests.cs b/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HttpsRedirectionMiddlewareTests.cs
- index 9d482151ca2..b55c1952729 100644
- --- a/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HttpsRedirectionMiddlewareTests.cs
- +++ b/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HttpsRedirectionMiddlewareTests.cs
- @@ -22,9 +22,6 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
- public async Task SetOptions_DefaultsSetCorrectly()
- {
- var builder = new WebHostBuilder()
- - .ConfigureServices(services =>
- - {
- - })
- .Configure(app =>
- {
- app.UseHttpsRedirection();
- @@ -34,9 +31,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
- });
- });
-
- - var featureCollection = new FeatureCollection();
- - featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
- - var server = new TestServer(builder, featureCollection);
- + var server = new TestServer(builder);
- var client = server.CreateClient();
-
- var request = new HttpRequestMessage(HttpMethod.Get, "");
- @@ -75,9 +70,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
- });
- });
-
- - var featureCollection = new FeatureCollection();
- - featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
- - var server = new TestServer(builder, featureCollection);
- + var server = new TestServer(builder);
- var client = server.CreateClient();
-
- var request = new HttpRequestMessage(HttpMethod.Get, "");
- @@ -115,9 +108,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
- });
- });
-
- - var featureCollection = new FeatureCollection();
- - featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
- - var server = new TestServer(builder, featureCollection);
- + var server = new TestServer(builder);
- var client = server.CreateClient();
-
- var request = new HttpRequestMessage(HttpMethod.Get, "");
- @@ -182,12 +173,6 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
- public async Task SetServerAddressesFeature_SingleHttpsAddress_Success()
- {
- var builder = new WebHostBuilder()
- - .ConfigureServices(services =>
- - {
- - services.AddHttpsRedirection(options =>
- - {
- - });
- - })
- .Configure(app =>
- {
- app.UseHttpsRedirection();
- @@ -215,12 +200,6 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
- public async Task SetServerAddressesFeature_MultipleHttpsAddresses_ThrowInMiddleware()
- {
- var builder = new WebHostBuilder()
- - .ConfigureServices(services =>
- - {
- - services.AddHttpsRedirection(options =>
- - {
- - });
- - })
- .Configure(app =>
- {
- app.UseHttpsRedirection();
- @@ -248,12 +227,6 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
- public async Task SetServerAddressesFeature_MultipleHttpsAddressesWithSamePort_Success()
- {
- var builder = new WebHostBuilder()
- - .ConfigureServices(services =>
- - {
- - services.AddHttpsRedirection(options =>
- - {
- - });
- - })
- .Configure(app =>
- {
- app.UseHttpsRedirection();
|