#include #include #include #include #include "../../handler/webget.h" #include "../../parser/config/proxy.h" #include "../../parser/infoparser.h" #include "../../parser/subparser.h" #include "../../script/script_quickjs.h" #include "../../utils/file_extra.h" #include "../../utils/logger.h" #include "../../utils/map_extra.h" #include "../../utils/network.h" #include "../../utils/regexp.h" #include "../../utils/urlencode.h" #include "nodemanip.h" #include "subexport.h" std::string override_conf_port; bool ss_libev, ssr_libev; extern int gCacheSubscription; extern bool gScriptCleanContext; int explodeConf(const std::string &filepath, std::vector &nodes) { return explodeConfContent(fileGet(filepath), nodes); } void copyNodes(std::vector &source, std::vector &dest) { std::move(source.begin(), source.end(), std::back_inserter(dest)); } int addNodes(std::string link, std::vector &allNodes, int groupID, parse_settings &parse_set) { std::string &proxy = *parse_set.proxy, &subInfo = *parse_set.sub_info; string_array &exclude_remarks = *parse_set.exclude_remarks; string_array &include_remarks = *parse_set.include_remarks; RegexMatchConfigs &stream_rules = *parse_set.stream_rules; RegexMatchConfigs &time_rules = *parse_set.time_rules; string_icase_map &request_headers = *parse_set.request_header; bool &authorized = parse_set.authorized; ConfType linkType = ConfType::Unknow; std::vector nodes; Proxy node; std::string strSub, extra_headers, custom_group; // TODO: replace with startsWith if appropriate link = replaceAllDistinct(link, "\"", ""); /// script:filepath,arg1,arg2,... script_safe_runner(parse_set.js_runtime, parse_set.js_context, [&](qjs::Context &ctx) { if(startsWith(link, "script:")) /// process subscription with script { writeLog(0, "Found script link. Start running...", LOG_LEVEL_INFO); string_array args = split(link.substr(7), ","); if(args.size() >= 1) { std::string script = fileGet(args[0], false); try { ctx.eval(script); args.erase(args.begin()); /// remove script path auto parse = (std::function) ctx.eval("parse"); switch(args.size()) { case 0: link = parse(std::string(), string_array()); break; case 1: link = parse(args[0], string_array()); break; default: { std::string first = args[0]; args.erase(args.begin()); link = parse(first, args); break; } } } catch(qjs::exception) { script_print_stack(ctx); } } } }, gScriptCleanContext); /* duk_context *ctx = duktape_init(); defer(duk_destroy_heap(ctx);) duktape_peval(ctx, script); duk_get_global_string(ctx, "parse"); for(size_t i = 1; i < args.size(); i++) duk_push_string(ctx, trim(args[i]).c_str()); if(duk_pcall(ctx, args.size() - 1) == 0) link = duktape_get_res_str(ctx); else { writeLog(0, "Error when trying to evaluate script:\n" + duktape_get_err_stack(ctx), LOG_LEVEL_ERROR); duk_pop(ctx); /// pop err } */ /// tag:group_name,link if(startsWith(link, "tag:")) { string_size pos = link.find(","); if(pos != link.npos) { custom_group = link.substr(4, pos - 4); link.erase(0, pos + 1); } } if(link == "nullnode") { node.GroupId = 0; writeLog(0, "Adding node placeholder..."); allNodes.emplace_back(std::move(node)); return 0; } writeLog(LOG_TYPE_INFO, "Received Link."); if(startsWith(link, "https://t.me/socks") || startsWith(link, "tg://socks")) linkType = ConfType::SOCKS; else if(startsWith(link, "https://t.me/http") || startsWith(link, "tg://http")) linkType = ConfType::HTTP; else if(isLink(link) || startsWith(link, "surge:///install-config")) linkType = ConfType::SUB; else if(startsWith(link, "Netch://")) linkType = ConfType::Netch; else if(fileExist(link)) linkType = ConfType::Local; switch(linkType) { case ConfType::SUB: writeLog(LOG_TYPE_INFO, "Downloading subscription data..."); if(startsWith(link, "surge:///install-config")) //surge config link link = urlDecode(getUrlArg(link, "url")); strSub = webGet(link, proxy, gCacheSubscription, &extra_headers, &request_headers); /* if(strSub.size() == 0) { //try to get it again with system proxy writeLog(LOG_TYPE_WARN, "Cannot download subscription directly. Using system proxy."); strProxy = getSystemProxy(); if(strProxy != "") { strSub = webGet(link, strProxy); } else writeLog(LOG_TYPE_WARN, "No system proxy is set. Skipping."); } */ if(strSub.size()) { writeLog(LOG_TYPE_INFO, "Parsing subscription data..."); if(explodeConfContent(strSub, nodes) == 0) { writeLog(LOG_TYPE_ERROR, "Invalid subscription!"); return -1; } if(startsWith(strSub, "ssd://")) { getSubInfoFromSSD(strSub, subInfo); } else { if(!getSubInfoFromHeader(extra_headers, subInfo)) getSubInfoFromNodes(nodes, stream_rules, time_rules, subInfo); } filterNodes(nodes, exclude_remarks, include_remarks, groupID); for(Proxy &x : nodes) { x.GroupId = groupID; if(custom_group.size()) x.Group = custom_group; } copyNodes(nodes, allNodes); } else { writeLog(LOG_TYPE_ERROR, "Cannot download subscription data."); return -1; } break; case ConfType::Local: if(!authorized) return -1; writeLog(LOG_TYPE_INFO, "Parsing configuration file data..."); if(explodeConf(link, nodes) == 0) { writeLog(LOG_TYPE_ERROR, "Invalid configuration file!"); return -1; } if(startsWith(strSub, "ssd://")) { getSubInfoFromSSD(strSub, subInfo); } else { getSubInfoFromNodes(nodes, stream_rules, time_rules, subInfo); } filterNodes(nodes, exclude_remarks, include_remarks, groupID); for(Proxy &x : nodes) { x.GroupId = groupID; if(custom_group.size()) x.Group = custom_group; } copyNodes(nodes, allNodes); break; default: explode(link, node); if(node.Type == -1) { writeLog(LOG_TYPE_ERROR, "No valid link found."); return -1; } node.GroupId = groupID; if(custom_group.size()) node.Group = custom_group; allNodes.emplace_back(std::move(node)); } return 0; } bool chkIgnore(const Proxy &node, string_array &exclude_remarks, string_array &include_remarks) { bool excluded = false, included = false; //std::string remarks = UTF8ToACP(node.remarks); //std::string remarks = node.remarks; //writeLog(LOG_TYPE_INFO, "Comparing exclude remarks..."); excluded = std::any_of(exclude_remarks.cbegin(), exclude_remarks.cend(), [&node](const auto &x) { std::string real_rule; if(applyMatcher(x, real_rule, node)) { if(real_rule.empty()) return true; return regFind(node.Remark, real_rule); } else return false; }); if(include_remarks.size() != 0) { //writeLog(LOG_TYPE_INFO, "Comparing include remarks..."); included = std::any_of(include_remarks.cbegin(), include_remarks.cend(), [&node](const auto &x) { std::string real_rule; if(applyMatcher(x, real_rule, node)) { if(real_rule.empty()) return true; return regFind(node.Remark, real_rule); } else return false; }); } else { included = true; } return excluded || !included; } void filterNodes(std::vector &nodes, string_array &exclude_remarks, string_array &include_remarks, int groupID) { int node_index = 0; std::vector::iterator iter = nodes.begin(); while(iter != nodes.end()) { if(chkIgnore(*iter, exclude_remarks, include_remarks)) { writeLog(LOG_TYPE_INFO, "Node " + iter->Group + " - " + iter->Remark + " has been ignored and will not be added."); nodes.erase(iter); } else { writeLog(LOG_TYPE_INFO, "Node " + iter->Group + " - " + iter->Remark + " has been added."); iter->Id = node_index; iter->GroupId = groupID; ++node_index; ++iter; } } /* std::vector> exclude_patterns, include_patterns; std::vector> exclude_match_data, include_match_data; unsigned int i = 0; PCRE2_SIZE erroroffset; int errornumber, rc; for(i = 0; i < exclude_remarks.size(); i++) { std::unique_ptr pattern(pcre2_compile(reinterpret_cast(exclude_remarks[i].c_str()), exclude_remarks[i].size(), PCRE2_UTF | PCRE2_MULTILINE | PCRE2_ALT_BSUX, &errornumber, &erroroffset, NULL), &pcre2_code_free); if(!pattern) return; exclude_patterns.emplace_back(std::move(pattern)); pcre2_jit_compile(exclude_patterns[i].get(), 0); std::unique_ptr match_data(pcre2_match_data_create_from_pattern(exclude_patterns[i].get(), NULL), &pcre2_match_data_free); exclude_match_data.emplace_back(std::move(match_data)); } for(i = 0; i < include_remarks.size(); i++) { std::unique_ptr pattern(pcre2_compile(reinterpret_cast(include_remarks[i].c_str()), include_remarks[i].size(), PCRE2_UTF | PCRE2_MULTILINE | PCRE2_ALT_BSUX, &errornumber, &erroroffset, NULL), &pcre2_code_free); if(!pattern) return; include_patterns.emplace_back(std::move(pattern)); pcre2_jit_compile(include_patterns[i].get(), 0); std::unique_ptr match_data(pcre2_match_data_create_from_pattern(include_patterns[i].get(), NULL), &pcre2_match_data_free); include_match_data.emplace_back(std::move(match_data)); } writeLog(LOG_TYPE_INFO, "Filter started."); while(iter != nodes.end()) { bool excluded = false, included = false; for(i = 0; i < exclude_patterns.size(); i++) { rc = pcre2_match(exclude_patterns[i].get(), reinterpret_cast(iter->remarks.c_str()), iter->remarks.size(), 0, 0, exclude_match_data[i].get(), NULL); if (rc < 0) { switch(rc) { case PCRE2_ERROR_NOMATCH: break; default: return; } } else excluded = true; } if(include_patterns.size() > 0) for(i = 0; i < include_patterns.size(); i++) { rc = pcre2_match(include_patterns[i].get(), reinterpret_cast(iter->remarks.c_str()), iter->remarks.size(), 0, 0, include_match_data[i].get(), NULL); if (rc < 0) { switch(rc) { case PCRE2_ERROR_NOMATCH: break; default: return; } } else included = true; } else included = true; if(excluded || !included) { writeLog(LOG_TYPE_INFO, "Node " + iter->group + " - " + iter->remarks + " has been ignored and will not be added."); nodes.erase(iter); } else { writeLog(LOG_TYPE_INFO, "Node " + iter->group + " - " + iter->remarks + " has been added."); iter->id = node_index; iter->groupID = groupID; ++node_index; ++iter; } } */ writeLog(LOG_TYPE_INFO, "Filter done."); } bool matchRange(const std::string &range, int target) { string_array vArray = split(range, ","); bool match = false; std::string range_begin_str, range_end_str; int range_begin, range_end; static const std::string reg_num = "-?\\d+", reg_range = "(\\d+)-(\\d+)", reg_not = "\\!-?(\\d+)", reg_not_range = "\\!(\\d+)-(\\d+)", reg_less = "(\\d+)-", reg_more = "(\\d+)\\+"; for(std::string &x : vArray) { if(regMatch(x, reg_num)) { if(to_int(x, INT_MAX) == target) match = true; } else if(regMatch(x, reg_range)) { /* range_begin = to_int(regReplace(x, reg_range, "$1"), INT_MAX); range_end = to_int(regReplace(x, reg_range, "$2"), INT_MIN); */ regGetMatch(x, reg_range, 3, 0, &range_begin_str, &range_end_str); range_begin = to_int(range_begin_str, INT_MAX); range_end = to_int(range_end_str, INT_MIN); if(target >= range_begin && target <= range_end) match = true; } else if(regMatch(x, reg_not)) { if(to_int(regReplace(x, reg_not, "$1"), INT_MAX) == target) match = false; } else if(regMatch(x, reg_not_range)) { /* range_begin = to_int(regReplace(x, reg_range, "$1"), INT_MAX); range_end = to_int(regReplace(x, reg_range, "$2"), INT_MIN); */ regGetMatch(x, reg_range, 3, 0, &range_begin_str, &range_end_str); range_begin = to_int(range_begin_str, INT_MAX); range_end = to_int(range_end_str, INT_MIN); if(target >= range_begin && target <= range_end) match = false; } else if(regMatch(x, reg_less)) { if(to_int(regReplace(x, reg_less, "$1"), INT_MAX) >= target) match = true; } else if(regMatch(x, reg_more)) { if(to_int(regReplace(x, reg_more, "$1"), INT_MIN) <= target) match = true; } } return match; } bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy &node) { std::string target, ret_real_rule; static const std::string groupid_regex = R"(^!!(?:GROUPID|INSERT)=([\d\-+!,]+)(?:!!(.*))?$)", group_regex = R"(^!!(?:GROUP)=(.+?)(?:!!(.*))?$)"; static const std::string type_regex = R"(^!!(?:TYPE)=(.+?)(?:!!(.*))?$)", port_regex = R"(^!!(?:PORT)=(.+?)(?:!!(.*))?$)", server_regex = R"(^!!(?:SERVER)=(.+?)(?:!!(.*))?$)"; static const string_array types = {"", "SS", "SSR", "VMESS", "TROJAN", "SNELL", "HTTP", "HTTPS", "SOCKS5"}; if(startsWith(rule, "!!GROUP=")) { regGetMatch(rule, group_regex, 3, 0, &target, &ret_real_rule); real_rule = ret_real_rule; return regFind(node.Group, target); } else if(startsWith(rule, "!!GROUPID=") || startsWith(rule, "!!INSERT=")) { int dir = startsWith(rule, "!!INSERT=") ? -1 : 1; regGetMatch(rule, groupid_regex, 3, 0, &target, &ret_real_rule); real_rule = ret_real_rule; return matchRange(target, dir * node.GroupId); } else if(startsWith(rule, "!!TYPE=")) { regGetMatch(rule, type_regex, 3, 0, &target, &ret_real_rule); real_rule = ret_real_rule; if(node.Type == ProxyType::Unknow) return false; return regMatch(types[node.Type], target); } else if(startsWith(rule, "!!PORT=")) { regGetMatch(rule, port_regex, 3, 0, &target, &ret_real_rule); real_rule = ret_real_rule; return matchRange(target, node.Port); } else if(startsWith(rule, "!!SERVER=")) { regGetMatch(rule, server_regex, 3, 0, &target, &ret_real_rule); real_rule = ret_real_rule; return regFind(node.Hostname, target); } else real_rule = rule; return true; }