#37 Make the header length match optional.
This commit is contained in:
parent
367eec812c
commit
5fe48ada42
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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")]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue