Use the Host header for the SSL handshake
authorStephen Toub <stoub@microsoft.com>
Mon, 23 Oct 2017 19:32:19 +0000 (15:32 -0400)
committerStephen Toub <stoub@microsoft.com>
Wed, 25 Oct 2017 17:40:03 +0000 (13:40 -0400)
Commit migrated from https://github.com/dotnet/corefx/commit/5ecea8ff47c03a073a77916a9c7d1d806f8c7ce3

src/libraries/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionHandler.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs
src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs

index c063548..6a5a072 100644 (file)
@@ -91,7 +91,42 @@ namespace System.Net.Http
 
             if (HttpUtilities.IsSupportedSecureScheme(uri.Scheme))
             {
-                SslStream sslStream = await EstablishSslConnection(uri.IdnHost, request, stream).ConfigureAwait(false);
+                // Get the appropriate host name to use for the SSL connection, allowing a host header to override.
+                string host = request.Headers.Host;
+                if (host == null)
+                {
+                    // No host header, use the host from the Uri.
+                    host = uri.IdnHost;
+                }
+                else
+                {
+                    // There is a host header.  Use it, but first see if we need to trim off a port.
+                    int colonPos = host.IndexOf(':');
+                    if (colonPos >= 0)
+                    {
+                        // There is colon, which could either be a port separator or a separator in
+                        // an IPv6 address.  See if this is an IPv6 address; if it's not, use everything
+                        // before the colon as the host name, and if it is, use everything before the last
+                        // colon iff the last colon is after the end of the IPv6 address (otherwise it's a
+                        // part of the address).
+                        int ipV6AddressEnd = host.IndexOf(']');
+                        if (ipV6AddressEnd == -1)
+                        {
+                            host = host.Substring(0, colonPos);
+                        }
+                        else
+                        {
+                            colonPos = host.LastIndexOf(':');
+                            if (colonPos > ipV6AddressEnd)
+                            {
+                                host = host.Substring(0, colonPos);
+                            }
+                        }
+                    }
+                }
+
+                // Establish the connection using the parsed host name.
+                SslStream sslStream = await EstablishSslConnection(host, request, stream).ConfigureAwait(false);
                 stream = sslStream;
                 transportContext = sslStream.TransportContext;
             }
index 1079186..3e3993c 100644 (file)
@@ -285,6 +285,49 @@ namespace System.Net.Http.Functional.Tests
             }
         }
 
+        [OuterLoop] // TODO: Issue #11345
+        [Theory]
+        [InlineData(false)]
+        [InlineData(true)]
+        public async Task SendAsync_GetWithValidHostHeader_Success(bool withPort)
+        {
+            var m = new HttpRequestMessage(HttpMethod.Get, Configuration.Http.SecureRemoteEchoServer);
+            m.Headers.Host = withPort ? Configuration.Http.SecureHost + ":123" : Configuration.Http.SecureHost;
+
+            using (HttpClient client = CreateHttpClient())
+            using (HttpResponseMessage response = await client.SendAsync(m))
+            {
+                string responseContent = await response.Content.ReadAsStringAsync();
+                _output.WriteLine(responseContent);
+                TestHelper.VerifyResponseBody(
+                    responseContent,
+                    response.Content.Headers.ContentMD5,
+                    false,
+                    null);
+            }
+        }
+
+        [OuterLoop] // TODO: Issue #11345
+        [Fact]
+        public async Task SendAsync_GetWithInvalidHostHeader_ThrowsException()
+        {
+            if (PlatformDetection.IsNetCore && !UseManagedHandler)
+            {
+                // [ActiveIssue(24862)]
+                // WinHttpHandler and CurlHandler do not use the Host header to influence the SSL auth.
+                // .NET Framework and ManagedHandler do.
+                return;
+            }
+
+            var m = new HttpRequestMessage(HttpMethod.Get, Configuration.Http.SecureRemoteEchoServer);
+            m.Headers.Host = "hostheaderthatdoesnotmatch";
+
+            using (HttpClient client = CreateHttpClient())
+            {
+                await Assert.ThrowsAsync<HttpRequestException>(() => client.SendAsync(m));
+            }
+        }
+
         [ActiveIssue(22158, TargetFrameworkMonikers.Uap)] 
         [OuterLoop] // TODO: Issue #11345
         [Fact]
index cde6c66..0c5645f 100644 (file)
@@ -86,9 +86,11 @@ namespace System.Net.WebSockets.Client.Tests
 
         [ActiveIssue(18784, TargetFrameworkMonikers.NetFramework)]
         [OuterLoop]
-        [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoHeadersServers))]
-        public async Task ConnectAsync_AddHostHeader_Success(Uri server)
+        [ConditionalTheory(nameof(WebSocketsSupported))]
+        public async Task ConnectAsync_AddHostHeader_Success()
         {
+            Uri server = System.Net.Test.Common.Configuration.WebSockets.RemoteEchoServer;
+
             // Send via the physical address such as "corefx-net.cloudapp.net"
             // Set the Host header to logical address like "subdomain.corefx-net.cloudapp.net"
             // Verify the scenario works and the remote server received "Host: subdomain.corefx-net.cloudapp.net"