feat: 目前完成了订阅功能和 Xray-Core 的更新管理.
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Proxd_man.Config.Outbound;
|
||||
|
||||
namespace Proxd_man.ShareLink;
|
||||
|
||||
public class ShareLinkParser
|
||||
{
|
||||
|
||||
private static readonly Lazy<List<IShareLinkParser>> parser = new(() => [
|
||||
new ShadowSocksLinkParserV2()
|
||||
]);
|
||||
|
||||
public static BaseOutboundSetting Parse(string shareLink)
|
||||
{
|
||||
foreach (var p in parser.Value.Where(p => p.CanParse(shareLink)))
|
||||
{
|
||||
return p.Parse(shareLink);
|
||||
}
|
||||
|
||||
throw new InvalidShareLinkException("不支持的分享链接: " + shareLink);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface IShareLinkParser
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 检查该分享链接是否可以被该实现解析.
|
||||
///
|
||||
/// 该方法只需要检查是否可以解析, 并不需要检查是否正确.
|
||||
/// </summary>
|
||||
/// <param name="shareLink">要尝试解析的出站配置分享链接.</param>
|
||||
/// <returns>如果可以被实现解析, 则返回 true.</returns>
|
||||
bool CanParse(string shareLink);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将分享链接解析为出站配置.
|
||||
/// </summary>
|
||||
/// <param name="shareLink">要解析的分享链接.</param>
|
||||
/// <returns>返回分享链接解析得出的出站配置.</returns>
|
||||
/// <exception cref="InvalidShareLinkException">当分享链接错误导致无法解析时抛出异常.</exception>
|
||||
BaseOutboundSetting Parse(string shareLink);
|
||||
|
||||
}
|
||||
|
||||
public class InvalidShareLinkException(string message, Exception? cause = null) : Exception(message, cause);
|
||||
Reference in New Issue
Block a user