#include #include #include #include #include #include #include "misc.h" #include "nodeinfo.h" #include "speedtestutil.h" #include "nodemanip.h" #include "ini_reader.h" #include "webget.h" #include "webserver.h" #include "subexport.h" #include "multithread.h" #include "logger.h" #include "string_hash.h" #include "templates.h" //common settings std::string pref_path = "pref.ini"; string_array def_exclude_remarks, def_include_remarks, rulesets, stream_rules, time_rules; std::vector ruleset_content_array; std::string listen_address = "127.0.0.1", default_url, insert_url, managed_config_prefix; int listen_port = 25500, max_pending_connections = 10, max_concurrent_threads = 4; bool api_mode = true, write_managed_config = false, enable_rule_generator = true, update_ruleset_on_request = false, overwrite_original_rules = true; bool print_debug_info = false, cfw_child_process = false, append_userinfo = true, enable_base_gen = false, async_fetch_ruleset = false; std::string access_token; extern std::string custom_group; extern int global_log_level; //global variables for template std::string template_path; string_map global_vars; //generator settings bool generator_mode = false; std::string gen_profile; //multi-thread lock std::mutex on_configuring; //preferences string_array renames, emojis; bool add_emoji = false, remove_old_emoji = false, append_proxy_type = false, filter_deprecated = true; bool udp_flag = false, tfo_flag = false, scv_flag = false, do_sort = false, config_update_strict = false; bool clash_use_new_field_name = false; std::string proxy_config, proxy_ruleset, proxy_subscription; int config_update_interval = 0; std::string clash_rule_base; string_array clash_extra_group; std::string surge_rule_base, surfboard_rule_base, mellow_rule_base, quan_rule_base, quanx_rule_base, loon_rule_base, sssub_rule_base; std::string surge_ssr_path, quanx_script_id; //pre-compiled rule bases YAML::Node clash_base; INIReader surge_base, mellow_base; //cache system bool serve_cache_on_fetch_fail = false; int cache_subscription = 60, cache_config = 300, cache_ruleset = 21600; string_array regex_blacklist = {"(.*)*"}; template void operator >> (const YAML::Node& node, T& i) { if(node.IsDefined() && !node.IsNull()) //fail-safe i = node.as(); }; template T safe_as (const YAML::Node& node) { if(node.IsDefined() && !node.IsNull()) return node.as(); return T(); }; std::string parseProxy(const std::string &source) { std::string proxy = source; if(source == "SYSTEM") proxy = getSystemProxy(); else if(source == "NONE") proxy = ""; return proxy; } std::string getRuleset(RESPONSE_CALLBACK_ARGS) { std::string url = urlsafe_base64_decode(getUrlArg(argument, "url")), type = getUrlArg(argument, "type"), group = urlsafe_base64_decode(getUrlArg(argument, "group")); std::string output_content, dummy; if(!url.size() || !type.size() || (type == "2" && !group.size()) || (type != "1" && type != "2")) { *status_code = 400; return "Invalid request!"; } std::string proxy = parseProxy(proxy_ruleset); output_content = fetchFile(url, proxy, cache_ruleset); if(!output_content.size()) { *status_code = 400; return "Invalid request!"; } if(type == "2") { std::string strLine; std::stringstream ss; const std::string rule_match_regex = "^(.*?,.*?)(,.*)(,.*)$"; ss << output_content; char delimiter = count(output_content.begin(), output_content.end(), '\n') < 1 ? '\r' : '\n'; std::string::size_type lineSize; output_content.clear(); while(getline(ss, strLine, delimiter)) { if(strLine.find("IP-CIDR6") == 0 || strLine.find("URL-REGEX") == 0 || strLine.find("PROCESS-NAME") == 0 || strLine.find("AND") == 0 || strLine.find("OR") == 0) //remove unsupported types continue; lineSize = strLine.size(); if(lineSize && strLine[lineSize - 1] == '\r') //remove line break { strLine.erase(lineSize - 1); lineSize--; } if(!strLine.empty() && (strLine[0] != ';' && strLine[0] != '#' && !(lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/'))) { strLine += "," + group; if(std::count(strLine.begin(), strLine.end(), ',') > 2 && regReplace(strLine, rule_match_regex, "$2") == ",no-resolve") strLine = regReplace(strLine, rule_match_regex, "$1$3$2"); else strLine = regReplace(strLine, rule_match_regex, "$1$3"); } output_content.append(strLine + "\n"); } } return output_content; } int importItems(string_array &target, bool scope_limit = true) { string_array result; std::stringstream ss; std::string path, content, strLine, dummy; unsigned int itemCount = 0; for(std::string &x : target) { if(x.find("!!import:") == x.npos) { result.emplace_back(x); continue; } path = x.substr(x.find(":") + 1); writeLog(0, "Trying to import items from " + path); std::string proxy = parseProxy(proxy_config); if(fileExist(path)) content = fileGet(path, scope_limit); else content = webGet(path, proxy, dummy, cache_config); if(!content.size()) return -1; ss << content; char delimiter = count(content.begin(), content.end(), '\n') < 1 ? '\r' : '\n'; std::string::size_type lineSize; while(getline(ss, strLine, delimiter)) { lineSize = strLine.size(); if(lineSize && strLine[lineSize - 1] == '\r') //remove line break { strLine = strLine.substr(0, lineSize - 1); lineSize--; } if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored continue; result.emplace_back(strLine); itemCount++; } ss.clear(); } target.swap(result); writeLog(0, "Imported " + std::to_string(itemCount) + " item(s)."); return 0; } void readRegexMatch(YAML::Node node, std::string delimiter, string_array &dest, bool scope_limit = true) { YAML::Node object; std::string url, match, rep, strLine; for(unsigned i = 0; i < node.size(); i++) { object = node[i]; object["import"] >> url; if(url.size()) { url = "!!import:" + url; dest.emplace_back(url); continue; } object["match"] >> match; object["replace"] >> rep; if(match.size() && rep.size()) strLine = match + delimiter + rep; else continue; dest.emplace_back(strLine); } importItems(dest, scope_limit); } void readEmoji(YAML::Node node, string_array &dest, bool scope_limit = true) { YAML::Node object; std::string url, match, rep, strLine; for(unsigned i = 0; i < node.size(); i++) { object = node[i]; object["import"] >> url; if(url.size()) { url = "!!import:" + url; dest.emplace_back(url); continue; } object["match"] >> match; object["emoji"] >> rep; if(match.size() && rep.size()) strLine = match + "," + rep; else continue; dest.emplace_back(strLine); } importItems(dest, scope_limit); } void readGroup(YAML::Node node, string_array &dest, bool scope_limit = true) { std::string strLine, name, type, url, interval; string_array tempArray; YAML::Node object; unsigned int i, j; for(i = 0; i < node.size(); i++) { name.clear(); eraseElements(tempArray); object = node[i]; object["import"] >> name; if(name.size()) { name = "!!import:" + name; dest.emplace_back(name); continue; } url = "http://www.gstatic.com/generate_204", interval = "300"; object["name"] >> name; object["type"] >> type; tempArray.emplace_back(name); tempArray.emplace_back(type); object["url"] >> url; object["interval"] >> interval; for(j = 0; j < object["rule"].size(); j++) tempArray.emplace_back(safe_as(object["rule"][j])); if(type != "select" && type != "ssid") { tempArray.emplace_back(url); tempArray.emplace_back(interval); } if((type == "select" && tempArray.size() < 3) || (type == "ssid" && tempArray.size() < 4) || (type != "select" && type != "ssid" && tempArray.size() < 5)) continue; strLine = std::accumulate(std::next(tempArray.begin()), tempArray.end(), tempArray[0], [](std::string a, std::string b) -> std::string { return std::move(a) + "`" + std::move(b); }); dest.emplace_back(strLine); } importItems(dest, scope_limit); } void readRuleset(YAML::Node node, string_array &dest, bool scope_limit = true) { std::string strLine, name, url, group; YAML::Node object; for(unsigned int i = 0; i < node.size(); i++) { url.clear(); name.clear(); object = node[i]; object["import"] >> name; if(name.size()) { name = "!!import:" + name; dest.emplace_back(name); continue; } object["ruleset"] >> url; object["group"] >> group; object["rule"] >> name; if(url.size()) strLine = group + "," + url; else if(name.size()) strLine = group + ",[]" + name; else continue; dest.emplace_back(strLine); } importItems(dest, scope_limit); } void refreshRulesets(string_array &ruleset_list, std::vector &rca) { eraseElements(rca); std::string rule_group, rule_url, dummy; ruleset_content rc; std::string proxy = parseProxy(proxy_ruleset); for(std::string &x : ruleset_list) { /* vArray = split(x, ","); if(vArray.size() != 2) continue; rule_group = trim(vArray[0]); rule_url = trim(vArray[1]); */ string_size pos = x.find(","); if(pos == x.npos) continue; 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); //std::cerr<<"Adding rule '"<> api_mode; section["api_access_token"] >> access_token; if(section["default_url"].IsSequence()) { section["default_url"] >> tempArray; if(tempArray.size()) { strLine = std::accumulate(std::next(tempArray.begin()), tempArray.end(), tempArray[0], [](std::string a, std::string b) { return std::move(a) + "|" + std::move(b); }); default_url = strLine; eraseElements(tempArray); } } if(section["insert_url"].IsSequence()) { section["insert_url"] >> tempArray; if(tempArray.size()) { strLine = std::accumulate(std::next(tempArray.begin()), tempArray.end(), tempArray[0], [](std::string a, std::string b) { return std::move(a) + "|" + std::move(b); }); insert_url = strLine; eraseElements(tempArray); } } if(section["exclude_remarks"].IsSequence()) section["exclude_remarks"] >> def_exclude_remarks; if(section["include_remarks"].IsSequence()) section["include_remarks"] >> def_include_remarks; section["clash_rule_base"] >> clash_rule_base; section["surge_rule_base"] >> surge_rule_base; section["surfboard_rule_base"] >> surfboard_rule_base; section["mellow_rule_base"] >> mellow_rule_base; section["quan_rule_base"] >> quan_rule_base; section["quanx_rule_base"] >> quanx_rule_base; section["loon_rule_base"] >> loon_rule_base; section["sssub_rule_base"] >> sssub_rule_base; section["append_proxy_type"] >> append_proxy_type; section["proxy_config"] >> proxy_config; section["proxy_ruleset"] >> proxy_ruleset; section["proxy_subscription"] >> proxy_subscription; if(node["userinfo"].IsDefined()) { section = node["userinfo"]; if(section["stream_rule"].IsSequence()) { readRegexMatch(section["stream_rule"], "|", tempArray, false); safe_set_streams(tempArray); eraseElements(tempArray); } if(section["time_rule"].IsSequence()) { readRegexMatch(section["time_rule"], "|", tempArray, false); safe_set_times(tempArray); eraseElements(tempArray); } } if(node["node_pref"].IsDefined()) { section = node["node_pref"]; section["udp_flag"] >> udp_flag; section["tcp_fast_open_flag"] >> tfo_flag; section["sort_flag"] >> do_sort; section["skip_cert_verify_flag"] >> scv_flag; section["filter_deprecated_nodes"] >> filter_deprecated; section["append_sub_userinfo"] >> append_userinfo; section["clash_use_new_field_name"] >> clash_use_new_field_name; } if(section["rename_node"].IsSequence()) { readRegexMatch(section["rename_node"], "@", tempArray, false); safe_set_renames(tempArray); eraseElements(tempArray); } if(node["managed_config"].IsDefined()) { section = node["managed_config"]; section["write_managed_config"] >> write_managed_config; section["managed_config_prefix"] >> managed_config_prefix; section["config_update_interval"] >> config_update_interval; section["config_update_strict"] >> config_update_strict; section["quanx_device_id"] >> quanx_script_id; } if(node["surge_external_proxy"].IsDefined()) node["surge_external_proxy"]["surge_ssr_path"] >> surge_ssr_path; if(node["emojis"].IsDefined()) { section = node["emojis"]; section["add_emoji"] >> add_emoji; section["remove_old_emoji"] >> remove_old_emoji; if(section["rules"].IsSequence()) { readEmoji(section["rules"], tempArray, false); safe_set_emojis(tempArray); eraseElements(tempArray); } } if(node["ruleset"].IsDefined()) { section = node["ruleset"]; section["enabled"] >> enable_rule_generator; if(!enable_rule_generator) { overwrite_original_rules = false; update_ruleset_on_request = false; } else { section["overwrite_original_rules"] >> overwrite_original_rules; section["update_ruleset_on_request"] >> update_ruleset_on_request; } if(section["surge_ruleset"].IsSequence()) readRuleset(section["surge_ruleset"], rulesets, false); } if(node["proxy_group"].IsDefined() && node["proxy_group"]["custom_proxy_group"].IsDefined()) readGroup(node["proxy_group"]["custom_proxy_group"], clash_extra_group, false); if(node["template"].IsDefined()) { node["template"]["template_path"] >> template_path; if(node["template"]["globals"].IsSequence()) { eraseElements(global_vars); std::string key, value; for(size_t i = 0; i < node["template"]["globals"].size(); i++) { node["template"]["globals"][i]["key"] >> key; node["template"]["globals"][i]["value"] >> value; global_vars[key] = value; } } } if(node["server"].IsDefined()) { node["server"]["listen"] >> listen_address; node["server"]["port"] >> listen_port; } if(node["advanced"].IsDefined()) { std::string log_level; node["advanced"]["log_level"] >> log_level; node["advanced"]["print_debug_info"] >> print_debug_info; if(print_debug_info) global_log_level = LOG_LEVEL_VERBOSE; else { switch(hash_(log_level)) { case "warn"_hash: global_log_level = LOG_LEVEL_WARNING; break; case "error"_hash: global_log_level = LOG_LEVEL_ERROR; break; case "fatal"_hash: global_log_level = LOG_LEVEL_FATAL; break; case "verbose"_hash: global_log_level = LOG_LEVEL_VERBOSE; break; case "debug"_hash: global_log_level = LOG_LEVEL_DEBUG; break; default: global_log_level = LOG_LEVEL_INFO; } } node["advanced"]["max_pending_connections"] >> max_pending_connections; node["advanced"]["max_concurrent_threads"] >> max_concurrent_threads; node["advanced"]["enable_base_gen"] >> enable_base_gen; if(node["advanced"]["enable_cache"].IsDefined()) { if(safe_as(node["advanced"]["enable_cache"])) { node["advanced"]["cache_subscription"] >> cache_subscription; node["advanced"]["cache_config"] >> cache_config; node["advanced"]["cache_ruleset"] >> cache_ruleset; node["advanced"]["serve_cache_on_fetch_fail"] >> serve_cache_on_fetch_fail; } else cache_subscription = cache_config = cache_ruleset = 0; //disable cache } node["advanced"]["async_fetch_ruleset"] >> async_fetch_ruleset; } } void readConf() { guarded_mutex guard(on_configuring); //std::cerr<<"Reading preference settings..."<> ext.clash_rule_base; section["surge_rule_base"] >> ext.surge_rule_base; section["surfboard_rule_base"] >> ext.surfboard_rule_base; section["mellow_rule_base"] >> ext.mellow_rule_base; section["quan_rule_base"] >> ext.quan_rule_base; section["quanx_rule_base"] >> ext.quanx_rule_base; section["loon_rule_base"] >> ext.loon_rule_base; section["sssub_rule_base"] >> ext.sssub_rule_base; section["enable_rule_generator"] >> ext.enable_rule_generator; section["overwrite_original_rules"] >> ext.overwrite_original_rules; if(section["custom_proxy_group"].size()) readGroup(section["custom_proxy_group"], ext.custom_proxy_group, api_mode); if(section["surge_ruleset"].size()) readRuleset(section["surge_ruleset"], ext.surge_ruleset, api_mode); if(section["rename_node"].size()) readRegexMatch(section["rename_node"], "@", ext.rename, api_mode); section["include_remarks"] >> ext.include; section["exclude_remarks"] >> ext.exclude; if(node["template_args"].IsSequence()) { 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; } } return 0; } 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); try { YAML::Node yaml = YAML::Load(base_content); if(yaml.size() && yaml["custom"]) return loadExternalYAML(yaml, ext); } catch (YAML::Exception &e) { //ignore } INIReader ini; ini.store_isolated_line = true; ini.SetIsolatedItemsSection("custom"); if(ini.Parse(base_content) != INIREADER_EXCEPTION_NONE) { //std::cerr<<"Load external configuration failed. Reason: "< rca; extra_settings ext; std::string subInfo, dummy; int interval = interval_str.size() ? to_int(interval_str, config_update_interval) : config_update_interval; bool ruleset_updated = false, authorized = !api_mode || getUrlArg(argument, "token") == access_token, strict = strict_str.size() ? strict_str == "true" : config_update_strict; if(std::find(regex_blacklist.cbegin(), regex_blacklist.cend(), include) != regex_blacklist.cend() || std::find(regex_blacklist.cbegin(), regex_blacklist.cend(), exclude) != regex_blacklist.cend()) return "Invalid request!"; //for external configuration std::string ext_clash_base = clash_rule_base, ext_surge_base = surge_rule_base, ext_mellow_base = mellow_rule_base, ext_surfboard_base = surfboard_rule_base; std::string ext_quan_base = quan_rule_base, ext_quanx_base = quanx_rule_base, ext_loon_base = loon_rule_base, ext_sssub_base = sssub_rule_base; //validate urls if(!url.size() && (!api_mode || authorized)) url = default_url; if(insert_url.size()) url = insert_url + "|" + url; if(!url.size() || !target.size()) { *status_code = 400; return "Invalid request!"; } //load request arguments as template variables string_array req_args = split(argument, "&"); string_size pos; string_map req_arg_map; for(std::string &x : req_args) { pos = x.find("="); if(pos == x.npos) continue; req_arg_map[x.substr(0, pos)] = x.substr(pos + 1); } //save template variables template_args tpl_args; tpl_args.global_vars = global_vars; tpl_args.request_params = req_arg_map; //check if we need to read configuration if((!api_mode || cfw_child_process) && !generator_mode) readConf(); //check for proxy settings std::string proxy = parseProxy(proxy_subscription); ext.emoji_array = safe_get_emojis(); ext.rename_array = safe_get_renames(); //check other flags if(emoji.size()) { ext.add_emoji = emoji == "true"; ext.remove_emoji = true; } else { ext.add_emoji = add_emoji; ext.remove_emoji = remove_old_emoji; } if(append_type.size()) ext.append_proxy_type = append_type == "true"; else ext.append_proxy_type = append_proxy_type; ext.tfo = tfo.size() ? tfo == "true" : tfo_flag; ext.udp = udp.size() ? udp == "true" : udp_flag; ext.sort_flag = sort_flag.size() ? sort_flag == "true" : do_sort; ext.skip_cert_verify = scv.size() ? scv == "true" : scv_flag; ext.filter_deprecated = fdn.size() ? fdn == "true" : filter_deprecated; ext.clash_new_field_name = clash_new_field.size() ? clash_new_field == "true" : clash_use_new_field_name; ext.nodelist = nodelist == "true"; ext.surge_ssr_path = surge_ssr_path; ext.quanx_dev_id = dev_id.size() ? dev_id : quanx_script_id; ext.enable_rule_generator = enable_rule_generator; ext.overwrite_original_rules = overwrite_original_rules; if(expand != "true") ext.managed_config_prefix = managed_config_prefix; //load external configuration if(config.size()) { //std::cerr<<"External configuration file provided. Loading...\n"; writeLog(0, "External configuration file provided. Loading...", LOG_LEVEL_INFO); //read predefined data first extra_group = clash_extra_group; extra_ruleset = rulesets; //then load external configuration ExternalConfig extconf; loadExternalConfig(config, extconf); if(!ext.nodelist) { if(extconf.clash_rule_base.size()) ext_clash_base = extconf.clash_rule_base; if(extconf.surge_rule_base.size()) ext_surge_base = extconf.surge_rule_base; if(extconf.surfboard_rule_base.size()) ext_surfboard_base = extconf.surfboard_rule_base; if(extconf.mellow_rule_base.size()) ext_mellow_base = extconf.mellow_rule_base; if(extconf.quan_rule_base.size()) ext_quan_base = extconf.quan_rule_base; if(extconf.quanx_rule_base.size()) ext_quanx_base = extconf.quanx_rule_base; if(extconf.loon_rule_base.size()) ext_loon_base = extconf.loon_rule_base; if(extconf.sssub_rule_base.size()) ext_sssub_base = extconf.sssub_rule_base; } if(extconf.rename.size()) 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()) extra_group = extconf.custom_proxy_group; //load custom rules ext.overwrite_original_rules = extconf.overwrite_original_rules; if(extconf.surge_ruleset.size() && !ext.nodelist) { extra_ruleset = extconf.surge_ruleset; refreshRulesets(extra_ruleset, rca); ruleset_updated = true; } else { if(ext.enable_rule_generator && !ext.nodelist) { if(update_ruleset_on_request) refreshRulesets(rulesets, ruleset_content_array); rca = ruleset_content_array; } } if(extconf.include.size()) include_remarks = extconf.include; if(extconf.exclude.size()) exclude_remarks = extconf.exclude; } else { //loading custom groups if(groups.size() && !ext.nodelist) { extra_group = split(groups, "@"); if(!extra_group.size()) extra_group = clash_extra_group; } else extra_group = clash_extra_group; //loading custom rulesets if(ruleset.size() && !ext.nodelist) { extra_ruleset = split(ruleset, "@"); if(!extra_ruleset.size()) { if(update_ruleset_on_request) refreshRulesets(rulesets, ruleset_content_array); rca = ruleset_content_array; } else { refreshRulesets(extra_ruleset, rca); ruleset_updated = true; } } else { if(enable_rule_generator && !ext.nodelist) { if(update_ruleset_on_request || cfw_child_process) refreshRulesets(rulesets, ruleset_content_array); rca = ruleset_content_array; } } } //loading urls string_array urls = split(url, "|"); std::vector nodes; int groupID = 0; //check custom include/exclude settings if(include.size() && regValid(include)) include_remarks = string_array{include}; if(exclude.size() && regValid(exclude)) exclude_remarks = string_array{exclude}; //start parsing urls string_array stream_temp = safe_get_streams(), time_temp = safe_get_times(); for(std::string &x : urls) { x = regTrim(x); //std::cerr<<"Fetching node data from url '"< dummy_ruleset; //std::cerr<<"Generate target: "; int surge_ver; proxy = parseProxy(proxy_config); switch(hash_(target)) { case "clash"_hash: case "clashr"_hash: //std::cerr<<"Clash"<<((target == "clashr") ? "R" : "")< nodes; string_array extra_group, extra_ruleset, include_remarks, exclude_remarks; std::vector rca; std::string subInfo; if(!url.size() && !api_mode) url = default_url; if(!url.size() || argument.substr(0, 8) != "sublink=") { *status_code = 400; return "Invalid request!"; } if(url == "sublink") { *status_code = 400; return "Please insert your subscription link instead of clicking the default link."; } if(insert_url.size()) url = insert_url + "|" + url; if(!api_mode || cfw_child_process) readConf(); extra_group = clash_extra_group; if(update_ruleset_on_request || cfw_child_process) refreshRulesets(rulesets, ruleset_content_array); rca = ruleset_content_array; extra_settings ext = {true, overwrite_original_rules, safe_get_renames(), safe_get_emojis(), add_emoji, remove_old_emoji, append_proxy_type, udp_flag, tfo_flag, false, do_sort, scv_flag, filter_deprecated, clash_use_new_field_name, "", "", ""}; std::string proxy = parseProxy(proxy_subscription); include_remarks = def_include_remarks; exclude_remarks = def_exclude_remarks; //start parsing urls int groupID = 0; string_array dummy; string_array urls = split(url, "|"); for(std::string &x : urls) { x = trim(x); //std::cerr<<"Fetching node data from url '"< nodes; std::string base_content, url = argument.size() <= 5 ? "" : argument.substr(5), dummy; const std::string proxygroup_name = clash_use_new_field_name ? "proxy-groups" : "Proxy Group", rule_name = clash_use_new_field_name ? "rules" : "Rule"; ini.store_any_line = true; if(!url.size()) url = default_url; if(!url.size() || argument.substr(0, 5) != "link=") { *status_code = 400; return "Invalid request!"; } if(url == "link") { *status_code = 400; return "Please insert your subscription link instead of clicking the default link."; } std::string proxy = parseProxy(proxy_config); YAML::Node clash; template_args tpl_args; tpl_args.global_vars = global_vars; tpl_args.local_vars["clash.new_field_name"] = clash_use_new_field_name ? "true" : "false"; tpl_args.request_params["target"] = "clash"; tpl_args.request_params["url"] = url; if(!enable_base_gen) { if(render_template(fetchFile(clash_rule_base, proxy, cache_config), tpl_args, base_content, template_path) != 0) { *status_code = 400; return base_content; } //base_content = fetchFile(clash_rule_base, proxy, cache_config); clash = YAML::Load(base_content); } else { clash = safe_get_clash_base(); } base_content = fetchFile(url, proxy, cache_config); if(ini.Parse(base_content) != INIREADER_EXCEPTION_NONE) { std::string errmsg = "Parsing Surge config failed! Reason: " + ini.GetLastError(); //std::cerr<= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored continue; else if(strLine.find("USER-AGENT") == 0 || strLine.find("URL-REGEX") == 0 || strLine.find("PROCESS-NAME") == 0 || strLine.find("AND") == 0 || strLine.find("OR") == 0) //remove unsupported types continue; strLine += strArray[2]; if(std::count(strLine.begin(), strLine.end(), ',') > 2) strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2"); rule.push_back(strLine); } ss.clear(); continue; } rule.push_back(x); } clash[rule_name] = rule; return YAML::Dump(clash); } std::string getProfile(RESPONSE_CALLBACK_ARGS) { std::string name = UrlDecode(getUrlArg(argument, "name")), token = UrlDecode(getUrlArg(argument, "token")); if(token.empty() || name.empty()) { *status_code = 403; return "Forbidden"; } if(!fileExist(name)) { *status_code = 404; return "Profile not found"; } //std::cerr<<"Trying to load profile '" + name + "'.\n"; writeLog(0, "Trying to load profile '" + name + "'.", LOG_LEVEL_INFO); INIReader ini; if(ini.ParseFile(name) != INIREADER_EXCEPTION_NONE && !ini.SectionExist("Profile")) { //std::cerr<<"Load profile failed! Reason: "<second) { *status_code = 403; return "Forbidden"; } token = access_token; } else { if(token != access_token) { *status_code = 403; return "Forbidden"; } } 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; std::string query; for(auto &x : sub_args) { query += x.first + "=" + UrlEncode(x.second) + "&"; } query.erase(query.size() - 1); return subconverter(query, postdata, status_code, extra_headers); } std::string getScript(RESPONSE_CALLBACK_ARGS) { std::string url = urlsafe_base64_decode(getUrlArg(argument, "url")), dev_id = getUrlArg(argument, "id"); std::string output_content, dummy; std::string proxy = parseProxy(proxy_config); output_content = fetchFile(url, proxy, cache_config); if(!dev_id.size()) dev_id = quanx_script_id; const std::string pattern = "(\\/\\*[\\s\\S]*?)^(.*?@supported )(.*?\\s?)$([\\s\\S]*\\*\\/\\s?"; if(dev_id.size()) { if(regFind(output_content, pattern)) output_content = regReplace(output_content, pattern, "$1$2" + dev_id + "$4"); else output_content = "/**\n * @supported " + dev_id + "\n * THIS COMMENT IS GENERATED BY SUBCONVERTER\n */\n\n" + output_content; } return output_content; } std::string getRewriteRemote(RESPONSE_CALLBACK_ARGS) { std::string url = urlsafe_base64_decode(getUrlArg(argument, "url")), dev_id = getUrlArg(argument, "id"); std::string output_content, dummy; std::string proxy = parseProxy(proxy_config); output_content = fetchFile(url, proxy, cache_config); if(!dev_id.size()) dev_id = quanx_script_id; if(dev_id.size()) { std::stringstream ss; std::string strLine; const std::string pattern = "^(.*? url script-.*? )(.*?)$"; string_size lineSize; char delimiter = count(output_content.begin(), output_content.end(), '\n') < 1 ? '\r' : '\n'; ss << output_content; output_content.clear(); while(getline(ss, strLine, delimiter)) { lineSize = strLine.size(); if(lineSize && strLine[lineSize - 1] == '\r') //remove line break { strLine.erase(lineSize - 1); lineSize--; } if(!strLine.empty() && regMatch(strLine, pattern)) { url = managed_config_prefix + "/qx-script?id=" + dev_id + "&url=" + urlsafe_base64_encode(regReplace(strLine, pattern, "$2")); strLine = regReplace(strLine, pattern, "$1") + url; } output_content.append(strLine + "\n"); } } 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'}; double streamval = stream; unsigned int level = 0; while(streamval > 1024.0) { if(level >= 5) break; level++; streamval /= 1024.0; } snprintf(chrs, 15, "%.2f %cB", streamval, units[level]); return std::string(chrs); } std::string subInfoToMessage(std::string subinfo) { typedef unsigned long long ull; subinfo = replace_all_distinct(subinfo, "; ", "&"); std::string retdata, useddata = "N/A", totaldata = "N/A", expirydata = "N/A"; std::string upload = getUrlArg(subinfo, "upload"), download = getUrlArg(subinfo, "download"), total = getUrlArg(subinfo, "total"), expire = getUrlArg(subinfo, "expire"); ull used = to_number(upload, 0) + to_number(download, 0), tot = to_number(total, 0); time_t expiry = to_number(expire, 0); if(used != 0) useddata = intToStream(used); if(tot != 0) totaldata = intToStream(tot); if(expiry != 0) { char buffer[30]; struct tm *dt = localtime(&expiry); strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M", dt); expirydata.assign(buffer); } if(useddata == "N/A" && totaldata == "N/A" && expirydata == "N/A") retdata = "Not Available"; else retdata += "Stream Used: " + useddata + " Stream Total: " + totaldata + " Expiry Time: " + expirydata; return retdata; } int simpleGenerator() { //std::cerr<<"\nReading generator configuration...\n"; writeLog(0, "Reading generator configuration...", LOG_LEVEL_INFO); std::string config = fileGet("generate.ini"), path, profile, arguments, content; if(config.empty()) { //std::cerr<<"Generator configuration not found or empty!\n"; writeLog(0, "Generator configuration not found or empty!", LOG_LEVEL_ERROR); return -1; } INIReader ini; if(ini.Parse(config) != INIREADER_EXCEPTION_NONE) { //std::cerr<<"Generator configuration broken! Reason:"<