Enhancements

Add basic support for generating sing-box configs.
Use cpp-httplib as web server.
Optimize codes.
This commit is contained in:
Tindy X 2023-11-07 22:12:01 +08:00
parent f99748b126
commit bbcb6434e1
No known key found for this signature in database
GPG Key ID: 7FA1E66774759718
32 changed files with 10286 additions and 234 deletions

View File

@ -157,7 +157,7 @@ jobs:
- uses: msys2/setup-msys2@v2
with:
update: true
install: base-devel git mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-libevent mingw-w64-x86_64-pcre2 patch
install: base-devel git mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-pcre2 patch
msystem: MINGW64
path-type: inherit
- name: Add commit id into version
@ -197,7 +197,7 @@ jobs:
- uses: msys2/setup-msys2@v2
with:
update: true
install: base-devel git mingw-w64-i686-gcc mingw-w64-i686-cmake mingw-w64-i686-libevent mingw-w64-i686-pcre2 patch
install: base-devel git mingw-w64-i686-gcc mingw-w64-i686-cmake mingw-w64-i686-pcre2 patch
msystem: MINGW32
path-type: inherit
- name: Add commit id into version

View File

@ -46,7 +46,8 @@ ADD_EXECUTABLE(${BUILD_TARGET_NAME}
src/parser/subparser.cpp
src/script/cron.cpp
src/script/script_quickjs.cpp
src/server/webserver_libevent.cpp
# src/server/webserver_libevent.cpp
src/server/webserver_httplib.cpp
src/utils/base64/base64.cpp
src/utils/codepage.cpp
src/utils/file.cpp
@ -66,12 +67,12 @@ SET(THREADS_PREFER_PTHREAD_FLAG ON)
FIND_PACKAGE(Threads REQUIRED)
TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT})
PKG_CHECK_MODULES(LIBEVENT libevent>=2.1.10 REQUIRED)
FIND_PATH(LIBEVENT_INCLUDE_DIR NAMES event.h PATHS ${LIBEVENT_INCLUDE_DIRS})
FIND_LIBRARY(LIBEVENT_LIBRARY NAMES event PATHS ${LIBEVENT_LIBRARY_DIRS})
TARGET_LINK_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${LIBEVENT_LIBRARY_DIRS})
TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${LIBEVENT_INCLUDE_DIR})
TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} ${LIBEVENT_LIBRARY})
#PKG_CHECK_MODULES(LIBEVENT libevent>=2.1.10 REQUIRED)
#FIND_PATH(LIBEVENT_INCLUDE_DIR NAMES event.h PATHS ${LIBEVENT_INCLUDE_DIRS})
#FIND_LIBRARY(LIBEVENT_LIBRARY NAMES event PATHS ${LIBEVENT_LIBRARY_DIRS})
#TARGET_LINK_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${LIBEVENT_LIBRARY_DIRS})
#TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${LIBEVENT_INCLUDE_DIR})
#TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} ${LIBEVENT_LIBRARY})
FIND_PACKAGE(CURL 7.54.0 REQUIRED)
TARGET_LINK_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${CURL_LIBRARY_DIRS})
@ -113,7 +114,7 @@ ELSE()
INSTALL(DIRECTORY base/ DESTINATION ${CMAKE_INSTALL_BINDIR}/${BUILD_TARGET_NAME} FILES_MATCHING PATTERN "*")
ENDIF()
ELSE()
ELSE() #BUILD_STATIC_LIBRARY
ADD_LIBRARY(${BUILD_TARGET_NAME} STATIC
src/generator/config/ruleconvert.cpp
@ -154,7 +155,7 @@ IF(WIN32)
TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} PRIVATE ws2_32)
ENDIF()
ENDIF()
ENDIF() #BUILD_STATIC_LIBRARY
IF(HAVE_TO_STRING)
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE -DHAVE_TO_STRING)

View File

@ -266,3 +266,33 @@ enhanced-mode-by-rule = true
}
{% endif %}
{% if request.target == "singbox" %}
{
"log": {
"disabled": false,
"level": "info",
"output": "box.log",
"timestamp": true
},
"dns": {},
"ntp": {
"enabled": false,
"server": "time.apple.com",
"server_port": 123,
"interval": "30m"
},
"inbounds": [
{
"type": "socks",
"tag": "socks-in",
"listen": "127.0.0.1",
"listen_port": 2080
}
],
"outbounds": [],
"route": {},
"experimental": {}
}
{% endif %}

26
base/base/singbox.json Normal file
View File

@ -0,0 +1,26 @@
{
"log": {
"disabled": false,
"level": "info",
"output": "box.log",
"timestamp": true
},
"dns": {},
"ntp": {
"enabled": false,
"server": "time.apple.com",
"server_port": 123,
"interval": "30m"
},
"inbounds": [
{
"type": "socks",
"tag": "socks-in",
"listen": "127.0.0.1",
"listen_port": 2080
}
],
"outbounds": [],
"route": {},
"experimental": {}
}

View File

@ -43,6 +43,9 @@ clash_rule_base=base/forcerule.yml
;mellow_rule_base=base/mellow.conf
;quan_rule_base=base/quan.conf
;quanx_rule_base=base/quanx.conf
;loon_rule_base=base/loon.conf
;sssub_rule_base=base/shadowsocks_base.json
;singbox_rule_base=base/singbox.json
;Options for renaming nodes
;rename=Test-(.*?)-(.*?)-(.*?)\((.*?)\)@\1\4x测试线路_自\2到\3

View File

@ -10,6 +10,9 @@ clash_rule_base = "base/forcerule.yml"
#mellow_rule_base = "base/mellow.conf"
#quan_rule_base = "base/quan.conf"
#quanx_rule_base = "base/quanx.conf"
#loon_rule_base = "base/loon.conf"
#sssub_rule_base = "base/shadowsocks_base.json"
#singbox_rule_base = "base/singbox.json"
# Options for adding emojis
#add_emoji = true

View File

@ -14,6 +14,9 @@ custom:
# mellow_rule_base: base/mellow.conf
# quan_rule_base: base/quan.conf
# quanx_rule_base: base/quanx.conf
# loon_rule_base: base/loon.conf
# sssub_rule_base: base/shadowsocks_base.json
# singbox_rule_base: base/singbox.json
# rename_node:
# - {import: snippet/rename.txt}

View File

