Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -584,10 +584,17 @@ public HttpConnectionKey(HttpConnectionKind kind, string? host, int port, string
}

// In the common case, SslHostName (when present) is equal to Host. If so, don't include in hash.
public override int GetHashCode() =>
(SslHostName == Host ?
public override int GetHashCode()
{
int hashCode = SslHostName == Host ?
HashCode.Combine(Kind, Host, Port, ProxyUri, Identity) :
HashCode.Combine(Kind, Host, Port, SslHostName, ProxyUri, Identity));
HashCode.Combine(Kind, Host, Port, SslHostName, ProxyUri, Identity);

// Uri equality ignores IPv6 scope IDs, but they identify distinct proxy endpoints.
return ProxyUri is { HostNameType: UriHostNameType.IPv6 } proxyUri ?
HashCode.Combine(hashCode, proxyUri.IdnHost) :
hashCode;
}

public override bool Equals([NotNullWhen(true)] object? obj) =>
obj is HttpConnectionKey hck &&
Expand All @@ -598,6 +605,7 @@ public bool Equals(HttpConnectionKey other) =>
Host == other.Host &&
Port == other.Port &&
ProxyUri == other.ProxyUri &&
(ProxyUri is not { HostNameType: UriHostNameType.IPv6 } || ProxyUri.IdnHost == other.ProxyUri?.IdnHost) &&
SslHostName == other.SslHostName &&
Identity == other.Identity;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,40 @@ public void Equals_DifferentParameters_ReturnsTrueIfAllEqual(string kindString,
object actualKey = Activator.CreateInstance(keyType, Enum.Parse(connectionKindType, kindString), host, port, sslHostName, proxyUri, identity);
Assert.Equal(expected, referenceKey.Equals(actualKey));
}

[ConditionalTheory(typeof(SocketsHttpHandler), nameof(SocketsHttpHandler.IsSupported))]
[InlineData("http://[fe80::1%1]/", "http://[fe80::1%2]/", false)]
[InlineData("http://[fe80::1]/", "http://[fe80::1%1]/", false)]
[InlineData("http://[fe80::1%eth0]/", "http://[fe80::1%eth1]/", false)]
[InlineData("http://[fe80::1%eth0]/", "http://[fe80::1%ETH0]/", false)]
[InlineData("http://[fe80::1%1]/", "http://[fe80::1%1]/", true)]
[InlineData("http://[fe80::1%1]/", "HTTP://[fe80:0:0:0:0:0:0:1%1]:80/", true)]
[InlineData("http://[fe80::1]/", "http://[fe80:0:0:0:0:0:0:1]:80/", true)]
[InlineData("http://user@[fe80::1%1]/#first", "http://other@[fe80::1%1]/#second", true)]
[InlineData("http://[fe80::1%1]:80/", "http://[fe80::1%1]:81/", false)]
[InlineData("http://[fe80::1%1]/", "https://[fe80::1%1]/", false)]
[InlineData("http://[fe80::1%1]/first", "http://[fe80::1%1]/second", false)]
[InlineData("http://PROXY.TEST/", "http://proxy.test:80/", true)]
[InlineData("http://127.0.0.1/", "http://127.1:80/", true)]
public void Equals_ProxyUris_DistinguishesIPv6Scope(string firstProxyUri, string secondProxyUri, bool expected)
{
Type connectionKindType = Type.GetType("System.Net.Http.HttpConnectionKind, System.Net.Http");
Type keyType = Type.GetType("System.Net.Http.HttpConnectionPoolManager+HttpConnectionKey, System.Net.Http");
object kind = Enum.Parse(connectionKindType, "Http");

foreach (string sslHostName in new[] { "localhost", "localhost-ssl" })
{
object firstKey = Activator.CreateInstance(keyType, kind, "localhost", 80, sslHostName, new Uri(firstProxyUri), string.Empty);
object secondKey = Activator.CreateInstance(keyType, kind, "localhost", 80, sslHostName, new Uri(secondProxyUri), string.Empty);

Assert.Equal(expected, firstKey.Equals(secondKey));
Assert.Equal(expected, secondKey.Equals(firstKey));
Assert.Equal(expected ? 1 : 2, new HashSet<object> { firstKey, secondKey }.Count);
if (expected)
{
Assert.Equal(firstKey.GetHashCode(), secondKey.GetHashCode());
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,49 @@ public sealed class SocketsHttpHandler_HttpClientHandler_Proxy_Test : HttpClient
{
public SocketsHttpHandler_HttpClientHandler_Proxy_Test(ITestOutputHelper output) : base(output) { }

[Theory]
[InlineData("http", false, false)]
[InlineData("http", true, false)]
[InlineData("https", false, false)]
[InlineData("https", true, false)]
[InlineData("socks5", false, false)]
[InlineData("socks5", true, false)]
[InlineData("http", false, true)]
[InlineData("http", true, true)]
[InlineData("https", false, true)]
[InlineData("https", true, true)]
[InlineData("socks5", false, true)]
[InlineData("socks5", true, true)]
public async Task Proxy_IPv6ScopeChange_ConnectsToSelectedProxy(string proxyScheme, bool useSsl, bool useHttp2)
{
var proxy = new WebProxy();
using var handler = new SocketsHttpHandler { Proxy = proxy };
DnsEndPoint endPoint = null;
handler.ConnectCallback = (context, _) =>
{
endPoint = context.DnsEndPoint;
throw new InvalidOperationException(nameof(Proxy_IPv6ScopeChange_ConnectsToSelectedProxy));
};
using var client = new HttpClient(handler)
{
DefaultRequestVersion = useHttp2 ? HttpVersion.Version20 : HttpVersion.Version11,
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact
};

for (int scope = 1; scope <= 2; scope++)
{
var proxyUri = new Uri($"{proxyScheme}://[fe80::1%{scope}]:8080/");
proxy.Address = proxyUri;
endPoint = null;

HttpRequestException exception = await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(useSsl ? "https://example.test/" : "http://example.test/"));
Assert.Equal(nameof(Proxy_IPv6ScopeChange_ConnectsToSelectedProxy), Assert.IsType<InvalidOperationException>(exception.InnerException).Message);
Assert.NotNull(endPoint);
Assert.Equal(IPAddress.Parse(proxyUri.IdnHost), IPAddress.Parse(endPoint.Host));
Assert.Equal(proxyUri.Port, endPoint.Port);
}
}

[Theory]
[InlineData(true)]
[InlineData(false)]
Expand Down
Loading