Add host filtering middleware
This commit is contained in:
parent
8b58a9a091
commit
2b80c90554
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"AllowedHosts": [ "localhost", "127.0.0.1", "[::1]" ]
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"AllowedHosts": [ "example.com", "localhost" ]
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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'.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue