diff --git a/README.md b/README.md index 3fcdebd..cc61f81 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Utility to convert between various proxy subscription formats. [Docker README](https://github.com/tindy2013/subconverter/blob/master/README-docker.md) -[中文文档](https://github.com/tindy2013/subconverter/blob/master/README-cn.md)1 +[中文文档](https://github.com/tindy2013/subconverter/blob/master/README-cn.md) - [subconverter](#subconverter) - [Supported Types](#supported-types) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index c8eb16a..e22daac 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -25,12 +25,17 @@ extern string_array ss_ciphers, ssr_ciphers; -const string_array clashr_protocols = {"origin", "auth_sha1_v4", "auth_aes128_md5", "auth_aes128_sha1", "auth_chain_a", "auth_chain_b"}; -const string_array clashr_obfs = {"plain", "http_simple", "http_post", "random_head", "tls1.2_ticket_auth", "tls1.2_ticket_fastauth"}; -const string_array clash_ssr_ciphers = {"rc4-md5", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "chacha20-ietf", "xchacha20", "none"}; +const string_array clashr_protocols = {"origin", "auth_sha1_v4", "auth_aes128_md5", "auth_aes128_sha1", "auth_chain_a", + "auth_chain_b"}; +const string_array clashr_obfs = {"plain", "http_simple", "http_post", "random_head", "tls1.2_ticket_auth", + "tls1.2_ticket_fastauth"}; +const string_array clash_ssr_ciphers = {"rc4-md5", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", + "aes-192-cfb", "aes-256-cfb", "chacha20-ietf", "xchacha20", "none"}; -std::string vmessLinkConstruct(const std::string &remarks, const std::string &add, const std::string &port, const std::string &type, const std::string &id, const std::string &aid, const std::string &net, const std::string &path, const std::string &host, const std::string &tls) -{ +std::string +vmessLinkConstruct(const std::string &remarks, const std::string &add, const std::string &port, const std::string &type, + const std::string &id, const std::string &aid, const std::string &net, const std::string &path, + const std::string &host, const std::string &tls) { rapidjson::StringBuffer sb; rapidjson::Writer writer(sb); writer.StartObject(); @@ -60,59 +65,45 @@ std::string vmessLinkConstruct(const std::string &remarks, const std::string &ad return sb.GetString(); } -bool matchRange(const std::string &range, int target) -{ +bool matchRange(const std::string &range, int target) { string_array vArray = split(range, ","); bool match = false; std::string range_begin_str, range_end_str; int range_begin, range_end; static const std::string reg_num = "-?\\d+", reg_range = "(\\d+)-(\\d+)", reg_not = "\\!-?(\\d+)", reg_not_range = "\\!(\\d+)-(\\d+)", reg_less = "(\\d+)-", reg_more = "(\\d+)\\+"; - for(std::string &x : vArray) - { - if(regMatch(x, reg_num)) - { - if(to_int(x, INT_MAX) == target) + for (std::string &x: vArray) { + if (regMatch(x, reg_num)) { + if (to_int(x, INT_MAX) == target) match = true; - } - else if(regMatch(x, reg_range)) - { + } else if (regMatch(x, reg_range)) { regGetMatch(x, reg_range, 3, 0, &range_begin_str, &range_end_str); range_begin = to_int(range_begin_str, INT_MAX); range_end = to_int(range_end_str, INT_MIN); - if(target >= range_begin && target <= range_end) + if (target >= range_begin && target <= range_end) match = true; - } - else if(regMatch(x, reg_not)) - { + } else if (regMatch(x, reg_not)) { match = true; - if(to_int(regReplace(x, reg_not, "$1"), INT_MAX) == target) + if (to_int(regReplace(x, reg_not, "$1"), INT_MAX) == target) match = false; - } - else if(regMatch(x, reg_not_range)) - { + } else if (regMatch(x, reg_not_range)) { match = true; regGetMatch(x, reg_range, 3, 0, &range_begin_str, &range_end_str); range_begin = to_int(range_begin_str, INT_MAX); range_end = to_int(range_end_str, INT_MIN); - if(target >= range_begin && target <= range_end) + if (target >= range_begin && target <= range_end) match = false; - } - else if(regMatch(x, reg_less)) - { - if(to_int(regReplace(x, reg_less, "$1"), INT_MAX) >= target) + } else if (regMatch(x, reg_less)) { + if (to_int(regReplace(x, reg_less, "$1"), INT_MAX) >= target) match = true; - } - else if(regMatch(x, reg_more)) - { - if(to_int(regReplace(x, reg_more, "$1"), INT_MIN) <= target) + } else if (regMatch(x, reg_more)) { + if (to_int(regReplace(x, reg_more, "$1"), INT_MIN) <= target) match = true; } } return match; } -bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy &node) -{ +bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy &node) { std::string target, ret_real_rule; static const std::string groupid_regex = R"(^!!(?:GROUPID|INSERT)=([\d\-+!,]+)(?:!!(.*))?$)", group_regex = R"(^!!(?:GROUP)=(.+?)(?:!!(.*))?$)"; static const std::string type_regex = R"(^!!(?:TYPE)=(.+?)(?:!!(.*))?$)", port_regex = R"(^!!(?:PORT)=(.+?)(?:!!(.*))?$)", server_regex = R"(^!!(?:SERVER)=(.+?)(?:!!(.*))?$)"; @@ -124,357 +115,429 @@ bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy & {ProxyType::HTTP, "HTTP"}, {ProxyType::HTTPS, "HTTPS"}, {ProxyType::SOCKS5, "SOCKS5"}, - {ProxyType::WireGuard, "WIREGUARD"}}; - if(startsWith(rule, "!!GROUP=")) - { + {ProxyType::WireGuard, "WIREGUARD"}, + {ProxyType::VLESS, "VLESS"}, + {ProxyType::Hysteria, "HYSTERIA"}, + {ProxyType::Hysteria2, "HYSTERIA2"}}; + if (startsWith(rule, "!!GROUP=")) { regGetMatch(rule, group_regex, 3, 0, &target, &ret_real_rule); real_rule = ret_real_rule; return regFind(node.Group, target); - } - else if(startsWith(rule, "!!GROUPID=") || startsWith(rule, "!!INSERT=")) - { + } else if (startsWith(rule, "!!GROUPID=") || startsWith(rule, "!!INSERT=")) { int dir = startsWith(rule, "!!INSERT=") ? -1 : 1; regGetMatch(rule, groupid_regex, 3, 0, &target, &ret_real_rule); real_rule = ret_real_rule; return matchRange(target, dir * node.GroupId); - } - else if(startsWith(rule, "!!TYPE=")) - { + } else if (startsWith(rule, "!!TYPE=")) { regGetMatch(rule, type_regex, 3, 0, &target, &ret_real_rule); real_rule = ret_real_rule; - if(node.Type == ProxyType::Unknown) + if (node.Type == ProxyType::Unknown) return false; return regMatch(types.at(node.Type), target); - } - else if(startsWith(rule, "!!PORT=")) - { + } else if (startsWith(rule, "!!PORT=")) { regGetMatch(rule, port_regex, 3, 0, &target, &ret_real_rule); real_rule = ret_real_rule; return matchRange(target, node.Port); - } - else if(startsWith(rule, "!!SERVER=")) - { + } else if (startsWith(rule, "!!SERVER=")) { regGetMatch(rule, server_regex, 3, 0, &target, &ret_real_rule); real_rule = ret_real_rule; return regFind(node.Hostname, target); - } - else + } else real_rule = rule; return true; } -void processRemark(std::string &remark, const string_array &remarks_list, bool proc_comma = true) -{ +void processRemark(std::string &remark, const string_array &remarks_list, bool proc_comma = true) { // Replace every '=' with '-' in the remark string to avoid parse errors from the clients. // Surge is tested to yield an error when handling '=' in the remark string, // not sure if other clients have the same problem. std::replace(remark.begin(), remark.end(), '=', '-'); - if(proc_comma) - { - if(remark.find(',') != std::string::npos) - { + if (proc_comma) { + if (remark.find(',') != std::string::npos) { remark.insert(0, "\""); remark.append("\""); } } std::string tempRemark = remark; int cnt = 2; - while(std::find(remarks_list.cbegin(), remarks_list.cend(), tempRemark) != remarks_list.cend()) - { + while (std::find(remarks_list.cbegin(), remarks_list.cend(), tempRemark) != remarks_list.cend()) { tempRemark = remark + " " + std::to_string(cnt); cnt++; } remark = tempRemark; } -void groupGenerate(const std::string &rule, std::vector &nodelist, string_array &filtered_nodelist, bool add_direct, extra_settings &ext) -{ +void +groupGenerate(const std::string &rule, std::vector &nodelist, string_array &filtered_nodelist, bool add_direct, + extra_settings &ext) { std::string real_rule; - if(startsWith(rule, "[]") && add_direct) - { + if (startsWith(rule, "[]") && add_direct) { filtered_nodelist.emplace_back(rule.substr(2)); } #ifndef NO_JS_RUNTIME - else if(startsWith(rule, "script:") && ext.authorized) - { - script_safe_runner(ext.js_runtime, ext.js_context, [&](qjs::Context &ctx){ + else if (startsWith(rule, "script:") && ext.authorized) { + script_safe_runner(ext.js_runtime, ext.js_context, [&](qjs::Context &ctx) { std::string script = fileGet(rule.substr(7), true); - try - { + try { ctx.eval(script); - auto filter = (std::function&)>) ctx.eval("filter"); + auto filter = (std::function &)>) ctx.eval("filter"); std::string result_list = filter(nodelist); filtered_nodelist = split(regTrim(result_list), "\n"); } - catch (qjs::exception) - { + catch (qjs::exception) { script_print_stack(ctx); } }, global.scriptCleanContext); } #endif // NO_JS_RUNTIME - else - { - for(Proxy &x : nodelist) - { - if(applyMatcher(rule, real_rule, x) && (real_rule.empty() || regFind(x.Remark, real_rule)) && std::find(filtered_nodelist.begin(), filtered_nodelist.end(), x.Remark) == filtered_nodelist.end()) + else { + for (Proxy &x: nodelist) { + if (applyMatcher(rule, real_rule, x) && (real_rule.empty() || regFind(x.Remark, real_rule)) && + std::find(filtered_nodelist.begin(), filtered_nodelist.end(), x.Remark) == filtered_nodelist.end()) filtered_nodelist.emplace_back(x.Remark); } } } -void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGroupConfigs &extra_proxy_group, bool clashR, extra_settings &ext) -{ +void +proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGroupConfigs &extra_proxy_group, bool clashR, + extra_settings &ext) { YAML::Node proxies, original_groups; std::vector nodelist; string_array remarks_list; /// proxies style bool block = false, compact = false; - switch(hash_(ext.clash_proxies_style)) - { - case "block"_hash: - block = true; - break; - default: - case "flow"_hash: - break; - case "compact"_hash: - compact = true; - break; + switch (hash_(ext.clash_proxies_style)) { + case "block"_hash: + block = true; + break; + default: + case "flow"_hash: + break; + case "compact"_hash: + compact = true; + break; } - for(Proxy &x : nodes) - { + for (Proxy &x: nodes) { YAML::Node singleproxy; std::string type = getProxyTypeName(x.Type); std::string pluginopts = replaceAllDistinct(x.PluginOption, ";", "&"); - if(ext.append_proxy_type) + if (ext.append_proxy_type) x.Remark = "[" + type + "] " + x.Remark; processRemark(x.Remark, remarks_list, false); tribool udp = ext.udp; + tribool xudp = ext.xudp; tribool scv = ext.skip_cert_verify; + tribool tfo = ext.tfo; udp.define(x.UDP); + xudp.define(x.XUDP); scv.define(x.AllowInsecure); + tfo.define(x.TCPFastOpen); singleproxy["name"] = x.Remark; singleproxy["server"] = x.Hostname; singleproxy["port"] = x.Port; - switch(x.Type) - { - case ProxyType::Shadowsocks: - //latest clash core removed support for chacha20 encryption - if(ext.filter_deprecated && x.EncryptMethod == "chacha20") - continue; - singleproxy["type"] = "ss"; - singleproxy["cipher"] = x.EncryptMethod; - singleproxy["password"] = x.Password; - if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) - singleproxy["password"].SetTag("str"); - switch(hash_(x.Plugin)) - { - case "simple-obfs"_hash: - case "obfs-local"_hash: - singleproxy["plugin"] = "obfs"; - singleproxy["plugin-opts"]["mode"] = urlDecode(getUrlArg(pluginopts, "obfs")); - singleproxy["plugin-opts"]["host"] = urlDecode(getUrlArg(pluginopts, "obfs-host")); - break; - case "v2ray-plugin"_hash: - singleproxy["plugin"] = "v2ray-plugin"; - singleproxy["plugin-opts"]["mode"] = getUrlArg(pluginopts, "mode"); - singleproxy["plugin-opts"]["host"] = getUrlArg(pluginopts, "host"); - singleproxy["plugin-opts"]["path"] = getUrlArg(pluginopts, "path"); - singleproxy["plugin-opts"]["tls"] = pluginopts.find("tls") != std::string::npos; - singleproxy["plugin-opts"]["mux"] = pluginopts.find("mux") != std::string::npos; - if(!scv.is_undef()) - singleproxy["plugin-opts"]["skip-cert-verify"] = scv.get(); - break; - } - break; - case ProxyType::VMess: - singleproxy["type"] = "vmess"; - singleproxy["uuid"] = x.UserId; - singleproxy["alterId"] = x.AlterId; - singleproxy["cipher"] = x.EncryptMethod; - singleproxy["tls"] = x.TLSSecure; - if(!scv.is_undef()) - singleproxy["skip-cert-verify"] = scv.get(); - if(!x.ServerName.empty()) - singleproxy["servername"] = x.ServerName; - switch(hash_(x.TransferProtocol)) - { - case "tcp"_hash: - break; - case "ws"_hash: - singleproxy["network"] = x.TransferProtocol; - if(ext.clash_new_field_name) - { - singleproxy["ws-opts"]["path"] = x.Path; - if(!x.Host.empty()) - singleproxy["ws-opts"]["headers"]["Host"] = x.Host; - if(!x.Edge.empty()) - singleproxy["ws-opts"]["headers"]["Edge"] = x.Edge; - } - else - { - singleproxy["ws-path"] = x.Path; - if(!x.Host.empty()) - singleproxy["ws-headers"]["Host"] = x.Host; - if(!x.Edge.empty()) - singleproxy["ws-headers"]["Edge"] = x.Edge; + switch (x.Type) { + case ProxyType::Shadowsocks: + //latest clash core removed support for chacha20 encryption + if (ext.filter_deprecated && x.EncryptMethod == "chacha20") + continue; + singleproxy["type"] = "ss"; + singleproxy["cipher"] = x.EncryptMethod; + singleproxy["password"] = x.Password; + if (std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) + singleproxy["password"].SetTag("str"); + switch (hash_(x.Plugin)) { + case "simple-obfs"_hash: + case "obfs-local"_hash: + singleproxy["plugin"] = "obfs"; + singleproxy["plugin-opts"]["mode"] = urlDecode(getUrlArg(pluginopts, "obfs")); + singleproxy["plugin-opts"]["host"] = urlDecode(getUrlArg(pluginopts, "obfs-host")); + break; + case "v2ray-plugin"_hash: + singleproxy["plugin"] = "v2ray-plugin"; + singleproxy["plugin-opts"]["mode"] = getUrlArg(pluginopts, "mode"); + singleproxy["plugin-opts"]["host"] = getUrlArg(pluginopts, "host"); + singleproxy["plugin-opts"]["path"] = getUrlArg(pluginopts, "path"); + singleproxy["plugin-opts"]["tls"] = pluginopts.find("tls") != std::string::npos; + singleproxy["plugin-opts"]["mux"] = pluginopts.find("mux") != std::string::npos; + if (!scv.is_undef()) + singleproxy["plugin-opts"]["skip-cert-verify"] = scv.get(); + break; } break; - case "http"_hash: - singleproxy["network"] = x.TransferProtocol; - singleproxy["http-opts"]["method"] = "GET"; - singleproxy["http-opts"]["path"].push_back(x.Path); - if(!x.Host.empty()) - singleproxy["http-opts"]["headers"]["Host"].push_back(x.Host); - if(!x.Edge.empty()) - singleproxy["http-opts"]["headers"]["Edge"].push_back(x.Edge); + case ProxyType::VMess: + singleproxy["type"] = "vmess"; + singleproxy["uuid"] = x.UserId; + singleproxy["alterId"] = x.AlterId; + singleproxy["cipher"] = x.EncryptMethod; + singleproxy["tls"] = x.TLSSecure; + if (!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + if (!x.ServerName.empty()) + singleproxy["servername"] = x.ServerName; + switch (hash_(x.TransferProtocol)) { + case "tcp"_hash: + break; + case "ws"_hash: + singleproxy["network"] = x.TransferProtocol; + if (ext.clash_new_field_name) { + singleproxy["ws-opts"]["path"] = x.Path; + if (!x.Host.empty()) + singleproxy["ws-opts"]["headers"]["Host"] = x.Host; + if (!x.Edge.empty()) + singleproxy["ws-opts"]["headers"]["Edge"] = x.Edge; + } else { + singleproxy["ws-path"] = x.Path; + if (!x.Host.empty()) + singleproxy["ws-headers"]["Host"] = x.Host; + if (!x.Edge.empty()) + singleproxy["ws-headers"]["Edge"] = x.Edge; + } + break; + case "http"_hash: + singleproxy["network"] = x.TransferProtocol; + singleproxy["http-opts"]["method"] = "GET"; + singleproxy["http-opts"]["path"].push_back(x.Path); + if (!x.Host.empty()) + singleproxy["http-opts"]["headers"]["Host"].push_back(x.Host); + if (!x.Edge.empty()) + singleproxy["http-opts"]["headers"]["Edge"].push_back(x.Edge); + break; + case "h2"_hash: + singleproxy["network"] = x.TransferProtocol; + singleproxy["h2-opts"]["path"] = x.Path; + if (!x.Host.empty()) + singleproxy["h2-opts"]["host"].push_back(x.Host); + break; + case "grpc"_hash: + singleproxy["network"] = x.TransferProtocol; + singleproxy["servername"] = x.Host; + singleproxy["grpc-opts"]["grpc-service-name"] = x.Path; + break; + default: + continue; + } break; - case "h2"_hash: - singleproxy["network"] = x.TransferProtocol; - singleproxy["h2-opts"]["path"] = x.Path; - if(!x.Host.empty()) - singleproxy["h2-opts"]["host"].push_back(x.Host); + case ProxyType::ShadowsocksR: + //ignoring all nodes with unsupported obfs, protocols and encryption + if (ext.filter_deprecated) { + if (!clashR && std::find(clash_ssr_ciphers.cbegin(), clash_ssr_ciphers.cend(), x.EncryptMethod) == + clash_ssr_ciphers.cend()) + continue; + if (std::find(clashr_protocols.cbegin(), clashr_protocols.cend(), x.Protocol) == + clashr_protocols.cend()) + continue; + if (std::find(clashr_obfs.cbegin(), clashr_obfs.cend(), x.OBFS) == clashr_obfs.cend()) + continue; + } + + singleproxy["type"] = "ssr"; + singleproxy["cipher"] = x.EncryptMethod == "none" ? "dummy" : x.EncryptMethod; + singleproxy["password"] = x.Password; + if (std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) + singleproxy["password"].SetTag("str"); + singleproxy["protocol"] = x.Protocol; + singleproxy["obfs"] = x.OBFS; + if (clashR) { + singleproxy["protocolparam"] = x.ProtocolParam; + singleproxy["obfsparam"] = x.OBFSParam; + } else { + singleproxy["protocol-param"] = x.ProtocolParam; + singleproxy["obfs-param"] = x.OBFSParam; + } break; - case "grpc"_hash: - singleproxy["network"] = x.TransferProtocol; - singleproxy["servername"] = x.Host; - singleproxy["grpc-opts"]["grpc-service-name"] = x.Path; + case ProxyType::SOCKS5: + singleproxy["type"] = "socks5"; + if (!x.Username.empty()) + singleproxy["username"] = x.Username; + if (!x.Password.empty()) { + singleproxy["password"] = x.Password; + if (std::all_of(x.Password.begin(), x.Password.end(), ::isdigit)) + singleproxy["password"].SetTag("str"); + } + if (!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + break; + case ProxyType::HTTP: + case ProxyType::HTTPS: + singleproxy["type"] = "http"; + if (!x.Username.empty()) + singleproxy["username"] = x.Username; + if (!x.Password.empty()) { + singleproxy["password"] = x.Password; + if (std::all_of(x.Password.begin(), x.Password.end(), ::isdigit)) + singleproxy["password"].SetTag("str"); + } + singleproxy["tls"] = x.TLSSecure; + if (!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + break; + case ProxyType::Trojan: + singleproxy["type"] = "trojan"; + singleproxy["password"] = x.Password; + if (!x.Host.empty()) + singleproxy["sni"] = x.Host; + if (std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) + singleproxy["password"].SetTag("str"); + if (!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + switch (hash_(x.TransferProtocol)) { + case "tcp"_hash: + break; + case "grpc"_hash: + singleproxy["network"] = x.TransferProtocol; + if (!x.Path.empty()) + singleproxy["grpc-opts"]["grpc-service-name"] = x.Path; + break; + case "ws"_hash: + singleproxy["network"] = x.TransferProtocol; + singleproxy["ws-opts"]["path"] = x.Path; + if (!x.Host.empty()) + singleproxy["ws-opts"]["headers"]["Host"] = x.Host; + break; + } + break; + case ProxyType::Snell: + if (x.SnellVersion >= 4) + continue; + singleproxy["type"] = "snell"; + singleproxy["psk"] = x.Password; + if (x.SnellVersion != 0) + singleproxy["version"] = x.SnellVersion; + if (!x.OBFS.empty()) { + singleproxy["obfs-opts"]["mode"] = x.OBFS; + if (!x.Host.empty()) + singleproxy["obfs-opts"]["host"] = x.Host; + } + if (std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) + singleproxy["password"].SetTag("str"); + break; + case ProxyType::WireGuard: + singleproxy["type"] = "wireguard"; + singleproxy["public-key"] = x.PublicKey; + singleproxy["private-key"] = x.PrivateKey; + singleproxy["ip"] = x.SelfIP; + if (!x.SelfIPv6.empty()) + singleproxy["ipv6"] = x.SelfIPv6; + if (!x.PreSharedKey.empty()) + singleproxy["preshared-key"] = x.PreSharedKey; + if (!x.DnsServers.empty()) + singleproxy["dns"] = x.DnsServers; + if (x.Mtu > 0) + singleproxy["mtu"] = x.Mtu; + break; + case ProxyType::Hysteria: + singleproxy["type"] = "hysteria"; + singleproxy["auth_str"] = x.Auth; + singleproxy["up"] = x.UpMbps; + singleproxy["down"] = x.DownMbps; + if (!tfo.is_undef()) + singleproxy["fast-open"] = tfo.get(); + if (!x.FakeType.empty()) + singleproxy["protocol"] = x.FakeType; + if (!x.Host.empty()) + singleproxy["sni"] = x.Host; + if (!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + if (x.Insecure == "1") + singleproxy["skip-cert-verify"] = true; + if (!x.Alpn.empty()) + singleproxy["alpn"].push_back(x.Alpn); + if (!x.OBFSParam.empty()) + singleproxy["obfs"] = x.OBFSParam; + break; + case ProxyType::Hysteria2: + singleproxy["type"] = "hysteria2"; + singleproxy["password"] = x.Password; + singleproxy["auth"] = x.Password; + if (!x.UpMbps.empty()) + singleproxy["up"] = x.UpMbps; + if (!x.DownMbps.empty()) + singleproxy["down"] = x.DownMbps; + if (!x.Host.empty()) + singleproxy["sni"] = x.Host; + if (!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + if (!x.Alpn.empty()) + singleproxy["alpn"].push_back(x.Alpn); + if (!x.OBFSParam.empty()) + singleproxy["obfs"] = x.OBFSParam; + if (!x.OBFSPassword.empty()) + singleproxy["obfs-password"] = x.OBFSPassword; + break; + case ProxyType::VLESS: + singleproxy["type"] = "vless"; + singleproxy["uuid"] = x.UserId; + singleproxy["tls"] = x.TLSSecure; + if (!tfo.is_undef()) + singleproxy["tfo"] = tfo.get(); + if (xudp && udp) + singleproxy["xudp"] = true; + if (!x.Host.empty()) + singleproxy["servername"] = x.Host; + if (!x.Flow.empty()) + singleproxy["flow"] = x.Flow; + if (!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + switch (hash_(x.TransferProtocol)) { + case "tcp"_hash: + break; + case "ws"_hash: + singleproxy["network"] = x.TransferProtocol; + if (ext.clash_new_field_name) { + singleproxy["ws-opts"]["path"] = x.Path; + if (!x.Host.empty()) + singleproxy["ws-opts"]["headers"]["Host"] = x.Host; + if (!x.Edge.empty()) + singleproxy["ws-opts"]["headers"]["Edge"] = x.Edge; + } else { + singleproxy["ws-path"] = x.Path; + if (!x.Host.empty()) + singleproxy["ws-headers"]["Host"] = x.Host; + if (!x.Edge.empty()) + singleproxy["ws-headers"]["Edge"] = x.Edge; + } + break; + case "http"_hash: + singleproxy["network"] = x.TransferProtocol; + singleproxy["http-opts"]["method"] = "GET"; + singleproxy["http-opts"]["path"].push_back(x.Path); + if (!x.Host.empty()) + singleproxy["http-opts"]["headers"]["Host"].push_back(x.Host); + if (!x.Edge.empty()) + singleproxy["http-opts"]["headers"]["Edge"].push_back(x.Edge); + break; + case "h2"_hash: + singleproxy["network"] = x.TransferProtocol; + singleproxy["h2-opts"]["path"] = x.Path; + if (!x.Host.empty()) + singleproxy["h2-opts"]["host"].push_back(x.Host); + break; + case "grpc"_hash: + singleproxy["network"] = x.TransferProtocol; + singleproxy["grpc-opts"]["grpc-mode"] = x.GRPCMode; + singleproxy["grpc-opts"]["grpc-service-name"] = x.GRPCServiceName; + break; + default: + continue; + } break; default: continue; - } - break; - case ProxyType::ShadowsocksR: - //ignoring all nodes with unsupported obfs, protocols and encryption - if(ext.filter_deprecated) - { - if(!clashR && std::find(clash_ssr_ciphers.cbegin(), clash_ssr_ciphers.cend(), x.EncryptMethod) == clash_ssr_ciphers.cend()) - continue; - if(std::find(clashr_protocols.cbegin(), clashr_protocols.cend(), x.Protocol) == clashr_protocols.cend()) - continue; - if(std::find(clashr_obfs.cbegin(), clashr_obfs.cend(), x.OBFS) == clashr_obfs.cend()) - continue; - } - - singleproxy["type"] = "ssr"; - singleproxy["cipher"] = x.EncryptMethod == "none" ? "dummy" : x.EncryptMethod; - singleproxy["password"] = x.Password; - if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) - singleproxy["password"].SetTag("str"); - singleproxy["protocol"] = x.Protocol; - singleproxy["obfs"] = x.OBFS; - if(clashR) - { - singleproxy["protocolparam"] = x.ProtocolParam; - singleproxy["obfsparam"] = x.OBFSParam; - } - else - { - singleproxy["protocol-param"] = x.ProtocolParam; - singleproxy["obfs-param"] = x.OBFSParam; - } - break; - case ProxyType::SOCKS5: - singleproxy["type"] = "socks5"; - if(!x.Username.empty()) - singleproxy["username"] = x.Username; - if(!x.Password.empty()) - { - singleproxy["password"] = x.Password; - if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit)) - singleproxy["password"].SetTag("str"); - } - if(!scv.is_undef()) - singleproxy["skip-cert-verify"] = scv.get(); - break; - case ProxyType::HTTP: - case ProxyType::HTTPS: - singleproxy["type"] = "http"; - if(!x.Username.empty()) - singleproxy["username"] = x.Username; - if(!x.Password.empty()) - { - singleproxy["password"] = x.Password; - if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit)) - singleproxy["password"].SetTag("str"); - } - singleproxy["tls"] = x.TLSSecure; - if(!scv.is_undef()) - singleproxy["skip-cert-verify"] = scv.get(); - break; - case ProxyType::Trojan: - singleproxy["type"] = "trojan"; - singleproxy["password"] = x.Password; - if(!x.Host.empty()) - singleproxy["sni"] = x.Host; - if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) - singleproxy["password"].SetTag("str"); - if(!scv.is_undef()) - singleproxy["skip-cert-verify"] = scv.get(); - switch(hash_(x.TransferProtocol)) - { - case "tcp"_hash: - break; - case "grpc"_hash: - singleproxy["network"] = x.TransferProtocol; - if(!x.Path.empty()) - singleproxy["grpc-opts"]["grpc-service-name"] = x.Path; - break; - case "ws"_hash: - singleproxy["network"] = x.TransferProtocol; - singleproxy["ws-opts"]["path"] = x.Path; - if(!x.Host.empty()) - singleproxy["ws-opts"]["headers"]["Host"] = x.Host; - break; - } - break; - case ProxyType::Snell: - if (x.SnellVersion >= 4) - continue; - singleproxy["type"] = "snell"; - singleproxy["psk"] = x.Password; - if(x.SnellVersion != 0) - singleproxy["version"] = x.SnellVersion; - if(!x.OBFS.empty()) - { - singleproxy["obfs-opts"]["mode"] = x.OBFS; - if(!x.Host.empty()) - singleproxy["obfs-opts"]["host"] = x.Host; - } - if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) - singleproxy["password"].SetTag("str"); - break; - case ProxyType::WireGuard: - singleproxy["type"] = "wireguard"; - singleproxy["public-key"] = x.PublicKey; - singleproxy["private-key"] = x.PrivateKey; - singleproxy["ip"] = x.SelfIP; - if(!x.SelfIPv6.empty()) - singleproxy["ipv6"] = x.SelfIPv6; - if(!x.PreSharedKey.empty()) - singleproxy["preshared-key"] = x.PreSharedKey; - if(!x.DnsServers.empty()) - singleproxy["dns"] = x.DnsServers; - if(x.Mtu > 0) - singleproxy["mtu"] = x.Mtu; - break; - default: - continue; } // UDP is not supported yet in clash using snell // sees in https://dreamacro.github.io/clash/configuration/outbound.html#snell - if(udp && x.Type != ProxyType::Snell) + if (udp && x.Type != ProxyType::Snell) singleproxy["udp"] = true; - if(block) + if (block) singleproxy.SetStyle(YAML::EmitterStyle::Block); else singleproxy.SetStyle(YAML::EmitterStyle::Flow); @@ -483,107 +546,100 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr nodelist.emplace_back(x); } - if(compact) + if (compact) proxies.SetStyle(YAML::EmitterStyle::Flow); - if(ext.nodelist) - { + if (ext.nodelist) { YAML::Node provider; provider["proxies"] = proxies; yamlnode.reset(provider); return; } - if(ext.clash_new_field_name) + if (ext.clash_new_field_name) yamlnode["proxies"] = proxies; else yamlnode["Proxy"] = proxies; - for(const ProxyGroupConfig &x : extra_proxy_group) - { + for (const ProxyGroupConfig &x: extra_proxy_group) { YAML::Node singlegroup; string_array filtered_nodelist; singlegroup["name"] = x.Name; singlegroup["type"] = x.TypeStr(); - switch(x.Type) - { - case ProxyGroupType::Select: - case ProxyGroupType::Relay: - break; - case ProxyGroupType::LoadBalance: - singlegroup["strategy"] = x.StrategyStr(); - [[fallthrough]]; - case ProxyGroupType::URLTest: - if(!x.Lazy.is_undef()) - singlegroup["lazy"] = x.Lazy.get(); - [[fallthrough]]; - case ProxyGroupType::Fallback: - singlegroup["url"] = x.Url; - if(x.Interval > 0) - singlegroup["interval"] = x.Interval; - if(x.Tolerance > 0) - singlegroup["tolerance"] = x.Tolerance; - break; - default: - continue; + switch (x.Type) { + case ProxyGroupType::Select: + case ProxyGroupType::Relay: + break; + case ProxyGroupType::LoadBalance: + singlegroup["strategy"] = x.StrategyStr(); + [[fallthrough]]; + case ProxyGroupType::URLTest: + if (!x.Lazy.is_undef()) + singlegroup["lazy"] = x.Lazy.get(); + [[fallthrough]]; + case ProxyGroupType::Fallback: + singlegroup["url"] = x.Url; + if (x.Interval > 0) + singlegroup["interval"] = x.Interval; + if (x.Tolerance > 0) + singlegroup["tolerance"] = x.Tolerance; + break; + default: + continue; } - if(!x.DisableUdp.is_undef()) + if (!x.DisableUdp.is_undef()) singlegroup["disable-udp"] = x.DisableUdp.get(); - for(const auto& y : x.Proxies) + for (const auto &y: x.Proxies) groupGenerate(y, nodelist, filtered_nodelist, true, ext); - if(!x.UsingProvider.empty()) + if (!x.UsingProvider.empty()) singlegroup["use"] = x.UsingProvider; - else - { - if(filtered_nodelist.empty()) + else { + if (filtered_nodelist.empty()) filtered_nodelist.emplace_back("DIRECT"); } - if(!filtered_nodelist.empty()) + if (!filtered_nodelist.empty()) singlegroup["proxies"] = filtered_nodelist; //singlegroup.SetStyle(YAML::EmitterStyle::Flow); bool replace_flag = false; - for(auto && original_group : original_groups) - { - if(original_group["name"].as() == x.Name) - { + for (auto &&original_group: original_groups) { + if (original_group["name"].as() == x.Name) { original_group.reset(singlegroup); replace_flag = true; break; } } - if(!replace_flag) + if (!replace_flag) original_groups.push_back(singlegroup); } - if(ext.clash_new_field_name) + if (ext.clash_new_field_name) yamlnode["proxy-groups"] = original_groups; else yamlnode["Proxy Group"] = original_groups; } -std::string proxyToClash(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, bool clashR, extra_settings &ext) -{ +std::string proxyToClash(std::vector &nodes, const std::string &base_conf, + std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, + bool clashR, extra_settings &ext) { YAML::Node yamlnode; - try - { + try { yamlnode = YAML::Load(base_conf); } - catch (std::exception &e) - { + catch (std::exception &e) { writeLog(0, std::string("Clash base loader failed with error: ") + e.what(), LOG_LEVEL_ERROR); return ""; } proxyToClash(nodes, yamlnode, extra_proxy_group, clashR, ext); - if(ext.nodelist) + if (ext.nodelist) return YAML::Dump(yamlnode); /* @@ -592,24 +648,24 @@ std::string proxyToClash(std::vector &nodes, const std::string &base_conf return YAML::Dump(yamlnode); */ - if(!ext.enable_rule_generator) + if (!ext.enable_rule_generator) return YAML::Dump(yamlnode); - if(!ext.managed_config_prefix.empty() || ext.clash_script) - { - if(yamlnode["mode"].IsDefined()) - { - if(ext.clash_new_field_name) + if (!ext.managed_config_prefix.empty() || ext.clash_script) { + if (yamlnode["mode"].IsDefined()) { + if (ext.clash_new_field_name) yamlnode["mode"] = ext.clash_script ? "script" : "rule"; else yamlnode["mode"] = ext.clash_script ? "Script" : "Rule"; } - renderClashScript(yamlnode, ruleset_content_array, ext.managed_config_prefix, ext.clash_script, ext.overwrite_original_rules, ext.clash_classical_ruleset); + renderClashScript(yamlnode, ruleset_content_array, ext.managed_config_prefix, ext.clash_script, + ext.overwrite_original_rules, ext.clash_classical_ruleset); return YAML::Dump(yamlnode); } - std::string output_content = rulesetToClashStr(yamlnode, ruleset_content_array, ext.overwrite_original_rules, ext.clash_new_field_name); + std::string output_content = rulesetToClashStr(yamlnode, ruleset_content_array, ext.overwrite_original_rules, + ext.clash_new_field_name); output_content.insert(0, YAML::Dump(yamlnode)); //rulesetToClash(yamlnode, ruleset_content_array, ext.overwrite_original_rules, ext.clash_new_field_name); //std::string output_content = YAML::Dump(yamlnode); @@ -618,16 +674,14 @@ std::string proxyToClash(std::vector &nodes, const std::string &base_conf } // peer = (public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, allowed-ips = "0.0.0.0/0, ::/0", endpoint = engage.cloudflareclient.com:2408, client-id = 139/184/125),(public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, endpoint = engage.cloudflareclient.com:2408) -std::string generatePeer(Proxy &node, bool client_id_as_reserved = false) -{ +std::string generatePeer(Proxy &node, bool client_id_as_reserved = false) { std::string result; result += "public-key = " + node.PublicKey; result += ", endpoint = " + node.Hostname + ":" + std::to_string(node.Port); - if(!node.AllowedIPs.empty()) + if (!node.AllowedIPs.empty()) result += ", allowed-ips = \"" + node.AllowedIPs + "\""; - if(!node.ClientId.empty()) - { - if(client_id_as_reserved) + if (!node.ClientId.empty()) { + if (client_id_as_reserved) result += ", reserved = [" + node.ClientId + "]"; else result += ", client-id = " + node.ClientId; @@ -635,8 +689,9 @@ std::string generatePeer(Proxy &node, bool client_id_as_reserved = false) return result; } -std::string proxyToSurge(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, int surge_ver, extra_settings &ext) -{ +std::string proxyToSurge(std::vector &nodes, const std::string &base_conf, + std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, + int surge_ver, extra_settings &ext) { INIReader ini; std::string output_nodelist; std::vector nodelist; @@ -653,8 +708,7 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf ini.add_direct_save_section("Host"); ini.add_direct_save_section("URL Rewrite"); ini.add_direct_save_section("Header Rewrite"); - if(ini.parse(base_conf) != 0 && !ext.nodelist) - { + if (ini.parse(base_conf) != 0 && !ext.nodelist) { writeLog(0, "Surge base loader failed with error: " + ini.get_last_error(), LOG_LEVEL_ERROR); return ""; } @@ -663,10 +717,8 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf ini.erase_section(); ini.set("{NONAME}", "DIRECT = direct"); - for(Proxy &x : nodes) - { - if(ext.append_proxy_type) - { + for (Proxy &x: nodes) { + if (ext.append_proxy_type) { std::string type = getProxyTypeName(x.Type); x.Remark = "[" + type + "] " + x.Remark; } @@ -686,343 +738,326 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf std::string proxy, section, real_section; string_array args, headers; - switch(x.Type) - { - case ProxyType::Shadowsocks: - if(surge_ver >= 3 || surge_ver == -3) - { - proxy = "ss, " + hostname + ", " + port + ", encrypt-method=" + method + ", password=" + password; - } - else - { - proxy = "custom, " + hostname + ", " + port + ", " + method + ", " + password + ", https://github.com/pobizhe/SSEncrypt/raw/master/SSEncrypt.module"; - } - if(!plugin.empty()) - { - switch(hash_(plugin)) - { - case "simple-obfs"_hash: - case "obfs-local"_hash: - if(!pluginopts.empty()) - proxy += "," + replaceAllDistinct(pluginopts, ";", ","); - break; - default: - continue; + switch (x.Type) { + case ProxyType::Shadowsocks: + if (surge_ver >= 3 || surge_ver == -3) { + proxy = "ss, " + hostname + ", " + port + ", encrypt-method=" + method + ", password=" + password; + } else { + proxy = "custom, " + hostname + ", " + port + ", " + method + ", " + password + + ", https://github.com/pobizhe/SSEncrypt/raw/master/SSEncrypt.module"; + } + if (!plugin.empty()) { + switch (hash_(plugin)) { + case "simple-obfs"_hash: + case "obfs-local"_hash: + if (!pluginopts.empty()) + proxy += "," + replaceAllDistinct(pluginopts, ";", ","); + break; + default: + continue; + } } - } - break; - case ProxyType::VMess: - if(surge_ver < 4 && surge_ver != -3) - continue; - proxy = "vmess, " + hostname + ", " + port + ", username=" + id + ", tls=" + (tlssecure ? "true" : "false") + ", vmess-aead=" + (x.AlterId == 0 ? "true" : "false"); - if(tlssecure && !tls13.is_undef()) - proxy += ", tls13=" + std::string(tls13 ? "true" : "false"); - switch(hash_(transproto)) - { - case "tcp"_hash: break; - case "ws"_hash: - if(host.empty()) - proxy += ", ws=true, ws-path=" + path + ", sni=" + hostname; - else - proxy += ", ws=true, ws-path=" + path + ", sni=" + host; - if(!host.empty()) - headers.push_back("Host:" + host); - if(!edge.empty()) - headers.push_back("Edge:" + edge); - if(!headers.empty()) - proxy += ", ws-headers=" + join(headers, "|"); + case ProxyType::VMess: + if (surge_ver < 4 && surge_ver != -3) + continue; + proxy = "vmess, " + hostname + ", " + port + ", username=" + id + ", tls=" + + (tlssecure ? "true" : "false") + ", vmess-aead=" + (x.AlterId == 0 ? "true" : "false"); + if (tlssecure && !tls13.is_undef()) + proxy += ", tls13=" + std::string(tls13 ? "true" : "false"); + switch (hash_(transproto)) { + case "tcp"_hash: + break; + case "ws"_hash: + if (host.empty()) + proxy += ", ws=true, ws-path=" + path + ", sni=" + hostname; + else + proxy += ", ws=true, ws-path=" + path + ", sni=" + host; + if (!host.empty()) + headers.push_back("Host:" + host); + if (!edge.empty()) + headers.push_back("Edge:" + edge); + if (!headers.empty()) + proxy += ", ws-headers=" + join(headers, "|"); + break; + default: + continue; + } + if (!scv.is_undef()) + proxy += ", skip-cert-verify=" + scv.get_str(); + break; + case ProxyType::ShadowsocksR: + if (ext.surge_ssr_path.empty() || surge_ver < 2) + continue; + proxy = "external, exec=\"" + ext.surge_ssr_path + "\", args=\""; + args = {"-l", std::to_string(local_port), "-s", hostname, "-p", port, "-m", method, "-k", password, + "-o", obfs, "-O", protocol}; + if (!obfsparam.empty()) { + args.emplace_back("-g"); + args.emplace_back(std::move(obfsparam)); + } + if (!protoparam.empty()) { + args.emplace_back("-G"); + args.emplace_back(std::move(protoparam)); + } + proxy += join(args, "\", args=\""); + proxy += "\", local-port=" + std::to_string(local_port); + if (isIPv4(hostname) || isIPv6(hostname)) + proxy += ", addresses=" + hostname; + else if (global.surgeResolveHostname) + proxy += ", addresses=" + hostnameToIPAddr(hostname); + local_port++; + break; + case ProxyType::SOCKS5: + proxy = "socks5, " + hostname + ", " + port; + if (!username.empty()) + proxy += ", username=" + username; + if (!password.empty()) + proxy += ", password=" + password; + if (!scv.is_undef()) + proxy += ", skip-cert-verify=" + scv.get_str(); + break; + case ProxyType::HTTPS: + if (surge_ver == -3) { + proxy = "https, " + hostname + ", " + port + ", " + username + ", " + password; + if (!scv.is_undef()) + proxy += ", skip-cert-verify=" + scv.get_str(); + break; + } + [[fallthrough]]; + case ProxyType::HTTP: + proxy = "http, " + hostname + ", " + port; + if (!username.empty()) + proxy += ", username=" + username; + if (!password.empty()) + proxy += ", password=" + password; + proxy += std::string(", tls=") + (x.TLSSecure ? "true" : "false"); + if (!scv.is_undef()) + proxy += ", skip-cert-verify=" + scv.get_str(); + break; + case ProxyType::Trojan: + if (surge_ver < 4 && surge_ver != -3) + continue; + proxy = "trojan, " + hostname + ", " + port + ", password=" + password; + if (x.SnellVersion != 0) + proxy += ", version=" + std::to_string(x.SnellVersion); + if (!host.empty()) + proxy += ", sni=" + host; + if (!scv.is_undef()) + proxy += ", skip-cert-verify=" + scv.get_str(); + break; + case ProxyType::Snell: + proxy = "snell, " + hostname + ", " + port + ", psk=" + password; + if (!obfs.empty()) { + proxy += ", obfs=" + obfs; + if (!host.empty()) + proxy += ", obfs-host=" + host; + } + if (x.SnellVersion != 0) + proxy += ", version=" + std::to_string(x.SnellVersion); + break; + case ProxyType::WireGuard: + if (surge_ver < 4 && surge_ver != -3) + continue; + section = randomStr(5); + real_section = "WireGuard " + section; + proxy = "wireguard, section-name=" + section; + if (!x.TestUrl.empty()) + proxy += ", test-url=" + x.TestUrl; + ini.set(real_section, "private-key", x.PrivateKey); + ini.set(real_section, "self-ip", x.SelfIP); + if (!x.SelfIPv6.empty()) + ini.set(real_section, "self-ip-v6", x.SelfIPv6); + if (!x.PreSharedKey.empty()) + ini.set(real_section, "preshared-key", x.PreSharedKey); + if (!x.DnsServers.empty()) + ini.set(real_section, "dns-server", join(x.DnsServers, ",")); + if (x.Mtu > 0) + ini.set(real_section, "mtu", std::to_string(x.Mtu)); + if (x.KeepAlive > 0) + ini.set(real_section, "keepalive", std::to_string(x.KeepAlive)); + ini.set(real_section, "peer", "(" + generatePeer(x) + ")"); break; default: continue; - } - if(!scv.is_undef()) - proxy += ", skip-cert-verify=" + scv.get_str(); - break; - case ProxyType::ShadowsocksR: - if(ext.surge_ssr_path.empty() || surge_ver < 2) - continue; - proxy = "external, exec=\"" + ext.surge_ssr_path + "\", args=\""; - args = {"-l", std::to_string(local_port), "-s", hostname, "-p", port, "-m", method, "-k", password, "-o", obfs, "-O", protocol}; - if(!obfsparam.empty()) - { - args.emplace_back("-g"); - args.emplace_back(std::move(obfsparam)); - } - if(!protoparam.empty()) - { - args.emplace_back("-G"); - args.emplace_back(std::move(protoparam)); - } - proxy += join(args, "\", args=\""); - proxy += "\", local-port=" + std::to_string(local_port); - if(isIPv4(hostname) || isIPv6(hostname)) - proxy += ", addresses=" + hostname; - else if(global.surgeResolveHostname) - proxy += ", addresses=" + hostnameToIPAddr(hostname); - local_port++; - break; - case ProxyType::SOCKS5: - proxy = "socks5, " + hostname + ", " + port; - if(!username.empty()) - proxy += ", username=" + username; - if(!password.empty()) - proxy += ", password=" + password; - if(!scv.is_undef()) - proxy += ", skip-cert-verify=" + scv.get_str(); - break; - case ProxyType::HTTPS: - if(surge_ver == -3) - { - proxy = "https, " + hostname + ", " + port + ", " + username + ", " + password; - if(!scv.is_undef()) - proxy += ", skip-cert-verify=" + scv.get_str(); - break; - } - [[fallthrough]]; - case ProxyType::HTTP: - proxy = "http, " + hostname + ", " + port; - if(!username.empty()) - proxy += ", username=" + username; - if(!password.empty()) - proxy += ", password=" + password; - proxy += std::string(", tls=") + (x.TLSSecure ? "true" : "false"); - if(!scv.is_undef()) - proxy += ", skip-cert-verify=" + scv.get_str(); - break; - case ProxyType::Trojan: - if(surge_ver < 4 && surge_ver != -3) - continue; - proxy = "trojan, " + hostname + ", " + port + ", password=" + password; - if(x.SnellVersion != 0) - proxy += ", version=" + std::to_string(x.SnellVersion); - if(!host.empty()) - proxy += ", sni=" + host; - if(!scv.is_undef()) - proxy += ", skip-cert-verify=" + scv.get_str(); - break; - case ProxyType::Snell: - proxy = "snell, " + hostname + ", " + port + ", psk=" + password; - if(!obfs.empty()) - { - proxy += ", obfs=" + obfs; - if(!host.empty()) - proxy += ", obfs-host=" + host; - } - if(x.SnellVersion != 0) - proxy += ", version=" + std::to_string(x.SnellVersion); - break; - case ProxyType::WireGuard: - if(surge_ver < 4 && surge_ver != -3) - continue; - section = randomStr(5); - real_section = "WireGuard " + section; - proxy = "wireguard, section-name=" + section; - if(!x.TestUrl.empty()) - proxy += ", test-url=" + x.TestUrl; - ini.set(real_section, "private-key", x.PrivateKey); - ini.set(real_section, "self-ip", x.SelfIP); - if(!x.SelfIPv6.empty()) - ini.set(real_section, "self-ip-v6", x.SelfIPv6); - if(!x.PreSharedKey.empty()) - ini.set(real_section, "preshared-key", x.PreSharedKey); - if(!x.DnsServers.empty()) - ini.set(real_section, "dns-server", join(x.DnsServers, ",")); - if(x.Mtu > 0) - ini.set(real_section, "mtu", std::to_string(x.Mtu)); - if(x.KeepAlive > 0) - ini.set(real_section, "keepalive", std::to_string(x.KeepAlive)); - ini.set(real_section, "peer", "(" + generatePeer(x) + ")"); - break; - default: - continue; } - if(!tfo.is_undef()) + if (!tfo.is_undef()) proxy += ", tfo=" + tfo.get_str(); - if(!udp.is_undef()) + if (!udp.is_undef()) proxy += ", udp-relay=" + udp.get_str(); - if(ext.nodelist) + if (ext.nodelist) output_nodelist += x.Remark + " = " + proxy + "\n"; - else - { + else { ini.set("{NONAME}", x.Remark + " = " + proxy); nodelist.emplace_back(x); } remarks_list.emplace_back(x.Remark); } - if(ext.nodelist) + if (ext.nodelist) return output_nodelist; ini.set_current_section("Proxy Group"); ini.erase_section(); - for(const ProxyGroupConfig &x : extra_proxy_group) - { + for (const ProxyGroupConfig &x: extra_proxy_group) { string_array filtered_nodelist; std::string group; - switch(x.Type) - { - case ProxyGroupType::Select: - case ProxyGroupType::URLTest: - case ProxyGroupType::Fallback: - break; - case ProxyGroupType::LoadBalance: - if(surge_ver < 1 && surge_ver != -3) - continue; - break; - case ProxyGroupType::SSID: - group = x.TypeStr() + ",default=" + x.Proxies[0] + ","; + switch (x.Type) { + case ProxyGroupType::Select: + case ProxyGroupType::URLTest: + case ProxyGroupType::Fallback: + break; + case ProxyGroupType::LoadBalance: + if (surge_ver < 1 && surge_ver != -3) + continue; + break; + case ProxyGroupType::SSID: + group = x.TypeStr() + ",default=" + x.Proxies[0] + ","; group += join(x.Proxies.begin() + 1, x.Proxies.end(), ","); ini.set("{NONAME}", x.Name + " = " + group); //insert order - continue; - default: - continue; + continue; + default: + continue; } - for(const auto &y : x.Proxies) + for (const auto &y: x.Proxies) groupGenerate(y, nodelist, filtered_nodelist, true, ext); - if(filtered_nodelist.empty()) + if (filtered_nodelist.empty()) filtered_nodelist.emplace_back("DIRECT"); - if(filtered_nodelist.size() == 1) - { + if (filtered_nodelist.size() == 1) { group = toLower(filtered_nodelist[0]); - switch(hash_(group)) - { - case "direct"_hash: - case "reject"_hash: - case "reject-tinygif"_hash: - ini.set("Proxy", "{NONAME}", x.Name + " = " + group); - continue; + switch (hash_(group)) { + case "direct"_hash: + case "reject"_hash: + case "reject-tinygif"_hash: + ini.set("Proxy", "{NONAME}", x.Name + " = " + group); + continue; } } group = x.TypeStr() + ","; group += join(filtered_nodelist, ","); - if(x.Type == ProxyGroupType::URLTest || x.Type == ProxyGroupType::Fallback || x.Type == ProxyGroupType::LoadBalance) - { + if (x.Type == ProxyGroupType::URLTest || x.Type == ProxyGroupType::Fallback || + x.Type == ProxyGroupType::LoadBalance) { group += ",url=" + x.Url + ",interval=" + std::to_string(x.Interval); - if(x.Tolerance > 0) + if (x.Tolerance > 0) group += ",tolerance=" + std::to_string(x.Tolerance); - if(x.Timeout > 0) + if (x.Timeout > 0) group += ",timeout=" + std::to_string(x.Timeout); - if(!x.Persistent.is_undef()) + if (!x.Persistent.is_undef()) group += ",persistent=" + x.Persistent.get_str(); - if(!x.EvaluateBeforeUse.is_undef()) + if (!x.EvaluateBeforeUse.is_undef()) group += ",evaluate-before-use=" + x.EvaluateBeforeUse.get_str(); } ini.set("{NONAME}", x.Name + " = " + group); //insert order } - if(ext.enable_rule_generator) + if (ext.enable_rule_generator) rulesetToSurge(ini, ruleset_content_array, surge_ver, ext.overwrite_original_rules, ext.managed_config_prefix); return ini.to_string(); } -std::string proxyToSingle(std::vector &nodes, int types, extra_settings &ext) -{ +std::string proxyToSingle(std::vector &nodes, int types, extra_settings &ext) { /// types: SS=1 SSR=2 VMess=4 Trojan=8 std::string proxyStr, allLinks; bool ss = GETBIT(types, 1), ssr = GETBIT(types, 2), vmess = GETBIT(types, 3), trojan = GETBIT(types, 4); - for(Proxy &x : nodes) - { + for (Proxy &x: nodes) { std::string remark = x.Remark; std::string &hostname = x.Hostname, &password = x.Password, &method = x.EncryptMethod, &plugin = x.Plugin, &pluginopts = x.PluginOption, &protocol = x.Protocol, &protoparam = x.ProtocolParam, &obfs = x.OBFS, &obfsparam = x.OBFSParam, &id = x.UserId, &transproto = x.TransferProtocol, &host = x.Host, &path = x.Path, &faketype = x.FakeType; bool &tlssecure = x.TLSSecure; std::string port = std::to_string(x.Port); std::string aid = std::to_string(x.AlterId); - switch(x.Type) - { - case ProxyType::Shadowsocks: - if(ss) - { - proxyStr = "ss://" + urlSafeBase64Encode(method + ":" + password) + "@" + hostname + ":" + port; - if(!plugin.empty() && !pluginopts.empty()) - { - proxyStr += "/?plugin=" + urlEncode(plugin + ";" + pluginopts); + switch (x.Type) { + case ProxyType::Shadowsocks: + if (ss) { + proxyStr = "ss://" + urlSafeBase64Encode(method + ":" + password) + "@" + hostname + ":" + port; + if (!plugin.empty() && !pluginopts.empty()) { + proxyStr += "/?plugin=" + urlEncode(plugin + ";" + pluginopts); + } + proxyStr += "#" + urlEncode(remark); + } else if (ssr) { + if (std::find(ssr_ciphers.begin(), ssr_ciphers.end(), method) != ssr_ciphers.end() && + plugin.empty()) + proxyStr = "ssr://" + urlSafeBase64Encode( + hostname + ":" + port + ":origin:" + method + ":plain:" + urlSafeBase64Encode(password) \ + + "/?group=" + urlSafeBase64Encode(x.Group) + "&remarks=" + urlSafeBase64Encode(remark)); + } else + continue; + break; + case ProxyType::ShadowsocksR: + if (ssr) { + proxyStr = "ssr://" + urlSafeBase64Encode( + hostname + ":" + port + ":" + protocol + ":" + method + ":" + obfs + ":" + + urlSafeBase64Encode(password) \ + + "/?group=" + urlSafeBase64Encode(x.Group) + "&remarks=" + urlSafeBase64Encode(remark) \ + + "&obfsparam=" + urlSafeBase64Encode(obfsparam) + "&protoparam=" + urlSafeBase64Encode(protoparam)); + } else if (ss) { + if (std::find(ss_ciphers.begin(), ss_ciphers.end(), method) != ss_ciphers.end() && + protocol == "origin" && obfs == "plain") + proxyStr = + "ss://" + urlSafeBase64Encode(method + ":" + password) + "@" + hostname + ":" + port + + "#" + urlEncode(remark); + } else + continue; + break; + case ProxyType::VMess: + if (!vmess) + continue; + proxyStr = "vmess://" + base64Encode( + vmessLinkConstruct(remark, hostname, port, faketype, id, aid, transproto, path, host, + tlssecure ? "tls" : "")); + break; + case ProxyType::Trojan: + if (!trojan) + continue; + proxyStr = "trojan://" + password + "@" + hostname + ":" + port + "?allowInsecure=" + + (x.AllowInsecure.get() ? "1" : "0"); + if (!host.empty()) + proxyStr += "&sni=" + host; + if (transproto == "ws") { + proxyStr += "&ws=1"; + if (!path.empty()) + proxyStr += "&wspath=" + urlEncode(path); } proxyStr += "#" + urlEncode(remark); - } - else if(ssr) - { - if(std::find(ssr_ciphers.begin(), ssr_ciphers.end(), method) != ssr_ciphers.end() && plugin.empty()) - proxyStr = "ssr://" + urlSafeBase64Encode(hostname + ":" + port + ":origin:" + method + ":plain:" + urlSafeBase64Encode(password) \ - + "/?group=" + urlSafeBase64Encode(x.Group) + "&remarks=" + urlSafeBase64Encode(remark)); - } - else + break; + default: continue; - break; - case ProxyType::ShadowsocksR: - if(ssr) - { - proxyStr = "ssr://" + urlSafeBase64Encode(hostname + ":" + port + ":" + protocol + ":" + method + ":" + obfs + ":" + urlSafeBase64Encode(password) \ - + "/?group=" + urlSafeBase64Encode(x.Group) + "&remarks=" + urlSafeBase64Encode(remark) \ - + "&obfsparam=" + urlSafeBase64Encode(obfsparam) + "&protoparam=" + urlSafeBase64Encode(protoparam)); - } - else if(ss) - { - if(std::find(ss_ciphers.begin(), ss_ciphers.end(), method) != ss_ciphers.end() && protocol == "origin" && obfs == "plain") - proxyStr = "ss://" + urlSafeBase64Encode(method + ":" + password) + "@" + hostname + ":" + port + "#" + urlEncode(remark); - } - else - continue; - break; - case ProxyType::VMess: - if(!vmess) - continue; - proxyStr = "vmess://" + base64Encode(vmessLinkConstruct(remark, hostname, port, faketype, id, aid, transproto, path, host, tlssecure ? "tls" : "")); - break; - case ProxyType::Trojan: - if(!trojan) - continue; - proxyStr = "trojan://" + password + "@" + hostname + ":" + port + "?allowInsecure=" + (x.AllowInsecure.get() ? "1" : "0"); - if(!host.empty()) - proxyStr += "&sni=" + host; - if(transproto == "ws") - { - proxyStr += "&ws=1"; - if(!path.empty()) - proxyStr += "&wspath=" + urlEncode(path); - } - proxyStr += "#" + urlEncode(remark); - break; - default: - continue; } allLinks += proxyStr + "\n"; } - if(ext.nodelist) + if (ext.nodelist) return allLinks; else return base64Encode(allLinks); } -std::string proxyToSSSub(std::string base_conf, std::vector &nodes, extra_settings &ext) -{ +std::string proxyToSSSub(std::string base_conf, std::vector &nodes, extra_settings &ext) { using namespace rapidjson_ext; rapidjson::Document base; auto &alloc = base.GetAllocator(); base_conf = trimWhitespace(base_conf); - if(base_conf.empty()) + if (base_conf.empty()) base_conf = "{}"; rapidjson::ParseResult result = base.Parse(base_conf.data()); if (!result) - writeLog(0, std::string("SIP008 base loader failed with error: ") + rapidjson::GetParseError_En(result.Code()) + " (" + std::to_string(result.Offset()) + ")", LOG_LEVEL_ERROR); + writeLog(0, std::string("SIP008 base loader failed with error: ") + rapidjson::GetParseError_En(result.Code()) + + " (" + std::to_string(result.Offset()) + ")", LOG_LEVEL_ERROR); rapidjson::Value proxies(rapidjson::kArrayType); - for(Proxy &x : nodes) - { + for (Proxy &x: nodes) { std::string &remark = x.Remark; std::string &hostname = x.Hostname; std::string &password = x.Password; @@ -1032,18 +1067,18 @@ std::string proxyToSSSub(std::string base_conf, std::vector &nodes, extra std::string &protocol = x.Protocol; std::string &obfs = x.OBFS; - switch(x.Type) - { - case ProxyType::Shadowsocks: - if(plugin == "simple-obfs") - plugin = "obfs-local"; - break; - case ProxyType::ShadowsocksR: - if(std::find(ss_ciphers.begin(), ss_ciphers.end(), method) == ss_ciphers.end() || protocol != "origin" || obfs != "plain") + switch (x.Type) { + case ProxyType::Shadowsocks: + if (plugin == "simple-obfs") + plugin = "obfs-local"; + break; + case ProxyType::ShadowsocksR: + if (std::find(ss_ciphers.begin(), ss_ciphers.end(), method) == ss_ciphers.end() || + protocol != "origin" || obfs != "plain") + continue; + break; + default: continue; - break; - default: - continue; } rapidjson::Value proxy(rapidjson::kObjectType); proxy.CopyFrom(base, alloc) @@ -1059,42 +1094,39 @@ std::string proxyToSSSub(std::string base_conf, std::vector &nodes, extra return proxies | SerializeObject(); } -std::string proxyToQuan(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) -{ +std::string +proxyToQuan(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, + const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { INIReader ini; ini.store_any_line = true; - if(!ext.nodelist && ini.parse(base_conf) != 0) - { + if (!ext.nodelist && ini.parse(base_conf) != 0) { writeLog(0, "Quantumult base loader failed with error: " + ini.get_last_error(), LOG_LEVEL_ERROR); return ""; } proxyToQuan(nodes, ini, ruleset_content_array, extra_proxy_group, ext); - if(ext.nodelist) - { + if (ext.nodelist) { string_array allnodes; std::string allLinks; ini.get_all("SERVER", "{NONAME}", allnodes); - if(!allnodes.empty()) + if (!allnodes.empty()) allLinks = join(allnodes, "\n"); return base64Encode(allLinks); } return ini.to_string(); } -void proxyToQuan(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) -{ +void proxyToQuan(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, + const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { std::string proxyStr; std::vector nodelist; string_array remarks_list; ini.set_current_section("SERVER"); ini.erase_section(); - for(Proxy &x : nodes) - { - if(ext.append_proxy_type) - { + for (Proxy &x: nodes) { + if (ext.append_proxy_type) { std::string type = getProxyTypeName(x.Type); x.Remark = "[" + type + "] " + x.Remark; } @@ -1106,108 +1138,106 @@ void proxyToQuan(std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) -{ +std::string proxyToQuanX(std::vector &nodes, const std::string &base_conf, + std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, + extra_settings &ext) { INIReader ini; ini.store_any_line = true; ini.add_direct_save_section("general"); @@ -1296,28 +1322,26 @@ std::string proxyToQuanX(std::vector &nodes, const std::string &base_conf ini.add_direct_save_section("task_local"); ini.add_direct_save_section("mitm"); ini.add_direct_save_section("server_remote"); - if(!ext.nodelist && ini.parse(base_conf) != 0) - { + if (!ext.nodelist && ini.parse(base_conf) != 0) { writeLog(0, "QuantumultX base loader failed with error: " + ini.get_last_error(), LOG_LEVEL_ERROR); return ""; } proxyToQuanX(nodes, ini, ruleset_content_array, extra_proxy_group, ext); - if(ext.nodelist) - { + if (ext.nodelist) { string_array allnodes; std::string allLinks; ini.get_all("server_local", "{NONAME}", allnodes); - if(!allnodes.empty()) + if (!allnodes.empty()) allLinks = join(allnodes, "\n"); return allLinks; } return ini.to_string(); } -void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) -{ +void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, + const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { std::string proxyStr; tribool udp, tfo, scv, tls13; std::vector nodelist; @@ -1325,10 +1349,8 @@ void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vectorsecond, ","); - if(vArray.size() > 1) - { - if(trim(vArray[vArray.size() - 1]).find("img-url") == 0) + if (vArray.size() > 1) { + if (trim(vArray[vArray.size() - 1]).find("img-url") == 0) filtered_nodelist.emplace_back(trim(vArray[vArray.size() - 1])); } } @@ -1541,26 +1545,24 @@ void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector 0) + if (x.Tolerance > 0) singlegroup += ", tolerance=" + std::to_string(x.Tolerance); } ini.set("{NONAME}", singlegroup); } - if(ext.enable_rule_generator) + if (ext.enable_rule_generator) rulesetToSurge(ini, ruleset_content_array, -1, ext.overwrite_original_rules, ext.managed_config_prefix); } -std::string proxyToSSD(std::vector &nodes, std::string &group, std::string &userinfo, extra_settings &ext) -{ +std::string proxyToSSD(std::vector &nodes, std::string &group, std::string &userinfo, extra_settings &ext) { rapidjson::StringBuffer sb; rapidjson::Writer writer(sb); int index = 0; - if(group.empty()) + if (group.empty()) group = "SSD"; writer.StartObject(); @@ -1572,17 +1574,18 @@ std::string proxyToSSD(std::vector &nodes, std::string &group, std::strin writer.String("aes-128-gcm"); writer.Key("password"); writer.String("password"); - if(!userinfo.empty()) - { + if (!userinfo.empty()) { std::string data = replaceAllDistinct(userinfo, "; ", "&"); - std::string upload = getUrlArg(data, "upload"), download = getUrlArg(data, "download"), total = getUrlArg(data, "total"), expiry = getUrlArg(data, "expire"); - double used = (to_number(upload, 0.0) + to_number(download, 0.0)) / std::pow(1024, 3) * 1.0, tot = to_number(total, 0.0) / std::pow(1024, 3) * 1.0; + std::string upload = getUrlArg(data, "upload"), download = getUrlArg(data, "download"), total = getUrlArg(data, + "total"), expiry = getUrlArg( + data, "expire"); + double used = (to_number(upload, 0.0) + to_number(download, 0.0)) / std::pow(1024, 3) * 1.0, tot = + to_number(total, 0.0) / std::pow(1024, 3) * 1.0; writer.Key("traffic_used"); writer.Double(used); writer.Key("traffic_total"); writer.Double(tot); - if(!expiry.empty()) - { + if (!expiry.empty()) { const time_t rawtime = to_int(expiry); char buffer[30]; struct tm *dt = localtime(&rawtime); @@ -1594,37 +1597,13 @@ std::string proxyToSSD(std::vector &nodes, std::string &group, std::strin writer.Key("servers"); writer.StartArray(); - for(Proxy &x : nodes) - { + for (Proxy &x: nodes) { std::string &hostname = x.Hostname, &password = x.Password, &method = x.EncryptMethod, &plugin = x.Plugin, &pluginopts = x.PluginOption, &protocol = x.Protocol, &obfs = x.OBFS; - switch(x.Type) - { - case ProxyType::Shadowsocks: - if(plugin == "obfs-local") - plugin = "simple-obfs"; - writer.StartObject(); - writer.Key("server"); - writer.String(hostname.data()); - writer.Key("port"); - writer.Int(x.Port); - writer.Key("encryption"); - writer.String(method.data()); - writer.Key("password"); - writer.String(password.data()); - writer.Key("plugin"); - writer.String(plugin.data()); - writer.Key("plugin_options"); - writer.String(pluginopts.data()); - writer.Key("remarks"); - writer.String(x.Remark.data()); - writer.Key("id"); - writer.Int(index); - writer.EndObject(); - break; - case ProxyType::ShadowsocksR: - if(std::count(ss_ciphers.begin(), ss_ciphers.end(), method) > 0 && protocol == "origin" && obfs == "plain") - { + switch (x.Type) { + case ProxyType::Shadowsocks: + if (plugin == "obfs-local") + plugin = "simple-obfs"; writer.StartObject(); writer.Key("server"); writer.String(hostname.data()); @@ -1634,17 +1613,38 @@ std::string proxyToSSD(std::vector &nodes, std::string &group, std::strin writer.String(method.data()); writer.Key("password"); writer.String(password.data()); + writer.Key("plugin"); + writer.String(plugin.data()); + writer.Key("plugin_options"); + writer.String(pluginopts.data()); writer.Key("remarks"); writer.String(x.Remark.data()); writer.Key("id"); writer.Int(index); writer.EndObject(); break; - } - else + case ProxyType::ShadowsocksR: + if (std::count(ss_ciphers.begin(), ss_ciphers.end(), method) > 0 && protocol == "origin" && + obfs == "plain") { + writer.StartObject(); + writer.Key("server"); + writer.String(hostname.data()); + writer.Key("port"); + writer.Int(x.Port); + writer.Key("encryption"); + writer.String(method.data()); + writer.Key("password"); + writer.String(password.data()); + writer.Key("remarks"); + writer.String(x.Remark.data()); + writer.Key("id"); + writer.Int(index); + writer.EndObject(); + break; + } else + continue; + default: continue; - default: - continue; } index++; } @@ -1653,12 +1653,12 @@ std::string proxyToSSD(std::vector &nodes, std::string &group, std::strin return "ssd://" + base64Encode(sb.GetString()); } -std::string proxyToMellow(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) -{ +std::string proxyToMellow(std::vector &nodes, const std::string &base_conf, + std::vector &ruleset_content_array, + const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { INIReader ini; ini.store_any_line = true; - if(ini.parse(base_conf) != 0) - { + if (ini.parse(base_conf) != 0) { writeLog(0, "Mellow base loader failed with error: " + ini.get_last_error(), LOG_LEVEL_ERROR); return ""; } @@ -1668,8 +1668,8 @@ std::string proxyToMellow(std::vector &nodes, const std::string &base_con return ini.to_string(); } -void proxyToMellow(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) -{ +void proxyToMellow(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, + const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { std::string proxy; std::string username, password, method; std::string plugin, pluginopts; @@ -1681,10 +1681,8 @@ void proxyToMellow(std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) -{ +std::string +proxyToLoon(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, + const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { INIReader ini; std::string output_nodelist; std::vector nodelist; @@ -1818,8 +1814,7 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, ini.store_any_line = true; ini.add_direct_save_section("Plugin"); - if(ini.parse(base_conf) != INIREADER_EXCEPTION_NONE && !ext.nodelist) - { + if (ini.parse(base_conf) != INIREADER_EXCEPTION_NONE && !ext.nodelist) { writeLog(0, "Loon base loader failed with error: " + ini.get_last_error(), LOG_LEVEL_ERROR); return ""; } @@ -1827,10 +1822,8 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, ini.set_current_section("Proxy"); ini.erase_section(); - for(Proxy &x : nodes) - { - if(ext.append_proxy_type) - { + for (Proxy &x: nodes) { + if (ext.append_proxy_type) { std::string type = getProxyTypeName(x.Type); x.Remark = "[" + type + "] " + x.Remark; } @@ -1845,111 +1838,107 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, std::string proxy; - switch(x.Type) - { - case ProxyType::Shadowsocks: - proxy = "Shadowsocks," + hostname + "," + port + "," + method + ",\"" + password + "\""; - if(plugin == "simple-obfs" || plugin == "obfs-local") - { - if(!pluginopts.empty()) - proxy += "," + replaceAllDistinct(replaceAllDistinct(pluginopts, ";obfs-host=", ","), "obfs=", ""); - } - else if(!plugin.empty()) - continue; - break; - case ProxyType::VMess: - if(method == "auto") - method = "chacha20-ietf-poly1305"; - - proxy = "vmess," + hostname + "," + port + "," + method + ",\"" + id + "\",over-tls=" + (tlssecure ? "true" : "false"); - if(tlssecure) - proxy += ",tls-name=" + host; - switch(hash_(transproto)) - { - case "tcp"_hash: - proxy += ",transport=tcp"; + switch (x.Type) { + case ProxyType::Shadowsocks: + proxy = "Shadowsocks," + hostname + "," + port + "," + method + ",\"" + password + "\""; + if (plugin == "simple-obfs" || plugin == "obfs-local") { + if (!pluginopts.empty()) + proxy += "," + + replaceAllDistinct(replaceAllDistinct(pluginopts, ";obfs-host=", ","), "obfs=", ""); + } else if (!plugin.empty()) + continue; break; - case "ws"_hash: - proxy += ",transport=ws,path=" + path + ",host=" + host; + case ProxyType::VMess: + if (method == "auto") + method = "chacha20-ietf-poly1305"; + + proxy = "vmess," + hostname + "," + port + "," + method + ",\"" + id + "\",over-tls=" + + (tlssecure ? "true" : "false"); + if (tlssecure) + proxy += ",tls-name=" + host; + switch (hash_(transproto)) { + case "tcp"_hash: + proxy += ",transport=tcp"; + break; + case "ws"_hash: + proxy += ",transport=ws,path=" + path + ",host=" + host; + break; + default: + continue; + } + if (!scv.is_undef()) + proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); + break; + case ProxyType::ShadowsocksR: + proxy = "ShadowsocksR," + hostname + "," + port + "," + method + ",\"" + password + "\",protocol=" + + protocol + ",protocol-param=" + protoparam + ",obfs=" + obfs + ",obfs-param=" + obfsparam; + break; + case ProxyType::HTTP: + proxy = "http," + hostname + "," + port + "," + username + ",\"" + password + "\""; + break; + case ProxyType::HTTPS: + proxy = "https," + hostname + "," + port + "," + username + ",\"" + password + "\""; + if (!host.empty()) + proxy += ",tls-name=" + host; + if (!scv.is_undef()) + proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); + break; + case ProxyType::Trojan: + proxy = "trojan," + hostname + "," + port + ",\"" + password + "\""; + if (!host.empty()) + proxy += ",tls-name=" + host; + if (!scv.is_undef()) + proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); + break; + case ProxyType::SOCKS5: + proxy = "socks5," + hostname + "," + port; + if (!username.empty() && !password.empty()) + proxy += "," + username + ",\"" + password + "\""; + proxy += ",over-tls=" + std::string(tlssecure ? "true" : "false"); + if (tlssecure) { + if (!host.empty()) + proxy += ",tls-name=" + host; + if (!scv.is_undef()) + proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); + } + break; + case ProxyType::WireGuard: + proxy = "wireguard, interface-ip=" + x.SelfIP; + if (!x.SelfIPv6.empty()) + proxy += ", interface-ipv6=" + x.SelfIPv6; + proxy += ", private-key=" + x.PrivateKey; + for (const auto &y: x.DnsServers) { + if (isIPv4(y)) + proxy += ", dns=" + y; + else if (isIPv6(y)) + proxy += ", dnsv6=" + y; + } + if (x.Mtu > 0) + proxy += ", mtu=" + std::to_string(x.Mtu); + if (x.KeepAlive > 0) + proxy += ", keepalive=" + std::to_string(x.KeepAlive); + proxy += ", peers=[{" + generatePeer(x, true) + "}]"; break; default: continue; - } - if(!scv.is_undef()) - proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); - break; - case ProxyType::ShadowsocksR: - proxy = "ShadowsocksR," + hostname + "," + port + "," + method + ",\"" + password + "\",protocol=" + protocol + ",protocol-param=" + protoparam + ",obfs=" + obfs + ",obfs-param=" + obfsparam; - break; - case ProxyType::HTTP: - proxy = "http," + hostname + "," + port + "," + username + ",\"" + password + "\""; - break; - case ProxyType::HTTPS: - proxy = "https," + hostname + "," + port + "," + username + ",\"" + password + "\""; - if(!host.empty()) - proxy += ",tls-name=" + host; - if(!scv.is_undef()) - proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); - break; - case ProxyType::Trojan: - proxy = "trojan," + hostname + "," + port + ",\"" + password + "\""; - if(!host.empty()) - proxy += ",tls-name=" + host; - if(!scv.is_undef()) - proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); - break; - case ProxyType::SOCKS5: - proxy = "socks5," + hostname + "," + port; - if (!username.empty() && !password.empty()) - proxy += "," + username + ",\"" + password + "\""; - proxy += ",over-tls=" + std::string(tlssecure ? "true" : "false"); - if (tlssecure) - { - if(!host.empty()) - proxy += ",tls-name=" + host; - if(!scv.is_undef()) - proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); - } - break; - case ProxyType::WireGuard: - proxy = "wireguard, interface-ip=" + x.SelfIP; - if(!x.SelfIPv6.empty()) - proxy += ", interface-ipv6=" + x.SelfIPv6; - proxy += ", private-key=" + x.PrivateKey; - for(const auto &y : x.DnsServers) - { - if(isIPv4(y)) - proxy += ", dns=" + y; - else if(isIPv6(y)) - proxy += ", dnsv6=" + y; - } - if(x.Mtu > 0) - proxy += ", mtu=" + std::to_string(x.Mtu); - if(x.KeepAlive > 0) - proxy += ", keepalive=" + std::to_string(x.KeepAlive); - proxy += ", peers=[{" + generatePeer(x, true) + "}]"; - break; - default: - continue; } - if(ext.tfo) + if (ext.tfo) proxy += ",fast-open=true"; - if(ext.udp) + if (ext.udp) proxy += ",udp=true"; - if(ext.nodelist) + if (ext.nodelist) output_nodelist += x.Remark + " = " + proxy + "\n"; - else - { + else { ini.set("{NONAME}", x.Remark + " = " + proxy); nodelist.emplace_back(x); remarks_list.emplace_back(x.Remark); } } - if(ext.nodelist) + if (ext.nodelist) return output_nodelist; string_multimap original_groups; @@ -1957,46 +1946,42 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, ini.get_items(original_groups); ini.erase_section(); - for(const ProxyGroupConfig &x : extra_proxy_group) - { + for (const ProxyGroupConfig &x: extra_proxy_group) { string_array filtered_nodelist; std::string group, group_extra; - switch(x.Type) - { - case ProxyGroupType::Select: - case ProxyGroupType::LoadBalance: - case ProxyGroupType::URLTest: - case ProxyGroupType::Fallback: - break; - case ProxyGroupType::SSID: - if(x.Proxies.size() < 2) - continue; - group = x.TypeStr() + ",default=" + x.Proxies[0] + ","; - group += join(x.Proxies.begin() + 1, x.Proxies.end(), ","); + switch (x.Type) { + case ProxyGroupType::Select: + case ProxyGroupType::LoadBalance: + case ProxyGroupType::URLTest: + case ProxyGroupType::Fallback: + break; + case ProxyGroupType::SSID: + if (x.Proxies.size() < 2) + continue; + group = x.TypeStr() + ",default=" + x.Proxies[0] + ","; + group += join(x.Proxies.begin() + 1, x.Proxies.end(), ","); ini.set("{NONAME}", x.Name + " = " + group); //insert order - continue; - default: - continue; + continue; + default: + continue; } - for(const auto &y : x.Proxies) + for (const auto &y: x.Proxies) groupGenerate(y, nodelist, filtered_nodelist, true, ext); - if(filtered_nodelist.empty()) + if (filtered_nodelist.empty()) filtered_nodelist.emplace_back("DIRECT"); - auto iter = std::find_if(original_groups.begin(), original_groups.end(), [&](const string_multimap::value_type &n) - { - return trim(n.first) == x.Name; - }); + auto iter = std::find_if(original_groups.begin(), original_groups.end(), + [&](const string_multimap::value_type &n) { + return trim(n.first) == x.Name; + }); - if(iter != original_groups.end()) - { + if (iter != original_groups.end()) { string_array vArray = split(iter->second, ","); - if(vArray.size() > 1) - { - if(trim(vArray[vArray.size() - 1]).find("img-url") == 0) + if (vArray.size() > 1) { + if (trim(vArray[vArray.size() - 1]).find("img-url") == 0) filtered_nodelist.emplace_back(trim(vArray[vArray.size() - 1])); } } @@ -2007,64 +1992,54 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, group += "," + y; */ group += join(filtered_nodelist, ","); - if(x.Type != ProxyGroupType::Select) - { + if (x.Type != ProxyGroupType::Select) { group += ",url=" + x.Url + ",interval=" + std::to_string(x.Interval); - if(x.Type == ProxyGroupType::LoadBalance) - { + if (x.Type == ProxyGroupType::LoadBalance) { group += ",algorithm=" + std::string(x.Strategy == BalanceStrategy::RoundRobin ? "round-robin" : "pcc"); - if(x.Timeout > 0) + if (x.Timeout > 0) group += ",max-timeout=" + std::to_string(x.Timeout); } - if(x.Type == ProxyGroupType::URLTest) - { - if(x.Tolerance > 0) + if (x.Type == ProxyGroupType::URLTest) { + if (x.Tolerance > 0) group += ",tolerance=" + std::to_string(x.Tolerance); } - if(x.Type == ProxyGroupType::Fallback) + if (x.Type == ProxyGroupType::Fallback) group += ",max-timeout=" + std::to_string(x.Timeout); } ini.set("{NONAME}", x.Name + " = " + group); //insert order } - if(ext.enable_rule_generator) + if (ext.enable_rule_generator) rulesetToSurge(ini, ruleset_content_array, -4, ext.overwrite_original_rules, ext.managed_config_prefix); return ini.to_string(); } -static std::string formatSingBoxInterval(Integer interval) -{ +static std::string formatSingBoxInterval(Integer interval) { std::string result; - if(interval >= 3600) - { + if (interval >= 3600) { result += std::to_string(interval / 3600) + "h"; interval %= 3600; } - if(interval >= 60) - { + if (interval >= 60) { result += std::to_string(interval / 60) + "m"; interval %= 60; } - if(interval > 0) + if (interval > 0) result += std::to_string(interval) + "s"; return result; } -static rapidjson::Value buildSingBoxTransport(const Proxy& proxy, rapidjson::MemoryPoolAllocator<>& allocator) -{ +static rapidjson::Value buildSingBoxTransport(const Proxy &proxy, rapidjson::MemoryPoolAllocator<> &allocator) { rapidjson::Value transport(rapidjson::kObjectType); - switch (hash_(proxy.TransferProtocol)) - { - case "http"_hash: - { + switch (hash_(proxy.TransferProtocol)) { + case "http"_hash: { if (!proxy.Host.empty()) transport.AddMember("host", rapidjson::StringRef(proxy.Host.c_str()), allocator); [[fallthrough]]; } - case "ws"_hash: - { + case "ws"_hash: { transport.AddMember("type", rapidjson::StringRef(proxy.TransferProtocol.c_str()), allocator); if (proxy.Path.empty()) transport.AddMember("path", "/", allocator); @@ -2079,8 +2054,7 @@ static rapidjson::Value buildSingBoxTransport(const Proxy& proxy, rapidjson::Mem transport.AddMember("headers", headers, allocator); break; } - case "grpc"_hash: - { + case "grpc"_hash: { transport.AddMember("type", "grpc", allocator); if (!proxy.Path.empty()) transport.AddMember("service_name", rapidjson::StringRef(proxy.Path.c_str()), allocator); @@ -2092,32 +2066,34 @@ static rapidjson::Value buildSingBoxTransport(const Proxy& proxy, rapidjson::Mem return transport; } -static void addSingBoxCommonMembers(rapidjson::Value &proxy, const Proxy &x, const rapidjson::GenericStringRef &type, rapidjson::MemoryPoolAllocator<> &allocator) -{ +static void addSingBoxCommonMembers(rapidjson::Value &proxy, const Proxy &x, + const rapidjson::GenericStringRef &type, + rapidjson::MemoryPoolAllocator<> &allocator) { proxy.AddMember("type", type, allocator); proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); proxy.AddMember("server_port", x.Port, allocator); } -static rapidjson::Value stringArrayToJsonArray(const std::string &array, const std::string &delimiter, rapidjson::MemoryPoolAllocator<> &allocator) -{ +static rapidjson::Value stringArrayToJsonArray(const std::string &array, const std::string &delimiter, + rapidjson::MemoryPoolAllocator<> &allocator) { rapidjson::Value result(rapidjson::kArrayType); string_array vArray = split(array, delimiter); - for (const auto &x : vArray) + for (const auto &x: vArray) result.PushBack(rapidjson::Value(trim(x).c_str(), allocator), allocator); return result; } -void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { +void +proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::vector &ruleset_content_array, + const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { using namespace rapidjson_ext; rapidjson::Document::AllocatorType &allocator = json.GetAllocator(); rapidjson::Value outbounds(rapidjson::kArrayType), route(rapidjson::kArrayType); std::vector nodelist; string_array remarks_list; - if (!ext.nodelist) - { + if (!ext.nodelist) { auto direct = buildObject(allocator, "type", "direct", "tag", "DIRECT"); outbounds.PushBack(direct, allocator); auto reject = buildObject(allocator, "type", "block", "tag", "REJECT"); @@ -2126,29 +2102,26 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v outbounds.PushBack(dns, allocator); } - for (Proxy &x : nodes) - { + for (Proxy &x: nodes) { std::string type = getProxyTypeName(x.Type); if (ext.append_proxy_type) x.Remark = "[" + type + "] " + x.Remark; processRemark(x.Remark, remarks_list, false); - tribool udp = ext.udp, tfo = ext.tfo, scv = ext.skip_cert_verify; + tribool udp = ext.udp, tfo = ext.tfo, scv = ext.skip_cert_verify, xudp = ext.xudp; udp.define(x.UDP); + xudp.define(x.XUDP); tfo.define(x.TCPFastOpen); scv.define(x.AllowInsecure); rapidjson::Value proxy(rapidjson::kObjectType); - switch (x.Type) - { - case ProxyType::Shadowsocks: - { + switch (x.Type) { + case ProxyType::Shadowsocks: { addSingBoxCommonMembers(proxy, x, "shadowsocks", allocator); proxy.AddMember("method", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator); proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); - if(!x.Plugin.empty() && !x.PluginOption.empty()) - { + if (!x.Plugin.empty() && !x.PluginOption.empty()) { if (x.Plugin == "simple-obfs") x.Plugin = "obfs-local"; proxy.AddMember("plugin", rapidjson::StringRef(x.Plugin.c_str()), allocator); @@ -2156,8 +2129,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v } break; } - case ProxyType::ShadowsocksR: - { + case ProxyType::ShadowsocksR: { addSingBoxCommonMembers(proxy, x, "shadowsocksr", allocator); proxy.AddMember("method", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator); proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); @@ -2167,8 +2139,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v proxy.AddMember("obfs_param", rapidjson::StringRef(x.OBFSParam.c_str()), allocator); break; } - case ProxyType::VMess: - { + case ProxyType::VMess: { addSingBoxCommonMembers(proxy, x, "vmess", allocator); proxy.AddMember("uuid", rapidjson::StringRef(x.UserId.c_str()), allocator); proxy.AddMember("alter_id", x.AlterId, allocator); @@ -2179,8 +2150,66 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v proxy.AddMember("transport", transport, allocator); break; } - case ProxyType::Trojan: - { + // TODO VLESS后续支持 +// case ProxyType::VLESS: { +// addSingBoxCommonMembers(proxy, x, "vmess", allocator); +// proxy.AddMember("uuid", rapidjson::StringRef(x.UserId.c_str()), allocator); +// proxy.AddMember("tls", rapidjson::StringRef((std::string ("{\"enabled\": ") + std::string ((x.TLSSecure ? "true" : "false")) + "}").c_str()), allocator); +// if (!tfo.is_undef()) +// singleproxy["tfo"] = tfo.get(); +// if (xudp && udp) +// singleproxy["xudp"] = true; +// if (!x.Host.empty()) +// singleproxy["servername"] = x.Host; +// if (!x.Flow.empty()) +// singleproxy["flow"] = x.Flow; +// if (!scv.is_undef()) +// singleproxy["skip-cert-verify"] = scv.get(); +// switch (hash_(x.TransferProtocol)) { +// case "tcp"_hash: +// break; +// case "ws"_hash: +// singleproxy["network"] = x.TransferProtocol; +// if (ext.clash_new_field_name) { +// singleproxy["ws-opts"]["path"] = x.Path; +// if (!x.Host.empty()) +// singleproxy["ws-opts"]["headers"]["Host"] = x.Host; +// if (!x.Edge.empty()) +// singleproxy["ws-opts"]["headers"]["Edge"] = x.Edge; +// } else { +// singleproxy["ws-path"] = x.Path; +// if (!x.Host.empty()) +// singleproxy["ws-headers"]["Host"] = x.Host; +// if (!x.Edge.empty()) +// singleproxy["ws-headers"]["Edge"] = x.Edge; +// } +// break; +// case "http"_hash: +// singleproxy["network"] = x.TransferProtocol; +// singleproxy["http-opts"]["method"] = "GET"; +// singleproxy["http-opts"]["path"].push_back(x.Path); +// if (!x.Host.empty()) +// singleproxy["http-opts"]["headers"]["Host"].push_back(x.Host); +// if (!x.Edge.empty()) +// singleproxy["http-opts"]["headers"]["Edge"].push_back(x.Edge); +// break; +// case "h2"_hash: +// singleproxy["network"] = x.TransferProtocol; +// singleproxy["h2-opts"]["path"] = x.Path; +// if (!x.Host.empty()) +// singleproxy["h2-opts"]["host"].push_back(x.Host); +// break; +// case "grpc"_hash: +// singleproxy["network"] = x.TransferProtocol; +// singleproxy["grpc-opts"]["grpc-mode"] = x.GRPCMode; +// singleproxy["grpc-opts"]["grpc-service-name"] = x.GRPCServiceName; +// break; +// default: +// continue; +// } +// break; +// } + case ProxyType::Trojan: { addSingBoxCommonMembers(proxy, x, "trojan", allocator); proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); @@ -2189,8 +2218,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v proxy.AddMember("transport", transport, allocator); break; } - case ProxyType::WireGuard: - { + case ProxyType::WireGuard: { proxy.AddMember("type", "wireguard", allocator); proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); rapidjson::Value addresses(rapidjson::kArrayType); @@ -2207,14 +2235,12 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v if (!x.PreSharedKey.empty()) peer.AddMember("pre_shared_key", rapidjson::StringRef(x.PreSharedKey.c_str()), allocator); - if (!x.AllowedIPs.empty()) - { + if (!x.AllowedIPs.empty()) { auto allowed_ips = stringArrayToJsonArray(x.AllowedIPs, ",", allocator); peer.AddMember("allowed_ips", allowed_ips, allocator); } - if (!x.ClientId.empty()) - { + if (!x.ClientId.empty()) { auto reserved = stringArrayToJsonArray(x.ClientId, ",", allocator); peer.AddMember("reserved", reserved, allocator); } @@ -2226,39 +2252,75 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v break; } case ProxyType::HTTP: - case ProxyType::HTTPS: - { + case ProxyType::HTTPS: { addSingBoxCommonMembers(proxy, x, "http", allocator); proxy.AddMember("username", rapidjson::StringRef(x.Username.c_str()), allocator); proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); break; } - case ProxyType::SOCKS5: - { + case ProxyType::SOCKS5: { addSingBoxCommonMembers(proxy, x, "socks", allocator); proxy.AddMember("version", "5", allocator); proxy.AddMember("username", rapidjson::StringRef(x.Username.c_str()), allocator); proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); break; } + case ProxyType::Hysteria: { + addSingBoxCommonMembers(proxy, x, "hysteria", allocator); + proxy.AddMember("auth_str", rapidjson::StringRef(x.Auth.c_str()), allocator); + proxy.AddMember("up", rapidjson::StringRef(x.UpMbps.c_str()), allocator); + proxy.AddMember("down", rapidjson::StringRef(x.DownMbps.c_str()), allocator); + rapidjson::Value tls(rapidjson::kObjectType); + tls.AddMember("enabled", true, allocator); + proxy.AddMember("tls", tls, allocator); + if (!tfo.is_undef()) + proxy.AddMember("tcp_fast_open", rapidjson::StringRef(std::string(tfo.get() ? "true" : "false").c_str()), allocator); + if (!x.FakeType.empty()) + proxy.AddMember("network", rapidjson::StringRef(x.FakeType.c_str()), allocator); + if (!x.Alpn.empty()) + proxy.AddMember("tls", rapidjson::StringRef(std::string(R"({ "enabled": true,"alpn": [)"+x.Alpn+"],}").c_str()), allocator); + if (!x.OBFSParam.empty()) + proxy.AddMember("obfs", rapidjson::StringRef(x.OBFSParam.c_str()), allocator); + break; + } + case ProxyType::Hysteria2: { + addSingBoxCommonMembers(proxy, x, "hysteria2", allocator); + proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); + rapidjson::Value tls(rapidjson::kObjectType); + tls.AddMember("enabled", true, allocator); + proxy.AddMember("tls", tls, allocator); + if (!x.UpMbps.empty()) + proxy.AddMember("up_mbps", rapidjson::StringRef(x.UpMbps.c_str()), allocator); + if (!x.DownMbps.empty()) + proxy.AddMember("down_mbps", rapidjson::StringRef(x.DownMbps.c_str()), allocator); + if (!x.Alpn.empty()) + proxy.AddMember("tls", rapidjson::StringRef(std::string(R"({ "enabled": true,"alpn": [)"+x.Alpn+"],}").c_str()), allocator); + if (!x.OBFSParam.empty()){ + if (!x.OBFSPassword.empty()){ + proxy.AddMember("obfs", rapidjson::StringRef(std::string(R"({ "type": )"+x.OBFSParam+",password: \""+x.OBFSPassword+"\"}").c_str()), allocator); + } else{ + proxy.AddMember("obfs", rapidjson::StringRef(std::string(R"({ "type": )"+x.OBFSParam+"}").c_str()), allocator); + } + } + break; + } default: continue; } - if (x.TLSSecure) - { + if (x.TLSSecure) { rapidjson::Value tls(rapidjson::kObjectType); tls.AddMember("enabled", true, allocator); if (!x.ServerName.empty()) tls.AddMember("server_name", rapidjson::StringRef(x.ServerName.c_str()), allocator); + if (!x.Alpn.empty()) + tls.AddMember("alpn", rapidjson::StringRef(("["+x.Alpn+"]").c_str()), allocator); tls.AddMember("insecure", buildBooleanValue(scv), allocator); proxy.AddMember("tls", tls, allocator); } - if (!udp.is_undef() && !udp) - { + if (!udp.is_undef() && !udp) { proxy.AddMember("network", "tcp", allocator); } - if (!tfo.is_undef()) - { + if (!tfo.is_undef()) { proxy.AddMember("tcp_fast_open", buildBooleanValue(tfo), allocator); } nodelist.push_back(x); @@ -2266,34 +2328,29 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v outbounds.PushBack(proxy, allocator); } - if (ext.nodelist) - { + if (ext.nodelist) { json | AddMemberOrReplace("outbounds", outbounds, allocator); return; } - for (const ProxyGroupConfig &x: extra_proxy_group) - { + for (const ProxyGroupConfig &x: extra_proxy_group) { string_array filtered_nodelist; std::string type; - switch (x.Type) - { - case ProxyGroupType::Select: - { + switch (x.Type) { + case ProxyGroupType::Select: { type = "selector"; break; } case ProxyGroupType::URLTest: case ProxyGroupType::Fallback: - case ProxyGroupType::LoadBalance: - { + case ProxyGroupType::LoadBalance: { type = "urltest"; break; } default: continue; } - for (const auto &y : x.Proxies) + for (const auto &y: x.Proxies) groupGenerate(y, nodelist, filtered_nodelist, true, ext); if (filtered_nodelist.empty()) @@ -2305,31 +2362,28 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v group.AddMember("tag", rapidjson::Value(x.Name.c_str(), allocator), allocator); rapidjson::Value group_outbounds(rapidjson::kArrayType); - for (const std::string& y: filtered_nodelist) - { + for (const std::string &y: filtered_nodelist) { group_outbounds.PushBack(rapidjson::Value(y.c_str(), allocator), allocator); } group.AddMember("outbounds", group_outbounds, allocator); - if (x.Type == ProxyGroupType::URLTest) - { + if (x.Type == ProxyGroupType::URLTest) { group.AddMember("url", rapidjson::Value(x.Url.c_str(), allocator), allocator); - group.AddMember("interval", rapidjson::Value(formatSingBoxInterval(x.Interval).c_str(), allocator), allocator); + group.AddMember("interval", rapidjson::Value(formatSingBoxInterval(x.Interval).c_str(), allocator), + allocator); if (x.Tolerance > 0) group.AddMember("tolerance", x.Tolerance, allocator); } outbounds.PushBack(group, allocator); } - if (global.singBoxAddClashModes) - { + if (global.singBoxAddClashModes) { auto global_group = rapidjson::Value(rapidjson::kObjectType); global_group.AddMember("type", "selector", allocator); global_group.AddMember("tag", "GLOBAL", allocator); global_group.AddMember("outbounds", rapidjson::Value(rapidjson::kArrayType), allocator); global_group["outbounds"].PushBack("DIRECT", allocator); - for (auto &x: remarks_list) - { + for (auto &x: remarks_list) { global_group["outbounds"].PushBack(rapidjson::Value(x.c_str(), allocator), allocator); } outbounds.PushBack(global_group, allocator); @@ -2338,29 +2392,26 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v json | AddMemberOrReplace("outbounds", outbounds, allocator); } -std::string proxyToSingBox(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) -{ +std::string proxyToSingBox(std::vector &nodes, const std::string &base_conf, + std::vector &ruleset_content_array, + const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { using namespace rapidjson_ext; rapidjson::Document json; - if (!ext.nodelist) - { + if (!ext.nodelist) { json.Parse(base_conf.data()); - if (json.HasParseError()) - { + if (json.HasParseError()) { writeLog(0, "sing-box base loader failed with error: " + std::string(rapidjson::GetParseError_En(json.GetParseError())), LOG_LEVEL_ERROR); return ""; } - } - else - { + } else { json.SetObject(); } proxyToSingBox(nodes, json, ruleset_content_array, extra_proxy_group, ext); - if(ext.nodelist || !ext.enable_rule_generator) + if (ext.nodelist || !ext.enable_rule_generator) return json | SerializeObject(); rulesetToSingBox(json, ruleset_content_array, ext.overwrite_original_rules); diff --git a/src/generator/config/subexport.h b/src/generator/config/subexport.h index 241dc7c..b157e11 100644 --- a/src/generator/config/subexport.h +++ b/src/generator/config/subexport.h @@ -34,6 +34,7 @@ struct extra_settings std::string quanx_dev_id; tribool udp = tribool(); tribool tfo = tribool(); + tribool xudp = tribool(); tribool skip_cert_verify = tribool(); tribool tls13 = tribool(); bool clash_classical_ruleset = false; diff --git a/src/parser/config/proxy.h b/src/parser/config/proxy.h index fd0f94f..de0f259 100644 --- a/src/parser/config/proxy.h +++ b/src/parser/config/proxy.h @@ -9,8 +9,7 @@ using String = std::string; using StringArray = std::vector; -enum class ProxyType -{ +enum class ProxyType { Unknown, Shadowsocks, ShadowsocksR, @@ -20,36 +19,44 @@ enum class ProxyType HTTP, HTTPS, SOCKS5, - WireGuard + WireGuard, + VLESS, + Hysteria, + Hysteria2 }; -inline String getProxyTypeName(ProxyType type) -{ - switch(type) - { - case ProxyType::Shadowsocks: - return "SS"; - case ProxyType::ShadowsocksR: - return "SSR"; - case ProxyType::VMess: - return "VMess"; - case ProxyType::Trojan: - return "Trojan"; - case ProxyType::Snell: - return "Snell"; - case ProxyType::HTTP: - return "HTTP"; - case ProxyType::HTTPS: - return "HTTPS"; - case ProxyType::SOCKS5: - return "SOCKS5"; - default: - return "Unknown"; +inline String getProxyTypeName(ProxyType type) { + switch (type) { + case ProxyType::Shadowsocks: + return "SS"; + case ProxyType::ShadowsocksR: + return "SSR"; + case ProxyType::VMess: + return "VMess"; + case ProxyType::Trojan: + return "Trojan"; + case ProxyType::Snell: + return "Snell"; + case ProxyType::HTTP: + return "HTTP"; + case ProxyType::HTTPS: + return "HTTPS"; + case ProxyType::SOCKS5: + return "SOCKS5"; + case ProxyType::WireGuard: + return "WireGuard"; + case ProxyType::VLESS: + return "Vless"; + case ProxyType::Hysteria: + return "Hysteria"; + case ProxyType::Hysteria2: + return "Hysteria2"; + default: + return "Unknown"; } } -struct Proxy -{ +struct Proxy { ProxyType Type = ProxyType::Unknown; uint32_t Id = 0; uint32_t GroupId = 0; @@ -81,6 +88,7 @@ struct Proxy String QUICSecret; tribool UDP; + tribool XUDP; tribool TCPFastOpen; tribool AllowInsecure; tribool TLS13; @@ -99,6 +107,19 @@ struct Proxy uint16_t KeepAlive = 0; String TestUrl; String ClientId; + + String Auth; + String Alpn; + String UpMbps; + String DownMbps; + String Insecure; + String Fingerprint; + String OBFSPassword; + String GRPCServiceName; + String GRPCMode; + String ShortId; + String Flow; + bool FlowShow = false; }; #define SS_DEFAULT_GROUP "SSProvider" @@ -109,5 +130,8 @@ struct Proxy #define TROJAN_DEFAULT_GROUP "TrojanProvider" #define SNELL_DEFAULT_GROUP "SnellProvider" #define WG_DEFAULT_GROUP "WireGuardProvider" +#define XRAY_DEFAULT_GROUP "XRayProvider" +#define HYSTERIA_DEFAULT_GROUP "HysteriaProvider" +#define HYSTERIA2_DEFAULT_GROUP "Hysteria2Provider" #endif // PROXY_H_INCLUDED diff --git a/src/parser/subparser.cpp b/src/parser/subparser.cpp index 9f85692..46f1aad 100644 --- a/src/parser/subparser.cpp +++ b/src/parser/subparser.cpp @@ -17,16 +17,25 @@ using namespace rapidjson; using namespace rapidjson_ext; using namespace YAML; -string_array ss_ciphers = {"rc4-md5", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "bf-cfb", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "salsa20", "chacha20", "chacha20-ietf", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "2022-blake3-chacha12-poly1305", "2022-blake3-chacha8-poly1305"}; -string_array ssr_ciphers = {"none", "table", "rc4", "rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "cast5-cfb", "des-cfb", "idea-cfb", "rc2-cfb", "seed-cfb", "salsa20", "chacha20", "chacha20-ietf"}; +string_array ss_ciphers = {"rc4-md5", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "aes-128-cfb", "aes-192-cfb", + "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "camellia-128-cfb", + "camellia-192-cfb", "camellia-256-cfb", "bf-cfb", "chacha20-ietf-poly1305", + "xchacha20-ietf-poly1305", "salsa20", "chacha20", "chacha20-ietf", "2022-blake3-aes-128-gcm", + "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "2022-blake3-chacha12-poly1305", + "2022-blake3-chacha8-poly1305"}; +string_array ssr_ciphers = {"none", "table", "rc4", "rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", + "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "bf-cfb", "camellia-128-cfb", + "camellia-192-cfb", "camellia-256-cfb", "cast5-cfb", "des-cfb", "idea-cfb", "rc2-cfb", + "seed-cfb", "salsa20", "chacha20", "chacha20-ietf"}; std::map parsedMD5; std::string modSSMD5 = "f7653207090ce3389115e9c88541afe0"; //remake from speedtestutil -void commonConstruct(Proxy &node, ProxyType type, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const tribool &udp, const tribool &tfo, const tribool &scv, const tribool &tls13) -{ +void commonConstruct(Proxy &node, ProxyType type, const std::string &group, const std::string &remarks, + const std::string &server, const std::string &port, const tribool &udp, const tribool &tfo, + const tribool &scv, const tribool &tls13) { node.Type = type; node.Group = group; node.Remark = remarks; @@ -38,8 +47,11 @@ void commonConstruct(Proxy &node, ProxyType type, const std::string &group, cons node.TLS13 = tls13; } -void vmessConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, const std::string &port, const std::string &type, const std::string &id, const std::string &aid, const std::string &net, const std::string &cipher, const std::string &path, const std::string &host, const std::string &edge, const std::string &tls, const std::string &sni, tribool udp, tribool tfo, tribool scv, tribool tls13) -{ +void vmessConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, + const std::string &port, const std::string &type, const std::string &id, const std::string &aid, + const std::string &net, const std::string &cipher, const std::string &path, const std::string &host, + const std::string &edge, const std::string &tls, const std::string &sni, tribool udp, tribool tfo, + tribool scv, tribool tls13) { commonConstruct(node, ProxyType::VMess, group, remarks, add, port, udp, tfo, scv, tls13); node.UserId = id.empty() ? "00000000-0000-0000-0000-000000000000" : id; node.AlterId = to_int(aid); @@ -48,13 +60,10 @@ void vmessConstruct(Proxy &node, const std::string &group, const std::string &re node.Edge = edge; node.ServerName = sni; - if(net == "quic") - { + if (net == "quic") { node.QUICSecure = host; node.QUICSecret = path; - } - else - { + } else { node.Host = (host.empty() && !isIPv4(add) && !isIPv6(add)) ? add.data() : trim(host); node.Path = path.empty() ? "/" : trim(path); } @@ -62,8 +71,10 @@ void vmessConstruct(Proxy &node, const std::string &group, const std::string &re node.TLSSecure = tls == "tls"; } -void ssrConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &protocol, const std::string &method, const std::string &obfs, const std::string &password, const std::string &obfsparam, const std::string &protoparam, tribool udp, tribool tfo, tribool scv) -{ +void ssrConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, + const std::string &port, const std::string &protocol, const std::string &method, + const std::string &obfs, const std::string &password, const std::string &obfsparam, + const std::string &protoparam, tribool udp, tribool tfo, tribool scv) { commonConstruct(node, ProxyType::ShadowsocksR, group, remarks, server, port, udp, tfo, scv, tribool()); node.Password = password; node.EncryptMethod = method; @@ -73,8 +84,10 @@ void ssrConstruct(Proxy &node, const std::string &group, const std::string &rema node.OBFSParam = obfsparam; } -void ssConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &password, const std::string &method, const std::string &plugin, const std::string &pluginopts, tribool udp, tribool tfo, tribool scv, tribool tls13) -{ +void ssConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, + const std::string &port, const std::string &password, const std::string &method, + const std::string &plugin, const std::string &pluginopts, tribool udp, tribool tfo, tribool scv, + tribool tls13) { commonConstruct(node, ProxyType::Shadowsocks, group, remarks, server, port, udp, tfo, scv, tls13); node.Password = password; node.EncryptMethod = method; @@ -82,23 +95,28 @@ void ssConstruct(Proxy &node, const std::string &group, const std::string &remar node.PluginOption = pluginopts; } -void socksConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &username, const std::string &password, tribool udp, tribool tfo, tribool scv) -{ +void socksConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, + const std::string &port, const std::string &username, const std::string &password, tribool udp, + tribool tfo, tribool scv) { commonConstruct(node, ProxyType::SOCKS5, group, remarks, server, port, udp, tfo, scv, tribool()); node.Username = username; node.Password = password; } -void httpConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &username, const std::string &password, bool tls, tribool tfo, tribool scv, tribool tls13) -{ - commonConstruct(node, tls ? ProxyType::HTTPS : ProxyType::HTTP, group, remarks, server, port, tribool(), tfo, scv, tls13); +void httpConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, + const std::string &port, const std::string &username, const std::string &password, bool tls, + tribool tfo, tribool scv, tribool tls13) { + commonConstruct(node, tls ? ProxyType::HTTPS : ProxyType::HTTP, group, remarks, server, port, tribool(), tfo, scv, + tls13); node.Username = username; node.Password = password; node.TLSSecure = tls; } -void trojanConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &password, const std::string &network, const std::string &host, const std::string &path, bool tlssecure, tribool udp, tribool tfo, tribool scv, tribool tls13) -{ +void trojanConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, + const std::string &port, const std::string &password, const std::string &network, + const std::string &host, const std::string &path, bool tlssecure, tribool udp, tribool tfo, + tribool scv, tribool tls13) { commonConstruct(node, ProxyType::Trojan, group, remarks, server, port, udp, tfo, scv, tls13); node.Password = password; node.Host = host; @@ -107,8 +125,9 @@ void trojanConstruct(Proxy &node, const std::string &group, const std::string &r node.Path = path; } -void snellConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &password, const std::string &obfs, const std::string &host, uint16_t version, tribool udp, tribool tfo, tribool scv) -{ +void snellConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, + const std::string &port, const std::string &password, const std::string &obfs, + const std::string &host, uint16_t version, tribool udp, tribool tfo, tribool scv) { commonConstruct(node, ProxyType::Snell, group, remarks, server, port, udp, tfo, scv, tribool()); node.Password = password; node.OBFS = obfs; @@ -116,7 +135,11 @@ void snellConstruct(Proxy &node, const std::string &group, const std::string &re node.SnellVersion = version; } -void wireguardConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &selfIp, const std::string &selfIpv6, const std::string &privKey, const std::string &pubKey, const std::string &psk, const string_array &dns, const std::string &mtu, const std::string &keepalive, const std::string &testUrl, const std::string &clientId, const tribool &udp) { +void wireguardConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, + const std::string &port, const std::string &selfIp, const std::string &selfIpv6, + const std::string &privKey, const std::string &pubKey, const std::string &psk, + const string_array &dns, const std::string &mtu, const std::string &keepalive, + const std::string &testUrl, const std::string &clientId, const tribool &udp) { commonConstruct(node, ProxyType::WireGuard, group, remarks, server, port, udp, tribool(), tribool(), tribool()); node.SelfIP = selfIp; node.SelfIPv6 = selfIpv6; @@ -130,35 +153,99 @@ void wireguardConstruct(Proxy &node, const std::string &group, const std::string node.ClientId = clientId; } -void explodeVmess(std::string vmess, Proxy &node) -{ +void hysteriaConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, + const std::string &port, const std::string &type, const std::string &auth, + const std::string &host, const std::string &up, const std::string &down, const std::string &alpn, + const std::string &obfsParam, const std::string &insecure, tribool udp, tribool tfo, tribool scv, + tribool tls13) { + commonConstruct(node, ProxyType::Hysteria, group, remarks, add, port, udp, tfo, scv, tls13); + node.Auth = auth; + node.Host = (host.empty() && !isIPv4(add) && !isIPv6(add)) ? add.data() : trim(host); + node.UpMbps = up; + node.DownMbps = down; + node.Alpn = alpn; + node.OBFSParam = obfsParam; + node.Insecure = insecure; + node.FakeType = type; +} + + +void vlessConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, + const std::string &port, const std::string &type, const std::string &id, const std::string &aid, + const std::string &net, const std::string &cipher, const std::string &flow, const std::string &mode, + const std::string &path, const std::string &host, const std::string &edge, const std::string &tls, + const std::string &pbk, const std::string &sid, const std::string &fp, tribool udp, tribool tfo, + tribool scv, tribool tls13) { + commonConstruct(node, ProxyType::VLESS, group, remarks, add, port, udp, tfo, scv, tls13); + node.UserId = id.empty() ? "00000000-0000-0000-0000-000000000000" : id; + node.AlterId = to_int(aid); + node.EncryptMethod = cipher; + node.TransferProtocol = net.empty() ? "tcp" : type == "http" ? "http" : net; + node.Edge = edge; + node.Flow = flow; + node.FakeType = type; + node.TLSSecure = tls == "tls" || tls == "xtls" || tls == "reality"; + node.PublicKey = pbk; + node.ShortId = sid; + node.Fingerprint = fp; + + switch (hash_(net)) { + case "grpc"_hash: + node.Host = host; + node.GRPCMode = mode.empty() ? "gun" : mode; + node.GRPCServiceName = path.empty() ? "/" : urlEncode(urlDecode(trim(path))); + break; + case "quic"_hash: + node.QUICSecure = host; + node.QUICSecret = path.empty() ? "/" : trim(path); + break; + default: + node.Host = (host.empty() && !isIPv4(add) && !isIPv6(add)) ? add.data() : trim(host); + node.Path = path.empty() ? "/" : urlDecode(trim(path)); + break; + } +} + + +void hysteria2Construct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, + const std::string &port, const std::string &password, const std::string &host, + const std::string &up, const std::string &down, const std::string &alpn, + const std::string &obfsParam, const std::string &obfsPassword, tribool udp, tribool tfo, + tribool scv) { + commonConstruct(node, ProxyType::Hysteria2, group, remarks, add, port, udp, tfo, scv, tribool()); + node.Password = password; + node.Host = (host.empty() && !isIPv4(add) && !isIPv6(add)) ? add.data() : trim(host); + node.UpMbps = up; + node.DownMbps = down; + node.Alpn = alpn; + node.OBFSParam = obfsParam; + node.OBFSPassword = obfsPassword; +} + +void explodeVmess(std::string vmess, Proxy &node) { std::string version, ps, add, port, type, id, aid, net, path, host, tls, sni; Document jsondata; std::vector vArray; - if(regMatch(vmess, "vmess://([A-Za-z0-9-_]+)\\?(.*)")) //shadowrocket style link + if (regMatch(vmess, "vmess://([A-Za-z0-9-_]+)\\?(.*)")) //shadowrocket style link { explodeShadowrocket(vmess, node); return; - } - else if(regMatch(vmess, "vmess://(.*?)@(.*)")) - { + } else if (regMatch(vmess, "vmess://(.*?)@(.*)")) { explodeStdVMess(vmess, node); return; - } - else if(regMatch(vmess, "vmess1://(.*?)\\?(.*)")) //kitsunebi style link + } else if (regMatch(vmess, "vmess1://(.*?)\\?(.*)")) //kitsunebi style link { explodeKitsunebi(vmess, node); return; } vmess = urlSafeBase64Decode(regReplace(vmess, "(vmess|vmess1)://", "")); - if(regMatch(vmess, "(.*?) = (.*)")) - { + if (regMatch(vmess, "(.*?) = (.*)")) { explodeQuan(vmess, node); return; } jsondata.Parse(vmess.data()); - if(jsondata.HasParseError() || !jsondata.IsObject()) + if (jsondata.HasParseError() || !jsondata.IsObject()) return; version = "1"; //link without version will treat as version 1 @@ -167,7 +254,7 @@ void explodeVmess(std::string vmess, Proxy &node) GetMember(jsondata, "ps", ps); GetMember(jsondata, "add", add); port = GetMember(jsondata, "port"); - if(port == "0") + if (port == "0") return; GetMember(jsondata, "type", type); GetMember(jsondata, "id", id); @@ -177,22 +264,19 @@ void explodeVmess(std::string vmess, Proxy &node) GetMember(jsondata, "host", host); GetMember(jsondata, "sni", sni); - switch(to_int(version)) - { - case 1: - if(!host.empty()) - { - vArray = split(host, ";"); - if(vArray.size() == 2) - { - host = vArray[0]; - path = vArray[1]; + switch (to_int(version)) { + case 1: + if (!host.empty()) { + vArray = split(host, ";"); + if (vArray.size() == 2) { + host = vArray[0]; + path = vArray[1]; + } } - } - break; - case 2: - path = GetMember(jsondata, "path"); - break; + break; + case 2: + path = GetMember(jsondata, "path"); + break; } add = trim(add); @@ -200,8 +284,7 @@ void explodeVmess(std::string vmess, Proxy &node) vmessConstruct(node, V2RAY_DEFAULT_GROUP, ps, add, port, type, id, aid, net, "auto", path, host, "", tls, sni); } -void explodeVmessConf(std::string content, std::vector &nodes) -{ +void explodeVmessConf(std::string content, std::vector &nodes) { Document json; rapidjson::Value nodejson, settings; std::string group, ps, add, port, type, id, aid, net, path, host, edge, tls, cipher, subid, sni; @@ -216,58 +299,51 @@ void explodeVmessConf(std::string content, std::vector &nodes) regGetMatch(content, "((?1)wssettings)", 2, 0, &wsset); json.Parse(content.data()); - if(json.HasParseError() || !json.IsObject()) + if (json.HasParseError() || !json.IsObject()) return; - try - { - if(json.HasMember("outbounds")) //single config + try { + if (json.HasMember("outbounds")) //single config { - if(json["outbounds"].Size() > 0 && json["outbounds"][0].HasMember("settings") && json["outbounds"][0]["settings"].HasMember("vnext") && json["outbounds"][0]["settings"]["vnext"].Size() > 0) - { + if (json["outbounds"].Size() > 0 && json["outbounds"][0].HasMember("settings") && + json["outbounds"][0]["settings"].HasMember("vnext") && + json["outbounds"][0]["settings"]["vnext"].Size() > 0) { Proxy node; nodejson = json["outbounds"][0]; add = GetMember(nodejson["settings"]["vnext"][0], "address"); port = GetMember(nodejson["settings"]["vnext"][0], "port"); - if(port == "0") + if (port == "0") return; - if(nodejson["settings"]["vnext"][0]["users"].Size()) - { + if (nodejson["settings"]["vnext"][0]["users"].Size()) { id = GetMember(nodejson["settings"]["vnext"][0]["users"][0], "id"); aid = GetMember(nodejson["settings"]["vnext"][0]["users"][0], "alterId"); cipher = GetMember(nodejson["settings"]["vnext"][0]["users"][0], "security"); } - if(nodejson.HasMember(streamset.data())) - { + if (nodejson.HasMember(streamset.data())) { net = GetMember(nodejson[streamset.data()], "network"); tls = GetMember(nodejson[streamset.data()], "security"); - if(net == "ws") - { - if(nodejson[streamset.data()].HasMember(wsset.data())) + if (net == "ws") { + if (nodejson[streamset.data()].HasMember(wsset.data())) settings = nodejson[streamset.data()][wsset.data()]; else settings.RemoveAllMembers(); path = GetMember(settings, "path"); - if(settings.HasMember("headers")) - { + if (settings.HasMember("headers")) { host = GetMember(settings["headers"], "Host"); edge = GetMember(settings["headers"], "Edge"); } } - if(nodejson[streamset.data()].HasMember(tcpset.data())) + if (nodejson[streamset.data()].HasMember(tcpset.data())) settings = nodejson[streamset.data()][tcpset.data()]; else settings.RemoveAllMembers(); - if(settings.IsObject() && settings.HasMember("header")) - { + if (settings.IsObject() && settings.HasMember("header")) { type = GetMember(settings["header"], "type"); - if(type == "http") - { - if(settings["header"].HasMember("request")) - { - if(settings["header"]["request"].HasMember("path") && settings["header"]["request"]["path"].Size()) + if (type == "http") { + if (settings["header"].HasMember("request")) { + if (settings["header"]["request"].HasMember("path") && + settings["header"]["request"]["path"].Size()) settings["header"]["request"]["path"][0] >> path; - if(settings["header"]["request"].HasMember("headers")) - { + if (settings["header"]["request"].HasMember("headers")) { host = GetMember(settings["header"]["request"]["headers"], "Host"); edge = GetMember(settings["header"]["request"]["headers"], "Edge"); } @@ -275,72 +351,72 @@ void explodeVmessConf(std::string content, std::vector &nodes) } } } - vmessConstruct(node, V2RAY_DEFAULT_GROUP, add + ":" + port, add, port, type, id, aid, net, cipher, path, host, edge, tls, "", udp, tfo, scv); + vmessConstruct(node, V2RAY_DEFAULT_GROUP, add + ":" + port, add, port, type, id, aid, net, cipher, path, + host, edge, tls, "", udp, tfo, scv); nodes.emplace_back(std::move(node)); } return; } } - catch(std::exception & e) - { + catch (std::exception &e) { //writeLog(0, "VMessConf parser throws an error. Leaving...", LOG_LEVEL_WARNING); //return; //ignore throw; } //read all subscribe remark as group name - for(uint32_t i = 0; i < json["subItem"].Size(); i++) - subdata.insert(std::pair(json["subItem"][i]["id"].GetString(), json["subItem"][i]["remarks"].GetString())); + for (uint32_t i = 0; i < json["subItem"].Size(); i++) + subdata.insert(std::pair(json["subItem"][i]["id"].GetString(), + json["subItem"][i]["remarks"].GetString())); - for(uint32_t i = 0; i < json["vmess"].Size(); i++) - { + for (uint32_t i = 0; i < json["vmess"].Size(); i++) { Proxy node; - if(json["vmess"][i]["address"].IsNull() || json["vmess"][i]["port"].IsNull() || json["vmess"][i]["id"].IsNull()) + if (json["vmess"][i]["address"].IsNull() || json["vmess"][i]["port"].IsNull() || + json["vmess"][i]["id"].IsNull()) continue; //common info json["vmess"][i]["remarks"] >> ps; json["vmess"][i]["address"] >> add; port = GetMember(json["vmess"][i], "port"); - if(port == "0") + if (port == "0") continue; json["vmess"][i]["subid"] >> subid; - if(!subid.empty()) - { + if (!subid.empty()) { iter = subdata.find(subid); - if(iter != subdata.end()) + if (iter != subdata.end()) group = iter->second; } - if(ps.empty()) + if (ps.empty()) ps = add + ":" + port; scv = GetMember(json["vmess"][i], "allowInsecure"); json["vmess"][i]["configType"] >> configType; - switch(configType) - { - case 1: //vmess config - json["vmess"][i]["headerType"] >> type; - json["vmess"][i]["id"] >> id; - json["vmess"][i]["alterId"] >> aid; - json["vmess"][i]["network"] >> net; - json["vmess"][i]["path"] >> path; - json["vmess"][i]["requestHost"] >> host; - json["vmess"][i]["streamSecurity"] >> tls; - json["vmess"][i]["security"] >> cipher; - json["vmess"][i]["sni"] >> sni; - vmessConstruct(node, V2RAY_DEFAULT_GROUP, ps, add, port, type, id, aid, net, cipher, path, host, "", tls, sni, udp, tfo, scv); - break; - case 3: //ss config - json["vmess"][i]["id"] >> id; - json["vmess"][i]["security"] >> cipher; - ssConstruct(node, SS_DEFAULT_GROUP, ps, add, port, id, cipher, "", "", udp, tfo, scv); - break; - case 4: //socks config - socksConstruct(node, SOCKS_DEFAULT_GROUP, ps, add, port, "", "", udp, tfo, scv); - break; - default: - continue; + switch (configType) { + case 1: //vmess config + json["vmess"][i]["headerType"] >> type; + json["vmess"][i]["id"] >> id; + json["vmess"][i]["alterId"] >> aid; + json["vmess"][i]["network"] >> net; + json["vmess"][i]["path"] >> path; + json["vmess"][i]["requestHost"] >> host; + json["vmess"][i]["streamSecurity"] >> tls; + json["vmess"][i]["security"] >> cipher; + json["vmess"][i]["sni"] >> sni; + vmessConstruct(node, V2RAY_DEFAULT_GROUP, ps, add, port, type, id, aid, net, cipher, path, host, "", + tls, sni, udp, tfo, scv); + break; + case 3: //ss config + json["vmess"][i]["id"] >> id; + json["vmess"][i]["security"] >> cipher; + ssConstruct(node, SS_DEFAULT_GROUP, ps, add, port, id, cipher, "", "", udp, tfo, scv); + break; + case 4: //socks config + socksConstruct(node, SOCKS_DEFAULT_GROUP, ps, add, port, "", "", udp, tfo, scv); + break; + default: + continue; } node.Id = index; nodes.emplace_back(std::move(node)); @@ -348,52 +424,46 @@ void explodeVmessConf(std::string content, std::vector &nodes) } } -void explodeSS(std::string ss, Proxy &node) -{ +void explodeSS(std::string ss, Proxy &node) { std::string ps, password, method, server, port, plugins, plugin, pluginopts, addition, group = SS_DEFAULT_GROUP, secret; //std::vector args, secret; ss = replaceAllDistinct(ss.substr(5), "/?", "?"); - if(strFind(ss, "#")) - { + if (strFind(ss, "#")) { auto sspos = ss.find('#'); ps = urlDecode(ss.substr(sspos + 1)); ss.erase(sspos); } - if(strFind(ss, "?")) - { + if (strFind(ss, "?")) { addition = ss.substr(ss.find('?') + 1); plugins = urlDecode(getUrlArg(addition, "plugin")); auto pluginpos = plugins.find(';'); plugin = plugins.substr(0, pluginpos); pluginopts = plugins.substr(pluginpos + 1); group = getUrlArg(addition, "group"); - if(!group.empty()) + if (!group.empty()) group = urlSafeBase64Decode(group); ss.erase(ss.find('?')); } - if(strFind(ss, "@")) - { - if(regGetMatch(ss, "(\\S+?)@(\\S+):(\\d+)", 4, 0, &secret, &server, &port)) + if (strFind(ss, "@")) { + if (regGetMatch(ss, "(\\S+?)@(\\S+):(\\d+)", 4, 0, &secret, &server, &port)) return; - if(regGetMatch(urlSafeBase64Decode(secret), "(\\S+?):(\\S+)", 3, 0, &method, &password)) + if (regGetMatch(urlSafeBase64Decode(secret), "(\\S+?):(\\S+)", 3, 0, &method, &password)) + return; + } else { + if (regGetMatch(urlSafeBase64Decode(ss), "(\\S+?):(\\S+)@(\\S+):(\\d+)", 5, 0, &method, &password, &server, + &port)) return; } - else - { - if(regGetMatch(urlSafeBase64Decode(ss), "(\\S+?):(\\S+)@(\\S+):(\\d+)", 5, 0, &method, &password, &server, &port)) - return; - } - if(port == "0") + if (port == "0") return; - if(ps.empty()) + if (ps.empty()) ps = server + ":" + port; ssConstruct(node, group, ps, server, port, password, method, plugin, pluginopts); } -void explodeSSD(std::string link, std::vector &nodes) -{ +void explodeSSD(std::string link, std::vector &nodes) { Document jsondata; uint32_t index = nodes.size(), listType = 0, listCount = 0; std::string group, port, method, password, server, remarks; @@ -402,34 +472,29 @@ void explodeSSD(std::string link, std::vector &nodes) link = urlSafeBase64Decode(link.substr(6)); jsondata.Parse(link.c_str()); - if(jsondata.HasParseError() || !jsondata.IsObject()) + if (jsondata.HasParseError() || !jsondata.IsObject()) return; - if(!jsondata.HasMember("servers")) + if (!jsondata.HasMember("servers")) return; GetMember(jsondata, "airport", group); - if(jsondata["servers"].IsArray()) - { + if (jsondata["servers"].IsArray()) { listType = 0; listCount = jsondata["servers"].Size(); - } - else if(jsondata["servers"].IsObject()) - { + } else if (jsondata["servers"].IsObject()) { listType = 1; listCount = jsondata["servers"].MemberCount(); uint32_t node_index = 0; - for(rapidjson::Value::MemberIterator iter = jsondata["servers"].MemberBegin(); iter != jsondata["servers"].MemberEnd(); iter++) - { + for (rapidjson::Value::MemberIterator iter = jsondata["servers"].MemberBegin(); + iter != jsondata["servers"].MemberEnd(); iter++) { node_map.emplace(node_index, iter->name.GetString()); node_index++; } - } - else + } else return; rapidjson::Value singlenode; - for(uint32_t i = 0; i < listCount; i++) - { + for (uint32_t i = 0; i < listCount; i++) { //get default info port = GetMember(jsondata, "port"); method = GetMember(jsondata, "encryption"); @@ -438,16 +503,15 @@ void explodeSSD(std::string link, std::vector &nodes) pluginopts = GetMember(jsondata, "plugin_options"); //get server-specific info - switch(listType) - { - case 0: - singlenode = jsondata["servers"][i]; - break; - case 1: - singlenode = jsondata["servers"].FindMember(node_map[i].data())->value; - break; - default: - continue; + switch (listType) { + case 0: + singlenode = jsondata["servers"][i]; + break; + case 1: + singlenode = jsondata["servers"].FindMember(node_map[i].data())->value; + break; + default: + continue; } singlenode["server"] >> server; GetMember(singlenode, "remarks", remarks); @@ -457,7 +521,7 @@ void explodeSSD(std::string link, std::vector &nodes) GetMember(singlenode, "plugin", plugin); GetMember(singlenode, "plugin_options", pluginopts); - if(port == "0") + if (port == "0") continue; Proxy node; @@ -468,8 +532,7 @@ void explodeSSD(std::string link, std::vector &nodes) } } -void explodeSSAndroid(std::string ss, std::vector &nodes) -{ +void explodeSSAndroid(std::string ss, std::vector &nodes) { std::string ps, password, method, server, port, group = SS_DEFAULT_GROUP; std::string plugin, pluginopts; @@ -478,20 +541,19 @@ void explodeSSAndroid(std::string ss, std::vector &nodes) //first add some extra data before parsing ss = "{\"nodes\":" + ss + "}"; json.Parse(ss.data()); - if(json.HasParseError() || !json.IsObject()) + if (json.HasParseError() || !json.IsObject()) return; - for(uint32_t i = 0; i < json["nodes"].Size(); i++) - { + for (uint32_t i = 0; i < json["nodes"].Size(); i++) { Proxy node; server = GetMember(json["nodes"][i], "server"); - if(server.empty()) + if (server.empty()) continue; ps = GetMember(json["nodes"][i], "remarks"); port = GetMember(json["nodes"][i], "server_port"); - if(port == "0") + if (port == "0") continue; - if(ps.empty()) + if (ps.empty()) ps = server + ":" + port; password = GetMember(json["nodes"][i], "password"); method = GetMember(json["nodes"][i], "method"); @@ -505,28 +567,26 @@ void explodeSSAndroid(std::string ss, std::vector &nodes) } } -void explodeSSConf(std::string content, std::vector &nodes) -{ +void explodeSSConf(std::string content, std::vector &nodes) { Document json; std::string ps, password, method, server, port, plugin, pluginopts, group = SS_DEFAULT_GROUP; auto index = nodes.size(); json.Parse(content.data()); - if(json.HasParseError() || !json.IsObject()) + if (json.HasParseError() || !json.IsObject()) return; const char *section = json.HasMember("version") && json.HasMember("servers") ? "servers" : "configs"; - if(!json.HasMember(section)) + if (!json.HasMember(section)) return; GetMember(json, "remarks", group); - for(uint32_t i = 0; i < json[section].Size(); i++) - { + for (uint32_t i = 0; i < json[section].Size(); i++) { Proxy node; ps = GetMember(json[section][i], "remarks"); port = GetMember(json[section][i], "server_port"); - if(port == "0") + if (port == "0") continue; - if(ps.empty()) + if (ps.empty()) ps = server + ":" + port; password = GetMember(json[section][i], "password"); @@ -542,14 +602,12 @@ void explodeSSConf(std::string content, std::vector &nodes) } } -void explodeSSR(std::string ssr, Proxy &node) -{ +void explodeSSR(std::string ssr, Proxy &node) { std::string strobfs; std::string remarks, group, server, port, method, password, protocol, protoparam, obfs, obfsparam; ssr = replaceAllDistinct(ssr.substr(6), "\r", ""); ssr = urlSafeBase64Decode(ssr); - if(strFind(ssr, "/?")) - { + if (strFind(ssr, "/?")) { strobfs = ssr.substr(ssr.find("/?") + 2); ssr = ssr.substr(0, ssr.find("/?")); group = urlSafeBase64Decode(getUrlArg(strobfs, "group")); @@ -558,38 +616,36 @@ void explodeSSR(std::string ssr, Proxy &node) protoparam = regReplace(urlSafeBase64Decode(getUrlArg(strobfs, "protoparam")), "\\s", ""); } - if(regGetMatch(ssr, "(\\S+):(\\d+?):(\\S+?):(\\S+?):(\\S+?):(\\S+)", 7, 0, &server, &port, &protocol, &method, &obfs, &password)) + if (regGetMatch(ssr, "(\\S+):(\\d+?):(\\S+?):(\\S+?):(\\S+?):(\\S+)", 7, 0, &server, &port, &protocol, &method, + &obfs, &password)) return; password = urlSafeBase64Decode(password); - if(port == "0") + if (port == "0") return; - if(group.empty()) + if (group.empty()) group = SSR_DEFAULT_GROUP; - if(remarks.empty()) + if (remarks.empty()) remarks = server + ":" + port; - if(find(ss_ciphers.begin(), ss_ciphers.end(), method) != ss_ciphers.end() && (obfs.empty() || obfs == "plain") && (protocol.empty() || protocol == "origin")) - { + if (find(ss_ciphers.begin(), ss_ciphers.end(), method) != ss_ciphers.end() && (obfs.empty() || obfs == "plain") && + (protocol.empty() || protocol == "origin")) { ssConstruct(node, group, remarks, server, port, password, method, "", ""); - } - else - { + } else { ssrConstruct(node, group, remarks, server, port, protocol, method, obfs, password, obfsparam, protoparam); } } -void explodeSSRConf(std::string content, std::vector &nodes) -{ +void explodeSSRConf(std::string content, std::vector &nodes) { Document json; std::string remarks, group, server, port, method, password, protocol, protoparam, obfs, obfsparam, plugin, pluginopts; auto index = nodes.size(); json.Parse(content.data()); - if(json.HasParseError() || !json.IsObject()) + if (json.HasParseError() || !json.IsObject()) return; - if(json.HasMember("local_port") && json.HasMember("local_address")) //single libev config + if (json.HasMember("local_port") && json.HasMember("local_address")) //single libev config { Proxy node; server = GetMember(json, "server"); @@ -598,34 +654,32 @@ void explodeSSRConf(std::string content, std::vector &nodes) method = GetMember(json, "method"); obfs = GetMember(json, "obfs"); protocol = GetMember(json, "protocol"); - if(find(ss_ciphers.begin(), ss_ciphers.end(), method) != ss_ciphers.end() && (obfs.empty() || obfs == "plain") && (protocol.empty() || protocol == "origin")) - { + if (find(ss_ciphers.begin(), ss_ciphers.end(), method) != ss_ciphers.end() && + (obfs.empty() || obfs == "plain") && (protocol.empty() || protocol == "origin")) { plugin = GetMember(json, "plugin"); pluginopts = GetMember(json, "plugin_opts"); ssConstruct(node, SS_DEFAULT_GROUP, remarks, server, port, password, method, plugin, pluginopts); - } - else - { + } else { protoparam = GetMember(json, "protocol_param"); obfsparam = GetMember(json, "obfs_param"); - ssrConstruct(node, SSR_DEFAULT_GROUP, remarks, server, port, protocol, method, obfs, password, obfsparam, protoparam); + ssrConstruct(node, SSR_DEFAULT_GROUP, remarks, server, port, protocol, method, obfs, password, obfsparam, + protoparam); } nodes.emplace_back(std::move(node)); return; } - for(uint32_t i = 0; i < json["configs"].Size(); i++) - { + for (uint32_t i = 0; i < json["configs"].Size(); i++) { Proxy node; group = GetMember(json["configs"][i], "group"); - if(group.empty()) + if (group.empty()) group = SSR_DEFAULT_GROUP; remarks = GetMember(json["configs"][i], "remarks"); server = GetMember(json["configs"][i], "server"); port = GetMember(json["configs"][i], "server_port"); - if(port == "0") + if (port == "0") continue; - if(remarks.empty()) + if (remarks.empty()) remarks = server + ":" + port; password = GetMember(json["configs"][i], "password"); @@ -643,37 +697,33 @@ void explodeSSRConf(std::string content, std::vector &nodes) } } -void explodeSocks(std::string link, Proxy &node) -{ +void explodeSocks(std::string link, Proxy &node) { std::string group, remarks, server, port, username, password; - if(strFind(link, "socks://")) //v2rayn socks link + if (strFind(link, "socks://")) //v2rayn socks link { - if(strFind(link, "#")) - { + if (strFind(link, "#")) { auto pos = link.find('#'); remarks = urlDecode(link.substr(pos + 1)); link.erase(pos); } link = urlSafeBase64Decode(link.substr(8)); - if(strFind(link, "@")) - { + if (strFind(link, "@")) { auto userinfo = split(link, '@'); - if(userinfo.size() < 2) + if (userinfo.size() < 2) return; link = userinfo[1]; userinfo = split(userinfo[0], ':'); - if(userinfo.size() < 2) + if (userinfo.size() < 2) return; username = userinfo[0]; password = userinfo[1]; } auto arguments = split(link, ':'); - if(arguments.size() < 2) + if (arguments.size() < 2) return; server = arguments[0]; port = arguments[1]; - } - else if(strFind(link, "https://t.me/socks") || strFind(link, "tg://socks")) //telegram style socks link + } else if (strFind(link, "https://t.me/socks") || strFind(link, "tg://socks")) //telegram style socks link { server = getUrlArg(link, "server"); port = getUrlArg(link, "port"); @@ -682,18 +732,17 @@ void explodeSocks(std::string link, Proxy &node) remarks = urlDecode(getUrlArg(link, "remarks")); group = urlDecode(getUrlArg(link, "group")); } - if(group.empty()) + if (group.empty()) group = SOCKS_DEFAULT_GROUP; - if(remarks.empty()) + if (remarks.empty()) remarks = server + ":" + port; - if(port == "0") + if (port == "0") return; socksConstruct(node, group, remarks, server, port, username, password); } -void explodeHTTP(const std::string &link, Proxy &node) -{ +void explodeHTTP(const std::string &link, Proxy &node) { std::string group, remarks, server, port, username, password; server = getUrlArg(link, "server"); port = getUrlArg(link, "port"); @@ -702,24 +751,22 @@ void explodeHTTP(const std::string &link, Proxy &node) remarks = urlDecode(getUrlArg(link, "remarks")); group = urlDecode(getUrlArg(link, "group")); - if(group.empty()) + if (group.empty()) group = HTTP_DEFAULT_GROUP; - if(remarks.empty()) + if (remarks.empty()) remarks = server + ":" + port; - if(port == "0") + if (port == "0") return; httpConstruct(node, group, remarks, server, port, username, password, strFind(link, "/https")); } -void explodeHTTPSub(std::string link, Proxy &node) -{ +void explodeHTTPSub(std::string link, Proxy &node) { std::string group, remarks, server, port, username, password; std::string addition; bool tls = strFind(link, "https://"); auto pos = link.find('?'); - if(pos != std::string::npos) - { + if (pos != std::string::npos) { addition = link.substr(pos + 1); link.erase(pos); remarks = urlDecode(getUrlArg(addition, "remarks")); @@ -727,150 +774,162 @@ void explodeHTTPSub(std::string link, Proxy &node) } link.erase(0, link.find("://") + 3); link = urlSafeBase64Decode(link); - if(strFind(link, "@")) - { - if(regGetMatch(link, "(.*?):(.*?)@(.*):(.*)", 5, 0, &username, &password, &server, &port)) + if (strFind(link, "@")) { + if (regGetMatch(link, "(.*?):(.*?)@(.*):(.*)", 5, 0, &username, &password, &server, &port)) return; - } - else - { - if(regGetMatch(link, "(.*):(.*)", 3, 0, &server, &port)) + } else { + if (regGetMatch(link, "(.*):(.*)", 3, 0, &server, &port)) return; } - if(group.empty()) + if (group.empty()) group = HTTP_DEFAULT_GROUP; - if(remarks.empty()) + if (remarks.empty()) remarks = server + ":" + port; - if(port == "0") + if (port == "0") return; httpConstruct(node, group, remarks, server, port, username, password, tls); } -void explodeTrojan(std::string trojan, Proxy &node) -{ +void explodeTrojan(std::string trojan, Proxy &node) { std::string server, port, psk, addition, group, remark, host, path, network; tribool tfo, scv; trojan.erase(0, 9); string_size pos = trojan.rfind('#'); - if(pos != std::string::npos) - { + if (pos != std::string::npos) { remark = urlDecode(trojan.substr(pos + 1)); trojan.erase(pos); } pos = trojan.find('?'); - if(pos != std::string::npos) - { + if (pos != std::string::npos) { addition = trojan.substr(pos + 1); trojan.erase(pos); } - if(regGetMatch(trojan, "(.*?)@(.*):(.*)", 4, 0, &psk, &server, &port)) + if (regGetMatch(trojan, "(.*?)@(.*):(.*)", 4, 0, &psk, &server, &port)) return; - if(port == "0") + if (port == "0") return; host = getUrlArg(addition, "sni"); - if(host.empty()) + if (host.empty()) host = getUrlArg(addition, "peer"); tfo = getUrlArg(addition, "tfo"); scv = getUrlArg(addition, "allowInsecure"); group = urlDecode(getUrlArg(addition, "group")); - if(getUrlArg(addition, "ws") == "1") - { + if (getUrlArg(addition, "ws") == "1") { path = getUrlArg(addition, "wspath"); network = "ws"; } - // support the trojan link format used by v2ryaN and X-ui. - // format: trojan://{password}@{server}:{port}?type=ws&security=tls&path={path (urlencoded)}&sni={host}#{name} - else if(getUrlArg(addition, "type") == "ws") - { + // support the trojan link format used by v2ryaN and X-ui. + // format: trojan://{password}@{server}:{port}?type=ws&security=tls&path={path (urlencoded)}&sni={host}#{name} + else if (getUrlArg(addition, "type") == "ws") { path = getUrlArg(addition, "path"); - if(path.substr(0, 3) == "%2F") + if (path.substr(0, 3) == "%2F") path = urlDecode(path); network = "ws"; } - if(remark.empty()) + if (remark.empty()) remark = server + ":" + port; - if(group.empty()) + if (group.empty()) group = TROJAN_DEFAULT_GROUP; trojanConstruct(node, group, remark, server, port, psk, network, host, path, true, tribool(), tfo, scv); } -void explodeQuan(const std::string &quan, Proxy &node) -{ +void explodeVless(std::string vless, Proxy &node) { + if (regMatch(vless, "vless://(.*?)@(.*)")) { + explodeStdVless(vless, node); + return; + } +} + +void explodeHysteria(std::string hysteria, Proxy &node) { + printf("explodeHysteria\n"); + if (regMatch(hysteria, "hysteria://(.*?)[:](.*)")) { + explodeStdHysteria(hysteria, node); + return; + } +} + +void explodeHysteria2(std::string hysteria2, Proxy &node) { + hysteria2 = regReplace(hysteria2, "(hysteria2|hy2)://", "hysteria2://"); + + // replace /? with ? + hysteria2 = regReplace(hysteria2, "/\\?", "?", true, false); + if (regMatch(hysteria2, "hysteria2://(.*?)[:](.*)")) { + explodeStdHysteria2(hysteria2, node); + return; + } +} + +void explodeQuan(const std::string &quan, Proxy &node) { std::string strTemp, itemName, itemVal; std::string group = V2RAY_DEFAULT_GROUP, ps, add, port, cipher, type = "none", id, aid = "0", net = "tcp", path, host, edge, tls; string_array configs, vArray, headers; strTemp = regReplace(quan, "(.*?) = (.*)", "$1,$2"); configs = split(strTemp, ","); - if(configs[1] == "vmess") - { - if(configs.size() < 6) + if (configs[1] == "vmess") { + if (configs.size() < 6) return; ps = trim(configs[0]); add = trim(configs[2]); port = trim(configs[3]); - if(port == "0") + if (port == "0") return; cipher = trim(configs[4]); id = trim(replaceAllDistinct(configs[5], "\"", "")); //read link - for(uint32_t i = 6; i < configs.size(); i++) - { + for (uint32_t i = 6; i < configs.size(); i++) { vArray = split(configs[i], "="); - if(vArray.size() < 2) + if (vArray.size() < 2) continue; itemName = trim(vArray[0]); itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "group"_hash: - group = itemVal; - break; - case "over-tls"_hash: - tls = itemVal == "true" ? "tls" : ""; - break; - case "tls-host"_hash: - host = itemVal; - break; - case "obfs-path"_hash: - path = replaceAllDistinct(itemVal, "\"", ""); - break; - case "obfs-header"_hash: - headers = split(replaceAllDistinct(replaceAllDistinct(itemVal, "\"", ""), "[Rr][Nn]", "|"), "|"); - for(std::string &x : headers) - { - if(regFind(x, "(?i)Host: ")) - host = x.substr(6); - else if(regFind(x, "(?i)Edge: ")) - edge = x.substr(6); - } - break; - case "obfs"_hash: - if(itemVal == "ws") - net = "ws"; - break; - default: - continue; + switch (hash_(itemName)) { + case "group"_hash: + group = itemVal; + break; + case "over-tls"_hash: + tls = itemVal == "true" ? "tls" : ""; + break; + case "tls-host"_hash: + host = itemVal; + break; + case "obfs-path"_hash: + path = replaceAllDistinct(itemVal, "\"", ""); + break; + case "obfs-header"_hash: + headers = split(replaceAllDistinct(replaceAllDistinct(itemVal, "\"", ""), "[Rr][Nn]", "|"), "|"); + for (std::string &x: headers) { + if (regFind(x, "(?i)Host: ")) + host = x.substr(6); + else if (regFind(x, "(?i)Edge: ")) + edge = x.substr(6); + } + break; + case "obfs"_hash: + if (itemVal == "ws") + net = "ws"; + break; + default: + continue; } } - if(path.empty()) + if (path.empty()) path = "/"; vmessConstruct(node, group, ps, add, port, type, id, aid, net, cipher, path, host, edge, tls, ""); } } -void explodeNetch(std::string netch, Proxy &node) -{ +void explodeNetch(std::string netch, Proxy &node) { Document json; std::string type, group, remark, address, port, username, password, method, plugin, pluginopts; std::string protocol, protoparam, obfs, obfsparam, id, aid, transprot, faketype, host, edge, path, tls, sni; @@ -878,7 +937,7 @@ void explodeNetch(std::string netch, Proxy &node) netch = urlSafeBase64Decode(netch.substr(8)); json.Parse(netch.data()); - if(json.HasParseError() || !json.IsObject()) + if (json.HasParseError() || !json.IsObject()) return; type = GetMember(json, "Type"); group = GetMember(json, "Group"); @@ -888,310 +947,382 @@ void explodeNetch(std::string netch, Proxy &node) tfo = GetMember(json, "EnableTFO"); scv = GetMember(json, "AllowInsecure"); port = GetMember(json, "Port"); - if(port == "0") + if (port == "0") return; method = GetMember(json, "EncryptMethod"); password = GetMember(json, "Password"); - if(remark.empty()) + if (remark.empty()) remark = address + ":" + port; - switch(hash_(type)) - { - case "SS"_hash: - plugin = GetMember(json, "Plugin"); - pluginopts = GetMember(json, "PluginOption"); - if(group.empty()) - group = SS_DEFAULT_GROUP; - ssConstruct(node, group, remark, address, port, password, method, plugin, pluginopts, udp, tfo, scv); - break; - case "SSR"_hash: - protocol = GetMember(json, "Protocol"); - obfs = GetMember(json, "OBFS"); - if(find(ss_ciphers.begin(), ss_ciphers.end(), method) != ss_ciphers.end() && (obfs.empty() || obfs == "plain") && (protocol.empty() || protocol == "origin")) - { + switch (hash_(type)) { + case "SS"_hash: plugin = GetMember(json, "Plugin"); pluginopts = GetMember(json, "PluginOption"); - if(group.empty()) + if (group.empty()) group = SS_DEFAULT_GROUP; ssConstruct(node, group, remark, address, port, password, method, plugin, pluginopts, udp, tfo, scv); - } - else - { - protoparam = GetMember(json, "ProtocolParam"); - obfsparam = GetMember(json, "OBFSParam"); - if(group.empty()) - group = SSR_DEFAULT_GROUP; - ssrConstruct(node, group, remark, address, port, protocol, method, obfs, password, obfsparam, protoparam, udp, tfo, scv); - } - break; - case "VMess"_hash: - id = GetMember(json, "UserID"); - aid = GetMember(json, "AlterID"); - transprot = GetMember(json, "TransferProtocol"); - faketype = GetMember(json, "FakeType"); - host = GetMember(json, "Host"); - path = GetMember(json, "Path"); - edge = GetMember(json, "Edge"); - tls = GetMember(json, "TLSSecure"); - sni = GetMember(json, "ServerName"); - if(group.empty()) - group = V2RAY_DEFAULT_GROUP; - vmessConstruct(node, group, remark, address, port, faketype, id, aid, transprot, method, path, host, edge, tls, sni, udp, tfo, scv); - break; - case "Socks5"_hash: - username = GetMember(json, "Username"); - if(group.empty()) - group = SOCKS_DEFAULT_GROUP; - socksConstruct(node, group, remark, address, port, username, password, udp, tfo, scv); - break; - case "HTTP"_hash: - case "HTTPS"_hash: - if(group.empty()) - group = HTTP_DEFAULT_GROUP; - httpConstruct(node, group, remark, address, port, username, password, type == "HTTPS", tfo, scv); - break; - case "Trojan"_hash: - host = GetMember(json, "Host"); - path = GetMember(json, "Path"); - transprot = GetMember(json, "TransferProtocol"); - tls = GetMember(json, "TLSSecure"); - if(group.empty()) - group = TROJAN_DEFAULT_GROUP; - trojanConstruct(node, group, remark, address, port, password, transprot, host, path, tls == "true", udp, tfo, scv); - break; - case "Snell"_hash: - obfs = GetMember(json, "OBFS"); - host = GetMember(json, "Host"); - aid = GetMember(json, "SnellVersion"); - if(group.empty()) - group = SNELL_DEFAULT_GROUP; - snellConstruct(node, group, remark, address, port, password, obfs, host, to_int(aid, 0), udp, tfo, scv); - break; - default: - return; + break; + case "SSR"_hash: + protocol = GetMember(json, "Protocol"); + obfs = GetMember(json, "OBFS"); + if (find(ss_ciphers.begin(), ss_ciphers.end(), method) != ss_ciphers.end() && + (obfs.empty() || obfs == "plain") && (protocol.empty() || protocol == "origin")) { + plugin = GetMember(json, "Plugin"); + pluginopts = GetMember(json, "PluginOption"); + if (group.empty()) + group = SS_DEFAULT_GROUP; + ssConstruct(node, group, remark, address, port, password, method, plugin, pluginopts, udp, tfo, scv); + } else { + protoparam = GetMember(json, "ProtocolParam"); + obfsparam = GetMember(json, "OBFSParam"); + if (group.empty()) + group = SSR_DEFAULT_GROUP; + ssrConstruct(node, group, remark, address, port, protocol, method, obfs, password, obfsparam, + protoparam, udp, tfo, scv); + } + break; + case "VMess"_hash: + id = GetMember(json, "UserID"); + aid = GetMember(json, "AlterID"); + transprot = GetMember(json, "TransferProtocol"); + faketype = GetMember(json, "FakeType"); + host = GetMember(json, "Host"); + path = GetMember(json, "Path"); + edge = GetMember(json, "Edge"); + tls = GetMember(json, "TLSSecure"); + sni = GetMember(json, "ServerName"); + if (group.empty()) + group = V2RAY_DEFAULT_GROUP; + vmessConstruct(node, group, remark, address, port, faketype, id, aid, transprot, method, path, host, edge, + tls, sni, udp, tfo, scv); + break; + case "Socks5"_hash: + username = GetMember(json, "Username"); + if (group.empty()) + group = SOCKS_DEFAULT_GROUP; + socksConstruct(node, group, remark, address, port, username, password, udp, tfo, scv); + break; + case "HTTP"_hash: + case "HTTPS"_hash: + if (group.empty()) + group = HTTP_DEFAULT_GROUP; + httpConstruct(node, group, remark, address, port, username, password, type == "HTTPS", tfo, scv); + break; + case "Trojan"_hash: + host = GetMember(json, "Host"); + path = GetMember(json, "Path"); + transprot = GetMember(json, "TransferProtocol"); + tls = GetMember(json, "TLSSecure"); + if (group.empty()) + group = TROJAN_DEFAULT_GROUP; + trojanConstruct(node, group, remark, address, port, password, transprot, host, path, tls == "true", udp, + tfo, scv); + break; + case "Snell"_hash: + obfs = GetMember(json, "OBFS"); + host = GetMember(json, "Host"); + aid = GetMember(json, "SnellVersion"); + if (group.empty()) + group = SNELL_DEFAULT_GROUP; + snellConstruct(node, group, remark, address, port, password, obfs, host, to_int(aid, 0), udp, tfo, scv); + break; + default: + return; } } -void explodeClash(Node yamlnode, std::vector &nodes) -{ +void explodeClash(Node yamlnode, std::vector &nodes) { std::string proxytype, ps, server, port, cipher, group, password; //common std::string type = "none", id, aid = "0", net = "tcp", path, host, edge, tls, sni; //vmess + std::string fp = "chrome", pbk, sid; //vless std::string plugin, pluginopts, pluginopts_mode, pluginopts_host, pluginopts_mux; //ss std::string protocol, protoparam, obfs, obfsparam; //ssr + std::string flow, mode; //trojan std::string user; //socks std::string ip, ipv6, private_key, public_key, mtu; //wireguard + std::string auth, up, down, obfsParam, insecure,alpn;//hysteria + std::string obfsPassword;//hysteria2 string_array dns_server; tribool udp, tfo, scv; Node singleproxy; uint32_t index = nodes.size(); const std::string section = yamlnode["proxies"].IsDefined() ? "proxies" : "Proxy"; - for(uint32_t i = 0; i < yamlnode[section].size(); i++) - { + for (uint32_t i = 0; i < yamlnode[section].size(); i++) { Proxy node; singleproxy = yamlnode[section][i]; singleproxy["type"] >>= proxytype; singleproxy["name"] >>= ps; singleproxy["server"] >>= server; singleproxy["port"] >>= port; - if(port.empty() || port == "0") + if (port.empty() || port == "0") continue; udp = safe_as(singleproxy["udp"]); scv = safe_as(singleproxy["skip-cert-verify"]); - switch(hash_(proxytype)) - { - case "vmess"_hash: - group = V2RAY_DEFAULT_GROUP; + switch (hash_(proxytype)) { + case "vmess"_hash: + group = V2RAY_DEFAULT_GROUP; - singleproxy["uuid"] >>= id; - singleproxy["alterId"] >>= aid; - singleproxy["cipher"] >>= cipher; - net = singleproxy["network"].IsDefined() ? safe_as(singleproxy["network"]) : "tcp"; - singleproxy["servername"] >>= sni; - switch(hash_(net)) - { - case "http"_hash: - singleproxy["http-opts"]["path"][0] >>= path; - singleproxy["http-opts"]["headers"]["Host"][0] >>= host; - edge.clear(); - break; - case "ws"_hash: - if(singleproxy["ws-opts"].IsDefined()) - { - path = singleproxy["ws-opts"]["path"].IsDefined() ? safe_as(singleproxy["ws-opts"]["path"]) : "/"; - singleproxy["ws-opts"]["headers"]["Host"] >>= host; - singleproxy["ws-opts"]["headers"]["Edge"] >>= edge; + singleproxy["uuid"] >>= id; + singleproxy["alterId"] >>= aid; + singleproxy["cipher"] >>= cipher; + net = singleproxy["network"].IsDefined() ? safe_as(singleproxy["network"]) : "tcp"; + singleproxy["servername"] >>= sni; + switch (hash_(net)) { + case "http"_hash: + singleproxy["http-opts"]["path"][0] >>= path; + singleproxy["http-opts"]["headers"]["Host"][0] >>= host; + edge.clear(); + break; + case "ws"_hash: + if (singleproxy["ws-opts"].IsDefined()) { + path = singleproxy["ws-opts"]["path"].IsDefined() ? safe_as( + singleproxy["ws-opts"]["path"]) : "/"; + singleproxy["ws-opts"]["headers"]["Host"] >>= host; + singleproxy["ws-opts"]["headers"]["Edge"] >>= edge; + } else { + path = singleproxy["ws-path"].IsDefined() ? safe_as(singleproxy["ws-path"]) + : "/"; + singleproxy["ws-headers"]["Host"] >>= host; + singleproxy["ws-headers"]["Edge"] >>= edge; + } + break; + case "h2"_hash: + singleproxy["h2-opts"]["path"] >>= path; + singleproxy["h2-opts"]["host"][0] >>= host; + edge.clear(); + break; + case "grpc"_hash: + singleproxy["servername"] >>= host; + singleproxy["grpc-opts"]["grpc-service-name"] >>= path; + edge.clear(); + break; } - else - { - path = singleproxy["ws-path"].IsDefined() ? safe_as(singleproxy["ws-path"]) : "/"; - singleproxy["ws-headers"]["Host"] >>= host; - singleproxy["ws-headers"]["Edge"] >>= edge; - } - break; - case "h2"_hash: - singleproxy["h2-opts"]["path"] >>= path; - singleproxy["h2-opts"]["host"][0] >>= host; - edge.clear(); - break; - case "grpc"_hash: - singleproxy["servername"] >>= host; - singleproxy["grpc-opts"]["grpc-service-name"] >>= path; - edge.clear(); - break; - } - tls = safe_as(singleproxy["tls"]) == "true" ? "tls" : ""; + tls = safe_as(singleproxy["tls"]) == "true" ? "tls" : ""; - vmessConstruct(node, group, ps, server, port, "", id, aid, net, cipher, path, host, edge, tls, sni, udp, tfo, scv); - break; - case "ss"_hash: - group = SS_DEFAULT_GROUP; + vmessConstruct(node, group, ps, server, port, "", id, aid, net, cipher, path, host, edge, tls, sni, udp, + tfo, scv); + break; + case "ss"_hash: + group = SS_DEFAULT_GROUP; - singleproxy["cipher"] >>= cipher; - singleproxy["password"] >>= password; - if(singleproxy["plugin"].IsDefined()) - { - switch(hash_(safe_as(singleproxy["plugin"]))) - { - case "obfs"_hash: + singleproxy["cipher"] >>= cipher; + singleproxy["password"] >>= password; + if (singleproxy["plugin"].IsDefined()) { + switch (hash_(safe_as(singleproxy["plugin"]))) { + case "obfs"_hash: + plugin = "obfs-local"; + if (singleproxy["plugin-opts"].IsDefined()) { + singleproxy["plugin-opts"]["mode"] >>= pluginopts_mode; + singleproxy["plugin-opts"]["host"] >>= pluginopts_host; + } + break; + case "v2ray-plugin"_hash: + plugin = "v2ray-plugin"; + if (singleproxy["plugin-opts"].IsDefined()) { + singleproxy["plugin-opts"]["mode"] >>= pluginopts_mode; + singleproxy["plugin-opts"]["host"] >>= pluginopts_host; + tls = safe_as(singleproxy["plugin-opts"]["tls"]) ? "tls;" : ""; + singleproxy["plugin-opts"]["path"] >>= path; + pluginopts_mux = safe_as(singleproxy["plugin-opts"]["mux"]) ? "mux=4;" : ""; + } + break; + default: + break; + } + } else if (singleproxy["obfs"].IsDefined()) { plugin = "obfs-local"; - if(singleproxy["plugin-opts"].IsDefined()) - { - singleproxy["plugin-opts"]["mode"] >>= pluginopts_mode; - singleproxy["plugin-opts"]["host"] >>= pluginopts_host; - } - break; - case "v2ray-plugin"_hash: - plugin = "v2ray-plugin"; - if(singleproxy["plugin-opts"].IsDefined()) - { - singleproxy["plugin-opts"]["mode"] >>= pluginopts_mode; - singleproxy["plugin-opts"]["host"] >>= pluginopts_host; - tls = safe_as(singleproxy["plugin-opts"]["tls"]) ? "tls;" : ""; - singleproxy["plugin-opts"]["path"] >>= path; - pluginopts_mux = safe_as(singleproxy["plugin-opts"]["mux"]) ? "mux=4;" : ""; - } - break; - default: - break; + singleproxy["obfs"] >>= pluginopts_mode; + singleproxy["obfs-host"] >>= pluginopts_host; + } else + plugin.clear(); + + switch (hash_(plugin)) { + case "simple-obfs"_hash: + case "obfs-local"_hash: + pluginopts = "obfs=" + pluginopts_mode; + pluginopts += pluginopts_host.empty() ? "" : ";obfs-host=" + pluginopts_host; + break; + case "v2ray-plugin"_hash: + pluginopts = "mode=" + pluginopts_mode + ";" + tls + pluginopts_mux; + if (!pluginopts_host.empty()) + pluginopts += "host=" + pluginopts_host + ";"; + if (!path.empty()) + pluginopts += "path=" + path + ";"; + if (!pluginopts_mux.empty()) + pluginopts += "mux=" + pluginopts_mux + ";"; + break; } - } - else if(singleproxy["obfs"].IsDefined()) - { - plugin = "obfs-local"; - singleproxy["obfs"] >>= pluginopts_mode; - singleproxy["obfs-host"] >>= pluginopts_host; - } - else - plugin.clear(); - switch(hash_(plugin)) - { - case "simple-obfs"_hash: - case "obfs-local"_hash: - pluginopts = "obfs=" + pluginopts_mode; - pluginopts += pluginopts_host.empty() ? "" : ";obfs-host=" + pluginopts_host; + //support for go-shadowsocks2 + if (cipher == "AEAD_CHACHA20_POLY1305") + cipher = "chacha20-ietf-poly1305"; + else if (strFind(cipher, "AEAD")) { + cipher = replaceAllDistinct(replaceAllDistinct(cipher, "AEAD_", ""), "_", "-"); + std::transform(cipher.begin(), cipher.end(), cipher.begin(), ::tolower); + } + + ssConstruct(node, group, ps, server, port, password, cipher, plugin, pluginopts, udp, tfo, scv); break; - case "v2ray-plugin"_hash: - pluginopts = "mode=" + pluginopts_mode + ";" + tls + pluginopts_mux; - if(!pluginopts_host.empty()) - pluginopts += "host=" + pluginopts_host + ";"; - if(!path.empty()) - pluginopts += "path=" + path + ";"; - if(!pluginopts_mux.empty()) - pluginopts += "mux=" + pluginopts_mux + ";"; + case "socks5"_hash: + group = SOCKS_DEFAULT_GROUP; + + singleproxy["username"] >>= user; + singleproxy["password"] >>= password; + + socksConstruct(node, group, ps, server, port, user, password); break; - } + case "ssr"_hash: + group = SSR_DEFAULT_GROUP; - //support for go-shadowsocks2 - if(cipher == "AEAD_CHACHA20_POLY1305") - cipher = "chacha20-ietf-poly1305"; - else if(strFind(cipher, "AEAD")) - { - cipher = replaceAllDistinct(replaceAllDistinct(cipher, "AEAD_", ""), "_", "-"); - std::transform(cipher.begin(), cipher.end(), cipher.begin(), ::tolower); - } + singleproxy["cipher"] >>= cipher; + if (cipher == "dummy") cipher = "none"; + singleproxy["password"] >>= password; + singleproxy["protocol"] >>= protocol; + singleproxy["obfs"] >>= obfs; + if (singleproxy["protocol-param"].IsDefined()) + singleproxy["protocol-param"] >>= protoparam; + else + singleproxy["protocolparam"] >>= protoparam; + if (singleproxy["obfs-param"].IsDefined()) + singleproxy["obfs-param"] >>= obfsparam; + else + singleproxy["obfsparam"] >>= obfsparam; - ssConstruct(node, group, ps, server, port, password, cipher, plugin, pluginopts, udp, tfo, scv); - break; - case "socks5"_hash: - group = SOCKS_DEFAULT_GROUP; - - singleproxy["username"] >>= user; - singleproxy["password"] >>= password; - - socksConstruct(node, group, ps, server, port, user, password); - break; - case "ssr"_hash: - group = SSR_DEFAULT_GROUP; - - singleproxy["cipher"] >>= cipher; - if(cipher == "dummy") cipher = "none"; - singleproxy["password"] >>= password; - singleproxy["protocol"] >>= protocol; - singleproxy["obfs"] >>= obfs; - if(singleproxy["protocol-param"].IsDefined()) - singleproxy["protocol-param"] >>= protoparam; - else - singleproxy["protocolparam"] >>= protoparam; - if(singleproxy["obfs-param"].IsDefined()) - singleproxy["obfs-param"] >>= obfsparam; - else - singleproxy["obfsparam"] >>= obfsparam; - - ssrConstruct(node, group, ps, server, port, protocol, cipher, obfs, password, obfsparam, protoparam, udp, tfo, scv); - break; - case "http"_hash: - group = HTTP_DEFAULT_GROUP; - - singleproxy["username"] >>= user; - singleproxy["password"] >>= password; - singleproxy["tls"] >>= tls; - - httpConstruct(node, group, ps, server, port, user, password, tls == "true", tfo, scv); - break; - case "trojan"_hash: - group = TROJAN_DEFAULT_GROUP; - singleproxy["password"] >>= password; - singleproxy["sni"] >>= host; - singleproxy["network"] >>= net; - switch(hash_(net)) - { - case "grpc"_hash: - singleproxy["grpc-opts"]["grpc-service-name"] >>= path; + ssrConstruct(node, group, ps, server, port, protocol, cipher, obfs, password, obfsparam, protoparam, + udp, tfo, scv); break; - case "ws"_hash: - singleproxy["ws-opts"]["path"] >>= path; + case "http"_hash: + group = HTTP_DEFAULT_GROUP; + + singleproxy["username"] >>= user; + singleproxy["password"] >>= password; + singleproxy["tls"] >>= tls; + + httpConstruct(node, group, ps, server, port, user, password, tls == "true", tfo, scv); + break; + case "trojan"_hash: + group = TROJAN_DEFAULT_GROUP; + singleproxy["password"] >>= password; + singleproxy["sni"] >>= host; + singleproxy["network"] >>= net; + switch (hash_(net)) { + case "grpc"_hash: + singleproxy["grpc-opts"]["grpc-service-name"] >>= path; + break; + case "ws"_hash: + singleproxy["ws-opts"]["path"] >>= path; + break; + default: + net = "tcp"; + path.clear(); + break; + } + + trojanConstruct(node, group, ps, server, port, password, net, host, path, true, udp, tfo, scv); + break; + case "snell"_hash: + group = SNELL_DEFAULT_GROUP; + singleproxy["psk"] >> password; + singleproxy["obfs-opts"]["mode"] >>= obfs; + singleproxy["obfs-opts"]["host"] >>= host; + singleproxy["version"] >>= aid; + + snellConstruct(node, group, ps, server, port, password, obfs, host, to_int(aid, 0), udp, tfo, scv); + break; + case "wireguard"_hash: + group = WG_DEFAULT_GROUP; + singleproxy["public-key"] >>= public_key; + singleproxy["private-key"] >>= private_key; + singleproxy["dns"] >>= dns_server; + singleproxy["mtu"] >>= mtu; + singleproxy["preshared-key"] >>= password; + singleproxy["ip"] >>= ip; + singleproxy["ipv6"] >>= ipv6; + + wireguardConstruct(node, group, ps, server, port, ip, ipv6, private_key, public_key, password, + dns_server, mtu, "0", "", "", udp); + break; + case "vless"_hash: + group = XRAY_DEFAULT_GROUP; + + singleproxy["uuid"] >>= id; + singleproxy["alterId"] >>= aid; + net = singleproxy["network"].IsDefined() ? safe_as(singleproxy["network"]) : "tcp"; + sni = singleproxy["sni"].IsDefined() ? safe_as(singleproxy["sni"]) : safe_as( + singleproxy["servername"]); + switch (hash_(net)) { + case "http"_hash: + singleproxy["http-opts"]["path"][0] >>= path; + singleproxy["http-opts"]["headers"]["Host"][0] >>= host; + edge.clear(); + break; + case "ws"_hash: + if (singleproxy["ws-opts"].IsDefined()) { + path = singleproxy["ws-opts"]["path"].IsDefined() ? safe_as( + singleproxy["ws-opts"]["path"]) : "/"; + singleproxy["ws-opts"]["headers"]["Host"] >>= host; + singleproxy["ws-opts"]["headers"]["Edge"] >>= edge; + } else { + path = singleproxy["ws-path"].IsDefined() ? safe_as(singleproxy["ws-path"]) + : "/"; + singleproxy["ws-headers"]["Host"] >>= host; + singleproxy["ws-headers"]["Edge"] >>= edge; + } + break; + case "h2"_hash: + singleproxy["h2-opts"]["path"] >>= path; + singleproxy["h2-opts"]["host"][0] >>= host; + edge.clear(); + break; + case "grpc"_hash: + singleproxy["servername"] >>= host; + singleproxy["grpc-opts"]["grpc-service-name"] >>= path; + edge.clear(); + break; + } + + tls = safe_as(singleproxy["tls"]) == "true" ? "tls" : ""; + if (singleproxy["reality-opts"].IsDefined()) { + host = singleproxy["sni"].IsDefined() ? safe_as(singleproxy["sni"]) + : safe_as(singleproxy["servername"]); + printf("host:%s", host.c_str()); + singleproxy["reality-opts"]["public-key"] >>= pbk; + singleproxy["reality-opts"]["short-id"] >>= sid; + } + singleproxy["flow"] >>= flow; + + vlessConstruct(node, XRAY_DEFAULT_GROUP, ps, server, port, type, id, aid, net, "auto", flow, mode, path, + host, "", tls, pbk, sid, fp); + break; + case "hysteria"_hash: + group = HYSTERIA_DEFAULT_GROUP; + singleproxy["auth_str"] >> auth; + singleproxy["up"] >> up; + singleproxy["down"] >> down; + singleproxy["obfs"] >> obfsParam; + singleproxy["protocol"] >> type; + singleproxy["sni"] >> host; + singleproxy["alpn"][0] >> alpn; + singleproxy["protocol"] >> insecure; + + hysteriaConstruct(node, group, ps, server, port, type, auth, host, up, down, alpn, obfsParam, insecure, + udp, tfo, scv); + break; + case "hysteria2"_hash: + group = HYSTERIA2_DEFAULT_GROUP; + singleproxy["password"] >>= password; + singleproxy["auth"] >>= password; + singleproxy["up"] >>= up; + singleproxy["down"] >>= down; + singleproxy["obfs"] >>= obfsParam; + singleproxy["obfs-password"] >>= obfsPassword; + singleproxy["sni"] >>= host; + singleproxy["alpn"][0] >>= alpn; + + hysteria2Construct(node, group, ps, server, port, password, host, up, down, alpn, obfsParam, + obfsPassword, udp, tfo, scv); break; default: - net = "tcp"; - path.clear(); - break; - } - - trojanConstruct(node, group, ps, server, port, password, net, host, path, true, udp, tfo, scv); - break; - case "snell"_hash: - group = SNELL_DEFAULT_GROUP; - singleproxy["psk"] >> password; - singleproxy["obfs-opts"]["mode"] >>= obfs; - singleproxy["obfs-opts"]["host"] >>= host; - singleproxy["version"] >>= aid; - - snellConstruct(node, group, ps, server, port, password, obfs, host, to_int(aid, 0), udp, tfo, scv); - break; - case "wireguard"_hash: - group = WG_DEFAULT_GROUP; - singleproxy["public-key"] >>= public_key; - singleproxy["private-key"] >>= private_key; - singleproxy["dns"] >>= dns_server; - singleproxy["mtu"] >>= mtu; - singleproxy["preshared-key"] >>= password; - singleproxy["ip"] >>= ip; - singleproxy["ipv6"] >>= ipv6; - - wireguardConstruct(node, group, ps, server, port, ip, ipv6, private_key, public_key, password, dns_server, mtu, "0", "", "", udp); - break; - default: - continue; + continue; } node.Id = index; @@ -1200,51 +1331,182 @@ void explodeClash(Node yamlnode, std::vector &nodes) } } -void explodeStdVMess(std::string vmess, Proxy &node) -{ +void explodeStdVMess(std::string vmess, Proxy &node) { std::string add, port, type, id, aid, net, path, host, tls, remarks; std::string addition; vmess = vmess.substr(8); string_size pos; pos = vmess.rfind('#'); - if(pos != std::string::npos) - { + if (pos != std::string::npos) { remarks = urlDecode(vmess.substr(pos + 1)); vmess.erase(pos); } const std::string stdvmess_matcher = R"(^([a-z]+)(?:\+([a-z]+))?:([\da-f]{4}(?:[\da-f]{4}-){4}[\da-f]{12})-(\d+)@(.+):(\d+)(?:\/?\?(.*))?$)"; - if(regGetMatch(vmess, stdvmess_matcher, 8, 0, &net, &tls, &id, &aid, &add, &port, &addition)) + if (regGetMatch(vmess, stdvmess_matcher, 8, 0, &net, &tls, &id, &aid, &add, &port, &addition)) return; - switch(hash_(net)) - { - case "tcp"_hash: - case "kcp"_hash: - type = getUrlArg(addition, "type"); - break; - case "http"_hash: - case "ws"_hash: - host = getUrlArg(addition, "host"); - path = getUrlArg(addition, "path"); - break; - case "quic"_hash: - type = getUrlArg(addition, "security"); - host = getUrlArg(addition, "type"); - path = getUrlArg(addition, "key"); - break; - default: - return; + switch (hash_(net)) { + case "tcp"_hash: + case "kcp"_hash: + type = getUrlArg(addition, "type"); + break; + case "http"_hash: + case "ws"_hash: + host = getUrlArg(addition, "host"); + path = getUrlArg(addition, "path"); + break; + case "quic"_hash: + type = getUrlArg(addition, "security"); + host = getUrlArg(addition, "type"); + path = getUrlArg(addition, "key"); + break; + default: + return; } - if(remarks.empty()) + if (remarks.empty()) remarks = add + ":" + port; vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, add, port, type, id, aid, net, "auto", path, host, "", tls, ""); } -void explodeShadowrocket(std::string rocket, Proxy &node) -{ + +void explodeStdHysteria(std::string hysteria, Proxy &node) { + std::string add, port, type, auth, host, insecure, up, down, alpn, obfsParam, remarks; + std::string addition; + hysteria = hysteria.substr(11); + string_size pos; + + pos = hysteria.rfind("#"); + if (pos != hysteria.npos) { + remarks = urlDecode(hysteria.substr(pos + 1)); + hysteria.erase(pos); + } + const std::string stdhysteria_matcher = R"(^(.*)[:](\d+)[?](.*)$)"; + if (regGetMatch(hysteria, stdhysteria_matcher, 4, 0, &add, &port, &addition)) + return; + type = getUrlArg(addition, "protocol"); + auth = getUrlArg(addition, "auth"); + host = getUrlArg(addition, "peer"); + insecure = getUrlArg(addition, "insecure"); + up = getUrlArg(addition, "upmbps"); + down = getUrlArg(addition, "downmbps"); + alpn = getUrlArg(addition, "alpn"); + obfsParam = getUrlArg(addition, "obfsParam"); + + if (remarks.empty()) + remarks = add + ":" + port; + + hysteriaConstruct(node, HYSTERIA_DEFAULT_GROUP, remarks, add, port, type, auth, host, up, down, alpn, obfsParam, + insecure); + return; +} + +void explodeStdHysteria2(std::string hysteria2, Proxy &node) { + std::string add, port, password, host, insecure, up, down, alpn, obfsParam, obfsPassword, remarks; + std::string addition; + tribool scv; + hysteria2 = hysteria2.substr(12); + string_size pos; + + pos = hysteria2.rfind("#"); + if (pos != hysteria2.npos) { + remarks = urlDecode(hysteria2.substr(pos + 1)); + hysteria2.erase(pos); + } + + pos = hysteria2.rfind("?"); + if (pos != hysteria2.npos) { + addition = hysteria2.substr(pos + 1); + hysteria2.erase(pos); + } + + if (strFind(hysteria2, "@")) { + if (regGetMatch(hysteria2, R"(^(.*?)@(.*)[:](\d+)$)", 4, 0, &password, &add, &port)) + return; + } else { + password = getUrlArg(addition, "password"); + if (password.empty()) + return; + + if (!strFind(hysteria2, ":")) + return; + + if (regGetMatch(hysteria2, R"(^(.*)[:](\d+)$)", 3, 0, &add, &port)) + return; + } + + scv = getUrlArg(addition, "insecure"); + up = getUrlArg(addition, "up"); + down = getUrlArg(addition, "down"); + alpn = getUrlArg(addition, "alpn"); + obfsParam = getUrlArg(addition, "obfs"); + obfsPassword = getUrlArg(addition, "obfs-password"); + host = getUrlArg(addition, "sni"); + + if (remarks.empty()) + remarks = add + ":" + port; + + hysteria2Construct(node, HYSTERIA2_DEFAULT_GROUP, remarks, add, port, password, host, up, down, alpn, obfsParam, + obfsPassword, tribool(), tribool(), scv); + return; +} + + +void explodeStdVless(std::string vless, Proxy &node) { + std::string add, port, type, id, aid, net, flow, pbk, sid, fp, mode, path, host, tls, remarks; + std::string addition; + vless = vless.substr(8); + string_size pos; + + pos = vless.rfind("#"); + if (pos != vless.npos) { + remarks = urlDecode(vless.substr(pos + 1)); + vless.erase(pos); + } + const std::string stdvless_matcher = R"(^([\da-f]{4}(?:[\da-f]{4}-){4}[\da-f]{12})@\[?([\d\-a-zA-Z:.]+)\]?:(\d+)(?:\/?\?(.*))?$)"; + if (regGetMatch(vless, stdvless_matcher, 5, 0, &id, &add, &port, &addition)) + return; + + tls = getUrlArg(addition, "security"); + net = getUrlArg(addition, "type"); + flow = getUrlArg(addition, "flow"); + pbk = getUrlArg(addition, "pbk"); + sid = getUrlArg(addition, "sid"); + fp = getUrlArg(addition, "fp"); + + switch (hash_(net)) { + case "tcp"_hash: + case "ws"_hash: + case "h2"_hash: + type = getUrlArg(addition, "headerType"); + host = getUrlArg(addition, strFind(addition, "sni") ? "sni" : "host"); + path = getUrlArg(addition, "path"); + break; + case "grpc"_hash: + host = getUrlArg(addition, "sni"); + path = getUrlArg(addition, "serviceName"); + mode = getUrlArg(addition, "mode"); + break; + case "quic"_hash: + type = getUrlArg(addition, "headerType"); + host = getUrlArg(addition, strFind(addition, "sni") ? "sni" : "quicSecurity"); + path = getUrlArg(addition, "key"); + break; + default: + return; + } + + if (remarks.empty()) + remarks = add + ":" + port; + + vlessConstruct(node, XRAY_DEFAULT_GROUP, remarks, add, port, type, id, aid, net, "auto", flow, mode, path, host, "", + tls, pbk, sid, fp); + return; +} + +void explodeShadowrocket(std::string rocket, Proxy &node) { std::string add, port, type, id, aid, net = "tcp", path, host, tls, cipher, remarks; std::string obfs; //for other style of link std::string addition; @@ -1254,23 +1516,19 @@ void explodeShadowrocket(std::string rocket, Proxy &node) addition = rocket.substr(pos + 1); rocket.erase(pos); - if(regGetMatch(urlSafeBase64Decode(rocket), "(.*?):(.*)@(.*):(.*)", 5, 0, &cipher, &id, &add, &port)) + if (regGetMatch(urlSafeBase64Decode(rocket), "(.*?):(.*)@(.*):(.*)", 5, 0, &cipher, &id, &add, &port)) return; - if(port == "0") + if (port == "0") return; remarks = urlDecode(getUrlArg(addition, "remarks")); obfs = getUrlArg(addition, "obfs"); - if(!obfs.empty()) - { - if(obfs == "websocket") - { + if (!obfs.empty()) { + if (obfs == "websocket") { net = "ws"; host = getUrlArg(addition, "obfsParam"); path = getUrlArg(addition, "path"); } - } - else - { + } else { net = getUrlArg(addition, "network"); host = getUrlArg(addition, "wsHost"); path = getUrlArg(addition, "wspath"); @@ -1278,25 +1536,23 @@ void explodeShadowrocket(std::string rocket, Proxy &node) tls = getUrlArg(addition, "tls") == "1" ? "tls" : ""; aid = getUrlArg(addition, "aid"); - if(aid.empty()) + if (aid.empty()) aid = "0"; - if(remarks.empty()) + if (remarks.empty()) remarks = add + ":" + port; vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, add, port, type, id, aid, net, cipher, path, host, "", tls, ""); } -void explodeKitsunebi(std::string kit, Proxy &node) -{ +void explodeKitsunebi(std::string kit, Proxy &node) { std::string add, port, type, id, aid = "0", net = "tcp", path, host, tls, cipher = "auto", remarks; std::string addition; string_size pos; kit = kit.substr(9); pos = kit.find('#'); - if(pos != std::string::npos) - { + if (pos != std::string::npos) { remarks = kit.substr(pos + 1); kit = kit.substr(0, pos); } @@ -1305,63 +1561,58 @@ void explodeKitsunebi(std::string kit, Proxy &node) addition = kit.substr(pos + 1); kit = kit.substr(0, pos); - if(regGetMatch(kit, "(.*?)@(.*):(.*)", 4, 0, &id, &add, &port)) + if (regGetMatch(kit, "(.*?)@(.*):(.*)", 4, 0, &id, &add, &port)) return; pos = port.find('/'); - if(pos != std::string::npos) - { + if (pos != std::string::npos) { path = port.substr(pos); port.erase(pos); } - if(port == "0") + if (port == "0") return; net = getUrlArg(addition, "network"); tls = getUrlArg(addition, "tls") == "true" ? "tls" : ""; host = getUrlArg(addition, "ws.host"); - if(remarks.empty()) + if (remarks.empty()) remarks = add + ":" + port; vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, add, port, type, id, aid, net, cipher, path, host, "", tls, ""); } // peer = (public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, allowed-ips = "0.0.0.0/0, ::/0", endpoint = engage.cloudflareclient.com:2408, client-id = 139/184/125),(public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, endpoint = engage.cloudflareclient.com:2408) -void parsePeers(Proxy &node, const std::string &data) -{ +void parsePeers(Proxy &node, const std::string &data) { auto peers = regGetAllMatch(data, R"(\((.*?)\))", true); - if(peers.empty()) + if (peers.empty()) return; auto peer = peers[0]; auto peerdata = regGetAllMatch(peer, R"(([a-z-]+) ?= ?([^" ),]+|".*?"),? ?)", true); - if(peerdata.size() % 2 != 0) + if (peerdata.size() % 2 != 0) return; - for(size_t i = 0; i < peerdata.size(); i += 2) - { + for (size_t i = 0; i < peerdata.size(); i += 2) { auto key = peerdata[i]; auto val = peerdata[i + 1]; - switch(hash_(key)) - { - case "public-key"_hash: - node.PublicKey = val; - break; - case "endpoint"_hash: - node.Hostname = val.substr(0, val.rfind(':')); - node.Port = to_int(val.substr(val.rfind(':') + 1)); - break; - case "client-id"_hash: - node.ClientId = val; - break; - case "allowed-ips"_hash: - node.AllowedIPs = trimOf(val, '"'); - break; - default: - break; + switch (hash_(key)) { + case "public-key"_hash: + node.PublicKey = val; + break; + case "endpoint"_hash: + node.Hostname = val.substr(0, val.rfind(':')); + node.Port = to_int(val.substr(val.rfind(':') + 1)); + break; + case "client-id"_hash: + node.ClientId = val; + break; + case "allowed-ips"_hash: + node.AllowedIPs = trimOf(val, '"'); + break; + default: + break; } } } -bool explodeSurge(std::string surge, std::vector &nodes) -{ +bool explodeSurge(std::string surge, std::vector &nodes) { std::multimap proxies; uint32_t i, index = nodes.size(); INIReader ini; @@ -1376,19 +1627,18 @@ bool explodeSurge(std::string surge, std::vector &nodes) ini.allow_dup_section_titles = true; ini.set_isolated_items_section("Proxy"); ini.add_direct_save_section("Proxy"); - if(surge.find("[Proxy]") != surge.npos) + if (surge.find("[Proxy]") != surge.npos) surge = regReplace(surge, R"(^[\S\s]*?\[)", "[", false); ini.parse(surge); - if(!ini.section_exist("Proxy")) + if (!ini.section_exist("Proxy")) return false; ini.enter_section("Proxy"); ini.get_items(proxies); const std::string proxystr = "(.*?)\\s*=\\s*(.*)"; - for(auto &x : proxies) - { + for (auto &x: proxies) { std::string remarks, server, port, method, username, password; //common std::string plugin, pluginopts, pluginopts_mode, pluginopts_host, mod_url, mod_md5; //ss std::string id, net, tls, host, edge, path; //v2 @@ -1408,665 +1658,637 @@ bool explodeSurge(std::string surge, std::vector &nodes) */ regGetMatch(x.second, proxystr, 3, 0, &remarks, &config); configs = split(config, ","); - if(configs.size() < 3) + if (configs.size() < 3) continue; - switch(hash_(configs[0])) - { - case "direct"_hash: - case "reject"_hash: - case "reject-tinygif"_hash: - continue; - case "custom"_hash: //surge 2 style custom proxy - //remove module detection to speed up parsing and compatible with broken module - /* - mod_url = trim(configs[5]); - if(parsedMD5.count(mod_url) > 0) - { - mod_md5 = parsedMD5[mod_url]; //read calculated MD5 from map - } - else - { - mod_md5 = getMD5(webGet(mod_url)); //retrieve module and calculate MD5 - parsedMD5.insert(std::pair(mod_url, mod_md5)); //save unrecognized module MD5 to map - } - */ - - //if(mod_md5 == modSSMD5) //is SSEncrypt module - { - if(configs.size() < 5) + switch (hash_(configs[0])) { + case "direct"_hash: + case "reject"_hash: + case "reject-tinygif"_hash: continue; - server = trim(configs[1]); - port = trim(configs[2]); - if(port == "0") - continue; - method = trim(configs[3]); - password = trim(configs[4]); - - for(i = 6; i < configs.size(); i++) - { - vArray = split(configs[i], "="); - if(vArray.size() < 2) - continue; - itemName = trim(vArray[0]); - itemVal = trim(vArray[1]); - switch(hash_(itemName)) + case "custom"_hash: //surge 2 style custom proxy + //remove module detection to speed up parsing and compatible with broken module + /* + mod_url = trim(configs[5]); + if(parsedMD5.count(mod_url) > 0) { - case "obfs"_hash: - plugin = "simple-obfs"; - pluginopts_mode = itemVal; - break; - case "obfs-host"_hash: - pluginopts_host = itemVal; - break; - case "udp-relay"_hash: - udp = itemVal; - break; - case "tfo"_hash: - tfo = itemVal; - break; - default: - continue; - } - } - if(!plugin.empty()) - { - pluginopts = "obfs=" + pluginopts_mode; - pluginopts += pluginopts_host.empty() ? "" : ";obfs-host=" + pluginopts_host; - } - - ssConstruct(node, SS_DEFAULT_GROUP, remarks, server, port, password, method, plugin, pluginopts, udp, tfo, scv); - } - //else - // continue; - break; - case "ss"_hash: //surge 3 style ss proxy - server = trim(configs[1]); - port = trim(configs[2]); - if(port == "0") - continue; - - for(i = 3; i < configs.size(); i++) - { - vArray = split(configs[i], "="); - if(vArray.size() < 2) - continue; - itemName = trim(vArray[0]); - itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "encrypt-method"_hash: - method = itemVal; - break; - case "password"_hash: - password = itemVal; - break; - case "obfs"_hash: - plugin = "simple-obfs"; - pluginopts_mode = itemVal; - break; - case "obfs-host"_hash: - pluginopts_host = itemVal; - break; - case "udp-relay"_hash: - udp = itemVal; - break; - case "tfo"_hash: - tfo = itemVal; - break; - default: - continue; - } - } - if(!plugin.empty()) - { - pluginopts = "obfs=" + pluginopts_mode; - pluginopts += pluginopts_host.empty() ? "" : ";obfs-host=" + pluginopts_host; - } - - ssConstruct(node, SS_DEFAULT_GROUP, remarks, server, port, password, method, plugin, pluginopts, udp, tfo, scv); - break; - case "socks5"_hash: //surge 3 style socks5 proxy - server = trim(configs[1]); - port = trim(configs[2]); - if(port == "0") - continue; - if(configs.size() >= 5) - { - username = trim(configs[3]); - password = trim(configs[4]); - } - for(i = 5; i < configs.size(); i++) - { - vArray = split(configs[i], "="); - if(vArray.size() < 2) - continue; - itemName = trim(vArray[0]); - itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "udp-relay"_hash: - udp = itemVal; - break; - case "tfo"_hash: - tfo = itemVal; - break; - case "skip-cert-verify"_hash: - scv = itemVal; - break; - default: - continue; - } - } - socksConstruct(node, SOCKS_DEFAULT_GROUP, remarks, server, port, username, password, udp, tfo, scv); - break; - case "vmess"_hash: //surge 4 style vmess proxy - server = trim(configs[1]); - port = trim(configs[2]); - if(port == "0") - continue; - net = "tcp"; - method = "auto"; - - for(i = 3; i < configs.size(); i++) - { - vArray = split(configs[i], "="); - if(vArray.size() != 2) - continue; - itemName = trim(vArray[0]); - itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "username"_hash: - id = itemVal; - break; - case "ws"_hash: - net = itemVal == "true" ? "ws" : "tcp"; - break; - case "tls"_hash: - tls = itemVal == "true" ? "tls" : ""; - break; - case "ws-path"_hash: - path = itemVal; - break; - case "obfs-host"_hash: - host = itemVal; - break; - case "ws-headers"_hash: - headers = split(itemVal, "|"); - for(auto &y : headers) - { - header = split(trim(y), ":"); - if(header.size() != 2) - continue; - else if(regMatch(header[0], "(?i)host")) - host = trimQuote(header[1]); - else if(regMatch(header[0], "(?i)edge")) - edge = trimQuote(header[1]); - } - break; - case "udp-relay"_hash: - udp = itemVal; - break; - case "tfo"_hash: - tfo = itemVal; - break; - case "skip-cert-verify"_hash: - scv = itemVal; - break; - case "tls13"_hash: - tls13 = itemVal; - break; - case "vmess-aead"_hash: - aead = itemVal == "true" ? "0" : "1"; - default: - continue; - } - } - - vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, server, port, "", id, aead, net, method, path, host, edge, tls, "", udp, tfo, scv, tls13); - break; - case "http"_hash: //http proxy - server = trim(configs[1]); - port = trim(configs[2]); - if(port == "0") - continue; - for(i = 3; i < configs.size(); i++) - { - vArray = split(configs[i], "="); - if(vArray.size() < 2) - continue; - itemName = trim(vArray[0]); - itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "username"_hash: - username = itemVal; - break; - case "password"_hash: - password = itemVal; - break; - case "skip-cert-verify"_hash: - scv = itemVal; - break; - default: - continue; - } - } - httpConstruct(node, HTTP_DEFAULT_GROUP, remarks, server, port, username, password, false, tfo, scv); - break; - case "trojan"_hash: // surge 4 style trojan proxy - server = trim(configs[1]); - port = trim(configs[2]); - if(port == "0") - continue; - - for(i = 3; i < configs.size(); i++) - { - vArray = split(configs[i], "="); - if(vArray.size() != 2) - continue; - itemName = trim(vArray[0]); - itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "password"_hash: - password = itemVal; - break; - case "sni"_hash: - host = itemVal; - break; - case "udp-relay"_hash: - udp = itemVal; - break; - case "tfo"_hash: - tfo = itemVal; - break; - case "skip-cert-verify"_hash: - scv = itemVal; - break; - default: - continue; - } - } - - trojanConstruct(node, TROJAN_DEFAULT_GROUP, remarks, server, port, password, "", host, "", true, udp, tfo, scv); - break; - case "snell"_hash: - server = trim(configs[1]); - port = trim(configs[2]); - if(port == "0") - continue; - - for(i = 3; i < configs.size(); i++) - { - vArray = split(configs[i], "="); - if(vArray.size() != 2) - continue; - itemName = trim(vArray[0]); - itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "psk"_hash: - password = itemVal; - break; - case "obfs"_hash: - plugin = itemVal; - break; - case "obfs-host"_hash: - host = itemVal; - break; - case "udp-relay"_hash: - udp = itemVal; - break; - case "tfo"_hash: - tfo = itemVal; - break; - case "skip-cert-verify"_hash: - scv = itemVal; - break; - case "version"_hash: - version = itemVal; - break; - default: - continue; - } - } - - snellConstruct(node, SNELL_DEFAULT_GROUP, remarks, server, port, password, plugin, host, to_int(version, 0), udp, tfo, scv); - break; - case "wireguard"_hash: - for (i = 1; i < configs.size(); i++) - { - vArray = split(trim(configs[i]), "="); - if(vArray.size() != 2) - continue; - itemName = trim(vArray[0]); - itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "section-name"_hash: - section = itemVal; - break; - case "test-url"_hash: - test_url = itemVal; - break; - } - } - if(section.empty()) - continue; - ini.get_items("WireGuard " + section, wireguard_config); - if(wireguard_config.empty()) - continue; - - for (auto &c : wireguard_config) - { - itemName = trim(c.first); - itemVal = trim(c.second); - switch(hash_(itemName)) - { - case "self-ip"_hash: - ip = itemVal; - break; - case "self-ip-v6"_hash: - ipv6 = itemVal; - break; - case "private-key"_hash: - private_key = itemVal; - break; - case "dns-server"_hash: - vArray = split(itemVal, ","); - for (auto &y : vArray) - dns_servers.emplace_back(trim(y)); - break; - case "mtu"_hash: - mtu = itemVal; - break; - case "peer"_hash: - peer = itemVal; - break; - case "keepalive"_hash: - keepalive = itemVal; - break; - } - } - - wireguardConstruct(node, WG_DEFAULT_GROUP, remarks, "", "0", ip, ipv6, private_key, "", "", dns_servers, mtu, keepalive, test_url, "", udp); - parsePeers(node, peer); - break; - default: - switch(hash_(remarks)) - { - case "shadowsocks"_hash: //quantumult x style ss/ssr link - server = trim(configs[0].substr(0, configs[0].rfind(":"))); - port = trim(configs[0].substr(configs[0].rfind(":") + 1)); - if(port == "0") - continue; - - for(i = 1; i < configs.size(); i++) - { - vArray = split(trim(configs[i]), "="); - if(vArray.size() != 2) - continue; - itemName = trim(vArray[0]); - itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "method"_hash: - method = itemVal; - break; - case "password"_hash: - password = itemVal; - break; - case "tag"_hash: - remarks = itemVal; - break; - case "ssr-protocol"_hash: - protocol = itemVal; - break; - case "ssr-protocol-param"_hash: - protoparam = itemVal; - break; - case "obfs"_hash: - { - switch(hash_(itemVal)) - { - case "http"_hash: - case "tls"_hash: - plugin = "simple-obfs"; - pluginopts_mode = itemVal; - break; - case "wss"_hash: - tls = "tls"; - [[fallthrough]]; - case "ws"_hash: - pluginopts_mode = "websocket"; - plugin = "v2ray-plugin"; - break; - default: - pluginopts_mode = itemVal; - } - break; - } - case "obfs-host"_hash: - pluginopts_host = itemVal; - break; - case "obfs-uri"_hash: - path = itemVal; - break; - case "udp-relay"_hash: - udp = itemVal; - break; - case "fast-open"_hash: - tfo = itemVal; - break; - case "tls13"_hash: - tls13 = itemVal; - break; - default: - continue; - } - } - if(remarks.empty()) - remarks = server + ":" + port; - switch(hash_(plugin)) - { - case "simple-obfs"_hash: - pluginopts = "obfs=" + pluginopts_mode; - if(!pluginopts_host.empty()) - pluginopts += ";obfs-host=" + pluginopts_host; - break; - case "v2ray-plugin"_hash: - if(pluginopts_host.empty() && !isIPv4(server) && !isIPv6(server)) - pluginopts_host = server; - pluginopts = "mode=" + pluginopts_mode; - if(!pluginopts_host.empty()) - pluginopts += ";host=" + pluginopts_host; - if(!path.empty()) - pluginopts += ";path=" + path; - pluginopts += ";" + tls; - break; - } - - if(!protocol.empty()) - { - ssrConstruct(node, SSR_DEFAULT_GROUP, remarks, server, port, protocol, method, pluginopts_mode, password, pluginopts_host, protoparam, udp, tfo, scv); + mod_md5 = parsedMD5[mod_url]; //read calculated MD5 from map } else { - ssConstruct(node, SS_DEFAULT_GROUP, remarks, server, port, password, method, plugin, pluginopts, udp, tfo, scv, tls13); + mod_md5 = getMD5(webGet(mod_url)); //retrieve module and calculate MD5 + parsedMD5.insert(std::pair(mod_url, mod_md5)); //save unrecognized module MD5 to map } + */ + + //if(mod_md5 == modSSMD5) //is SSEncrypt module + { + if (configs.size() < 5) + continue; + server = trim(configs[1]); + port = trim(configs[2]); + if (port == "0") + continue; + method = trim(configs[3]); + password = trim(configs[4]); + + for (i = 6; i < configs.size(); i++) { + vArray = split(configs[i], "="); + if (vArray.size() < 2) + continue; + itemName = trim(vArray[0]); + itemVal = trim(vArray[1]); + switch (hash_(itemName)) { + case "obfs"_hash: + plugin = "simple-obfs"; + pluginopts_mode = itemVal; + break; + case "obfs-host"_hash: + pluginopts_host = itemVal; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "tfo"_hash: + tfo = itemVal; + break; + default: + continue; + } + } + if (!plugin.empty()) { + pluginopts = "obfs=" + pluginopts_mode; + pluginopts += pluginopts_host.empty() ? "" : ";obfs-host=" + pluginopts_host; + } + + ssConstruct(node, SS_DEFAULT_GROUP, remarks, server, port, password, method, plugin, pluginopts, udp, + tfo, scv); + } + //else + // continue; break; - case "vmess"_hash: //quantumult x style vmess link - server = trim(configs[0].substr(0, configs[0].rfind(":"))); - port = trim(configs[0].substr(configs[0].rfind(":") + 1)); - if(port == "0") + case "ss"_hash: //surge 3 style ss proxy + server = trim(configs[1]); + port = trim(configs[2]); + if (port == "0") + continue; + + for (i = 3; i < configs.size(); i++) { + vArray = split(configs[i], "="); + if (vArray.size() < 2) + continue; + itemName = trim(vArray[0]); + itemVal = trim(vArray[1]); + switch (hash_(itemName)) { + case "encrypt-method"_hash: + method = itemVal; + break; + case "password"_hash: + password = itemVal; + break; + case "obfs"_hash: + plugin = "simple-obfs"; + pluginopts_mode = itemVal; + break; + case "obfs-host"_hash: + pluginopts_host = itemVal; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "tfo"_hash: + tfo = itemVal; + break; + default: + continue; + } + } + if (!plugin.empty()) { + pluginopts = "obfs=" + pluginopts_mode; + pluginopts += pluginopts_host.empty() ? "" : ";obfs-host=" + pluginopts_host; + } + + ssConstruct(node, SS_DEFAULT_GROUP, remarks, server, port, password, method, plugin, pluginopts, udp, + tfo, scv); + break; + case "socks5"_hash: //surge 3 style socks5 proxy + server = trim(configs[1]); + port = trim(configs[2]); + if (port == "0") + continue; + if (configs.size() >= 5) { + username = trim(configs[3]); + password = trim(configs[4]); + } + for (i = 5; i < configs.size(); i++) { + vArray = split(configs[i], "="); + if (vArray.size() < 2) + continue; + itemName = trim(vArray[0]); + itemVal = trim(vArray[1]); + switch (hash_(itemName)) { + case "udp-relay"_hash: + udp = itemVal; + break; + case "tfo"_hash: + tfo = itemVal; + break; + case "skip-cert-verify"_hash: + scv = itemVal; + break; + default: + continue; + } + } + socksConstruct(node, SOCKS_DEFAULT_GROUP, remarks, server, port, username, password, udp, tfo, scv); + break; + case "vmess"_hash: //surge 4 style vmess proxy + server = trim(configs[1]); + port = trim(configs[2]); + if (port == "0") continue; net = "tcp"; + method = "auto"; - for(i = 1; i < configs.size(); i++) - { - vArray = split(trim(configs[i]), "="); - if(vArray.size() != 2) + for (i = 3; i < configs.size(); i++) { + vArray = split(configs[i], "="); + if (vArray.size() != 2) continue; itemName = trim(vArray[0]); itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "method"_hash: - method = itemVal; - break; - case "password"_hash: - id = itemVal; - break; - case "tag"_hash: - remarks = itemVal; - break; - case "obfs"_hash: - switch(hash_(itemVal)) - { + switch (hash_(itemName)) { + case "username"_hash: + id = itemVal; + break; case "ws"_hash: - net = "ws"; + net = itemVal == "true" ? "ws" : "tcp"; break; - case "over-tls"_hash: - tls = "tls"; + case "tls"_hash: + tls = itemVal == "true" ? "tls" : ""; break; - case "wss"_hash: - net = "ws"; - tls = "tls"; + case "ws-path"_hash: + path = itemVal; break; - } - break; - case "obfs-host"_hash: - host = itemVal; - break; - case "obfs-uri"_hash: - path = itemVal; - break; - case "over-tls"_hash: - tls = itemVal == "true" ? "tls" : ""; - break; - case "udp-relay"_hash: - udp = itemVal; - break; - case "fast-open"_hash: - tfo = itemVal; - break; - case "tls13"_hash: - tls13 = itemVal; - break; - case "aead"_hash: - aead = itemVal == "true" ? "0" : "1"; - default: - continue; + case "obfs-host"_hash: + host = itemVal; + break; + case "ws-headers"_hash: + headers = split(itemVal, "|"); + for (auto &y: headers) { + header = split(trim(y), ":"); + if (header.size() != 2) + continue; + else if (regMatch(header[0], "(?i)host")) + host = trimQuote(header[1]); + else if (regMatch(header[0], "(?i)edge")) + edge = trimQuote(header[1]); + } + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "tfo"_hash: + tfo = itemVal; + break; + case "skip-cert-verify"_hash: + scv = itemVal; + break; + case "tls13"_hash: + tls13 = itemVal; + break; + case "vmess-aead"_hash: + aead = itemVal == "true" ? "0" : "1"; + default: + continue; } } - if(remarks.empty()) - remarks = server + ":" + port; - vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, server, port, "", id, aead, net, method, path, host, "", tls, "", udp, tfo, scv, tls13); + vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, server, port, "", id, aead, net, method, path, host, + edge, tls, "", udp, tfo, scv, tls13); break; - case "trojan"_hash: //quantumult x style trojan link - server = trim(configs[0].substr(0, configs[0].rfind(':'))); - port = trim(configs[0].substr(configs[0].rfind(':') + 1)); - if(port == "0") + case "http"_hash: //http proxy + server = trim(configs[1]); + port = trim(configs[2]); + if (port == "0") continue; - - for(i = 1; i < configs.size(); i++) - { - vArray = split(trim(configs[i]), "="); - if(vArray.size() != 2) + for (i = 3; i < configs.size(); i++) { + vArray = split(configs[i], "="); + if (vArray.size() < 2) continue; itemName = trim(vArray[0]); itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "password"_hash: - password = itemVal; - break; - case "tag"_hash: - remarks = itemVal; - break; - case "over-tls"_hash: - tls = itemVal; - break; - case "tls-host"_hash: - host = itemVal; - break; - case "udp-relay"_hash: - udp = itemVal; - break; - case "fast-open"_hash: - tfo = itemVal; - break; - case "tls-verification"_hash: - scv = itemVal == "false"; - break; - case "tls13"_hash: - tls13 = itemVal; - break; - default: - continue; + switch (hash_(itemName)) { + case "username"_hash: + username = itemVal; + break; + case "password"_hash: + password = itemVal; + break; + case "skip-cert-verify"_hash: + scv = itemVal; + break; + default: + continue; } } - if(remarks.empty()) - remarks = server + ":" + port; - - trojanConstruct(node, TROJAN_DEFAULT_GROUP, remarks, server, port, password, "", host, "", tls == "true", udp, tfo, scv, tls13); + httpConstruct(node, HTTP_DEFAULT_GROUP, remarks, server, port, username, password, false, tfo, scv); break; - case "http"_hash: //quantumult x style http links - server = trim(configs[0].substr(0, configs[0].rfind(':'))); - port = trim(configs[0].substr(configs[0].rfind(':') + 1)); - if(port == "0") + case "trojan"_hash: // surge 4 style trojan proxy + server = trim(configs[1]); + port = trim(configs[2]); + if (port == "0") continue; - for(i = 1; i < configs.size(); i++) - { - vArray = split(trim(configs[i]), "="); - if(vArray.size() != 2) + for (i = 3; i < configs.size(); i++) { + vArray = split(configs[i], "="); + if (vArray.size() != 2) continue; itemName = trim(vArray[0]); itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "username"_hash: - username = itemVal; - break; - case "password"_hash: - password = itemVal; - break; - case "tag"_hash: - remarks = itemVal; - break; - case "over-tls"_hash: - tls = itemVal; - break; - case "tls-verification"_hash: - scv = itemVal == "false"; - break; - case "tls13"_hash: - tls13 = itemVal; - break; - case "fast-open"_hash: - tfo = itemVal; - break; - default: - continue; + switch (hash_(itemName)) { + case "password"_hash: + password = itemVal; + break; + case "sni"_hash: + host = itemVal; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "tfo"_hash: + tfo = itemVal; + break; + case "skip-cert-verify"_hash: + scv = itemVal; + break; + default: + continue; } } - if(remarks.empty()) - remarks = server + ":" + port; - if(username == "none") - username.clear(); - if(password == "none") - password.clear(); + trojanConstruct(node, TROJAN_DEFAULT_GROUP, remarks, server, port, password, "", host, "", true, udp, + tfo, scv); + break; + case "snell"_hash: + server = trim(configs[1]); + port = trim(configs[2]); + if (port == "0") + continue; - httpConstruct(node, HTTP_DEFAULT_GROUP, remarks, server, port, username, password, tls == "true", tfo, scv, tls13); + for (i = 3; i < configs.size(); i++) { + vArray = split(configs[i], "="); + if (vArray.size() != 2) + continue; + itemName = trim(vArray[0]); + itemVal = trim(vArray[1]); + switch (hash_(itemName)) { + case "psk"_hash: + password = itemVal; + break; + case "obfs"_hash: + plugin = itemVal; + break; + case "obfs-host"_hash: + host = itemVal; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "tfo"_hash: + tfo = itemVal; + break; + case "skip-cert-verify"_hash: + scv = itemVal; + break; + case "version"_hash: + version = itemVal; + break; + default: + continue; + } + } + + snellConstruct(node, SNELL_DEFAULT_GROUP, remarks, server, port, password, plugin, host, + to_int(version, 0), udp, tfo, scv); + break; + case "wireguard"_hash: + for (i = 1; i < configs.size(); i++) { + vArray = split(trim(configs[i]), "="); + if (vArray.size() != 2) + continue; + itemName = trim(vArray[0]); + itemVal = trim(vArray[1]); + switch (hash_(itemName)) { + case "section-name"_hash: + section = itemVal; + break; + case "test-url"_hash: + test_url = itemVal; + break; + } + } + if (section.empty()) + continue; + ini.get_items("WireGuard " + section, wireguard_config); + if (wireguard_config.empty()) + continue; + + for (auto &c: wireguard_config) { + itemName = trim(c.first); + itemVal = trim(c.second); + switch (hash_(itemName)) { + case "self-ip"_hash: + ip = itemVal; + break; + case "self-ip-v6"_hash: + ipv6 = itemVal; + break; + case "private-key"_hash: + private_key = itemVal; + break; + case "dns-server"_hash: + vArray = split(itemVal, ","); + for (auto &y: vArray) + dns_servers.emplace_back(trim(y)); + break; + case "mtu"_hash: + mtu = itemVal; + break; + case "peer"_hash: + peer = itemVal; + break; + case "keepalive"_hash: + keepalive = itemVal; + break; + } + } + + wireguardConstruct(node, WG_DEFAULT_GROUP, remarks, "", "0", ip, ipv6, private_key, "", "", dns_servers, + mtu, keepalive, test_url, "", udp); + parsePeers(node, peer); break; default: - continue; - } - break; + switch (hash_(remarks)) { + case "shadowsocks"_hash: //quantumult x style ss/ssr link + server = trim(configs[0].substr(0, configs[0].rfind(":"))); + port = trim(configs[0].substr(configs[0].rfind(":") + 1)); + if (port == "0") + continue; + + for (i = 1; i < configs.size(); i++) { + vArray = split(trim(configs[i]), "="); + if (vArray.size() != 2) + continue; + itemName = trim(vArray[0]); + itemVal = trim(vArray[1]); + switch (hash_(itemName)) { + case "method"_hash: + method = itemVal; + break; + case "password"_hash: + password = itemVal; + break; + case "tag"_hash: + remarks = itemVal; + break; + case "ssr-protocol"_hash: + protocol = itemVal; + break; + case "ssr-protocol-param"_hash: + protoparam = itemVal; + break; + case "obfs"_hash: { + switch (hash_(itemVal)) { + case "http"_hash: + case "tls"_hash: + plugin = "simple-obfs"; + pluginopts_mode = itemVal; + break; + case "wss"_hash: + tls = "tls"; + [[fallthrough]]; + case "ws"_hash: + pluginopts_mode = "websocket"; + plugin = "v2ray-plugin"; + break; + default: + pluginopts_mode = itemVal; + } + break; + } + case "obfs-host"_hash: + pluginopts_host = itemVal; + break; + case "obfs-uri"_hash: + path = itemVal; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "fast-open"_hash: + tfo = itemVal; + break; + case "tls13"_hash: + tls13 = itemVal; + break; + default: + continue; + } + } + if (remarks.empty()) + remarks = server + ":" + port; + switch (hash_(plugin)) { + case "simple-obfs"_hash: + pluginopts = "obfs=" + pluginopts_mode; + if (!pluginopts_host.empty()) + pluginopts += ";obfs-host=" + pluginopts_host; + break; + case "v2ray-plugin"_hash: + if (pluginopts_host.empty() && !isIPv4(server) && !isIPv6(server)) + pluginopts_host = server; + pluginopts = "mode=" + pluginopts_mode; + if (!pluginopts_host.empty()) + pluginopts += ";host=" + pluginopts_host; + if (!path.empty()) + pluginopts += ";path=" + path; + pluginopts += ";" + tls; + break; + } + + if (!protocol.empty()) { + ssrConstruct(node, SSR_DEFAULT_GROUP, remarks, server, port, protocol, method, + pluginopts_mode, password, pluginopts_host, protoparam, udp, tfo, scv); + } else { + ssConstruct(node, SS_DEFAULT_GROUP, remarks, server, port, password, method, plugin, + pluginopts, udp, tfo, scv, tls13); + } + break; + case "vmess"_hash: //quantumult x style vmess link + server = trim(configs[0].substr(0, configs[0].rfind(":"))); + port = trim(configs[0].substr(configs[0].rfind(":") + 1)); + if (port == "0") + continue; + net = "tcp"; + + for (i = 1; i < configs.size(); i++) { + vArray = split(trim(configs[i]), "="); + if (vArray.size() != 2) + continue; + itemName = trim(vArray[0]); + itemVal = trim(vArray[1]); + switch (hash_(itemName)) { + case "method"_hash: + method = itemVal; + break; + case "password"_hash: + id = itemVal; + break; + case "tag"_hash: + remarks = itemVal; + break; + case "obfs"_hash: + switch (hash_(itemVal)) { + case "ws"_hash: + net = "ws"; + break; + case "over-tls"_hash: + tls = "tls"; + break; + case "wss"_hash: + net = "ws"; + tls = "tls"; + break; + } + break; + case "obfs-host"_hash: + host = itemVal; + break; + case "obfs-uri"_hash: + path = itemVal; + break; + case "over-tls"_hash: + tls = itemVal == "true" ? "tls" : ""; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "fast-open"_hash: + tfo = itemVal; + break; + case "tls13"_hash: + tls13 = itemVal; + break; + case "aead"_hash: + aead = itemVal == "true" ? "0" : "1"; + default: + continue; + } + } + if (remarks.empty()) + remarks = server + ":" + port; + + vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, server, port, "", id, aead, net, method, + path, host, "", tls, "", udp, tfo, scv, tls13); + break; + case "trojan"_hash: //quantumult x style trojan link + server = trim(configs[0].substr(0, configs[0].rfind(':'))); + port = trim(configs[0].substr(configs[0].rfind(':') + 1)); + if (port == "0") + continue; + + for (i = 1; i < configs.size(); i++) { + vArray = split(trim(configs[i]), "="); + if (vArray.size() != 2) + continue; + itemName = trim(vArray[0]); + itemVal = trim(vArray[1]); + switch (hash_(itemName)) { + case "password"_hash: + password = itemVal; + break; + case "tag"_hash: + remarks = itemVal; + break; + case "over-tls"_hash: + tls = itemVal; + break; + case "tls-host"_hash: + host = itemVal; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "fast-open"_hash: + tfo = itemVal; + break; + case "tls-verification"_hash: + scv = itemVal == "false"; + break; + case "tls13"_hash: + tls13 = itemVal; + break; + default: + continue; + } + } + if (remarks.empty()) + remarks = server + ":" + port; + + trojanConstruct(node, TROJAN_DEFAULT_GROUP, remarks, server, port, password, "", host, "", + tls == "true", udp, tfo, scv, tls13); + break; + case "http"_hash: //quantumult x style http links + server = trim(configs[0].substr(0, configs[0].rfind(':'))); + port = trim(configs[0].substr(configs[0].rfind(':') + 1)); + if (port == "0") + continue; + + for (i = 1; i < configs.size(); i++) { + vArray = split(trim(configs[i]), "="); + if (vArray.size() != 2) + continue; + itemName = trim(vArray[0]); + itemVal = trim(vArray[1]); + switch (hash_(itemName)) { + case "username"_hash: + username = itemVal; + break; + case "password"_hash: + password = itemVal; + break; + case "tag"_hash: + remarks = itemVal; + break; + case "over-tls"_hash: + tls = itemVal; + break; + case "tls-verification"_hash: + scv = itemVal == "false"; + break; + case "tls13"_hash: + tls13 = itemVal; + break; + case "fast-open"_hash: + tfo = itemVal; + break; + default: + continue; + } + } + if (remarks.empty()) + remarks = server + ":" + port; + + if (username == "none") + username.clear(); + if (password == "none") + password.clear(); + + httpConstruct(node, HTTP_DEFAULT_GROUP, remarks, server, port, username, password, + tls == "true", tfo, scv, tls13); + break; + default: + continue; + } + break; } node.Id = index; @@ -2076,8 +2298,7 @@ bool explodeSurge(std::string surge, std::vector &nodes) return index; } -void explodeSSTap(std::string sstap, std::vector &nodes) -{ +void explodeSSTap(std::string sstap, std::vector &nodes) { std::string configType, group, remarks, server, port; std::string cipher; std::string user, pass; @@ -2085,47 +2306,46 @@ void explodeSSTap(std::string sstap, std::vector &nodes) Document json; uint32_t index = nodes.size(); json.Parse(sstap.data()); - if(json.HasParseError() || !json.IsObject()) + if (json.HasParseError() || !json.IsObject()) return; - for(uint32_t i = 0; i < json["configs"].Size(); i++) - { + for (uint32_t i = 0; i < json["configs"].Size(); i++) { Proxy node; json["configs"][i]["group"] >> group; json["configs"][i]["remarks"] >> remarks; json["configs"][i]["server"] >> server; port = GetMember(json["configs"][i], "server_port"); - if(port == "0") + if (port == "0") continue; - if(remarks.empty()) + if (remarks.empty()) remarks = server + ":" + port; json["configs"][i]["password"] >> pass; json["configs"][i]["type"] >> configType; - switch(to_int(configType, 0)) - { - case 5: //socks 5 - json["configs"][i]["username"] >> user; - socksConstruct(node, group, remarks, server, port, user, pass); - break; - case 6: //ss/ssr - json["configs"][i]["protocol"] >> protocol; - json["configs"][i]["obfs"] >> obfs; - json["configs"][i]["method"] >> cipher; - if(find(ss_ciphers.begin(), ss_ciphers.end(), cipher) != ss_ciphers.end() && protocol == "origin" && obfs == "plain") //is ss - { - ssConstruct(node, group, remarks, server, port, pass, cipher, "", ""); - } - else //is ssr cipher - { - json["configs"][i]["obfsparam"] >> obfsparam; - json["configs"][i]["protocolparam"] >> protoparam; - ssrConstruct(node, group, remarks, server, port, protocol, cipher, obfs, pass, obfsparam, protoparam); - } - break; - default: - continue; + switch (to_int(configType, 0)) { + case 5: //socks 5 + json["configs"][i]["username"] >> user; + socksConstruct(node, group, remarks, server, port, user, pass); + break; + case 6: //ss/ssr + json["configs"][i]["protocol"] >> protocol; + json["configs"][i]["obfs"] >> obfs; + json["configs"][i]["method"] >> cipher; + if (find(ss_ciphers.begin(), ss_ciphers.end(), cipher) != ss_ciphers.end() && protocol == "origin" && + obfs == "plain") //is ss + { + ssConstruct(node, group, remarks, server, port, pass, cipher, "", ""); + } else //is ssr cipher + { + json["configs"][i]["obfsparam"] >> obfsparam; + json["configs"][i]["protocolparam"] >> protoparam; + ssrConstruct(node, group, remarks, server, port, protocol, cipher, obfs, pass, obfsparam, + protoparam); + } + break; + default: + continue; } node.Id = index; @@ -2134,20 +2354,18 @@ void explodeSSTap(std::string sstap, std::vector &nodes) } } -void explodeNetchConf(std::string netch, std::vector &nodes) -{ +void explodeNetchConf(std::string netch, std::vector &nodes) { Document json; uint32_t index = nodes.size(); json.Parse(netch.data()); - if(json.HasParseError() || !json.IsObject()) + if (json.HasParseError() || !json.IsObject()) return; - if(!json.HasMember("Server")) + if (!json.HasMember("Server")) return; - for(uint32_t i = 0; i < json["Server"].Size(); i++) - { + for (uint32_t i = 0; i < json["Server"].Size(); i++) { Proxy node; explodeNetch("Netch://" + base64Encode(json["Server"][i] | SerializeObject()), node); @@ -2157,132 +2375,125 @@ void explodeNetchConf(std::string netch, std::vector &nodes) } } -int explodeConfContent(const std::string &content, std::vector &nodes) -{ +int explodeConfContent(const std::string &content, std::vector &nodes) { ConfType filetype = ConfType::Unknow; - if(strFind(content, "\"version\"")) + if (strFind(content, "\"version\"")) filetype = ConfType::SS; - else if(strFind(content, "\"serverSubscribes\"")) + else if (strFind(content, "\"serverSubscribes\"")) filetype = ConfType::SSR; - else if(strFind(content, "\"uiItem\"") || strFind(content, "vnext")) + else if (strFind(content, "\"uiItem\"") || strFind(content, "vnext")) filetype = ConfType::V2Ray; - else if(strFind(content, "\"proxy_apps\"")) + else if (strFind(content, "\"proxy_apps\"")) filetype = ConfType::SSConf; - else if(strFind(content, "\"idInUse\"")) + else if (strFind(content, "\"idInUse\"")) filetype = ConfType::SSTap; - else if(strFind(content, "\"local_address\"") && strFind(content, "\"local_port\"")) + else if (strFind(content, "\"local_address\"") && strFind(content, "\"local_port\"")) filetype = ConfType::SSR; //use ssr config parser - else if(strFind(content, "\"ModeFileNameType\"")) + else if (strFind(content, "\"ModeFileNameType\"")) filetype = ConfType::Netch; - switch(filetype) - { - case ConfType::SS: - explodeSSConf(content, nodes); - break; - case ConfType::SSR: - explodeSSRConf(content, nodes); - break; - case ConfType::V2Ray: - explodeVmessConf(content, nodes); - break; - case ConfType::SSConf: - explodeSSAndroid(content, nodes); - break; - case ConfType::SSTap: - explodeSSTap(content, nodes); - break; - case ConfType::Netch: - explodeNetchConf(content, nodes); - break; - default: - //try to parse as a local subscription - explodeSub(content, nodes); + switch (filetype) { + case ConfType::SS: + explodeSSConf(content, nodes); + break; + case ConfType::SSR: + explodeSSRConf(content, nodes); + break; + case ConfType::V2Ray: + explodeVmessConf(content, nodes); + break; + case ConfType::SSConf: + explodeSSAndroid(content, nodes); + break; + case ConfType::SSTap: + explodeSSTap(content, nodes); + break; + case ConfType::Netch: + explodeNetchConf(content, nodes); + break; + default: + //try to parse as a local subscription + explodeSub(content, nodes); } return !nodes.empty(); } -void explode(const std::string &link, Proxy &node) -{ - if(startsWith(link, "ssr://")) +void explode(const std::string &link, Proxy &node) { + if (startsWith(link, "ssr://")) explodeSSR(link, node); - else if(startsWith(link, "vmess://") || startsWith(link, "vmess1://")) + else if (startsWith(link, "vmess://") || startsWith(link, "vmess1://")) explodeVmess(link, node); - else if(startsWith(link, "ss://")) + else if (startsWith(link, "ss://")) explodeSS(link, node); - else if(startsWith(link, "socks://") || startsWith(link, "https://t.me/socks") || startsWith(link, "tg://socks")) + else if (startsWith(link, "socks://") || startsWith(link, "https://t.me/socks") || startsWith(link, "tg://socks")) explodeSocks(link, node); - else if(startsWith(link, "https://t.me/http") || startsWith(link, "tg://http")) //telegram style http link + else if (startsWith(link, "https://t.me/http") || startsWith(link, "tg://http")) //telegram style http link explodeHTTP(link, node); - else if(startsWith(link, "Netch://")) + else if (startsWith(link, "Netch://")) explodeNetch(link, node); - else if(startsWith(link, "trojan://")) + else if (startsWith(link, "trojan://")) explodeTrojan(link, node); - else if(isLink(link)) + else if (strFind(link, "vless://") || strFind(link, "vless1://")) + explodeVless(link, node); + else if (strFind(link, "hysteria://")) + explodeHysteria(link, node); + else if (strFind(link, "hysteria2://") || strFind(link, "hy2://")) + explodeHysteria2(link, node); + else if (isLink(link)) explodeHTTPSub(link, node); } -void explodeSub(std::string sub, std::vector &nodes) -{ +void explodeSub(std::string sub, std::vector &nodes) { std::stringstream strstream; std::string strLink; bool processed = false; //try to parse as SSD configuration - if(startsWith(sub, "ssd://")) - { + if (startsWith(sub, "ssd://")) { explodeSSD(sub, nodes); processed = true; } //try to parse as clash configuration - try - { - if(!processed && regFind(sub, "\"?(Proxy|proxies)\"?:")) - { + try { + if (!processed && regFind(sub, "\"?(Proxy|proxies)\"?:")) { regGetMatch(sub, R"(^(?:Proxy|proxies):$\s(?:(?:^ +?.*$| *?-.*$|)\s?)+)", 1, &sub); Node yamlnode = Load(sub); - if(yamlnode.size() && (yamlnode["Proxy"].IsDefined() || yamlnode["proxies"].IsDefined())) - { + if (yamlnode.size() && (yamlnode["Proxy"].IsDefined() || yamlnode["proxies"].IsDefined())) { explodeClash(yamlnode, nodes); processed = true; } } } - catch (std::exception &e) - { + catch (std::exception &e) { //writeLog(0, e.what(), LOG_LEVEL_DEBUG); //ignore throw; } //try to parse as surge configuration - if(!processed && explodeSurge(sub, nodes)) - { + if (!processed && explodeSurge(sub, nodes)) { processed = true; } //try to parse as normal subscription - if(!processed) - { + if (!processed) { sub = urlSafeBase64Decode(sub); - if(regFind(sub, "(vmess|shadowsocks|http|trojan)\\s*?=")) - { - if(explodeSurge(sub, nodes)) + if (regFind(sub, "(vmess|shadowsocks|http|trojan)\\s*?=")) { + if (explodeSurge(sub, nodes)) return; } strstream << sub; - char delimiter = count(sub.begin(), sub.end(), '\n') < 1 ? count(sub.begin(), sub.end(), '\r') < 1 ? ' ' : '\r' : '\n'; - while(getline(strstream, strLink, delimiter)) - { + char delimiter = + count(sub.begin(), sub.end(), '\n') < 1 ? count(sub.begin(), sub.end(), '\r') < 1 ? ' ' : '\r' : '\n'; + while (getline(strstream, strLink, delimiter)) { Proxy node; - if(strLink.rfind('\r') != std::string::npos) + if (strLink.rfind('\r') != std::string::npos) strLink.erase(strLink.size() - 1); explode(strLink, node); - if(strLink.empty() || node.Type == ProxyType::Unknown) - { + if (strLink.empty() || node.Type == ProxyType::Unknown) { continue; } nodes.emplace_back(std::move(node)); diff --git a/src/parser/subparser.h b/src/parser/subparser.h index 5d236a9..49dc150 100644 --- a/src/parser/subparser.h +++ b/src/parser/subparser.h @@ -19,7 +19,9 @@ enum class ConfType SUB, Local }; - +void hysteriaConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, const std::string &port, const std::string &type, const std::string &auth, const std::string &host, const std::string &up, const std::string &down, const std::string &alpn, const std::string &obfsParam, const std::string &insecure ,tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool(), tribool tls13 = tribool()); +void hysteria2Construct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, const std::string &port, const std::string &password, const std::string &host, const std::string &up, const std::string &down, const std::string &alpn, const std::string &obfsParam, const std::string &obfsPassword, const std::string &insecure ,tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool(), tribool tls13 = tribool()); +void vlessConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, const std::string &port, const std::string &type, const std::string &id, const std::string &aid, const std::string &net, const std::string &cipher, const std::string &flow, const std::string &mode, const std::string &path, const std::string &host, const std::string &edge, const std::string &tls,const std::string &pkd, const std::string &sid, const std::string &fp, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool(), tribool tls13 = tribool()); void vmessConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, const std::string &port, const std::string &type, const std::string &id, const std::string &aid, const std::string &net, const std::string &cipher, const std::string &path, const std::string &host, const std::string &edge, const std::string &tls, const std::string &sni, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool(), tribool tls13 = tribool()); void ssrConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &protocol, const std::string &method, const std::string &obfs, const std::string &password, const std::string &obfsparam, const std::string &protoparam, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); void ssConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &password, const std::string &method, const std::string &plugin, const std::string &pluginopts, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool(), tribool tls13 = tribool()); @@ -33,8 +35,14 @@ void explodeSS(std::string ss, Proxy &node); void explodeTrojan(std::string trojan, Proxy &node); void explodeQuan(const std::string &quan, Proxy &node); void explodeStdVMess(std::string vmess, Proxy &node); +void explodeStdVless(std::string vless, Proxy &node); +void explodeStdHysteria(std::string hysteria, Proxy &node); +void explodeStdHysteria2(std::string hysteria2, Proxy &node); void explodeShadowrocket(std::string kit, Proxy &node); void explodeKitsunebi(std::string kit, Proxy &node); +void explodeVless(std::string vless, Proxy &node); +void explodeHysteria(std::string hysteria, Proxy &node); +void explodeHysteria2(std::string hysteria2, Proxy &node); /// Parse a link void explode(const std::string &link, Proxy &node); void explodeSSD(std::string link, std::vector &nodes); diff --git a/src/version.h b/src/version.h index 1188570..199ef09 100644 --- a/src/version.h +++ b/src/version.h @@ -1,6 +1,6 @@ #ifndef VERSION_H_INCLUDED #define VERSION_H_INCLUDED -#define VERSION "v0.8.1" +#define VERSION "v0.8.2" #endif // VERSION_H_INCLUDED