diff --git a/samples/HttpOverridesSample/Startup.cs b/samples/HttpOverridesSample/Startup.cs
index 473a978a55..9cacb60dc1 100644
--- a/samples/HttpOverridesSample/Startup.cs
+++ b/samples/HttpOverridesSample/Startup.cs
@@ -10,10 +10,9 @@ namespace HttpOverridesSample
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
- app.UseIISPlatformHandler();
- app.UseOverrideHeaders(new OverrideHeaderOptions
+ app.UseForwardedHeaders(new ForwardedHeadersOptions
{
- ForwardedOptions = ForwardedHeaders.All
+ ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseHttpMethodOverride();
diff --git a/samples/HttpOverridesSample/project.json b/samples/HttpOverridesSample/project.json
index 7550445e20..6f7515973c 100644
--- a/samples/HttpOverridesSample/project.json
+++ b/samples/HttpOverridesSample/project.json
@@ -4,9 +4,9 @@
"emitEntryPoint": true
},
"dependencies": {
+ "Microsoft.AspNetCore.HttpOverrides": "1.0.0-*",
"Microsoft.AspNetCore.IISPlatformHandler": "1.0.0-*",
- "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*",
- "Microsoft.AspNetCore.HttpOverrides": "0.1.0-*"
+ "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*"
},
"commands": {
"web": "HttpOverridesSample"
@@ -23,4 +23,4 @@
"**.user",
"**.vspscc"
]
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderExtensions.cs b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersExtensions.cs
similarity index 61%
rename from src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderExtensions.cs
rename to src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersExtensions.cs
index 8925c4bb68..df3c221996 100644
--- a/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderExtensions.cs
+++ b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersExtensions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// 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;
@@ -7,31 +7,30 @@ using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Builder
{
- public static class OverrideHeaderExtensions
+ public static class ForwardedHeadersExtensions
{
///
/// Forwards proxied headers onto current request
///
///
- /// Enables the different override options.
///
- public static IApplicationBuilder UseOverrideHeaders(this IApplicationBuilder builder)
+ public static IApplicationBuilder UseForwardedHeaders(this IApplicationBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
- return builder.UseMiddleware();
+ return builder.UseMiddleware();
}
///
/// Forwards proxied headers onto current request
///
///
- /// Enables the different override options.
+ /// Enables the different forwarding options.
///
- public static IApplicationBuilder UseOverrideHeaders(this IApplicationBuilder builder, OverrideHeaderOptions options)
+ public static IApplicationBuilder UseForwardedHeaders(this IApplicationBuilder builder, ForwardedHeadersOptions options)
{
if (builder == null)
{
@@ -42,7 +41,7 @@ namespace Microsoft.AspNetCore.Builder
throw new ArgumentNullException(nameof(options));
}
- return builder.UseMiddleware(Options.Create(options));
+ return builder.UseMiddleware(Options.Create(options));
}
}
}
diff --git a/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs
new file mode 100644
index 0000000000..042b0887b7
--- /dev/null
+++ b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs
@@ -0,0 +1,275 @@
+// 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.Net;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+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
+{
+ public class ForwardedHeadersMiddleware
+ {
+ private const string XForwardedForHeaderName = "X-Forwarded-For";
+ private const string XForwardedHostHeaderName = "X-Forwarded-Host";
+ private const string XForwardedProtoHeaderName = "X-Forwarded-Proto";
+ private const string XOriginalForName = "X-Original-For";
+ private const string XOriginalHostName = "X-Original-Host";
+ private const string XOriginalProtoName = "X-Original-Proto";
+
+ private readonly ForwardedHeadersOptions _options;
+ private readonly RequestDelegate _next;
+ private readonly ILogger _logger;
+
+ public ForwardedHeadersMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions options)
+ {
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
+ if (loggerFactory == null)
+ {
+ throw new ArgumentNullException(nameof(loggerFactory));
+ }
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ _options = options.Value;
+ _logger = loggerFactory.CreateLogger();
+ _next = next;
+ }
+
+ public Task Invoke(HttpContext context)
+ {
+ ApplyForwarders(context);
+ return _next(context);
+ }
+
+ public void ApplyForwarders(HttpContext context)
+ {
+ // Gather expected headers. Enabled headers must have the same number of entries.
+ string[] forwardedFor = null, forwardedProto = null, forwardedHost = null;
+ bool checkFor = false, checkProto = false, checkHost = false;
+ int entryCount = 0;
+
+ if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedFor) == ForwardedHeaders.XForwardedFor)
+ {
+ checkFor = true;
+ forwardedFor = context.Request.Headers.GetCommaSeparatedValues(XForwardedForHeaderName);
+ if (StringValues.IsNullOrEmpty(forwardedFor))
+ {
+ return;
+ }
+ entryCount = forwardedFor.Length;
+ }
+
+ if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedProto) == ForwardedHeaders.XForwardedProto)
+ {
+ checkProto = true;
+ forwardedProto = context.Request.Headers.GetCommaSeparatedValues(XForwardedProtoHeaderName);
+ if (StringValues.IsNullOrEmpty(forwardedProto))
+ {
+ return;
+ }
+ if (checkFor && forwardedFor.Length != forwardedProto.Length)
+ {
+ _logger.LogDebug(1, "Parameter count mismatch between X-Forwarded-For and X-Forwarded-Proto.");
+ return;
+ }
+ entryCount = forwardedProto.Length;
+ }
+
+ if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedHost) == ForwardedHeaders.XForwardedHost)
+ {
+ checkHost = true;
+ forwardedHost = context.Request.Headers.GetCommaSeparatedValues(XForwardedHostHeaderName);
+ if (StringValues.IsNullOrEmpty(forwardedHost))
+ {
+ return;
+ }
+ if ((checkFor && forwardedFor.Length != forwardedHost.Length)
+ || (checkProto && forwardedProto.Length != forwardedHost.Length))
+ {
+ _logger.LogDebug(1, "Parameter count mismatch between X-Forwarded-Host and X-Forwarded-For or X-Forwarded-Proto.");
+ return;
+ }
+ entryCount = forwardedHost.Length;
+ }
+
+ // Apply ForwardLimit, if any
+ int offset = 0;
+ if (_options.ForwardLimit.HasValue && entryCount > _options.ForwardLimit)
+ {
+ offset = entryCount - _options.ForwardLimit.Value;
+ entryCount = _options.ForwardLimit.Value;
+ }
+
+ // Group the data together.
+ var sets = new List(entryCount);
+ for (int i = 0; i < entryCount; i++)
+ {
+ var set = new SetOfForwarders();
+ if (checkFor)
+ {
+ set.IpAndPortText = forwardedFor[offset + i];
+ }
+ if (checkProto)
+ {
+ set.Scheme = forwardedProto[offset + i];
+ }
+ if (checkHost)
+ {
+ set.Host = forwardedHost[offset + i];
+ }
+ sets.Add(set);
+ }
+ // They get processed in reverse order, right to left.
+ sets.Reverse();
+
+ // Gather initial values
+ var connection = context.Connection;
+ var request = context.Request;
+ var currentValues = new SetOfForwarders()
+ {
+ RemoteIpAndPort = connection.RemoteIpAddress != null ? new IPEndPoint(connection.RemoteIpAddress, connection.RemotePort) : null,
+ // Host and Scheme initial values are never inspected, no need to set them here.
+ };
+
+ var checkKnownIps = _options.KnownNetworks.Count > 0 || _options.KnownProxies.Count > 0;
+ bool applyChanges = false;
+ int entriesConsumed = 0;
+
+ foreach (var set in sets)
+ {
+ if (checkFor)
+ {
+ // For the first instance, allow remoteIp to be null for servers that don't support it natively.
+ if (currentValues.RemoteIpAndPort != null && checkKnownIps && !CheckKnownAddress(currentValues.RemoteIpAndPort.Address))
+ {
+ // Stop at the first unknown remote IP, but still apply changes processed so far.
+ _logger.LogDebug(1, $"Unknown proxy: {currentValues.RemoteIpAndPort}");
+ break;
+ }
+ if (!IPEndPointParser.TryParse(set.IpAndPortText, out set.RemoteIpAndPort))
+ {
+ _logger.LogDebug(2, $"Failed to parse forwarded IPAddress: {currentValues.IpAndPortText}");
+ return;
+ }
+ }
+
+ if (checkProto)
+ {
+ if (string.IsNullOrEmpty(set.Scheme))
+ {
+ _logger.LogDebug(3, $"Failed to parse forwarded scheme: {set.Scheme}");
+ return;
+ }
+ }
+
+ if (checkHost)
+ {
+ if (string.IsNullOrEmpty(set.Host))
+ {
+ _logger.LogDebug(4, $"Failed to parse forwarded host: {set.Host}");
+ return;
+ }
+ }
+
+ applyChanges = true;
+ currentValues = set;
+ entriesConsumed++;
+ }
+
+ if (applyChanges)
+ {
+ if (checkFor)
+ {
+ if (connection.RemoteIpAddress != null)
+ {
+ // Save the original
+ request.Headers[XOriginalForName] = new IPEndPoint(connection.RemoteIpAddress, connection.RemotePort).ToString();
+ }
+ if (forwardedFor.Length > entriesConsumed)
+ {
+ // Truncate the consumed header values
+ request.Headers[XForwardedForHeaderName] = forwardedFor.Take(forwardedFor.Length - entriesConsumed).ToArray();
+ }
+ else
+ {
+ // All values were consumed
+ request.Headers.Remove(XForwardedForHeaderName);
+ }
+ connection.RemoteIpAddress = currentValues.RemoteIpAndPort.Address;
+ connection.RemotePort = currentValues.RemoteIpAndPort.Port;
+ }
+
+ if (checkProto)
+ {
+ // Save the original
+ request.Headers[XOriginalProtoName] = request.Scheme;
+ if (forwardedProto.Length > entriesConsumed)
+ {
+ // Truncate the consumed header values
+ request.Headers[XForwardedProtoHeaderName] = forwardedProto.Take(forwardedProto.Length - entriesConsumed).ToArray();
+ }
+ else
+ {
+ // All values were consumed
+ request.Headers.Remove(XForwardedProtoHeaderName);
+ }
+ request.Scheme = currentValues.Scheme;
+ }
+
+ if (checkHost)
+ {
+ // Save the original
+ request.Headers[XOriginalHostName] = request.Host.ToString();
+ if (forwardedHost.Length > entriesConsumed)
+ {
+ // Truncate the consumed header values
+ request.Headers[XForwardedHostHeaderName] = forwardedHost.Take(forwardedHost.Length - entriesConsumed).ToArray();
+ }
+ else
+ {
+ // All values were consumed
+ request.Headers.Remove(XForwardedHostHeaderName);
+ }
+ request.Host = HostString.FromUriComponent(currentValues.Host);
+ }
+ }
+ }
+
+ private bool CheckKnownAddress(IPAddress address)
+ {
+ if (_options.KnownProxies.Contains(address))
+ {
+ return true;
+ }
+ foreach (var network in _options.KnownNetworks)
+ {
+ if (network.Contains(address))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private class SetOfForwarders
+ {
+ public string IpAndPortText;
+ public IPEndPoint RemoteIpAndPort;
+ public string Host;
+ public string Scheme;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs
new file mode 100644
index 0000000000..2802e79359
--- /dev/null
+++ b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs
@@ -0,0 +1,34 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Net;
+using Microsoft.AspNetCore.HttpOverrides;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ public class ForwardedHeadersOptions
+ {
+ ///
+ /// Identifies which forwarders should be processed.
+ ///
+ public ForwardedHeaders ForwardedHeaders { get; set; }
+
+ ///
+ /// Limits the number of entries in the headers that will be processed. The default value is 1.
+ /// Set to null to disable the limit, but this should only be done if
+ /// KnownProxies or KnownNetworks are configured.
+ ///
+ public int? ForwardLimit { get; set; } = 1;
+
+ ///
+ /// Addresses of known proxies to accept forwarded headers from.
+ ///
+ public IList KnownProxies { get; } = new List() { IPAddress.IPv6Loopback };
+
+ ///
+ /// Address ranges of known proxies to accept forwarded headers from.
+ ///
+ public IList KnownNetworks { get; } = new List() { new IPNetwork(IPAddress.Loopback, 8) };
+ }
+}
diff --git a/src/Microsoft.AspNetCore.HttpOverrides/IPNetwork.cs b/src/Microsoft.AspNetCore.HttpOverrides/IPNetwork.cs
new file mode 100644
index 0000000000..034e5754b6
--- /dev/null
+++ b/src/Microsoft.AspNetCore.HttpOverrides/IPNetwork.cs
@@ -0,0 +1,67 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Net;
+
+namespace Microsoft.AspNetCore.HttpOverrides
+{
+ public class IPNetwork
+ {
+ public IPNetwork(IPAddress prefix, int prefixLength)
+ {
+ Prefix = prefix;
+ PrefixLength = prefixLength;
+ PrefixBytes = Prefix.GetAddressBytes();
+ Mask = CreateMask();
+ }
+
+ public IPAddress Prefix { get; }
+
+ private byte[] PrefixBytes { get; }
+
+ ///
+ /// The CIDR notation of the subnet mask
+ ///
+ public int PrefixLength { get; }
+
+ private byte[] Mask { get; }
+
+ public bool Contains(IPAddress address)
+ {
+ if (Prefix.AddressFamily != address.AddressFamily)
+ {
+ return false;
+ }
+
+ var addressBytes = address.GetAddressBytes();
+ for (int i = 0; i < PrefixBytes.Length && Mask[i] != 0; i++)
+ {
+ if (PrefixBytes[i] != (addressBytes[i] & Mask[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private byte[] CreateMask()
+ {
+ var mask = new byte[PrefixBytes.Length];
+ int remainingBits = PrefixLength;
+ int i = 0;
+ while (remainingBits >= 8)
+ {
+ mask[i] = 0xFF;
+ i++;
+ remainingBits -= 8;
+ }
+ if (remainingBits > 0)
+ {
+ mask[i] = (byte)(0xFF << (8 - remainingBits));
+ }
+
+ return mask;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.HttpOverrides/IPEndPointParser.cs b/src/Microsoft.AspNetCore.HttpOverrides/Internal/IPEndPointParser.cs
similarity index 97%
rename from src/Microsoft.AspNetCore.HttpOverrides/IPEndPointParser.cs
rename to src/Microsoft.AspNetCore.HttpOverrides/Internal/IPEndPointParser.cs
index 9bdd0a66cf..d33b44e72b 100644
--- a/src/Microsoft.AspNetCore.HttpOverrides/IPEndPointParser.cs
+++ b/src/Microsoft.AspNetCore.HttpOverrides/Internal/IPEndPointParser.cs
@@ -3,7 +3,7 @@
using System.Net;
-namespace Microsoft.AspNetCore.HttpOverrides
+namespace Microsoft.AspNetCore.HttpOverrides.Internal
{
public static class IPEndPointParser
{
diff --git a/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderMiddleware.cs b/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderMiddleware.cs
deleted file mode 100644
index e23a883031..0000000000
--- a/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderMiddleware.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Net;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Options;
-
-namespace Microsoft.AspNetCore.HttpOverrides
-{
- public class OverrideHeaderMiddleware
- {
- private const string XForwardedForHeaderName = "X-Forwarded-For";
- private const string XForwardedHostHeaderName = "X-Forwarded-Host";
- private const string XForwardedProtoHeaderName = "X-Forwarded-Proto";
- private const string XOriginalForName = "X-Original-For";
- private const string XOriginalHostName = "X-Original-Host";
- private const string XOriginalProtoName = "X-Original-Proto";
- private readonly OverrideHeaderOptions _options;
- private readonly RequestDelegate _next;
-
- public OverrideHeaderMiddleware(RequestDelegate next, IOptions options)
- {
- if (next == null)
- {
- throw new ArgumentNullException(nameof(next));
- }
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
-
- _options = options.Value;
- _next = next;
- }
-
- public Task Invoke(HttpContext context)
- {
- UpdateRemoteIp(context);
-
- UpdateHost(context);
-
- UpdateScheme(context);
-
- return _next(context);
- }
-
- private void UpdateRemoteIp(HttpContext context)
- {
- if ((_options.ForwardedOptions & ForwardedHeaders.XForwardedFor) != 0)
- {
- var xForwardedForHeaderValue = context.Request.Headers.GetCommaSeparatedValues(XForwardedForHeaderName);
- if (xForwardedForHeaderValue != null && xForwardedForHeaderValue.Length > 0)
- {
- IPEndPoint endpoint;
- if (IPEndPointParser.TryParse(xForwardedForHeaderValue[0], out endpoint))
- {
- var connection = context.Connection;
- var remoteIP = connection.RemoteIpAddress;
- if (remoteIP != null)
- {
- var remoteIPString = new IPEndPoint(remoteIP, connection.RemotePort).ToString();
- context.Request.Headers[XOriginalForName] = remoteIPString;
- }
- connection.RemoteIpAddress = endpoint.Address;
- connection.RemotePort = endpoint.Port;
- }
- }
- }
- }
-
- private void UpdateHost(HttpContext context)
- {
- if ((_options.ForwardedOptions & ForwardedHeaders.XForwardedHost) != 0)
- {
- var xForwardHostHeaderValue = context.Request.Headers[XForwardedHostHeaderName];
- if (!string.IsNullOrEmpty(xForwardHostHeaderValue))
- {
- var hostString = context.Request.Host.ToString();
- if (!string.IsNullOrEmpty(hostString))
- {
- context.Request.Headers[XOriginalHostName] = hostString;
- }
- context.Request.Host = HostString.FromUriComponent(xForwardHostHeaderValue);
- }
- }
- }
-
- private void UpdateScheme(HttpContext context)
- {
- if ((_options.ForwardedOptions & ForwardedHeaders.XForwardedProto) != 0)
- {
- var xForwardProtoHeaderValue = context.Request.Headers[XForwardedProtoHeaderName];
- if (!string.IsNullOrEmpty(xForwardProtoHeaderValue))
- {
- if (!string.IsNullOrEmpty(context.Request.Scheme))
- {
- context.Request.Headers[XOriginalProtoName] = context.Request.Scheme;
- }
- context.Request.Scheme = xForwardProtoHeaderValue;
- }
- }
- }
- }
-}
diff --git a/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderOptions.cs b/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderOptions.cs
deleted file mode 100644
index 011c564813..0000000000
--- a/src/Microsoft.AspNetCore.HttpOverrides/OverrideHeaderOptions.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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.HttpOverrides;
-
-namespace Microsoft.AspNetCore.Builder
-{
- public class OverrideHeaderOptions
- {
- public ForwardedHeaders ForwardedOptions { get; set; }
- }
-}
diff --git a/src/Microsoft.AspNetCore.HttpOverrides/project.json b/src/Microsoft.AspNetCore.HttpOverrides/project.json
index 78d4310cab..a37a16f335 100644
--- a/src/Microsoft.AspNetCore.HttpOverrides/project.json
+++ b/src/Microsoft.AspNetCore.HttpOverrides/project.json
@@ -1,5 +1,5 @@
{
- "version": "0.1.0-*",
+ "version": "1.0.0-*",
"compilationOptions": {
"warningsAsErrors": true,
"keyFile": "../../tools/Key.snk"
@@ -11,6 +11,7 @@
},
"dependencies": {
"Microsoft.AspNetCore.Http.Extensions": "1.0.0-*",
+ "Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
"Microsoft.Extensions.Options": "1.0.0-*"
},
"frameworks": {
diff --git a/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs b/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs
new file mode 100644
index 0000000000..6632a1bba6
--- /dev/null
+++ b/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs
@@ -0,0 +1,522 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
+using Xunit;
+
+namespace Microsoft.AspNetCore.HttpOverrides
+{
+ public class ForwardedHeadersMiddlewareTests
+ {
+ [Fact]
+ public async Task XForwardedForDefaultSettingsChangeRemoteIpAndPort()
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseForwardedHeaders(new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.XForwardedFor
+ });
+ app.Run(context =>
+ {
+ 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"));
+ assertsExecuted = true;
+ return Task.FromResult(0);
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.Add("X-Forwarded-For", "11.111.111.11:9090");
+ await server.CreateClient().SendAsync(req);
+ Assert.True(assertsExecuted);
+ }
+
+ [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)
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.Use((context, next) =>
+ {
+ context.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1");
+ context.Connection.RemotePort = 99;
+ return next();
+ });
+ app.UseForwardedHeaders(new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.XForwardedFor,
+ ForwardLimit = limit,
+ });
+ app.Run(context =>
+ {
+ 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"]);
+ assertsExecuted = true;
+ return Task.FromResult(0);
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.TryAddWithoutValidation("X-Forwarded-For", header);
+ await server.CreateClient().SendAsync(req);
+ Assert.True(assertsExecuted);
+ }
+
+ [Theory]
+ [InlineData(1, "11.111.111.11:12345", "11.111.111.11", 12345, "")]
+ [InlineData(10, "11.111.111.11:12345", "11.111.111.11", 12345, "")]
+ [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12:23456")]
+ [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "")]
+ [InlineData(10, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "")]
+ [InlineData(10, "12.112.112.12.23456, 11.111.111.11:12345", "10.0.0.1", 99, "12.112.112.12.23456, 11.111.111.11:12345")] // Invalid
+ [InlineData(10, "13.113.113.13:34567, 12.112.112.12.23456, 11.111.111.11:12345", "10.0.0.1", 99,
+ "13.113.113.13:34567, 12.112.112.12.23456, 11.111.111.11:12345")] // Invalid
+ [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")]
+ [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "13.113.113.13", 34567, "")]
+ public async Task XForwardedForForwardLimit(int limit, string header, string expectedIp, int expectedPort, string remainingHeader)
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.Use((context, next) =>
+ {
+ context.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1");
+ context.Connection.RemotePort = 99;
+ return next();
+ });
+ var options = new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.XForwardedFor,
+ ForwardLimit = limit,
+ };
+ options.KnownProxies.Clear();
+ options.KnownNetworks.Clear();
+ app.UseForwardedHeaders(options);
+ app.Run(context =>
+ {
+ Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString());
+ Assert.Equal(expectedPort, context.Connection.RemotePort);
+ Assert.Equal(remainingHeader, context.Request.Headers["X-Forwarded-For"].ToString());
+ assertsExecuted = true;
+ return Task.FromResult(0);
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.TryAddWithoutValidation("X-Forwarded-For", header);
+ await server.CreateClient().SendAsync(req);
+ Assert.True(assertsExecuted);
+ }
+
+ [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)
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.Use((context, next) =>
+ {
+ context.Connection.RemoteIpAddress = IPAddress.Parse(originalIp);
+ context.Connection.RemotePort = 99;
+ return next();
+ });
+ app.UseForwardedHeaders(new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.XForwardedFor,
+ });
+ app.Run(context =>
+ {
+ 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"));
+ }
+ assertsExecuted = true;
+ return Task.FromResult(0);
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.TryAddWithoutValidation("X-Forwarded-For", "10.0.0.1:1234");
+ await server.CreateClient().SendAsync(req);
+ Assert.True(assertsExecuted);
+ }
+
+ [Theory]
+ [InlineData(1, "11.111.111.11:12345", "20.0.0.1", "10.0.0.1", 99)]
+ [InlineData(1, "", "10.0.0.1", "10.0.0.1", 99)]
+ [InlineData(1, "11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345)]
+ [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345)]
+ [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345)]
+ [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)]
+ [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)]
+ [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)]
+ [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)]
+ [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)]
+ [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", "10.0.0.1", 99)] // 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", "10.0.0.1", 99)] // Invalid 3rd IP
+ public async Task XForwardedForForwardKnownIps(int limit, string header, string knownIPs, string expectedIp, int expectedPort)
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.Use((context, next) =>
+ {
+ context.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1");
+ context.Connection.RemotePort = 99;
+ return next();
+ });
+ var options = new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.XForwardedFor,
+ ForwardLimit = limit,
+ };
+ foreach (var ip in knownIPs.Split(',').Select(text => IPAddress.Parse(text)))
+ {
+ options.KnownProxies.Add(ip);
+ }
+ app.UseForwardedHeaders(options);
+ app.Run(context =>
+ {
+ Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString());
+ Assert.Equal(expectedPort, context.Connection.RemotePort);
+ assertsExecuted = true;
+ return Task.FromResult(0);
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.TryAddWithoutValidation("X-Forwarded-For", header);
+ await server.CreateClient().SendAsync(req);
+ Assert.True(assertsExecuted);
+ }
+
+ [Fact]
+ public async Task XForwardedForOverrideBadIpDoesntChangeRemoteIp()
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseForwardedHeaders(new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.XForwardedFor
+ });
+ app.Run(context =>
+ {
+ Assert.Null(context.Connection.RemoteIpAddress);
+ assertsExecuted = true;
+ return Task.FromResult(0);
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.Add("X-Forwarded-For", "BAD-IP");
+ await server.CreateClient().SendAsync(req);
+ Assert.True(assertsExecuted);
+ }
+
+ [Fact]
+ public async Task XForwardedHostOverrideChangesRequestHost()
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseForwardedHeaders(new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.XForwardedHost
+ });
+ app.Run(context =>
+ {
+ Assert.Equal("testhost", context.Request.Host.ToString());
+ assertsExecuted = true;
+ return Task.FromResult(0);
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.Add("X-Forwarded-Host", "testhost");
+ await server.CreateClient().SendAsync(req);
+ 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)
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseForwardedHeaders(new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.XForwardedProto,
+ ForwardLimit = limit,
+ });
+ app.Run(context =>
+ {
+ Assert.Equal(expected, context.Request.Scheme);
+ assertsExecuted = true;
+ return Task.FromResult(0);
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.Add("X-Forwarded-Proto", header);
+ await server.CreateClient().SendAsync(req);
+ 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", "http")]
+ public async Task XForwardedProtoOverrideLimitedByXForwardedForCount(int limit, string protoHeader, string forHeader, string expected)
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseForwardedHeaders(new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor,
+ ForwardLimit = limit,
+ });
+ app.Run(context =>
+ {
+ Assert.Equal(expected, context.Request.Scheme);
+ assertsExecuted = true;
+ return Task.FromResult(0);
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.Add("X-Forwarded-Proto", protoHeader);
+ req.Headers.Add("X-Forwarded-For", forHeader);
+ await server.CreateClient().SendAsync(req);
+ Assert.True(assertsExecuted);
+ }
+
+ [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")]
+ [InlineData("h2, h1", "E::, D::", "F::", true, "http")]
+ public async Task XForwardedProtoOverrideLimitedByLoopback(string protoHeader, string forHeader, string remoteIp, bool loopback, string expected)
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.Use((context, next) =>
+ {
+ context.Connection.RemoteIpAddress = IPAddress.Parse(remoteIp);
+ return next();
+ });
+ var options = new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor,
+ ForwardLimit = 5,
+ };
+ if (!loopback)
+ {
+ options.KnownNetworks.Clear();
+ options.KnownProxies.Clear();
+ }
+ app.UseForwardedHeaders(options);
+ app.Run(context =>
+ {
+ Assert.Equal(expected, context.Request.Scheme);
+ assertsExecuted = true;
+ return Task.FromResult(0);
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.Add("X-Forwarded-Proto", protoHeader);
+ req.Headers.Add("X-Forwarded-For", forHeader);
+ await server.CreateClient().SendAsync(req);
+ Assert.True(assertsExecuted);
+ }
+
+ [Fact]
+ public void AllForwardsDisabledByDefault()
+ {
+ var options = new ForwardedHeadersOptions();
+ Assert.True(options.ForwardedHeaders == ForwardedHeaders.None);
+ Assert.Equal(1, options.ForwardLimit);
+ Assert.Equal(1, options.KnownNetworks.Count());
+ Assert.Equal(1, options.KnownProxies.Count());
+ }
+
+ [Fact]
+ public async Task AllForwardsEnabledChangeRequestRemoteIpHostandProtocol()
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseForwardedHeaders(new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.All
+ });
+ app.Run(context =>
+ {
+ Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString());
+ Assert.Equal("testhost", context.Request.Host.ToString());
+ Assert.Equal("Protocol", context.Request.Scheme);
+ assertsExecuted = true;
+ return Task.FromResult(0);
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.Add("X-Forwarded-For", "11.111.111.11");
+ req.Headers.Add("X-Forwarded-Host", "testhost");
+ req.Headers.Add("X-Forwarded-Proto", "Protocol");
+ await server.CreateClient().SendAsync(req);
+ Assert.True(assertsExecuted);
+ }
+
+ [Fact]
+ public async Task AllOptionsDisabledRequestDoesntChange()
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseForwardedHeaders(new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.None
+ });
+ app.Run(context =>
+ {
+ Assert.Null(context.Connection.RemoteIpAddress);
+ Assert.Equal("localhost", context.Request.Host.ToString());
+ Assert.Equal("http", context.Request.Scheme);
+ assertsExecuted = true;
+ return Task.FromResult(0);
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.Add("X-Forwarded-For", "11.111.111.11");
+ req.Headers.Add("X-Forwarded-Host", "otherhost");
+ req.Headers.Add("X-Forwarded-Proto", "Protocol");
+ await server.CreateClient().SendAsync(req);
+ Assert.True(assertsExecuted);
+ }
+
+ [Fact]
+ public async Task PartiallyEnabledForwardsPartiallyChangesRequest()
+ {
+ var assertsExecuted = false;
+
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseForwardedHeaders(new ForwardedHeadersOptions
+ {
+ ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
+ });
+ app.Run(context =>
+ {
+ Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString());
+ Assert.Equal("localhost", context.Request.Host.ToString());
+ Assert.Equal("Protocol", context.Request.Scheme);
+ assertsExecuted = true;
+ return Task.FromResult(0);
+
+ });
+ });
+ var server = new TestServer(builder);
+
+ var req = new HttpRequestMessage(HttpMethod.Get, "");
+ req.Headers.Add("X-Forwarded-For", "11.111.111.11");
+ req.Headers.Add("X-Forwarded-Proto", "Protocol");
+ await server.CreateClient().SendAsync(req);
+ Assert.True(assertsExecuted);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPEndPointParserTest.cs b/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPEndPointParserTest.cs
index 0ba8e134ee..f5106bd2a7 100644
--- a/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPEndPointParserTest.cs
+++ b/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPEndPointParserTest.cs
@@ -4,7 +4,7 @@
using System.Net;
using Xunit;
-namespace Microsoft.AspNetCore.HttpOverrides
+namespace Microsoft.AspNetCore.HttpOverrides.Internal
{
public class IPEndPointParserTests
{
diff --git a/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPNetworkTest.cs b/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPNetworkTest.cs
new file mode 100644
index 0000000000..b3700c583f
--- /dev/null
+++ b/test/Microsoft.AspNetCore.HttpOverrides.Tests/IPNetworkTest.cs
@@ -0,0 +1,33 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Net;
+using Xunit;
+
+namespace Microsoft.AspNetCore.HttpOverrides
+{
+ public class IPNetworkTest
+ {
+ [Theory]
+ [InlineData("10.1.1.0", 8, "10.1.1.10")]
+ [InlineData("174.0.0.0", 7, "175.1.1.10")]
+ [InlineData("10.174.0.0", 15, "10.175.1.10")]
+ [InlineData("10.168.0.0", 14, "10.171.1.10")]
+ public void Contains_Positive(string prefixText, int length, string addressText)
+ {
+ var network = new IPNetwork(IPAddress.Parse(prefixText), length);
+ Assert.True(network.Contains(IPAddress.Parse(addressText)));
+ }
+
+ [Theory]
+ [InlineData("10.1.0.0", 16, "10.2.1.10")]
+ [InlineData("174.0.0.0", 7, "173.1.1.10")]
+ [InlineData("10.174.0.0", 15, "10.173.1.10")]
+ [InlineData("10.168.0.0", 14, "10.172.1.10")]
+ public void Contains_Negative(string prefixText, int length, string addressText)
+ {
+ var network = new IPNetwork(IPAddress.Parse(prefixText), length);
+ Assert.False(network.Contains(IPAddress.Parse(addressText)));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.HttpOverrides.Tests/OverrideHeaderMiddlewareTest.cs b/test/Microsoft.AspNetCore.HttpOverrides.Tests/OverrideHeaderMiddlewareTest.cs
deleted file mode 100644
index b66e57e2cd..0000000000
--- a/test/Microsoft.AspNetCore.HttpOverrides.Tests/OverrideHeaderMiddlewareTest.cs
+++ /dev/null
@@ -1,223 +0,0 @@
-// 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.Net.Http;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.TestHost;
-using Xunit;
-
-namespace Microsoft.AspNetCore.HttpOverrides
-{
- public class OverrideMiddlewareHeaderTests
- {
- [Fact]
- public async Task XForwardedForOverrideChangesRemoteIp()
- {
- var assertsExecuted = false;
-
- var builder = new WebHostBuilder()
- .Configure(app =>
- {
- app.UseOverrideHeaders(new OverrideHeaderOptions
- {
- ForwardedOptions = ForwardedHeaders.XForwardedFor
- });
- app.Run(context =>
- {
- Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString());
- assertsExecuted = true;
- return Task.FromResult(0);
- });
- });
- var server = new TestServer(builder);
-
- var req = new HttpRequestMessage(HttpMethod.Get, "");
- req.Headers.Add("X-Forwarded-For", "11.111.111.11");
- await server.CreateClient().SendAsync(req);
- Assert.True(assertsExecuted);
- }
-
- [Fact]
- public async Task XForwardedForOverrideBadIpDoesntChangeRemoteIp()
- {
- var assertsExecuted = false;
-
- var builder = new WebHostBuilder()
- .Configure(app =>
- {
- app.UseOverrideHeaders(new OverrideHeaderOptions
- {
- ForwardedOptions = ForwardedHeaders.XForwardedFor
- });
- app.Run(context =>
- {
- Assert.Null(context.Connection.RemoteIpAddress);
- assertsExecuted = true;
- return Task.FromResult(0);
- });
- });
- var server = new TestServer(builder);
-
- var req = new HttpRequestMessage(HttpMethod.Get, "");
- req.Headers.Add("X-Forwarded-For", "BAD-IP");
- await server.CreateClient().SendAsync(req);
- Assert.True(assertsExecuted);
- }
-
- [Fact]
- public async Task XForwardedHostOverrideChangesRequestHost()
- {
- var assertsExecuted = false;
-
- var builder = new WebHostBuilder()
- .Configure(app =>
- {
- app.UseOverrideHeaders(new OverrideHeaderOptions
- {
- ForwardedOptions = ForwardedHeaders.XForwardedHost
- });
- app.Run(context =>
- {
- Assert.Equal("testhost", context.Request.Host.ToString());
- assertsExecuted = true;
- return Task.FromResult(0);
- });
- });
- var server = new TestServer(builder);
-
- var req = new HttpRequestMessage(HttpMethod.Get, "");
- req.Headers.Add("X-Forwarded-Host", "testhost");
- await server.CreateClient().SendAsync(req);
- Assert.True(assertsExecuted);
- }
-
- [Fact]
- public async Task XForwardedProtoOverrideChangesRequestProtocol()
- {
- var assertsExecuted = false;
-
- var builder = new WebHostBuilder()
- .Configure(app =>
- {
- app.UseOverrideHeaders(new OverrideHeaderOptions
- {
- ForwardedOptions = ForwardedHeaders.XForwardedProto
- });
- app.Run(context =>
- {
- Assert.Equal("TestProtocol", context.Request.Scheme);
- assertsExecuted = true;
- return Task.FromResult(0);
- });
- });
- var server = new TestServer(builder);
-
- var req = new HttpRequestMessage(HttpMethod.Get, "");
- req.Headers.Add("X-Forwarded-Proto", "TestProtocol");
- await server.CreateClient().SendAsync(req);
- Assert.True(assertsExecuted);
- }
-
- [Fact]
- public void AllForwardsDisabledByDefault()
- {
- var options = new OverrideHeaderOptions();
- Assert.True(options.ForwardedOptions == 0);
- }
-
- [Fact]
- public async Task AllForwardsEnabledChangeRequestRemoteIpHostandProtocol()
- {
- var assertsExecuted = false;
-
- var builder = new WebHostBuilder()
- .Configure(app =>
- {
- app.UseOverrideHeaders(new OverrideHeaderOptions
- {
- ForwardedOptions = ForwardedHeaders.All
- });
- app.Run(context =>
- {
- Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString());
- Assert.Equal("testhost", context.Request.Host.ToString());
- Assert.Equal("Protocol", context.Request.Scheme);
- assertsExecuted = true;
- return Task.FromResult(0);
- });
- });
- var server = new TestServer(builder);
-
- var req = new HttpRequestMessage(HttpMethod.Get, "");
- req.Headers.Add("X-Forwarded-For", "11.111.111.11");
- req.Headers.Add("X-Forwarded-Host", "testhost");
- req.Headers.Add("X-Forwarded-Proto", "Protocol");
- await server.CreateClient().SendAsync(req);
- Assert.True(assertsExecuted);
- }
-
- [Fact]
- public async Task AllOptionsDisabledRequestDoesntChange()
- {
- var assertsExecuted = false;
-
- var builder = new WebHostBuilder()
- .Configure(app =>
- {
- app.UseOverrideHeaders(new OverrideHeaderOptions
- {
- ForwardedOptions = ForwardedHeaders.None
- });
- app.Run(context =>
- {
- Assert.Null(context.Connection.RemoteIpAddress);
- Assert.Equal("localhost", context.Request.Host.ToString());
- Assert.Equal("http", context.Request.Scheme);
- assertsExecuted = true;
- return Task.FromResult(0);
- });
- });
- var server = new TestServer(builder);
-
- var req = new HttpRequestMessage(HttpMethod.Get, "");
- req.Headers.Add("X-Forwarded-For", "11.111.111.11");
- req.Headers.Add("X-Forwarded-Host", "otherhost");
- req.Headers.Add("X-Forwarded-Proto", "Protocol");
- await server.CreateClient().SendAsync(req);
- Assert.True(assertsExecuted);
- }
-
- [Fact]
- public async Task PartiallyEnabledForwardsPartiallyChangesRequest()
- {
- var assertsExecuted = false;
-
- var builder = new WebHostBuilder()
- .Configure(app =>
- {
- app.UseOverrideHeaders(new OverrideHeaderOptions
- {
- ForwardedOptions = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
- });
- app.Run(context =>
- {
- Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString());
- Assert.Equal("localhost", context.Request.Host.ToString());
- Assert.Equal("Protocol", context.Request.Scheme);
- assertsExecuted = true;
- return Task.FromResult(0);
-
- });
- });
- var server = new TestServer(builder);
-
- var req = new HttpRequestMessage(HttpMethod.Get, "");
- req.Headers.Add("X-Forwarded-For", "11.111.111.11");
- req.Headers.Add("X-Forwarded-Proto", "Protocol");
- await server.CreateClient().SendAsync(req);
- Assert.True(assertsExecuted);
- }
- }
-}
diff --git a/test/Microsoft.AspNetCore.HttpOverrides.Tests/project.json b/test/Microsoft.AspNetCore.HttpOverrides.Tests/project.json
index 107fcfb6ab..2729c1bed4 100644
--- a/test/Microsoft.AspNetCore.HttpOverrides.Tests/project.json
+++ b/test/Microsoft.AspNetCore.HttpOverrides.Tests/project.json
@@ -4,7 +4,7 @@
"warningsAsErrors": true
},
"dependencies": {
- "Microsoft.AspNetCore.HttpOverrides": "0.1.0-*",
+ "Microsoft.AspNetCore.HttpOverrides": "1.0.0-*",
"Microsoft.AspNetCore.TestHost": "1.0.0-*",
"Microsoft.Extensions.Logging.Testing": "1.0.0-*",
"xunit": "2.1.0"