From e14f7dd6bff2ff9e61877dc346e80ba251afc4f8 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Mon, 10 Feb 2020 18:22:12 +0800 Subject: [PATCH] Enhancements Fix when processing duplicated node name for Surge, the program will crash. Add an interface to directly convert Surge config to Clash config. Add Netease Music IP ruleset. Clean up codes. Update build scripts. --- CMakeLists.txt | 1 + base/rules/lhie1/Netease Music IP.list | 19 + scripts/build.clang.sh | 1 + src/interfaces.cpp | 1331 ++++++++++++++++++++++++ src/interfaces.h | 17 + src/main.cpp | 1163 +-------------------- src/subexport.cpp | 2 +- src/subexport.h | 5 + 8 files changed, 1386 insertions(+), 1153 deletions(-) create mode 100644 base/rules/lhie1/Netease Music IP.list create mode 100644 src/interfaces.cpp create mode 100644 src/interfaces.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6088e2e..ff85b6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ IF(USING_MALLOC_TRIM) ENDIF() ADD_EXECUTABLE(subconverter + src/interfaces.cpp src/logger.cpp src/main.cpp src/misc.cpp diff --git a/base/rules/lhie1/Netease Music IP.list b/base/rules/lhie1/Netease Music IP.list new file mode 100644 index 0000000..e696997 --- /dev/null +++ b/base/rules/lhie1/Netease Music IP.list @@ -0,0 +1,19 @@ +IP-CIDR,39.105.63.80/32 +IP-CIDR,45.254.48.1/32 +IP-CIDR,47.100.127.239/32 +IP-CIDR,59.111.160.195/32 +IP-CIDR,59.111.160.197/32 +IP-CIDR,59.111.181.35/32 +IP-CIDR,59.111.181.38/32 +IP-CIDR,59.111.181.60/32 +IP-CIDR,101.71.154.241/32 +IP-CIDR,103.126.92.132/32 +IP-CIDR,103.126.92.133/32 +IP-CIDR,112.13.119.17/32 +IP-CIDR,112.13.122.1/32 +IP-CIDR,115.236.118.33/32 +IP-CIDR,115.236.121.1/32 +IP-CIDR,118.24.63.156/32 +IP-CIDR,193.112.159.225/32 +IP-CIDR,223.252.199.66/32 +IP-CIDR,223.252.199.67/32 diff --git a/scripts/build.clang.sh b/scripts/build.clang.sh index 9ded4c7..07f41a8 100644 --- a/scripts/build.clang.sh +++ b/scripts/build.clang.sh @@ -3,6 +3,7 @@ mkdir obj set -xe +c++ -std=c++17 -Wall -fexceptions -c src/interfaces.cpp -o obj/interfaces.o c++ -std=c++17 -Wall -fexceptions -c src/logger.cpp -o obj/logger.o c++ -std=c++17 -Wall -fexceptions -c src/main.cpp -o obj/main.o c++ -std=c++17 -Wall -fexceptions -c src/misc.cpp -o obj/misc.o diff --git a/src/interfaces.cpp b/src/interfaces.cpp new file mode 100644 index 0000000..9d35698 --- /dev/null +++ b/src/interfaces.cpp @@ -0,0 +1,1331 @@ +#include +#include +#include +#include + +#include + +#include "misc.h" +#include "nodeinfo.h" +#include "speedtestutil.h" +#include "nodemanip.h" +#include "ini_reader.h" +#include "webget.h" +#include "webserver.h" +#include "subexport.h" +#include "multithread.h" +#include "logger.h" + +//common settings +std::string pref_path = "pref.ini"; +bool generator_mode = false; +string_array def_exclude_remarks, def_include_remarks, rulesets, stream_rules, time_rules; +std::vector ruleset_content_array; +std::string listen_address = "127.0.0.1", default_url, managed_config_prefix; +int listen_port = 25500, max_pending_connections = 10, max_concurrent_threads = 4; +bool api_mode = true, write_managed_config = false, enable_rule_generator = true, update_ruleset_on_request = false, overwrite_original_rules = true; +bool print_debug_info = false, cfw_child_process = false; +std::string access_token; +extern std::string custom_group; + +//multi-thread lock +std::mutex on_configuring; + +//preferences +string_array renames, emojis; +bool add_emoji = false, remove_old_emoji = false, append_proxy_type = false, filter_deprecated = true; +bool udp_flag = false, tfo_flag = false, scv_flag = false, do_sort = false; +std::string proxy_ruleset, proxy_subscription; + +std::string clash_rule_base; +string_array clash_extra_group; +std::string surge_rule_base, surfboard_rule_base, mellow_rule_base, quan_rule_base, quanx_rule_base; +std::string surge_ssr_path; + +//pre-compiled rule bases +YAML::Node clash_base; +INIReader surge_base, mellow_base; + +string_array regex_blacklist = {"(.*)*"}; + +template void operator >> (const YAML::Node& node, T& i) +{ + if(node.IsDefined()) //fail-safe + i = node.as(); +}; + +int importItems(string_array &target) +{ + 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); + + if(fileExist(path)) + content = fileGet(path, false, api_mode); + else + content = webGet(path, ""); + if(!content.size()) + return -1; + + ss << content; + char delimiter = count(content.begin(), content.end(), '\n') < 1 ? '\r' : '\n'; + std::string::size_type lineSize; + while(getline(ss, strLine, delimiter)) + { + lineSize = strLine.size(); + if(strLine[lineSize - 1] == '\r') //remove line break + { + strLine = strLine.substr(0, lineSize - 1); + lineSize--; + } + if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored + continue; + result.emplace_back(strLine); + itemCount++; + } + ss.clear(); + } + target.swap(result); + writeLog(0, "Imported " + std::to_string(itemCount) + " item(s)."); + return 0; +} + +void readRegexMatch(YAML::Node node, std::string delimiter, string_array &dest) +{ + YAML::Node object; + std::string url, match, rep, strLine; + + for(unsigned i = 0; i < node.size(); i++) + { + object = node[i]; + object["import"] >> url; + if(url.size()) + { + url = "!!import:" + url; + dest.emplace_back(url); + continue; + } + object["match"] >> match; + object["replace"] >> rep; + if(match.size() && rep.size()) + strLine = match + delimiter + rep; + else + continue; + dest.emplace_back(strLine); + } + importItems(dest); +} + +void readEmoji(YAML::Node node, string_array &dest) +{ + YAML::Node object; + std::string url, match, rep, strLine; + + for(unsigned i = 0; i < node.size(); i++) + { + object = node[i]; + object["import"] >> url; + if(url.size()) + { + url = "!!import:" + url; + dest.emplace_back(url); + continue; + } + object["match"] >> match; + object["emoji"] >> rep; + if(match.size() && rep.size()) + strLine = match + "," + rep; + else + continue; + dest.emplace_back(strLine); + } + importItems(dest); +} + +void readGroup(YAML::Node node, string_array &dest) +{ + std::string strLine, name, type, url, interval; + 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()) + { + name = "!!import:" + name; + dest.emplace_back(name); + continue; + } + url = "http://www.gstatic.com/generate_204", interval = "300"; + object["name"] >> name; + object["type"] >> type; + tempArray.emplace_back(name); + tempArray.emplace_back(type); + object["url"] >> url; + object["interval"] >> interval; + for(j = 0; j < object["rule"].size(); j++) + tempArray.emplace_back(object["rule"][j].as()); + if(type != "select" && type != "ssid") + { + tempArray.emplace_back(url); + tempArray.emplace_back(interval); + } + + if((type == "select" && tempArray.size() < 3) || (type == "ssid" && tempArray.size() < 4) || (type != "select" && type != "ssid" && tempArray.size() < 5)) + continue; + + strLine = std::accumulate(std::next(tempArray.begin()), tempArray.end(), tempArray[0], [](std::string a, std::string b) -> std::string + { + return std::move(a) + "`" + std::move(b); + }); + dest.emplace_back(strLine); + } + importItems(dest); +} + +void readRuleset(YAML::Node node, string_array &dest) +{ + std::string strLine, name, url, group; + YAML::Node object; + + for(unsigned int i = 0; i < node.size(); i++) + { + name = ""; + object = node[i]; + object["import"] >> name; + if(name.size()) + { + name = "!!import:" + name; + dest.emplace_back(name); + continue; + } + object["ruleset"] >> url; + object["group"] >> group; + object["rule"] >> name; + if(url.size()) + strLine = group + "," + url; + else if(name.size()) + strLine = group + ",[]" + name; + else + continue; + dest.emplace_back(strLine); + } + importItems(dest); +} + +void refreshRulesets(string_array &ruleset_list, std::vector &rca) +{ + eraseElements(rca); + std::string rule_group, rule_url; + ruleset_content rc; + + std::string proxy; + if(proxy_ruleset == "SYSTEM") + proxy = getSystemProxy(); + else if(proxy_ruleset == "NONE") + proxy = ""; + else + proxy = proxy_ruleset; + + for(std::string &x : ruleset_list) + { + /* + vArray = split(x, ","); + if(vArray.size() != 2) + continue; + rule_group = trim(vArray[0]); + rule_url = trim(vArray[1]); + */ + if(x.find(",") == x.npos) + continue; + rule_group = trim(x.substr(0, x.find(","))); + rule_url = trim(x.substr(x.find(",") + 1)); + if(rule_url.find("[]") == 0) + { + std::cerr<<"Adding rule '"<> api_mode; + section["api_access_token"] >> access_token; + if(section["default_url"].IsSequence()) + { + section["default_url"] >> tempArray; + if(tempArray.size()) + strLine = std::accumulate(std::next(tempArray.begin()), tempArray.end(), tempArray[0], [](std::string a, std::string b) + { + return std::move(a) + "|" + std::move(b); + }); + default_url = strLine; + } + if(section["exclude_remarks"].IsSequence()) + section["exclude_remarks"] >> def_exclude_remarks; + if(section["include_remarks"].IsSequence()) + section["include_remarks"] >> def_include_remarks; + section["clash_rule_base"] >> clash_rule_base; + section["surge_rule_base"] >> surge_rule_base; + section["surfboard_rule_base"] >> surfboard_rule_base; + section["mellow_rule_base"] >> mellow_rule_base; + section["quan_rule_base"] >> quan_rule_base; + section["quanx_rule_base"] >> quanx_rule_base; + + section["append_proxy_type"] >> append_proxy_type; + section["proxy_ruleset"] >> proxy_ruleset; + section["proxy_subscription"] >> proxy_subscription; + + if(node["userinfo"].IsDefined()) + { + section = node["userinfo"]; + if(section["stream_rule"].IsSequence()) + { + readRegexMatch(section["stream_rule"], "@", tempArray); + safe_set_streams(tempArray); + eraseElements(tempArray); + } + if(section["time_rule"].IsSequence()) + { + readRegexMatch(section["time_rule"], "@", tempArray); + safe_set_times(tempArray); + eraseElements(tempArray); + } + } + + if(node["node_pref"].IsDefined()) + { + section = node["node_pref"]; + section["udp_flag"] >> udp_flag; + section["tcp_fast_open_flag"] >> tfo_flag; + section["sort_flag"] >> do_sort; + section["skip_cert_verify_flag"] >> scv_flag; + } + + if(section["rename_node"].IsSequence()) + { + readRegexMatch(section["rename_node"], "@", tempArray); + safe_set_renames(tempArray); + eraseElements(tempArray); + } + + if(node["managed_config"].IsDefined()) + { + section = node["managed_config"]; + section["write_managed_config"] >> write_managed_config; + section["managed_config_prefix"] >> managed_config_prefix; + } + + if(node["surge_external_proxy"].IsDefined()) + node["surge_external_proxy"]["surge_ssr_path"] >> surge_ssr_path; + + if(node["emojis"].IsDefined()) + { + section = node["emojis"]; + section["add_emoji"] >> add_emoji; + section["remove_old_emoji"] >> remove_old_emoji; + if(section["rules"].IsSequence()) + { + readEmoji(section["rules"], tempArray); + safe_set_emojis(tempArray); + eraseElements(tempArray); + } + } + + if(node["ruleset"].IsDefined()) + { + section = node["ruleset"]; + section["enabled"] >> enable_rule_generator; + if(!enable_rule_generator) + { + overwrite_original_rules = false; + update_ruleset_on_request = false; + } + else + { + section["overwrite_original_rules"] >> overwrite_original_rules; + section["update_ruleset_on_request"] >> update_ruleset_on_request; + } + if(section["surge_ruleset"].IsSequence()) + readRuleset(section["surge_ruleset"], rulesets); + } + + if(node["proxy_group"].IsDefined() && node["proxy_group"]["custom_proxy_group"].IsDefined()) + readGroup(node["proxy_group"]["custom_proxy_group"], clash_extra_group); + + if(node["server"].IsDefined()) + { + node["server"]["listen"] >> listen_address; + node["server"]["port"] >> listen_port; + } + + if(node["advanced"].IsDefined()) + { + node["advanced"]["print_debug_info"] >> print_debug_info; + node["advanced"]["max_pending_connections"] >> max_pending_connections; + node["advanced"]["max_concurrent_threads"] >> max_concurrent_threads; + } +} + +void readConf() +{ + guarded_mutex guard(on_configuring); + std::cerr<<"Reading preference settings..."<> ext.clash_rule_base; + section["surge_rule_base"] >> ext.surge_rule_base; + section["surfboard_rule_base"] >> ext.surfboard_rule_base; + section["mellow_rule_base"] >> ext.mellow_rule_base; + section["quan_rule_base"] >> ext.quan_rule_base; + section["quanx_rule_base"] >> ext.quanx_rule_base; + + ext.enable_rule_generator = section["enable_rule_generator"].as(); + ext.overwrite_original_rules = section["overwrite_original_rules"].as(); + + if(section["custom_proxy_group"].size()) + readGroup(section["custom_proxy_group"], ext.custom_proxy_group); + + if(section["surge_ruleset"].size()) + readRuleset(section["surge_ruleset"], ext.surge_ruleset); + + if(section["rename_node"].size()) + readRegexMatch(section["rename_node"], "@", ext.rename); + + return 0; +} + +int loadExternalConfig(std::string &path, ExternalConfig &ext, std::string proxy) +{ + std::string base_content; + if(fileExist(path)) + base_content = fileGet(path, false, api_mode); + else + base_content = webGet(path, proxy); + + try + { + YAML::Node yaml = YAML::Load(base_content); + if(yaml.size() && yaml["custom"]) + return loadExternalYAML(yaml, ext); + } + catch (YAML::Exception &e) + { + //ignore + } + + INIReader ini; + ini.store_isolated_line = true; + ini.SetIsolatedItemsSection("custom"); + if(ini.Parse(base_content) != INIREADER_EXCEPTION_NONE) + { + std::cerr<<"Load external configuration failed. Reason: "< rca; + extra_settings ext; + std::string subInfo; + bool ruleset_updated = false; + + if(std::find(regex_blacklist.cbegin(), regex_blacklist.cend(), include) != regex_blacklist.cend() || std::find(regex_blacklist.cbegin(), regex_blacklist.cend(), exclude) != regex_blacklist.cend()) + return "Invalid request!"; + + //for external configuration + std::string ext_clash_base = clash_rule_base, ext_surge_base = surge_rule_base, ext_mellow_base = mellow_rule_base, ext_surfboard_base = surfboard_rule_base; + std::string ext_quan_base = quan_rule_base, ext_quanx_base = quanx_rule_base; + + //validate urls + if(!url.size()) + url = default_url; + if(!url.size() || !target.size()) + { + *status_code = 400; + return "Invalid request!"; + } + + //check if we need to read configuration + if(!api_mode || cfw_child_process) + readConf(); + + //check for proxy settings + std::string proxy; + if(proxy_subscription == "SYSTEM") + proxy = getSystemProxy(); + else if(proxy_subscription == "NONE") + proxy = ""; + else + proxy = proxy_subscription; + + ext.emoji_array = safe_get_emojis(); + ext.rename_array = safe_get_renames(); + + //load external configuration + if(config.size()) + { + std::cerr<<"External configuration file provided. Loading...\n"; + //read predefined data first + extra_group = clash_extra_group; + extra_ruleset = rulesets; + //then load external configuration + ExternalConfig extconf; + loadExternalConfig(config, extconf, proxy); + if(extconf.clash_rule_base.size()) + ext_clash_base = extconf.clash_rule_base; + if(extconf.surge_rule_base.size()) + ext_surge_base = extconf.surge_rule_base; + if(extconf.surfboard_rule_base.size()) + ext_surfboard_base = extconf.surfboard_rule_base; + if(extconf.mellow_rule_base.size()) + ext_mellow_base = extconf.mellow_rule_base; + if(extconf.quan_rule_base.size()) + ext_quan_base = extconf.quan_rule_base; + if(extconf.quanx_rule_base.size()) + ext_quanx_base = extconf.quanx_rule_base; + if(extconf.rename.size()) + ext.rename_array = extconf.rename; + if(extconf.emoji.size()) + ext.emoji_array = extconf.emoji; + ext.enable_rule_generator = extconf.enable_rule_generator; + //load custom group + if(extconf.custom_proxy_group.size()) + extra_group = extconf.custom_proxy_group; + //load custom rules + ext.overwrite_original_rules = extconf.overwrite_original_rules; + if(extconf.surge_ruleset.size()) + { + extra_ruleset = extconf.surge_ruleset; + refreshRulesets(extra_ruleset, rca); + ruleset_updated = true; + } + else + { + if(ext.enable_rule_generator) + { + if(update_ruleset_on_request) + refreshRulesets(rulesets, ruleset_content_array); + rca = ruleset_content_array; + } + } + } + else + { + //loading custom groups + if(groups.size()) + { + extra_group = split(groups, "@"); + if(!extra_group.size()) + extra_group = clash_extra_group; + } + else + extra_group = clash_extra_group; + + //loading custom rulesets + if(ruleset.size()) + { + extra_ruleset = split(ruleset, "@"); + if(!extra_ruleset.size()) + { + if(update_ruleset_on_request) + refreshRulesets(rulesets, ruleset_content_array); + rca = ruleset_content_array; + } + else + { + refreshRulesets(extra_ruleset, rca); + ruleset_updated = true; + } + } + else + { + if(enable_rule_generator) + { + if(update_ruleset_on_request || cfw_child_process) + refreshRulesets(rulesets, ruleset_content_array); + rca = ruleset_content_array; + } + } + } + + //check other flags + if(emoji.size()) + { + ext.add_emoji = ext.remove_emoji = emoji == "true"; + } + else + { + ext.add_emoji = add_emoji; + ext.remove_emoji = remove_old_emoji; + } + if(append_type.size()) + ext.append_proxy_type = append_type == "true"; + else + ext.append_proxy_type = append_proxy_type; + + ext.tfo = tfo.size() ? tfo == "true" : tfo_flag; + ext.udp = udp.size() ? udp == "true" : udp_flag; + ext.sort_flag = sort_flag.size() ? sort_flag == "true" : do_sort; + ext.skip_cert_verify = scv.size() ? scv == "true" : scv_flag; + ext.filter_deprecated = fdn.size() ? fdn == "true" : filter_deprecated; + + ext.nodelist = nodelist == "true"; + ext.surge_ssr_path = surge_ssr_path; + ext.enable_rule_generator = enable_rule_generator; + + //loading urls + string_array urls = split(url, "|"); + std::vector nodes; + int groupID = 0; + + //check custom include/exclude settings + if(include.size() && regValid(include)) + include_remarks.emplace_back(include); + else + include_remarks = def_include_remarks; + if(exclude.size() && regValid(exclude)) + exclude_remarks.emplace_back(exclude); + else + exclude_remarks = def_exclude_remarks; + + //start parsing urls + string_array stream_temp = safe_get_streams(), time_temp = safe_get_times(); + for(std::string &x : urls) + { + x = trim(x); + std::cerr<<"Fetching node data from url '"< nodes; + string_array extra_group, extra_ruleset, include_remarks, exclude_remarks; + std::vector rca; + std::string subInfo; + + if(!url.size()) + url = default_url; + if(!url.size() || argument.substr(0, 8) != "sublink=") + { + *status_code = 400; + return "Invalid request!"; + } + if(url == "sublink") + { + *status_code = 400; + return "Please insert your subscription link instead of clicking the default link."; + } + if(!api_mode || cfw_child_process) + readConf(); + + extra_group = clash_extra_group; + + if(update_ruleset_on_request || cfw_child_process) + refreshRulesets(rulesets, ruleset_content_array); + rca = ruleset_content_array; + + extra_settings ext = {true, overwrite_original_rules, safe_get_renames(), safe_get_emojis(), add_emoji, remove_old_emoji, append_proxy_type, udp_flag, tfo_flag, false, do_sort, scv_flag, filter_deprecated, ""}; + + std::string proxy; + if(proxy_subscription == "SYSTEM") + proxy = getSystemProxy(); + else if(proxy_subscription == "NONE") + proxy = ""; + else + proxy = proxy_subscription; + + include_remarks = def_include_remarks; + exclude_remarks = def_exclude_remarks; + + //start parsing urls + int groupID = 0; + string_array dummy; + string_array urls = split(url, "|"); + for(std::string &x : urls) + { + x = trim(x); + std::cerr<<"Fetching node data from url '"< nodes; + std::string base_content, url = argument.size() <= 5 ? "" : argument.substr(5); + ini.store_any_line = true; + + if(!url.size()) + url = default_url; + if(!url.size() || argument.substr(0, 5) != "link=") + { + *status_code = 400; + return "Invalid request!"; + } + if(url == "link") + { + *status_code = 400; + return "Please insert your subscription link instead of clicking the default link."; + } + + if(fileExist(url)) + base_content = fileGet(url, false); + else + base_content = webGet(url, getSystemProxy()); + + if(ini.Parse(base_content) != INIREADER_EXCEPTION_NONE) + { + std::string errmsg = "Parsing Surge config failed! Reason: " + ini.GetLastError(); + std::cerr<= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored + continue; + else if(strLine.find("USER-AGENT") == 0 || strLine.find("URL-REGEX") == 0 || strLine.find("PROCESS-NAME") == 0 || strLine.find("AND") == 0 || strLine.find("OR") == 0) //remove unsupported types + continue; + strLine += strArray[2]; + if(std::count(strLine.begin(), strLine.end(), ',') > 2) + strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2"); + rule.push_back(strLine); + } + ss.clear(); + continue; + } + rule.push_back(x); + } + clash["Rule"] = rule; + + return YAML::Dump(clash); +} diff --git a/src/interfaces.h b/src/interfaces.h new file mode 100644 index 0000000..e281e69 --- /dev/null +++ b/src/interfaces.h @@ -0,0 +1,17 @@ +#ifndef INTERFACES_H_INCLUDED +#define INTERFACES_H_INCLUDED + +#include +#include + +#include "subexport.h" +#include "webserver.h" + +void refreshRulesets(string_array &ruleset_list, std::vector &rca); +void readConf(); +void generateBase(); +std::string subconverter(RESPONSE_CALLBACK_ARGS); +std::string simpleToClashR(RESPONSE_CALLBACK_ARGS); +std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS); + +#endif // INTERFACES_H_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index 0d60984..88e512f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,61 +1,18 @@ #include #include -#include #include -#include -#include - -#include "socket.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 "interfaces.h" #include "version.h" -#include "logger.h" +#include "misc.h" +#include "socket.h" +#include "webget.h" -//common settings -std::string pref_path = "pref.ini"; -bool generator_mode = false; -string_array def_exclude_remarks, def_include_remarks, rulesets, stream_rules, time_rules; -std::vector ruleset_content_array; -std::string listen_address = "127.0.0.1", default_url, managed_config_prefix; -int listen_port = 25500, max_pending_connections = 10, max_concurrent_threads = 4; -bool api_mode = true, write_managed_config = false, enable_rule_generator = true, update_ruleset_on_request = false, overwrite_original_rules = true; -bool print_debug_info = false, cfw_child_process = false; -std::string access_token; -extern std::string custom_group; - -//multi-thread lock -std::mutex on_configuring; - -//preferences -string_array renames, emojis; -bool add_emoji = false, remove_old_emoji = false, append_proxy_type = false, filter_deprecated = true; -bool udp_flag = false, tfo_flag = false, scv_flag = false, do_sort = false; -std::string proxy_ruleset, proxy_subscription; - -std::string clash_rule_base; -string_array clash_extra_group; -std::string surge_rule_base, surfboard_rule_base, mellow_rule_base, quan_rule_base, quanx_rule_base; -std::string surge_ssr_path; - -//pre-compiled rule bases -YAML::Node clash_base; -INIReader surge_base, mellow_base; - -string_array regex_blacklist = {"(.*)*"}; - -template void operator >> (const YAML::Node& node, T& i) -{ - if(node.IsDefined()) //fail-safe - i = node.as(); -}; +extern std::string pref_path, access_token, listen_address; +extern bool api_mode, cfw_child_process, update_ruleset_on_request; +extern int listen_port, max_concurrent_threads, max_pending_connections; +extern string_array rulesets; +extern std::vector ruleset_content_array; #ifndef _WIN32 void SetConsoleTitle(std::string title) @@ -89,1106 +46,6 @@ void setcd(std::string &file) chdir(path.data()); } -int importItems(string_array &target) -{ - 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); - - if(fileExist(path)) - content = fileGet(path, false, api_mode); - else - content = webGet(path, ""); - if(!content.size()) - return -1; - - ss << content; - char delimiter = count(content.begin(), content.end(), '\n') < 1 ? '\r' : '\n'; - std::string::size_type lineSize; - while(getline(ss, strLine, delimiter)) - { - lineSize = strLine.size(); - if(strLine[lineSize - 1] == '\r') //remove line break - { - strLine = strLine.substr(0, lineSize - 1); - lineSize--; - } - if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored - continue; - result.emplace_back(strLine); - itemCount++; - } - ss.clear(); - } - target.swap(result); - writeLog(0, "Imported " + std::to_string(itemCount) + " item(s)."); - return 0; -} - -void readRegexMatch(YAML::Node node, std::string delimiter, string_array &dest) -{ - YAML::Node object; - std::string url, match, rep, strLine; - - for(unsigned i = 0; i < node.size(); i++) - { - object = node[i]; - object["import"] >> url; - if(url.size()) - { - url = "!!import:" + url; - dest.emplace_back(url); - continue; - } - object["match"] >> match; - object["replace"] >> rep; - if(match.size() && rep.size()) - strLine = match + delimiter + rep; - else - continue; - dest.emplace_back(strLine); - } - importItems(dest); -} - -void readEmoji(YAML::Node node, string_array &dest) -{ - YAML::Node object; - std::string url, match, rep, strLine; - - for(unsigned i = 0; i < node.size(); i++) - { - object = node[i]; - object["import"] >> url; - if(url.size()) - { - url = "!!import:" + url; - dest.emplace_back(url); - continue; - } - object["match"] >> match; - object["emoji"] >> rep; - if(match.size() && rep.size()) - strLine = match + "," + rep; - else - continue; - dest.emplace_back(strLine); - } - importItems(dest); -} - -void readGroup(YAML::Node node, string_array &dest) -{ - std::string strLine, name, type, url, interval; - 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()) - { - name = "!!import:" + name; - dest.emplace_back(name); - continue; - } - url = "http://www.gstatic.com/generate_204", interval = "300"; - object["name"] >> name; - object["type"] >> type; - tempArray.emplace_back(name); - tempArray.emplace_back(type); - object["url"] >> url; - object["interval"] >> interval; - for(j = 0; j < object["rule"].size(); j++) - tempArray.emplace_back(object["rule"][j].as()); - if(type != "select" && type != "ssid") - { - tempArray.emplace_back(url); - tempArray.emplace_back(interval); - } - - if((type == "select" && tempArray.size() < 3) || (type == "ssid" && tempArray.size() < 4) || (type != "select" && type != "ssid" && tempArray.size() < 5)) - continue; - - strLine = std::accumulate(std::next(tempArray.begin()), tempArray.end(), tempArray[0], [](std::string a, std::string b) -> std::string - { - return std::move(a) + "`" + std::move(b); - }); - dest.emplace_back(strLine); - } - importItems(dest); -} - -void readRuleset(YAML::Node node, string_array &dest) -{ - std::string strLine, name, url, group; - YAML::Node object; - - for(unsigned int i = 0; i < node.size(); i++) - { - name = ""; - object = node[i]; - object["import"] >> name; - if(name.size()) - { - name = "!!import:" + name; - dest.emplace_back(name); - continue; - } - object["ruleset"] >> url; - object["group"] >> group; - object["rule"] >> name; - if(url.size()) - strLine = group + "," + url; - else if(name.size()) - strLine = group + ",[]" + name; - else - continue; - dest.emplace_back(strLine); - } - importItems(dest); -} - -void refreshRulesets(string_array &ruleset_list, std::vector &rca) -{ - eraseElements(rca); - std::string rule_group, rule_url; - ruleset_content rc; - - std::string proxy; - if(proxy_ruleset == "SYSTEM") - proxy = getSystemProxy(); - else if(proxy_ruleset == "NONE") - proxy = ""; - else - proxy = proxy_ruleset; - - for(std::string &x : ruleset_list) - { - /* - vArray = split(x, ","); - if(vArray.size() != 2) - continue; - rule_group = trim(vArray[0]); - rule_url = trim(vArray[1]); - */ - if(x.find(",") == x.npos) - continue; - rule_group = trim(x.substr(0, x.find(","))); - rule_url = trim(x.substr(x.find(",") + 1)); - if(rule_url.find("[]") == 0) - { - std::cerr<<"Adding rule '"<> api_mode; - section["api_access_token"] >> access_token; - if(section["default_url"].IsSequence()) - { - section["default_url"] >> tempArray; - if(tempArray.size()) - strLine = std::accumulate(std::next(tempArray.begin()), tempArray.end(), tempArray[0], [](std::string a, std::string b) - { - return std::move(a) + "|" + std::move(b); - }); - default_url = strLine; - } - if(section["exclude_remarks"].IsSequence()) - section["exclude_remarks"] >> def_exclude_remarks; - if(section["include_remarks"].IsSequence()) - section["include_remarks"] >> def_include_remarks; - section["clash_rule_base"] >> clash_rule_base; - section["surge_rule_base"] >> surge_rule_base; - section["surfboard_rule_base"] >> surfboard_rule_base; - section["mellow_rule_base"] >> mellow_rule_base; - section["quan_rule_base"] >> quan_rule_base; - section["quanx_rule_base"] >> quanx_rule_base; - - section["append_proxy_type"] >> append_proxy_type; - section["proxy_ruleset"] >> proxy_ruleset; - section["proxy_subscription"] >> proxy_subscription; - - if(node["userinfo"].IsDefined()) - { - section = node["userinfo"]; - if(section["stream_rule"].IsSequence()) - { - readRegexMatch(section["stream_rule"], "@", tempArray); - safe_set_streams(tempArray); - eraseElements(tempArray); - } - if(section["time_rule"].IsSequence()) - { - readRegexMatch(section["time_rule"], "@", tempArray); - safe_set_times(tempArray); - eraseElements(tempArray); - } - } - - if(node["node_pref"].IsDefined()) - { - section = node["node_pref"]; - section["udp_flag"] >> udp_flag; - section["tcp_fast_open_flag"] >> tfo_flag; - section["sort_flag"] >> do_sort; - section["skip_cert_verify_flag"] >> scv_flag; - } - - if(section["rename_node"].IsSequence()) - { - readRegexMatch(section["rename_node"], "@", tempArray); - safe_set_renames(tempArray); - eraseElements(tempArray); - } - - if(node["managed_config"].IsDefined()) - { - section = node["managed_config"]; - section["write_managed_config"] >> write_managed_config; - section["managed_config_prefix"] >> managed_config_prefix; - } - - if(node["surge_external_proxy"].IsDefined()) - node["surge_external_proxy"]["surge_ssr_path"] >> surge_ssr_path; - - if(node["emojis"].IsDefined()) - { - section = node["emojis"]; - section["add_emoji"] >> add_emoji; - section["remove_old_emoji"] >> remove_old_emoji; - if(section["rules"].IsSequence()) - { - readEmoji(section["rules"], tempArray); - safe_set_emojis(tempArray); - eraseElements(tempArray); - } - } - - if(node["ruleset"].IsDefined()) - { - section = node["ruleset"]; - section["enabled"] >> enable_rule_generator; - if(!enable_rule_generator) - { - overwrite_original_rules = false; - update_ruleset_on_request = false; - } - else - { - section["overwrite_original_rules"] >> overwrite_original_rules; - section["update_ruleset_on_request"] >> update_ruleset_on_request; - } - if(section["surge_ruleset"].IsSequence()) - readRuleset(section["surge_ruleset"], rulesets); - } - - if(node["proxy_group"].IsDefined() && node["proxy_group"]["custom_proxy_group"].IsDefined()) - readGroup(node["proxy_group"]["custom_proxy_group"], clash_extra_group); - - if(node["server"].IsDefined()) - { - node["server"]["listen"] >> listen_address; - node["server"]["port"] >> listen_port; - } - - if(node["advanced"].IsDefined()) - { - node["advanced"]["print_debug_info"] >> print_debug_info; - node["advanced"]["max_pending_connections"] >> max_pending_connections; - node["advanced"]["max_concurrent_threads"] >> max_concurrent_threads; - } -} - -void readConf() -{ - guarded_mutex guard(on_configuring); - std::cerr<<"Reading preference settings..."<> ext.clash_rule_base; - section["surge_rule_base"] >> ext.surge_rule_base; - section["surfboard_rule_base"] >> ext.surfboard_rule_base; - section["mellow_rule_base"] >> ext.mellow_rule_base; - section["quan_rule_base"] >> ext.quan_rule_base; - section["quanx_rule_base"] >> ext.quanx_rule_base; - - ext.enable_rule_generator = section["enable_rule_generator"].as(); - ext.overwrite_original_rules = section["overwrite_original_rules"].as(); - - if(section["custom_proxy_group"].size()) - readGroup(section["custom_proxy_group"], ext.custom_proxy_group); - - if(section["surge_ruleset"].size()) - readRuleset(section["surge_ruleset"], ext.surge_ruleset); - - if(section["rename_node"].size()) - readRegexMatch(section["rename_node"], "@", ext.rename); - - return 0; -} - -int loadExternalConfig(std::string &path, ExternalConfig &ext, std::string proxy) -{ - std::string base_content; - if(fileExist(path)) - base_content = fileGet(path, false, api_mode); - else - base_content = webGet(path, proxy); - - try - { - YAML::Node yaml = YAML::Load(base_content); - if(yaml.size() && yaml["custom"]) - return loadExternalYAML(yaml, ext); - } - catch (YAML::Exception &e) - { - //ignore - } - - INIReader ini; - ini.store_isolated_line = true; - ini.SetIsolatedItemsSection("custom"); - if(ini.Parse(base_content) != INIREADER_EXCEPTION_NONE) - { - std::cerr<<"Load external configuration failed. Reason: "< rca; - extra_settings ext; - std::string subInfo; - bool ruleset_updated = false; - - if(std::find(regex_blacklist.cbegin(), regex_blacklist.cend(), include) != regex_blacklist.cend() || std::find(regex_blacklist.cbegin(), regex_blacklist.cend(), exclude) != regex_blacklist.cend()) - return "Invalid request!"; - - //for external configuration - std::string ext_clash_base = clash_rule_base, ext_surge_base = surge_rule_base, ext_mellow_base = mellow_rule_base, ext_surfboard_base = surfboard_rule_base; - std::string ext_quan_base = quan_rule_base, ext_quanx_base = quanx_rule_base; - - //validate urls - if(!url.size()) - url = default_url; - if(!url.size() || !target.size()) - { - *status_code = 400; - return "Invalid request!"; - } - - //check if we need to read configuration - if(!api_mode || cfw_child_process) - readConf(); - - //check for proxy settings - std::string proxy; - if(proxy_subscription == "SYSTEM") - proxy = getSystemProxy(); - else if(proxy_subscription == "NONE") - proxy = ""; - else - proxy = proxy_subscription; - - ext.emoji_array = safe_get_emojis(); - ext.rename_array = safe_get_renames(); - - //load external configuration - if(config.size()) - { - std::cerr<<"External configuration file provided. Loading...\n"; - //read predefined data first - extra_group = clash_extra_group; - extra_ruleset = rulesets; - //then load external configuration - ExternalConfig extconf; - loadExternalConfig(config, extconf, proxy); - if(extconf.clash_rule_base.size()) - ext_clash_base = extconf.clash_rule_base; - if(extconf.surge_rule_base.size()) - ext_surge_base = extconf.surge_rule_base; - if(extconf.surfboard_rule_base.size()) - ext_surfboard_base = extconf.surfboard_rule_base; - if(extconf.mellow_rule_base.size()) - ext_mellow_base = extconf.mellow_rule_base; - if(extconf.quan_rule_base.size()) - ext_quan_base = extconf.quan_rule_base; - if(extconf.quanx_rule_base.size()) - ext_quanx_base = extconf.quanx_rule_base; - if(extconf.rename.size()) - ext.rename_array = extconf.rename; - if(extconf.emoji.size()) - ext.emoji_array = extconf.emoji; - ext.enable_rule_generator = extconf.enable_rule_generator; - //load custom group - if(extconf.custom_proxy_group.size()) - extra_group = extconf.custom_proxy_group; - //load custom rules - ext.overwrite_original_rules = extconf.overwrite_original_rules; - if(extconf.surge_ruleset.size()) - { - extra_ruleset = extconf.surge_ruleset; - refreshRulesets(extra_ruleset, rca); - ruleset_updated = true; - } - else - { - if(ext.enable_rule_generator) - { - if(update_ruleset_on_request) - refreshRulesets(rulesets, ruleset_content_array); - rca = ruleset_content_array; - } - } - } - else - { - //loading custom groups - if(groups.size()) - { - extra_group = split(groups, "@"); - if(!extra_group.size()) - extra_group = clash_extra_group; - } - else - extra_group = clash_extra_group; - - //loading custom rulesets - if(ruleset.size()) - { - extra_ruleset = split(ruleset, "@"); - if(!extra_ruleset.size()) - { - if(update_ruleset_on_request) - refreshRulesets(rulesets, ruleset_content_array); - rca = ruleset_content_array; - } - else - { - refreshRulesets(extra_ruleset, rca); - ruleset_updated = true; - } - } - else - { - if(enable_rule_generator) - { - if(update_ruleset_on_request || cfw_child_process) - refreshRulesets(rulesets, ruleset_content_array); - rca = ruleset_content_array; - } - } - } - - //check other flags - if(emoji.size()) - { - ext.add_emoji = ext.remove_emoji = emoji == "true"; - } - else - { - ext.add_emoji = add_emoji; - ext.remove_emoji = remove_old_emoji; - } - if(append_type.size()) - ext.append_proxy_type = append_type == "true"; - else - ext.append_proxy_type = append_proxy_type; - - ext.tfo = tfo.size() ? tfo == "true" : tfo_flag; - ext.udp = udp.size() ? udp == "true" : udp_flag; - ext.sort_flag = sort_flag.size() ? sort_flag == "true" : do_sort; - ext.skip_cert_verify = scv.size() ? scv == "true" : scv_flag; - ext.filter_deprecated = fdn.size() ? fdn == "true" : filter_deprecated; - - ext.nodelist = nodelist == "true"; - ext.surge_ssr_path = surge_ssr_path; - ext.enable_rule_generator = enable_rule_generator; - - //loading urls - string_array urls = split(url, "|"); - std::vector nodes; - int groupID = 0; - - //check custom include/exclude settings - if(include.size() && regValid(include)) - include_remarks.emplace_back(include); - else - include_remarks = def_include_remarks; - if(exclude.size() && regValid(exclude)) - exclude_remarks.emplace_back(exclude); - else - exclude_remarks = def_exclude_remarks; - - //start parsing urls - string_array stream_temp = safe_get_streams(), time_temp = safe_get_times(); - for(std::string &x : urls) - { - x = trim(x); - std::cerr<<"Fetching node data from url '"< nodes; - string_array extra_group, extra_ruleset, include_remarks, exclude_remarks; - std::vector rca; - std::string subInfo; - - if(!url.size()) - url = default_url; - if(!url.size() || argument.substr(0, 8) != "sublink=") - { - *status_code = 400; - return "Invalid request!"; - } - if(url == "sublink") - { - *status_code = 400; - return "Please insert your subscription link instead of clicking the default link."; - } - if(!api_mode || cfw_child_process) - readConf(); - - extra_group = clash_extra_group; - - if(update_ruleset_on_request || cfw_child_process) - refreshRulesets(rulesets, ruleset_content_array); - rca = ruleset_content_array; - - extra_settings ext = {true, overwrite_original_rules, safe_get_renames(), safe_get_emojis(), add_emoji, remove_old_emoji, append_proxy_type, udp_flag, tfo_flag, false, do_sort, scv_flag, filter_deprecated, ""}; - - std::string proxy; - if(proxy_subscription == "SYSTEM") - proxy = getSystemProxy(); - else if(proxy_subscription == "NONE") - proxy = ""; - else - proxy = proxy_subscription; - - include_remarks = def_include_remarks; - exclude_remarks = def_exclude_remarks; - - //start parsing urls - int groupID = 0; - string_array dummy; - string_array urls = split(url, "|"); - for(std::string &x : urls) - { - x = trim(x); - std::cerr<<"Fetching node data from url '"< std::string { return subconverter(argument + "&target=clash", postdata, status_code, extra_headers); diff --git a/src/subexport.cpp b/src/subexport.cpp index a8f5e0c..a992fcd 100644 --- a/src/subexport.cpp +++ b/src/subexport.cpp @@ -819,7 +819,7 @@ std::string netchToSurge(std::vector &nodes, std::string &base_conf, s x.remarks = "[" + type + "]" + x.remarks; remark = x.remarks; - while(std::count(remarks_list.begin(), remarks_list.end(), remark) > 0) + while(std::count(remarks_list.begin(), remarks_list.end(), x.remarks) > 0) x.remarks += "$"; remark = x.remarks; diff --git a/src/subexport.h b/src/subexport.h index a61c037..c967951 100644 --- a/src/subexport.h +++ b/src/subexport.h @@ -2,6 +2,11 @@ #define SUBEXPORT_H_INCLUDED #include +#include + +#include "misc.h" +#include "ini_reader.h" +#include "nodeinfo.h" struct ruleset_content {