Files
v2rayN/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs
T
Wei-Lun Li 522bca492c 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>
2026-11-03 16:00:48 +08:00

121 lines
3.3 KiB
C#

namespace ServiceLib.Handler.Fmt;
public class SocksFmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
var item = ResolveSocksNew(str) ?? ResolveSocks(str);
if (item == null)
{
return null;
}
if (item.Address.Length == 0 || item.Port == 0)
{
return null;
}
item.ConfigType = EConfigType.SOCKS;
return item;
}
public static string? ToUri(ProfileItem? item)
{
if (item == null)
{
return null;
}
var remark = string.Empty;
if (item.Remarks.IsNotEmpty())
{
remark = "#" + Utils.UrlEncode(item.Remarks);
}
//new
var pw = Utils.Base64Encode($"{item.Username}:{item.Password}", true);
return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark);
}
private static ProfileItem? ResolveSocks(string result)
{
ProfileItem item = new()
{
ConfigType = EConfigType.SOCKS
};
result = result[Global.ProtocolShares[EConfigType.SOCKS].Length..];
//remark
var indexRemark = result.IndexOf('#');
if (indexRemark > 0)
{
try
{
item.Remarks = Utils.UrlDecode(result.Substring(indexRemark + 1));
}
catch { }
result = result[..indexRemark];
}
//part decode
var indexS = result.IndexOf('@');
if (indexS > 0)
{
}
else
{
result = Utils.Base64Decode(result);
}
var arr1 = result.Split('@');
if (arr1.Length != 2)
{
return null;
}
var arr21 = arr1.First().Split(':');
var indexPort = arr1.Last().LastIndexOf(':');
if (arr21.Length != 2 || indexPort < 0)
{
return null;
}
item.Address = arr1[1][..indexPort];
item.Port = arr1[1][(indexPort + 1)..].ToInt();
item.Username = arr21.First();
item.Password = arr21[1];
return item;
}
private static ProfileItem? ResolveSocksNew(string result)
{
var parsedUrl = Utils.TryUri(result);
// 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;
}
ProfileItem item = new()
{
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
Address = parsedUrl.IdnHost,
Port = parsedUrl.Port,
};
// parse base64 UserInfo
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
if (rawUserInfo.IsNotEmpty())
{
var userInfoParts = rawUserInfo.Contains(':')
? rawUserInfo.Split(":", 2)
: Utils.Base64Decode(rawUserInfo).Split(":", 2);
if (userInfoParts.Length == 2)
{
item.Username = userInfoParts.First();
item.Password = userInfoParts.Last();
}
}
return item;
}
}