#include #include #include #include "../config/binding.h" #include "../handler/webget.h" #include "../script/cron.h" #include "../server/webserver.h" #include "../utils/logger.h" #include "../utils/network.h" #include "interfaces.h" #include "multithread.h" #include "settings.h" //multi-thread lock std::mutex gMutexConfigure; Settings global; extern WebServer webServer; 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}}; int importItems(string_array &target, bool scope_limit) { string_array result; std::stringstream ss; std::string path, content, strLine; unsigned int itemCount = 0; for(std::string &x : target) { if(x.find("!!import:") == std::string::npos) { result.emplace_back(x); continue; } path = x.substr(x.find(":") + 1); writeLog(0, "Trying to import items from " + path); std::string proxy = parseProxy(global.proxyConfig); if(fileExist(path)) content = fileGet(path, scope_limit); else if(isLink(path)) content = webGet(path, proxy, global.cacheConfig); else writeLog(0, "File not found or not a valid URL: " + path, LOG_LEVEL_ERROR); if(content.empty()) 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; } toml::value parseToml(const std::string &content, const std::string &fname) { std::istringstream is(content); return toml::parse(is, fname); } void importItems(std::vector &root, const std::string &import_key, bool scope_limit = true) { std::string content; std::vector newRoot; auto iter = root.begin(); size_t count = 0; std::string proxy = parseProxy(global.proxyConfig); while(iter != root.end()) { auto& table = iter->as_table(); if(table.find("import") == table.end()) newRoot.emplace_back(std::move(*iter)); else { const std::string &path = toml::get(table.at("import")); writeLog(0, "Trying to import items from " + path); if(fileExist(path)) content = fileGet(path, scope_limit); else if(isLink(path)) content = webGet(path, proxy, global.cacheConfig); else writeLog(0, "File not found or not a valid URL: " + path, LOG_LEVEL_ERROR); if(!content.empty()) { auto items = parseToml(content, path); auto list = toml::find>(items, import_key); count += list.size(); std::move(list.begin(), list.end(), std::back_inserter(newRoot)); } } iter++; } root.swap(newRoot); writeLog(0, "Imported " + std::to_string(count) + " item(s)."); } void readRegexMatch(YAML::Node node, const std::string &delimiter, string_array &dest, bool scope_limit = true) { for(auto && object : node) { std::string script, url, match, rep, strLine; object["script"] >>= script; if(!script.empty()) { dest.emplace_back("!!script:" + script); continue; } object["import"] >>= url; if(!url.empty()) { dest.emplace_back("!!import:" + url); continue; } object["match"] >>= match; object["replace"] >>= rep; if(!match.empty() && !rep.empty()) 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) { for(auto && object : node) { std::string script, url, match, rep, strLine; object["script"] >>= script; if(!script.empty()) { dest.emplace_back("!!script:" + script); continue; } object["import"] >>= url; if(!url.empty()) { url = "!!import:" + url; dest.emplace_back(url); continue; } object["match"] >>= match; object["emoji"] >>= rep; if(!match.empty() && !rep.empty()) 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) { for(YAML::Node && object : node) { string_array tempArray; std::string name, type; object["import"] >>= name; if(!name.empty()) { 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(name); tempArray.emplace_back(type); object["url"] >>= url; object["interval"] >>= interval; object["tolerance"] >>= tolerance; object["timeout"] >>= timeout; for(std::size_t 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(url); tempArray.emplace_back(interval + "," + timeout + "," + tolerance); } std::string strLine = join(tempArray, "`"); dest.emplace_back(std::move(strLine)); } importItems(dest, scope_limit); } void readRuleset(YAML::Node node, string_array &dest, bool scope_limit = true) { for(auto && object : node) { std::string strLine, name, url, group, interval; object["import"] >>= name; if(!name.empty()) { dest.emplace_back("!!import:" + name); continue; } object["ruleset"] >>= url; object["group"] >>= group; object["rule"] >>= name; object["interval"] >>= interval; if(!url.empty()) { strLine = group + "," + url; if(!interval.empty()) strLine += "," + interval; } else if(!name.empty()) strLine = group + ",[]" + name; else continue; dest.emplace_back(std::move(strLine)); } importItems(dest, scope_limit); } void refreshRulesets(RulesetConfigs &ruleset_list, std::vector &ruleset_content_array) { eraseElements(ruleset_content_array); std::string rule_group, rule_url, rule_url_typed, interval; RulesetContent rc; std::string proxy = parseProxy(global.proxyRuleset); for(RulesetConfig &x : ruleset_list) { rule_group = x.Group; rule_url = x.Url; std::string::size_type pos = x.Url.find("[]"); if(pos != std::string::npos) { writeLog(0, "Adding rule '" + rule_url.substr(pos + 2) + "," + rule_group + "'.", LOG_LEVEL_INFO); rc = {rule_group, "", "", RULESET_SURGE, std::async(std::launch::async, [=](){return rule_url.substr(pos);}), 0}; } else { 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, global.cacheRuleset, true, global.asyncFetchRuleset), x.Interval}; } 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"] >> global.APIMode; section["api_access_token"] >> global.accessToken; 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); }); global.defaultUrls = strLine; eraseElements(tempArray); } } global.enableInsert = 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); }); global.insertUrls = strLine; eraseElements(tempArray); } } section["prepend_insert_url"] >> global.prependInsert; if(section["exclude_remarks"].IsSequence()) section["exclude_remarks"] >> global.excludeRemarks; if(section["include_remarks"].IsSequence()) section["include_remarks"] >> global.includeRemarks; global.filterScript = safe_as(section["enable_filter"]) ? safe_as(section["filter_script"]) : ""; section["base_path"] >> global.basePath; section["clash_rule_base"] >> global.clashBase; section["surge_rule_base"] >> global.surgeBase; section["surfboard_rule_base"] >> global.surfboardBase; section["mellow_rule_base"] >> global.mellowBase; section["quan_rule_base"] >> global.quanBase; section["quanx_rule_base"] >> global.quanXBase; section["loon_rule_base"] >> global.loonBase; section["sssub_rule_base"] >> global.SSSubBase; section["singbox_rule_base"] >> global.singBoxBase; section["default_external_config"] >> global.defaultExtConfig; section["append_proxy_type"] >> global.appendType; section["proxy_config"] >> global.proxyConfig; section["proxy_ruleset"] >> global.proxyRuleset; section["proxy_subscription"] >> global.proxySubscription; if(node["userinfo"].IsDefined()) { section = node["userinfo"]; if(section["stream_rule"].IsSequence()) { readRegexMatch(section["stream_rule"], "|", tempArray, false); auto configs = INIBinding::from::from_ini(tempArray, "|"); safe_set_streams(configs); eraseElements(tempArray); } if(section["time_rule"].IsSequence()) { readRegexMatch(section["time_rule"], "|", tempArray, false); auto configs = INIBinding::from::from_ini(tempArray, "|"); safe_set_times(configs); 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; */ global.UDPFlag.set(safe_as(section["udp_flag"])); global.TFOFlag.set(safe_as(section["tcp_fast_open_flag"])); global.skipCertVerify.set(safe_as(section["skip_cert_verify_flag"])); global.TLS13Flag.set(safe_as(section["tls13_flag"])); section["sort_flag"] >> global.enableSort; section["sort_script"] >> global.sortScript; section["filter_deprecated_nodes"] >> global.filterDeprecated; section["append_sub_userinfo"] >> global.appendUserinfo; section["clash_use_new_field_name"] >> global.clashUseNewField; section["clash_proxies_style"] >> global.clashProxiesStyle; section["singbox_add_clash_modes"] >> global.singBoxAddClashModes; } if(section["rename_node"].IsSequence()) { readRegexMatch(section["rename_node"], "@", tempArray, false); auto configs = INIBinding::from::from_ini(tempArray, "@"); safe_set_renames(configs); eraseElements(tempArray); } if(node["managed_config"].IsDefined()) { section = node["managed_config"]; section["write_managed_config"] >> global.writeManagedConfig; section["managed_config_prefix"] >> global.managedConfigPrefix; section["config_update_interval"] >> global.updateInterval; section["config_update_strict"] >> global.updateStrict; section["quanx_device_id"] >> global.quanXDevID; } if(node["surge_external_proxy"].IsDefined()) { node["surge_external_proxy"]["surge_ssr_path"] >> global.surgeSSRPath; node["surge_external_proxy"]["resolve_hostname"] >> global.surgeResolveHostname; } if(node["emojis"].IsDefined()) { section = node["emojis"]; section["add_emoji"] >> global.addEmoji; section["remove_old_emoji"] >> global.removeEmoji; if(section["rules"].IsSequence()) { readEmoji(section["rules"], tempArray, false); auto configs = INIBinding::from::from_ini(tempArray, ","); safe_set_emojis(configs); eraseElements(tempArray); } } const char *rulesets_title = node["rulesets"].IsDefined() ? "rulesets" : "ruleset"; if(node[rulesets_title].IsDefined()) { section = node[rulesets_title]; section["enabled"] >> global.enableRuleGen; if(!global.enableRuleGen) { global.overwriteOriginalRules = false; global.updateRulesetOnRequest = false; } else { section["overwrite_original_rules"] >> global.overwriteOriginalRules; section["update_ruleset_on_request"] >> global.updateRulesetOnRequest; } const char *ruleset_title = section["rulesets"].IsDefined() ? "rulesets" : "surge_ruleset"; if(section[ruleset_title].IsSequence()) { string_array vArray; readRuleset(section[ruleset_title], vArray, false); global.customRulesets = INIBinding::from::from_ini(vArray); } } const char *groups_title = node["proxy_groups"].IsDefined() ? "proxy_groups" : "proxy_group"; if(node[groups_title].IsDefined() && node[groups_title]["custom_proxy_group"].IsDefined()) { string_array vArray; readGroup(node[groups_title]["custom_proxy_group"], vArray, false); global.customProxyGroups = INIBinding::from::from_ini(vArray); } if(node["template"].IsDefined()) { node["template"]["template_path"] >> global.templatePath; if(node["template"]["globals"].IsSequence()) { eraseElements(global.templateVars); 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; global.templateVars[key] = value; } } } if(node["aliases"].IsSequence()) { webServer.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; webServer.append_redirect(uri, target); } } if(node["tasks"].IsSequence()) { string_array vArray; for(size_t i = 0; i < node["tasks"].size(); i++) { std::string name, exp, path, timeout; node["tasks"][i]["import"] >> name; if(name.size()) { vArray.emplace_back("!!import:" + name); continue; } node["tasks"][i]["name"] >> name; node["tasks"][i]["cronexp"] >> exp; node["tasks"][i]["path"] >> path; node["tasks"][i]["timeout"] >> timeout; strLine = name + "`" + exp + "`" + path + "`" + timeout; vArray.emplace_back(std::move(strLine)); } importItems(vArray, false); global.enableCron = !vArray.empty(); global.cronTasks = INIBinding::from::from_ini(vArray); refresh_schedule(); } if(node["server"].IsDefined()) { node["server"]["listen"] >> global.listenAddress; node["server"]["port"] >> global.listenPort; node["server"]["serve_file_root"] >>= webServer.serve_file_root; webServer.serve_file = !webServer.serve_file_root.empty(); } if(node["advanced"].IsDefined()) { std::string log_level; node["advanced"]["log_level"] >> log_level; node["advanced"]["print_debug_info"] >> global.printDbgInfo; if(global.printDbgInfo) global.logLevel = LOG_LEVEL_VERBOSE; else { switch(hash_(log_level)) { case "warn"_hash: global.logLevel = LOG_LEVEL_WARNING; break; case "error"_hash: global.logLevel = LOG_LEVEL_ERROR; break; case "fatal"_hash: global.logLevel = LOG_LEVEL_FATAL; break; case "verbose"_hash: global.logLevel = LOG_LEVEL_VERBOSE; break; case "debug"_hash: global.logLevel = LOG_LEVEL_DEBUG; break; default: global.logLevel = LOG_LEVEL_INFO; } } node["advanced"]["max_pending_connections"] >> global.maxPendingConns; node["advanced"]["max_concurrent_threads"] >> global.maxConcurThreads; node["advanced"]["max_allowed_rulesets"] >> global.maxAllowedRulesets; node["advanced"]["max_allowed_rules"] >> global.maxAllowedRules; node["advanced"]["max_allowed_download_size"] >> global.maxAllowedDownloadSize; if(node["advanced"]["enable_cache"].IsDefined()) { if(safe_as(node["advanced"]["enable_cache"])) { node["advanced"]["cache_subscription"] >> global.cacheSubscription; node["advanced"]["cache_config"] >> global.cacheConfig; node["advanced"]["cache_ruleset"] >> global.cacheRuleset; node["advanced"]["serve_cache_on_fetch_fail"] >> global.serveCacheOnFetchFail; } else global.cacheSubscription = global.cacheConfig = global.cacheRuleset = 0; //disable cache } node["advanced"]["script_clean_context"] >> global.scriptCleanContext; node["advanced"]["async_fetch_ruleset"] >> global.asyncFetchRuleset; node["advanced"]["skip_failed_links"] >> global.skipFailedLinks; } writeLog(0, "Load preference settings in YAML format completed.", LOG_LEVEL_INFO); } template void find_if_exist(const toml::value &v, const toml::key &k, T& target, U&&... args) { if(v.contains(k)) target = toml::find(v, k); if constexpr (sizeof...(args) > 0) find_if_exist(v, std::forward(args)...); } void operate_toml_kv_table(const std::vector &arr, const toml::key &key_name, const toml::key &value_name, std::function binary_op) { for(const toml::table &table : arr) { const auto &key = table.at(key_name), &value = table.at(value_name); binary_op(key, value); } } void readTOMLConf(toml::value &root) { auto section_common = toml::find(root, "common"); string_array default_url, insert_url; find_if_exist(section_common, "default_url", default_url, "insert_url", insert_url); global.defaultUrls = join(default_url, "|"); global.insertUrls = join(insert_url, "|"); bool filter = false; find_if_exist(section_common, "api_mode", global.APIMode, "api_access_token", global.accessToken, "exclude_remarks", global.excludeRemarks, "include_remarks", global.includeRemarks, "enable_insert", global.enableInsert, "prepend_insert_url", global.prependInsert, "enable_filter", filter, "default_external_config", global.defaultExtConfig, "base_path", global.basePath, "clash_rule_base", global.clashBase, "surge_rule_base", global.surgeBase, "surfboard_rule_base", global.surfboardBase, "mellow_rule_base", global.mellowBase, "quan_rule_base", global.quanBase, "quanx_rule_base", global.quanXBase, "loon_rule_base", global.loonBase, "sssub_rule_base", global.SSSubBase, "singbox_rule_base", global.singBoxBase, "proxy_config", global.proxyConfig, "proxy_ruleset", global.proxyRuleset, "proxy_subscription", global.proxySubscription, "append_proxy_type", global.appendType ); if(filter) find_if_exist(section_common, "filter_script", global.filterScript); else global.filterScript.clear(); safe_set_streams(toml::find_or(root, "userinfo", "stream_rule", RegexMatchConfigs{})); safe_set_times(toml::find_or(root, "userinfo", "time_rule", RegexMatchConfigs{})); auto section_node_pref = toml::find(root, "node_pref"); find_if_exist(section_node_pref, "udp_flag", global.UDPFlag, "tcp_fast_open_flag", global.TFOFlag, "skip_cert_verify_flag", global.skipCertVerify, "tls13_flag", global.TLS13Flag, "sort_flag", global.enableSort, "sort_script", global.sortScript, "filter_deprecated_nodes", global.filterDeprecated, "append_sub_userinfo", global.appendUserinfo, "clash_use_new_field_name", global.clashUseNewField, "clash_proxies_style", global.clashProxiesStyle, "singbox_add_clash_modes", global.singBoxAddClashModes ); auto renameconfs = toml::find_or>(section_node_pref, "rename_node", {}); importItems(renameconfs, "rename_node", false); safe_set_renames(toml::get(toml::value(renameconfs))); auto section_managed = toml::find(root, "managed_config"); find_if_exist(section_managed, "write_managed_config", global.writeManagedConfig, "managed_config_prefix", global.managedConfigPrefix, "config_update_interval", global.updateInterval, "config_update_strict", global.updateStrict, "quanx_device_id", global.quanXDevID ); auto section_surge_external = toml::find(root, "surge_external_proxy"); find_if_exist(section_surge_external, "surge_ssr_path", global.surgeSSRPath, "resolve_hostname", global.surgeResolveHostname ); auto section_emojis = toml::find(root, "emojis"); find_if_exist(section_emojis, "add_emoji", global.addEmoji, "remove_old_emoji", global.removeEmoji ); auto emojiconfs = toml::find_or>(section_emojis, "emoji", {}); importItems(emojiconfs, "emoji", false); safe_set_emojis(toml::get(toml::value(emojiconfs))); auto groups = toml::find_or>(root, "custom_groups", {}); importItems(groups, "custom_groups", false); global.customProxyGroups = toml::get(toml::value(groups)); auto section_ruleset = toml::find(root, "ruleset"); find_if_exist(section_ruleset, "enabled", global.enableRuleGen, "overwrite_original_rules", global.overwriteOriginalRules, "update_ruleset_on_request", global.updateRulesetOnRequest ); auto rulesets = toml::find_or>(root, "rulesets", {}); importItems(rulesets, "rulesets", false); global.customRulesets = toml::get(toml::value(rulesets)); auto section_template = toml::find(root, "template"); global.templatePath = toml::find_or(section_template, "template_path", "template"); eraseElements(global.templateVars); operate_toml_kv_table(toml::find_or>(section_template, "globals", {}), "key", "value", [&](const toml::value &key, const toml::value &value) { global.templateVars[key.as_string()] = value.as_string(); }); webServer.reset_redirect(); operate_toml_kv_table(toml::find_or>(root, "aliases", {}), "uri", "target", [&](const toml::value &key, const toml::value &value) { webServer.append_redirect(key.as_string(), value.as_string()); }); auto tasks = toml::find_or>(root, "tasks", {}); importItems(tasks, "tasks", false); global.cronTasks = toml::get(toml::value(tasks)); refresh_schedule(); auto section_server = toml::find(root, "server"); find_if_exist(section_server, "listen", global.listenAddress, "port", global.listenPort, "serve_file_root", webServer.serve_file_root ); webServer.serve_file = !webServer.serve_file_root.empty(); auto section_advanced = toml::find(root, "advanced"); std::string log_level; bool enable_cache = true; int cache_subscription = global.cacheSubscription, cache_config = global.cacheConfig, cache_ruleset = global.cacheRuleset; find_if_exist(section_advanced, "log_level", log_level, "print_debug_info", global.printDbgInfo, "max_pending_connections", global.maxPendingConns, "max_concurrent_threads", global.maxConcurThreads, "max_allowed_rulesets", global.maxAllowedRulesets, "max_allowed_rules", global.maxAllowedRules, "max_allowed_download_size", global.maxAllowedDownloadSize, "enable_cache", enable_cache, "cache_subscription", cache_subscription, "cache_config", cache_config, "cache_ruleset", cache_ruleset, "script_clean_context", global.scriptCleanContext, "async_fetch_ruleset", global.asyncFetchRuleset, "skip_failed_links", global.skipFailedLinks ); if(global.printDbgInfo) global.logLevel = LOG_LEVEL_VERBOSE; else { switch(hash_(log_level)) { case "warn"_hash: global.logLevel = LOG_LEVEL_WARNING; break; case "error"_hash: global.logLevel = LOG_LEVEL_ERROR; break; case "fatal"_hash: global.logLevel = LOG_LEVEL_FATAL; break; case "verbose"_hash: global.logLevel = LOG_LEVEL_VERBOSE; break; case "debug"_hash: global.logLevel = LOG_LEVEL_DEBUG; break; default: global.logLevel = LOG_LEVEL_INFO; } } if(enable_cache) { global.cacheSubscription = cache_subscription; global.cacheConfig = cache_config; global.cacheRuleset = cache_ruleset; } else { global.cacheSubscription = global.cacheConfig = global.cacheRuleset = 0; } writeLog(0, "Load preference settings in TOML format completed.", LOG_LEVEL_INFO); } void readConf() { guarded_mutex guard(gMutexConfigure); writeLog(0, "Loading preference settings...", LOG_LEVEL_INFO); eraseElements(global.excludeRemarks); eraseElements(global.includeRemarks); eraseElements(global.customProxyGroups); eraseElements(global.customRulesets); try { std::string prefdata = fileGet(global.prefPath, false); if(prefdata.find("common:") != std::string::npos) { YAML::Node yaml = YAML::Load(prefdata); if(yaml.size() && yaml["common"]) return readYAMLConf(yaml); } toml::value conf = parseToml(prefdata, global.prefPath); if(!conf.is_uninitialized() && toml::find_or(conf, "version", 0)) return readTOMLConf(conf); } catch (YAML::Exception &e) { //ignore yaml parse error writeLog(0, e.what(), LOG_LEVEL_DEBUG); writeLog(0, "Unable to load preference settings as YAML.", LOG_LEVEL_DEBUG); } catch (toml::exception &e) { //ignore toml parse error writeLog(0, e.what(), LOG_LEVEL_DEBUG); writeLog(0, "Unable to load preference settings as TOML.", LOG_LEVEL_DEBUG); } INIReader ini; ini.allow_dup_section_titles = true; //ini.do_utf8_to_gbk = true; int retVal = ini.parse_file(global.prefPath); if(retVal != INIREADER_EXCEPTION_NONE) { writeLog(0, "Unable to load preference settings as INI. Reason: " + ini.get_last_error(), LOG_LEVEL_FATAL); return; } string_array tempArray; ini.enter_section("common"); ini.get_bool_if_exist("api_mode", global.APIMode); ini.get_if_exist("api_access_token", global.accessToken); ini.get_if_exist("default_url", global.defaultUrls); global.enableInsert = ini.get("enable_insert"); ini.get_if_exist("insert_url", global.insertUrls); ini.get_bool_if_exist("prepend_insert_url", global.prependInsert); if(ini.item_prefix_exist("exclude_remarks")) ini.get_all("exclude_remarks", global.excludeRemarks); if(ini.item_prefix_exist("include_remarks")) ini.get_all("include_remarks", global.includeRemarks); global.filterScript = ini.get_bool("enable_filter") ? ini.get("filter_script") : ""; ini.get_if_exist("base_path", global.basePath); ini.get_if_exist("clash_rule_base", global.clashBase); ini.get_if_exist("surge_rule_base", global.surgeBase); ini.get_if_exist("surfboard_rule_base", global.surfboardBase); ini.get_if_exist("mellow_rule_base", global.mellowBase); ini.get_if_exist("quan_rule_base", global.quanBase); ini.get_if_exist("quanx_rule_base", global.quanXBase); ini.get_if_exist("loon_rule_base", global.loonBase); ini.get_if_exist("sssub_rule_base", global.SSSubBase); ini.get_if_exist("singbox_rule_base", global.singBoxBase); ini.get_if_exist("default_external_config", global.defaultExtConfig); ini.get_bool_if_exist("append_proxy_type", global.appendType); ini.get_if_exist("proxy_config", global.proxyConfig); ini.get_if_exist("proxy_ruleset", global.proxyRuleset); ini.get_if_exist("proxy_subscription", global.proxySubscription); if(ini.section_exist("surge_external_proxy")) { ini.enter_section("surge_external_proxy"); ini.get_if_exist("surge_ssr_path", global.surgeSSRPath); ini.get_bool_if_exist("resolve_hostname", global.surgeResolveHostname); } if(ini.section_exist("node_pref")) { ini.enter_section("node_pref"); /* ini.GetBoolIfExist("udp_flag", udp_flag); ini.get_bool_if_exist("tcp_fast_open_flag", tfo_flag); ini.get_bool_if_exist("skip_cert_verify_flag", scv_flag); */ global.UDPFlag.set(ini.get("udp_flag")); global.TFOFlag.set(ini.get("tcp_fast_open_flag")); global.skipCertVerify.set(ini.get("skip_cert_verify_flag")); global.TLS13Flag.set(ini.get("tls13_flag")); ini.get_bool_if_exist("sort_flag", global.enableSort); global.sortScript = ini.get("sort_script"); ini.get_bool_if_exist("filter_deprecated_nodes", global.filterDeprecated); ini.get_bool_if_exist("append_sub_userinfo", global.appendUserinfo); ini.get_bool_if_exist("clash_use_new_field_name", global.clashUseNewField); ini.get_if_exist("clash_proxies_style", global.clashProxiesStyle); ini.get_bool_if_exist("singbox_add_clash_modes", global.singBoxAddClashModes); if(ini.item_prefix_exist("rename_node")) { ini.get_all("rename_node", tempArray); importItems(tempArray, false); auto configs = INIBinding::from::from_ini(tempArray, "@"); safe_set_renames(configs); eraseElements(tempArray); } } if(ini.section_exist("userinfo")) { ini.enter_section("userinfo"); if(ini.item_prefix_exist("stream_rule")) { ini.get_all("stream_rule", tempArray); importItems(tempArray, false); auto configs = INIBinding::from::from_ini(tempArray, "|"); safe_set_streams(configs); eraseElements(tempArray); } if(ini.item_prefix_exist("time_rule")) { ini.get_all("time_rule", tempArray); importItems(tempArray, false); auto configs = INIBinding::from::from_ini(tempArray, "|"); safe_set_times(configs); eraseElements(tempArray); } } ini.enter_section("managed_config"); ini.get_bool_if_exist("write_managed_config", global.writeManagedConfig); ini.get_if_exist("managed_config_prefix", global.managedConfigPrefix); ini.get_int_if_exist("config_update_interval", global.updateInterval); ini.get_bool_if_exist("config_update_strict", global.updateStrict); ini.get_if_exist("quanx_device_id", global.quanXDevID); ini.enter_section("emojis"); ini.get_bool_if_exist("add_emoji", global.addEmoji); ini.get_bool_if_exist("remove_old_emoji", global.removeEmoji); if(ini.item_prefix_exist("rule")) { ini.get_all("rule", tempArray); importItems(tempArray, false); auto configs = INIBinding::from::from_ini(tempArray, ","); safe_set_emojis(configs); eraseElements(tempArray); } if(ini.section_exist("rulesets")) ini.enter_section("rulesets"); else ini.enter_section("ruleset"); global.enableRuleGen = ini.get_bool("enabled"); if(global.enableRuleGen) { ini.get_bool_if_exist("overwrite_original_rules", global.overwriteOriginalRules); ini.get_bool_if_exist("update_ruleset_on_request", global.updateRulesetOnRequest); if(ini.item_prefix_exist("ruleset")) { string_array vArray; ini.get_all("ruleset", vArray); importItems(vArray, false); global.customRulesets = INIBinding::from::from_ini(vArray); } else if(ini.item_prefix_exist("surge_ruleset")) { string_array vArray; ini.get_all("surge_ruleset", vArray); importItems(vArray, false); global.customRulesets = INIBinding::from::from_ini(vArray); } } else { global.overwriteOriginalRules = false; global.updateRulesetOnRequest = false; } if(ini.section_exist("proxy_groups")) ini.enter_section("proxy_groups"); else ini.enter_section("clash_proxy_group"); if(ini.item_prefix_exist("custom_proxy_group")) { string_array vArray; ini.get_all("custom_proxy_group", vArray); importItems(vArray, false); global.customProxyGroups = INIBinding::from::from_ini(vArray); } ini.enter_section("template"); ini.get_if_exist("template_path", global.templatePath); string_multimap tempmap; ini.get_items(tempmap); eraseElements(global.templateVars); for(auto &x : tempmap) { if(x.first == "template_path") continue; global.templateVars[x.first] = x.second; } global.templateVars["managed_config_prefix"] = global.managedConfigPrefix; if(ini.section_exist("aliases")) { ini.enter_section("aliases"); ini.get_items(tempmap); webServer.reset_redirect(); for(auto &x : tempmap) webServer.append_redirect(x.first, x.second); } if(ini.section_exist("tasks")) { string_array vArray; ini.enter_section("tasks"); ini.get_all("task", vArray); importItems(vArray, false); global.enableCron = !vArray.empty(); global.cronTasks = INIBinding::from::from_ini(vArray); refresh_schedule(); } ini.enter_section("server"); ini.get_if_exist("listen", global.listenAddress); ini.get_int_if_exist("port", global.listenPort); webServer.serve_file_root = ini.get("serve_file_root"); webServer.serve_file = !webServer.serve_file_root.empty(); ini.enter_section("advanced"); std::string log_level; ini.get_if_exist("log_level", log_level); ini.get_bool_if_exist("print_debug_info", global.printDbgInfo); if(global.printDbgInfo) global.logLevel = LOG_LEVEL_VERBOSE; else { switch(hash_(log_level)) { case "warn"_hash: global.logLevel = LOG_LEVEL_WARNING; break; case "error"_hash: global.logLevel = LOG_LEVEL_ERROR; break; case "fatal"_hash: global.logLevel = LOG_LEVEL_FATAL; break; case "verbose"_hash: global.logLevel = LOG_LEVEL_VERBOSE; break; case "debug"_hash: global.logLevel = LOG_LEVEL_DEBUG; break; default: global.logLevel = LOG_LEVEL_INFO; } } ini.get_int_if_exist("max_pending_connections", global.maxPendingConns); ini.get_int_if_exist("max_concurrent_threads", global.maxConcurThreads); ini.get_number_if_exist("max_allowed_rulesets", global.maxAllowedRulesets); ini.get_number_if_exist("max_allowed_rules", global.maxAllowedRules); ini.get_number_if_exist("max_allowed_download_size", global.maxAllowedDownloadSize); if(ini.item_exist("enable_cache")) { if(ini.get_bool("enable_cache")) { ini.get_int_if_exist("cache_subscription", global.cacheSubscription); ini.get_int_if_exist("cache_config", global.cacheConfig); ini.get_int_if_exist("cache_ruleset", global.cacheRuleset); ini.get_bool_if_exist("serve_cache_on_fetch_fail", global.serveCacheOnFetchFail); } else { global.cacheSubscription = global.cacheConfig = global.cacheRuleset = 0; //disable cache global.serveCacheOnFetchFail = false; } } ini.get_bool_if_exist("script_clean_context", global.scriptCleanContext); ini.get_bool_if_exist("async_fetch_ruleset", global.asyncFetchRuleset); ini.get_bool_if_exist("skip_failed_links", global.skipFailedLinks); writeLog(0, "Load preference settings in INI format completed.", LOG_LEVEL_INFO); } int loadExternalYAML(YAML::Node &node, ExternalConfig &ext) { YAML::Node section = node["custom"], object; std::string name, type, url, interval; std::string group, strLine; section["clash_rule_base"] >> 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["singbox_rule_base"] >> ext.singbox_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()) { string_array vArray; readGroup(section[group_name], vArray, global.APIMode); ext.custom_proxy_group = INIBinding::from::from_ini(vArray); } const char *ruleset_name = section["rulesets"].IsDefined() ? "rulesets" : "surge_ruleset"; if(section[ruleset_name].size()) { string_array vArray; readRuleset(section[ruleset_name], vArray, global.APIMode); if(global.maxAllowedRulesets && vArray.size() > global.maxAllowedRulesets) { writeLog(0, "Ruleset count in external config has exceeded limit.", LOG_LEVEL_WARNING); return -1; } ext.surge_ruleset = INIBinding::from::from_ini(vArray); } if(section["rename_node"].size()) { string_array vArray; readRegexMatch(section["rename_node"], "@", vArray, global.APIMode); ext.rename = INIBinding::from::from_ini(vArray, "@"); } 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()) { string_array vArray; readEmoji(section[emoji_name], vArray, global.APIMode); ext.emoji = INIBinding::from::from_ini(vArray, ","); } 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 loadExternalTOML(toml::value &root, ExternalConfig &ext) { auto section = toml::find(root, "custom"); find_if_exist(section, "enable_rule_generator", ext.enable_rule_generator, "overwrite_original_rules", ext.overwrite_original_rules, "clash_rule_base", ext.clash_rule_base, "surge_rule_base", ext.surge_rule_base, "surfboard_rule_base", ext.surfboard_rule_base, "mellow_rule_base", ext.mellow_rule_base, "quan_rule_base", ext.quan_rule_base, "quanx_rule_base", ext.quanx_rule_base, "loon_rule_base", ext.loon_rule_base, "sssub_rule_base", ext.sssub_rule_base, "singbox_rule_base", ext.singbox_rule_base, "add_emoji", ext.add_emoji, "remove_old_emoji", ext.remove_old_emoji, "include_remarks", ext.include, "exclude_remarks", ext.exclude ); if(ext.tpl_args != nullptr) operate_toml_kv_table(toml::find_or>(section, "template_args", {}), "key", "value", [&](const toml::value &key, const toml::value &value) { std::string val = toml::format(value); ext.tpl_args->local_vars[key.as_string()] = val; }); auto groups = toml::find_or>(root, "custom_groups", {}); importItems(groups, "custom_groups", false); ext.custom_proxy_group = toml::get(toml::value(groups)); auto rulesets = toml::find_or>(root, "rulesets", {}); importItems(rulesets, "rulesets", false); if(global.maxAllowedRulesets && rulesets.size() > global.maxAllowedRulesets) { writeLog(0, "Ruleset count in external config has exceeded limit. ", LOG_LEVEL_WARNING); return -1; } ext.surge_ruleset = toml::get(toml::value(rulesets)); auto emojiconfs = toml::find_or>(root, "emoji", {}); importItems(emojiconfs, "emoji", false); ext.emoji = toml::get(toml::value(emojiconfs)); auto renameconfs = toml::find_or>(root, "rename_node", {}); importItems(renameconfs, "rename_node", false); ext.rename = toml::get(toml::value(renameconfs)); return 0; } int loadExternalConfig(std::string &path, ExternalConfig &ext) { std::string base_content, proxy = parseProxy(global.proxyConfig), config = fetchFile(path, proxy, global.cacheConfig); if(render_template(config, *ext.tpl_args, base_content, global.templatePath) != 0) base_content = config; try { YAML::Node yaml = YAML::Load(base_content); if(yaml.size() && yaml["custom"].IsDefined()) return loadExternalYAML(yaml, ext); toml::value conf = parseToml(base_content, path); if(!conf.is_uninitialized() && toml::find_or(conf, "version", 0)) return loadExternalTOML(conf, ext); } catch (YAML::Exception &e) { //ignore } catch (toml::exception &e) { //ignore } INIReader ini; ini.store_isolated_line = true; ini.set_isolated_items_section("custom"); if(ini.parse(base_content) != INIREADER_EXCEPTION_NONE) { //std::cerr<<"Load external configuration failed. Reason: "<::from_ini(vArray); } std::string ruleset_name = ini.item_prefix_exist("ruleset") ? "ruleset" : "surge_ruleset"; if(ini.item_prefix_exist(ruleset_name)) { string_array vArray; ini.get_all(ruleset_name, vArray); importItems(vArray, global.APIMode); if(global.maxAllowedRulesets && vArray.size() > global.maxAllowedRulesets) { writeLog(0, "Ruleset count in external config has exceeded limit. ", LOG_LEVEL_WARNING); return -1; } ext.surge_ruleset = INIBinding::from::from_ini(vArray); } ini.get_if_exist("clash_rule_base", ext.clash_rule_base); ini.get_if_exist("surge_rule_base", ext.surge_rule_base); ini.get_if_exist("surfboard_rule_base", ext.surfboard_rule_base); ini.get_if_exist("mellow_rule_base", ext.mellow_rule_base); ini.get_if_exist("quan_rule_base", ext.quan_rule_base); ini.get_if_exist("quanx_rule_base", ext.quanx_rule_base); ini.get_if_exist("loon_rule_base", ext.loon_rule_base); ini.get_if_exist("sssub_rule_base", ext.sssub_rule_base); ini.get_if_exist("singbox_rule_base", ext.singbox_rule_base); ini.get_bool_if_exist("overwrite_original_rules", ext.overwrite_original_rules); ini.get_bool_if_exist("enable_rule_generator", ext.enable_rule_generator); if(ini.item_prefix_exist("rename")) { string_array vArray; ini.get_all("rename", vArray); importItems(vArray, global.APIMode); ext.rename = INIBinding::from::from_ini(vArray, "@"); } ext.add_emoji = ini.get("add_emoji"); ext.remove_old_emoji = ini.get("remove_old_emoji"); if(ini.item_prefix_exist("emoji")) { string_array vArray; ini.get_all("emoji", vArray); importItems(vArray, global.APIMode); ext.emoji = INIBinding::from::from_ini(vArray, ","); } if(ini.item_prefix_exist("include_remarks")) ini.get_all("include_remarks", ext.include); if(ini.item_prefix_exist("exclude_remarks")) ini.get_all("exclude_remarks", ext.exclude); if(ini.section_exist("template") && ext.tpl_args != nullptr) { ini.enter_section("template"); string_multimap tempmap; ini.get_items(tempmap); for(auto &x : tempmap) ext.tpl_args->local_vars[x.first] = x.second; } return 0; }