diff --git a/src/DefaultBuilder/src/ForwardedHeadersOptionsSetup.cs b/src/DefaultBuilder/src/ForwardedHeadersOptionsSetup.cs
index 8109ca39b323..9d2fe4d790cb 100644
--- a/src/DefaultBuilder/src/ForwardedHeadersOptionsSetup.cs
+++ b/src/DefaultBuilder/src/ForwardedHeadersOptionsSetup.cs
@@ -27,9 +27,6 @@ public void Configure(ForwardedHeadersOptions options)
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// Only loopback proxies are allowed by default. Clear that restriction because forwarders are
// being enabled by explicit configuration.
-#pragma warning disable ASPDEPR005 // KnownNetworks is obsolete
- options.KnownNetworks.Clear();
-#pragma warning restore ASPDEPR005 // KnownNetworks is obsolete
options.KnownIPNetworks.Clear();
options.KnownProxies.Clear();
}
diff --git a/src/Middleware/HttpOverrides/src/DualIPNetworkList.cs b/src/Middleware/HttpOverrides/src/DualIPNetworkList.cs
new file mode 100644
index 000000000000..7a3b92b1038f
--- /dev/null
+++ b/src/Middleware/HttpOverrides/src/DualIPNetworkList.cs
@@ -0,0 +1,140 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable ASPDEPR005 // Type or member is obsolete
+
+using AspNetIPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
+using IPAddress = System.Net.IPAddress;
+using IPNetwork = System.Net.IPNetwork;
+
+namespace Microsoft.AspNetCore.Builder;
+
+///
+/// Internal list implementation that keeps and the obsolete
+/// collections in sync. Modifications
+/// through either interface are reflected in the other.
+///
+internal sealed class DualIPNetworkList : IList, IList
+{
+ // Two independent underlying lists so each side behaves exactly like a List with respect to
+ // enumeration versioning, capacity growth, etc. They are kept strictly in sync by all mutating operations.
+ private readonly List _system = new();
+ private readonly List _aspnet = new();
+
+ public DualIPNetworkList()
+ {
+ // Default entry (loopback) added to both representations.
+ var loopback = new IPNetwork(IPAddress.Loopback, 8);
+ _system.Add(loopback);
+ _aspnet.Add(new AspNetIPNetwork(loopback.BaseAddress, loopback.PrefixLength));
+ }
+
+ int ICollection.Count => _system.Count;
+ int ICollection.Count => _aspnet.Count;
+
+ bool ICollection.IsReadOnly => false;
+ bool ICollection.IsReadOnly => false;
+
+ IPNetwork IList.this[int index]
+ {
+ get => _system[index];
+ set
+ {
+ _system[index] = value;
+ _aspnet[index] = new AspNetIPNetwork(value.BaseAddress, value.PrefixLength);
+ }
+ }
+
+ AspNetIPNetwork IList.this[int index]
+ {
+ get => _aspnet[index];
+ set
+ {
+ _aspnet[index] = value;
+ _system[index] = new IPNetwork(value.Prefix, value.PrefixLength);
+ }
+ }
+
+ void ICollection.Add(IPNetwork item)
+ {
+ _system.Add(item);
+ _aspnet.Add(new AspNetIPNetwork(item.BaseAddress, item.PrefixLength));
+ }
+
+ void ICollection.Add(AspNetIPNetwork item)
+ {
+ _aspnet.Add(item);
+ _system.Add(new IPNetwork(item.Prefix, item.PrefixLength));
+ }
+
+ public void Clear()
+ {
+ _system.Clear();
+ _aspnet.Clear();
+ }
+
+ void ICollection.Clear() => Clear();
+ void ICollection.Clear() => Clear();
+
+ bool ICollection.Contains(IPNetwork item) => _system.Contains(item);
+ bool ICollection.Contains(AspNetIPNetwork item) => _aspnet.Contains(item);
+
+ public void CopyTo(IPNetwork[] array, int arrayIndex) => _system.CopyTo(array, arrayIndex);
+ public void CopyTo(AspNetIPNetwork[] array, int arrayIndex) => _aspnet.CopyTo(array, arrayIndex);
+
+ void ICollection.CopyTo(IPNetwork[] array, int arrayIndex) => CopyTo(array, arrayIndex);
+ void ICollection.CopyTo(AspNetIPNetwork[] array, int arrayIndex) => CopyTo(array, arrayIndex);
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _system.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => _system.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => _aspnet.GetEnumerator();
+
+ int IList.IndexOf(IPNetwork item) => _system.IndexOf(item);
+ int IList.IndexOf(AspNetIPNetwork item) => _aspnet.IndexOf(item);
+
+ void IList.Insert(int index, IPNetwork item)
+ {
+ _system.Insert(index, item);
+ _aspnet.Insert(index, new AspNetIPNetwork(item.BaseAddress, item.PrefixLength));
+ }
+
+ void IList.Insert(int index, AspNetIPNetwork item)
+ {
+ _aspnet.Insert(index, item);
+ _system.Insert(index, new IPNetwork(item.Prefix, item.PrefixLength));
+ }
+
+ bool ICollection.Remove(IPNetwork item)
+ {
+ var idx = _system.IndexOf(item);
+ if (idx >= 0)
+ {
+ RemoveAt(idx);
+ return true;
+ }
+ return false;
+ }
+
+ bool ICollection.Remove(AspNetIPNetwork item)
+ {
+ var idx = _aspnet.IndexOf(item);
+ if (idx >= 0)
+ {
+ RemoveAt(idx);
+ return true;
+ }
+ return false;
+ }
+
+ public void RemoveAt(int index)
+ {
+ _system.RemoveAt(index);
+ _aspnet.RemoveAt(index);
+ }
+
+ void IList.RemoveAt(int index) => RemoveAt(index);
+ void IList.RemoveAt(int index) => RemoveAt(index);
+}
+
+#pragma warning restore ASPDEPR005 // Type or member is obsolete
diff --git a/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs b/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs
index fb1e757ff2e2..d60191021f08 100644
--- a/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs
+++ b/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs
@@ -213,11 +213,7 @@ public void ApplyForwarders(HttpContext context)
// Host and Scheme initial values are never inspected, no need to set them here.
};
- var checkKnownIps = _options.KnownIPNetworks.Count > 0
-#pragma warning disable ASPDEPR005 // KnownNetworks is obsolete
- || _options.KnownNetworks.Count > 0
-#pragma warning restore ASPDEPR005 // KnownNetworks is obsolete
- || _options.KnownProxies.Count > 0;
+ var checkKnownIps = _options.KnownIPNetworks.Count > 0 || _options.KnownProxies.Count > 0;
bool applyChanges = false;
int entriesConsumed = 0;
@@ -410,15 +406,6 @@ private bool CheckKnownAddress(IPAddress address)
return true;
}
}
-#pragma warning disable ASPDEPR005 // KnownNetworks is obsolete
- foreach (var network in _options.KnownNetworks)
- {
- if (network.Contains(address))
- {
- return true;
- }
- }
-#pragma warning restore ASPDEPR005 // KnownNetworks is obsolete
return false;
}
diff --git a/src/Middleware/HttpOverrides/src/ForwardedHeadersOptions.cs b/src/Middleware/HttpOverrides/src/ForwardedHeadersOptions.cs
index e0ed1820c001..bd835a21a673 100644
--- a/src/Middleware/HttpOverrides/src/ForwardedHeadersOptions.cs
+++ b/src/Middleware/HttpOverrides/src/ForwardedHeadersOptions.cs
@@ -13,6 +13,10 @@ namespace Microsoft.AspNetCore.Builder;
///
public class ForwardedHeadersOptions
{
+ // Backing dual list that keeps the obsolete and new types in sync.
+ // Once the obsolete IList property is removed this can be changed to a simple List.
+ private readonly DualIPNetworkList _knownNetworks = new();
+
///
/// Gets or sets the header used to retrieve the originating client IP. Defaults to the value specified by
/// .
@@ -87,12 +91,12 @@ public class ForwardedHeadersOptions
/// Obsolete, please use instead
///
[Obsolete("Please use KnownIPNetworks instead. For more information, visit https://aka.ms/aspnet/deprecate/005.", DiagnosticId = "ASPDEPR005")]
- public IList KnownNetworks { get; } = new List() { new(IPAddress.Loopback, 8) };
+ public IList KnownNetworks => _knownNetworks;
///
/// Address ranges of known proxies to accept forwarded headers from.
///
- public IList KnownIPNetworks { get; } = new List() { new(IPAddress.Loopback, 8) };
+ public IList KnownIPNetworks => _knownNetworks;
///
/// The allowed values from x-forwarded-host. If the list is empty then all hosts are allowed.
diff --git a/src/Middleware/HttpOverrides/test/DualIPNetworkListTests.cs b/src/Middleware/HttpOverrides/test/DualIPNetworkListTests.cs
new file mode 100644
index 000000000000..581d197acb85
--- /dev/null
+++ b/src/Middleware/HttpOverrides/test/DualIPNetworkListTests.cs
@@ -0,0 +1,248 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Net;
+using Microsoft.AspNetCore.Builder;
+using Xunit;
+using AspNetIPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
+
+namespace Microsoft.AspNetCore.HttpOverrides;
+
+public class DualIPNetworkListTests
+{
+ [Fact]
+ public void DefaultContainsLoopback()
+ {
+ var options = new ForwardedHeadersOptions();
+ Assert.Single(options.KnownIPNetworks);
+ Assert.Equal("127.0.0.0", options.KnownIPNetworks[0].BaseAddress.ToString());
+ Assert.Equal(8, options.KnownIPNetworks[0].PrefixLength);
+#pragma warning disable ASPDEPR005
+ Assert.Single(options.KnownNetworks);
+ Assert.Equal("127.0.0.0", options.KnownNetworks[0].Prefix.ToString());
+ Assert.Equal(8, options.KnownNetworks[0].PrefixLength);
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void AddThroughSystemCollectionVisibleViaObsolete()
+ {
+ var options = new ForwardedHeadersOptions();
+ options.KnownIPNetworks.Add(System.Net.IPNetwork.Parse("10.0.0.0/8"));
+#pragma warning disable ASPDEPR005
+ var obsoleteList = options.KnownNetworks;
+ Assert.Equal(2, obsoleteList.Count);
+ Assert.Equal(IPAddress.Parse("10.0.0.0"), obsoleteList[1].Prefix);
+ Assert.Equal(8, obsoleteList[1].PrefixLength);
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void AddThroughObsoleteCollectionVisibleViaSystem()
+ {
+#pragma warning disable ASPDEPR005
+ var options = new ForwardedHeadersOptions();
+ options.KnownNetworks.Add(new AspNetIPNetwork(IPAddress.Parse("192.168.0.0"), 16));
+ Assert.Equal(2, options.KnownIPNetworks.Count);
+ Assert.Equal("192.168.0.0/16", options.KnownIPNetworks[1].ToString());
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void ReplaceViaSystemIndexerUpdatesObsolete()
+ {
+ var options = new ForwardedHeadersOptions();
+ options.KnownIPNetworks[0] = System.Net.IPNetwork.Parse("172.16.0.0/12");
+#pragma warning disable ASPDEPR005
+ Assert.Equal(IPAddress.Parse("172.16.0.0"), options.KnownNetworks[0].Prefix);
+ Assert.Equal(12, options.KnownNetworks[0].PrefixLength);
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void ReplaceViaObsoleteIndexerUpdatesSystem()
+ {
+#pragma warning disable ASPDEPR005
+ var options = new ForwardedHeadersOptions();
+ options.KnownNetworks[0] = new AspNetIPNetwork(IPAddress.Parse("172.16.0.0"), 12);
+ Assert.Equal("172.16.0.0/12", options.KnownIPNetworks[0].ToString());
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void ClearClearsBoth()
+ {
+ var options = new ForwardedHeadersOptions();
+ options.KnownIPNetworks.Clear();
+#pragma warning disable ASPDEPR005
+ Assert.Empty(options.KnownNetworks);
+#pragma warning restore ASPDEPR005
+ Assert.Empty(options.KnownIPNetworks);
+ }
+
+ [Fact]
+ public void RemoveThroughEitherCollectionRemovesFromBoth()
+ {
+ var options = new ForwardedHeadersOptions();
+ options.KnownIPNetworks.Add(System.Net.IPNetwork.Parse("10.0.0.0/8"));
+ var first = options.KnownIPNetworks[0];
+ var removed = options.KnownIPNetworks.Remove(first);
+ Assert.True(removed);
+#pragma warning disable ASPDEPR005
+ var obsoleteList = options.KnownNetworks;
+ Assert.DoesNotContain(obsoleteList, n => n.Prefix.Equals(IPAddress.Loopback));
+#pragma warning restore ASPDEPR005
+ Assert.Single(options.KnownIPNetworks); // only the 10.0.0.0/8 entry should remain
+ }
+
+ // New tests to cover each IList member for both interfaces
+
+ [Fact]
+ public void ContainsWorksForBothLists()
+ {
+ var options = new ForwardedHeadersOptions();
+ var loopback = options.KnownIPNetworks[0];
+ Assert.Contains(loopback, options.KnownIPNetworks);
+#pragma warning disable ASPDEPR005
+ Assert.Contains(options.KnownNetworks, n => n.Prefix.Equals(loopback.BaseAddress) && n.PrefixLength == loopback.PrefixLength);
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void CopyToSystem()
+ {
+ var options = new ForwardedHeadersOptions();
+ options.KnownIPNetworks.Add(System.Net.IPNetwork.Parse("10.0.0.0/8"));
+ var arr = new System.Net.IPNetwork[5];
+ options.KnownIPNetworks.CopyTo(arr, 1);
+ Assert.Equal("127.0.0.0/8", arr[1].ToString());
+ Assert.Equal("10.0.0.0/8", arr[2].ToString());
+ }
+
+ [Fact]
+ public void CopyToObsolete()
+ {
+#pragma warning disable ASPDEPR005
+ var options = new ForwardedHeadersOptions();
+ options.KnownNetworks.Add(new AspNetIPNetwork(IPAddress.Parse("10.0.0.0"), 8));
+ var arr = new AspNetIPNetwork[5];
+ options.KnownNetworks.CopyTo(arr, 2);
+ Assert.Null(arr[0]);
+ Assert.Equal(IPAddress.Parse("127.0.0.0"), arr[2].Prefix);
+ Assert.Equal(8, arr[2].PrefixLength);
+ Assert.Equal(IPAddress.Parse("10.0.0.0"), arr[3].Prefix);
+ Assert.Equal(8, arr[3].PrefixLength);
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void IndexOfSystem()
+ {
+ var options = new ForwardedHeadersOptions();
+ options.KnownIPNetworks.Add(System.Net.IPNetwork.Parse("10.0.0.0/8"));
+ Assert.Equal(1, options.KnownIPNetworks.IndexOf(System.Net.IPNetwork.Parse("10.0.0.0/8")));
+ }
+
+ [Fact]
+ public void IndexOfObsolete()
+ {
+ // AspNetIPNetwork doesn't implement Equals, so IndexOf uses reference equality.
+ // This keeps the obsolete behavior intact.
+
+#pragma warning disable ASPDEPR005
+ var options = new ForwardedHeadersOptions();
+ var item = new AspNetIPNetwork(IPAddress.Parse("10.0.0.0"), 8);
+ options.KnownNetworks.Add(item);
+ Assert.Equal(-1, options.KnownNetworks.IndexOf(new AspNetIPNetwork(IPAddress.Parse("10.0.0.0"), 8)));
+ Assert.Equal(1, options.KnownNetworks.IndexOf(item));
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void InsertSystem()
+ {
+ var options = new ForwardedHeadersOptions();
+ options.KnownIPNetworks.Insert(0, System.Net.IPNetwork.Parse("10.0.0.0/8"));
+ Assert.Equal("10.0.0.0/8", options.KnownIPNetworks[0].ToString());
+#pragma warning disable ASPDEPR005
+ Assert.Equal(IPAddress.Parse("10.0.0.0"), options.KnownNetworks[0].Prefix);
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void InsertObsolete()
+ {
+#pragma warning disable ASPDEPR005
+ var options = new ForwardedHeadersOptions();
+ options.KnownNetworks.Insert(0, new AspNetIPNetwork(IPAddress.Parse("10.0.0.0"), 8));
+ Assert.Equal("10.0.0.0/8", options.KnownIPNetworks[0].ToString());
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void RemoveAtSystem()
+ {
+ var options = new ForwardedHeadersOptions();
+ options.KnownIPNetworks.Add(System.Net.IPNetwork.Parse("10.0.0.0/8"));
+ options.KnownIPNetworks.RemoveAt(0); // remove loopback
+#pragma warning disable ASPDEPR005
+ Assert.DoesNotContain(options.KnownNetworks, n => n.Prefix.Equals(IPAddress.Loopback));
+#pragma warning restore ASPDEPR005
+ Assert.Single(options.KnownIPNetworks); // only 10.0.0.0/8
+ }
+
+ [Fact]
+ public void RemoveAtObsolete()
+ {
+#pragma warning disable ASPDEPR005
+ var options = new ForwardedHeadersOptions();
+ options.KnownNetworks.Add(new AspNetIPNetwork(IPAddress.Parse("10.0.0.0"), 8));
+ options.KnownNetworks.RemoveAt(0); // remove loopback
+ Assert.DoesNotContain(options.KnownIPNetworks, n => n.BaseAddress.Equals(IPAddress.Loopback));
+ Assert.Single(options.KnownIPNetworks); // only 10.0.0.0/8
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void EnumerateSystem()
+ {
+ var options = new ForwardedHeadersOptions();
+ options.KnownIPNetworks.Add(System.Net.IPNetwork.Parse("10.0.0.0/8"));
+ var list = options.KnownIPNetworks.ToList();
+ Assert.Equal(2, list.Count);
+ Assert.Contains(list, n => n.BaseAddress.Equals(IPAddress.Parse("10.0.0.0")));
+ }
+
+ [Fact]
+ public void EnumerateObsolete()
+ {
+#pragma warning disable ASPDEPR005
+ var options = new ForwardedHeadersOptions();
+ options.KnownNetworks.Add(new AspNetIPNetwork(IPAddress.Parse("10.0.0.0"), 8));
+ var list = options.KnownNetworks.ToList();
+ Assert.Equal(2, list.Count);
+ Assert.Contains(list, n => n.Prefix.Equals(IPAddress.Parse("10.0.0.0")));
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void IsReadOnlyFalse()
+ {
+ var options = new ForwardedHeadersOptions();
+ Assert.False(options.KnownIPNetworks.IsReadOnly);
+#pragma warning disable ASPDEPR005
+ Assert.False(options.KnownNetworks.IsReadOnly);
+#pragma warning restore ASPDEPR005
+ }
+
+ [Fact]
+ public void CountSyncAfterMixedOperations()
+ {
+ var options = new ForwardedHeadersOptions();
+ options.KnownIPNetworks.Add(System.Net.IPNetwork.Parse("10.0.0.0/8"));
+#pragma warning disable ASPDEPR005
+ options.KnownNetworks.Add(new AspNetIPNetwork(IPAddress.Parse("192.168.0.0"), 16));
+ Assert.Equal(options.KnownIPNetworks.Count, options.KnownNetworks.Count);
+#pragma warning restore ASPDEPR005
+ }
+}
diff --git a/src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs b/src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs
index 317d2853d023..606fe2ebc91c 100644
--- a/src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs
+++ b/src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs
@@ -104,7 +104,7 @@ public async Task XForwardedForFirstValueIsInvalid(int limit, string header, str
[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)
+ public async Task XForwardedForForwardLimit_Obsolete(int limit, string header, string expectedIp, int expectedPort, string remainingHeader, bool requireSymmetry)
{
using var host = new HostBuilder()
.ConfigureWebHost(webHostBuilder =>
@@ -123,6 +123,61 @@ public async Task XForwardedForForwardLimit(int limit, string header, string exp
#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);
});
@@ -847,7 +902,7 @@ public async Task XForwardedProtoOverrideCanBeIndependentOfXForwardedForCount(in
[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)
+ public async Task XForwardedProtoOverrideLimitedByLoopback_Obsolete(string protoHeader, string forHeader, string remoteIp, bool loopback, string expected)
{
using var host = new HostBuilder()
.ConfigureWebHost(webHostBuilder =>
@@ -867,6 +922,56 @@ public async Task XForwardedProtoOverrideLimitedByLoopback(string protoHeader, s
#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();
}
@@ -1127,7 +1232,7 @@ public async Task XForwardForIPv4ToIPv6Mapping(string forHeader, string knownPro
[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)
+ public async Task ForwardersWithDIOptionsRunsOnce_Obsolete(int limit, string header, string expectedScheme, string remainingHeader)
{
using var host = new HostBuilder()
.ConfigureWebHost(webHostBuilder =>
@@ -1143,6 +1248,45 @@ public async Task ForwardersWithDIOptionsRunsOnce(int limit, string header, stri
#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(options =>
+ {
+ options.ForwardedHeaders = ForwardedHeaders.XForwardedProto;
+ options.KnownProxies.Clear();
options.KnownIPNetworks.Clear();
options.ForwardLimit = limit;
});
@@ -1170,7 +1314,7 @@ public async Task ForwardersWithDIOptionsRunsOnce(int limit, string header, stri
[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)
+ public async Task ForwardersWithDirectOptionsRunsTwice_Obsolete(int limit, string header, string expectedScheme, string remainingHeader)
{
using var host = new HostBuilder()
.ConfigureWebHost(webHostBuilder =>
@@ -1188,6 +1332,42 @@ public async Task ForwardersWithDirectOptionsRunsTwice(int limit, string header,
#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);
diff --git a/src/Shared/RoslynUtils/WellKnownTypes.cs b/src/Shared/RoslynUtils/WellKnownTypes.cs
index 05925ddf68bc..71ed75a2e5e5 100644
--- a/src/Shared/RoslynUtils/WellKnownTypes.cs
+++ b/src/Shared/RoslynUtils/WellKnownTypes.cs
@@ -19,6 +19,7 @@ public static WellKnownTypes GetOrCreate(Compilation compilation) =>
private readonly INamedTypeSymbol?[] _lazyWellKnownTypes;
private readonly Compilation _compilation;
+ private readonly INamedTypeSymbol _missingTypeSymbol;
static WellKnownTypes()
{
@@ -51,6 +52,7 @@ private WellKnownTypes(Compilation compilation)
{
_lazyWellKnownTypes = new INamedTypeSymbol?[WellKnownTypeData.WellKnownTypeNames.Length];
_compilation = compilation;
+ _missingTypeSymbol = compilation.GetTypeByMetadataName(typeof(MissingType).FullName!)!;
}
public INamedTypeSymbol Get(SpecialType type)