Skip to content

Commit

Permalink
add support for https proxy (#87638)
Browse files Browse the repository at this point in the history
* add support for https proxy

* winhttp

* https

* diag
  • Loading branch information
wfurt committed Jun 22, 2023
1 parent 2e764d6 commit 28151b5
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 13 deletions.
Expand Up @@ -305,17 +305,15 @@ public async Task AuthenticatedProxyTunnelRequest_PostAsyncWithNoCreds_Throws()
}
}

[Fact]
[ConditionalFact(nameof(HttpClientHandlerTestBase.IsWinHttpHandler))]
public async Task Proxy_SslProxyUnsupported_Throws()
{
using (HttpClientHandler handler = CreateHttpClientHandler())
using (HttpClient client = CreateHttpClient(handler))
{
handler.Proxy = new WebProxy($"https://{Guid.NewGuid():N}");

Type expectedType = IsWinHttpHandler ? typeof(HttpRequestException) : typeof(NotSupportedException);

await Assert.ThrowsAsync(expectedType, () => client.GetAsync($"http://{Guid.NewGuid():N}"));
await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync($"http://{Guid.NewGuid():N}"));
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Net.Http/src/Resources/Strings.resx
Expand Up @@ -370,7 +370,7 @@
<value>Cannot access a closed stream.</value>
</data>
<data name="net_http_invalid_proxy_scheme" xml:space="preserve">
<value>Only the 'http', 'socks4', 'socks4a' and 'socks5' schemes are allowed for proxies.</value>
<value>Only the 'http', 'https', 'socks4', 'socks4a' and 'socks5' schemes are allowed for proxies.</value>
</data>
<data name="net_http_request_invalid_char_encoding" xml:space="preserve">
<value>Request headers must contain only ASCII characters.</value>
Expand Down
Expand Up @@ -114,6 +114,7 @@ internal sealed class HttpConnectionPool : IDisposable
private readonly SslClientAuthenticationOptions? _sslOptionsHttp2;
private readonly SslClientAuthenticationOptions? _sslOptionsHttp2Only;
private SslClientAuthenticationOptions? _sslOptionsHttp3;
private SslClientAuthenticationOptions? _sslOptionsProxy;

/// <summary>Whether the pool has been used since the last time a cleanup occurred.</summary>
private bool _usedSinceLastCleanup = true;
Expand Down Expand Up @@ -302,6 +303,12 @@ public HttpConnectionPool(HttpConnectionPoolManager poolManager, HttpConnectionK
_http2RequestQueue = new RequestQueue<Http2Connection?>();
}

if (_proxyUri != null && HttpUtilities.IsSupportedSecureScheme(_proxyUri.Scheme))
{
_sslOptionsProxy = ConstructSslOptions(poolManager, _proxyUri.IdnHost);
_sslOptionsProxy.ApplicationProtocols = null;
}

if (NetEventSource.Log.IsEnabled()) Trace($"{this}");
}

