diff --git a/v2rayN/v2rayN/Handler/V2rayConfigHandler.cs b/v2rayN/v2rayN/Handler/V2rayConfigHandler.cs index b3f25e06462..127f2b7a991 100644 --- a/v2rayN/v2rayN/Handler/V2rayConfigHandler.cs +++ b/v2rayN/v2rayN/Handler/V2rayConfigHandler.cs @@ -1,8 +1,12 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; using v2rayN.Base; using v2rayN.Mode; @@ -392,7 +396,7 @@ private static int outbound(Config config, ref V2rayConfig v2rayConfig) outbound.mux.enabled = false; outbound.mux.concurrency = -1; - + outbound.protocol = "shadowsocks"; outbound.settings.vnext = null; @@ -454,7 +458,7 @@ private static int boundStreamSettings(Config config, string iobound, ref Stream { //远程服务器底层传输配置 streamSettings.network = config.network(); - string host = config.requestHost(); + string host = config.requestHost(); //if tls if (config.streamSecurity() == Global.StreamSecurity) { @@ -1195,7 +1199,7 @@ public static VmessItem ImportFromClipboardConfig(string clipboardData, out stri int indexSplit = result.IndexOf("?"); if (indexSplit > 0) { - vmessItem = ResolveVmess4Kitsunebi(result); + vmessItem = ResolveStdVmess(result) ?? ResolveVmess4Kitsunebi(result); } else { @@ -1242,68 +1246,21 @@ public static VmessItem ImportFromClipboardConfig(string clipboardData, out stri { msg = UIRes.I18N("ConfigurationFormatIncorrect"); - vmessItem.configType = (int)EConfigType.Shadowsocks; - result = result.Substring(Global.ssProtocol.Length); - //remark - int indexRemark = result.IndexOf("#"); - if (indexRemark > 0) + vmessItem = ResolveSSLegacy(result); + if (vmessItem == null) { - try - { - vmessItem.remarks = WebUtility.UrlDecode(result.Substring(indexRemark + 1, result.Length - indexRemark - 1)); - } - catch { } - result = result.Substring(0, indexRemark); + vmessItem = ResolveSip002(result); } - //part decode - int indexS = result.IndexOf("@"); - if (indexS > 0) - { - result = Utils.Base64Decode(result.Substring(0, indexS)) + result.Substring(indexS, result.Length - indexS); - } - else - { - result = Utils.Base64Decode(result); - } - - //密码中可能包含“@”,所以从后往前搜索 - int indexAddressAndPort = result.LastIndexOf("@"); - if (indexAddressAndPort < 0) + if (vmessItem == null) { return null; } - string addressAndPort = result.Substring(indexAddressAndPort + 1); - string securityAndId = result.Substring(0, indexAddressAndPort); - - //IPv6地址中包含“:”,所以从后往前搜索 - int indexPort = addressAndPort.LastIndexOf(":"); - if (indexPort < 0) + if (vmessItem.address.Length == 0 || vmessItem.port == 0 || vmessItem.security.Length == 0 || vmessItem.id.Length == 0) { return null; } - //加密方式中不包含“:”,所以从前往后搜索 - int indexId = securityAndId.IndexOf(":"); - if (indexId < 0) - { - return null; - } - - string address = addressAndPort.Substring(0, indexPort); - string port = addressAndPort.Substring(indexPort + 1); - string security = securityAndId.Substring(0, indexId); - string id = securityAndId.Substring(indexId + 1); - - //所有字段均不能为空 - if (address.Length == 0 || port.Length == 0 || security.Length == 0 || id.Length == 0) - { - return null; - } - - vmessItem.address = address; - vmessItem.port = Utils.ToInt(port); - vmessItem.security = security; - vmessItem.id = id; + vmessItem.configType = (int)EConfigType.Shadowsocks; } else if (result.StartsWith(Global.socksProtocol)) { @@ -1428,6 +1385,177 @@ private static VmessItem ResolveVmess4Kitsunebi(string result) return vmessItem; } + private static VmessItem ResolveSip002(string result) + { + Uri parsedUrl; + try + { + parsedUrl = new Uri(result); + } + catch (UriFormatException) + { + return null; + } + VmessItem server = new VmessItem + { + remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), + address = parsedUrl.IdnHost, + port = parsedUrl.Port, + }; + + // parse base64 UserInfo + string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped); + string base64 = rawUserInfo.Replace('-', '+').Replace('_', '/'); // Web-safe base64 to normal base64 + string userInfo; + try + { + userInfo = Encoding.UTF8.GetString(Convert.FromBase64String( + base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))); + } + catch (FormatException) + { + return null; + } + string[] userInfoParts = userInfo.Split(new char[] { ':' }, 2); + if (userInfoParts.Length != 2) + { + return null; + } + server.security = userInfoParts[0]; + server.id = userInfoParts[1]; + + NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query); + if (queryParameters["plugin"] != null) + { + return null; + } + + return server; + } + + private static readonly Regex UrlFinder = new Regex(@"ss://(?[A-Za-z0-9+-/=_]+)(?:#(?\S+))?", RegexOptions.IgnoreCase); + private static readonly Regex DetailsParser = new Regex(@"^((?.+?):(?.*)@(?.+?):(?\d+?))$", RegexOptions.IgnoreCase); + + private static VmessItem ResolveSSLegacy(string result) + { + var match = UrlFinder.Match(result); + if (!match.Success) + return null; + + VmessItem server = new VmessItem(); + var base64 = match.Groups["base64"].Value.TrimEnd('/'); + var tag = match.Groups["tag"].Value; + if (!tag.IsNullOrEmpty()) + { + server.remarks = HttpUtility.UrlDecode(tag, Encoding.UTF8); + } + Match details; + try + { + details = DetailsParser.Match(Encoding.UTF8.GetString(Convert.FromBase64String( + base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '=')))); + } + catch (FormatException) + { + return null; + } + if (!details.Success) + return null; + server.security = details.Groups["method"].Value; + server.id = details.Groups["password"].Value; + server.address = details.Groups["hostname"].Value; + server.port = int.Parse(details.Groups["port"].Value); + return server; + } + + + private static readonly Regex StdVmessUserInfo = new Regex( + @"^(?[a-z]+)(\+(?[a-z]+))?:(?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-(?[0-9]+)$"); + + private static VmessItem ResolveStdVmess(string result) + { + VmessItem i = new VmessItem + { + configType = (int)EConfigType.Vmess, + security = "auto" + }; + + Uri u = new Uri(result); + + i.address = u.IdnHost; + i.port = u.Port; + i.remarks = u.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + var q = HttpUtility.ParseQueryString(u.Query); + + var m = StdVmessUserInfo.Match(u.UserInfo); + if (!m.Success) return null; + + i.id = m.Groups["id"].Value; + if (!int.TryParse(m.Groups["alterId"].Value, out int aid)) + { + return null; + } + i.alterId = aid; + + if (m.Groups["streamSecurity"].Success) + { + i.streamSecurity = m.Groups["streamSecurity"].Value; + } + switch (i.streamSecurity) + { + case "tls": + // TODO tls config + break; + default: + if (!string.IsNullOrWhiteSpace(i.streamSecurity)) + return null; + break; + } + + i.network = m.Groups["network"].Value; + switch (i.network) + { + case "tcp": + string t1 = q["type"] ?? "none"; + i.headerType = t1; + // TODO http option + + break; + case "kcp": + i.headerType = q["type"] ?? "none"; + // TODO kcp seed + break; + + case "ws": + string p1 = q["path"] ?? "/"; + string h1 = q["host"] ?? ""; + i.requestHost = h1; + i.path = p1; + break; + + case "http": + i.network = "h2"; + string p2 = q["path"] ?? "/"; + string h2 = q["host"] ?? ""; + i.requestHost = h2; + i.path = p2; + break; + + case "quic": + string s = q["security"] ?? "none"; + string k = q["key"] ?? ""; + string t3 = q["type"] ?? "none"; + i.headerType = t3; + i.requestHost = s; + i.path = k; + break; + + default: + return null; + } + + return i; + } #endregion #region Gen speedtest config @@ -1449,7 +1577,7 @@ public static string GenerateClientSpeedtestConfigString(Config config, List +