diff options
3 files changed, 77 insertions, 6 deletions
diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs index 8a590a4777..d2e8c02b04 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs @@ -294,7 +294,12 @@ namespace System.Net.Http else { Debug.Assert(_pool.UsingProxy); - await WriteAsciiStringAsync(uri.IdnHost).ConfigureAwait(false); + + // If the hostname is an IPv6 address, uri.IdnHost will return the address without enclosing []. + // In this case, use uri.Host instead, which will correctly enclose with []. + // Note we don't need punycode encoding if it's an IP address, so using uri.Host is fine. + await WriteAsciiStringAsync(uri.HostNameType == UriHostNameType.IPv6 ? + uri.Host : uri.IdnHost).ConfigureAwait(false); if (!uri.IsDefaultPort) { diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPoolManager.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPoolManager.cs index df1f4f50ad..73d87d9d46 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPoolManager.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPoolManager.cs @@ -148,10 +148,15 @@ namespace System.Net.Http { Uri uri = request.RequestUri; + // If the hostname is an IPv6 address, uri.IdnHost will return the address without enclosing []. + // In this case, use uri.Host instead, which will correctly enclose with []. + // Note we don't need punycode encoding if it's an IP address, so using uri.Host is fine. + bool isIPv6Address = uri.HostNameType == UriHostNameType.IPv6; + if (isProxyConnect) { Debug.Assert(uri == proxyUri); - return new HttpConnectionKey(HttpConnectionKind.ProxyConnect, uri.IdnHost, uri.Port, null, proxyUri); + return new HttpConnectionKey(HttpConnectionKind.ProxyConnect, isIPv6Address ? uri.Host : uri.IdnHost, uri.Port, null, proxyUri); } string sslHostName = null; @@ -177,7 +182,7 @@ namespace System.Net.Http if (HttpUtilities.IsNonSecureWebSocketScheme(uri.Scheme)) { // Non-secure websocket connection through proxy to the destination. - return new HttpConnectionKey(HttpConnectionKind.ProxyTunnel, uri.IdnHost, uri.Port, null, proxyUri); + return new HttpConnectionKey(HttpConnectionKind.ProxyTunnel, isIPv6Address ? uri.Host : uri.IdnHost, uri.Port, null, proxyUri); } else { @@ -190,16 +195,16 @@ namespace System.Net.Http else { // Tunnel SSL connection through proxy to the destination. - return new HttpConnectionKey(HttpConnectionKind.SslProxyTunnel, uri.IdnHost, uri.Port, sslHostName, proxyUri); + return new HttpConnectionKey(HttpConnectionKind.SslProxyTunnel, isIPv6Address ? uri.Host : uri.IdnHost, uri.Port, sslHostName, proxyUri); } } else if (sslHostName != null) { - return new HttpConnectionKey(HttpConnectionKind.Https, uri.IdnHost, uri.Port, sslHostName, null); + return new HttpConnectionKey(HttpConnectionKind.Https, isIPv6Address ? uri.Host : uri.IdnHost, uri.Port, sslHostName, null); } else { - return new HttpConnectionKey(HttpConnectionKind.Http, uri.IdnHost, uri.Port, null, null); + return new HttpConnectionKey(HttpConnectionKind.Http, isIPv6Address ? uri.Host : uri.IdnHost, uri.Port, null, null); } } diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs index e35a5843b7..945f5d3140 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs @@ -471,6 +471,67 @@ namespace System.Net.Http.Functional.Tests } } + [Theory] + [InlineData("[::1234]")] + [InlineData("[::1234]:8080")] + public async Task GetAsync_IPv6AddressInHostHeader_CorrectlyFormatted(string host) + { + string ipv6Address = "http://" + host; + bool connectionAccepted = false; + + await LoopbackServer.CreateClientAndServerAsync(async proxyUri => + { + using (HttpClientHandler handler = CreateHttpClientHandler()) + using (var client = new HttpClient(handler)) + { + handler.Proxy = new WebProxy(proxyUri); + try { await client.GetAsync(ipv6Address); } catch { } + } + }, server => server.AcceptConnectionAsync(async connection => + { + connectionAccepted = true; + List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync(); + Assert.Contains($"Host: {host}", headers); + })); + + Assert.True(connectionAccepted); + } + + public static IEnumerable<object[]> SecureAndNonSecure_IPBasedUri_MemberData() => + from address in new[] { IPAddress.Loopback, IPAddress.IPv6Loopback } + from useSsl in new[] { true, false } + select new object[] { address, useSsl }; + + [Theory] + [MemberData(nameof(SecureAndNonSecure_IPBasedUri_MemberData))] + public async Task GetAsync_SecureAndNonSecureIPBasedUri_CorrectlyFormatted(IPAddress address, bool useSsl) + { + var options = new LoopbackServer.Options { Address = address, UseSsl= useSsl }; + bool connectionAccepted = false; + string host = ""; + + await LoopbackServer.CreateClientAndServerAsync(async url => + { + host = $"{url.Host}:{url.Port}"; + using (HttpClientHandler handler = CreateHttpClientHandler()) + using (var client = new HttpClient(handler)) + { + if (useSsl) + { + handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates; + } + try { await client.GetAsync(url); } catch { } + } + }, server => server.AcceptConnectionAsync(async connection => + { + connectionAccepted = true; + List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync(); + Assert.Contains($"Host: {host}", headers); + }), options); + + Assert.True(connectionAccepted); + } + [OuterLoop] // TODO: Issue #11345 [Theory, MemberData(nameof(CompressedServers))] public async Task GetAsync_SetAutomaticDecompression_HeadersRemoved(Uri server) |