#include #include #include #include #include #include "config/binding.h" #include "generator/config/nodemanip.h" #include "generator/config/ruleconvert.h" #include "generator/config/subexport.h" #include "generator/template/templates.h" #include "script/script_quickjs.h" #include "server/webserver.h" #include "utils/base64/base64.h" #include "utils/file_extra.h" #include "utils/ini_reader/ini_reader.h" #include "utils/logger.h" #include "utils/network.h" #include "utils/regexp.h" #include "utils/stl_extra.h" #include "utils/string.h" #include "utils/string_hash.h" #include "utils/system.h" #include "utils/urlencode.h" #include "interfaces.h" #include "multithread.h" #include "settings.h" #include "upload.h" #include "webget.h" extern WebServer webServer; string_array gRegexBlacklist = {"(.*)*"}; 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; 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}, {"clash-verge","","","clash",true}, {"ClashX Pro","","","clash",true}, {"ClashX","\\/([0-9.]+)","0.13","clash",true}, {"Clash","","","clash",true}, {"Kitsunebi","","","v2ray"}, {"Loon","","","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) { std::istringstream src_stream(src_ver), target_stream(target_ver); int src_part, target_part; char dot; while (src_stream >> src_part) { if (target_stream >> target_part) { if (src_part < target_part) { return false; } else if (src_part > target_part) { return true; } // Skip the dot separator in both streams src_stream >> dot; target_stream >> dot; } else { // If we run out of target parts, the source version is greater only if it has more parts return true; } } // If we get here, the common parts are equal, so check if target_ver has more parts return !bool(target_stream >> target_part); } 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; } } } std::string getRuleset(RESPONSE_CALLBACK_ARGS) { auto &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 = urlSafeBase64Decode(getUrlArg(argument, "url")), type = getUrlArg(argument, "type"), group = urlSafeBase64Decode(getUrlArg(argument, "group")); std::string output_content, dummy; int type_int = to_int(type, 0); if(url.empty() || type.empty() || (type_int == 2 && group.empty()) || (type_int < 1 || type_int > 6)) { *status_code = 400; return "Invalid request!"; } std::string proxy = parseProxy(global.proxyRuleset); string_array vArray = split(url, "|"); for(std::string &x : vArray) x.insert(0, "ruleset,"); std::vector rca; RulesetConfigs confs = INIBinding::from::from_ini(vArray); refreshRulesets(confs, rca); for(RulesetContent &x : rca) { std::string content = x.rule_content.get(); output_content += convertRuleset(content, x.rule_type); } if(output_content.empty()) { *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 == std::string::npos) return 1; posb = pose + 1; pose = strLine.find(',', posb); if(pose == std::string::npos) { pose = strLine.size(); if(strLine[pose - 1] == '\r') pose--; } 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)) { if(strFind(strLine, "//")) { strLine.erase(strLine.find("//")); strLine = trimWhitespace(strLine); } switch(type_int) { case 2: if(!std::any_of(QuanXRuleTypes.begin(), QuanXRuleTypes.end(), [&strLine](const std::string& type){return startsWith(strLine, type);})) continue; break; case 1: if(!std::any_of(SurgeRuleTypes.begin(), SurgeRuleTypes.end(), [&strLine](const 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; if(strLine[posb - 2] == 'X') output_content += '.'; output_content += strLine.substr(posb, pose); output_content += '\n'; continue; case 6: if(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](const std::string& type){return startsWith(strLine, type);})) continue; output_content += " - "; default: break; } 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; } void checkExternalBase(const std::string &path, std::string &dest) { if(isLink(path) || (startsWith(path, global.basePath) && fileExist(path))) dest = path; } std::string subconverter(RESPONSE_CALLBACK_ARGS) { auto &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.empty() ? to_int(argSurgeVer, 3) : 3; if(argTarget == "auto") matchUserAgent(request.headers["User-Agent"], argTarget, argClashNewField, intSurgeVer); /// don't try to load groups or rulesets when generating simple subscriptions bool lSimpleSubscription = false; switch(hash_(argTarget)) { case "ss"_hash: case "ssd"_hash: case "ssr"_hash: case "sssub"_hash: case "v2ray"_hash: case "trojan"_hash: case "mixed"_hash: lSimpleSubscription = true; break; 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 "singbox"_hash: break; default: *status_code = 400; return "Invalid target!"; } //check if we need to read configuration if(global.reloadConfOnRequest && (!global.APIMode || global.CFWChildProcess) && !global.generatorMode) readConf(); /// string values std::string argUrl = getUrlArg(argument, "url"); std::string argGroupName = getUrlArg(argument, "group"), argUploadPath = getUrlArg(argument, "upload_path"); std::string argIncludeRemark = getUrlArg(argument, "include"), argExcludeRemark = getUrlArg(argument, "exclude"); std::string argCustomGroups = urlSafeBase64Decode(getUrlArg(argument, "groups")), argCustomRulesets = urlSafeBase64Decode(getUrlArg(argument, "ruleset")), argExternalConfig = 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 = getUrlArg(argument, "rename"), argFilterScript = getUrlArg(argument, "filter_script"); /// 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; ProxyGroupConfigs lCustomProxyGroups = global.customProxyGroups; RulesetConfigs lCustomRulesets = global.customRulesets; string_array lIncludeRemarks = global.includeRemarks, lExcludeRemarks = global.excludeRemarks; std::vector lRulesetContent; extra_settings ext; std::string subInfo, dummy; int interval = !argUpdateInterval.empty() ? to_int(argUpdateInterval, global.updateInterval) : global.updateInterval; bool authorized = !global.APIMode || getUrlArg(argument, "token") == global.accessToken, strict = !argUpdateStrict.empty() ? argUpdateStrict == "true" : global.updateStrict; 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 = global.clashBase, lSurgeBase = global.surgeBase, lMellowBase = global.mellowBase, lSurfboardBase = global.surfboardBase; std::string lQuanBase = global.quanBase, lQuanXBase = global.quanXBase, lLoonBase = global.loonBase, lSSSubBase = global.SSSubBase; std::string lSingBoxBase = global.singBoxBase; /// validate urls argEnableInsert.define(global.enableInsert); if(argUrl.empty() && (!global.APIMode || authorized)) argUrl = global.defaultUrls; if((argUrl.empty() && !(!global.insertUrls.empty() && argEnableInsert)) || argTarget.empty()) { *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); // } string_map req_arg_map; for (auto &x : argument) { if(x.first == "token") continue; req_arg_map[x.first] = x.second; } req_arg_map["target"] = argTarget; req_arg_map["ver"] = std::to_string(intSurgeVer); /// save template variables template_args tpl_args; tpl_args.global_vars = global.templateVars; tpl_args.request_params = req_arg_map; /// check for proxy settings std::string proxy = parseProxy(global.proxySubscription); /// check other flags ext.authorized = authorized; ext.append_proxy_type = argAppendType.get(global.appendType); if((argTarget == "clash" || argTarget == "clashr") && argGenClashScript.is_undef()) argExpandRulesets.define(true); ext.clash_proxies_style = global.clashProxiesStyle; ext.clash_proxy_groups_style = global.clashProxyGroupsStyle; /// read preference from argument, assign global var if not in argument ext.tfo.define(argTFO).define(global.TFOFlag); ext.udp.define(argUDP).define(global.UDPFlag); ext.skip_cert_verify.define(argSkipCertVerify).define(global.skipCertVerify); ext.tls13.define(argTLS13).define(global.TLS13Flag); ext.sort_flag = argSort.get(global.enableSort); argUseSortScript.define(!global.sortScript.empty()); if(ext.sort_flag && argUseSortScript) ext.sort_script = global.sortScript; ext.filter_deprecated = argFilterDeprecated.get(global.filterDeprecated); ext.clash_new_field_name = argClashNewField.get(global.clashUseNewField); 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 = global.surgeSSRPath; ext.quanx_dev_id = !argDeviceID.empty() ? argDeviceID : global.quanXDevID; ext.enable_rule_generator = global.enableRuleGen; ext.overwrite_original_rules = global.overwriteOriginalRules; if(!argExpandRulesets) ext.managed_config_prefix = global.managedConfigPrefix; /// load external configuration if(argExternalConfig.empty()) argExternalConfig = global.defaultExtConfig; if(!argExternalConfig.empty()) { //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.sssub_rule_base, lSSSubBase); if(!lSimpleSubscription) { 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.singbox_rule_base, lSingBoxBase); if(!extconf.surge_ruleset.empty()) lCustomRulesets = extconf.surge_ruleset; if(!extconf.custom_proxy_group.empty()) lCustomProxyGroups = extconf.custom_proxy_group; ext.enable_rule_generator = extconf.enable_rule_generator; ext.overwrite_original_rules = extconf.overwrite_original_rules; } } if(!extconf.rename.empty()) ext.rename_array = extconf.rename; if(!extconf.emoji.empty()) ext.emoji_array = extconf.emoji; if(!extconf.include.empty()) lIncludeRemarks = extconf.include; if(!extconf.exclude.empty()) lExcludeRemarks = extconf.exclude; argAddEmoji.define(extconf.add_emoji); argRemoveEmoji.define(extconf.remove_old_emoji); } } else { if(!lSimpleSubscription) { /// loading custom groups if(!argCustomGroups.empty() && !ext.nodelist) { string_array vArray = split(argCustomGroups, "@"); lCustomProxyGroups = INIBinding::from::from_ini(vArray); } /// loading custom rulesets if(!argCustomRulesets.empty() && !ext.nodelist) { string_array vArray = split(argCustomRulesets, "@"); lCustomRulesets = INIBinding::from::from_ini(vArray); } } } if(ext.enable_rule_generator && !ext.nodelist && !lSimpleSubscription) { if(lCustomRulesets != global.customRulesets) refreshRulesets(lCustomRulesets, lRulesetContent); else { if(global.updateRulesetOnRequest) refreshRulesets(global.customRulesets, global.rulesetsContent); lRulesetContent = global.rulesetsContent; } } if(!argEmoji.is_undef()) { argAddEmoji.set(argEmoji); argRemoveEmoji.set(true); } ext.add_emoji = argAddEmoji.get(global.addEmoji); ext.remove_emoji = argRemoveEmoji.get(global.removeEmoji); if(ext.add_emoji && ext.emoji_array.empty()) ext.emoji_array = safe_get_emojis(); if(!argRenames.empty()) ext.rename_array = INIBinding::from::from_ini(split(argRenames, "`"), "@"); else if(ext.rename_array.empty()) ext.rename_array = safe_get_renames(); /// check custom include/exclude settings if(!argIncludeRemark.empty() && regValid(argIncludeRemark)) lIncludeRemarks = string_array{argIncludeRemark}; if(!argExcludeRemark.empty() && regValid(argExcludeRemark)) lExcludeRemarks = string_array{argExcludeRemark}; /// initialize script runtime if(authorized && !global.scriptCleanContext) { ext.js_runtime = new qjs::Runtime(); script_runtime_init(*ext.js_runtime); ext.js_context = new qjs::Context(*ext.js_runtime); script_context_init(*ext.js_context); } //start parsing urls RegexMatchConfigs stream_temp = safe_get_streams(), time_temp = safe_get_times(); //loading urls string_array urls; std::vector nodes, insert_nodes; int groupID = 0; parse_settings parse_set; parse_set.proxy = &proxy; parse_set.exclude_remarks = &lExcludeRemarks; parse_set.include_remarks = &lIncludeRemarks; parse_set.stream_rules = &stream_temp; parse_set.time_rules = &time_temp; parse_set.sub_info = &subInfo; parse_set.authorized = authorized; parse_set.request_header = &request.headers; parse_set.js_runtime = ext.js_runtime; parse_set.js_context = ext.js_context; if(!global.insertUrls.empty() && argEnableInsert) { groupID = -1; urls = split(global.insertUrls, "|"); 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, parse_set) == -1) { if(global.skipFailedLinks) 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 '"<) ctx.eval("filter"); nodes.erase(std::remove_if(nodes.begin(), nodes.end(), filter), nodes.end()); } catch(qjs::exception) { script_print_stack(ctx); } }, global.scriptCleanContext); } //check custom group name if(!argGroupName.empty()) for(Proxy &x : nodes) x.Group = argGroupName; //do pre-process now preprocessNodes(nodes, ext); /* //insert node info to template int index = 0; std::string template_node_prefix; for(Proxy &x : nodes) { template_node_prefix = std::to_string(index) + "."; tpl_args.node_list[template_node_prefix + "remarks"] = x.remarks; tpl_args.node_list[template_node_prefix + "group"] = x.Group; tpl_args.node_list[template_node_prefix + "groupid"] = std::to_string(x.GroupId); index++; } */ ProxyGroupConfigs dummy_group; std::vector dummy_ruleset; std::string managed_url = base64Decode(getUrlArg(argument, "profile_data")); if(managed_url.empty()) managed_url = global.managedConfigPrefix + "/sub?" + joinArguments(argument); //std::cerr<<"Generate target: "; proxy = parseProxy(global.proxyConfig); 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; proxyToClash(nodes, yamlnode, dummy_group, argTarget == "clashr", ext); output_content = YAML::Dump(yamlnode); } else { if(render_template(fetchFile(lClashBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0) { *status_code = 400; return base_content; } output_content = proxyToClash(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 = proxyToSurge(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, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0) { *status_code = 400; return base_content; } output_content = proxyToSurge(nodes, base_content, lRulesetContent, lCustomProxyGroups, intSurgeVer, ext); if(argUpload) uploadGist("surge" + argSurgeVer, argUploadPath, output_content, true); if(global.writeManagedConfig && !global.managedConfigPrefix.empty()) 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, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0) { *status_code = 400; return base_content; } output_content = proxyToSurge(nodes, base_content, lRulesetContent, lCustomProxyGroups, -3, ext); if(argUpload) uploadGist("surfboard", argUploadPath, output_content, true); if(global.writeManagedConfig && !global.managedConfigPrefix.empty()) 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, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0) { *status_code = 400; return base_content; } output_content = proxyToMellow(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, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0) { *status_code = 400; return base_content; } output_content = proxyToSSSub(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 = proxyToSingle(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 = proxyToSingle(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 = proxyToSingle(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 = proxyToSingle(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 = proxyToSingle(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, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0) { *status_code = 400; return base_content; } } output_content = proxyToQuan(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, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0) { *status_code = 400; return base_content; } } output_content = proxyToQuanX(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, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0) { *status_code = 400; return base_content; } } output_content = proxyToLoon(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 = proxyToSSD(nodes, argGroupName, subInfo, ext); if(argUpload) uploadGist("ssd", argUploadPath, output_content, false); break; case "singbox"_hash: writeLog(0, "Generate target: sing-box", LOG_LEVEL_INFO); if(!ext.nodelist) { if(render_template(fetchFile(lSingBoxBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0) { *status_code = 400; return base_content; } } output_content = proxyToSingBox(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); if(argUpload) uploadGist("singbox", 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.empty()) response.headers.emplace("Content-Disposition", "attachment; filename=\"" + argFilename + "\"; filename*=utf-8''" + urlEncode(argFilename)); return output_content; } std::string simpleToClashR(RESPONSE_CALLBACK_ARGS) { auto argument = joinArguments(request.argument); int *status_code = &response.status_code; std::string url = argument.size() <= 8 ? "" : argument.substr(8); if(url.empty() || 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.emplace("target", "clashr"); request.argument.emplace("url", url); return subconverter(request, response); } std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) { auto argument = joinArguments(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 = global.clashUseNewField ? "proxy-groups" : "Proxy Group", rule_name = global.clashUseNewField ? "rules" : "Rule"; ini.store_any_line = true; if(url.empty()) url = global.defaultUrls; if(url.empty() || 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(global.proxyConfig); YAML::Node clash; template_args tpl_args; tpl_args.global_vars = global.templateVars; tpl_args.local_vars["clash.new_field_name"] = global.clashUseNewField ? "true" : "false"; tpl_args.request_params["target"] = "clash"; tpl_args.request_params["url"] = url; if(render_template(fetchFile(global.clashBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0) { *status_code = 400; return base_content; } clash = YAML::Load(base_content); base_content = fetchFile(url, proxy, global.cacheConfig); if(ini.parse(base_content) != INIREADER_EXCEPTION_NONE) { std::string errmsg = "Parsing Surge config failed! Reason: " + ini.get_last_error(); //std::cerr<= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored continue; else if(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](const 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](const std::string& type){return startsWith(strLine, type);})) continue; rule.push_back(x); } clash[rule_name] = rule; response.headers["profile-update-interval"] = std::to_string(global.updateInterval / 3600); writeLog(0, "Conversion completed.", LOG_LEVEL_INFO); return YAML::Dump(clash); } std::string getProfile(RESPONSE_CALLBACK_ARGS) { auto &argument = request.argument; int *status_code = &response.status_code; std::string name = getUrlArg(argument, "name"), token = getUrlArg(argument, "token"); string_array profiles = split(name, "|"); if(token.empty() || profiles.empty()) { *status_code = 403; return "Forbidden"; } std::string profile_content; name = profiles[0]; /*if(vfs::vfs_exist(name)) { profile_content = vfs::vfs_get(name); } else */ if(fileExist(name)) { profile_content = fileGet(name, true); } else { *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.parse(profile_content) != INIREADER_EXCEPTION_NONE && !ini.section_exist("Profile")) { //std::cerr<<"Load profile failed! Reason: "<second) { *status_code = 403; return "Forbidden"; } token = global.accessToken; } else { if(token != global.accessToken) { *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.parse_file(name) != INIREADER_EXCEPTION_NONE && !ini.section_exist("Profile")) { writeLog(0, "Ignoring broken profile '" + name + "'...", LOG_LEVEL_WARNING); continue; } url = ini.get("Profile", "url"); if(!url.empty()) { 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", base64Encode(global.managedConfigPrefix + "/getprofile?" + joinArguments(argument))); std::copy(argument.cbegin(), argument.cend(), std::inserter(contents, contents.end())); request.argument = contents; return subconverter(request, response); } /* std::string jinja2_webGet(const std::string &url) { std::string proxy = parseProxy(global.proxyConfig); writeLog(0, "Template called fetch with url '" + url + "'.", LOG_LEVEL_INFO); return webGet(url, proxy, global.cacheConfig); }*/ 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 {chrs}; } std::string subInfoToMessage(std::string subinfo) { using ull = unsigned long long; subinfo = replaceAllDistinct(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); auto 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, 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 '"<