Files
NeoTachyon/tachyon/files/usr/lib/core/helpers.uc
T
Dushnilin bb1de1fd83 Merge branch 'temp-upstream-rebrand' into main
Resolves merge conflicts:
- install.sh, build.sh, .github/workflows/build.yml: Reverted to Dushnilin's versions to avoid installing SourceForge mirrors.
- fe-app-tachyon: Resolved type, formatting and copyOutbound conflicts, keeping both Dushnilin's custom features and ushan0v's frontend performance optimizations.
- backend: Kept Dushnilin's ucode refactorings, Telegram bot integration, and release repository URL (Dushnilin/tachyon) while merging updates.uc cache format v8 & persistent cache format v7.
- tests: Merged tests from both branches, preserving ucode -L wrappers for local test execution.
2026-07-14 08:01:45 +03:00

544 lines
16 KiB
Ucode

#!/usr/bin/env ucode
let fs = require("fs");
const SB_DNS_SERVER_TAG = getenv("SB_DNS_SERVER_TAG") || "dns-server";
const SB_FAKEIP_DNS_SERVER_TAG = getenv("SB_FAKEIP_DNS_SERVER_TAG") || "fakeip-server";
const SB_BOOTSTRAP_SERVER_TAG = getenv("SB_BOOTSTRAP_SERVER_TAG") || "bootstrap-dns-server";
const SB_FAKEIP_DNS_RULE_TAG = getenv("SB_FAKEIP_DNS_RULE_TAG") || "fakeip-dns-rule-tag";
const SB_FAKEIP_RULESET_DNS_RULE_TAG = getenv("SB_FAKEIP_RULESET_DNS_RULE_TAG") || "fakeip-ruleset-dns-rule-tag";
const SB_SERVICE_FAKEIP_DNS_RULE_TAG = getenv("SB_SERVICE_FAKEIP_DNS_RULE_TAG") || "service-fakeip-dns-rule-tag";
const SB_TPROXY_INBOUND_TAG = getenv("SB_TPROXY_INBOUND_TAG") || "tproxy-in";
const SB_TPROXY_INBOUND6_TAG = getenv("SB_TPROXY_INBOUND6_TAG") || "tproxy6-in";
const SB_DNS_INBOUND_TAG = getenv("SB_DNS_INBOUND_TAG") || "dns-in";
const SB_SERVICE_MIXED_INBOUND_TAG = getenv("SB_SERVICE_MIXED_INBOUND_TAG") || "service-mixed-in";
const SB_DIRECT_OUTBOUND_TAG = getenv("SB_DIRECT_OUTBOUND_TAG") || "direct-out";
const SB_BYPASS_OUTBOUND_TAG = getenv("SB_BYPASS_OUTBOUND_TAG") || "bypass-out";
let common = require("core.common");
let core_ip = require("core.ip");
let as_string = common.as_string;
function ascii_lower(value) {
return lc(as_string(value));
}
function file_has_cr(path) {
let data = fs.readfile(as_string(path));
if (data == null)
return null;
return index(data, "\r") >= 0;
}
function file_has_cr_exit(path) {
let result = file_has_cr(path);
exit(result == null ? 2 : (result ? 0 : 1));
}
function write_text_file(path, text) {
let result = fs.writefile(path, as_string(text));
if (result == null)
return false;
if (type(result) == "boolean" && !result)
return false;
return true;
}
function file_remove_cr(path) {
path = as_string(path);
let data = fs.readfile(path);
if (data == null)
return true;
if (index(data, "\r") < 0)
return true;
let stamp = clock();
let tmp_path = sprintf("%s.%d.%d.tmp", path, stamp[0], stamp[1]);
if (!write_text_file(tmp_path, replace(data, /\r/g, "")))
return false;
if (!fs.rename(tmp_path, path)) {
fs.unlink(tmp_path);
return false;
}
return true;
}
function valid_ipv4(value) {
return core_ip.valid_ipv4(value, true, true);
}
function valid_ipv4_cidr(value) {
return core_ip.valid_ipv4_cidr(value, true);
}
function valid_domain(value) {
return match(ascii_lower(value), /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/) != null;
}
function valid_domain_suffix(value) {
value = as_string(value);
if (substr(value, 0, 1) == ".")
value = substr(value, 1);
return valid_domain(value);
}
function digit_char(value) {
return match(as_string(value), /^[0-9]$/) != null;
}
function strip_leading_zeroes(value) {
value = as_string(value);
let i = 0;
while (i < length(value) - 1 && substr(value, i, 1) == "0")
i++;
return substr(value, i);
}
function version_compare(lhs, rhs) {
lhs = as_string(lhs);
rhs = as_string(rhs);
let li = 0, ri = 0;
while (li < length(lhs) || ri < length(rhs)) {
if (li >= length(lhs))
return substr(rhs, ri, 1) == "~" ? 1 : -1;
if (ri >= length(rhs))
return substr(lhs, li, 1) == "~" ? -1 : 1;
let lc = substr(lhs, li, 1);
let rc = substr(rhs, ri, 1);
if (lc == rc) {
li++;
ri++;
continue;
}
if (lc == "~" || rc == "~")
return lc == "~" ? -1 : 1;
if (digit_char(lc) && digit_char(rc)) {
let ls = li, rs = ri;
while (li < length(lhs) && digit_char(substr(lhs, li, 1)))
li++;
while (ri < length(rhs) && digit_char(substr(rhs, ri, 1)))
ri++;
let lnum = strip_leading_zeroes(substr(lhs, ls, li - ls));
let rnum = strip_leading_zeroes(substr(rhs, rs, ri - rs));
if (length(lnum) != length(rnum))
return length(lnum) < length(rnum) ? -1 : 1;
if (lnum != rnum)
return lnum < rnum ? -1 : 1;
continue;
}
return lc < rc ? -1 : 1;
}
return 0;
}
function version_at_least(current, required) {
return version_compare(current, required) >= 0;
}
function first_index_any(value, needles, start) {
value = as_string(value);
start = int(start || 0);
for (let i = start; i < length(value); i++) {
let c = substr(value, i, 1);
for (let needle in needles)
if (c == needle)
return i;
}
return -1;
}
function strip_anchored_scheme(value) {
value = as_string(value);
let marker = index(value, "://");
if (marker < 0)
return value;
let prefix = substr(value, 0, marker);
if (index(prefix, "/") >= 0 || index(prefix, "?") >= 0)
return value;
return substr(value, marker + 3);
}
function strip_first_scheme_marker(value) {
value = as_string(value);
let marker = index(value, "://");
return marker >= 0 ? substr(value, marker + 3) : value;
}
function url_get_scheme(value) {
value = as_string(value);
let marker = index(value, "://");
print(marker >= 0 ? substr(value, 0, marker) : value, "\n");
}
function url_get_userinfo(value) {
value = strip_anchored_scheme(value);
let at = index(value, "@");
if (at >= 0)
print(substr(value, 0, at), "\n");
}
function url_authority(value) {
value = strip_first_scheme_marker(value);
let at = index(value, "@");
if (at >= 0)
value = substr(value, at + 1);
let end = first_index_any(value, ["/", "?", "#"], 0);
return end >= 0 ? substr(value, 0, end) : value;
}
function url_get_host(value) {
let authority = url_authority(value);
let colon = index(authority, ":");
print(colon >= 0 ? substr(authority, 0, colon) : authority, "\n");
}
function url_get_port(value) {
let authority = url_authority(value);
let colon = index(authority, ":");
print(colon >= 0 ? substr(authority, colon + 1) : "", "\n");
}
function url_get_path(value) {
value = strip_anchored_scheme(value);
let slash = index(value, "/");
value = slash >= 0 ? substr(value, slash) : "";
let query = index(value, "?");
if (query >= 0)
value = substr(value, 0, query);
print(value, "\n");
}
function url_get_query_param(value, param) {
value = as_string(value);
param = as_string(param);
let result = null;
for (let i = 0; i < length(value); i++) {
let separator = substr(value, i, 1);
if (separator != "?" && separator != "&")
continue;
let prefix_start = i + 1;
if (substr(value, prefix_start, length(param) + 1) != param + "=")
continue;
let value_start = prefix_start + length(param) + 1;
let value_end = first_index_any(value, ["&", "?", "#"], value_start);
result = value_end >= 0 ? substr(value, value_start, value_end - value_start) : substr(value, value_start);
}
print(result != null ? result : "", "\n");
}
function url_file_extension(value) {
let basename = as_string(value);
let slash = rindex(basename, "/");
if (slash >= 0)
basename = substr(basename, slash + 1);
let query = index(basename, "?");
if (query >= 0)
basename = substr(basename, 0, query);
let fragment = index(basename, "#");
if (fragment >= 0)
basename = substr(basename, 0, fragment);
let dot = rindex(basename, ".");
if (dot >= 0)
print(substr(basename, dot + 1), "\n");
else
print("\n");
}
function url_strip_fragment(value) {
value = as_string(value);
let fragment = index(value, "#");
print(fragment >= 0 ? substr(value, 0, fragment) : value, "\n");
}
function normalize_strategy_whitespace(value) {
value = replace(as_string(value), /[\t\r\n]/g, " ");
value = replace(value, / +/g, " ");
value = replace(value, /^ /, "");
value = replace(value, / $/, "");
print(value);
}
function strip_list_comment(line) {
line = replace(as_string(line), /[[:space:]]*\/\/.*$/, "");
return replace(line, /[[:space:]]*#.*$/, "");
}
function print_lines(values) {
for (let value in values)
print(as_string(value), "\n");
}
function text_list_values(value, separator_mode) {
let result = [];
separator_mode = as_string(separator_mode);
for (let line in split(as_string(value), "\n")) {
line = strip_list_comment(line);
line = separator_mode == "comma-space"
? replace(line, /[ ,]/g, "\n")
: replace(line, /,/g, "\n");
for (let item in split(line, "\n")) {
item = trim(replace(item, /\r/g, ""));
if (item != "")
push(result, item);
}
}
return result;
}
function text_list_to_lines(value, separator_mode) {
print_lines(text_list_values(value, separator_mode));
}
function stdin_lines_to_json_array() {
let input = common.read_stdin();
if (input == "") {
print("[]\n");
return;
}
if (substr(input, length(input) - 1) == "\n")
input = substr(input, 0, length(input) - 1);
common.write_compact_string_array(split(input, "\n"));
}
function network_status_ipv4_address() {
let value = common.read_stdin_json();
if (type(value) != "object")
return;
let addresses = value["ipv4-address"];
if (type(addresses) != "array" || length(addresses) == 0 || type(addresses[0]) != "object")
return;
let address = as_string(addresses[0].address || "");
if (address != "")
print(address, "\n");
}
function stdin_first_line_last_field() {
let input = common.read_stdin();
if (input == "")
return;
let newline = index(input, "\n");
let line = newline >= 0 ? substr(input, 0, newline) : input;
let trimmed = trim(line);
if (trimmed == "") {
print(line, "\n");
return;
}
let fields = split(trimmed, /[ \t\r\n]+/);
if (length(fields) > 0 && fields[0] != "")
print(fields[length(fields) - 1], "\n");
else
print("\n");
}
function stdin_trim_string() {
print(trim(common.read_stdin()), "\n");
}
function whitespace_list_contains(list, needle) {
needle = as_string(needle);
for (let item in split(trim(as_string(list)), /[ \t\r\n]+/))
if (as_string(item) == needle)
return true;
return false;
}
function download_via_proxy_option_for_purpose(purpose) {
purpose = as_string(purpose || "lists");
if (purpose == "lists")
print("download_lists_via_proxy\n");
else if (purpose == "components")
print("download_components_via_proxy\n");
else
exit(1);
}
function md5sum_hex_prefix(prefix_length) {
let input = common.read_stdin();
let newline = index(input, "\n");
let line = newline >= 0 ? substr(input, 0, newline) : input;
let fields = split(trim(as_string(line)), /[ \t\r\n]+/);
let hash = length(fields) > 0 ? as_string(fields[0]) : "";
prefix_length = int(prefix_length || 0);
if (prefix_length > 0)
print(substr(hash, 0, prefix_length), "\n");
}
function md5sum_hwid() {
let input = common.read_stdin();
let newline = index(input, "\n");
let line = newline >= 0 ? substr(input, 0, newline) : input;
let fields = split(trim(as_string(line)), /[ \t\r\n]+/);
let hash = length(fields) > 0 ? substr(as_string(fields[0]), 0, 16) : "";
print(
substr(hash, 0, 4), "-",
substr(hash, 4, 4), "-",
substr(hash, 8, 4), "-",
substr(hash, 12, 4), "\n"
);
}
function tag_is_reserved(tag, reserved) {
tag = as_string(tag);
for (let value in reserved)
if (tag == as_string(value))
return true;
return false;
}
function allocate_runtime_tag_value(base, postfix, reserved) {
base = as_string(base);
postfix = as_string(postfix);
reserved = type(reserved) == "array" ? reserved : [];
let candidate = base + "-" + postfix;
let suffix = 1;
while (tag_is_reserved(candidate, reserved)) {
candidate = base + "-" + postfix + "-" + suffix;
suffix++;
}
return candidate;
}
function default_reserved_runtime_tags() {
return [
SB_DNS_SERVER_TAG,
SB_FAKEIP_DNS_SERVER_TAG,
SB_BOOTSTRAP_SERVER_TAG,
SB_FAKEIP_DNS_RULE_TAG,
SB_FAKEIP_RULESET_DNS_RULE_TAG,
SB_SERVICE_FAKEIP_DNS_RULE_TAG,
SB_TPROXY_INBOUND_TAG,
SB_TPROXY_INBOUND6_TAG,
SB_DNS_INBOUND_TAG,
SB_SERVICE_MIXED_INBOUND_TAG,
SB_DIRECT_OUTBOUND_TAG,
SB_BYPASS_OUTBOUND_TAG
];
}
function print_runtime_tag(base, postfix, reserved) {
print(allocate_runtime_tag_value(base, postfix, reserved), "\n");
}
function allocate_runtime_tag(base, postfix) {
print_runtime_tag(base, postfix, slice(ARGV, 3));
}
function sing_box_version_is_extended(value) {
return index(as_string(value), "extended") >= 0;
}
let mode = ARGV[0] || "";
if (mode == "stdin-lines-to-json-array")
stdin_lines_to_json_array();
else if (mode == "file-has-cr")
file_has_cr_exit(ARGV[1]);
else if (mode == "file-remove-cr")
exit(file_remove_cr(ARGV[1]) ? 0 : 1);
else if (mode == "valid-ipv4")
exit(valid_ipv4(ARGV[1]) ? 0 : 1);
else if (mode == "valid-ipv4-cidr")
exit(valid_ipv4_cidr(ARGV[1]) ? 0 : 1);
else if (mode == "valid-domain")
exit(valid_domain(ARGV[1]) ? 0 : 1);
else if (mode == "valid-domain-suffix")
exit(valid_domain_suffix(ARGV[1]) ? 0 : 1);
else if (mode == "version-at-least")
exit(version_at_least(ARGV[1], ARGV[2]) ? 0 : 1);
else if (mode == "network-status-ipv4-address")
network_status_ipv4_address();
else if (mode == "stdin-first-line-last-field")
stdin_first_line_last_field();
else if (mode == "stdin-trim-string")
stdin_trim_string();
else if (mode == "whitespace-list-contains")
exit(whitespace_list_contains(ARGV[1], ARGV[2]) ? 0 : 1);
else if (mode == "download-via-proxy-option-for-purpose")
download_via_proxy_option_for_purpose(ARGV[1]);
else if (mode == "md5sum-hex-prefix")
md5sum_hex_prefix(ARGV[1]);
else if (mode == "md5sum-hwid")
md5sum_hwid();
else if (mode == "allocate-runtime-tag")
allocate_runtime_tag(ARGV[1], ARGV[2]);
else if (mode == "inbound-tag")
print_runtime_tag(ARGV[1], "in", default_reserved_runtime_tags());
else if (mode == "server-inbound-tag")
print_runtime_tag("server-" + as_string(ARGV[1]), "in", default_reserved_runtime_tags());
else if (mode == "tailscale-dns-tag")
print_runtime_tag("server-" + as_string(ARGV[1]), "tailscale-dns", default_reserved_runtime_tags());
else if (mode == "outbound-tag")
print_runtime_tag(ARGV[1], "out", default_reserved_runtime_tags());
else if (mode == "domain-resolver-tag")
print_runtime_tag(ARGV[1], "domain-resolver", default_reserved_runtime_tags());
else if (mode == "sing-box-version-is-extended")
exit(sing_box_version_is_extended(ARGV[1]) ? 0 : 1);
else if (mode == "url-get-scheme")
url_get_scheme(ARGV[1]);
else if (mode == "url-get-userinfo")
url_get_userinfo(ARGV[1]);
else if (mode == "url-get-host")
url_get_host(ARGV[1]);
else if (mode == "url-get-port")
url_get_port(ARGV[1]);
else if (mode == "url-get-path")
url_get_path(ARGV[1]);
else if (mode == "url-get-query-param")
url_get_query_param(ARGV[1], ARGV[2]);
else if (mode == "url-file-extension")
url_file_extension(ARGV[1]);
else if (mode == "url-strip-fragment")
url_strip_fragment(ARGV[1]);
else if (mode == "normalize-strategy-whitespace")
normalize_strategy_whitespace(ARGV[1]);
else if (mode == "text-list-to-lines")
text_list_to_lines(ARGV[1], ARGV[2]);
else {
warn("Usage: core/helpers.uc <operation> ...\n");
exit(1);
}