From 025d0eb4e13d648700aa72952fdd3c65bc8a4335 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Thu, 9 Apr 2020 21:07:50 +0800 Subject: [PATCH] Enhancements Fix support for parsing some Surge configurations. Fix adding extra blank line to section end when exporting some configurations. Fix adding real internal link as MANAGED-CONFIG URL when using profile. Fix not filtering load-balance group in Surfboard configurations. (Issue [#128](https://github.com/tindy2013/subconverter/issues/128)). Fix rule type filter not working properly. Add support for using template in external configurations. Add relay group support for Clash configurations. Optimize codes. --- src/ini_reader.h | 17 +++++++----- src/interfaces.cpp | 60 +++++++++++++++++++------------------------ src/speedtestutil.cpp | 3 ++- src/subexport.cpp | 35 ++++++++++++++++++++----- src/templates.cpp | 4 +-- 5 files changed, 70 insertions(+), 49 deletions(-) diff --git a/src/ini_reader.h b/src/ini_reader.h index 866601d..462c324 100644 --- a/src/ini_reader.h +++ b/src/ini_reader.h @@ -22,6 +22,7 @@ enum typedef std::map> ini_data_struct; typedef std::multimap string_multimap; typedef std::vector string_array; +typedef std::string::size_type string_size; class INIReader { @@ -898,6 +899,7 @@ public: std::string ToString() { std::string content; + string_size strsize; if(!parsed) return std::string(); @@ -907,16 +909,19 @@ public: content += "[" + x + "]\n"; if(ini_content.find(x) != ini_content.end()) { - for(auto &y : ini_content.at(x)) + auto section = ini_content.at(x); + for(auto iter = section.begin(); iter != section.end(); iter++) { - if(y.first != "{NONAME}") - content += y.first + "="; - content += y.second + "\n"; + if(iter->first != "{NONAME}") + content += iter->first + "="; + content += iter->second + "\n"; + if(std::next(iter) == section.end()) + strsize = iter->second.size(); } } - content += "\n"; + if(strsize) + content += "\n"; } - return content.erase(content.size() - 2); } diff --git a/src/interfaces.cpp b/src/interfaces.cpp index 293ade2..027e592 100644 --- a/src/interfaces.cpp +++ b/src/interfaces.cpp @@ -125,9 +125,12 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS) while(getline(ss, strLine, delimiter)) { - if(type_int == 2 && !std::any_of(quanx_rule_type.begin(), quanx_rule_type.end(), [strLine](std::string type){return startsWith(strLine, type);})) - continue; - else if(!std::any_of(surge_rule_type.begin(), surge_rule_type.end(), [strLine](std::string type){return startsWith(strLine, type);})) + if(type_int == 2) + { + if(!std::any_of(quanx_rule_type.begin(), quanx_rule_type.end(), [&strLine](std::string type){return startsWith(strLine, type);})) + continue; + } + else if(!std::any_of(surge_rule_type.begin(), surge_rule_type.end(), [&strLine](std::string type){return startsWith(strLine, type);})) continue; lineSize = strLine.size(); @@ -175,8 +178,10 @@ int importItems(string_array &target, bool scope_limit = true) if(fileExist(path)) content = fileGet(path, scope_limit); - else + else if(startsWith(path, "http://") || startsWith(path, "https://") || startsWith(path, "data:")) content = webGet(path, proxy, dummy, cache_config); + else + writeLog(0, "File not found or not a valid URL: " + path, LOG_LEVEL_ERROR); if(!content.size()) return -1; @@ -831,7 +836,7 @@ struct ExternalConfig string_array emoji; string_array include; string_array exclude; - string_map template_args; + template_args *tpl_args = NULL; bool overwrite_original_rules = false; bool enable_rule_generator = true; }; @@ -866,14 +871,14 @@ int loadExternalYAML(YAML::Node &node, ExternalConfig &ext) section["include_remarks"] >> ext.include; section["exclude_remarks"] >> ext.exclude; - if(node["template_args"].IsSequence()) + if(node["template_args"].IsSequence() && ext.tpl_args != NULL) { std::string key, value; for(size_t i = 0; i < node["template_args"].size(); i++) { node["template_args"][i]["key"] >> key; node["template_args"][i]["value"] >> value; - ext.template_args[key] = value; + ext.tpl_args->local_vars[key] = value; } } @@ -882,9 +887,9 @@ int loadExternalYAML(YAML::Node &node, ExternalConfig &ext) int loadExternalConfig(std::string &path, ExternalConfig &ext) { - std::string base_content, dummy; - std::string proxy = parseProxy(proxy_config); - base_content = fetchFile(path, proxy, cache_config); + std::string base_content, dummy, proxy = parseProxy(proxy_config), config = fetchFile(path, proxy, cache_config); + if(render_template(config, *ext.tpl_args, base_content, template_path) != 0) + base_content = config; try { @@ -946,13 +951,13 @@ int loadExternalConfig(std::string &path, ExternalConfig &ext) if(ini.ItemPrefixExist("exclude_remarks")) ini.GetAll("exclude_remarks", ext.exclude); - if(ini.SectionExist("template")) + if(ini.SectionExist("template") && ext.tpl_args != NULL) { ini.EnterSection("template"); string_multimap tempmap; ini.GetItems(tempmap); for(auto &x : tempmap) - ext.template_args[x.first] = x.second; + ext.tpl_args->local_vars[x.first] = x.second; } return 0; @@ -1094,6 +1099,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) extra_ruleset = rulesets; //then load external configuration ExternalConfig extconf; + extconf.tpl_args = &tpl_args; loadExternalConfig(config, extconf); if(!ext.nodelist) { @@ -1118,8 +1124,6 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) ext.rename_array = extconf.rename; if(extconf.emoji.size()) ext.emoji_array = extconf.emoji; - if(extconf.template_args.size()) - tpl_args.local_vars = extconf.template_args; ext.enable_rule_generator = extconf.enable_rule_generator; //load custom group if(extconf.custom_proxy_group.size()) @@ -1230,6 +1234,9 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) string_array dummy_group; std::vector dummy_ruleset; + std::string managed_url = base64_decode(UrlDecode(getUrlArg(argument, "profile_data"))); + if(managed_url.empty()) + managed_url = managed_config_prefix + "/sub?" + argument; //std::cerr<<"Generate target: "; int surge_ver; @@ -1292,7 +1299,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) uploadGist("surge" + version, upload_path, output_content, true); if(write_managed_config && managed_config_prefix.size()) - output_content = "#!MANAGED-CONFIG " + managed_config_prefix + "/sub?" + argument + (interval ? " interval=" + std::to_string(interval) : "") \ + output_content = "#!MANAGED-CONFIG " + managed_url + (interval ? " interval=" + std::to_string(interval) : "") \ + " strict=" + std::string(strict ? "true" : "false") + "\n\n" + output_content; } break; @@ -1311,7 +1318,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) uploadGist("surfboard", upload_path, output_content, true); if(write_managed_config && managed_config_prefix.size()) - output_content = "#!MANAGED-CONFIG " + managed_config_prefix + "/sub?" + argument + (interval ? " interval=" + std::to_string(interval) : "") \ + output_content = "#!MANAGED-CONFIG " + managed_url + (interval ? " interval=" + std::to_string(interval) : "") \ + " strict=" + std::string(strict ? "true" : "false") + "\n\n" + output_content; break; case "mellow"_hash: @@ -1801,27 +1808,14 @@ std::string getProfile(RESPONSE_CALLBACK_ARGS) } } - string_map sub_args; - for(auto &x : contents) - { - sub_args[x.first] = x.second; - } - string_array args = split(argument, "&"); - string_size pos; - for(std::string &x : args) - { - pos = x.find("="); - if(pos == x.npos) - continue; - sub_args[x.substr(0, pos)] = UrlDecode(x.substr(pos + 1)); - } - sub_args["token"] = token; + contents.emplace("token", token); + contents.emplace("profile_data", base64_encode(managed_config_prefix + "/getprofile?" + argument)); std::string query; - for(auto &x : sub_args) + for(auto &x : contents) { query += x.first + "=" + UrlEncode(x.second) + "&"; } - query.erase(query.size() - 1); + query += argument; return subconverter(query, postdata, status_code, extra_headers); } diff --git a/src/speedtestutil.cpp b/src/speedtestutil.cpp index bd3f986..c18c4a5 100644 --- a/src/speedtestutil.cpp +++ b/src/speedtestutil.cpp @@ -1211,7 +1211,8 @@ bool explodeSurge(std::string surge, const std::string &custom_port, int local_p ini.SetIsolatedItemsSection("Proxy"); ini.IncludeSection("Proxy"); ini.AddDirectSaveSection("Proxy"); - surge = regReplace(surge, "^#!.*$\\r?\\n", ""); + if(surge.find("[Proxy]") != surge.npos) + surge = regReplace(surge, "^[\\S\\s]*?\\[", "["); ini.Parse(surge); if(!ini.SectionExist("Proxy")) diff --git a/src/subexport.cpp b/src/subexport.cpp index 484cc29..ff627c6 100644 --- a/src/subexport.cpp +++ b/src/subexport.cpp @@ -28,6 +28,7 @@ const string_array clashr_obfs = {"plain", "http_simple", "http_post", "tls1.2_t /// rule type lists #define basic_types "DOMAIN", "DOMAIN-SUFFIX", "DOMAIN-KEYWORD", "IP-CIDR", "SRC-IP-CIDR", "GEOIP", "MATCH", "FINAL" const string_array clash_rule_type = {basic_types, "IP-CIDR6", "SRC-PORT", "DST-PORT"}; +const string_array surge2_rule_type = {basic_types, "IP-CIDR6", "USER-AGENT", "URL-REGEX", "PROCESS-NAME", "IN-PORT", "DEST-PORT", "SRC-IP"}; const string_array surge_rule_type = {basic_types, "IP-CIDR6", "USER-AGENT", "URL-REGEX", "AND", "OR", "NOT", "PROCESS-NAME", "IN-PORT", "DEST-PORT", "SRC-IP"}; const string_array quanx_rule_type = {basic_types, "USER-AGENT", "URL-REGEX", "PROCESS-NAME", "HOST", "HOST-SUFFIX", "HOST-KEYWORD"}; const string_array surfb_rule_type = {basic_types, "IP-CIDR6", "PROCESS-NAME", "IN-PORT", "DEST-PORT", "SRC-IP"}; @@ -688,12 +689,29 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_ continue; /// remove unsupported types - if((surge_ver == -1 || surge_ver == -2) && !std::any_of(quanx_rule_type.begin(), quanx_rule_type.end(), [strLine](std::string type){return startsWith(strLine, type);})) - continue; - else if(surge_ver == -3 && !std::any_of(surfb_rule_type.begin(), surfb_rule_type.end(), [strLine](std::string type){return startsWith(strLine, type);})) - continue; - else if(!std::any_of(surge_rule_type.begin(), surge_rule_type.end(), [strLine](std::string type){return startsWith(strLine, type);})) - continue; + switch(surge_ver) + { + case -1: + case -2: + if(!std::any_of(quanx_rule_type.begin(), quanx_rule_type.end(), [strLine](std::string type){return startsWith(strLine, type);})) + continue; + break; + case -3: + if(!std::any_of(surfb_rule_type.begin(), surfb_rule_type.end(), [strLine](std::string type){return startsWith(strLine, type);})) + continue; + break; + default: + if(surge_ver > 2) + { + if(!std::any_of(surge_rule_type.begin(), surge_rule_type.end(), [strLine](std::string type){return startsWith(strLine, type);})) + continue; + } + else + { + if(!std::any_of(surge2_rule_type.begin(), surge2_rule_type.end(), [strLine](std::string type){return startsWith(strLine, type);})) + continue; + } + } strLine += "," + rule_group; if(surge_ver == -1 || surge_ver == -2) @@ -1036,6 +1054,7 @@ void netchToClash(std::vector &nodes, YAML::Node &yamlnode, string_arr switch(hash_(vArray[1])) { case "select"_hash: + case "relay"_hash: break; case "url-test"_hash: case "fallback"_hash: @@ -1298,9 +1317,11 @@ std::string netchToSurge(std::vector &nodes, std::string &base_conf, s { case "select"_hash: break; + case "load-balance"_hash: + if(surge_ver < 1) + continue; case "url-test"_hash: case "fallback"_hash: - case "load-balance"_hash: if(rules_upper_bound < 5) continue; rules_upper_bound -= 2; diff --git a/src/templates.cpp b/src/templates.cpp index 17939c8..ebdb762 100644 --- a/src/templates.cpp +++ b/src/templates.cpp @@ -70,8 +70,8 @@ int render_template(const std::string &content, const template_args &vars, std:: }); m_callbacks.add_callback("join", 3, [](inja::Arguments &args) { - std::string str1 = args.at(0)->get(), str2 = args.at(1)->get(), delim = args.at(2)->get(); - return std::move(str1) + std::move(delim) + std::move(str2); + std::string str1 = args.at(0)->get(), str2 = args.at(1)->get(), str3 = args.at(2)->get(); + return std::move(str1) + std::move(str2) + std::move(str3); }); m_callbacks.add_callback("fetch", 1, template_webGet); m_callbacks.add_callback("parseHostname", 1, parseHostname);