fix(fmt): import legacy base64 SOCKS links again (#10505)

SocksFmt.Resolve tries ResolveSocksNew first and only falls back to the
legacy socks://base64(user:pass@host:port) parser when System.Uri
rejects the link. System.Uri accepts most legacy links, though: it takes
the base64 as the host (lower-cased) and anything after a '/' as the
path. It only rejects a base64 that ends in '=' padding and has no '/',
so links without padding, or with a '/' or '_' in the base64, were
imported with the base64 as the address, port -1 and no credentials.

A legacy link has no port of its own, so let ResolveSocksNew give up
when the URI has none and leave the link to ResolveSocks.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Wei-Lun Li
2026-11-03 17:00:48 +09:00
committed by GitHub
parent 0f1f82af4e
commit 522bca492c
2 changed files with 44 additions and 1 deletions
@@ -0,0 +1,40 @@
namespace ServiceLib.Tests.Fmt;
public class SocksFmtTests
{
// The legacy form base64-encodes the whole "user:pass@host:port".
[Test]
[Arguments("socks://YWRtaW46YWRtaW5AMS4yLjMuNDoxMDgw#demo", "admin", "admin", "1.2.3.4", 1080)]
[Arguments("socks://dXNlcjpwYXNzQDEuMi4zLjQ6MTA4MA==#demo", "user", "pass", "1.2.3.4", 1080)]
[Arguments("socks://dXNlcjpxd2VydHk/QDEuMi4zLjQ6MTA4MA==#demo", "user", "qwerty?", "1.2.3.4", 1080)]
[Arguments("socks://dXNlcjpxd2VydHk_QDEuMi4zLjQ6MTA4MA#demo", "user", "qwerty?", "1.2.3.4", 1080)]
public async Task ResolveConfig_LegacyBase64_ShouldDecodeServerAndCredentials(
string shareUri, string username, string password, string address, int port)
{
var resolved = FmtHandler.ResolveConfig(shareUri, out var msg);
await resolved.Should().NotBeNull().Because($"uri: {shareUri}, msg: {msg}");
await resolved!.ConfigType.Should().BeEqualTo(EConfigType.SOCKS);
await resolved.Address.Should().BeEqualTo(address);
await resolved.Port.Should().BeEqualTo(port);
await resolved.Username.Should().BeEqualTo(username);
await resolved.Password.Should().BeEqualTo(password);
await resolved.Remarks.Should().BeEqualTo("demo");
}
[Test]
[Arguments("socks://YWRtaW46YWRtaW4@1.2.3.4:1080#demo", "admin", "admin")]
[Arguments("socks://admin:admin@1.2.3.4:1080#demo", "admin", "admin")]
[Arguments("socks://1.2.3.4:1080#demo", "", "")]
public async Task ResolveConfig_HostAndPort_ShouldKeepParsingIt(string shareUri, string username, string password)
{
var resolved = FmtHandler.ResolveConfig(shareUri, out var msg);
await resolved.Should().NotBeNull().Because($"uri: {shareUri}, msg: {msg}");
await resolved!.Address.Should().BeEqualTo("1.2.3.4");
await resolved.Port.Should().BeEqualTo(1080);
await resolved.Username.Should().BeEqualTo(username);
await resolved.Password.Should().BeEqualTo(password);
await resolved.Remarks.Should().BeEqualTo("demo");
}
}
+4 -1
View File
@@ -86,7 +86,10 @@ public class SocksFmt : BaseFmt
private static ProfileItem? ResolveSocksNew(string result)
{
var parsedUrl = Utils.TryUri(result);
if (parsedUrl == null)
// A legacy socks://base64(user:pass@host:port) link has no port of its own, but System.Uri
// accepts many of them, reading the base64 as the host (and whatever follows a '/' as the
// path). Leave those to ResolveSocks.
if (parsedUrl == null || parsedUrl.Port == -1)
{
return null;
}