using System.Buffers.Text; using System.Net; using System.Text; using Proxd_man.Config.Outbound; using Proxd_man.Config.Stream; namespace Proxd_man.ShareLink; /// /// Shadowsocks 分享链接解析器. /// /// 遵循了 Shadowsocks 的 SIP002 URI Scheme 规范. /// 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 }; } } /// /// Shadowsocks 分享链接解析器. /// /// 这个版本使用 Uri 进行解析, 而不是手搓解析过程. /// /// 遵循了 Shadowsocks 的 SIP002 URI Scheme 规范. /// 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(); } }