Expand Down Expand Up @@ -1525,10 +1532,18 @@ private async ValueTask<(Stream, TransportContext?)> ConnectAsync(HttpRequestMes
case HttpConnectionKind.ProxyConnect:
Debug.Assert(_originAuthority != null);
stream = await ConnectToTcpHostAsync(_originAuthority.IdnHost, _originAuthority.Port, request, async, cancellationToken).ConfigureAwait(false);
if (_kind == HttpConnectionKind.ProxyConnect && _sslOptionsProxy != null)
{
stream = await ConnectHelper.EstablishSslConnectionAsync(_sslOptionsProxy, request, async, stream, cancellationToken).ConfigureAwait(false);
}
break;

case HttpConnectionKind.Proxy:
stream = await ConnectToTcpHostAsync(_proxyUri!.IdnHost, _proxyUri.Port, request, async, cancellationToken).ConfigureAwait(false);
if (_sslOptionsProxy != null)
{
stream = await ConnectHelper.EstablishSslConnectionAsync(_sslOptionsProxy, request, async, stream, cancellationToken).ConfigureAwait(false);
}
break;

case HttpConnectionKind.ProxyTunnel:
Expand Down
Expand Up @@ -25,7 +25,7 @@ namespace System.Net.Http
// (8) HttpConnection.SendAsyncCore: Write request to connection and read response
// Also, handle cookie processing
//
// Redirect and deompression handling are done above HttpConnectionPoolManager,
// Redirect and decompression handling are done above HttpConnectionPoolManager,
// in RedirectHandler and DecompressionHandler respectively.

/// <summary>Provides a set of connection pools, each for its own endpoint.</summary>
Expand Down
Expand Up @@ -128,11 +128,18 @@ private HttpEnvironmentProxy(Uri? httpProxy, Uri? httpsProxy, string? bypassList

int hostIndex = 0;
string protocol = "http";
ushort port = 80;

if (value.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
{
hostIndex = 7;
}
else if (value.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
hostIndex = 8;
protocol = "https";
port = 443;
}
else if (value.StartsWith("socks4://", StringComparison.OrdinalIgnoreCase))
{
hostIndex = 9;
Expand All @@ -156,7 +163,6 @@ private HttpEnvironmentProxy(Uri? httpProxy, Uri? httpsProxy, string? bypassList

string? user = null;
string? password = null;
ushort port = 80;
string host;

// Check if there is authentication part with user and possibly password.
Expand Down
Expand Up @@ -22,7 +22,7 @@ internal static class HttpUtilities
string.Equals(scheme, "wss", StringComparison.OrdinalIgnoreCase);

internal static bool IsSupportedProxyScheme(string scheme) =>
string.Equals(scheme, "http", StringComparison.OrdinalIgnoreCase) || IsSocksScheme(scheme);
string.Equals(scheme, "http", StringComparison.OrdinalIgnoreCase) || string.Equals(scheme, "https", StringComparison.OrdinalIgnoreCase) || IsSocksScheme(scheme);

internal static bool IsSocksScheme(string scheme) =>
string.Equals(scheme, "socks5", StringComparison.OrdinalIgnoreCase) ||
Expand Down
Expand Up @@ -663,10 +663,10 @@ public void SendAsync_ExpectedDiagnosticSynchronousExceptionActivityLogging()
using (HttpClientHandler handler = CreateHttpClientHandler(useVersion))
using (HttpClient client = CreateHttpClient(handler, useVersion))
{
// Set a https proxy.
// Set a ftp proxy.
// Forces a synchronous exception for SocketsHttpHandler.
// SocketsHttpHandler only allow http scheme for proxies.
handler.Proxy = new WebProxy($"https://foo.bar", false);
// SocketsHttpHandler only allow http & https & socks scheme for proxies.
handler.Proxy = new WebProxy($"ftp://foo.bar", false);
var request = new HttpRequestMessage(HttpMethod.Get, InvalidUri)
{
Version = Version.Parse(useVersion)
Expand Down
Expand Up @@ -7,13 +7,11 @@
using System.IO.Pipes;
using System.Linq;
using System.Net.Http.Headers;
using System.Net.Quic;
using System.Net.Security;
using System.Net.Sockets;
using System.Net.Test.Common;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
Expand Down Expand Up @@ -656,6 +654,45 @@ public sealed class SocketsHttpHandler_HttpClientHandler_SslProtocols_Test : Htt
public sealed class SocketsHttpHandler_HttpClientHandler_Proxy_Test : HttpClientHandler_Proxy_Test
{
public SocketsHttpHandler_HttpClientHandler_Proxy_Test(ITestOutputHelper output) : base(output) { }

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task Proxy_Https_Succeeds(bool secureUri)
{
var releaseServer = new TaskCompletionSource();
await LoopbackServer.CreateClientAndServerAsync(async uri =>
{
bool validationCalled = false;
using SocketsHttpHandler handler = CreateSocketsHttpHandler(allowAllCertificates: true);
handler.Proxy = new UseSpecifiedUriWebProxy(uri, new NetworkCredential("abc", "password"));
handler.SslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, error) =>
{
validationCalled = true;
return true;
};
using (HttpClient client = CreateHttpClient(handler))
{
HttpResponseMessage response = await client.GetAsync(secureUri ? "https://foo.bar/" : "http://foo.bar/");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.True(validationCalled);
}
}, server => server.AcceptConnectionAsync(async connection =>
{
await connection.ReadRequestHeaderAndSendResponseAsync();
if (secureUri)
{
// client will send CONNECT and if that succeeds it will negotiate TLS
var sslConnection = await LoopbackServer.Connection.CreateAsync(null, connection.Stream, new LoopbackServer.Options { UseSsl = true });
await sslConnection.ReadRequestHeaderAndSendResponseAsync();
}
}),
new LoopbackServer.Options { UseSsl = true });
}
}

public abstract class SocketsHttpHandler_TrailingHeaders_Test : HttpClientHandlerTestBase
Expand Down
Expand Up @@ -142,6 +142,8 @@ public void HttpProxy_EnvironmentProxy_Loaded()
[InlineData("socks4://1.2.3.4:8888/foo", "1.2.3.4", "8888", null, null)]
[InlineData("socks4a://1.2.3.4:8888/foo", "1.2.3.4", "8888", null, null)]
[InlineData("socks5://1.2.3.4:8888/foo", "1.2.3.4", "8888", null, null)]
[InlineData("https://1.1.1.5:3005", "1.1.1.5", "3005", null, null)]
[InlineData("https://1.1.1.5", "1.1.1.5", "443", null, null)]
public void HttpProxy_Uri_Parsing(string _input, string _host, string _port, string _user, string _password)
{
RemoteExecutor.Invoke((input, host, port, user, password) =>
Expand Down

0 comments on commit 28151b5

Please sign in to comment.