diff --git a/v2rayN/v2rayN/Forms/AddServer3Form.cs b/v2rayN/v2rayN/Forms/AddServer3Form.cs index 8113a784..d1a4a858 100644 --- a/v2rayN/v2rayN/Forms/AddServer3Form.cs +++ b/v2rayN/v2rayN/Forms/AddServer3Form.cs @@ -119,7 +119,7 @@ namespace v2rayN.Forms { ClearServer(); - VmessItem vmessItem = V2rayConfigHandler.ImportFromClipboardConfig(Utils.GetClipboardData(), out string msg); + VmessItem vmessItem = ShareHandler.ImportFromClipboardConfig(Utils.GetClipboardData(), out string msg); if (vmessItem == null) { UI.ShowWarning(msg); diff --git a/v2rayN/v2rayN/Forms/AddServer4Form.cs b/v2rayN/v2rayN/Forms/AddServer4Form.cs index 23ec6aaa..29102347 100644 --- a/v2rayN/v2rayN/Forms/AddServer4Form.cs +++ b/v2rayN/v2rayN/Forms/AddServer4Form.cs @@ -108,7 +108,7 @@ namespace v2rayN.Forms { ClearServer(); - VmessItem vmessItem = V2rayConfigHandler.ImportFromClipboardConfig(Utils.GetClipboardData(), out string msg); + VmessItem vmessItem = ShareHandler.ImportFromClipboardConfig(Utils.GetClipboardData(), out string msg); if (vmessItem == null) { UI.ShowWarning(msg); diff --git a/v2rayN/v2rayN/Forms/AddServer5Form.cs b/v2rayN/v2rayN/Forms/AddServer5Form.cs index d426e5c6..32c7e48c 100644 --- a/v2rayN/v2rayN/Forms/AddServer5Form.cs +++ b/v2rayN/v2rayN/Forms/AddServer5Form.cs @@ -263,7 +263,7 @@ namespace v2rayN.Forms { ClearServer(); - VmessItem vmessItem = V2rayConfigHandler.ImportFromClipboardConfig(Utils.GetClipboardData(), out string msg); + VmessItem vmessItem = ShareHandler.ImportFromClipboardConfig(Utils.GetClipboardData(), out string msg); if (vmessItem == null) { UI.ShowWarning(msg); diff --git a/v2rayN/v2rayN/Forms/AddServerForm.cs b/v2rayN/v2rayN/Forms/AddServerForm.cs index bd80867b..ab54fd9b 100644 --- a/v2rayN/v2rayN/Forms/AddServerForm.cs +++ b/v2rayN/v2rayN/Forms/AddServerForm.cs @@ -268,7 +268,7 @@ namespace v2rayN.Forms { ClearServer(); - VmessItem vmessItem = V2rayConfigHandler.ImportFromClipboardConfig(Utils.GetClipboardData(), out string msg); + VmessItem vmessItem = ShareHandler.ImportFromClipboardConfig(Utils.GetClipboardData(), out string msg); if (vmessItem == null) { UI.ShowWarning(msg); diff --git a/v2rayN/v2rayN/Forms/MainForm.cs b/v2rayN/v2rayN/Forms/MainForm.cs index 96e6819e..67b78ee5 100644 --- a/v2rayN/v2rayN/Forms/MainForm.cs +++ b/v2rayN/v2rayN/Forms/MainForm.cs @@ -685,7 +685,7 @@ namespace v2rayN.Forms StringBuilder sb = new StringBuilder(); foreach (int v in lvSelecteds) { - string url = ConfigHandler.GetVmessQRCode(config, v); + string url = ShareHandler.GetShareUrl(config, v); if (Utils.IsNullOrEmpty(url)) { continue; @@ -708,7 +708,7 @@ namespace v2rayN.Forms StringBuilder sb = new StringBuilder(); foreach (int v in lvSelecteds) { - string url = ConfigHandler.GetVmessQRCode(config, v); + string url = ShareHandler.GetShareUrl(config, v); if (Utils.IsNullOrEmpty(url)) { continue; diff --git a/v2rayN/v2rayN/Forms/QRCodeControl.cs b/v2rayN/v2rayN/Forms/QRCodeControl.cs index 02d59b48..855db30d 100644 --- a/v2rayN/v2rayN/Forms/QRCodeControl.cs +++ b/v2rayN/v2rayN/Forms/QRCodeControl.cs @@ -24,7 +24,7 @@ namespace v2rayN.Forms { if (Index >= 0) { - string url = ConfigHandler.GetVmessQRCode(config, Index); + string url = ShareHandler.GetShareUrl(config, Index); if (Utils.IsNullOrEmpty(url)) { picQRCode.Image = null; diff --git a/v2rayN/v2rayN/Forms/RoutingSettingDetailsForm.Designer.cs b/v2rayN/v2rayN/Forms/RoutingSettingDetailsForm.Designer.cs index ee40a2d0..43d2de45 100644 --- a/v2rayN/v2rayN/Forms/RoutingSettingDetailsForm.Designer.cs +++ b/v2rayN/v2rayN/Forms/RoutingSettingDetailsForm.Designer.cs @@ -31,6 +31,8 @@ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(RoutingSettingDetailsForm)); this.panel1 = new System.Windows.Forms.Panel(); this.panel3 = new System.Windows.Forms.Panel(); + this.clbProtocol = new System.Windows.Forms.CheckedListBox(); + this.label3 = new System.Windows.Forms.Label(); this.txtPort = new System.Windows.Forms.TextBox(); this.label1 = new System.Windows.Forms.Label(); this.labRoutingTips = new System.Windows.Forms.Label(); @@ -46,8 +48,6 @@ this.txtIP = new System.Windows.Forms.TextBox(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.txtDomain = new System.Windows.Forms.TextBox(); - this.label3 = new System.Windows.Forms.Label(); - this.clbProtocol = new System.Windows.Forms.CheckedListBox(); this.panel3.SuspendLayout(); this.panel4.SuspendLayout(); this.panel2.SuspendLayout(); @@ -74,6 +74,23 @@ resources.ApplyResources(this.panel3, "panel3"); this.panel3.Name = "panel3"; // + // clbProtocol + // + this.clbProtocol.CheckOnClick = true; + resources.ApplyResources(this.clbProtocol, "clbProtocol"); + this.clbProtocol.FormattingEnabled = true; + this.clbProtocol.Items.AddRange(new object[] { + resources.GetString("clbProtocol.Items"), + resources.GetString("clbProtocol.Items1"), + resources.GetString("clbProtocol.Items2")}); + this.clbProtocol.MultiColumn = true; + this.clbProtocol.Name = "clbProtocol"; + // + // label3 + // + resources.ApplyResources(this.label3, "label3"); + this.label3.Name = "label3"; + // // txtPort // resources.ApplyResources(this.txtPort, "txtPort"); @@ -169,23 +186,6 @@ resources.ApplyResources(this.txtDomain, "txtDomain"); this.txtDomain.Name = "txtDomain"; // - // label3 - // - resources.ApplyResources(this.label3, "label3"); - this.label3.Name = "label3"; - // - // clbProtocol - // - this.clbProtocol.CheckOnClick = true; - resources.ApplyResources(this.clbProtocol, "clbProtocol"); - this.clbProtocol.FormattingEnabled = true; - this.clbProtocol.Items.AddRange(new object[] { - resources.GetString("clbProtocol.Items"), - resources.GetString("clbProtocol.Items1"), - resources.GetString("clbProtocol.Items2")}); - this.clbProtocol.MultiColumn = true; - this.clbProtocol.Name = "clbProtocol"; - // // RoutingSettingDetailsForm // resources.ApplyResources(this, "$this"); diff --git a/v2rayN/v2rayN/Forms/RoutingSettingDetailsForm.cs b/v2rayN/v2rayN/Forms/RoutingSettingDetailsForm.cs index a0511439..65fe6a8f 100644 --- a/v2rayN/v2rayN/Forms/RoutingSettingDetailsForm.cs +++ b/v2rayN/v2rayN/Forms/RoutingSettingDetailsForm.cs @@ -88,6 +88,29 @@ namespace v2rayN.Forms private void btnOK_Click(object sender, EventArgs e) { EndBindingData(); + var hasRule = false; + if (routingItem.domain != null && routingItem.domain.Count > 0) + { + hasRule = true; + } + if (routingItem.ip != null && routingItem.ip.Count > 0) + { + hasRule = true; + } + if (routingItem.protocol != null && routingItem.protocol.Count > 0) + { + hasRule = true; + } + if (!Utils.IsNullOrEmpty(routingItem.port)) + { + hasRule = true; + } + if (!hasRule) + { + UI.ShowWarning(string.Format(UIRes.I18N("RoutingRuleDetailRequiredTips"), "Port/Protocol/Domain/IP")); + return; + } + if (ConfigHandler.AddRoutingRule(ref config, routingItem, EditIndex) == 0) { this.DialogResult = DialogResult.OK; diff --git a/v2rayN/v2rayN/Forms/RoutingSettingDetailsForm.resx b/v2rayN/v2rayN/Forms/RoutingSettingDetailsForm.resx index 0481d1dd..855817b0 100644 --- a/v2rayN/v2rayN/Forms/RoutingSettingDetailsForm.resx +++ b/v2rayN/v2rayN/Forms/RoutingSettingDetailsForm.resx @@ -193,7 +193,7 @@ 36 - protocol + Protocol label3 @@ -420,54 +420,6 @@ 2 - - btnClose - - - System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - panel4 - - - 0 - - - btnOK - - - System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - panel4 - - - 1 - - - Bottom - - - 0, 516 - - - 742, 60 - - - 10 - - - panel4 - - - System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 1 - NoControl @@ -522,53 +474,44 @@ 1 - - groupBox2 + + Bottom - - System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 0, 516 - - panel2 + + 742, 60 - - 0 + + 10 - - groupBox1 + + panel4 - - System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - panel2 - - - 1 - - - Fill - - - 0, 121 - - - 742, 395 - - - 11 - - - panel2 - - + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + $this - - 0 + + 1 + + + Fill + + + 3, 17 + + + True + + + 344, 375 + + + 25 txtIP @@ -609,32 +552,20 @@ 0 - + Fill - + 3, 17 - + True - - 344, 375 + + 386, 375 - - 25 - - - txtIP - - - System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - groupBox2 - - - 0 + + 24 txtDomain @@ -675,31 +606,28 @@ 1 - + Fill - - 3, 17 + + 0, 121 - - True + + 742, 395 - - 386, 375 + + 11 - - 24 + + panel2 - - txtDomain + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + $this - - groupBox1 - - + 0 diff --git a/v2rayN/v2rayN/Handler/ConfigHandler.cs b/v2rayN/v2rayN/Handler/ConfigHandler.cs index cfdd2281..abdd02f5 100644 --- a/v2rayN/v2rayN/Handler/ConfigHandler.cs +++ b/v2rayN/v2rayN/Handler/ConfigHandler.cs @@ -339,100 +339,6 @@ namespace v2rayN.Handler Utils.ToJsonFile(config, Utils.GetPath(configRes)); } - /// - /// 取得服务器QRCode配置 - /// - /// - /// - /// - public static string GetVmessQRCode(Config config, int index) - { - try - { - string url = string.Empty; - - VmessItem vmessItem = config.vmess[index]; - if (vmessItem.configType == (int)EConfigType.Vmess) - { - VmessQRCode vmessQRCode = new VmessQRCode - { - v = vmessItem.configVersion.ToString(), - ps = vmessItem.remarks.TrimEx(), //备注也许很长 ; - add = vmessItem.address, - port = vmessItem.port.ToString(), - id = vmessItem.id, - aid = vmessItem.alterId.ToString(), - net = vmessItem.network, - type = vmessItem.headerType, - host = vmessItem.requestHost, - path = vmessItem.path, - tls = vmessItem.streamSecurity - }; - - url = Utils.ToJson(vmessQRCode); - url = Utils.Base64Encode(url); - url = string.Format("{0}{1}", Global.vmessProtocol, url); - - } - else if (vmessItem.configType == (int)EConfigType.Shadowsocks) - { - string remark = string.Empty; - if (!Utils.IsNullOrEmpty(vmessItem.remarks)) - { - remark = "#" + WebUtility.UrlEncode(vmessItem.remarks); - } - url = string.Format("{0}:{1}@{2}:{3}", - vmessItem.security, - vmessItem.id, - vmessItem.address, - vmessItem.port); - url = Utils.Base64Encode(url); - url = string.Format("{0}{1}{2}", Global.ssProtocol, url, remark); - } - else if (vmessItem.configType == (int)EConfigType.Socks) - { - string remark = string.Empty; - if (!Utils.IsNullOrEmpty(vmessItem.remarks)) - { - remark = "#" + WebUtility.UrlEncode(vmessItem.remarks); - } - url = string.Format("{0}:{1}@{2}:{3}", - vmessItem.security, - vmessItem.id, - vmessItem.address, - vmessItem.port); - url = Utils.Base64Encode(url); - url = string.Format("{0}{1}{2}", Global.socksProtocol, url, remark); - } - else if (vmessItem.configType == (int)EConfigType.Trojan) - { - string remark = string.Empty; - if (!Utils.IsNullOrEmpty(vmessItem.remarks)) - { - remark = "#" + WebUtility.UrlEncode(vmessItem.remarks); - } - string query = string.Empty; - if (!Utils.IsNullOrEmpty(vmessItem.requestHost)) - { - query = string.Format("?sni={0}", vmessItem.requestHost); - } - url = string.Format("{0}@{1}:{2}", - vmessItem.id, - vmessItem.address, - vmessItem.port); - url = string.Format("{0}{1}{2}{3}", Global.trojanProtocol, url, query, remark); - } - else - { - } - return url; - } - catch - { - return ""; - } - } - /// /// 移动服务器 /// @@ -824,7 +730,7 @@ namespace v2rayN.Handler } continue; } - VmessItem vmessItem = V2rayConfigHandler.ImportFromClipboardConfig(str, out string msg); + VmessItem vmessItem = ShareHandler.ImportFromClipboardConfig(str, out string msg); if (vmessItem == null) { continue; @@ -858,6 +764,13 @@ namespace v2rayN.Handler countServers++; } } + else if (vmessItem.configType == (int)EConfigType.VLESS) + { + if (AddVlessServer(ref config, vmessItem, -1) == 0) + { + countServers++; + } + } } return countServers; } diff --git a/v2rayN/v2rayN/Handler/ShareHandler.cs b/v2rayN/v2rayN/Handler/ShareHandler.cs new file mode 100644 index 00000000..766f16e0 --- /dev/null +++ b/v2rayN/v2rayN/Handler/ShareHandler.cs @@ -0,0 +1,683 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Web; +using v2rayN.Base; +using v2rayN.Mode; + +namespace v2rayN.Handler +{ + class ShareHandler + { + + #region GetShareUrl + + /// + /// GetShareUrl + /// + /// + /// + /// + public static string GetShareUrl(Config config, int index) + { + try + { + string url = string.Empty; + + VmessItem item = config.vmess[index]; + if (item.configType == (int)EConfigType.Vmess) + { + VmessQRCode vmessQRCode = new VmessQRCode + { + v = item.configVersion.ToString(), + ps = item.remarks.TrimEx(), //备注也许很长 ; + add = item.address, + port = item.port.ToString(), + id = item.id, + aid = item.alterId.ToString(), + net = item.network, + type = item.headerType, + host = item.requestHost, + path = item.path, + tls = item.streamSecurity + }; + + url = Utils.ToJson(vmessQRCode); + url = Utils.Base64Encode(url); + url = string.Format("{0}{1}", Global.vmessProtocol, url); + + } + else if (item.configType == (int)EConfigType.Shadowsocks) + { + string remark = string.Empty; + if (!Utils.IsNullOrEmpty(item.remarks)) + { + remark = "#" + WebUtility.UrlEncode(item.remarks); + } + url = string.Format("{0}:{1}@{2}:{3}", + item.security, + item.id, + item.address, + item.port); + url = Utils.Base64Encode(url); + url = string.Format("{0}{1}{2}", Global.ssProtocol, url, remark); + } + else if (item.configType == (int)EConfigType.Socks) + { + string remark = string.Empty; + if (!Utils.IsNullOrEmpty(item.remarks)) + { + remark = "#" + WebUtility.UrlEncode(item.remarks); + } + url = string.Format("{0}:{1}@{2}:{3}", + item.security, + item.id, + item.address, + item.port); + url = Utils.Base64Encode(url); + url = string.Format("{0}{1}{2}", Global.socksProtocol, url, remark); + } + else if (item.configType == (int)EConfigType.Trojan) + { + string remark = string.Empty; + if (!Utils.IsNullOrEmpty(item.remarks)) + { + remark = "#" + WebUtility.UrlEncode(item.remarks); + } + string query = string.Empty; + if (!Utils.IsNullOrEmpty(item.requestHost)) + { + query = string.Format("?sni={0}", item.requestHost); + } + url = string.Format("{0}@{1}:{2}", + item.id, + item.address, + item.port); + url = string.Format("{0}{1}{2}{3}", Global.trojanProtocol, url, query, remark); + } + else if (item.configType == (int)EConfigType.VLESS) + { + string remark = string.Empty; + if (!Utils.IsNullOrEmpty(item.remarks)) + { + remark = "#" + WebUtility.UrlEncode(item.remarks); + } + var dicQuery = new Dictionary(); + if (!Utils.IsNullOrEmpty(item.flow)) + { + dicQuery.Add("flow", item.flow); + } + if (!Utils.IsNullOrEmpty(item.security)) + { + dicQuery.Add("encryption", item.security); + } + else + { + dicQuery.Add("encryption", "none"); + } + if (!Utils.IsNullOrEmpty(item.streamSecurity)) + { + dicQuery.Add("security", item.streamSecurity); + } + else + { + dicQuery.Add("security", "none"); + } + if (!Utils.IsNullOrEmpty(item.network)) + { + dicQuery.Add("type", item.network); + } + else + { + dicQuery.Add("type", "tcp"); + } + + switch (item.network) + { + case "tcp": + if (!Utils.IsNullOrEmpty(item.headerType)) + { + dicQuery.Add("headerType", item.headerType); + } + else + { + dicQuery.Add("headerType", "none"); + } + if (!Utils.IsNullOrEmpty(item.requestHost)) + { + dicQuery.Add("host", item.requestHost); + } + break; + case "kcp": + if (!Utils.IsNullOrEmpty(item.headerType)) + { + dicQuery.Add("headerType", item.headerType); + } + else + { + dicQuery.Add("headerType", "none"); + } + if (!Utils.IsNullOrEmpty(item.path)) + { + dicQuery.Add("seed", item.path); + } + break; + + case "ws": + if (!Utils.IsNullOrEmpty(item.requestHost)) + { + dicQuery.Add("host", item.requestHost); + } + if (!Utils.IsNullOrEmpty(item.path)) + { + dicQuery.Add("path", item.path); + } + break; + + case "http": + case "h2": + dicQuery["type"] = "http"; + if (!Utils.IsNullOrEmpty(item.requestHost)) + { + dicQuery.Add("host", item.requestHost); + } + if (!Utils.IsNullOrEmpty(item.path)) + { + dicQuery.Add("path", item.path); + } + break; + + case "quic": + if (!Utils.IsNullOrEmpty(item.headerType)) + { + dicQuery.Add("headerType", item.headerType); + } + else + { + dicQuery.Add("headerType", "none"); + } + dicQuery.Add("quicSecurity", item.requestHost); + dicQuery.Add("key", item.path); + break; + } + string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray()); + + url = string.Format("{0}@{1}:{2}", + item.id, + item.address, + item.port); + url = string.Format("{0}{1}{2}{3}", Global.vlessProtocol, url, query, remark); + } + else + { + } + return url; + } + catch + { + return ""; + } + } + + #endregion + + #region ImportShareUrl + + + /// + /// 从剪贴板导入URL + /// + /// + /// + /// + public static VmessItem ImportFromClipboardConfig(string clipboardData, out string msg) + { + msg = string.Empty; + VmessItem vmessItem = new VmessItem(); + + try + { + //载入配置文件 + string result = clipboardData.TrimEx();// Utils.GetClipboardData(); + if (Utils.IsNullOrEmpty(result)) + { + msg = UIRes.I18N("FailedReadConfiguration"); + return null; + } + + if (result.StartsWith(Global.vmessProtocol)) + { + int indexSplit = result.IndexOf("?"); + if (indexSplit > 0) + { + vmessItem = ResolveStdVmess(result) ?? ResolveVmess4Kitsunebi(result); + } + else + { + vmessItem.configType = (int)EConfigType.Vmess; + result = result.Substring(Global.vmessProtocol.Length); + result = Utils.Base64Decode(result); + + //转成Json + VmessQRCode vmessQRCode = Utils.FromJson(result); + if (vmessQRCode == null) + { + msg = UIRes.I18N("FailedConversionConfiguration"); + return null; + } + vmessItem.security = Global.DefaultSecurity; + vmessItem.network = Global.DefaultNetwork; + vmessItem.headerType = Global.None; + + + vmessItem.configVersion = Utils.ToInt(vmessQRCode.v); + vmessItem.remarks = Utils.ToString(vmessQRCode.ps); + vmessItem.address = Utils.ToString(vmessQRCode.add); + vmessItem.port = Utils.ToInt(vmessQRCode.port); + vmessItem.id = Utils.ToString(vmessQRCode.id); + vmessItem.alterId = Utils.ToInt(vmessQRCode.aid); + + if (!Utils.IsNullOrEmpty(vmessQRCode.net)) + { + vmessItem.network = vmessQRCode.net; + } + if (!Utils.IsNullOrEmpty(vmessQRCode.type)) + { + vmessItem.headerType = vmessQRCode.type; + } + + vmessItem.requestHost = Utils.ToString(vmessQRCode.host); + vmessItem.path = Utils.ToString(vmessQRCode.path); + vmessItem.streamSecurity = Utils.ToString(vmessQRCode.tls); + } + + ConfigHandler.UpgradeServerVersion(ref vmessItem); + } + else if (result.StartsWith(Global.ssProtocol)) + { + msg = UIRes.I18N("ConfigurationFormatIncorrect"); + + vmessItem = ResolveSSLegacy(result); + if (vmessItem == null) + { + vmessItem = ResolveSip002(result); + } + if (vmessItem == null) + { + return null; + } + if (vmessItem.address.Length == 0 || vmessItem.port == 0 || vmessItem.security.Length == 0 || vmessItem.id.Length == 0) + { + return null; + } + + vmessItem.configType = (int)EConfigType.Shadowsocks; + } + else if (result.StartsWith(Global.socksProtocol)) + { + msg = UIRes.I18N("ConfigurationFormatIncorrect"); + + vmessItem.configType = (int)EConfigType.Socks; + result = result.Substring(Global.socksProtocol.Length); + //remark + int indexRemark = result.IndexOf("#"); + if (indexRemark > 0) + { + try + { + vmessItem.remarks = WebUtility.UrlDecode(result.Substring(indexRemark + 1, result.Length - indexRemark - 1)); + } + catch { } + result = result.Substring(0, indexRemark); + } + //part decode + int indexS = result.IndexOf("@"); + if (indexS > 0) + { + } + else + { + result = Utils.Base64Decode(result); + } + + string[] arr1 = result.Split('@'); + if (arr1.Length != 2) + { + return null; + } + string[] arr21 = arr1[0].Split(':'); + //string[] arr22 = arr1[1].Split(':'); + int indexPort = arr1[1].LastIndexOf(":"); + if (arr21.Length != 2 || indexPort < 0) + { + return null; + } + vmessItem.address = arr1[1].Substring(0, indexPort); + vmessItem.port = Utils.ToInt(arr1[1].Substring(indexPort + 1, arr1[1].Length - (indexPort + 1))); + vmessItem.security = arr21[0]; + vmessItem.id = arr21[1]; + } + else if (result.StartsWith(Global.trojanProtocol)) + { + msg = UIRes.I18N("ConfigurationFormatIncorrect"); + + vmessItem.configType = (int)EConfigType.Trojan; + + Uri uri = new Uri(result); + vmessItem.address = uri.IdnHost; + vmessItem.port = uri.Port; + vmessItem.id = uri.UserInfo; + + var qurery = HttpUtility.ParseQueryString(uri.Query); + vmessItem.requestHost = qurery["sni"] ?? ""; + + var remarks = uri.Fragment.Replace("#", ""); + if (Utils.IsNullOrEmpty(remarks)) + { + vmessItem.remarks = "NONE"; + } + else + { + vmessItem.remarks = WebUtility.UrlDecode(remarks); + } + } + else if (result.StartsWith(Global.vlessProtocol)) + { + vmessItem = ResolveStdVLESS(result); + + ConfigHandler.UpgradeServerVersion(ref vmessItem); + } + else + { + msg = UIRes.I18N("NonvmessOrssProtocol"); + return null; + } + } + catch + { + msg = UIRes.I18N("Incorrectconfiguration"); + return null; + } + + return vmessItem; + } + + + private static VmessItem ResolveVmess4Kitsunebi(string result) + { + VmessItem vmessItem = new VmessItem + { + configType = (int)EConfigType.Vmess + }; + result = result.Substring(Global.vmessProtocol.Length); + int indexSplit = result.IndexOf("?"); + if (indexSplit > 0) + { + result = result.Substring(0, indexSplit); + } + result = Utils.Base64Decode(result); + + string[] arr1 = result.Split('@'); + if (arr1.Length != 2) + { + return null; + } + string[] arr21 = arr1[0].Split(':'); + string[] arr22 = arr1[1].Split(':'); + if (arr21.Length != 2 || arr21.Length != 2) + { + return null; + } + + vmessItem.address = arr22[0]; + vmessItem.port = Utils.ToInt(arr22[1]); + vmessItem.security = arr21[0]; + vmessItem.id = arr21[1]; + + vmessItem.network = Global.DefaultNetwork; + vmessItem.headerType = Global.None; + vmessItem.remarks = "Alien"; + vmessItem.alterId = 0; + + 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 (!Utils.IsNullOrEmpty(tag)) + { + 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": + case "h2": + 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; + } + + private static VmessItem ResolveStdVLESS(string result) + { + VmessItem item = new VmessItem + { + configType = (int)EConfigType.VLESS, + security = "none" + }; + + Uri url = new Uri(result); + + item.address = url.IdnHost; + item.port = url.Port; + item.remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + item.id = url.UserInfo; + + var query = HttpUtility.ParseQueryString(url.Query); + + item.flow = query["flow"] ?? ""; + item.security = query["encryption"] ?? "none"; + item.streamSecurity = query["security"] ?? ""; + item.network = query["type"] ?? "tcp"; + switch (item.network) + { + case "tcp": + item.headerType = query["headerType"] ?? "none"; + item.requestHost = query["host"] ?? ""; + + break; + case "kcp": + item.headerType = query["headerType"] ?? "none"; + item.path = query["seed"] ?? ""; + break; + + case "ws": + item.requestHost = query["host"] ?? ""; + item.path = query["path"] ?? "/"; + break; + + case "http": + case "h2": + item.network = "h2"; + item.requestHost = query["host"] ?? ""; + item.path = query["path"] ?? "/"; + break; + + case "quic": + item.headerType = query["headerType"] ?? "none"; + item.requestHost = query["quicSecurity"] ?? "none"; + item.path = query["key"] ?? ""; + break; + + default: + return null; + } + + return item; + } + + #endregion + } +} diff --git a/v2rayN/v2rayN/Handler/V2rayConfigHandler.cs b/v2rayN/v2rayN/Handler/V2rayConfigHandler.cs index 473400c3..6fa3ecb6 100644 --- a/v2rayN/v2rayN/Handler/V2rayConfigHandler.cs +++ b/v2rayN/v2rayN/Handler/V2rayConfigHandler.cs @@ -1,13 +1,7 @@ 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; namespace v2rayN.Handler @@ -467,7 +461,7 @@ namespace v2rayN.Handler } else { - usersItem.flow = config.flow(); + usersItem.flow = config.flow().Replace("splice", "direct"); } outbound.mux.enabled = false; @@ -1283,179 +1277,6 @@ namespace v2rayN.Handler return vmessItem; } - /// - /// 从剪贴板导入URL - /// - /// - /// - /// - public static VmessItem ImportFromClipboardConfig(string clipboardData, out string msg) - { - msg = string.Empty; - VmessItem vmessItem = new VmessItem(); - - try - { - //载入配置文件 - string result = clipboardData.TrimEx();// Utils.GetClipboardData(); - if (Utils.IsNullOrEmpty(result)) - { - msg = UIRes.I18N("FailedReadConfiguration"); - return null; - } - - if (result.StartsWith(Global.vmessProtocol)) - { - int indexSplit = result.IndexOf("?"); - if (indexSplit > 0) - { - vmessItem = ResolveStdVmess(result) ?? ResolveVmess4Kitsunebi(result); - } - else - { - vmessItem.configType = (int)EConfigType.Vmess; - result = result.Substring(Global.vmessProtocol.Length); - result = Utils.Base64Decode(result); - - //转成Json - VmessQRCode vmessQRCode = Utils.FromJson(result); - if (vmessQRCode == null) - { - msg = UIRes.I18N("FailedConversionConfiguration"); - return null; - } - vmessItem.security = Global.DefaultSecurity; - vmessItem.network = Global.DefaultNetwork; - vmessItem.headerType = Global.None; - - - vmessItem.configVersion = Utils.ToInt(vmessQRCode.v); - vmessItem.remarks = Utils.ToString(vmessQRCode.ps); - vmessItem.address = Utils.ToString(vmessQRCode.add); - vmessItem.port = Utils.ToInt(vmessQRCode.port); - vmessItem.id = Utils.ToString(vmessQRCode.id); - vmessItem.alterId = Utils.ToInt(vmessQRCode.aid); - - if (!Utils.IsNullOrEmpty(vmessQRCode.net)) - { - vmessItem.network = vmessQRCode.net; - } - if (!Utils.IsNullOrEmpty(vmessQRCode.type)) - { - vmessItem.headerType = vmessQRCode.type; - } - - vmessItem.requestHost = Utils.ToString(vmessQRCode.host); - vmessItem.path = Utils.ToString(vmessQRCode.path); - vmessItem.streamSecurity = Utils.ToString(vmessQRCode.tls); - } - - ConfigHandler.UpgradeServerVersion(ref vmessItem); - } - else if (result.StartsWith(Global.ssProtocol)) - { - msg = UIRes.I18N("ConfigurationFormatIncorrect"); - - vmessItem = ResolveSSLegacy(result); - if (vmessItem == null) - { - vmessItem = ResolveSip002(result); - } - if (vmessItem == null) - { - return null; - } - if (vmessItem.address.Length == 0 || vmessItem.port == 0 || vmessItem.security.Length == 0 || vmessItem.id.Length == 0) - { - return null; - } - - vmessItem.configType = (int)EConfigType.Shadowsocks; - } - else if (result.StartsWith(Global.socksProtocol)) - { - msg = UIRes.I18N("ConfigurationFormatIncorrect"); - - vmessItem.configType = (int)EConfigType.Socks; - result = result.Substring(Global.socksProtocol.Length); - //remark - int indexRemark = result.IndexOf("#"); - if (indexRemark > 0) - { - try - { - vmessItem.remarks = WebUtility.UrlDecode(result.Substring(indexRemark + 1, result.Length - indexRemark - 1)); - } - catch { } - result = result.Substring(0, indexRemark); - } - //part decode - int indexS = result.IndexOf("@"); - if (indexS > 0) - { - } - else - { - result = Utils.Base64Decode(result); - } - - string[] arr1 = result.Split('@'); - if (arr1.Length != 2) - { - return null; - } - string[] arr21 = arr1[0].Split(':'); - //string[] arr22 = arr1[1].Split(':'); - int indexPort = arr1[1].LastIndexOf(":"); - if (arr21.Length != 2 || indexPort < 0) - { - return null; - } - vmessItem.address = arr1[1].Substring(0, indexPort); - vmessItem.port = Utils.ToInt(arr1[1].Substring(indexPort + 1, arr1[1].Length - (indexPort + 1))); - vmessItem.security = arr21[0]; - vmessItem.id = arr21[1]; - } - else if (result.StartsWith(Global.trojanProtocol)) - { - msg = UIRes.I18N("ConfigurationFormatIncorrect"); - - vmessItem.configType = (int)EConfigType.Trojan; - - Uri uri = new Uri(result); - vmessItem.address = uri.IdnHost; - vmessItem.port = uri.Port; - vmessItem.id = uri.UserInfo; - - var qurery = HttpUtility.ParseQueryString(uri.Query); - vmessItem.requestHost = qurery["sni"] ?? ""; - - var remarks = uri.Fragment.Replace("#", ""); - if (Utils.IsNullOrEmpty(remarks)) - { - vmessItem.remarks = "NONE"; - } - else - { - vmessItem.remarks = WebUtility.UrlDecode(remarks); - } - } - else - { - msg = UIRes.I18N("NonvmessOrssProtocol"); - return null; - } - } - catch - { - msg = UIRes.I18N("Incorrectconfiguration"); - return null; - } - - return vmessItem; - } - - /// /// 导出为客户端配置 /// @@ -1480,216 +1301,6 @@ namespace v2rayN.Handler return GenerateServerConfig(config, fileName, out msg); } - private static VmessItem ResolveVmess4Kitsunebi(string result) - { - VmessItem vmessItem = new VmessItem - { - configType = (int)EConfigType.Vmess - }; - result = result.Substring(Global.vmessProtocol.Length); - int indexSplit = result.IndexOf("?"); - if (indexSplit > 0) - { - result = result.Substring(0, indexSplit); - } - result = Utils.Base64Decode(result); - - string[] arr1 = result.Split('@'); - if (arr1.Length != 2) - { - return null; - } - string[] arr21 = arr1[0].Split(':'); - string[] arr22 = arr1[1].Split(':'); - if (arr21.Length != 2 || arr21.Length != 2) - { - return null; - } - - vmessItem.address = arr22[0]; - vmessItem.port = Utils.ToInt(arr22[1]); - vmessItem.security = arr21[0]; - vmessItem.id = arr21[1]; - - vmessItem.network = Global.DefaultNetwork; - vmessItem.headerType = Global.None; - vmessItem.remarks = "Alien"; - vmessItem.alterId = 0; - - 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 diff --git a/v2rayN/v2rayN/Properties/AssemblyInfo.cs b/v2rayN/v2rayN/Properties/AssemblyInfo.cs index 82cf7900..66a0322b 100644 --- a/v2rayN/v2rayN/Properties/AssemblyInfo.cs +++ b/v2rayN/v2rayN/Properties/AssemblyInfo.cs @@ -32,4 +32,4 @@ using System.Runtime.InteropServices; // 方法是按如下所示使用“*”: //[assembly: AssemblyVersion("1.0.*")] //[assembly: AssemblyVersion("1.0.0")] -[assembly: AssemblyFileVersion("4.2")] +[assembly: AssemblyFileVersion("4.3")] diff --git a/v2rayN/v2rayN/Resx/ResUI.Designer.cs b/v2rayN/v2rayN/Resx/ResUI.Designer.cs index 91bf3499..a92b8267 100644 --- a/v2rayN/v2rayN/Resx/ResUI.Designer.cs +++ b/v2rayN/v2rayN/Resx/ResUI.Designer.cs @@ -19,7 +19,7 @@ namespace v2rayN.Resx { // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen // (以 /str 作为命令选项),或重新生成 VS 项目。 - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class ResUI { @@ -717,6 +717,15 @@ namespace v2rayN.Resx { } } + /// + /// 查找类似 {0},One of the required. 的本地化字符串。 + /// + internal static string RoutingRuleDetailRequiredTips { + get { + return ResourceManager.GetString("RoutingRuleDetailRequiredTips", resourceCulture); + } + } + /// /// 查找类似 The client configuration file is saved at: {0} 的本地化字符串。 /// diff --git a/v2rayN/v2rayN/Resx/ResUI.resx b/v2rayN/v2rayN/Resx/ResUI.resx index d934df5b..21747ecc 100644 --- a/v2rayN/v2rayN/Resx/ResUI.resx +++ b/v2rayN/v2rayN/Resx/ResUI.resx @@ -370,4 +370,7 @@ Are you sure to remove the rules? + + {0},One of the required. + \ No newline at end of file diff --git a/v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx b/v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx index 3346ab6a..59b4f18b 100644 --- a/v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx @@ -370,4 +370,7 @@ 是否确定移除规则? + + {0},必填其中一项. + \ No newline at end of file diff --git a/v2rayN/v2rayN/v2rayN.csproj b/v2rayN/v2rayN/v2rayN.csproj index f6d76a16..ea9c4b89 100644 --- a/v2rayN/v2rayN/v2rayN.csproj +++ b/v2rayN/v2rayN/v2rayN.csproj @@ -131,6 +131,7 @@ RoutingSettingDetailsForm.cs + Form