@ -26,7 +26,7 @@ exclude_remarks=(到期|剩余流量|时间|官网|产品|平台)
enable_filter=false
;Script used for filtering nodes. Supports inline script and script path. A "filter" function with 1 argument which is a node should be defined in the script.
;Example: Inline script: Set value to content of script. Replace all line break with "\n".
; Script path: Set value to "path:/path/to/script.js".
; Script path: set value to "path:/path/to/script.js".
;filter_script=function filter(node) {\n const info = JSON.parse(node.ProxyInfo);\n if(info.EncryptMethod.includes('chacha20'))\n return true;\n return false;\n}
;Setting an external config file as default when none is specified, supports local files/URL
@ -59,6 +59,9 @@ loon_rule_base=base/all_base.tpl
;Shadowsocks Android config base used by the generator, supports local files/URL
sssub_rule_base=base/all_base.tpl
;sing-box config base used by the generator, supports local files/URL
singbox_rule_base=base/all_base.tpl
;Proxy used to download configs, rulesets or subscriptions, set to NONE or empty to disable it, set to SYSTEM to use system proxy.
;Accept cURL-supported proxies (http:// https:// socks4a:// socks5://)
;Additional support for CORS proxy ( https://github.com/Rob--W/cors-anywhere https://github.com/Zibri/cloudflare-cors-anywhere etc.), prefix the address with "cors:" to recognize the address as CORS proxy.
@ -237,6 +240,7 @@ clash.log_level=info
/mellow=/sub?target=mellow
/surfboard=/sub?target=surfboard
/loon=/sub?target=loon
/singbox=/sub?target=singbox
/ss=/sub?target=ss
/ssd=/sub?target=ssd
/sssub=/sub?target=sssub

View File

@ -25,8 +25,8 @@ exclude_remarks = ["(到期|剩余流量|时间|官网|产品)"]
# Enable script support for filtering nodes
enable_filter = false
# Script used for filtering nodes. Supports inline script and script path. A "filter" function with 1 argument which is a node should be defined in the script.
# Example: Inline script: Set value to content of script.
# Script path: Set value to "path:/path/to/script.js".
# Example: Inline script: set value to content of script.
# Script path: set value to "path:/path/to/script.js".
#filter_script = '''
#function filter(node) {
# const info = JSON.parse(node.ProxyInfo);
@ -66,6 +66,9 @@ loon_rule_base = "base/all_base.tpl"
# Shadowsocks Android config base used by the generator, supports local files/URL
sssub_rule_base = "base/all_base.tpl"
# sing-box config base used by the generator, supports local files/URL
singbox_rule_base = "base/all_base.tpl"
# Proxy used to download rulesets or subscriptions, set to NONE or empty to disable it, set to SYSTEM to use system proxy.
# Accept cURL-supported proxies (http:// https:// socks4a:// socks5://)
@ -266,6 +269,10 @@ target = "/sub?target=surfboard"
uri = "/loon"
target = "/sub?target=loon"
[[aliases]]
uri = "/singbox"
target = "/sub?target=singbox"
[[aliases]]
uri = "/ss"
target = "/sub?target=ss"

View File

@ -19,6 +19,7 @@ common:
quanx_rule_base: base/all_base.tpl
loon_rule_base: base/all_base.tpl
sssub_rule_base: base/all_base.tpl
singbox_rule_base: base/all_base.tpl
proxy_config: SYSTEM
proxy_ruleset: SYSTEM
proxy_subscription: NONE
@ -116,6 +117,7 @@ aliases:
- {uri: /mellow, target: "/sub?target=mellow"}
- {uri: /surfboard, target: "/sub?target=surfboard"}
- {uri: /loon, target: "/sub?target=loon"}
- {uri: /singbox, target: "/sub?target=singbox"}
- {uri: /ss, target: "/sub?target=ss"}
- {uri: /ssd, target: "/sub?target=ssd"}
- {uri: /sssub, target: "/sub?target=sssub"}

9262
include/httplib.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ ARG SHA=""
WORKDIR /
RUN set -xe && \
apk add --no-cache --virtual .build-tools git g++ build-base linux-headers cmake python3 && \
apk add --no-cache --virtual .build-deps curl-dev rapidjson-dev libevent-dev pcre2-dev yaml-cpp-dev && \
apk add --no-cache --virtual .build-deps curl-dev rapidjson-dev pcre2-dev yaml-cpp-dev && \
git clone https://github.com/ftk/quickjspp --depth=1 && \
cd quickjspp && \
git submodule update --init && \
@ -48,7 +48,7 @@ RUN set -xe && \
FROM alpine:3.16
LABEL maintainer="tindy.it@gmail.com"
RUN apk add --no-cache --virtual subconverter-deps pcre2 libcurl yaml-cpp libevent
RUN apk add --no-cache --virtual subconverter-deps pcre2 libcurl yaml-cpp
COPY --from=builder /subconverter/subconverter /usr/bin/
COPY --from=builder /subconverter/base /base/

View File

@ -2,7 +2,7 @@
set -xe
apk add gcc g++ build-base linux-headers cmake make autoconf automake libtool python2 python3
apk add mbedtls-dev mbedtls-static zlib-dev rapidjson-dev libevent-dev libevent-static zlib-static pcre2-dev
apk add mbedtls-dev mbedtls-static zlib-dev rapidjson-dev zlib-static pcre2-dev
git clone https://github.com/curl/curl --depth=1 --branch curl-8_4_0
cd curl
@ -45,7 +45,7 @@ cmake -DCMAKE_BUILD_TYPE=Release .
make -j3
rm subconverter
# shellcheck disable=SC2046
g++ -o base/subconverter $(find CMakeFiles/subconverter.dir/src/ -name "*.o") -static -lpcre2-8 -levent -lyaml-cpp -L/usr/lib64 -lcurl -lmbedtls -lmbedcrypto -lmbedx509 -lz -l:quickjs/libquickjs.a -llibcron -O3 -s
g++ -o base/subconverter $(find CMakeFiles/subconverter.dir/src/ -name "*.o") -static -lpcre2-8 -lyaml-cpp -L/usr/lib64 -lcurl -lmbedtls -lmbedcrypto -lmbedx509 -lz -l:quickjs/libquickjs.a -llibcron -O3 -s
python3 -m ensurepip
python3 -m pip install gitpython

View File

@ -1,7 +1,7 @@
#!/bin/bash
set -xe
brew reinstall rapidjson libevent zlib pcre2 pkgconfig
brew reinstall rapidjson zlib pcre2 pkgconfig
#git clone https://github.com/curl/curl --depth=1 --branch curl-7_88_1
#cd curl
@ -46,13 +46,13 @@ cmake -DCMAKE_CXX_STANDARD=11 .
make install -j4
cd ..
cp /usr/local/lib/libevent.a .
cp /usr/local/opt/zlib/lib/libz.a .
cp /usr/local/lib/libpcre2-8.a .
cmake -DCMAKE_BUILD_TYPE=Release .
make -j8
rm subconverter
# shellcheck disable=SC2046
c++ -Xlinker -unexported_symbol -Xlinker "*" -o base/subconverter -framework CoreFoundation -framework Security $(find CMakeFiles/subconverter.dir/src/ -name "*.o") $(find . -name "*.a") -lcurl -O3
python -m ensurepip

View File

@ -52,5 +52,6 @@ rm -f C:/Strawberry/perl/bin/pkg-config C:/Strawberry/perl/bin/pkg-config.bat
cmake -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" .
make -j4
rm subconverter.exe
g++ $(find CMakeFiles/subconverter.dir/src -name "*.obj") curl/lib/libcurl.a -o base/subconverter.exe -static -lbcrypt -levent -lpcre2-8 -l:quickjs/libquickjs.a -llibcron -lyaml-cpp -liphlpapi -lcrypt32 -lws2_32 -lwsock32 -lz -s
# shellcheck disable=SC2046
g++ $(find CMakeFiles/subconverter.dir/src -name "*.obj") curl/lib/libcurl.a -o base/subconverter.exe -static -lbcrypt -lpcre2-8 -l:quickjs/libquickjs.a -llibcron -lyaml-cpp -liphlpapi -lcrypt32 -lws2_32 -lwsock32 -lz -s
mv base subconverter

View File

@ -3,7 +3,7 @@ set -xe
apt update
apt install -y git cmake clang pkg-config
apt install -y libevent libcurl openssl pcre2
apt install -y libcurl openssl pcre2
git clone https://github.com/jbeder/yaml-cpp --depth=1
cd yaml-cpp

View File

@ -5,6 +5,7 @@
#include "../../utils/network.h"
#include "../../utils/regexp.h"
#include "../../utils/string.h"
#include "../../utils/rapidjson_extra.h"
#include "subexport.h"
/// rule type lists
@ -28,7 +29,7 @@ std::string convertRuleset(const std::string &content, int type)
if(regFind(content, "^payload:\\r?\\n")) /// Clash
{
output = regReplace(regReplace(content, "payload:\\r?\\n", "", true), "\\s?^\\s*-\\s+('|\"?)(.*)\\1$", "\n$2", true);
output = regReplace(regReplace(content, "payload:\\r?\\n", "", true), R"(\s?^\s*-\s+('|"?)(.*)\1$)", "\n$2", true);
if(type == RULESET_CLASH_CLASSICAL) /// classical type
return output;
std::stringstream ss;
@ -51,8 +52,8 @@ std::string convertRuleset(const std::string &content, int type)
if(!strLine.empty() && (strLine[0] != ';' && strLine[0] != '#' && !(lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')))
{
pos = strLine.find("/");
if(pos != strLine.npos) /// ipcidr
pos = strLine.find('/');
if(pos != std::string::npos) /// ipcidr
{
if(isIPv4(strLine.substr(0, pos)))
output += "IP-CIDR,";
@ -143,7 +144,7 @@ void rulesetToClash(YAML::Node &base_rule, std::vector<RulesetContent> &ruleset_
lineSize = strLine.size();
if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored
continue;
if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);}))
if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);}))
continue;
if(strFind(strLine, "//"))
{
@ -218,7 +219,7 @@ std::string rulesetToClashStr(YAML::Node &base_rule, std::vector<RulesetContent>
lineSize = strLine.size();
if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored
continue;
if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](std::string type){ return startsWith(strLine, type); }))
if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](const std::string& type){ return startsWith(strLine, type); }))
continue;
if(strFind(strLine, "//"))
{
@ -235,7 +236,7 @@ std::string rulesetToClashStr(YAML::Node &base_rule, std::vector<RulesetContent>
return output_content;
}
void rulesetToSurge(INIReader &base_rule, std::vector<RulesetContent> &ruleset_content_array, int surge_ver, bool overwrite_original_rules, std::string remote_path_prefix)
void rulesetToSurge(INIReader &base_rule, std::vector<RulesetContent> &ruleset_content_array, int surge_ver, bool overwrite_original_rules, const std::string &remote_path_prefix)
{
string_array allRules;
std::string rule_group, rule_path, rule_path_typed, retrieved_rules, strLine;
@ -268,6 +269,8 @@ void rulesetToSurge(INIReader &base_rule, std::vector<RulesetContent> &ruleset_c
case -4:
base_rule.erase_section("Remote Rule");
break;
default:
break;
}
}
@ -313,7 +316,7 @@ void rulesetToSurge(INIReader &base_rule, std::vector<RulesetContent> &ruleset_c
}
if(fileExist(rule_path))
{
if(surge_ver > 2 && remote_path_prefix.size())
if(surge_ver > 2 && !remote_path_prefix.empty())
{
strLine = "RULE-SET," + remote_path_prefix + "/getruleset?type=1&url=" + urlSafeBase64Encode(rule_path_typed) + "," + rule_group;
if(x.update_interval)
@ -321,14 +324,14 @@ void rulesetToSurge(INIReader &base_rule, std::vector<RulesetContent> &ruleset_c
allRules.emplace_back(std::move(strLine));
continue;
}
else if(surge_ver == -1 && remote_path_prefix.size())
else if(surge_ver == -1 && !remote_path_prefix.empty())
{
strLine = remote_path_prefix + "/getruleset?type=2&url=" + urlSafeBase64Encode(rule_path_typed) + "&group=" + urlSafeBase64Encode(rule_group);
strLine += ", tag=" + rule_group + ", enabled=true";
base_rule.set("filter_remote", "{NONAME}", strLine);
continue;
}
else if(surge_ver == -4 && remote_path_prefix.size())
else if(surge_ver == -4 && !remote_path_prefix.empty())
{
strLine = remote_path_prefix + "/getruleset?type=1&url=" + urlSafeBase64Encode(rule_path_typed) + "," + rule_group;
base_rule.set("Remote Rule", "{NONAME}", strLine);
@ -341,7 +344,7 @@ void rulesetToSurge(INIReader &base_rule, std::vector<RulesetContent> &ruleset_c
{
if(x.rule_type != RULESET_SURGE)
{
if(remote_path_prefix.size())
if(!remote_path_prefix.empty())
strLine = "RULE-SET," + remote_path_prefix + "/getruleset?type=1&url=" + urlSafeBase64Encode(rule_path_typed) + "," + rule_group;
else
continue;
@ -355,7 +358,7 @@ void rulesetToSurge(INIReader &base_rule, std::vector<RulesetContent> &ruleset_c
allRules.emplace_back(std::move(strLine));
continue;
}
else if(surge_ver == -1 && remote_path_prefix.size())
else if(surge_ver == -1 && !remote_path_prefix.empty())
{
strLine = remote_path_prefix + "/getruleset?type=2&url=" + urlSafeBase64Encode(rule_path_typed) + "&group=" + urlSafeBase64Encode(rule_group);
strLine += ", tag=" + rule_group + ", enabled=true";
@ -401,22 +404,22 @@ void rulesetToSurge(INIReader &base_rule, std::vector<RulesetContent> &ruleset_c
continue;
[[fallthrough]];
case -1:
if(!std::any_of(QuanXRuleTypes.begin(), QuanXRuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);}))
if(!std::any_of(QuanXRuleTypes.begin(), QuanXRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);}))
continue;
break;
case -3:
if(!std::any_of(SurfRuleTypes.begin(), SurfRuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);}))
if(!std::any_of(SurfRuleTypes.begin(), SurfRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);}))
continue;
break;
default:
if(surge_ver > 2)
{
if(!std::any_of(SurgeRuleTypes.begin(), SurgeRuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);}))
if(!std::any_of(SurgeRuleTypes.begin(), SurgeRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);}))
continue;
}
else
{
if(!std::any_of(Surge2RuleTypes.begin(), Surge2RuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);}))
if(!std::any_of(Surge2RuleTypes.begin(), Surge2RuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);}))
continue;
}
}
@ -453,3 +456,100 @@ void rulesetToSurge(INIReader &base_rule, std::vector<RulesetContent> &ruleset_c
base_rule.set("{NONAME}", x);
}
}
static rapidjson::Value transformRuleToSingBox(const std::string& rule, rapidjson::MemoryPoolAllocator<>& allocator)
{
std::string type, value, group, option;
regGetMatch(rule, "^(.*?),(.*?)(?:,(.*?))?(,.*)?$", 5, nullptr, &type, &value, &group, &option);
rapidjson::Value rule_obj(rapidjson::kObjectType);
type = replaceAllDistinct(toLower(type), "-", "_");
type = replaceAllDistinct(type, "ip_cidr6", "ip_cidr");
if (type == "match" || type == "final") {
rule_obj.AddMember("outbound", rapidjson::Value(value.c_str(), allocator), allocator);
} else {
rule_obj.AddMember(rapidjson::Value(type.c_str(), allocator), rapidjson::Value(value.c_str(), allocator), allocator);
rule_obj.AddMember("outbound", rapidjson::Value(group.c_str(), allocator), allocator);
}
return rule_obj;
}
void rulesetToSingBox(rapidjson::Document &base_rule, std::vector<RulesetContent> &ruleset_content_array, bool overwrite_original_rules)
{
std::string rule_group, retrieved_rules, strLine, final;
std::stringstream strStrm;
size_t total_rules = 0;
rapidjson::MemoryPoolAllocator<>& allocator = base_rule.GetAllocator();
rapidjson::Value rules(rapidjson::kArrayType);
if (!overwrite_original_rules)
{
if (base_rule.HasMember("route") && base_rule["route"].HasMember("rules") && base_rule["route"]["rules"].IsArray())
rules.Swap(base_rule["route"]["rules"]);
}
for(RulesetContent &x : ruleset_content_array)
{
if(global.maxAllowedRules && total_rules > global.maxAllowedRules)
break;
rule_group = x.rule_group;
retrieved_rules = x.rule_content.get();
if(retrieved_rules.empty())
{
writeLog(0, "Failed to fetch ruleset or ruleset is empty: '" + x.rule_path + "'!", LOG_LEVEL_WARNING);
continue;
}
if(startsWith(retrieved_rules, "[]"))
{
strLine = retrieved_rules.substr(2);
if(startsWith(strLine, "FINAL") || startsWith(strLine, "MATCH"))
{
final = rule_group;
continue;
}
strLine += "," + rule_group;
if(count_least(strLine, ',', 3))
strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2");
rules.PushBack(transformRuleToSingBox(strLine, allocator), allocator);
total_rules++;
continue;
}
retrieved_rules = convertRuleset(retrieved_rules, x.rule_type);
char delimiter = getLineBreak(retrieved_rules);
strStrm.clear();
strStrm<<retrieved_rules;
std::string::size_type lineSize;
while(getline(strStrm, strLine, delimiter))
{
if(global.maxAllowedRules && total_rules > global.maxAllowedRules)
break;
strLine = trimWhitespace(strLine, true, true); //remove whitespaces
lineSize = strLine.size();
if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored
continue;
if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);}))
continue;
if(strFind(strLine, "//"))
{
strLine.erase(strLine.find("//"));
strLine = trimWhitespace(strLine);
}
strLine += "," + rule_group;
if(count_least(strLine, ',', 3))
strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2");
rules.PushBack(transformRuleToSingBox(strLine, allocator), allocator);
}
}
if (!base_rule.HasMember("route"))
base_rule.AddMember("route", rapidjson::Value(rapidjson::kObjectType), allocator);
if (!base_rule["route"].HasMember("rules"))
base_rule["route"].AddMember("rules", rapidjson::Value(rapidjson::kArrayType), allocator);
base_rule["route"]["rules"].Swap(rules);
if (!base_rule["route"].HasMember("final"))
base_rule["route"].AddMember("final", rapidjson::Value(final.c_str(), allocator), allocator);
else
base_rule["route"]["final"].SetString(final.c_str(), allocator);
}

