diff --git a/src/ini_reader.h b/src/ini_reader.h index 4349273..866601d 100644 --- a/src/ini_reader.h +++ b/src/ini_reader.h @@ -240,7 +240,7 @@ public: strLine.erase(lineSize - 1); lineSize--; } - if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored + if((!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) && !inDirectSaveSection) //empty lines and comments are ignored continue; if(strLine[0] == '[' && strLine[lineSize - 1] == ']') //is a section title { diff --git a/src/interfaces.cpp b/src/interfaces.cpp index 6a81a7f..55d6046 100644 --- a/src/interfaces.cpp +++ b/src/interfaces.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "misc.h" @@ -342,10 +343,11 @@ void refreshRulesets(string_array &ruleset_list, std::vector &r rule_group = trim(vArray[0]); rule_url = trim(vArray[1]); */ - if(x.find(",") == x.npos) + string_size pos = x.find(","); + if(pos == x.npos) continue; - rule_group = trim(x.substr(0, x.find(","))); - rule_url = trim(x.substr(x.find(",") + 1)); + rule_group = trim(x.substr(0, pos)); + rule_url = trim(x.substr(pos + 1)); if(rule_url.find("[]") == 0) { writeLog(0, "Adding rule '" + rule_url.substr(2) + "," + rule_group + "'.", LOG_LEVEL_INFO); @@ -1880,6 +1882,42 @@ std::string getRewriteRemote(RESPONSE_CALLBACK_ARGS) return output_content; } +std::string parseHostname(inja::Arguments &args) +{ + std::string data = args.at(0)->get(); + string_array urls = split(data, ","); + if(!urls.size()) + return std::string(); + + std::string input_content, output_content, proxy = parseProxy(proxy_config); + for(std::string &x : urls) + { + input_content = webGet(x, proxy, cache_config); + output_content += regReplace(input_content, "(?:[\\s\\S]*?)^(?i:hostname\\s*?=\\s*?)(.*?)\\s$(?:[\\s\\S]*)", "$1") + ","; + } + string_array vArray = split(output_content, ","); + std::set hostnames; + for(std::string &x : vArray) + hostnames.emplace(trim(x)); + output_content = std::accumulate(hostnames.begin(), hostnames.end(), std::string(), [](std::string a, std::string b) + { + return std::move(a) + "," + std::move(b); + }); + return output_content; +} + +std::string template_webGet(inja::Arguments &args) +{ + std::string data = args.at(0)->get(), proxy = parseProxy(proxy_config); + return webGet(data, proxy, cache_config); +} + +std::string jinja2_webGet(const std::string &url) +{ + std::string proxy = parseProxy(proxy_config); + return webGet(url, proxy, cache_config); +} + static inline std::string intToStream(unsigned long long stream) { char chrs[16] = {}, units[6] = {' ', 'K', 'M', 'G', 'T', 'P'}; diff --git a/src/interfaces.h b/src/interfaces.h index 795c5cc..ec7d89a 100644 --- a/src/interfaces.h +++ b/src/interfaces.h @@ -3,6 +3,7 @@ #include #include +#include #include "subexport.h" #include "webserver.h" @@ -21,4 +22,8 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS); std::string simpleToClashR(RESPONSE_CALLBACK_ARGS); std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS); +std::string template_webGet(inja::Arguments &args); +std::string jinja2_webGet(const std::string &url); +std::string parseHostname(inja::Arguments &args); + #endif // INTERFACES_H_INCLUDED diff --git a/src/speedtestutil.cpp b/src/speedtestutil.cpp index afa48fc..dbf81f9 100644 --- a/src/speedtestutil.cpp +++ b/src/speedtestutil.cpp @@ -634,8 +634,8 @@ void explodeSocks(std::string link, const std::string &custom_port, nodeInfo &no { server = getUrlArg(link, "server"); port = getUrlArg(link, "port"); - username = getUrlArg(link, "user"); - password = getUrlArg(link, "pass"); + username = UrlDecode(getUrlArg(link, "user")); + password = UrlDecode(getUrlArg(link, "pass")); remarks = UrlDecode(getUrlArg(link, "remarks")); group = UrlDecode(getUrlArg(link, "group")); } @@ -659,8 +659,8 @@ void explodeHTTP(std::string link, const std::string &custom_port, nodeInfo &nod std::string group, remarks, server, port, username, password; server = getUrlArg(link, "server"); port = getUrlArg(link, "port"); - username = getUrlArg(link, "user"); - password = getUrlArg(link, "pass"); + username = UrlDecode(getUrlArg(link, "user")); + password = UrlDecode(getUrlArg(link, "pass")); remarks = UrlDecode(getUrlArg(link, "remarks")); group = UrlDecode(getUrlArg(link, "group")); diff --git a/src/subexport.cpp b/src/subexport.cpp index d8ea414..86a7161 100644 --- a/src/subexport.cpp +++ b/src/subexport.cpp @@ -565,15 +565,10 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_ for(ruleset_content &x : ruleset_content_array) { rule_group = x.rule_group; - retrived_rules = x.rule_content.get(); - if(retrived_rules.empty()) + rule_path = x.rule_path; + if(rule_path.empty()) { - writeLog(0, "Failed to fetch ruleset or ruleset is empty: '" + x.rule_path + "'!", LOG_LEVEL_WARNING); - continue; - } - if(retrived_rules.find("[]") == 0) - { - strLine = retrived_rules.substr(2); + strLine = x.rule_content.get().substr(2); if(strLine == "MATCH") strLine = "FINAL"; strLine += "," + rule_group; @@ -595,7 +590,6 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_ } else { - rule_path = x.rule_path; if(!fileExist(rule_path)) { if(surge_ver > 2) @@ -640,6 +634,12 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_ continue; } } + retrived_rules = x.rule_content.get(); + if(retrived_rules.empty()) + { + writeLog(0, "Failed to fetch ruleset or ruleset is empty: '" + x.rule_path + "'!", LOG_LEVEL_WARNING); + continue; + } char delimiter = count(retrived_rules.begin(), retrived_rules.end(), '\n') < 1 ? '\r' : '\n'; @@ -1081,6 +1081,8 @@ std::string netchToSurge(std::vector &nodes, std::string &base_conf, s ini.store_any_line = true; // filter out sections that requires direct-save + ini.AddDirectSaveSection("General"); + ini.AddDirectSaveSection("Replica"); ini.AddDirectSaveSection("Rule"); ini.AddDirectSaveSection("MITM"); ini.AddDirectSaveSection("Script"); diff --git a/src/template_jinja2.cpp b/src/template_jinja2.cpp new file mode 100644 index 0000000..a5000fd --- /dev/null +++ b/src/template_jinja2.cpp @@ -0,0 +1,47 @@ +#include + +#include +#include +#include +#include + +#include "interfaces.h" +#include "templates.h" +#include "webget.h" +#include "misc.h" + +static inline void parse_json_pointer(nlohmann::json &json, const std::string &path, const std::string &value) +{ + std::string pointer = "/" + replace_all_distinct(path, ".", "/"); + json[nlohmann::json::json_pointer(pointer)] = value; +} + +int render_template(const std::string &content, const template_args &vars, std::string &output, const std::string &include_scope) +{ + jinja2::Template tpl; + nlohmann::json data; + for(auto &x : vars.global_vars) + parse_json_pointer(data["global"], x.first, x.second); + for(auto &x : vars.request_params) + parse_json_pointer(data["request"], x.first, x.second); + for(auto &x : vars.local_vars) + parse_json_pointer(data["local"], x.first, x.second); + tpl.Load(content); + jinja2::ValuesMap valmap = {{"global", jinja2::Reflect(data["global"])}, {"local", jinja2::Reflect(data["local"])}, {"request", jinja2::Reflect(data["request"])}}; + valmap["fetch"] = jinja2::MakeCallable(jinja2_webGet, jinja2::ArgInfo{"url"}); + valmap["replace"] = jinja2::MakeCallable([](const std::string &src, const std::string &target, const std::string &rep) + { + return regReplace(src, target, rep); + }, jinja2::ArgInfo{"src"}, jinja2::ArgInfo{"target"}, jinja2::ArgInfo{"rep"}); + try + { + output = tpl.RenderAsString(valmap).value(); + return 0; + } + catch (std::exception &e) + { + output = "Template render failed! Reason: " + std::string(e.what()); + return -1; + } + return -2; +} diff --git a/src/templates.cpp b/src/templates.cpp index 338244b..17939c8 100644 --- a/src/templates.cpp +++ b/src/templates.cpp @@ -4,6 +4,7 @@ #include #include +#include "interfaces.h" #include "templates.h" #include "logger.h" #include "misc.h" @@ -49,6 +50,31 @@ int render_template(const std::string &content, const template_args &vars, std:: std::string data = args.at(0)->get(); return trim(data); }); + m_callbacks.add_callback("replace", 3, [](inja::Arguments &args) + { + std::string src = args.at(0)->get(), target = args.at(1)->get(), rep = args.at(2)->get(); + if(target.empty() || src.empty()) + return src; + return regReplace(src, target, rep); + }); + m_callbacks.add_callback("set", 2, [&data](inja::Arguments &args) + { + std::string key = args.at(0)->get(), value = args.at(1)->get(); + parse_json_pointer(data, key, value); + return std::string(); + }); + m_callbacks.add_callback("join", 2, [](inja::Arguments &args) + { + std::string str1 = args.at(0)->get(), str2 = args.at(1)->get(); + return std::move(str1) + std::move(str2); + }); + 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); + }); + m_callbacks.add_callback("fetch", 1, template_webGet); + m_callbacks.add_callback("parseHostname", 1, parseHostname); m_parser_config.include_scope_limit = true; m_parser_config.include_scope = include_scope;