Forward custom header names (#102)

* Forward custom header names
This commit is contained in:
Morten Andersen 2016-10-03 19:16:03 +02:00 committed by Chris R
parent 9bc8a83a39
commit 76256446f5
3 changed files with 106 additions and 19 deletions

View File

@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.HttpOverrides
{
/// <summary>
/// Default values related to <see cref="ForwardedHeadersMiddleware"/> middleware
/// </summary>
/// <seealso cref="Microsoft.AspNetCore.Builder.ForwardedHeadersOptions"/>
public static class ForwardedHeadersDefaults
{
/// <summary>
/// X-Forwarded-For
/// </summary>
public static string XForwardedForHeaderName { get; } = "X-Forwarded-For";
/// <summary>
/// X-Forwarded-Host
/// </summary>
public static string XForwardedHostHeaderName { get; } = "X-Forwarded-Host";
/// <summary>
/// X-Forwarded-Proto
/// </summary>
public static string XForwardedProtoHeaderName { get; } = "X-Forwarded-Proto";
/// <summary>
/// X-Original-For
/// </summary>
public static string XOriginalForHeaderName { get; } = "X-Original-For";
/// <summary>
/// X-Original-Host
/// </summary>
public static string XOriginalHostHeaderName { get; } = "X-Original-Host";
/// <summary>
/// X-Original-Proto
/// </summary>
public static string XOriginalProtoHeaderName { get; } = "X-Original-Proto";
}
}

View File

@ -17,13 +17,6 @@ namespace Microsoft.AspNetCore.HttpOverrides
{ {
public class ForwardedHeadersMiddleware 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 ForwardedHeadersOptions _options;
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -43,11 +36,27 @@ namespace Microsoft.AspNetCore.HttpOverrides
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
} }
// Make sure required options is not null or whitespace
EnsureOptionNotNullorWhitespace(options.Value.ForwardedForHeaderName, nameof(options.Value.ForwardedForHeaderName));
EnsureOptionNotNullorWhitespace(options.Value.ForwardedHostHeaderName, nameof(options.Value.ForwardedHostHeaderName));
EnsureOptionNotNullorWhitespace(options.Value.ForwardedProtoHeaderName, nameof(options.Value.ForwardedProtoHeaderName));
EnsureOptionNotNullorWhitespace(options.Value.OriginalForHeaderName, nameof(options.Value.OriginalForHeaderName));
EnsureOptionNotNullorWhitespace(options.Value.OriginalHostHeaderName, nameof(options.Value.OriginalHostHeaderName));
EnsureOptionNotNullorWhitespace(options.Value.OriginalProtoHeaderName, nameof(options.Value.OriginalProtoHeaderName));
_options = options.Value; _options = options.Value;
_logger = loggerFactory.CreateLogger<ForwardedHeadersMiddleware>(); _logger = loggerFactory.CreateLogger<ForwardedHeadersMiddleware>();
_next = next; _next = next;
} }
private static void EnsureOptionNotNullorWhitespace(string value, string propertyName)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException($"options.{propertyName} is required", "options");
}
}
public Task Invoke(HttpContext context) public Task Invoke(HttpContext context)
{ {
ApplyForwarders(context); ApplyForwarders(context);
@ -64,14 +73,14 @@ namespace Microsoft.AspNetCore.HttpOverrides
if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedFor) == ForwardedHeaders.XForwardedFor) if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedFor) == ForwardedHeaders.XForwardedFor)
{ {
checkFor = true; checkFor = true;
forwardedFor = context.Request.Headers.GetCommaSeparatedValues(XForwardedForHeaderName); forwardedFor = context.Request.Headers.GetCommaSeparatedValues(_options.ForwardedForHeaderName);
entryCount = Math.Max(forwardedFor.Length, entryCount); entryCount = Math.Max(forwardedFor.Length, entryCount);
} }
if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedProto) == ForwardedHeaders.XForwardedProto) if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedProto) == ForwardedHeaders.XForwardedProto)
{ {
checkProto = true; checkProto = true;
forwardedProto = context.Request.Headers.GetCommaSeparatedValues(XForwardedProtoHeaderName); forwardedProto = context.Request.Headers.GetCommaSeparatedValues(_options.ForwardedProtoHeaderName);
if (_options.RequireHeaderSymmetry && checkFor && forwardedFor.Length != forwardedProto.Length) if (_options.RequireHeaderSymmetry && checkFor && forwardedFor.Length != forwardedProto.Length)
{ {
_logger.LogWarning(1, "Parameter count mismatch between X-Forwarded-For and X-Forwarded-Proto."); _logger.LogWarning(1, "Parameter count mismatch between X-Forwarded-For and X-Forwarded-Proto.");
@ -83,7 +92,7 @@ namespace Microsoft.AspNetCore.HttpOverrides
if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedHost) == ForwardedHeaders.XForwardedHost) if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedHost) == ForwardedHeaders.XForwardedHost)
{ {
checkHost = true; checkHost = true;
forwardedHost = context.Request.Headers.GetCommaSeparatedValues(XForwardedHostHeaderName); forwardedHost = context.Request.Headers.GetCommaSeparatedValues(_options.ForwardedHostHeaderName);
if (_options.RequireHeaderSymmetry if (_options.RequireHeaderSymmetry
&& ((checkFor && forwardedFor.Length != forwardedHost.Length) && ((checkFor && forwardedFor.Length != forwardedHost.Length)
|| (checkProto && forwardedProto.Length != forwardedHost.Length))) || (checkProto && forwardedProto.Length != forwardedHost.Length)))
@ -198,17 +207,17 @@ namespace Microsoft.AspNetCore.HttpOverrides
if (connection.RemoteIpAddress != null) if (connection.RemoteIpAddress != null)
{ {
// Save the original // Save the original
request.Headers[XOriginalForName] = new IPEndPoint(connection.RemoteIpAddress, connection.RemotePort).ToString(); request.Headers[_options.OriginalForHeaderName] = new IPEndPoint(connection.RemoteIpAddress, connection.RemotePort).ToString();
} }
if (forwardedFor.Length > entriesConsumed) if (forwardedFor.Length > entriesConsumed)
{ {
// Truncate the consumed header values // Truncate the consumed header values
request.Headers[XForwardedForHeaderName] = forwardedFor.Take(forwardedFor.Length - entriesConsumed).ToArray(); request.Headers[_options.ForwardedForHeaderName] = forwardedFor.Take(forwardedFor.Length - entriesConsumed).ToArray();
} }
else else
{ {
// All values were consumed // All values were consumed
request.Headers.Remove(XForwardedForHeaderName); request.Headers.Remove(_options.ForwardedForHeaderName);
} }
connection.RemoteIpAddress = currentValues.RemoteIpAndPort.Address; connection.RemoteIpAddress = currentValues.RemoteIpAndPort.Address;
connection.RemotePort = currentValues.RemoteIpAndPort.Port; connection.RemotePort = currentValues.RemoteIpAndPort.Port;
@ -217,16 +226,16 @@ namespace Microsoft.AspNetCore.HttpOverrides
if (checkProto && currentValues.Scheme != null) if (checkProto && currentValues.Scheme != null)
{ {
// Save the original // Save the original
request.Headers[XOriginalProtoName] = request.Scheme; request.Headers[_options.OriginalProtoHeaderName] = request.Scheme;
if (forwardedProto.Length > entriesConsumed) if (forwardedProto.Length > entriesConsumed)
{ {
// Truncate the consumed header values // Truncate the consumed header values
request.Headers[XForwardedProtoHeaderName] = forwardedProto.Take(forwardedProto.Length - entriesConsumed).ToArray(); request.Headers[_options.ForwardedProtoHeaderName] = forwardedProto.Take(forwardedProto.Length - entriesConsumed).ToArray();
} }
else else
{ {
// All values were consumed // All values were consumed
request.Headers.Remove(XForwardedProtoHeaderName); request.Headers.Remove(_options.ForwardedProtoHeaderName);
} }
request.Scheme = currentValues.Scheme; request.Scheme = currentValues.Scheme;
} }
@ -234,16 +243,16 @@ namespace Microsoft.AspNetCore.HttpOverrides
if (checkHost && currentValues.Host != null) if (checkHost && currentValues.Host != null)
{ {
// Save the original // Save the original
request.Headers[XOriginalHostName] = request.Host.ToString(); request.Headers[_options.OriginalHostHeaderName] = request.Host.ToString();
if (forwardedHost.Length > entriesConsumed) if (forwardedHost.Length > entriesConsumed)
{ {
// Truncate the consumed header values // Truncate the consumed header values
request.Headers[XForwardedHostHeaderName] = forwardedHost.Take(forwardedHost.Length - entriesConsumed).ToArray(); request.Headers[_options.ForwardedHostHeaderName] = forwardedHost.Take(forwardedHost.Length - entriesConsumed).ToArray();
} }
else else
{ {
// All values were consumed // All values were consumed
request.Headers.Remove(XForwardedHostHeaderName); request.Headers.Remove(_options.ForwardedHostHeaderName);
} }
request.Host = HostString.FromUriComponent(currentValues.Host); request.Host = HostString.FromUriComponent(currentValues.Host);
} }

View File

@ -9,6 +9,42 @@ namespace Microsoft.AspNetCore.Builder
{ {
public class ForwardedHeadersOptions public class ForwardedHeadersOptions
{ {
/// <summary>
/// Use this header instead of <see cref="ForwardedHeadersDefaults.XForwardedForHeaderName"/>
/// </summary>
/// <seealso cref="Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersDefaults"/>
public string ForwardedForHeaderName { get; set; } = ForwardedHeadersDefaults.XForwardedForHeaderName;
/// <summary>
/// Use this header instead of <see cref="ForwardedHeadersDefaults.XForwardedHostHeaderName"/>
/// </summary>
/// <seealso cref="ForwardedHeadersDefaults"/>
public string ForwardedHostHeaderName { get; set; } = ForwardedHeadersDefaults.XForwardedHostHeaderName;
/// <summary>
/// Use this header instead of <see cref="ForwardedHeadersDefaults.XForwardedProtoHeaderName"/>
/// </summary>
/// <seealso cref="ForwardedHeadersDefaults"/>
public string ForwardedProtoHeaderName { get; set; } = ForwardedHeadersDefaults.XForwardedProtoHeaderName;
/// <summary>
/// Use this header instead of <see cref="ForwardedHeadersDefaults.XOriginalForHeaderName"/>
/// </summary>
/// <seealso cref="ForwardedHeadersDefaults"/>
public string OriginalForHeaderName { get; set; } = ForwardedHeadersDefaults.XOriginalForHeaderName;
/// <summary>
/// Use this header instead of <see cref="ForwardedHeadersDefaults.XOriginalHostHeaderName"/>
/// </summary>
/// <seealso cref="ForwardedHeadersDefaults"/>
public string OriginalHostHeaderName { get; set; } = ForwardedHeadersDefaults.XOriginalHostHeaderName;
/// <summary>
/// Use this header instead of <see cref="ForwardedHeadersDefaults.XOriginalProtoHeaderName"/>
/// </summary>
/// <seealso cref="ForwardedHeadersDefaults"/>
public string OriginalProtoHeaderName { get; set; } = ForwardedHeadersDefaults.XOriginalProtoHeaderName;
/// <summary> /// <summary>
/// Identifies which forwarders should be processed. /// Identifies which forwarders should be processed.
/// </summary> /// </summary>