View File

@ -6,6 +6,7 @@
#include <future>
#include <yaml-cpp/yaml.h>
#include <rapidjson/document.h>
#include "../../utils/ini_reader/ini_reader.h"
@ -31,6 +32,7 @@ struct RulesetContent
std::string convertRuleset(const std::string &content, int type);
void rulesetToClash(YAML::Node &base_rule, std::vector<RulesetContent> &ruleset_content_array, bool overwrite_original_rules, bool new_field_name);
std::string rulesetToClashStr(YAML::Node &base_rule, std::vector<RulesetContent> &ruleset_content_array, bool overwrite_original_rules, bool new_field_name);
void rulesetToSurge(INIReader &base_rule, std::vector<RulesetContent> &ruleset_content_array, int surge_ver, bool overwrite_original_rules, std::string remote_path_prefix);
void rulesetToSurge(INIReader &base_rule, std::vector<RulesetContent> &ruleset_content_array, int surge_ver, bool overwrite_original_rules, const std::string& remote_path_prefix);
void rulesetToSingBox(rapidjson::Document &base_rule, std::vector<RulesetContent> &ruleset_content_array, bool overwrite_original_rules);
#endif // RULECONVERT_H_INCLUDED

View File

@ -1817,7 +1817,6 @@ void proxyToMellow(std::vector<Proxy> &nodes, INIReader &ini, std::vector<Rulese
std::string proxyToLoon(std::vector<Proxy> &nodes, const std::string &base_conf, std::vector<RulesetContent> &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext)
{
rapidjson::Document json;
INIReader ini;
std::string output_nodelist;
std::vector<Proxy> nodelist;
@ -2042,3 +2041,303 @@ std::string proxyToLoon(std::vector<Proxy> &nodes, const std::string &base_conf,
return ini.to_string();
}
static std::string formatSingBoxInterval(Integer interval)
{
std::string result;
if(interval >= 3600)
{
result += std::to_string(interval / 3600) + "h";
interval %= 3600;
}
if(interval >= 60)
{
result += std::to_string(interval / 60) + "m";
interval %= 60;
}
if(interval > 0)
result += std::to_string(interval) + "s";
return result;
}
static rapidjson::Value buildV2RayTransport(const Proxy& proxy, rapidjson::MemoryPoolAllocator<>& allocator)
{
rapidjson::Value transport(rapidjson::kObjectType);
switch (hash_(proxy.TransferProtocol))
{
case "http"_hash:
{
if (!proxy.Host.empty())
transport.AddMember("host", rapidjson::StringRef(proxy.Host.c_str()), allocator);
[[fallthrough]];
}
case "ws"_hash:
{
transport.AddMember("type", rapidjson::StringRef(proxy.TransferProtocol.c_str()), allocator);
if (proxy.Path.empty())
transport.AddMember("path", "/", allocator);
else
transport.AddMember("path", rapidjson::StringRef(proxy.Path.c_str()), allocator);
rapidjson::Value headers(rapidjson::kObjectType);
if (!proxy.Host.empty())
headers.AddMember("Host", rapidjson::StringRef(proxy.Host.c_str()), allocator);
if (!proxy.Edge.empty())
headers.AddMember("Edge", rapidjson::StringRef(proxy.Edge.c_str()), allocator);
transport.AddMember("headers", headers, allocator);
break;
}
case "grpc"_hash:
{
transport.AddMember("type", "grpc", allocator);
if (!proxy.Path.empty())
transport.AddMember("service_name", rapidjson::StringRef(proxy.Path.c_str()), allocator);
break;
}
default:
break;
}
return transport;
}
void proxyToSingBox(std::vector<Proxy> &nodes, rapidjson::Document &json, std::vector<RulesetContent> &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) {
rapidjson::Document::AllocatorType &allocator = json.GetAllocator();
rapidjson::Value outbounds(rapidjson::kArrayType), route(rapidjson::kArrayType);
std::vector<Proxy> nodelist;
auto direct = buildObject(allocator, "type", "direct", "tag", "DIRECT");
outbounds.PushBack(direct, allocator);
auto reject = buildObject(allocator, "type", "block", "tag", "REJECT");
outbounds.PushBack(reject, allocator);
for (Proxy &x : nodes)
{
std::string type = getProxyTypeName(x.Type);
if (ext.append_proxy_type)
x.Remark = "[" + type + "] " + x.Remark;
tribool udp = ext.udp, tfo = ext.tfo, scv = ext.skip_cert_verify;
udp.define(x.UDP);
tfo.define(x.TCPFastOpen);
scv.define(x.AllowInsecure);
rapidjson::Value proxy(rapidjson::kObjectType);
switch (x.Type)
{
case ProxyType::Shadowsocks:
{
proxy.AddMember("type", "shadowsocks", allocator);
proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator);
proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator);
proxy.AddMember("server_port", x.Port, allocator);
proxy.AddMember("method", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator);
proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator);
if(!x.Plugin.empty() && !x.PluginOption.empty())
{
proxy.AddMember("plugin", rapidjson::StringRef(x.Plugin.c_str()), allocator);
proxy.AddMember("plugin_opts", rapidjson::StringRef(x.PluginOption.c_str()), allocator);
}
break;
}
case ProxyType::ShadowsocksR:
{
proxy.AddMember("type", "shadowsocksr", allocator);
proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator);
proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator);
proxy.AddMember("server_port", x.Port, allocator);
proxy.AddMember("method", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator);
proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator);
proxy.AddMember("protocol", rapidjson::StringRef(x.Protocol.c_str()), allocator);
proxy.AddMember("protocol_param", rapidjson::StringRef(x.ProtocolParam.c_str()), allocator);
proxy.AddMember("obfs", rapidjson::StringRef(x.OBFS.c_str()), allocator);
proxy.AddMember("obfs_param", rapidjson::StringRef(x.OBFSParam.c_str()), allocator);
break;
}
case ProxyType::VMess:
{
proxy.AddMember("type", "vmess", allocator);
proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator);
proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator);
proxy.AddMember("server_port", x.Port, allocator);
proxy.AddMember("uuid", rapidjson::StringRef(x.UserId.c_str()), allocator);
proxy.AddMember("alter_id", x.AlterId, allocator);
proxy.AddMember("cipher", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator);
auto transport = buildV2RayTransport(x, allocator);
proxy.AddMember("transport", transport, allocator);
break;
}
case ProxyType::Trojan:
{
proxy.AddMember("type", "trojan", allocator);
proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator);
proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator);
proxy.AddMember("server_port", x.Port, allocator);
proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator);
auto transport = buildV2RayTransport(x, allocator);
proxy.AddMember("transport", transport, allocator);
break;
}
case ProxyType::WireGuard:
{
proxy.AddMember("type", "wireguard", allocator);
proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator);
rapidjson::Value addresses(rapidjson::kArrayType);
addresses.PushBack(rapidjson::StringRef(x.SelfIP.c_str()), allocator);
if (!x.SelfIPv6.empty())
addresses.PushBack(rapidjson::StringRef(x.SelfIPv6.c_str()), allocator);
proxy.AddMember("local_address", addresses, allocator);
proxy.AddMember("private_key", rapidjson::StringRef(x.PrivateKey.c_str()), allocator);
rapidjson::Value peer(rapidjson::kObjectType);
peer.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator);
peer.AddMember("server_port", x.Port, allocator);
peer.AddMember("public_key", rapidjson::StringRef(x.PublicKey.c_str()), allocator);
if (!x.PreSharedKey.empty())
peer.AddMember("pre_shared_key", rapidjson::StringRef(x.PreSharedKey.c_str()), allocator);
if (!x.AllowedIPs.empty())
{
auto allowed = split(x.AllowedIPs, ",");
rapidjson::Value allowed_ips(rapidjson::kArrayType);
for (const auto &ip: allowed) {
allowed_ips.PushBack(rapidjson::Value(trim(ip).c_str(), allocator), allocator);
}
peer.AddMember("allowed_ips", allowed_ips, allocator);
}
if (!x.ClientId.empty())
{
auto client_id = split(x.ClientId, ",");
rapidjson::Value reserved(rapidjson::kArrayType);
for (const auto &id : client_id)
{
reserved.PushBack(to_int(trim(id)), allocator);
}
peer.AddMember("reserved", reserved, allocator);
}
rapidjson::Value peers(rapidjson::kArrayType);
peers.PushBack(peer, allocator);
proxy.AddMember("peers", peers, allocator);
proxy.AddMember("mtu", x.Mtu, allocator);
break;
}
case ProxyType::HTTP:
case ProxyType::HTTPS:
{
proxy.AddMember("type", "http", allocator);
proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator);
proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator);
proxy.AddMember("server_port", x.Port, allocator);
if (x.TLSSecure)
{
proxy.AddMember("username", rapidjson::StringRef(x.Username.c_str()), allocator);
proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator);
}
break;
}
case ProxyType::SOCKS5:
{
proxy.AddMember("type", "socks", allocator);
proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator);
proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator);
proxy.AddMember("port", x.Port, allocator);
proxy.AddMember("version", 5, allocator);
proxy.AddMember("username", rapidjson::StringRef(x.Username.c_str()), allocator);
proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator);
break;
}
default:
continue;
}
if (x.TLSSecure)
{
rapidjson::Value tls(rapidjson::kObjectType);
tls.AddMember("enable", true, allocator);
if (!x.ServerName.empty())
tls.AddMember("server_name", rapidjson::StringRef(x.ServerName.c_str()), allocator);
tls.AddMember("insecure", buildBooleanValue(scv), allocator);
proxy.AddMember("tls", tls, allocator);
}
if (!udp.is_undef() && !udp)
{
proxy.AddMember("network", "tcp", allocator);
}
if (!tfo.is_undef())
{
proxy.AddMember("tcp_fast_open", buildBooleanValue(tfo), allocator);
}
nodelist.push_back(x);
outbounds.PushBack(proxy, allocator);
}
for (const ProxyGroupConfig& x: extra_proxy_group)
{
string_array filtered_nodelist;
std::string type;
switch (x.Type)
{
case ProxyGroupType::Select:
{
type = "selector";
break;
}
case ProxyGroupType::URLTest:
case ProxyGroupType::Fallback:
case ProxyGroupType::LoadBalance:
{
type = "urltest";
break;
}
default:
continue;
}
for (const auto &y : x.Proxies)
groupGenerate(y, nodelist, filtered_nodelist, true, ext);
if (filtered_nodelist.empty())
filtered_nodelist.emplace_back("DIRECT");
rapidjson::Value group(rapidjson::kObjectType);
group.AddMember("type", rapidjson::Value(type.c_str(), allocator), allocator);
group.AddMember("tag", rapidjson::Value(x.Name.c_str(), allocator), allocator);
rapidjson::Value group_outbounds(rapidjson::kArrayType);
for (const std::string& y: filtered_nodelist)
{
group_outbounds.PushBack(rapidjson::Value(y.c_str(), allocator), allocator);
}
group.AddMember("outbounds", group_outbounds, allocator);
if (x.Type == ProxyGroupType::URLTest)
{
group.AddMember("url", rapidjson::Value(x.Url.c_str(), allocator), allocator);
group.AddMember("interval", rapidjson::Value(formatSingBoxInterval(x.Interval).c_str(), allocator), allocator);
if (x.Tolerance > 0)
group.AddMember("tolerance", x.Tolerance, allocator);
}
outbounds.PushBack(group, allocator);
}
if (json.HasMember("outbounds"))
json.RemoveMember("outbounds");
json.AddMember("outbounds", outbounds, allocator);
}
std::string proxyToSingBox(std::vector<Proxy> &nodes, const std::string &base_conf, std::vector<RulesetContent> &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext)
{
rapidjson::Document json;
json.Parse(base_conf.data());
if(json.HasParseError())
{
writeLog(0, "sing-box base loader failed with error: " + std::string(rapidjson::GetParseError_En(json.GetParseError())), LOG_LEVEL_ERROR);
return "";
}
proxyToSingBox(nodes, json, ruleset_content_array, extra_proxy_group, ext);
rulesetToSingBox(json, ruleset_content_array, ext.overwrite_original_rules);
return SerializeObject(json);
}

