From dbb09e4ab936413694faa3f743c8d8f6cd8e5aba Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Tue, 28 May 2019 18:36:25 +0200 Subject: [PATCH] User-Agent header is ignored in "CONNECT" requests (dotnet/corefx#37785) * flow UserAgent on CONNECT * Apply Stephen feedback * address PR feedback * update test * fix style Commit migrated from https://github.com/dotnet/corefx/commit/1064d0352c007fd2d8fb32d7f6e286fbb6d41264 --- .../Http/SocketsHttpHandler/HttpConnectionPool.cs | 10 ++++- .../tests/FunctionalTests/HttpClientHandlerTest.cs | 52 ++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index c747304..ba60505 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Net.Http.Headers; using System.Net.Http.HPack; using System.Net.Security; using System.Net.Sockets; @@ -617,7 +618,7 @@ namespace System.Net.Http case HttpConnectionKind.ProxyTunnel: case HttpConnectionKind.SslProxyTunnel: HttpResponseMessage response; - (stream, response) = await EstablishProxyTunnel(cancellationToken).ConfigureAwait(false); + (stream, response) = await EstablishProxyTunnel(request.HasHeaders ? request.Headers : null, cancellationToken).ConfigureAwait(false); if (response != null) { // Return non-success response from proxy. @@ -666,12 +667,17 @@ namespace System.Net.Http } // Returns the established stream or an HttpResponseMessage from the proxy indicating failure. - private async ValueTask<(Stream, HttpResponseMessage)> EstablishProxyTunnel(CancellationToken cancellationToken) + private async ValueTask<(Stream, HttpResponseMessage)> EstablishProxyTunnel(HttpRequestHeaders headers, CancellationToken cancellationToken) { // Send a CONNECT request to the proxy server to establish a tunnel. HttpRequestMessage tunnelRequest = new HttpRequestMessage(HttpMethod.Connect, _proxyUri); tunnelRequest.Headers.Host = $"{_host}:{_port}"; // This specifies destination host/port to connect to + if (headers != null && headers.TryGetValues(HttpKnownHeaderNames.UserAgent, out IEnumerable values)) + { + tunnelRequest.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.UserAgent, values); + } + HttpResponseMessage tunnelResponse = await _poolManager.SendProxyConnectAsync(tunnelRequest, _proxyUri, cancellationToken).ConfigureAwait(false); if (tunnelResponse.StatusCode != HttpStatusCode.OK) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs index 70ff032..7cb3dbe 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs @@ -449,6 +449,58 @@ namespace System.Net.Http.Functional.Tests Assert.True(connectionAccepted); } + [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "UAP HTTP stack doesn't support .Proxy property")] + [Theory] + [InlineData(true)] + [InlineData(false)] + [OuterLoop("Uses external server")] + public async Task ProxyTunnelRequest_UserAgentHeaderAdded(bool addUserAgentHeader) + { + if (!UseSocketsHttpHandler) + { + return; // Skip test since the fix is only in SocketsHttpHandler. + } + + string addressUri = $"https://{Configuration.Http.SecureHost}/"; + bool connectionAccepted = false; + + await LoopbackServer.CreateClientAndServerAsync(async proxyUri => + { + using (HttpClientHandler handler = CreateHttpClientHandler()) + using (var client = new HttpClient(handler)) + { + handler.Proxy = new WebProxy(proxyUri); + handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates; + if (addUserAgentHeader) + { + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Mozilla", "5.0")); + } + try + { + await client.GetAsync(addressUri); + } + catch + { + } + } + }, server => server.AcceptConnectionAsync(async connection => + { + connectionAccepted = true; + List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); + Assert.Contains($"CONNECT {Configuration.Http.SecureHost}:443 HTTP/1.1", headers); + if (addUserAgentHeader) + { + Assert.Contains("User-Agent: Mozilla/5.0", headers); + } + else + { + Assert.DoesNotContain("User-Agent:", headers); + } + })); + + Assert.True(connectionAccepted); + } + public static IEnumerable SecureAndNonSecure_IPBasedUri_MemberData() => from address in new[] { IPAddress.Loopback, IPAddress.IPv6Loopback } from useSsl in new[] { true, false } -- 2.7.4