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 {