Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for https proxy #87638

Merged
merged 5 commits into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -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
Original file line number Diff line number Diff line change
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 @@ public ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal static bool IsSecureWebSocketScheme(string scheme) =>
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);
wfurt marked this conversation as resolved.
Show resolved Hide resolved

internal static bool IsSocksScheme(string scheme) =>
string.Equals(scheme, "socks5", StringComparison.OrdinalIgnoreCase) ||
Expand Down
Original file line number Diff line number Diff line change
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 SocketsHttpHandler_HttpClientHandler_SslProtocols_Test(ITestOutputHelper
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
Original file line number Diff line number Diff line change
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