Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion src/libraries/System.Net.Http/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@
<value>Cannot access a closed stream.</value>
</data>
<data name="net_http_invalid_proxy_scheme" xml:space="preserve">
<value>Only the 'http', 'https', 'socks4', 'socks4a' and 'socks5' schemes are allowed for proxies.</value>
<value>Only the 'http', 'https', 'socks4', 'socks4a', 'socks5', and 'socks5h' 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
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ private HttpEnvironmentProxy(Uri? httpProxy, Uri? httpsProxy, string? bypassList
hostIndex = 9;
protocol = "socks5";
}
else if (value.StartsWith("socks5h://", StringComparison.OrdinalIgnoreCase))
{
hostIndex = 10;
protocol = "socks5h";
}
else if (value.StartsWith("socks4a://", StringComparison.OrdinalIgnoreCase))
{
hostIndex = 10;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal static bool IsSupportedProxyScheme(string scheme) =>

internal static bool IsSocksScheme(string scheme) =>
string.Equals(scheme, "socks5", StringComparison.OrdinalIgnoreCase) ||
string.Equals(scheme, "socks5h", StringComparison.OrdinalIgnoreCase) ||
string.Equals(scheme, "socks4a", StringComparison.OrdinalIgnoreCase) ||
string.Equals(scheme, "socks4", StringComparison.OrdinalIgnoreCase);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public static async ValueTask EstablishSocksTunnelAsync(Stream stream, string ho
{
NetworkCredential? credentials = proxyCredentials?.GetCredential(proxyUri, proxyUri.Scheme);

if (string.Equals(proxyUri.Scheme, "socks5", StringComparison.OrdinalIgnoreCase))
if (string.Equals(proxyUri.Scheme, "socks5", StringComparison.OrdinalIgnoreCase) ||
string.Equals(proxyUri.Scheme, "socks5h", StringComparison.OrdinalIgnoreCase))
{
await EstablishSocks5TunnelAsync(stream, host, port, credentials, async).ConfigureAwait(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public abstract class SocksProxyTest : HttpClientHandlerTestBase
{
public SocksProxyTest(ITestOutputHelper helper) : base(helper) { }

private static string[] Hosts(string socksScheme) => socksScheme == "socks5"
private static string[] Hosts(string socksScheme) => socksScheme == "socks5" || socksScheme == "socks5h"
? new[] { "localhost", "127.0.0.1", "::1" }
: new[] { "localhost", "127.0.0.1" };

public static IEnumerable<object[]> TestLoopbackAsync_MemberData() =>
from scheme in new[] { "socks4", "socks4a", "socks5" }
from scheme in new[] { "socks4", "socks4a", "socks5", "socks5h" }
from useSsl in BoolValues
from useAuth in BoolValues
from host in Hosts(scheme)
Expand Down Expand Up @@ -77,7 +77,7 @@ public static IEnumerable<object[]> TestExceptionalAsync_MemberData()

yield return new object[] { "socks4", new string('a', 256), false, null, "Failed to resolve the destination host to an IPv4 address." };

foreach (string scheme in new[] { "socks4a", "socks5" })
foreach (string scheme in new[] { "socks4a", "socks5", "socks5h" })
{
yield return new object[] { scheme, new string('a', 256), false, null, "Encoding the host took more than the maximum of 255 bytes." };
}
Expand All @@ -86,6 +86,10 @@ public static IEnumerable<object[]> TestExceptionalAsync_MemberData()
yield return new object[] { "socks5", "localhost", true, new NetworkCredential("bad_username", "bad_password"), "Failed to authenticate with the SOCKS server." };
yield return new object[] { "socks5", "localhost", true, new NetworkCredential(new string('a', 256), "foo"), "Encoding the UserName took more than the maximum of 255 bytes." };
yield return new object[] { "socks5", "localhost", true, new NetworkCredential("foo", new string('a', 256)), "Encoding the Password took more than the maximum of 255 bytes." };
yield return new object[] { "socks5h", "localhost", true, null, "SOCKS server did not return a suitable authentication method." };
yield return new object[] { "socks5h", "localhost", true, new NetworkCredential("bad_username", "bad_password"), "Failed to authenticate with the SOCKS server." };
yield return new object[] { "socks5h", "localhost", true, new NetworkCredential(new string('a', 256), "foo"), "Encoding the UserName took more than the maximum of 255 bytes." };
yield return new object[] { "socks5h", "localhost", true, new NetworkCredential("foo", new string('a', 256)), "Encoding the Password took more than the maximum of 255 bytes." };
}

[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ await RemoteExecutor.Invoke(() =>
[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("socks5h://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)]
// Everything before the last '@' is considered as user info (unlike regular Uri parsing).
Expand Down
Loading