mirror of
https://github.com/2dust/v2rayN.git
synced 2026-11-04 19:45:00 +03:00
522bca492c
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>
121 lines
3.3 KiB
C#
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;
|
|
}
|
|
}
|