#include #include #include #include #include #include #include "yamlcpp_extra.h" #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" #include "upload.h" #include "script_duktape.h" //common settings std::string gPrefPath = "pref.ini", gDefaultExtConfig; string_array gExcludeRemarks, gIncludeRemarks, gCustomRulesets, gStreamNodeRules, gTimeNodeRules; std::vector gRulesetContent; std::string gListenAddress = "127.0.0.1", gDefaultUrls, gInsertUrls, gManagedConfigPrefix; int gListenPort = 25500, gMaxPendingConns = 10, gMaxConcurThreads = 4; bool gPrependInsert = true, gSkipFailedLinks = false; bool gAPIMode = true, gWriteManagedConfig = false, gEnableRuleGen = true, gUpdateRulesetOnRequest = false, gOverwriteOriginalRules = true; bool gPrintDbgInfo = false, gCFWChildProcess = false, gAppendUserinfo = true, gAsyncFetchRuleset = false, gSurgeResolveHostname = true; std::string gAccessToken, gBasePath = "base"; extern std::string custom_group; extern int gLogLevel; extern long gMaxAllowedDownloadSize; string_map gAliases; extern bool gServeFile; extern std::string gServeFileRoot; //global variables for template std::string gTemplatePath = "templates"; string_map gTemplateVars; //generator settings bool gGeneratorMode = false; std::string gGenerateProfiles; //multi-thread lock std::mutex gMutexConfigure; //preferences string_array gRenames, gEmojis; bool gAddEmoji = false, gRemoveEmoji = false, gAppendType = false, gFilterDeprecated = true; tribool gUDP, gTFO, gSkipCertVerify, gTLS13, gEnableInsert; bool gEnableSort = false, gUpdateStrict = false; bool gClashUseNewField = false; std::string gClashProxiesStyle = "flow"; std::string gProxyConfig, gProxyRuleset, gProxySubscription; int gUpdateInterval = 0; std::string gSortScript, gFilterScript; std::string gClashBase; string_array gCustomProxyGroups; std::string gSurgeBase, gSurfboardBase, gMellowBase, gQuanBase, gQuanXBase, gLoonBase, gSSSubBase; std::string gSurgeSSRPath, gQuanXDevID; //cache system bool gServeCacheOnFetchFail = false; int gCacheSubscription = 60, gCacheConfig = 300, gCacheRuleset = 21600; //limits size_t gMaxAllowedRulesets = 64, gMaxAllowedRules = 32768; string_array gRegexBlacklist = {"(.*)*"}; void refreshRulesets(string_array &ruleset_list, std::vector &ruleset_content_array); std::string parseProxy(const std::string &source) { std::string proxy = source; if(source == "SYSTEM") proxy = getSystemProxy(); else if(source == "NONE") proxy = ""; return proxy; } extern string_array ClashRuleTypes, SurgeRuleTypes, QuanXRuleTypes; const std::map RulesetTypes = {{"clash-domain:", RULESET_CLASH_DOMAIN}, {"clash-ipcidr:", RULESET_CLASH_IPCIDR}, {"clash-classic:", RULESET_CLASH_CLASSICAL}, \ {"quanx:", RULESET_QUANX}, {"surge:", RULESET_SURGE}}; struct UAProfile { std::string head; std::string version_match; std::string version_target; std::string target; tribool clash_new_name = tribool(); int surge_ver = -1; }; const std::vector UAMatchList = { {"ClashForAndroid","\\/([0-9.]+)","2.0","clash",true}, {"ClashForAndroid","\\/([0-9.]+)R","","clashr",false}, {"ClashForAndroid","","","clash",false}, {"ClashforWindows","\\/([0-9.]+)","0.11","clash",true}, {"ClashforWindows","","","clash",false}, {"ClashX Pro","","","clash",true}, {"ClashX","\\/([0-9.]+)","0.13","clash",true}, {"Clash","","","clash",true}, {"Kitsunebi","","","v2ray"}, {"LoonWidget","","","loon"}, {"Pharos","","","mixed"}, {"Potatso","","","mixed"}, {"Quantumult%20X","","","quanx"}, {"Quantumult","","","quan"}, {"Qv2ray","","","v2ray"}, {"Shadowrocket","","","mixed"}, {"Surfboard","","","surfboard"}, {"Surge","\\/([0-9.]+).*x86","906","surge",false,4}, /// Surge for Mac (supports VMess) {"Surge","\\/([0-9.]+).*x86","368","surge",false,3}, /// Surge for Mac (supports new rule types and Shadowsocks without plugin) {"Surge","\\/([0-9.]+)","1419","surge",false,4}, /// Surge iOS 4 (first version) {"Surge","\\/([0-9.]+)","900","surge",false,3}, /// Surge iOS 3 (approx) {"Surge","","","surge",false,2}, /// any version of Surge as fallback {"Trojan-Qt5","","","trojan"}, {"V2rayU","","","v2ray"}, {"V2RayX","","","v2ray"} }; bool verGreaterEqual(const std::string &src_ver, const std::string &target_ver) { string_size src_pos_beg = 0, src_pos_end, target_pos_beg = 0, target_pos_end; while(true) { src_pos_end = src_ver.find('.', src_pos_beg); if(src_pos_end == src_ver.npos) src_pos_end = src_ver.size(); int part_src = std::stoi(src_ver.substr(src_pos_beg, src_pos_end - src_pos_beg)); target_pos_end = target_ver.find('.', target_pos_beg); if(target_pos_end == target_ver.npos) target_pos_end = target_ver.size(); int part_target = std::stoi(target_ver.substr(target_pos_beg, target_pos_end - target_pos_beg)); if(part_src > part_target) break; else if(part_src < part_target) return false; else if(src_pos_end >= src_ver.size() - 1 || target_pos_end >= target_ver.size() - 1) break; src_pos_beg = src_pos_end + 1; target_pos_beg = target_pos_end + 1; } return true; } void matchUserAgent(const std::string &user_agent, std::string &target, tribool &clash_new_name, int &surge_ver) { if(user_agent.empty()) return; for(const UAProfile &x : UAMatchList) { if(startsWith(user_agent, x.head)) { if(!x.version_match.empty()) { std::string version; if(regGetMatch(user_agent, x.version_match, 2, 0, &version)) continue; if(!x.version_target.empty() && !verGreaterEqual(version, x.version_target)) continue; } target = x.target; clash_new_name = x.clash_new_name; if(x.surge_ver != -1) surge_ver = x.surge_ver; return; } } return; } std::string convertRuleset(const std::string &content, int type) { /// Target: Surge type,pattern[,flag] /// Source: QuanX type,pattern[,group] /// Clash payload:\n - 'ipcidr/domain/classic(Surge-like)' std::string output, strLine; if(type == RULESET_SURGE) return content; if(startsWith(content, "payload:")) /// Clash { output = regReplace(regReplace(content, "payload:\\r?\\n", "", true), "\\s?^\\s*-\\s+('?)(.*)\\1$", "\n$2", true); if(type == RULESET_CLASH_CLASSICAL) /// classical type return output; std::stringstream ss; ss << output; char delimiter = getLineBreak(output); output.clear(); string_size pos, lineSize; while(getline(ss, strLine, delimiter)) { strLine = trim(strLine); lineSize = strLine.size(); if(lineSize && strLine[lineSize - 1] == '\r') //remove line break strLine.erase(--lineSize); if(!strLine.empty() && (strLine[0] != ';' && strLine[0] != '#' && !(lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/'))) { pos = strLine.find("/"); if(pos != strLine.npos) /// ipcidr { if(isIPv4(strLine.substr(0, pos))) output += "IP-CIDR,"; else output += "IP-CIDR6,"; } else { if(strLine[0] == '.' || (lineSize >= 2 && strLine[0] == '+' && strLine[1] == '.')) /// suffix { bool keyword_flag = false; while(endsWith(strLine, ".*")) { keyword_flag = true; strLine.erase(strLine.size() - 2); } output += "DOMAIN-"; if(keyword_flag) output += "KEYWORD,"; else output += "SUFFIX,"; strLine.erase(0, 2 - (strLine[0] == '.')); } else output += "DOMAIN,"; } } output += strLine; output += '\n'; } return output; } else /// QuanX { output = regReplace(regReplace(content, "^(?i:host)", "DOMAIN", true), "^(?i:ip6-cidr)", "IP-CIDR6", true); //translate type output = regReplace(output, "^((?i:DOMAIN(?:-(?:SUFFIX|KEYWORD))?|IP-CIDR6?|USER-AGENT),)\\s*?(\\S*?)(?:,(?!no-resolve).*?)(,no-resolve)?$", "\\U$1\\E$2${3:-}", true); //remove group return output; } } std::string getConvertedRuleset(RESPONSE_CALLBACK_ARGS) { std::string url = UrlDecode(getUrlArg(request.argument, "url")), type = getUrlArg(request.argument, "type"); return convertRuleset(fetchFile(url, parseProxy(gProxyRuleset), gCacheRuleset), to_int(type)); } std::string getRuleset(RESPONSE_CALLBACK_ARGS) { std::string &argument = request.argument; int *status_code = &response.status_code; /// type: 1 for Surge, 2 for Quantumult X, 3 for Clash domain rule-provider, 4 for Clash ipcidr rule-provider, 5 for Surge DOMAIN-SET, 6 for Clash classical ruleset 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; int type_int = to_int(type, 0); if(!url.size() || !type.size() || (type_int == 2 && !group.size()) || (type_int < 1 || type_int > 6)) { *status_code = 400; return "Invalid request!"; } std::string proxy = parseProxy(gProxyRuleset); string_array vArray = split(url, "|"); for(std::string &x : vArray) x.insert(0, "ruleset,"); std::vector rca; refreshRulesets(vArray, rca); for(ruleset_content &x : rca) { std::string content = x.rule_content.get(); output_content += convertRuleset(content, x.rule_type); } if(!output_content.size()) { *status_code = 400; return "Invalid request!"; } std::string strLine; std::stringstream ss; const std::string rule_match_regex = "^(.*?,.*?)(,.*)(,.*)$"; ss << output_content; char delimiter = getLineBreak(output_content); std::string::size_type lineSize, posb, pose; auto filterLine = [&]() { posb = 0; pose = strLine.find(','); if(pose == strLine.npos) return 1; posb = pose + 1; pose = strLine.find(',', posb); if(pose == strLine.npos) { pose = strLine.size(); if(strLine[pose - 1] == '\r') pose--; } else pose -= posb; return 0; }; lineSize = output_content.size(); output_content.clear(); output_content.reserve(lineSize); if(type_int == 3 || type_int == 4 || type_int == 6) output_content = "payload:\n"; while(getline(ss, strLine, delimiter)) { switch(type_int) { case 2: if(!std::any_of(QuanXRuleTypes.begin(), QuanXRuleTypes.end(), [&strLine](std::string type){return startsWith(strLine, type);})) continue; break; case 1: if(!std::any_of(SurgeRuleTypes.begin(), SurgeRuleTypes.end(), [&strLine](std::string type){return startsWith(strLine, type);})) continue; break; case 3: if(!startsWith(strLine, "DOMAIN-SUFFIX,") && !startsWith(strLine, "DOMAIN,")) continue; if(filterLine()) continue; output_content += " - '"; if(strLine[posb - 2] == 'X') output_content += "+."; output_content += strLine.substr(posb, pose); output_content += "'\n"; continue; case 4: if(!startsWith(strLine, "IP-CIDR,") && !startsWith(strLine, "IP-CIDR6,")) continue; if(filterLine()) continue; output_content += " - '"; output_content += strLine.substr(posb, pose); output_content += "'\n"; continue; case 5: if(!startsWith(strLine, "DOMAIN-SUFFIX,") && !startsWith(strLine, "DOMAIN,")) continue; if(filterLine()) continue; output_content += strLine.substr(posb, pose); output_content += '\n'; continue; case 6: if(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](std::string type){return startsWith(strLine, type);})) continue; output_content += " - "; } lineSize = strLine.size(); if(lineSize && strLine[lineSize - 1] == '\r') //remove line break strLine.erase(--lineSize); if(!strLine.empty() && (strLine[0] != ';' && strLine[0] != '#' && !(lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/'))) { if(type_int == 2) { if(startsWith(strLine, "IP-CIDR6")) strLine.replace(0, 8, "IP6-CIDR"); strLine += "," + group; if(count_least(strLine, ',', 3) && 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 += strLine; output_content += '\n'; } if(output_content == "payload:\n") { switch(type_int) { case 3: output_content += " - '--placeholder--'"; break; case 4: output_content += " - '0.0.0.0/32'"; break; case 6: output_content += " - 'DOMAIN,--placeholder--'"; break; } } return output_content; } int importItems(string_array &target, bool scope_limit = true) { string_array result; std::stringstream ss; std::string path, content, strLine; 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(gProxyConfig); if(fileExist(path)) content = fileGet(path, scope_limit); else if(isLink(path)) content = webGet(path, proxy, gCacheConfig); else writeLog(0, "File not found or not a valid URL: " + path, LOG_LEVEL_ERROR); if(!content.size()) return -1; ss << content; char delimiter = getLineBreak(content); std::string::size_type lineSize; while(getline(ss, strLine, delimiter)) { lineSize = strLine.size(); if(lineSize && strLine[lineSize - 1] == '\r') //remove line break strLine.erase(--lineSize); if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored continue; result.emplace_back(std::move(strLine)); itemCount++; } ss.clear(); } target.swap(result); writeLog(0, "Imported " + std::to_string(itemCount) + " item(s)."); return 0; } void readRegexMatch(YAML::Node node, const std::string &delimiter, string_array &dest, bool scope_limit = true) { YAML::Node object; std::string script, url, match, rep, strLine; for(unsigned i = 0; i < node.size(); i++) { object = node[i]; object["script"] >>= script; if(script.size()) { dest.emplace_back("!!script:" + script); continue; } object["import"] >>= url; if(url.size()) { dest.emplace_back("!!import:" + url); continue; } object["match"] >>= match; object["replace"] >>= rep; if(match.size() && rep.size()) strLine = match + delimiter + rep; else continue; dest.emplace_back(std::move(strLine)); } importItems(dest, scope_limit); } void readEmoji(YAML::Node node, string_array &dest, bool scope_limit = true) { YAML::Node object; std::string script, url, match, rep, strLine; for(unsigned i = 0; i < node.size(); i++) { object = node[i]; object["script"] >>= script; if(script.size()) { dest.emplace_back("!!script:" + script); continue; } 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(std::move(strLine)); } importItems(dest, scope_limit); } void readGroup(YAML::Node node, string_array &dest, bool scope_limit = true) { std::string strLine, name, type; string_array tempArray; YAML::Node object; unsigned int i, j; for(i = 0; i < node.size(); i++) { eraseElements(tempArray); object = node[i]; object["import"] >>= name; if(name.size()) { dest.emplace_back("!!import:" + name); continue; } std::string url = "http://www.gstatic.com/generate_204", interval = "300", tolerance, timeout; object["name"] >>= name; object["type"] >>= type; tempArray.emplace_back(std::move(name)); tempArray.emplace_back(type); object["url"] >>= url; object["interval"] >>= interval; object["tolerance"] >>= tolerance; object["timeout"] >>= timeout; for(j = 0; j < object["rule"].size(); j++) tempArray.emplace_back(safe_as(object["rule"][j])); switch(hash_(type)) { case "select"_hash: if(tempArray.size() < 3) continue; break; case "ssid"_hash: if(tempArray.size() < 4) continue; break; default: if(tempArray.size() < 3) continue; tempArray.emplace_back(std::move(url)); tempArray.emplace_back(interval + "," + timeout + "," + tolerance); } 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(std::move(strLine)); } importItems(dest, scope_limit); } void readRuleset(YAML::Node node, string_array &dest, bool scope_limit = true) { std::string strLine, name, url, group, interval; YAML::Node object; for(unsigned int i = 0; i < node.size(); i++) { object = node[i]; object["import"] >>= name; if(name.size()) { dest.emplace_back("!!import:" + name); continue; } object["ruleset"] >>= url; object["group"] >>= group; object["rule"] >>= name; object["interval"] >>= interval; if(url.size()) { strLine = group + "," + url; if(interval.size()) strLine += "," + interval; } else if(name.size()) strLine = group + ",[]" + name; else continue; dest.emplace_back(std::move(strLine)); } importItems(dest, scope_limit); } void refreshRulesets(string_array &ruleset_list, std::vector &ruleset_content_array) { eraseElements(ruleset_content_array); std::string rule_group, rule_url, rule_url_typed, interval; ruleset_content rc; std::string proxy = parseProxy(gProxyRuleset); for(std::string &x : ruleset_list) { string_size pos = x.find(","); if(pos == x.npos || pos == x.size() - 1) continue; rule_group = trim(x.substr(0, pos)); if(x.find("[]", pos + 1) == pos + 1) { rule_url = trim(x.substr(pos + 1)); writeLog(0, "Adding rule '" + rule_url.substr(2) + "," + rule_group + "'.", LOG_LEVEL_INFO); rc = {rule_group, "", "", RULESET_SURGE, std::async(std::launch::async, [rule_url](){return rule_url;}), 0}; } else { string_size pos2 = x.find(",", pos + 1); rule_url = trim(x.substr(pos + 1, pos2 - pos - 1)); if(pos2 != x.npos) interval = x.substr(pos2 + 1); else interval.clear(); ruleset_type type = RULESET_SURGE; rule_url_typed = rule_url; auto iter = std::find_if(RulesetTypes.begin(), RulesetTypes.end(), [rule_url](auto y){ return startsWith(rule_url, y.first); }); if(iter != RulesetTypes.end()) { rule_url.erase(0, iter->first.size()); type = iter->second; } writeLog(0, "Updating ruleset url '" + rule_url + "' with group '" + rule_group + "'.", LOG_LEVEL_INFO); rc = {rule_group, rule_url, rule_url_typed, type, fetchFileAsync(rule_url, proxy, gCacheRuleset, gAsyncFetchRuleset), to_int(interval, 0)}; } ruleset_content_array.emplace_back(std::move(rc)); } ruleset_content_array.shrink_to_fit(); } void readYAMLConf(YAML::Node &node) { YAML::Node section = node["common"]; std::string strLine; string_array tempArray; section["api_mode"] >> gAPIMode; section["api_access_token"] >> gAccessToken; 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); }); gDefaultUrls = strLine; eraseElements(tempArray); } } gEnableInsert = safe_as(section["enable_insert"]); 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); }); gInsertUrls = strLine; eraseElements(tempArray); } } section["prepend_insert_url"] >> gPrependInsert; if(section["exclude_remarks"].IsSequence()) section["exclude_remarks"] >> gExcludeRemarks; if(section["include_remarks"].IsSequence()) section["include_remarks"] >> gIncludeRemarks; gFilterScript = safe_as(section["enable_filter"]) ? safe_as(section["filter_script"]) : ""; if(startsWith(gFilterScript, "path:")) gFilterScript = fileGet(gFilterScript.substr(5), false); section["base_path"] >> gBasePath; section["clash_rule_base"] >> gClashBase; section["surge_rule_base"] >> gSurgeBase; section["surfboard_rule_base"] >> gSurfboardBase; section["mellow_rule_base"] >> gMellowBase; section["quan_rule_base"] >> gQuanBase; section["quanx_rule_base"] >> gQuanXBase; section["loon_rule_base"] >> gLoonBase; section["sssub_rule_base"] >> gSSSubBase; section["default_external_config"] >> gDefaultExtConfig; section["append_proxy_type"] >> gAppendType; section["proxy_config"] >> gProxyConfig; section["proxy_ruleset"] >> gProxyRuleset; section["proxy_subscription"] >> gProxySubscription; 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["skip_cert_verify_flag"] >> scv_flag; */ gUDP.set(safe_as(section["udp_flag"])); gTFO.set(safe_as(section["tcp_fast_open_flag"])); gSkipCertVerify.set(safe_as(section["skip_cert_verify_flag"])); gTLS13.set(safe_as(section["tls13_flag"])); section["sort_flag"] >> gEnableSort; section["sort_script"] >> gSortScript; section["filter_deprecated_nodes"] >> gFilterDeprecated; section["append_sub_userinfo"] >> gAppendUserinfo; section["clash_use_new_field_name"] >> gClashUseNewField; section["clash_proxies_style"] >> gClashProxiesStyle; } 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"] >> gWriteManagedConfig; section["managed_config_prefix"] >> gManagedConfigPrefix; section["config_update_interval"] >> gUpdateInterval; section["config_update_strict"] >> gUpdateStrict; section["quanx_device_id"] >> gQuanXDevID; } if(node["surge_external_proxy"].IsDefined()) { node["surge_external_proxy"]["surge_ssr_path"] >> gSurgeSSRPath; node["surge_external_proxy"]["resolve_hostname"] >> gSurgeResolveHostname; } if(node["emojis"].IsDefined()) { section = node["emojis"]; section["add_emoji"] >> gAddEmoji; section["remove_old_emoji"] >> gRemoveEmoji; if(section["rules"].IsSequence()) { readEmoji(section["rules"], tempArray, false); safe_set_emojis(tempArray); eraseElements(tempArray); } } const char *rulesets_title = node["rulesets"].IsDefined() ? "rulesets" : "ruleset"; if(node[rulesets_title].IsDefined()) { section = node[rulesets_title]; section["enabled"] >> gEnableRuleGen; if(!gEnableRuleGen) { gOverwriteOriginalRules = false; gUpdateRulesetOnRequest = false; } else { section["overwrite_original_rules"] >> gOverwriteOriginalRules; section["update_ruleset_on_request"] >> gUpdateRulesetOnRequest; } const char *ruleset_title = section["rulesets"].IsDefined() ? "rulesets" : "surge_ruleset"; if(section[ruleset_title].IsSequence()) readRuleset(section[ruleset_title], gCustomRulesets, false); } const char *groups_title = node["proxy_groups"].IsDefined() ? "proxy_groups" : "proxy_group"; if(node[groups_title].IsDefined() && node[groups_title]["custom_proxy_group"].IsDefined()) readGroup(node[groups_title]["custom_proxy_group"], gCustomProxyGroups, false); if(node["template"].IsDefined()) { node["template"]["template_path"] >> gTemplatePath; if(node["template"]["globals"].IsSequence()) { eraseElements(gTemplateVars); for(size_t i = 0; i < node["template"]["globals"].size(); i++) { std::string key, value; node["template"]["globals"][i]["key"] >> key; node["template"]["globals"][i]["value"] >> value; gTemplateVars[key] = value; } } } if(node["aliases"].IsSequence()) { reset_redirect(); for(size_t i = 0; i < node["aliases"].size(); i++) { std::string uri, target; node["aliases"][i]["uri"] >> uri; node["aliases"][i]["target"] >> target; append_redirect(uri, target); } } if(node["server"].IsDefined()) { node["server"]["listen"] >> gListenAddress; node["server"]["port"] >> gListenPort; node["server"]["serve_file_root"] >>= gServeFileRoot; gServeFile = !gServeFileRoot.empty(); } if(node["advanced"].IsDefined()) { std::string log_level; node["advanced"]["log_level"] >> log_level; node["advanced"]["print_debug_info"] >> gPrintDbgInfo; if(gPrintDbgInfo) gLogLevel = LOG_LEVEL_VERBOSE; else { switch(hash_(log_level)) { case "warn"_hash: gLogLevel = LOG_LEVEL_WARNING; break; case "error"_hash: gLogLevel = LOG_LEVEL_ERROR; break; case "fatal"_hash: gLogLevel = LOG_LEVEL_FATAL; break; case "verbose"_hash: gLogLevel = LOG_LEVEL_VERBOSE; break; case "debug"_hash: gLogLevel = LOG_LEVEL_DEBUG; break; default: gLogLevel = LOG_LEVEL_INFO; } } node["advanced"]["max_pending_connections"] >> gMaxPendingConns; node["advanced"]["max_concurrent_threads"] >> gMaxConcurThreads; node["advanced"]["max_allowed_rulesets"] >> gMaxAllowedRulesets; node["advanced"]["max_allowed_rules"] >> gMaxAllowedRules; node["advanced"]["max_allowed_download_size"] >> gMaxAllowedDownloadSize; if(node["advanced"]["enable_cache"].IsDefined()) { if(safe_as(node["advanced"]["enable_cache"])) { node["advanced"]["cache_subscription"] >> gCacheSubscription; node["advanced"]["cache_config"] >> gCacheConfig; node["advanced"]["cache_ruleset"] >> gCacheRuleset; node["advanced"]["serve_cache_on_fetch_fail"] >> gServeCacheOnFetchFail; } else gCacheSubscription = gCacheConfig = gCacheRuleset = 0; //disable cache } node["advanced"]["async_fetch_ruleset"] >> gAsyncFetchRuleset; node["advanced"]["skip_failed_links"] >> gSkipFailedLinks; } } void readConf() { guarded_mutex guard(gMutexConfigure); //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; const char *group_name = section["proxy_groups"].IsDefined() ? "proxy_groups" : "custom_proxy_group"; if(section[group_name].size()) readGroup(section[group_name], ext.custom_proxy_group, gAPIMode); const char *ruleset_name = section["rulesets"].IsDefined() ? "rulesets" : "surge_ruleset"; if(section[ruleset_name].size()) { readRuleset(section[ruleset_name], ext.surge_ruleset, gAPIMode); if(gMaxAllowedRulesets && ext.surge_ruleset.size() > gMaxAllowedRulesets) { writeLog(0, "Ruleset count in external config has exceeded limit.", LOG_LEVEL_WARNING); eraseElements(ext.surge_ruleset); return -1; } } if(section["rename_node"].size()) readRegexMatch(section["rename_node"], "@", ext.rename, gAPIMode); ext.add_emoji = safe_as(section["add_emoji"]); ext.remove_old_emoji = safe_as(section["remove_old_emoji"]); const char *emoji_name = section["emojis"].IsDefined() ? "emojis" : "emoji"; if(section[emoji_name].size()) readEmoji(section[emoji_name], ext.emoji, gAPIMode); section["include_remarks"] >> ext.include; section["exclude_remarks"] >> ext.exclude; 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.tpl_args->local_vars[key] = value; } } return 0; } int loadExternalConfig(std::string &path, ExternalConfig &ext) { std::string base_content, proxy = parseProxy(gProxyConfig), config = fetchFile(path, proxy, gCacheConfig); if(render_template(config, *ext.tpl_args, base_content, gTemplatePath) != 0) base_content = config; try { YAML::Node yaml = YAML::Load(base_content); if(yaml.size() && yaml["custom"].IsDefined()) 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: "< gMaxAllowedRulesets) { writeLog(0, "Ruleset count in external config has exceeded limit. ", LOG_LEVEL_WARNING); eraseElements(ext.surge_ruleset); return -1; } } ini.GetIfExist("clash_rule_base", ext.clash_rule_base); ini.GetIfExist("surge_rule_base", ext.surge_rule_base); ini.GetIfExist("surfboard_rule_base", ext.surfboard_rule_base); ini.GetIfExist("mellow_rule_base", ext.mellow_rule_base); ini.GetIfExist("quan_rule_base", ext.quan_rule_base); ini.GetIfExist("quanx_rule_base", ext.quanx_rule_base); ini.GetIfExist("loon_rule_base", ext.loon_rule_base); ini.GetIfExist("sssub_rule_base", ext.sssub_rule_base); ini.GetBoolIfExist("overwrite_original_rules", ext.overwrite_original_rules); ini.GetBoolIfExist("enable_rule_generator", ext.enable_rule_generator); if(ini.ItemPrefixExist("rename")) { ini.GetAll("rename", ext.rename); importItems(ext.rename, gAPIMode); } ext.add_emoji = ini.Get("add_emoji"); ext.remove_old_emoji = ini.Get("remove_old_emoji"); if(ini.ItemPrefixExist("emoji")) { ini.GetAll("emoji", ext.emoji); importItems(ext.emoji, gAPIMode); } if(ini.ItemPrefixExist("include_remarks")) ini.GetAll("include_remarks", ext.include); if(ini.ItemPrefixExist("exclude_remarks")) ini.GetAll("exclude_remarks", ext.exclude); if(ini.SectionExist("template") && ext.tpl_args != NULL) { ini.EnterSection("template"); string_multimap tempmap; ini.GetItems(tempmap); for(auto &x : tempmap) ext.tpl_args->local_vars[x.first] = x.second; } return 0; } void checkExternalBase(const std::string &path, std::string &dest) { if(isLink(path) || (startsWith(path, gBasePath) && fileExist(path))) dest = path; } std::string subconverter(RESPONSE_CALLBACK_ARGS) { std::string &argument = request.argument; int *status_code = &response.status_code; std::string argTarget = getUrlArg(argument, "target"), argSurgeVer = getUrlArg(argument, "ver"); tribool argClashNewField = getUrlArg(argument, "new_name"); int intSurgeVer = argSurgeVer.size() ? to_int(argSurgeVer, 3) : 3; if(argTarget == "auto") matchUserAgent(request.headers["User-Agent"], argTarget, argClashNewField, intSurgeVer); switch(hash_(argTarget)) { case "clash"_hash: case "clashr"_hash: case "surge"_hash: case "quan"_hash: case "quanx"_hash: case "loon"_hash: case "surfboard"_hash: case "mellow"_hash: case "ss"_hash: case "ssd"_hash: case "ssr"_hash: case "sssub"_hash: case "v2ray"_hash: case "trojan"_hash: case "mixed"_hash: break; default: *status_code = 400; return "Invalid target!"; } //check if we need to read configuration if((!gAPIMode || gCFWChildProcess) && !gGeneratorMode) readConf(); /// string values std::string argUrl = UrlDecode(getUrlArg(argument, "url")); std::string argGroupName = UrlDecode(getUrlArg(argument, "group")), argUploadPath = getUrlArg(argument, "upload_path"); std::string argIncludeRemark = UrlDecode(getUrlArg(argument, "include")), argExcludeRemark = UrlDecode(getUrlArg(argument, "exclude")); std::string argCustomGroups = urlsafe_base64_decode(getUrlArg(argument, "groups")), argCustomRulesets = urlsafe_base64_decode(getUrlArg(argument, "ruleset")), argExternalConfig = UrlDecode(getUrlArg(argument, "config")); std::string argDeviceID = getUrlArg(argument, "dev_id"), argFilename = getUrlArg(argument, "filename"), argUpdateInterval = getUrlArg(argument, "interval"), argUpdateStrict = getUrlArg(argument, "strict"); std::string argRenames = UrlDecode(getUrlArg(argument, "rename")); /// switches with default value tribool argUpload = getUrlArg(argument, "upload"), argEmoji = getUrlArg(argument, "emoji"), argAddEmoji = getUrlArg(argument, "add_emoji"), argRemoveEmoji = getUrlArg(argument, "remove_emoji"); tribool argAppendType = getUrlArg(argument, "append_type"), argTFO = getUrlArg(argument, "tfo"), argUDP = getUrlArg(argument, "udp"), argGenNodeList = getUrlArg(argument, "list"); tribool argSort = getUrlArg(argument, "sort"), argUseSortScript = getUrlArg(argument, "sort_script"); tribool argGenClashScript = getUrlArg(argument, "script"), argEnableInsert = getUrlArg(argument, "insert"); tribool argSkipCertVerify = getUrlArg(argument, "scv"), argFilterDeprecated = getUrlArg(argument, "fdn"), argExpandRulesets = getUrlArg(argument, "expand"), argAppendUserinfo = getUrlArg(argument, "append_info"); tribool argPrependInsert = getUrlArg(argument, "prepend"), argGenClassicalRuleProvider = getUrlArg(argument, "classic"), argTLS13 = getUrlArg(argument, "tls13"); std::string base_content, output_content; string_array lCustomProxyGroups = gCustomProxyGroups, lCustomRulesets = gCustomRulesets, lIncludeRemarks = gIncludeRemarks, lExcludeRemarks = gExcludeRemarks; std::vector lRulesetContent; extra_settings ext; std::string subInfo, dummy; int interval = argUpdateInterval.size() ? to_int(argUpdateInterval, gUpdateInterval) : gUpdateInterval; bool authorized = !gAPIMode || getUrlArg(argument, "token") == gAccessToken, strict = argUpdateStrict.size() ? argUpdateStrict == "true" : gUpdateStrict; if(std::find(gRegexBlacklist.cbegin(), gRegexBlacklist.cend(), argIncludeRemark) != gRegexBlacklist.cend() || std::find(gRegexBlacklist.cbegin(), gRegexBlacklist.cend(), argExcludeRemark) != gRegexBlacklist.cend()) return "Invalid request!"; //for external configuration std::string lClashBase = gClashBase, lSurgeBase = gSurgeBase, lMellowBase = gMellowBase, lSurfboardBase = gSurfboardBase; std::string lQuanBase = gQuanBase, lQuanXBase = gQuanXBase, lLoonBase = gLoonBase, lSSSubBase = gSSSubBase; //validate urls argEnableInsert.define(gEnableInsert); if(!argUrl.size() && (!gAPIMode || authorized)) argUrl = gDefaultUrls; if((!argUrl.size() && !(gInsertUrls.size() && argEnableInsert)) || !argTarget.size()) { *status_code = 400; return "Invalid request!"; } //load request arguments as template variables string_array req_args = split(argument, "&"); string_map req_arg_map; for(std::string &x : req_args) { string_size pos = x.find("="); if(pos == x.npos) { req_arg_map[x] = ""; continue; } if(x.substr(0, pos) == "token") continue; req_arg_map[x.substr(0, pos)] = x.substr(pos + 1); } //save template variables template_args tpl_args; tpl_args.global_vars = gTemplateVars; tpl_args.request_params = req_arg_map; //check for proxy settings std::string proxy = parseProxy(gProxySubscription); //check other flags ext.append_proxy_type = argAppendType.get(gAppendType); if((argTarget == "clash" || argTarget == "clashr") && argGenClashScript.is_undef()) argExpandRulesets.define(true); ext.clash_proxies_style = gClashProxiesStyle; /// read preference from argument, assign global var if not in argument ext.tfo.parse(argTFO).parse(gTFO); ext.udp.parse(argUDP).parse(gUDP); ext.skip_cert_verify.parse(argSkipCertVerify).parse(gSkipCertVerify); ext.tls13.parse(argTLS13).parse(gTLS13); ext.sort_flag = argSort.get(gEnableSort); argUseSortScript.define(gSortScript.size() != 0); if(ext.sort_flag && argUseSortScript) ext.sort_script = gSortScript; ext.filter_deprecated = argFilterDeprecated.get(gFilterDeprecated); ext.clash_new_field_name = argClashNewField.get(gClashUseNewField); ext.clash_script = argGenClashScript.get(); ext.clash_classical_ruleset = argGenClassicalRuleProvider.get(); if(!argExpandRulesets) ext.clash_new_field_name = true; else ext.clash_script = false; ext.nodelist = argGenNodeList; ext.surge_ssr_path = gSurgeSSRPath; ext.quanx_dev_id = argDeviceID.size() ? argDeviceID : gQuanXDevID; ext.enable_rule_generator = gEnableRuleGen; ext.overwrite_original_rules = gOverwriteOriginalRules; if(!argExpandRulesets) ext.managed_config_prefix = gManagedConfigPrefix; //load external configuration if(argExternalConfig.empty()) argExternalConfig = gDefaultExtConfig; if(argExternalConfig.size()) { //std::cerr<<"External configuration file provided. Loading...\n"; writeLog(0, "External configuration file provided. Loading...", LOG_LEVEL_INFO); ExternalConfig extconf; extconf.tpl_args = &tpl_args; if(loadExternalConfig(argExternalConfig, extconf) == 0) { if(!ext.nodelist) { checkExternalBase(extconf.clash_rule_base, lClashBase); checkExternalBase(extconf.surge_rule_base, lSurgeBase); checkExternalBase(extconf.surfboard_rule_base, lSurfboardBase); checkExternalBase(extconf.mellow_rule_base, lMellowBase); checkExternalBase(extconf.quan_rule_base, lQuanBase); checkExternalBase(extconf.quanx_rule_base, lQuanXBase); checkExternalBase(extconf.loon_rule_base, lLoonBase); checkExternalBase(extconf.sssub_rule_base, lSSSubBase); if(extconf.surge_ruleset.size()) lCustomRulesets = extconf.surge_ruleset; if(extconf.custom_proxy_group.size()) lCustomProxyGroups = extconf.custom_proxy_group; ext.enable_rule_generator = extconf.enable_rule_generator; ext.overwrite_original_rules = extconf.overwrite_original_rules; } if(extconf.rename.size()) ext.rename_array = extconf.rename; if(extconf.emoji.size()) ext.emoji_array = extconf.emoji; if(extconf.include.size()) lIncludeRemarks = extconf.include; if(extconf.exclude.size()) lExcludeRemarks = extconf.exclude; argAddEmoji.define(extconf.add_emoji); argRemoveEmoji.define(extconf.remove_old_emoji); } } else { //loading custom groups if(argCustomGroups.size() && !ext.nodelist) lCustomProxyGroups = split(argCustomGroups, "@"); //loading custom rulesets if(argCustomRulesets.size() && !ext.nodelist) lCustomRulesets = split(argCustomRulesets, "@"); } if(ext.enable_rule_generator && !ext.nodelist) { if(lCustomRulesets != gCustomRulesets) refreshRulesets(lCustomRulesets, lRulesetContent); else { if(gUpdateRulesetOnRequest) refreshRulesets(gCustomRulesets, gRulesetContent); lRulesetContent = gRulesetContent; } } if(!argEmoji.is_undef()) { argAddEmoji.set(argEmoji); argRemoveEmoji.set(true); } ext.add_emoji = argAddEmoji.get(gAddEmoji); ext.remove_emoji = argRemoveEmoji.get(gRemoveEmoji); if(ext.add_emoji && ext.emoji_array.empty()) ext.emoji_array = safe_get_emojis(); if(argRenames.size()) ext.rename_array = split(argRenames, "`"); else if(ext.rename_array.empty()) ext.rename_array = safe_get_renames(); //check custom include/exclude settings if(argIncludeRemark.size() && regValid(argIncludeRemark)) lIncludeRemarks = string_array{argIncludeRemark}; if(argExcludeRemark.size() && regValid(argExcludeRemark)) lExcludeRemarks = string_array{argExcludeRemark}; //start parsing urls string_array stream_temp = safe_get_streams(), time_temp = safe_get_times(); //loading urls string_array urls; std::vector nodes, insert_nodes; int groupID = 0; if(gInsertUrls.size() && argEnableInsert) { groupID = -1; urls = split(gInsertUrls, "|"); importItems(urls, true); for(std::string &x : urls) { x = regTrim(x); writeLog(0, "Fetching node data from url '" + x + "'.", LOG_LEVEL_INFO); if(addNodes(x, insert_nodes, groupID, proxy, lExcludeRemarks, lIncludeRemarks, stream_temp, time_temp, subInfo, authorized, request.headers) == -1) { if(gSkipFailedLinks) writeLog(0, "The following link doesn't contain any valid node info: " + x, LOG_LEVEL_WARNING); else { *status_code = 400; return "The following link doesn't contain any valid node info: " + x; } } groupID--; } } urls = split(argUrl, "|"); importItems(urls, true); groupID = 0; for(std::string &x : urls) { x = regTrim(x); //std::cerr<<"Fetching node data from url '"< dummy_ruleset; std::string managed_url = base64_decode(UrlDecode(getUrlArg(argument, "profile_data"))); if(managed_url.empty()) managed_url = gManagedConfigPrefix + "/sub?" + argument; //std::cerr<<"Generate target: "; proxy = parseProxy(gProxyConfig); switch(hash_(argTarget)) { case "clash"_hash: case "clashr"_hash: writeLog(0, argTarget == "clashr" ? "Generate target: ClashR" : "Generate target: Clash", LOG_LEVEL_INFO); tpl_args.local_vars["clash.new_field_name"] = ext.clash_new_field_name ? "true" : "false"; response.headers["profile-update-interval"] = std::to_string(interval / 3600); if(ext.nodelist) { YAML::Node yamlnode; netchToClash(nodes, yamlnode, dummy_group, argTarget == "clashr", ext); output_content = YAML::Dump(yamlnode); } else { if(render_template(fetchFile(lClashBase, proxy, gCacheConfig), tpl_args, base_content, gTemplatePath) != 0) { *status_code = 400; return base_content; } output_content = netchToClash(nodes, base_content, lRulesetContent, lCustomProxyGroups, argTarget == "clashr", ext); } if(argUpload) uploadGist(argTarget, argUploadPath, output_content, false); break; case "surge"_hash: writeLog(0, "Generate target: Surge " + std::to_string(intSurgeVer), LOG_LEVEL_INFO); if(ext.nodelist) { output_content = netchToSurge(nodes, base_content, dummy_ruleset, dummy_group, intSurgeVer, ext); if(argUpload) uploadGist("surge" + argSurgeVer + "list", argUploadPath, output_content, true); } else { if(render_template(fetchFile(lSurgeBase, proxy, gCacheConfig), tpl_args, base_content, gTemplatePath) != 0) { *status_code = 400; return base_content; } output_content = netchToSurge(nodes, base_content, lRulesetContent, lCustomProxyGroups, intSurgeVer, ext); if(argUpload) uploadGist("surge" + argSurgeVer, argUploadPath, output_content, true); if(gWriteManagedConfig && gManagedConfigPrefix.size()) output_content = "#!MANAGED-CONFIG " + managed_url + (interval ? " interval=" + std::to_string(interval) : "") \ + " strict=" + std::string(strict ? "true" : "false") + "\n\n" + output_content; } break; case "surfboard"_hash: writeLog(0, "Generate target: Surfboard", LOG_LEVEL_INFO); if(render_template(fetchFile(lSurfboardBase, proxy, gCacheConfig), tpl_args, base_content, gTemplatePath) != 0) { *status_code = 400; return base_content; } output_content = netchToSurge(nodes, base_content, lRulesetContent, lCustomProxyGroups, -3, ext); if(argUpload) uploadGist("surfboard", argUploadPath, output_content, true); if(gWriteManagedConfig && gManagedConfigPrefix.size()) 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: writeLog(0, "Generate target: Mellow", LOG_LEVEL_INFO); if(render_template(fetchFile(lMellowBase, proxy, gCacheConfig), tpl_args, base_content, gTemplatePath) != 0) { *status_code = 400; return base_content; } output_content = netchToMellow(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); if(argUpload) uploadGist("mellow", argUploadPath, output_content, true); break; case "sssub"_hash: writeLog(0, "Generate target: SS Subscription", LOG_LEVEL_INFO); if(render_template(fetchFile(lSSSubBase, proxy, gCacheConfig), tpl_args, base_content, gTemplatePath) != 0) { *status_code = 400; return base_content; } output_content = netchToSSSub(base_content, nodes, ext); if(argUpload) uploadGist("sssub", argUploadPath, output_content, false); break; case "ss"_hash: writeLog(0, "Generate target: SS", LOG_LEVEL_INFO); output_content = netchToSingle(nodes, 1, ext); if(argUpload) uploadGist("ss", argUploadPath, output_content, false); break; case "ssr"_hash: writeLog(0, "Generate target: SSR", LOG_LEVEL_INFO); output_content = netchToSingle(nodes, 2, ext); if(argUpload) uploadGist("ssr", argUploadPath, output_content, false); break; case "v2ray"_hash: writeLog(0, "Generate target: v2rayN", LOG_LEVEL_INFO); output_content = netchToSingle(nodes, 4, ext); if(argUpload) uploadGist("v2ray", argUploadPath, output_content, false); break; case "trojan"_hash: writeLog(0, "Generate target: Trojan", LOG_LEVEL_INFO); output_content = netchToSingle(nodes, 8, ext); if(argUpload) uploadGist("trojan", argUploadPath, output_content, false); break; case "mixed"_hash: writeLog(0, "Generate target: Standard Subscription", LOG_LEVEL_INFO); output_content = netchToSingle(nodes, 15, ext); if(argUpload) uploadGist("sub", argUploadPath, output_content, false); break; case "quan"_hash: writeLog(0, "Generate target: Quantumult", LOG_LEVEL_INFO); if(!ext.nodelist) { if(render_template(fetchFile(lQuanBase, proxy, gCacheConfig), tpl_args, base_content, gTemplatePath) != 0) { *status_code = 400; return base_content; } } output_content = netchToQuan(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); if(argUpload) uploadGist("quan", argUploadPath, output_content, false); break; case "quanx"_hash: writeLog(0, "Generate target: Quantumult X", LOG_LEVEL_INFO); if(!ext.nodelist) { if(render_template(fetchFile(lQuanXBase, proxy, gCacheConfig), tpl_args, base_content, gTemplatePath) != 0) { *status_code = 400; return base_content; } } output_content = netchToQuanX(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); if(argUpload) uploadGist("quanx", argUploadPath, output_content, false); break; case "loon"_hash: writeLog(0, "Generate target: Loon", LOG_LEVEL_INFO); if(!ext.nodelist) { if(render_template(fetchFile(lLoonBase, proxy, gCacheConfig), tpl_args, base_content, gTemplatePath) != 0) { *status_code = 400; return base_content; } } output_content = netchToLoon(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); if(argUpload) uploadGist("loon", argUploadPath, output_content, false); break; case "ssd"_hash: writeLog(0, "Generate target: SSD", LOG_LEVEL_INFO); output_content = netchToSSD(nodes, argGroupName, subInfo, ext); if(argUpload) uploadGist("ssd", argUploadPath, output_content, false); break; default: writeLog(0, "Generate target: Unspecified", LOG_LEVEL_INFO); *status_code = 500; return "Unrecognized target"; } writeLog(0, "Generate completed.", LOG_LEVEL_INFO); if(argFilename.size()) response.headers.emplace("Content-Disposition", "attachment; filename=\"" + argFilename + "\""); return output_content; } std::string simpleToClashR(RESPONSE_CALLBACK_ARGS) { std::string &argument = request.argument; int *status_code = &response.status_code; std::string url = argument.size() <= 8 ? "" : argument.substr(8); 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."; } request.argument = "target=clashr&url=" + UrlEncode(url); return subconverter(request, response); } std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) { std::string &argument = request.argument; int *status_code = &response.status_code; INIReader ini; string_array dummy_str_array; std::vector nodes; std::string base_content, url = argument.size() <= 5 ? "" : argument.substr(5); const std::string proxygroup_name = gClashUseNewField ? "proxy-groups" : "Proxy Group", rule_name = gClashUseNewField ? "rules" : "Rule"; ini.store_any_line = true; if(!url.size()) url = gDefaultUrls; 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."; } writeLog(0, "SurgeConfToClash called with url '" + url + "'.", LOG_LEVEL_INFO); std::string proxy = parseProxy(gProxyConfig); YAML::Node clash; template_args tpl_args; tpl_args.global_vars = gTemplateVars; tpl_args.local_vars["clash.new_field_name"] = gClashUseNewField ? "true" : "false"; tpl_args.request_params["target"] = "clash"; tpl_args.request_params["url"] = url; if(render_template(fetchFile(gClashBase, proxy, gCacheConfig), tpl_args, base_content, gTemplatePath) != 0) { *status_code = 400; return base_content; } clash = YAML::Load(base_content); base_content = fetchFile(url, proxy, gCacheConfig); 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(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](std::string type){return startsWith(strLine, type);})) //remove unsupported types continue; strLine += strArray[2]; if(count_least(strLine, ',', 3)) strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2"); rule.push_back(strLine); } ss.clear(); continue; } else if(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](std::string type){return startsWith(strLine, type);})) continue; rule.push_back(x); } clash[rule_name] = rule; response.headers["profile-update-interval"] = std::to_string(gUpdateInterval / 3600); writeLog(0, "Conversion completed.", LOG_LEVEL_INFO); return YAML::Dump(clash); } std::string getProfile(RESPONSE_CALLBACK_ARGS) { std::string &argument = request.argument; int *status_code = &response.status_code; std::string name = UrlDecode(getUrlArg(argument, "name")), token = UrlDecode(getUrlArg(argument, "token")); string_array profiles = split(name, "|"); name = profiles[0]; 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 = gAccessToken; } else { if(token != gAccessToken) { *status_code = 403; return "Forbidden"; } } /// check if more than one profile is provided if(profiles.size() > 1) { writeLog(0, "Multiple profiles are provided. Trying to combine profiles...", LOG_TYPE_INFO); std::string all_urls, url; auto iter = contents.find("url"); if(iter != contents.end()) all_urls = iter->second; for(size_t i = 1; i < profiles.size(); i++) { name = profiles[i]; if(!fileExist(name)) { writeLog(0, "Ignoring non-exist profile '" + name + "'...", LOG_LEVEL_WARNING); continue; } if(ini.ParseFile(name) != INIREADER_EXCEPTION_NONE && !ini.SectionExist("Profile")) { writeLog(0, "Ignoring broken profile '" + name + "'...", LOG_LEVEL_WARNING); continue; } url = ini.Get("Profile", "url"); if(url.size()) { all_urls += "|" + url; writeLog(0, "Profile url from '" + name + "' added.", LOG_LEVEL_INFO); } else { writeLog(0, "Profile '" + name + "' does not have url key. Skipping...", LOG_LEVEL_INFO); } } iter->second = all_urls; } contents.emplace("token", token); contents.emplace("profile_data", base64_encode(gManagedConfigPrefix + "/getprofile?" + argument)); std::string query = std::accumulate(contents.begin(), contents.end(), std::string(), [](const std::string &x, auto y){ return x + y.first + "=" + UrlEncode(y.second) + "&"; }); query += argument; request.argument = query; return subconverter(request, response); } std::string getScript(RESPONSE_CALLBACK_ARGS) { std::string &argument = request.argument; std::string url = urlsafe_base64_decode(getUrlArg(argument, "url")), dev_id = getUrlArg(argument, "id"); std::string output_content; std::string proxy = parseProxy(gProxyConfig); output_content = fetchFile(url, proxy, gCacheConfig); if(!dev_id.size()) dev_id = gQuanXDevID; 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 &argument = request.argument; std::string url = urlsafe_base64_decode(getUrlArg(argument, "url")), dev_id = getUrlArg(argument, "id"); std::string output_content; std::string proxy = parseProxy(gProxyConfig); output_content = fetchFile(url, proxy, gCacheConfig); if(!dev_id.size()) dev_id = gQuanXDevID; if(dev_id.size()) { std::stringstream ss; std::string strLine; const std::string pattern = "^(.*? url script-.*? )(.*?)$"; string_size lineSize; char delimiter = getLineBreak(output_content); 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); if(!strLine.empty() && regMatch(strLine, pattern)) { url = gManagedConfigPrefix + "/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(), hostname; const std::string matcher = R"(^(?i:hostname\s*?=\s*?)(.*?)\s$)"; string_array urls = split(data, ","); if(!urls.size()) return std::string(); std::string input_content, output_content, proxy = parseProxy(gProxyConfig); for(std::string &x : urls) { input_content = webGet(x, proxy, gCacheConfig); regGetMatch(input_content, matcher, 2, 0, &hostname); if(hostname.size()) { output_content += hostname + ","; hostname.clear(); } } 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(gProxyConfig); writeLog(0, "Template called fetch with url '" + data + "'.", LOG_LEVEL_INFO); return webGet(data, proxy, gCacheConfig); } std::string jinja2_webGet(const std::string &url) { std::string proxy = parseProxy(gProxyConfig); writeLog(0, "Template called fetch with url '" + url + "'.", LOG_LEVEL_INFO); return webGet(url, proxy, gCacheConfig); } 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:"<second), LOG_LEVEL_INFO); //std::cerr<<"Artifact '"<