View File

@ -37,11 +37,11 @@ struct extra_settings
tribool skip_cert_verify = tribool();
tribool tls13 = tribool();
bool clash_classical_ruleset = false;
std::string sort_script = "";
std::string sort_script;
std::string clash_proxies_style = "flow";
bool authorized = false;
extra_settings() {};
extra_settings() = default;
extra_settings(const extra_settings&) = delete;
extra_settings(extra_settings&&) = delete;
@ -57,9 +57,6 @@ struct extra_settings
#endif // NO_JS_RUNTIME
};
void rulesetToClash(YAML::Node &base_rule, std::vector<RulesetContent> &ruleset_content_array, bool overwrite_original_rules, bool new_field_name);
void rulesetToSurge(INIReader &base_rule, std::vector<RulesetContent> &ruleset_content_array, int surge_ver, bool overwrite_original_rules, std::string remote_path_prefix);
std::string proxyToClash(std::vector<Proxy> &nodes, const std::string &base_conf, std::vector<RulesetContent> &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, bool clashR, extra_settings &ext);
void proxyToClash(std::vector<Proxy> &nodes, YAML::Node &yamlnode, const ProxyGroupConfigs &extra_proxy_group, bool clashR, extra_settings &ext);
std::string proxyToSurge(std::vector<Proxy> &nodes, const std::string &base_conf, std::vector<RulesetContent> &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, int surge_ver, extra_settings &ext);
@ -73,5 +70,6 @@ void proxyToQuanX(std::vector<Proxy> &nodes, INIReader &ini, std::vector<Ruleset
std::string proxyToQuan(std::vector<Proxy> &nodes, const std::string &base_conf, std::vector<RulesetContent> &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext);
void proxyToQuan(std::vector<Proxy> &nodes, INIReader &ini, std::vector<RulesetContent> &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext);
std::string proxyToSSD(std::vector<Proxy> &nodes, std::string &group, std::string &userinfo, extra_settings &ext);
std::string proxyToSingBox(std::vector<Proxy> &nodes, const std::string &base_conf, std::vector<RulesetContent> &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext);
#endif // SUBEXPORT_H_INCLUDED

View File

@ -89,30 +89,28 @@ const std::vector<UAProfile> UAMatchList = {
{"V2RayX","","","v2ray"}
};
bool verGreaterEqual(const std::string &src_ver, const std::string &target_ver)
bool verGreaterEqual(const std::string& src_ver, const std::string& target_ver)
{
string_size src_pos_beg = 0, src_pos_end, target_pos_beg = 0, target_pos_end;
while(true)
{
src_pos_end = src_ver.find('.', src_pos_beg);
if(src_pos_end == src_ver.npos)
src_pos_end = src_ver.size();
int part_src = std::stoi(src_ver.substr(src_pos_beg, src_pos_end - src_pos_beg));
target_pos_end = target_ver.find('.', target_pos_beg);
if(target_pos_end == target_ver.npos)
target_pos_end = target_ver.size();
int part_target = std::stoi(target_ver.substr(target_pos_beg, target_pos_end - target_pos_beg));
if(part_src > part_target)
break;
else if(part_src < part_target)
return false;
else if(src_pos_end >= src_ver.size() - 1 || target_pos_end >= target_ver.size() - 1)
break;
src_pos_beg = src_pos_end + 1;
target_pos_beg = target_pos_end + 1;
std::istringstream src_stream(src_ver), target_stream(target_ver);
int src_part, target_part;
char dot;
while (src_stream >> src_part) {
if (target_stream >> target_part) {
if (src_part < target_part) {
return false;
} else if (src_part > target_part) {
return true;
}
// Skip the dot separator in both streams
src_stream >> dot;
target_stream >> dot;
} else {
// If we run out of target parts, the source version is greater only if it has more parts
return true;
}
}
return true;
// If we get here, the common parts are equal, so check if target_ver has more parts
return !bool(target_stream >> target_part);
}
void matchUserAgent(const std::string &user_agent, std::string &target, tribool &clash_new_name, int &surge_ver)
@ -321,7 +319,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
case "ss"_hash: case "ssd"_hash: case "ssr"_hash: case "sssub"_hash: case "v2ray"_hash: case "trojan"_hash: case "mixed"_hash:
lSimpleSubscription = true;
break;
case "clash"_hash: case "clashr"_hash: case "surge"_hash: case "quan"_hash: case "quanx"_hash: case "loon"_hash: case "surfboard"_hash: case "mellow"_hash:
case "clash"_hash: case "clashr"_hash: case "surge"_hash: case "quan"_hash: case "quanx"_hash: case "loon"_hash: case "surfboard"_hash: case "mellow"_hash: case "singbox"_hash:
break;
default:
*status_code = 400;
@ -363,6 +361,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
/// for external configuration
std::string lClashBase = global.clashBase, lSurgeBase = global.surgeBase, lMellowBase = global.mellowBase, lSurfboardBase = global.surfboardBase;
std::string lQuanBase = global.quanBase, lQuanXBase = global.quanXBase, lLoonBase = global.loonBase, lSSSubBase = global.SSSubBase;
std::string lSingBoxBase = global.singBoxBase;
/// validate urls
argEnableInsert.define(global.enableInsert);
@ -458,6 +457,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
checkExternalBase(extconf.quan_rule_base, lQuanBase);
checkExternalBase(extconf.quanx_rule_base, lQuanXBase);
checkExternalBase(extconf.loon_rule_base, lLoonBase);
checkExternalBase(extconf.singbox_rule_base, lSingBoxBase);
if(extconf.surge_ruleset.size())
lCustomRulesets = extconf.surge_ruleset;
@ -877,6 +877,22 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
if(argUpload)
uploadGist("ssd", argUploadPath, output_content, false);
break;
case "singbox"_hash:
writeLog(0, "Generate target: sing-box", LOG_LEVEL_INFO);
if(!ext.nodelist)
{
if(render_template(fetchFile(lSingBoxBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0)
{
*status_code = 400;
return base_content;
}
}
output_content = proxyToSingBox(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext);
if(argUpload)
uploadGist("singbox", argUploadPath, output_content, false);
break;
default:
writeLog(0, "Generate target: Unspecified", LOG_LEVEL_INFO);
*status_code = 500;

View File

@ -331,6 +331,7 @@ void readYAMLConf(YAML::Node &node)
section["quanx_rule_base"] >> global.quanXBase;
section["loon_rule_base"] >> global.loonBase;
section["sssub_rule_base"] >> global.SSSubBase;
section["singbox_rule_base"] >> global.singBoxBase;
section["default_external_config"] >> global.defaultExtConfig;
section["append_proxy_type"] >> global.appendType;
@ -605,6 +606,8 @@ void readTOMLConf(toml::value &root)
"quan_rule_base", global.quanBase,
"quanx_rule_base", global.quanXBase,
"loon_rule_base", global.loonBase,
"sssub_rule_base", global.SSSubBase,
"singbox_rule_base", global.singBoxBase,
"proxy_config", global.proxyConfig,
"proxy_ruleset", global.proxyRuleset,
"proxy_subscription", global.proxySubscription,
@ -842,6 +845,8 @@ void readConf()
ini.get_if_exist("quan_rule_base", global.quanBase);
ini.get_if_exist("quanx_rule_base", global.quanXBase);
ini.get_if_exist("loon_rule_base", global.loonBase);
ini.get_if_exist("sssub_rule_base", global.SSSubBase);
ini.get_if_exist("singbox_rule_base", global.singBoxBase);
ini.get_if_exist("default_external_config", global.defaultExtConfig);
ini.get_bool_if_exist("append_proxy_type", global.appendType);
ini.get_if_exist("proxy_config", global.proxyConfig);
@ -1074,6 +1079,7 @@ int loadExternalYAML(YAML::Node &node, ExternalConfig &ext)
section["quanx_rule_base"] >> ext.quanx_rule_base;
section["loon_rule_base"] >> ext.loon_rule_base;
section["sssub_rule_base"] >> ext.sssub_rule_base;
section["singbox_rule_base"] >> ext.singbox_rule_base;
section["enable_rule_generator"] >> ext.enable_rule_generator;
section["overwrite_original_rules"] >> ext.overwrite_original_rules;
@ -1146,7 +1152,9 @@ int loadExternalTOML(toml::value &root, ExternalConfig &ext)
"mellow_rule_base", ext.mellow_rule_base,
"quan_rule_base", ext.quan_rule_base,
"quanx_rule_base", ext.quanx_rule_base,
"loon_rule_base", ext.loon_rule_base,
"sssub_rule_base", ext.sssub_rule_base,
"singbox_rule_base", ext.singbox_rule_base,
"add_emoji", ext.add_emoji,
"remove_old_emoji", ext.remove_old_emoji,
"include_remarks", ext.include,
@ -1248,6 +1256,7 @@ int loadExternalConfig(std::string &path, ExternalConfig &ext)
ini.get_if_exist("quanx_rule_base", ext.quanx_rule_base);
ini.get_if_exist("loon_rule_base", ext.loon_rule_base);
ini.get_if_exist("sssub_rule_base", ext.sssub_rule_base);
ini.get_if_exist("singbox_rule_base", ext.singbox_rule_base);
ini.get_bool_if_exist("overwrite_original_rules", ext.overwrite_original_rules);
ini.get_bool_if_exist("enable_rule_generator", ext.enable_rule_generator);

View File

@ -54,7 +54,7 @@ struct Settings
std::string clashBase;
ProxyGroupConfigs customProxyGroups;
std::string surgeBase, surfboardBase, mellowBase, quanBase, quanXBase, loonBase, SSSubBase;
std::string surgeBase, surfboardBase, mellowBase, quanBase, quanXBase, loonBase, SSSubBase, singBoxBase;
std::string surgeSSRPath, quanXDevID;
//cache system
@ -83,6 +83,7 @@ struct ExternalConfig
std::string quanx_rule_base;
std::string loon_rule_base;
std::string sssub_rule_base;
std::string singbox_rule_base;
RegexMatchConfigs rename;
RegexMatchConfigs emoji;
string_array include;

View File

@ -1,7 +1,7 @@
#include <iostream>
#include <string>
#include <unistd.h>
#include <signal.h>
#include <csignal>
#include <sys/types.h>
#include <dirent.h>
@ -29,7 +29,7 @@ WebServer webServer;
#ifndef _WIN32
void SetConsoleTitle(const std::string &title)
{
system(std::string("echo \"\\033]0;" + title + "\\007\\c\"").data());
system(std::string("echo \"\\033]0;" + title + R"(\007\c")").data());
}
#endif // _WIN32
@ -46,10 +46,10 @@ void setcd(std::string &file)
strrchr(szTemp, '\\')[1] = '\0';
#else
char *ret = realpath(file.data(), szTemp);
if(ret == NULL)
if(ret == nullptr)
return;
ret = strcpy(filename, strrchr(szTemp, '/') + 1);
if(ret == NULL)
if(ret == nullptr)
return;
strrchr(szTemp, '/')[1] = '\0';
#endif // _WIN32
@ -84,7 +84,7 @@ void chkArg(int argc, char *argv[])
else if(strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--log") == 0)
{
if(i < argc - 1)
if(freopen(argv[++i], "a", stderr) == NULL)
if(freopen(argv[++i], "a", stderr) == nullptr)
std::cerr<<"Error redirecting output to file.\n";
}
}
@ -293,7 +293,7 @@ int main(int argc, char *argv[])
//webServer.append_response("GET", "/list-profiles", "text/plain;charset=utf-8", listProfiles);
std::string env_port = getEnv("PORT");
if(env_port.size())
if(!env_port.empty())
global.listenPort = to_int(env_port, global.listenPort);
listener_args args = {global.listenAddress, global.listenPort, global.maxPendingConns, global.maxConcurThreads, cron_tick_caller, 200};
//std::cout<<"Serving HTTP @ http://"<<listen_address<<":"<<listen_port<<std::endl;

View File

@ -13,12 +13,28 @@ int script_context_init(qjs::Context &context);
int script_cleanup(qjs::Context &context);
void script_print_stack(qjs::Context &context);
inline JSValue JS_NewString(JSContext *ctx, const std::string& str)
{
return JS_NewStringLen(ctx, str.c_str(), str.size());
}
inline std::string JS_GetPropertyToString(JSContext *ctx, JSValue v, const char* prop)
{
auto val = JS_GetPropertyStr(ctx, v, prop);
auto valData = JS_ToCString(ctx, val);
std::string result = valData;
JS_FreeCString(ctx, valData);
size_t len;
const char *str = JS_ToCStringLen(ctx, &len, val);
std::string result(str, len);
JS_FreeCString(ctx, str);
JS_FreeValue(ctx, val);
return result;
}
inline std::string JS_GetPropertyToString(JSContext *ctx, JSValueConst obj, uint32_t index) {
JSValue val = JS_GetPropertyUint32(ctx, obj, index);
size_t len;
const char *str = JS_ToCStringLen(ctx, &len, val);
std::string result(str, len);
JS_FreeCString(ctx, str);
JS_FreeValue(ctx, val);
return result;
}
@ -33,7 +49,7 @@ inline int JS_GetPropertyToInt32(JSContext *ctx, JSValue v, const char* prop, in
return result;
}
inline int JS_GetPropertyToUInt32(JSContext *ctx, JSValue v, const char* prop, uint32_t def_value = 0)
inline uint32_t JS_GetPropertyToUInt32(JSContext *ctx, JSValue v, const char* prop, uint32_t def_value = 0)
{
uint32_t result = def_value;
auto val = JS_GetPropertyStr(ctx, v, prop);
@ -85,79 +101,98 @@ namespace qjs
}
};
template<>
struct js_traits<StringArray> {
static StringArray unwrap(JSContext *ctx, JSValueConst v) {
StringArray arr;
uint32_t length = JS_GetPropertyToUInt32(ctx, v, "length");
for (uint32_t i = 0; i < length; i++) {
arr.push_back(JS_GetPropertyToString(ctx, v, i));
}
return arr;
}
static JSValue wrap(JSContext *ctx, const StringArray& arr) {
JSValue jsArray = JS_NewArray(ctx);
for (std::size_t i = 0; i < arr.size(); i++) {
JS_SetPropertyUint32(ctx, jsArray, i, JS_NewString(ctx, arr[i]));
}
return jsArray;
}
};
template<>
struct js_traits<Proxy>
{
static JSValue wrap(JSContext *ctx, const Proxy &n) noexcept
{
auto obj = JS_NewObject(ctx);
/*
JS_SetPropertyStr(ctx, obj, "LinkType", JS_NewInt32(ctx, n.linkType));
JS_SetPropertyStr(ctx, obj, "ID", JS_NewInt32(ctx, n.id));
JS_SetPropertyStr(ctx, obj, "GroupID", JS_NewInt32(ctx, n.groupID));
JS_SetPropertyStr(ctx, obj, "Group", JS_NewStringLen(ctx, n.group.c_str(), n.group.size()));
JS_SetPropertyStr(ctx, obj, "Remark", JS_NewStringLen(ctx, n.remarks.c_str(), n.remarks.size()));
JS_SetPropertyStr(ctx, obj, "Server", JS_NewStringLen(ctx, n.server.c_str(), n.server.size()));
JS_SetPropertyStr(ctx, obj, "Port", JS_NewInt32(ctx, n.port));
JS_SetPropertyStr(ctx, obj, "ProxyInfo", JS_NewStringLen(ctx, n.proxyStr.c_str(), n.proxyStr.size()));
*/
JS_SetPropertyStr(ctx, obj, "Type", JS_NewInt32(ctx, n.Type));
JS_SetPropertyStr(ctx, obj, "Id", JS_NewInt32(ctx, n.Id));
JS_SetPropertyStr(ctx, obj, "GroupId", JS_NewInt32(ctx, n.GroupId));
JS_SetPropertyStr(ctx, obj, "Group", JS_NewStringLen(ctx, n.Group.c_str(), n.Group.size()));
JS_SetPropertyStr(ctx, obj, "Remark", JS_NewStringLen(ctx, n.Remark.c_str(), n.Remark.size()));
JS_SetPropertyStr(ctx, obj, "Server", JS_NewStringLen(ctx, n.Hostname.c_str(), n.Hostname.size()));
JS_SetPropertyStr(ctx, obj, "Port", JS_NewInt32(ctx, n.Port));
JSValue obj = JS_NewObjectProto(ctx, JS_NULL);
if (JS_IsException(obj)) {
return obj;
}
JS_SetPropertyStr(ctx, obj, "Username", JS_NewStringLen(ctx, n.Username.c_str(), n.Username.size()));
JS_SetPropertyStr(ctx, obj, "Password", JS_NewStringLen(ctx, n.Password.c_str(), n.Password.size()));
JS_SetPropertyStr(ctx, obj, "EncryptMethod", JS_NewStringLen(ctx, n.EncryptMethod.c_str(), n.EncryptMethod.size()));
JS_SetPropertyStr(ctx, obj, "Plugin", JS_NewStringLen(ctx, n.Plugin.c_str(), n.Plugin.size()));
JS_SetPropertyStr(ctx, obj, "PluginOption", JS_NewStringLen(ctx, n.PluginOption.c_str(), n.PluginOption.size()));
JS_SetPropertyStr(ctx, obj, "Protocol", JS_NewStringLen(ctx, n.Protocol.c_str(), n.Protocol.size()));
JS_SetPropertyStr(ctx, obj, "ProtocolParam", JS_NewStringLen(ctx, n.ProtocolParam.c_str(), n.ProtocolParam.size()));
JS_SetPropertyStr(ctx, obj, "OBFS", JS_NewStringLen(ctx, n.OBFS.c_str(), n.OBFS.size()));
JS_SetPropertyStr(ctx, obj, "OBFSParam", JS_NewStringLen(ctx, n.OBFSParam.c_str(), n.OBFSParam.size()));
JS_SetPropertyStr(ctx, obj, "UserId", JS_NewStringLen(ctx, n.UserId.c_str(), n.UserId.size()));
JS_DefinePropertyValueStr(ctx, obj, "Type", JS_NewInt32(ctx, n.Type), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "Id", JS_NewUint32(ctx, n.Id), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "GroupId", JS_NewUint32(ctx, n.GroupId), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "Group", JS_NewString(ctx, n.Group), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "Remark", JS_NewString(ctx, n.Remark), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "Server", JS_NewString(ctx, n.Hostname), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "Port", JS_NewInt32(ctx, n.Port), JS_PROP_C_W_E);
JS_SetPropertyStr(ctx, obj, "AlterId", JS_NewInt32(ctx, n.AlterId));
JS_SetPropertyStr(ctx, obj, "TransferProtocol", JS_NewStringLen(ctx, n.TransferProtocol.c_str(), n.TransferProtocol.size()));
JS_SetPropertyStr(ctx, obj, "FakeType", JS_NewStringLen(ctx, n.FakeType.c_str(), n.FakeType.size()));
JS_SetPropertyStr(ctx, obj, "TLSSecure", JS_NewBool(ctx, n.TLSSecure));
JS_DefinePropertyValueStr(ctx, obj, "Username", JS_NewString(ctx, n.Username), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "Password", JS_NewString(ctx, n.Password), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "EncryptMethod", JS_NewString(ctx, n.EncryptMethod), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "Plugin", JS_NewString(ctx, n.Plugin), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "PluginOption", JS_NewString(ctx, n.PluginOption), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "Protocol", JS_NewString(ctx, n.Protocol), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "ProtocolParam", JS_NewString(ctx, n.ProtocolParam), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "OBFS", JS_NewString(ctx, n.OBFS), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "OBFSParam", JS_NewString(ctx, n.OBFSParam), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "UserId", JS_NewString(ctx, n.UserId), JS_PROP_C_W_E);
JS_SetPropertyStr(ctx, obj, "Host", JS_NewStringLen(ctx, n.Host.c_str(), n.Host.size()));
JS_SetPropertyStr(ctx, obj, "Path", JS_NewStringLen(ctx, n.Path.c_str(), n.Path.size()));
JS_SetPropertyStr(ctx, obj, "Edge", JS_NewStringLen(ctx, n.Edge.c_str(), n.Edge.size()));
JS_DefinePropertyValueStr(ctx, obj, "AlterId", JS_NewInt32(ctx, n.AlterId), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "TransferProtocol", JS_NewString(ctx, n.TransferProtocol), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "FakeType", JS_NewString(ctx, n.FakeType), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "TLSSecure", JS_NewBool(ctx, n.TLSSecure), JS_PROP_C_W_E);
JS_SetPropertyStr(ctx, obj, "QUICSecure", JS_NewStringLen(ctx, n.QUICSecure.c_str(), n.QUICSecure.size()));
JS_SetPropertyStr(ctx, obj, "QUICSecret", JS_NewStringLen(ctx, n.QUICSecret.c_str(), n.QUICSecret.size()));
JS_DefinePropertyValueStr(ctx, obj, "Host", JS_NewString(ctx, n.Host), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "Path", JS_NewString(ctx, n.Path), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "Edge", JS_NewString(ctx, n.Edge), JS_PROP_C_W_E);
JS_SetPropertyStr(ctx, obj, "UDP", js_traits<tribool>::wrap(ctx, n.UDP));
JS_SetPropertyStr(ctx, obj, "TCPFastOpen", js_traits<tribool>::wrap(ctx, n.TCPFastOpen));
JS_SetPropertyStr(ctx, obj, "AllowInsecure", js_traits<tribool>::wrap(ctx, n.AllowInsecure));
JS_SetPropertyStr(ctx, obj, "TLS13", js_traits<tribool>::wrap(ctx, n.TLS13));
JS_DefinePropertyValueStr(ctx, obj, "QUICSecure", JS_NewString(ctx, n.QUICSecure), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "QUICSecret", JS_NewString(ctx, n.QUICSecret), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "UDP", js_traits<tribool>::wrap(ctx, n.UDP), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "TCPFastOpen", js_traits<tribool>::wrap(ctx, n.TCPFastOpen), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "AllowInsecure", js_traits<tribool>::wrap(ctx, n.AllowInsecure), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "TLS13", js_traits<tribool>::wrap(ctx, n.TLS13), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "SnellVersion", JS_NewInt32(ctx, n.SnellVersion), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "ServerName", JS_NewString(ctx, n.ServerName), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "SelfIP", JS_NewString(ctx, n.SelfIP), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "SelfIPv6", JS_NewString(ctx, n.SelfIPv6), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "PublicKey", JS_NewString(ctx, n.PublicKey), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "PrivateKey", JS_NewString(ctx, n.PrivateKey), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "PreSharedKey", JS_NewString(ctx, n.PreSharedKey), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "DnsServers", js_traits<StringArray>::wrap(ctx, n.DnsServers), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "Mtu", JS_NewUint32(ctx, n.Mtu), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "AllowedIPs", JS_NewString(ctx, n.AllowedIPs), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "KeepAlive", JS_NewUint32(ctx, n.KeepAlive), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "TestUrl", JS_NewString(ctx, n.TestUrl), JS_PROP_C_W_E);
JS_DefinePropertyValueStr(ctx, obj, "ClientId", JS_NewString(ctx, n.ClientId), JS_PROP_C_W_E);
return obj;
}
static Proxy unwrap(JSContext *ctx, JSValueConst v)
{
Proxy node;
/*
node.linkType = JS_GetPropertyToInt32(ctx, v, "LinkType");
node.id = JS_GetPropertyToInt32(ctx, v, "ID");
node.groupID = JS_GetPropertyToInt32(ctx, v, "GroupID");
node.group = JS_GetPropertyToString(ctx, v, "Group");
node.remarks = JS_GetPropertyToString(ctx, v, "Remark");
node.server = JS_GetPropertyToString(ctx, v, "Server");
node.port = JS_GetPropertyToInt32(ctx, v, "Port");
node.proxyStr = JS_GetPropertyToString(ctx, v, "ProxyInfo");
*/
node.Type = JS_GetPropertyToInt32(ctx, v, "Type");
node.Id = JS_GetPropertyToInt32(ctx, v, "Id");
node.GroupId = JS_GetPropertyToInt32(ctx, v, "GroupId");
node.Group = JS_GetPropertyToString(ctx, v, "Group");
node.Remark = JS_GetPropertyToString(ctx, v, "Remark");
node.Hostname = JS_GetPropertyToString(ctx, v, "Hostname");
node.Hostname = JS_GetPropertyToString(ctx, v, "Server");
node.Port = JS_GetPropertyToUInt32(ctx, v, "Port");
node.Username = JS_GetPropertyToString(ctx, v, "Username");
@ -187,6 +222,21 @@ namespace qjs
node.AllowInsecure = js_traits<tribool>::JS_GetPropertyToTriBool(ctx, v, "AllowInsecure");
node.TLS13 = js_traits<tribool>::JS_GetPropertyToTriBool(ctx, v, "TLS13");
node.SnellVersion = JS_GetPropertyToInt32(ctx, v, "SnellVersion");
node.ServerName = JS_GetPropertyToString(ctx, v, "ServerName");
node.SelfIP = JS_GetPropertyToString(ctx, v, "SelfIP");
node.SelfIPv6 = JS_GetPropertyToString(ctx, v, "SelfIPv6");
node.PublicKey = JS_GetPropertyToString(ctx, v, "PublicKey");
node.PrivateKey = JS_GetPropertyToString(ctx, v, "PrivateKey");
node.PreSharedKey = JS_GetPropertyToString(ctx, v, "PreSharedKey");
node.DnsServers = js_traits<StringArray>::unwrap(ctx, JS_GetPropertyStr(ctx, v, "DnsServers"));
node.Mtu = JS_GetPropertyToUInt32(ctx, v, "Mtu");
node.AllowedIPs = JS_GetPropertyToString(ctx, v, "AllowedIPs");
node.KeepAlive = JS_GetPropertyToUInt32(ctx, v, "KeepAlive");
node.TestUrl = JS_GetPropertyToString(ctx, v, "TestUrl");
node.ClientId = JS_GetPropertyToString(ctx, v, "ClientId");
return node;
}
};

View File

@ -74,7 +74,7 @@ void file_not_found(std::string arguments, int sock)
std::string response = "HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/plain\r\nConnection: close\r\n"
"Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: *\r\n"
"Content-Length: " + std::__cxx11::to_string(prompt_info.size()) + "\r\n\r\n" + prompt_info + "\r\n";
"Content-Length: " + std::to_string(prompt_info.size()) + "\r\n\r\n" + prompt_info + "\r\n";
if (sendall(sock, response) == -1)
{
@ -124,7 +124,7 @@ void serve_options(SOCKET sock)
void serve_content(SOCKET sock, std::string type, std::string content)
{
send_header(sock, type.data());
std::string extra_header = "Content-Length: " + std::__cxx11::to_string(content.size()) + "\r\n";
std::string extra_header = "Content-Length: " + std::to_string(content.size()) + "\r\n";
sendall(sock, extra_header);
send(sock, "\r\n", 2, 0);
if (sendall(sock, content) == -1)

View File

@ -4,7 +4,6 @@
#include <string>
#include <map>
#include <atomic>
#include <event2/http.h>
#include <curl/curlver.h>
#include "../utils/map_extra.h"
@ -69,10 +68,7 @@ public:
int start_web_server(void *argv);
int start_web_server_multi(void *argv);
void stop_web_server();
private:
int serveFile(const std::string &filename, std::string &content_type, std::string &return_data);
inline int process_request(Request &request, Response &response, std::string &return_data);
void on_request(evhttp_request *req, void *args);
std::vector<responseRoute> responses;
string_map redirect_map;
};

View File

@ -0,0 +1,223 @@
#include <string>
#ifdef MALLOC_TRIM
#include <malloc.h>
#endif // MALLOC_TRIM
#include "httplib.h"
#include "../utils/base64/base64.h"
#include "../utils/logger.h"
#include "../utils/string_hash.h"
#include "../utils/stl_extra.h"
#include "../utils/urlencode.h"
#include "webserver.h"
int WebServer::start_web_server(void *argv)
{
return start_web_server_multi(argv);
}
void WebServer::stop_web_server()
{
SERVER_EXIT_FLAG = true;
}
static httplib::Server::Handler makeHandler(const responseRoute &rr)
{
return [rr](const httplib::Request &request, httplib::Response &response)
{
Request req;
Response resp;
req.method = request.method;
req.url = request.path;
for (auto &h: request.headers)
{
req.headers[h.first] = h.second;
}
for (auto &p: request.params)
{
req.argument += p.first + "=" + p.second + "&";
}
req.argument.pop_back();
req.postdata = request.body;
if (request.get_header_value("Content-Type") == "application/x-www-form-urlencoded")
{
req.postdata = urlDecode(req.postdata);
}
auto result = rr.rc(req, resp);
response.status = resp.status_code;
for (auto &h: resp.headers) {
response.set_header(h.first, h.second);
}
auto content_type = resp.content_type;
if (content_type.empty())
{
content_type = rr.content_type;
}
response.set_content(result, content_type);
};
}
int WebServer::start_web_server_multi(void *argv)
{
httplib::Server server;
auto *args = (listener_args *)argv;
for (auto &x : responses)
{
switch (hash_(x.method))
{
case "GET"_hash: case "HEAD"_hash:
server.Get(x.path, makeHandler(x));
break;
case "POST"_hash:
server.Post(x.path, makeHandler(x));
break;
case "PUT"_hash:
server.Put(x.path, makeHandler(x));
break;
case "DELETE"_hash:
server.Delete(x.path, makeHandler(x));
break;
case "PATCH"_hash:
server.Patch(x.path, makeHandler(x));
break;
}
}
server.Options(R"(.*)", [&](const httplib::Request &req, httplib::Response &res) {
auto path = req.path;
std::string allowed;
for (auto &rr : responses) {
if (rr.path == path)
{
allowed += rr.method + ",";
}
}
if (!allowed.empty())
{
allowed.pop_back();
res.status = 200;
res.set_header("Access-Control-Allow-Methods", allowed);
res.set_header("Access-Control-Allow-Origin", "*");
res.set_header("Access-Control-Allow-Headers", "Content-Type,Authorization");
}
});
server.set_pre_routing_handler([&](const httplib::Request &req, httplib::Response &res) {
std::string params;
for (auto &p: req.params)
{
params += p.first + "=" + p.second + "&";
}
if (!params.empty())
{
params.pop_back();
params = "?" + params;
}
writeLog(0, "Accept connection from client " + req.remote_addr + ":" + std::to_string(req.remote_port), LOG_LEVEL_DEBUG);
writeLog(0, "handle_cmd: " + req.method + " handle_uri: " + req.path + params, LOG_LEVEL_VERBOSE);
if (req.has_header("SubConverter-Request"))
{
res.status = 500;
res.set_content("Loop request detected!", "text/plain");
return httplib::Server::HandlerResponse::Handled;
}
res.set_header("Server", "subconverter/" VERSION " cURL/" LIBCURL_VERSION);
if (require_auth)
{
static std::string auth_token = "Basic " + base64Encode(auth_user + ":" + auth_password);
auto auth = req.get_header_value("Authorization");
if (auth != auth_token)
{
res.status = 401;
res.set_header("WWW-Authenticate", "Basic realm=" + auth_realm + ", charset=\"UTF-8\"");
res.set_content("Unauthorized", "text/plain");
return httplib::Server::HandlerResponse::Handled;
}
}
res.set_header("X-Client-IP", req.remote_addr);
if (req.has_header("Access-Control-Request-Headers"))
{
res.set_header("Access-Control-Allow-Headers", req.get_header_value("Access-Control-Request-Headers"));
}
return httplib::Server::HandlerResponse::Unhandled;
});
for (auto &x : redirect_map)
{
server.Get(x.first, [x](const httplib::Request &req, httplib::Response &res) {
res.set_redirect(x.second);
});
}
server.set_exception_handler([](const httplib::Request &req, httplib::Response &res, const std::exception_ptr &e) {
try
{
std::rethrow_exception(e);
}
catch (const httplib::Error &err)
{
res.set_content(to_string(err), "text/plain");
}
catch (const std::exception &ex)
{
std::string params;
for (auto &p: req.params)
{
params += p.first + "=" + p.second + "&";
}
params.pop_back();
std::string return_data = "Internal server error while processing request path '" + req.path + "' with arguments '" + params + "'!\n";
return_data += "\n exception: ";
return_data += type(ex);
return_data += "\n what(): ";
return_data += ex.what();
res.set_content(return_data, "text/plain");
}
catch (...)
{
res.status = 500;
}
});
if (serve_file)
{
server.set_mount_point("/", serve_file_root);
}
server.bind_to_port(args->listen_address, args->port, 0);
pthread_t tid;
pthread_create(&tid, nullptr, [](void *args) -> void * {
auto *server = (httplib::Server *)args;
server->listen_after_bind();
return nullptr;
}, &server);
while (!SERVER_EXIT_FLAG)
{
if (args->looper_callback)
{
args->looper_callback();
}
std::this_thread::sleep_for(std::chrono::milliseconds(args->looper_interval));
}
server.stop();
return 0;
}
void WebServer::append_response(const std::string &method, const std::string &uri, const std::string &content_type, response_callback response)
{
responseRoute rr;
rr.method = method;
rr.path = uri;
rr.content_type = content_type;
rr.rc = response;
responses.emplace_back(std::move(rr));
}
void WebServer::append_redirect(const std::string &uri, const std::string &target)
{
redirect_map[uri] = target;
}
void WebServer::reset_redirect()
{
eraseElements(redirect_map);
}

View File

@ -1,6 +1,5 @@
#include <memory>
#include <cstdint>
#include <iostream>
#include <evhttp.h>
#include <atomic>
#ifdef MALLOC_TRIM
@ -8,13 +7,11 @@
#endif // MALLOC_TRIM
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <string.h>
#include <cstring>
#include <pthread.h>
#include <thread>
#include <functional>
#include "../utils/base64/base64.h"
#include "../utils/file_extra.h"
@ -65,7 +62,7 @@ bool matchSpaceSeparatedList(const std::string& source, const std::string &targe
while(pos_begin < total)
{
pos_end = source.find(' ', pos_begin);
if(pos_end == source.npos)
if(pos_end == std::string::npos)
pos_end = total;
if(source.compare(pos_begin, pos_end - pos_begin, target) == 0)
return true;
@ -76,12 +73,12 @@ bool matchSpaceSeparatedList(const std::string& source, const std::string &targe
std::string checkMIMEType(const std::string &filename)
{
string_size name_begin = 0, name_end = 0;
string_size name_begin, name_end;
name_begin = filename.rfind('/');
if(name_begin == filename.npos)
if(name_begin == std::string::npos)
name_begin = 0;
name_end = filename.rfind('.');
if(name_end == filename.npos || name_end < name_begin || name_end == filename.size() - 1)
if(name_end == std::string::npos || name_end < name_begin || name_end == filename.size() - 1)
return "application/octet-stream";
std::string extension = filename.substr(name_end + 1);
for(MIME_type &x : mime_types)
@ -90,10 +87,10 @@ std::string checkMIMEType(const std::string &filename)
return "application/octet-stream";
}
int WebServer::serveFile(const std::string &filename, std::string &content_type, std::string &return_data)
int serveFile(WebServer *server, const std::string &filename, std::string &content_type, std::string &return_data)
{
std::string realname = serve_file_root + filename;
if(filename.compare("/") == 0)
std::string realname = server->serve_file_root + filename;
if(filename == "/")
realname += "index.html";
if(!fileExist(realname))
return 1;
@ -115,12 +112,12 @@ static inline void buffer_cleanup(struct evbuffer *eb)
#endif // MALLOC_TRIM
}
inline int WebServer::process_request(Request &request, Response &response, std::string &return_data)
inline int process_request(WebServer *server, Request &request, Response &response, std::string &return_data)
{
writeLog(0, "handle_cmd: " + request.method + " handle_uri: " + request.url, LOG_LEVEL_VERBOSE);
string_size pos = request.url.find("?");
if(pos != request.url.npos)
string_size pos = request.url.find('?');
if(pos != std::string::npos)
{
request.argument = request.url.substr(pos + 1);
request.url.erase(pos);
@ -128,13 +125,13 @@ inline int WebServer::process_request(Request &request, Response &response, std:
if(request.method == "OPTIONS")
{
for(responseRoute &x : responses)
for(responseRoute &x : server->responses)
if(matchSpaceSeparatedList(replaceAllDistinct(request.postdata, ",", ""), x.method) && x.path == request.url)
return 1;
return -1;
}
for(responseRoute &x : responses)
for(responseRoute &x : server->responses)
{
if(x.method == request.method && x.path == request.url)
{
@ -159,13 +156,13 @@ inline int WebServer::process_request(Request &request, Response &response, std:
}
}
auto iter = redirect_map.find(request.url);
if(iter != redirect_map.end())
auto iter = server->redirect_map.find(request.url);
if(iter != server->redirect_map.end())
{
return_data = iter->second;
if(request.argument.size())
if(!request.argument.empty())
{
if(return_data.find("?") != return_data.npos)
if(return_data.find('?') != std::string::npos)
return_data += "&" + request.argument;
else
return_data += "?" + request.argument;
@ -173,19 +170,19 @@ inline int WebServer::process_request(Request &request, Response &response, std:
return 2;
}
if(serve_file)
if(server->serve_file)
{
if(request.method.compare("GET") == 0 && serveFile(request.url, response.content_type, return_data) == 0)
if(request.method == "GET" && serveFile(server, request.url, response.content_type, return_data) == 0)
return 0;
}
return -1;
}
void WebServer::on_request(evhttp_request *req, void *args)
void on_request(evhttp_request *req, void *args)
{
(void)args;
static std::string auth_token = "Basic " + base64Encode(auth_user + ":" + auth_password);
auto server = (WebServer*) args;
static std::string auth_token = "Basic " + base64Encode(server->auth_user + ":" + server->auth_password);
const char *req_content_type = evhttp_find_header(req->input_headers, "Content-Type"), *req_ac_method = evhttp_find_header(req->input_headers, "Access-Control-Request-Method");
const char *uri = req->uri, *internal_flag = evhttp_find_header(req->input_headers, "SubConverter-Request");
@ -201,12 +198,12 @@ void WebServer::on_request(evhttp_request *req, void *args)
return;
}
if (require_auth)
if (server->require_auth)
{
const char *auth = evhttp_find_header(req->input_headers, "Authorization");
if (auth == nullptr || auth_token != auth)
{
evhttp_add_header(req->output_headers, "WWW-Authenticate", ("Basic realm=\"" + auth_realm + "\"").data());
evhttp_add_header(req->output_headers, "WWW-Authenticate", ("Basic realm=\"" + server->auth_realm + "\"").data());
auto buffer = evhttp_request_get_output_buffer(req);
evbuffer_add_printf(buffer, "Unauthorized");
evhttp_send_reply(req, 401, nullptr, buffer);
@ -252,7 +249,7 @@ void WebServer::on_request(evhttp_request *req, void *args)
request.headers.emplace("X-Client-IP", client_ip);
std::string return_data;
int retVal = process_request(request, response, return_data);
int retVal = process_request(server, request, response, return_data);
std::string &content_type = response.content_type;
auto *output_buffer = evhttp_request_get_output_buffer(req);
@ -277,7 +274,7 @@ void WebServer::on_request(evhttp_request *req, void *args)
evhttp_send_reply(req, HTTP_MOVETEMP, nullptr, nullptr);
break;
case 0: //found normal
if (content_type.size())
if (!content_type.empty())
evhttp_add_header(req->output_headers, "Content-Type", content_type.c_str());
evhttp_add_header(req->output_headers, "Access-Control-Allow-Origin", "*");
evhttp_add_header(req->output_headers, "Connection", "close");
@ -298,7 +295,7 @@ void WebServer::on_request(evhttp_request *req, void *args)
int WebServer::start_web_server(void *argv)
{
struct listener_args *args = reinterpret_cast<listener_args*>(argv);
auto *args = reinterpret_cast<listener_args*>(argv);
std::string listen_address = args->listen_address;
int port = args->port;
if (!event_init())
@ -320,7 +317,7 @@ int WebServer::start_web_server(void *argv)
auto call_on_request = [&](evhttp_request *req, void *args) { on_request(req, args); };
evhttp_set_allowed_methods(server.get(), EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_OPTIONS | EVHTTP_REQ_PUT | EVHTTP_REQ_PATCH | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD);
evhttp_set_gencb(server.get(), wrap(call_on_request), nullptr);
evhttp_set_gencb(server.get(), wrap(call_on_request), this);
evhttp_set_timeout(server.get(), 30);
if (event_dispatch() == -1)
{
@ -360,7 +357,7 @@ int httpserver_bindsocket(std::string listen_address, int listen_port, int backl
}
#endif
struct sockaddr_in addr;
struct sockaddr_in addr {};
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(listen_address.data());
@ -380,7 +377,7 @@ int httpserver_bindsocket(std::string listen_address, int listen_port, int backl
int WebServer::start_web_server_multi(void *argv)
{
struct listener_args *args = reinterpret_cast<listener_args*>(argv);
auto *args = reinterpret_cast<listener_args*>(argv);
std::string listen_address = args->listen_address;
int port = args->port, nthreads = args->max_workers, max_conn = args->max_conn;
@ -404,7 +401,7 @@ int WebServer::start_web_server_multi(void *argv)
return -1;
evhttp_set_allowed_methods(httpd, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_OPTIONS | EVHTTP_REQ_PUT | EVHTTP_REQ_PATCH | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD);
evhttp_set_gencb(httpd, wrap(call_on_request), nullptr);
evhttp_set_gencb(httpd, wrap(call_on_request), this);
evhttp_set_timeout(httpd, 30);
if (pthread_create(&ths[i], nullptr, httpserver_dispatch, base[i]) != 0)
return -1;

View File

@ -72,5 +72,26 @@ inline std::string SerializeObject(const rapidjson::Value& value)
return sb.GetString();
}
template <typename ...Args>
inline rapidjson::Value buildObject(rapidjson::MemoryPoolAllocator<> & allocator, Args... kvs)
{
static_assert(sizeof...(kvs) % 2 == 0, "buildObject requires an even number of arguments");
static_assert((std::is_same<Args, const char*>::value && ...), "buildObject requires all arguments to be const char*");
rapidjson::Value ret(rapidjson::kObjectType);
auto args = {kvs...};
auto it = args.begin();
while (it != args.end())
{
const char *key = *it++, *value = *it++;
ret.AddMember(rapidjson::StringRef(key), rapidjson::StringRef(value), allocator);
}
return ret;
}
inline rapidjson::Value buildBooleanValue(bool value)
{
return value ? rapidjson::Value(rapidjson::kTrueType) : rapidjson::Value(rapidjson::kFalseType);
}
#endif // RAPIDJSON_EXTRA_H_INCLUDED

View File

@ -1,5 +1,5 @@
#include <string>
#include <stdarg.h>
#include <cstdarg>
/*
#ifdef USE_STD_REGEX
@ -193,7 +193,7 @@ std::vector<std::string> regGetAllMatch(const std::string &src, const std::strin
reg.setPattern(match).addModifier("m").addPcre2Option(PCRE2_UTF|PCRE2_ALT_BSUX).compile();
jp::VecNum vec_num;
jp::RegexMatch rm;
size_t count = rm.setRegexObject(&reg).setSubject(src).setNumberedSubstringVector(&vec_num).setModifier("g").match();
size_t count = rm.setRegexObject(&reg).setSubject(src).setNumberedSubstringVector(&vec_num).setModifier("gm").match();
std::vector<std::string> result;
if(!count)
return result;
@ -209,6 +209,7 @@ std::vector<std::string> regGetAllMatch(const std::string &src, const std::strin
{
match_index++;
index = begin;
continue;
}
result.push_back(std::move(vec_num[match_index][index]));
index++;
@ -220,5 +221,5 @@ std::vector<std::string> regGetAllMatch(const std::string &src, const std::strin
std::string regTrim(const std::string &src)
{
return regReplace(src, "^\\s*([\\s\\S]*)\\s*$", "$1", false, false);
return regReplace(src, R"(^\s*([\s\S]*)\s*$)", "$1", false, false);
}

View File

@ -8,105 +8,102 @@
class tribool
{
private:
char _M_VALUE = 0;
public:
tribool() : value_(indeterminate) {}
tribool(bool value) : value_(value ? true_value : false_value) {}
tribool(const std::string& str) { set(str); }
tribool() { clear(); }
tribool(const tribool& other) = default;
tribool& operator=(const tribool& other) = default;
template <typename T> tribool(const T &value) { set(value); }
tribool(const tribool &value) { _M_VALUE = value._M_VALUE; }
~tribool() = default;
tribool& operator=(const tribool &src)
tribool& operator=(bool value)
{
_M_VALUE = src._M_VALUE;
value_ = value ? true_value : false_value;
return *this;
}
template <typename T> tribool& operator=(const T &value)
bool operator==(const tribool& other) const { return value_ == other.value_; }
operator bool() const { return value_ == true_value; }
bool is_undef() const { return value_ == indeterminate; }
template <typename T> tribool& define(const T& value)
{
set(value);
return *this;
}
inline bool operator==(const tribool& rhs){ return _M_VALUE == rhs._M_VALUE; }
operator bool() const { return _M_VALUE == 3; }
bool is_undef() const { return _M_VALUE <= 1; }
template <typename T> tribool& define(const T &value)
{
if(_M_VALUE <= 1)
if (is_undef())
*this = value;
return *this;
}
template <typename T> tribool& parse(const T &value)
template <typename T> tribool& parse(const T& value)
{
return define(value);
}
tribool reverse()
{
if(_M_VALUE > 1)
_M_VALUE = _M_VALUE > 2 ? 2 : 3;
if (value_ == false_value)
value_ = true_value;
else if (value_ == true_value)
value_ = false_value;
return *this;
}
bool get(const bool &def_value = false) const
bool get(const bool& def_value = false) const
{
if(_M_VALUE <= 1)
if (is_undef())
return def_value;
return _M_VALUE == 3;
return value_ == true_value;
}
std::string get_str() const
{
switch(_M_VALUE)
switch (value_)
{
case 2:
return "false";
case 3:
return "true";
case indeterminate:
return "undef";
case false_value:
return "false";
case true_value:
return "true";
default:
return "";
}
return "undef";
}
template <typename T> bool set(const T &value)
template <typename T> bool set(const T& value)
{
_M_VALUE = (bool)value + 2;
return _M_VALUE > 2;
value_ = (bool)value ? true_value : false_value;
return value_;
}
bool set(const std::string &str)
bool set(const std::string& str)
{
switch(hash_(str))
switch (hash_(str))
{
case "true"_hash:
case "1"_hash:
_M_VALUE = 3;
break;
case "false"_hash:
case "0"_hash:
_M_VALUE = 2;
break;
default:
if(to_int(str, 0) > 1)
_M_VALUE = 3;
else
_M_VALUE = 0;
break;
case "true"_hash:
case "1"_hash:
value_ = true_value;
break;
case "false"_hash:
case "0"_hash:
value_ = false_value;
break;
default:
if (to_int(str, 0) > 1)
value_ = true_value;
else
value_ = indeterminate;
break;
}
return _M_VALUE;
return !is_undef();
}
void clear() { _M_VALUE = 0; }
void clear() { value_ = indeterminate; }
private:
enum value_type : char { indeterminate = 0, false_value = 1, true_value = 2 };
value_type value_;
};
#endif // TRIBOOL_H_INCLUDED