Files

202 lines
6.6 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
{
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
{
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();
}
}