#37 Make the header length match optional.

This commit is contained in:
Chris R 2016-05-19 14:30:54 -07:00
parent 367eec812c
commit 5fe48ada42
4 changed files with 96 additions and 43 deletions

View File

@ -65,75 +65,61 @@ namespace Microsoft.AspNetCore.HttpOverrides
{ {
checkFor = true; checkFor = true;
forwardedFor = context.Request.Headers.GetCommaSeparatedValues(XForwardedForHeaderName); forwardedFor = context.Request.Headers.GetCommaSeparatedValues(XForwardedForHeaderName);
if (StringValues.IsNullOrEmpty(forwardedFor)) entryCount = Math.Max(forwardedFor.Length, entryCount);
{
return;
}
entryCount = forwardedFor.Length;
} }
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(XForwardedProtoHeaderName);
if (StringValues.IsNullOrEmpty(forwardedProto)) if (_options.RequireHeaderSymmetry && checkFor && forwardedFor.Length != forwardedProto.Length)
{
return;
}
if (checkFor && forwardedFor.Length != forwardedProto.Length)
{ {
_logger.LogDebug(1, "Parameter count mismatch between X-Forwarded-For and X-Forwarded-Proto."); _logger.LogDebug(1, "Parameter count mismatch between X-Forwarded-For and X-Forwarded-Proto.");
return; return;
} }
entryCount = forwardedProto.Length; entryCount = Math.Max(forwardedProto.Length, entryCount);
} }
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(XForwardedHostHeaderName);
if (StringValues.IsNullOrEmpty(forwardedHost)) if (_options.RequireHeaderSymmetry
{ && ((checkFor && forwardedFor.Length != forwardedHost.Length)
return; || (checkProto && forwardedProto.Length != forwardedHost.Length)))
}
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."); _logger.LogDebug(1, "Parameter count mismatch between X-Forwarded-Host and X-Forwarded-For or X-Forwarded-Proto.");
return; return;
} }
entryCount = forwardedHost.Length; entryCount = Math.Max(forwardedHost.Length, entryCount);
} }
// Apply ForwardLimit, if any // Apply ForwardLimit, if any
int offset = 0;
if (_options.ForwardLimit.HasValue && entryCount > _options.ForwardLimit) if (_options.ForwardLimit.HasValue && entryCount > _options.ForwardLimit)
{ {
offset = entryCount - _options.ForwardLimit.Value;
entryCount = _options.ForwardLimit.Value; entryCount = _options.ForwardLimit.Value;
} }
// Group the data together. // Group the data together.
var sets = new List<SetOfForwarders>(entryCount); var sets = new SetOfForwarders[entryCount];
for (int i = 0; i < entryCount; i++) for (int i = 0; i < sets.Length; i++)
{ {
// They get processed in reverse order, right to left.
var set = new SetOfForwarders(); var set = new SetOfForwarders();
if (checkFor) if (checkFor && i < forwardedFor.Length)
{ {
set.IpAndPortText = forwardedFor[offset + i]; set.IpAndPortText = forwardedFor[forwardedFor.Length - i - 1];
} }
if (checkProto) if (checkProto && i < forwardedProto.Length)
{ {
set.Scheme = forwardedProto[offset + i]; set.Scheme = forwardedProto[forwardedProto.Length - i - 1];
} }
if (checkHost) if (checkHost && i < forwardedHost.Length)
{ {
set.Host = forwardedHost[offset + i]; set.Host = forwardedHost[forwardedHost.Length - i - 1];
} }
sets.Add(set); sets[i] = set;
} }
// They get processed in reverse order, right to left.
sets.Reverse();
// Gather initial values // Gather initial values
var connection = context.Connection; var connection = context.Connection;
@ -148,8 +134,9 @@ namespace Microsoft.AspNetCore.HttpOverrides
bool applyChanges = false; bool applyChanges = false;
int entriesConsumed = 0; int entriesConsumed = 0;
foreach (var set in sets) for ( ; entriesConsumed < sets.Length; entriesConsumed++)
{ {
var set = sets[entriesConsumed];
if (checkFor) if (checkFor)
{ {
// For the first instance, allow remoteIp to be null for servers that don't support it natively. // For the first instance, allow remoteIp to be null for servers that don't support it natively.
@ -159,7 +146,16 @@ namespace Microsoft.AspNetCore.HttpOverrides
_logger.LogDebug(1, $"Unknown proxy: {currentValues.RemoteIpAndPort}"); _logger.LogDebug(1, $"Unknown proxy: {currentValues.RemoteIpAndPort}");
break; break;
} }
if (!IPEndPointParser.TryParse(set.IpAndPortText, out set.RemoteIpAndPort))
IPEndPoint parsedEndPoint;
if (IPEndPointParser.TryParse(set.IpAndPortText, out parsedEndPoint))
{
applyChanges = true;
set.RemoteIpAndPort = parsedEndPoint;
currentValues.IpAndPortText = set.IpAndPortText;
currentValues.RemoteIpAndPort = set.RemoteIpAndPort;
}
else if (_options.RequireHeaderSymmetry)
{ {
_logger.LogDebug(2, $"Failed to parse forwarded IPAddress: {currentValues.IpAndPortText}"); _logger.LogDebug(2, $"Failed to parse forwarded IPAddress: {currentValues.IpAndPortText}");
return; return;
@ -168,7 +164,12 @@ namespace Microsoft.AspNetCore.HttpOverrides
if (checkProto) if (checkProto)
{ {
if (string.IsNullOrEmpty(set.Scheme)) if (!string.IsNullOrEmpty(set.Scheme))
{
applyChanges = true;
currentValues.Scheme = set.Scheme;
}
else if (_options.RequireHeaderSymmetry)
{ {
_logger.LogDebug(3, $"Failed to parse forwarded scheme: {set.Scheme}"); _logger.LogDebug(3, $"Failed to parse forwarded scheme: {set.Scheme}");
return; return;
@ -177,21 +178,22 @@ namespace Microsoft.AspNetCore.HttpOverrides
if (checkHost) if (checkHost)
{ {
if (string.IsNullOrEmpty(set.Host)) if (!string.IsNullOrEmpty(set.Host))
{
applyChanges = true;
currentValues.Host = set.Host;
}
else if (_options.RequireHeaderSymmetry)
{ {
_logger.LogDebug(4, $"Failed to parse forwarded host: {set.Host}"); _logger.LogDebug(4, $"Failed to parse forwarded host: {set.Host}");
return; return;
} }
} }
applyChanges = true;
currentValues = set;
entriesConsumed++;
} }
if (applyChanges) if (applyChanges)
{ {
if (checkFor) if (checkFor && currentValues.RemoteIpAndPort != null)
{ {
if (connection.RemoteIpAddress != null) if (connection.RemoteIpAddress != null)
{ {
@ -212,7 +214,7 @@ namespace Microsoft.AspNetCore.HttpOverrides
connection.RemotePort = currentValues.RemoteIpAndPort.Port; connection.RemotePort = currentValues.RemoteIpAndPort.Port;
} }
if (checkProto) if (checkProto && currentValues.Scheme != null)
{ {
// Save the original // Save the original
request.Headers[XOriginalProtoName] = request.Scheme; request.Headers[XOriginalProtoName] = request.Scheme;
@ -229,7 +231,7 @@ namespace Microsoft.AspNetCore.HttpOverrides
request.Scheme = currentValues.Scheme; request.Scheme = currentValues.Scheme;
} }
if (checkHost) if (checkHost && currentValues.Host != null)
{ {
// Save the original // Save the original
request.Headers[XOriginalHostName] = request.Host.ToString(); request.Headers[XOriginalHostName] = request.Host.ToString();
@ -264,7 +266,7 @@ namespace Microsoft.AspNetCore.HttpOverrides
return false; return false;
} }
private class SetOfForwarders private struct SetOfForwarders
{ {
public string IpAndPortText; public string IpAndPortText;
public IPEndPoint RemoteIpAndPort; public IPEndPoint RemoteIpAndPort;

View File

@ -30,5 +30,11 @@ namespace Microsoft.AspNetCore.Builder
/// Address ranges of known proxies to accept forwarded headers from. /// Address ranges of known proxies to accept forwarded headers from.
/// </summary> /// </summary>
public IList<IPNetwork> KnownNetworks { get; } = new List<IPNetwork>() { new IPNetwork(IPAddress.Loopback, 8) }; public IList<IPNetwork> KnownNetworks { get; } = new List<IPNetwork>() { new IPNetwork(IPAddress.Loopback, 8) };
/// <summary>
/// Require the number of header values to be in sync between the different headers being processed.
/// The default is 'true'.
/// </summary>
public bool RequireHeaderSymmetry { get; set; } = true;
} }
} }

View File

@ -14,6 +14,11 @@ namespace Microsoft.AspNetCore.HttpOverrides.Internal
IPAddress address; IPAddress address;
endpoint = null; endpoint = null;
if (string.IsNullOrEmpty(addressWithPort))
{
return false;
}
var lastColonIndex = addressWithPort.LastIndexOf(':'); var lastColonIndex = addressWithPort.LastIndexOf(':');
if (lastColonIndex > 0) if (lastColonIndex > 0)
{ {

View File

@ -363,6 +363,46 @@ namespace Microsoft.AspNetCore.HttpOverrides
Assert.True(assertsExecuted); Assert.True(assertsExecuted);
} }
[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", "h3")]
public async Task XForwardedProtoOverrideCanBeIndependentOfXForwardedForCount(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,
RequireHeaderSymmetry = false,
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] [Theory]
[InlineData("", "", "::1", false, "http")] [InlineData("", "", "::1", false, "http")]
[InlineData("h1", "", "::1", false, "http")] [InlineData("h1", "", "::1", false, "http")]