204 lines
6.7 KiB
C#
204 lines
6.7 KiB
C#
using System.Buffers.Text;
|
|
using System.Net;
|
|
using System.Text;
|
|
using Proxd_man.Config.Outbound;
|
|
using Proxd_man.Config.Stream;
|
|
|
|
namespace Proxd_man.ShareLink;
|
|
|
|
/// <summary>
|
|
/// Shadowsocks 分享链接解析器.
|
|
///
|
|
/// 遵循了 Shadowsocks 的 <a href="https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme"> SIP002 URI Scheme 规范</a>.
|
|
/// </summary>
|
|
public class ShadowSocksLinkParser : IShareLinkParser
|
|
{
|
|
public bool CanParse(string shareLink)
|
|
{
|
|
return shareLink.StartsWith("ss://", StringComparison.CurrentCultureIgnoreCase);
|
|
}
|
|
|
|
public BaseOutboundSetting Parse(string shareLink)
|
|
{
|
|
var content = shareLink[5..];
|
|
if (content.Length == 0)
|
|
{
|
|
throw new InvalidShareLinkException("Shadowsocks 分享链接内容为空.");
|
|
}
|
|
|
|
// 按照顺序, 先解析 userInfo.
|
|
var userInfoEndAt = content.IndexOf('@');
|
|
if (userInfoEndAt == -1)
|
|
{
|
|
throw new InvalidShareLinkException("Shadowsocks 分享链接中未包含用户信息.");
|
|
}
|
|
|
|
var userInfo = content[..userInfoEndAt];
|
|
string userInfoContent;
|
|
|
|
if (userInfo.Contains(':'))
|
|
{
|
|
userInfoContent = userInfo.Trim();
|
|
}
|
|
else
|
|
{
|
|
if (!Base64.IsValid(userInfo))
|
|
{
|
|
throw new InvalidShareLinkException("Shadowsocks 分享链接中的用户信息不是有效的 Base64 字符串.");
|
|
}
|
|
|
|
var userInfoBytes = Convert.FromBase64String(userInfo);
|
|
userInfoContent = Encoding.UTF8.GetString(userInfoBytes);
|
|
}
|
|
|
|
var userInfoParts = userInfoContent.Split(':', 2);
|
|
var method = userInfoParts[0];
|
|
var password = userInfoParts[1];
|
|
|
|
// 开始解析 userInfo 后面的内容.
|
|
content = content[(userInfoEndAt + 1)..];
|
|
|
|
var pathSeparatorAt = content.IndexOf('/');
|
|
var fragmentSeparatorAt = content.IndexOf('#');
|
|
int hostEndAt;
|
|
|
|
// 首先检查一下合法性, 遵循这些规则:
|
|
// 如果有 pathSeparator, 则检查是否有 "?", 且是否位于 pathSeparator 之后, 否则抛出异常.
|
|
// 其次, fragmentSeparator 只能出现在 pathSeparator 之后.
|
|
|
|
if (pathSeparatorAt != -1)
|
|
{
|
|
if (content.Contains('?') && content.IndexOf('?') < pathSeparatorAt)
|
|
{
|
|
throw new InvalidShareLinkException("Shadowsocks 分享链接中的 path 部分不合法.");
|
|
}
|
|
|
|
if (fragmentSeparatorAt != -1 && fragmentSeparatorAt < pathSeparatorAt)
|
|
{
|
|
throw new InvalidShareLinkException("Shadowsocks 分享链接中的 tag 部分不合法.");
|
|
}
|
|
|
|
hostEndAt = pathSeparatorAt;
|
|
} else if (fragmentSeparatorAt != -1)
|
|
{
|
|
hostEndAt = fragmentSeparatorAt;
|
|
}
|
|
else
|
|
{
|
|
hostEndAt = content.Length;
|
|
}
|
|
|
|
if (content[pathSeparatorAt..].Contains("?plugin="))
|
|
{
|
|
throw new InvalidShareLinkException("Shadowsocks 分享链接暂时不支持 plugin 参数.");
|
|
}
|
|
|
|
var name = fragmentSeparatorAt != -1 ? null :
|
|
// 需要 Url 解码
|
|
WebUtility.UrlDecode(content[(fragmentSeparatorAt + 1)..].Trim());
|
|
|
|
var hostContent = content[..hostEndAt];
|
|
var hostPortParts = hostContent.Split(':');
|
|
switch (hostPortParts.Length)
|
|
{
|
|
case > 2:
|
|
throw new InvalidShareLinkException("Shadowsocks 分享链接中的 host 部分不合法.");
|
|
case < 2:
|
|
throw new InvalidShareLinkException("Shadowsocks 分享链接中的 host 部分不包含端口.");
|
|
}
|
|
|
|
var host = hostPortParts[0];
|
|
if (!int.TryParse(hostPortParts[1], out var port))
|
|
{
|
|
throw new InvalidShareLinkException("Shadowsocks 分享链接中的端口不是一个数字.");
|
|
}
|
|
|
|
if (port is < 0 or > 65535)
|
|
{
|
|
throw new InvalidShareLinkException("Shadowsocks 分享链接中的端口超出了端口范围.");
|
|
}
|
|
|
|
return new ShadowSocksOutboundSetting
|
|
{
|
|
Protocol = OutboundProtocolType.Shadowsocks,
|
|
Name = name ?? $"{host}:{port}",
|
|
StreamSetting = new RawStreamSetting(),
|
|
|
|
Address = host,
|
|
Port = port,
|
|
Method = method,
|
|
Password = password
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shadowsocks 分享链接解析器.
|
|
///
|
|
/// 这个版本使用 Uri 进行解析, 而不是手搓解析过程.
|
|
///
|
|
/// 遵循了 Shadowsocks 的 <a href="https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme"> SIP002 URI Scheme 规范</a>.
|
|
/// </summary>
|
|
public class ShadowSocksLinkParserV2 : IShareLinkParser
|
|
{
|
|
public bool CanParse(string shareLink)
|
|
{
|
|
return shareLink.StartsWith("ss://", StringComparison.CurrentCultureIgnoreCase);
|
|
}
|
|
|
|
public BaseOutboundSetting Parse(string shareLink)
|
|
{
|
|
var shareLinkUri = new Uri(shareLink);
|
|
|
|
var userInfoContent = shareLinkUri.UserInfo;
|
|
|
|
if (!userInfoContent.Contains(':'))
|
|
{
|
|
if (!Base64.IsValid(userInfoContent))
|
|
{
|
|
throw new InvalidShareLinkException("Shadowsocks 分享链接中的用户信息不是有效的 Base64 字符串.");
|
|
}
|
|
|
|
var userInfoBytes = Convert.FromBase64String(userInfoContent);
|
|
userInfoContent = Encoding.UTF8.GetString(userInfoBytes);
|
|
}
|
|
|
|
var userInfoParts = userInfoContent.Split(':');
|
|
if (userInfoParts.Length != 2)
|
|
{
|
|
throw new InvalidShareLinkException("Shadowsocks 分享链接中的 userInfo 不合法.");
|
|
}
|
|
|
|
var method = WebUtility.UrlDecode(userInfoParts[0]);
|
|
var password = WebUtility.UrlDecode(userInfoParts[1]);
|
|
|
|
var host = shareLinkUri.Host;
|
|
var port = (ushort) shareLinkUri.Port;
|
|
|
|
return new ShadowSocksOutboundSetting
|
|
{
|
|
Protocol = OutboundProtocolType.Shadowsocks,
|
|
Name = string.IsNullOrEmpty(shareLinkUri.Fragment) ? $"{host}:{port}" : WebUtility.UrlDecode(shareLinkUri.Fragment.Trim()),
|
|
StreamSetting = new RawStreamSetting(),
|
|
|
|
Address = host,
|
|
Port = port,
|
|
Method = method,
|
|
Password = password
|
|
};
|
|
}
|
|
}
|
|
|
|
public class VMessLinkParser : IShareLinkParser
|
|
{
|
|
public bool CanParse(string shareLink)
|
|
{
|
|
return shareLink.StartsWith("vmess://", StringComparison.CurrentCultureIgnoreCase);
|
|
}
|
|
|
|
public BaseOutboundSetting Parse(string shareLink)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|