/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs
1 595 строк
61 KB
Sébastien Ros
[release/10.0] Implement KnownNetworks dual list (#63658)
17 сен 2025, 19:43
Не верифицирован
17 сен 2025, 19:43
72692ad
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; using System.Net; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace Microsoft.AspNetCore.HttpOverrides; public class ForwardedHeadersMiddlewareTests { [Fact] public async Task XForwardedForDefaultSettingsChangeRemoteIpAndPort() { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = "11.111.111.11:9090"; }); Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); Assert.Equal(9090, context.Connection.RemotePort); // No Original set if RemoteIpAddress started null. Assert.False(context.Request.Headers.ContainsKey("X-Original-For")); // Should have been consumed and removed Assert.False(context.Request.Headers.ContainsKey("X-Forwarded-For")); } [Theory] [InlineData(1, "11.111.111.11.12345", "10.0.0.1", 99)] // Invalid public async Task XForwardedForFirstValueIsInvalid(int limit, string header, string expectedIp, int expectedPort) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor, ForwardLimit = limit, }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = header; c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1"); c.Connection.RemotePort = 99; }); Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString()); Assert.Equal(expectedPort, context.Connection.RemotePort); Assert.False(context.Request.Headers.ContainsKey("X-Original-For")); Assert.True(context.Request.Headers.ContainsKey("X-Forwarded-For")); Assert.Equal(header, context.Request.Headers["X-Forwarded-For"]); } [Theory] [InlineData(1, "11.111.111.11:12345", "11.111.111.11", 12345, "", false)] [InlineData(1, "11.111.111.11:12345", "11.111.111.11", 12345, "", true)] [InlineData(10, "11.111.111.11:12345", "11.111.111.11", 12345, "", false)] [InlineData(10, "11.111.111.11:12345", "11.111.111.11", 12345, "", true)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12:23456", false)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12:23456", true)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "", false)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "", true)] [InlineData(10, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "", false)] [InlineData(10, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "", true)] [InlineData(10, "12.112.112.12.23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12.23456", false)] // Invalid 2nd value [InlineData(10, "12.112.112.12.23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12.23456", true)] // Invalid 2nd value [InlineData(10, "13.113.113.13:34567, 12.112.112.12.23456, 11.111.111.11:12345", "11.111.111.11", 12345, "13.113.113.13:34567,12.112.112.12.23456", false)] // Invalid 2nd value [InlineData(10, "13.113.113.13:34567, 12.112.112.12.23456, 11.111.111.11:12345", "11.111.111.11", 12345, "13.113.113.13:34567,12.112.112.12.23456", true)] // Invalid 2nd value [InlineData(2, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "13.113.113.13:34567", false)] [InlineData(2, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "13.113.113.13:34567", true)] [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "13.113.113.13", 34567, "", false)] [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "13.113.113.13", 34567, "", true)] public async Task XForwardedForForwardLimit_Obsolete(int limit, string header, string expectedIp, int expectedPort, string remainingHeader, bool requireSymmetry) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { var options = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor, RequireHeaderSymmetry = requireSymmetry, ForwardLimit = limit, }; options.KnownProxies.Clear(); #pragma warning disable ASPDEPR005 // KnownNetworks is obsolete options.KnownNetworks.Clear(); #pragma warning restore ASPDEPR005 // KnownNetworks is obsolete app.UseForwardedHeaders(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = header; c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1"); c.Connection.RemotePort = 99; }); Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString()); Assert.Equal(expectedPort, context.Connection.RemotePort); Assert.Equal(remainingHeader, context.Request.Headers["X-Forwarded-For"].ToString()); } [Theory] [InlineData(1, "11.111.111.11:12345", "11.111.111.11", 12345, "", false)] [InlineData(1, "11.111.111.11:12345", "11.111.111.11", 12345, "", true)] [InlineData(10, "11.111.111.11:12345", "11.111.111.11", 12345, "", false)] [InlineData(10, "11.111.111.11:12345", "11.111.111.11", 12345, "", true)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12:23456", false)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12:23456", true)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "", false)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "", true)] [InlineData(10, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "", false)] [InlineData(10, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "", true)] [InlineData(10, "12.112.112.12.23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12.23456", false)] // Invalid 2nd value [InlineData(10, "12.112.112.12.23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12.23456", true)] // Invalid 2nd value [InlineData(10, "13.113.113.13:34567, 12.112.112.12.23456, 11.111.111.11:12345", "11.111.111.11", 12345, "13.113.113.13:34567,12.112.112.12.23456", false)] // Invalid 2nd value [InlineData(10, "13.113.113.13:34567, 12.112.112.12.23456, 11.111.111.11:12345", "11.111.111.11", 12345, "13.113.113.13:34567,12.112.112.12.23456", true)] // Invalid 2nd value [InlineData(2, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "13.113.113.13:34567", false)] [InlineData(2, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "13.113.113.13:34567", true)] [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "13.113.113.13", 34567, "", false)] [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "13.113.113.13", 34567, "", true)] public async Task XForwardedForForwardLimit(int limit, string header, string expectedIp, int expectedPort, string remainingHeader, bool requireSymmetry) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { var options = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor, RequireHeaderSymmetry = requireSymmetry, ForwardLimit = limit, }; options.KnownProxies.Clear(); options.KnownIPNetworks.Clear(); app.UseForwardedHeaders(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = header; c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1"); c.Connection.RemotePort = 99; }); Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString()); Assert.Equal(expectedPort, context.Connection.RemotePort); Assert.Equal(remainingHeader, context.Request.Headers["X-Forwarded-For"].ToString()); } [Theory] [InlineData("11.111.111.11", false)] [InlineData("127.0.0.1", true)] [InlineData("127.0.1.1", true)] [InlineData("::1", true)] [InlineData("::", false)] public async Task XForwardedForLoopback(string originalIp, bool expectForwarded) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor, }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = "10.0.0.1:1234"; c.Connection.RemoteIpAddress = IPAddress.Parse(originalIp); c.Connection.RemotePort = 99; }); if (expectForwarded) { Assert.Equal("10.0.0.1", context.Connection.RemoteIpAddress.ToString()); Assert.Equal(1234, context.Connection.RemotePort); Assert.True(context.Request.Headers.ContainsKey("X-Original-For")); Assert.Equal(new IPEndPoint(IPAddress.Parse(originalIp), 99).ToString(), context.Request.Headers["X-Original-For"]); } else { Assert.Equal(originalIp, context.Connection.RemoteIpAddress.ToString()); Assert.Equal(99, context.Connection.RemotePort); Assert.False(context.Request.Headers.ContainsKey("X-Original-For")); } } [Theory] [InlineData(1, "11.111.111.11:12345", "20.0.0.1", "10.0.0.1", 99, false)] [InlineData(1, "11.111.111.11:12345", "20.0.0.1", "10.0.0.1", 99, true)] [InlineData(1, "", "10.0.0.1", "10.0.0.1", 99, false)] [InlineData(1, "", "10.0.0.1", "10.0.0.1", 99, true)] [InlineData(1, "11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345, false)] [InlineData(1, "11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345, true)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345, false)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345, true)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11", "11.111.111.11", 12345, false)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11", "11.111.111.11", 12345, true)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11", "12.112.112.12", 23456, false)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11", "12.112.112.12", 23456, true)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "11.111.111.11", 12345, false)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "11.111.111.11", 12345, true)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "12.112.112.12", 23456, false)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "12.112.112.12", 23456, true)] [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "13.113.113.13", 34567, false)] [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "13.113.113.13", 34567, true)] [InlineData(3, "13.113.113.13:34567, 12.112.112.12;23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "11.111.111.11", 12345, false)] // Invalid 2nd IP [InlineData(3, "13.113.113.13:34567, 12.112.112.12;23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "11.111.111.11", 12345, true)] // Invalid 2nd IP [InlineData(3, "13.113.113.13;34567, 12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "12.112.112.12", 23456, false)] // Invalid 3rd IP [InlineData(3, "13.113.113.13;34567, 12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "12.112.112.12", 23456, true)] // Invalid 3rd IP public async Task XForwardedForForwardKnownIps(int limit, string header, string knownIPs, string expectedIp, int expectedPort, bool requireSymmetry) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { var options = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor, RequireHeaderSymmetry = requireSymmetry, ForwardLimit = limit, }; foreach (var ip in knownIPs.Split(',').Select(text => IPAddress.Parse(text))) { options.KnownProxies.Add(ip); } app.UseForwardedHeaders(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = header; c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1"); c.Connection.RemotePort = 99; }); Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString()); Assert.Equal(expectedPort, context.Connection.RemotePort); } [Fact] public async Task XForwardedForOverrideBadIpDoesntChangeRemoteIp() { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = "BAD-IP"; }); Assert.Null(context.Connection.RemoteIpAddress); } [Fact] public async Task XForwardedHostOverrideChangesRequestHost() { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedHost }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Host"] = "testhost"; }); Assert.Equal("testhost", context.Request.Host.ToString()); } public static TheoryData<string> HostHeaderData { get { return new TheoryData<string>() { "z", "1", "y:1", "1:1", "[ABCdef]", "[abcDEF]:0", "[abcdef:127.2355.1246.114]:0", "[::1]:80", "127.0.0.1:80", "900.900.900.900:9523547852", "foo", "foo:234", "foo.bar.baz", "foo.BAR.baz:46245", "foo.ba-ar.baz:46245", "-foo:1234", "xn--c1yn36f:134", "-", "_", "~", "!", "$", "'", "(", ")", }; } } [Theory] [MemberData(nameof(HostHeaderData))] public async Task XForwardedHostAllowsValidCharacters(string hostHeader) { var assertsExecuted = false; using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedHost }); app.Run(context => { Assert.Equal(hostHeader, context.Request.Host.ToString()); assertsExecuted = true; return Task.FromResult(0); }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Host"] = hostHeader; }); Assert.True(assertsExecuted); } public static TheoryData<string> HostHeaderInvalidData { get { // see https://tools.ietf.org/html/rfc7230#section-5.4 var data = new TheoryData<string>() { "", // Empty "[]", // Too short "[::]", // Too short "[ghijkl]", // Non-hex "[afd:adf:123", // Incomplete "[afd:adf]123", // Missing : "[afd:adf]:", // Missing port digits "[afd adf]", // Space "[ad-314]", // dash ":1234", // Missing host "a:b:c", // Missing [] "::1", // Missing [] "::", // Missing everything "abcd:1abcd", // Letters in port "abcd:1.2", // Dot in port "1.2.3.4:", // Missing port digits "1.2 .4", // Space }; // These aren't allowed anywhere in the host header var invalid = "\"#%*+/;<=>?@[]\\^`{}|"; foreach (var ch in invalid) { data.Add(ch.ToString()); } invalid = "!\"#$%&'()*+,/;<=>?@[]\\^_`{}|~-"; foreach (var ch in invalid) { data.Add("[abd" + ch + "]:1234"); } invalid = "!\"#$%&'()*+/;<=>?@[]\\^_`{}|~:abcABC-."; foreach (var ch in invalid) { data.Add("a.b.c:" + ch); } return data; } } [Theory] [MemberData(nameof(HostHeaderInvalidData))] public async Task XForwardedHostFailsForInvalidCharacters(string hostHeader) { var assertsExecuted = false; using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedHost }); app.Run(context => { Assert.NotEqual(hostHeader, context.Request.Host.Value); assertsExecuted = true; return Task.FromResult(0); }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Host"] = hostHeader; }); 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 hostHeader, string allowedHost) { bool assertsExecuted = false; using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedHost, AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) }); app.Run(context => { Assert.Equal(hostHeader, context.Request.Headers.Host); assertsExecuted = true; return Task.FromResult(0); }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.SendAsync(ctx => { ctx.Request.Headers["X-forwarded-Host"] = hostHeader; }); 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 hostHeader, string allowedHost) { bool assertsExecuted = false; using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedHost, AllowedHosts = new[] { allowedHost } }); app.Run(context => { Assert.NotEqual<string>(hostHeader, context.Request.Headers.Host); assertsExecuted = true; return Task.FromResult(0); }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.SendAsync(ctx => { ctx.Request.Headers["X-forwarded-Host"] = hostHeader; }); Assert.True(assertsExecuted); } [Fact] public async Task XForwardedHostStopsAtFirstUnspecifiedHost() { bool assertsExecuted = false; using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .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.Host); assertsExecuted = true; return Task.FromResult(0); }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); 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")] [InlineData(1, "h1", "h1")] [InlineData(3, "h1", "h1")] [InlineData(1, "h2, h1", "h1")] [InlineData(2, "h2, h1", "h2")] [InlineData(10, "h3, h2, h1", "h3")] public async Task XForwardedProtoOverrideChangesRequestProtocol(int limit, string header, string expected) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto, ForwardLimit = limit, }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = header; }); Assert.Equal(expected, context.Request.Scheme); } public static TheoryData<string> ProtoHeaderData { get { // ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) return new TheoryData<string>() { "z", "Z", "1", "y+", "1-", "a.", }; } } [Theory] [MemberData(nameof(ProtoHeaderData))] public async Task XForwardedProtoAcceptsValidProtocols(string scheme) { var assertsExecuted = false; using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto }); app.Run(context => { Assert.Equal(scheme, context.Request.Scheme); assertsExecuted = true; return Task.FromResult(0); }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = scheme; }); Assert.True(assertsExecuted); } public static TheoryData<string> ProtoHeaderInvalidData { get { // ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) var data = new TheoryData<string>() { "a b", // Space }; // These aren't allowed anywhere in the scheme header var invalid = "!\"#$%&'()*/:;<=>?@[]\\^_`{}|~"; foreach (var ch in invalid) { data.Add(ch.ToString()); } return data; } } [Theory] [MemberData(nameof(ProtoHeaderInvalidData))] public async Task XForwardedProtoRejectsInvalidProtocols(string scheme) { var assertsExecuted = false; using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto, }); app.Run(context => { Assert.Equal("http", context.Request.Scheme); assertsExecuted = true; return Task.FromResult(0); }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = scheme; }); Assert.True(assertsExecuted); } [Theory] [InlineData(0, "h1", "::1", "http")] [InlineData(1, "", "::1", "http")] [InlineData(1, "h1", "::1", "h1")] [InlineData(3, "h1", "::1", "h1")] [InlineData(3, "h2, h1", "::1", "http")] [InlineData(5, "h2, h1", "::1, ::1", "h2")] [InlineData(10, "h3, h2, h1", "::1, ::1, ::1", "h3")] [InlineData(10, "h3, h2, h1", "::1, badip, ::1", "h1")] public async Task XForwardedProtoOverrideLimitedByXForwardedForCount(int limit, string protoHeader, string forHeader, string expected) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor, RequireHeaderSymmetry = true, ForwardLimit = limit, }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = protoHeader; c.Request.Headers["X-Forwarded-For"] = forHeader; }); Assert.Equal(expected, context.Request.Scheme); } [Theory] [InlineData(0, "h1", "::1", "http")] [InlineData(1, "", "::1", "http")] [InlineData(1, "h1", "", "h1")] [InlineData(1, "h1", "::1", "h1")] [InlineData(3, "h1", "::1", "h1")] [InlineData(3, "h1", "::1, ::1", "h1")] [InlineData(3, "h2, h1", "::1", "h2")] [InlineData(5, "h2, h1", "::1, ::1", "h2")] [InlineData(10, "h3, h2, h1", "::1, ::1, ::1", "h3")] [InlineData(10, "h3, h2, h1", "::1, badip, ::1", "h1")] public async Task XForwardedProtoOverrideCanBeIndependentOfXForwardedForCount(int limit, string protoHeader, string forHeader, string expected) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor, RequireHeaderSymmetry = false, ForwardLimit = limit, }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = protoHeader; c.Request.Headers["X-Forwarded-For"] = forHeader; }); Assert.Equal(expected, context.Request.Scheme); } [Theory] [InlineData("", "", "::1", false, "http")] [InlineData("h1", "", "::1", false, "http")] [InlineData("h1", "F::", "::1", false, "h1")] [InlineData("h1", "F::", "E::", false, "h1")] [InlineData("", "", "::1", true, "http")] [InlineData("h1", "", "::1", true, "http")] [InlineData("h1", "F::", "::1", true, "h1")] [InlineData("h1", "", "F::", true, "http")] [InlineData("h1", "E::", "F::", true, "http")] [InlineData("h2, h1", "", "::1", true, "http")] [InlineData("h2, h1", "F::, D::", "::1", true, "h1")] [InlineData("h2, h1", "E::, D::", "F::", true, "http")] public async Task XForwardedProtoOverrideLimitedByLoopback_Obsolete(string protoHeader, string forHeader, string remoteIp, bool loopback, string expected) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { var options = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor, RequireHeaderSymmetry = true, ForwardLimit = 5, }; if (!loopback) { #pragma warning disable ASPDEPR005 // KnownNetworks is obsolete options.KnownNetworks.Clear(); #pragma warning restore ASPDEPR005 // KnownNetworks is obsolete options.KnownProxies.Clear(); } app.UseForwardedHeaders(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = protoHeader; c.Request.Headers["X-Forwarded-For"] = forHeader; c.Connection.RemoteIpAddress = IPAddress.Parse(remoteIp); }); Assert.Equal(expected, context.Request.Scheme); } [Theory] [InlineData("", "", "::1", false, "http")] [InlineData("h1", "", "::1", false, "http")] [InlineData("h1", "F::", "::1", false, "h1")] [InlineData("h1", "F::", "E::", false, "h1")] [InlineData("", "", "::1", true, "http")] [InlineData("h1", "", "::1", true, "http")] [InlineData("h1", "F::", "::1", true, "h1")] [InlineData("h1", "", "F::", true, "http")] [InlineData("h1", "E::", "F::", true, "http")] [InlineData("h2, h1", "", "::1", true, "http")] [InlineData("h2, h1", "F::, D::", "::1", true, "h1")] [InlineData("h2, h1", "E::, D::", "F::", true, "http")] public async Task XForwardedProtoOverrideLimitedByLoopback(string protoHeader, string forHeader, string remoteIp, bool loopback, string expected) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { var options = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor, RequireHeaderSymmetry = true, ForwardLimit = 5, }; if (!loopback) { options.KnownIPNetworks.Clear(); options.KnownProxies.Clear(); } app.UseForwardedHeaders(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = protoHeader; c.Request.Headers["X-Forwarded-For"] = forHeader; c.Connection.RemoteIpAddress = IPAddress.Parse(remoteIp); }); Assert.Equal(expected, context.Request.Scheme); } [Fact] public void AllForwardsDisabledByDefault() { var options = new ForwardedHeadersOptions(); Assert.True(options.ForwardedHeaders == ForwardedHeaders.None); Assert.Equal(1, options.ForwardLimit); Assert.Single(options.KnownIPNetworks); Assert.Single(options.KnownProxies); } [Fact] public async Task AllForwardsEnabledChangeRequestRemoteIpHostProtocolAndPathBase() { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = "Protocol"; c.Request.Headers["X-Forwarded-For"] = "11.111.111.11"; c.Request.Headers["X-Forwarded-Host"] = "testhost"; c.Request.Headers["X-Forwarded-Prefix"] = "/pathbase"; }); Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); Assert.Equal("testhost", context.Request.Host.ToString()); Assert.Equal("Protocol", context.Request.Scheme); Assert.Equal("/pathbase", context.Request.PathBase); } [Fact] public async Task AllOptionsDisabledRequestDoesntChange() { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.None }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = "Protocol"; c.Request.Headers["X-Forwarded-For"] = "11.111.111.11"; c.Request.Headers["X-Forwarded-Host"] = "otherhost"; c.Request.Headers["X-Forwarded-Prefix"] = "/pathbase"; }); Assert.Null(context.Connection.RemoteIpAddress); Assert.Equal("localhost", context.Request.Host.ToString()); Assert.Equal("http", context.Request.Scheme); Assert.Equal(PathString.Empty, context.Request.PathBase); } [Theory] [InlineData(ForwardedHeaders.XForwardedFor, false)] [InlineData(ForwardedHeaders.XForwardedFor, true)] [InlineData(ForwardedHeaders.XForwardedHost, false)] [InlineData(ForwardedHeaders.XForwardedHost, true)] [InlineData(ForwardedHeaders.XForwardedProto, false)] [InlineData(ForwardedHeaders.XForwardedProto, true)] [InlineData(ForwardedHeaders.XForwardedPrefix, false)] [InlineData(ForwardedHeaders.XForwardedPrefix, true)] public async Task IgnoreXForwardedHeadersFromUnknownProxy(ForwardedHeaders forwardedHeaders, bool unknownProxy) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { var options = new ForwardedHeadersOptions { ForwardedHeaders = forwardedHeaders }; if (!unknownProxy) { var proxy = IPAddress.Parse("10.0.0.1"); options.KnownProxies.Add(proxy); } app.UseForwardedHeaders(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = "11.111.111.11"; c.Request.Headers["X-Forwarded-Host"] = "testhost"; c.Request.Headers["X-Forwarded-Proto"] = "Protocol"; c.Request.Headers["X-Forwarded-Prefix"] = "/pathbase"; c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1"); c.Connection.RemotePort = 99; }); if (unknownProxy) { Assert.Equal("10.0.0.1", context.Connection.RemoteIpAddress.ToString()); Assert.Equal("localhost", context.Request.Host.ToString()); Assert.Equal("http", context.Request.Scheme); Assert.Equal(PathString.Empty, context.Request.PathBase); } else { if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedFor)) { Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); } if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedHost)) { Assert.Equal("testhost", context.Request.Host.ToString()); } if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedProto)) { Assert.Equal("Protocol", context.Request.Scheme); } if (forwardedHeaders.HasFlag(ForwardedHeaders.XForwardedPrefix)) { Assert.Equal("/pathbase", context.Request.PathBase); } } } [Fact] public async Task PartiallyEnabledForwardsPartiallyChangesRequest() { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = "Protocol"; c.Request.Headers["X-Forwarded-For"] = "11.111.111.11"; }); Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); Assert.Equal("localhost", context.Request.Host.ToString()); Assert.Equal("Protocol", context.Request.Scheme); } [Theory] [InlineData("22.33.44.55,::ffff:127.0.0.1", "", "", "22.33.44.55")] [InlineData("22.33.44.55,::ffff:172.123.142.121", "172.123.142.121", "", "22.33.44.55")] [InlineData("22.33.44.55,::ffff:172.123.142.121", "::ffff:172.123.142.121", "", "22.33.44.55")] [InlineData("22.33.44.55,::ffff:172.123.142.121,172.32.24.23", "", "172.0.0.0/8", "22.33.44.55")] [InlineData("2a00:1450:4009:802::200e,2a02:26f0:2d:183::356e,::ffff:172.123.142.121,172.32.24.23", "", "172.0.0.0/8,2a02:26f0:2d:183::1/64", "2a00:1450:4009:802::200e")] [InlineData("22.33.44.55,2a02:26f0:2d:183::356e,::ffff:127.0.0.1", "2a02:26f0:2d:183::356e", "", "22.33.44.55")] public async Task XForwardForIPv4ToIPv6Mapping(string forHeader, string knownProxies, string knownNetworks, string expectedRemoteIp) { var options = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor, ForwardLimit = null, }; foreach (var knownProxy in knownProxies.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries)) { var proxy = IPAddress.Parse(knownProxy); options.KnownProxies.Add(proxy); } foreach (var knownNetwork in knownNetworks.Split(new string[] { "," }, options: StringSplitOptions.RemoveEmptyEntries)) { var knownNetworkParts = knownNetwork.Split('/'); var networkIp = IPAddress.Parse(knownNetworkParts[0]); var prefixLength = int.Parse(knownNetworkParts[1], CultureInfo.InvariantCulture); options.KnownIPNetworks.Add(new System.Net.IPNetwork(networkIp, prefixLength)); } using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = forHeader; }); Assert.Equal(expectedRemoteIp, context.Connection.RemoteIpAddress.ToString()); } [Theory] [InlineData(1, "httpa, httpb, httpc", "httpc", "httpa,httpb")] [InlineData(2, "httpa, httpb, httpc", "httpb", "httpa")] public async Task ForwardersWithDIOptionsRunsOnce_Obsolete(int limit, string header, string expectedScheme, string remainingHeader) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .ConfigureServices(services => { services.Configure<ForwardedHeadersOptions>(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedProto; options.KnownProxies.Clear(); #pragma warning disable ASPDEPR005 // KnownNetworks is obsolete options.KnownNetworks.Clear(); #pragma warning restore ASPDEPR005 // KnownNetworks is obsolete options.ForwardLimit = limit; }); }) .Configure(app => { app.UseForwardedHeaders(); app.UseForwardedHeaders(); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = header; }); Assert.Equal(expectedScheme, context.Request.Scheme); Assert.Equal(remainingHeader, context.Request.Headers["X-Forwarded-Proto"].ToString()); } [Theory] [InlineData(1, "httpa, httpb, httpc", "httpc", "httpa,httpb")] [InlineData(2, "httpa, httpb, httpc", "httpb", "httpa")] public async Task ForwardersWithDIOptionsRunsOnce(int limit, string header, string expectedScheme, string remainingHeader) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .ConfigureServices(services => { services.Configure<ForwardedHeadersOptions>(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedProto; options.KnownProxies.Clear(); options.KnownIPNetworks.Clear(); options.ForwardLimit = limit; }); }) .Configure(app => { app.UseForwardedHeaders(); app.UseForwardedHeaders(); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = header; }); Assert.Equal(expectedScheme, context.Request.Scheme); Assert.Equal(remainingHeader, context.Request.Headers["X-Forwarded-Proto"].ToString()); } [Theory] [InlineData(1, "httpa, httpb, httpc", "httpb", "httpa")] [InlineData(2, "httpa, httpb, httpc", "httpa", "")] public async Task ForwardersWithDirectOptionsRunsTwice_Obsolete(int limit, string header, string expectedScheme, string remainingHeader) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { var options = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto, ForwardLimit = limit, }; options.KnownProxies.Clear(); #pragma warning disable ASPDEPR005 // KnownNetworks is obsolete options.KnownNetworks.Clear(); #pragma warning restore ASPDEPR005 // KnownNetworks is obsolete app.UseForwardedHeaders(options); app.UseForwardedHeaders(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = header; }); Assert.Equal(expectedScheme, context.Request.Scheme); Assert.Equal(remainingHeader, context.Request.Headers["X-Forwarded-Proto"].ToString()); } [Theory] [InlineData(1, "httpa, httpb, httpc", "httpb", "httpa")] [InlineData(2, "httpa, httpb, httpc", "httpa", "")] public async Task ForwardersWithDirectOptionsRunsTwice(int limit, string header, string expectedScheme, string remainingHeader) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { var options = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto, ForwardLimit = limit, }; options.KnownProxies.Clear(); options.KnownIPNetworks.Clear(); app.UseForwardedHeaders(options); app.UseForwardedHeaders(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = header; }); Assert.Equal(expectedScheme, context.Request.Scheme); Assert.Equal(remainingHeader, context.Request.Headers["X-Forwarded-Proto"].ToString()); } [Theory] [InlineData("/foo", "/foo")] [InlineData("/foo/", "/foo/")] [InlineData("/foo/bar", "/foo/bar")] [InlineData("/foo%20bar", "/foo bar")] [InlineData("/foo?bar?", "/foo?bar?")] [InlineData("/foo%2F", "/foo%2F")] [InlineData("/foo%2F/", "/foo%2F/")] public async Task XForwardedPrefixReplaceEmptyPathBase( string forwardedPrefix, string expectedUnescapedPathBase) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedPrefix, }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Prefix"] = forwardedPrefix; }); Assert.Equal(expectedUnescapedPathBase, context.Request.PathBase.Value); // No X-Original-Prefix header set when original PathBase is empty Assert.False(context.Request.Headers.ContainsKey("X-Original-Prefix")); // Should have been consumed and removed Assert.False(context.Request.Headers.ContainsKey("X-Forwarded-Prefix")); } [Theory] [InlineData("/foo", "/bar", "/bar", "/foo")] [InlineData("/foo", "/", "/", "/foo")] [InlineData("/foo", "/foo/bar", "/foo/bar", "/foo")] [InlineData("/foo/bar", "/foo", "/foo", "/foo/bar")] [InlineData("/foo bar", "/", "/", "/foo%20bar")] public async Task XForwardedPrefixReplaceNonEmptyPathBase( string pathBase, string forwardedPrefix, string expectedUnescapedPathBase, string expectedOriginalPrefix) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedPrefix, }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.PathBase = new PathString(pathBase); c.Request.Headers["X-Forwarded-Prefix"] = forwardedPrefix; }); Assert.Equal(expectedUnescapedPathBase, context.Request.PathBase.Value); Assert.Equal(expectedOriginalPrefix, context.Request.Headers["X-Original-Prefix"]); // Should have been consumed and removed Assert.False(context.Request.Headers.ContainsKey("X-Forwarded-Prefix")); } [Theory] [InlineData("invalid")] [InlineData("invalid/")] [InlineData("%2Finvalid")] public async Task XForwardedPrefixInvalidPath(string forwardedPrefix) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedPrefix, }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Prefix"] = forwardedPrefix; }); Assert.Equal(PathString.Empty, context.Request.PathBase); Assert.False(context.Request.Headers.ContainsKey("X-Original-Prefix")); Assert.True(context.Request.Headers.ContainsKey("X-Forwarded-Prefix")); } [Theory] [InlineData("11.111.111.11", "host1, host2", "h1, h2", "/prefix1, /prefix2")] [InlineData("11.111.111.11, 22.222.222.22", "host1", "h1, h2", "/prefix1, /prefix2")] [InlineData("11.111.111.11, 22.222.222.22", "host1, host2", "h1", "/prefix1, /prefix2")] [InlineData("11.111.111.11, 22.222.222.22", "host1, host2", "h1, h2", "/prefix1")] public async Task XForwardedPrefixParameterCountMismatch( string forwardedFor, string forwardedHost, string forwardedProto, string forwardedPrefix) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedPrefix, RequireHeaderSymmetry = true, }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = forwardedFor; c.Request.Headers["X-Forwarded-Host"] = forwardedHost; c.Request.Headers["X-Forwarded-Proto"] = forwardedProto; c.Request.Headers["X-Forwarded-Prefix"] = forwardedPrefix; }); Assert.Equal(PathString.Empty, context.Request.PathBase); Assert.False(context.Request.Headers.ContainsKey("X-Original-For")); Assert.False(context.Request.Headers.ContainsKey("X-Original-Host")); Assert.False(context.Request.Headers.ContainsKey("X-Original-Proto")); Assert.False(context.Request.Headers.ContainsKey("X-Original-Prefix")); Assert.True(context.Request.Headers.ContainsKey("X-Forwarded-For")); Assert.True(context.Request.Headers.ContainsKey("X-Forwarded-Host")); Assert.True(context.Request.Headers.ContainsKey("X-Forwarded-Proto")); Assert.True(context.Request.Headers.ContainsKey("X-Forwarded-Prefix")); } [Theory] [InlineData(1, "/prefix1, /prefix2", "/prefix1")] [InlineData(1, "/prefix1, /prefix2, /prefix3", "/prefix1,/prefix2")] public async Task XForwardedPrefixTruncateConsumedValues( int limit, string forwardedPrefix, string expectedforwardedPrefix) { using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedPrefix, ForwardLimit = limit, }); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Prefix"] = forwardedPrefix; }); Assert.Equal(expectedforwardedPrefix, context.Request.Headers["X-Forwarded-Prefix"].ToString()); } }