diff --git a/fe-app-tachyon/package-lock.json b/fe-app-tachyon/package-lock.json index d3fdde9a..3afcfd5b 100644 --- a/fe-app-tachyon/package-lock.json +++ b/fe-app-tachyon/package-lock.json @@ -1,12 +1,12 @@ { "name": "fe-app-tachyon", - "version": "1.2.17", + "version": "1.2.20", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fe-app-tachyon", - "version": "1.2.17", + "version": "1.2.20", "license": "MIT", "devDependencies": { "@babel/parser": "7.28.4", diff --git a/fe-app-tachyon/package.json b/fe-app-tachyon/package.json index 116919c2..ddfe9363 100644 --- a/fe-app-tachyon/package.json +++ b/fe-app-tachyon/package.json @@ -1,6 +1,6 @@ { "name": "fe-app-tachyon", - "version": "1.2.17", + "version": "1.2.20", "license": "MIT", "type": "module", "scripts": { diff --git a/tachyon/files/usr/lib/core/common.uc b/tachyon/files/usr/lib/core/common.uc index bba6c761..82928b35 100644 --- a/tachyon/files/usr/lib/core/common.uc +++ b/tachyon/files/usr/lib/core/common.uc @@ -159,14 +159,9 @@ function command_success_from_args(args) { } function command_capture(command) { - let output = ""; let p = fs.popen(command, "r"); if (!p) return null; - let chunk; - while ((chunk = p.read('all')) != null) { - if (length(chunk) == 0) break; - output += chunk; - } + let output = as_string(p.read("all") || ""); let status = p.close(); status = status > 255 ? int(status / 256) : status; return { status: status, output: output }; diff --git a/tachyon/files/usr/lib/core/url.uc b/tachyon/files/usr/lib/core/url.uc index c600fc90..c5d88ace 100644 --- a/tachyon/files/usr/lib/core/url.uc +++ b/tachyon/files/usr/lib/core/url.uc @@ -18,35 +18,42 @@ function first_index_any(value, needles, start) { return -1; } -function hex_digit_value(value) { - value = ord(lc(as_string(value))); - if (value >= 48 && value <= 57) - return value - 48; - if (value >= 97 && value <= 102) - return value - 87; +function hex_digit_value(code) { + if (code >= 48 && code <= 57) + return code - 48; + if (code >= 97 && code <= 102) + return code - 87; + if (code >= 65 && code <= 70) + return code - 55; return -1; } function decode(value) { value = as_string(value); - let result = ""; + if (index(value, "%") < 0 && index(value, "+") < 0) + return value; - for (let i = 0; i < length(value); i++) { - let c = substr(value, i, 1); - if (c == "+") { + let result = ""; + let len = length(value); + + for (let i = 0; i < len; i++) { + let code = ord(value, i); + if (code == 43) { result += " "; continue; } - if (c == "%") { - let high = i + 1 < length(value) ? hex_digit_value(substr(value, i + 1, 1)) : -1; - let low = i + 2 < length(value) ? hex_digit_value(substr(value, i + 2, 1)) : -1; - if (high >= 0 && low >= 0) { - result += chr(high * 16 + low); - i += 2; - continue; + if (code == 37) { + if (i + 2 < len) { + let high = hex_digit_value(ord(value, i + 1)); + let low = hex_digit_value(ord(value, i + 2)); + if (high >= 0 && low >= 0) { + result += chr(high * 16 + low); + i += 2; + continue; + } } } - result += c; + result += chr(code); } return result; diff --git a/tachyon/files/usr/lib/service/lifecycle.uc b/tachyon/files/usr/lib/service/lifecycle.uc index 69f232d3..af1f39a4 100644 --- a/tachyon/files/usr/lib/service/lifecycle.uc +++ b/tachyon/files/usr/lib/service/lifecycle.uc @@ -202,13 +202,10 @@ function external_config_fingerprint() { return join("\n", lines); } -function trim(value) { - return replace(as_string(value), /^[ \t\r\n]+|[ \t\r\n]+$/g, ""); -} - function owner_pid() { - let pid = trim(command_output_from_args([ "sh", "-c", "echo $PPID" ])); - return match(pid, /^[0-9]+$/) != null ? pid : "0"; + let status = fs.readfile("/proc/self/status") || ""; + let m = match(status, /PPid:\s+([0-9]+)/); + return m ? m[1] : "0"; } function bool_text(value) { diff --git a/tachyon/files/usr/lib/service/state.uc b/tachyon/files/usr/lib/service/state.uc index 9e827027..6a2d8a00 100644 --- a/tachyon/files/usr/lib/service/state.uc +++ b/tachyon/files/usr/lib/service/state.uc @@ -202,13 +202,12 @@ function numeric_text(value) { } function current_epoch() { - let value = command_trimmed_output_from_args([ "date", "+%s" ]); - return numeric_text(value) ? value : "0"; + return as_string(time()); } function current_year() { - let value = command_trimmed_output_from_args([ "date", "+%Y" ]); - return numeric_text(value) ? int(value) : null; + let tm = localtime(time()); + return tm ? tm.year + 1900 : null; } function time_sync_needed(year) { diff --git a/tachyon/files/usr/lib/service/watchdog.uc b/tachyon/files/usr/lib/service/watchdog.uc index 988dc089..1b090e4b 100644 --- a/tachyon/files/usr/lib/service/watchdog.uc +++ b/tachyon/files/usr/lib/service/watchdog.uc @@ -62,7 +62,7 @@ function send_telegram_notification(message) { } function process_running(pid) { - return match(as_string(pid), /^[0-9]+$/) != null && command_success_from_args([ "kill", "-0", pid ]); + return match(as_string(pid), /^[0-9]+$/) != null && fs.stat("/proc/" + pid) != null; } function stop_runtime() { @@ -361,21 +361,42 @@ function worker() { } } - let pids = split(trim(command_output_from_args(["pidof", binary_name])), /\s+/); - let pid = (length(pids) > 0 && pids[0] != "") ? pids[0] : ""; + let pid = ""; + let proc = fs.opendir("/proc"); + if (proc) { + let entry; + while ((entry = proc.read()) != null) { + if (match(entry, /^[0-9]+$/)) { + let exe = fs.readlink("/proc/" + entry + "/exe") || ""; + let slash = rindex(exe, "/"); + if ((slash >= 0 ? substr(exe, slash + 1) : exe) == binary_name) { + pid = entry; + break; + } + } + } + proc.close(); + } let list_update_pid = trim(fs.readfile("/var/run/tachyon_list_update.pid") || ""); let list_update_running = process_running(list_update_pid); let tachyon_cli_running = false; - let tachyon_cli_pids = split(trim(command_output_from_args(["pgrep", "-f", "/usr/bin/tachyon "])), /\s+/); - for (let cli_pid in tachyon_cli_pids) { - if (cli_pid == "") continue; - let cmdline = trim(command_output_from_args(["cat", "/proc/" + cli_pid + "/cmdline"]) || ""); - if (index(cmdline, "start") >= 0 || index(cmdline, "restart") >= 0 || index(cmdline, "reload") >= 0 || index(cmdline, "stop") >= 0) { - tachyon_cli_running = true; - break; + proc = fs.opendir("/proc"); + if (proc) { + let entry; + while ((entry = proc.read()) != null) { + if (match(entry, /^[0-9]+$/)) { + let cmdline = fs.readfile("/proc/" + entry + "/cmdline") || ""; + if (index(cmdline, "/usr/bin/tachyon") >= 0) { + if (index(cmdline, "start") >= 0 || index(cmdline, "restart") >= 0 || index(cmdline, "reload") >= 0 || index(cmdline, "stop") >= 0) { + tachyon_cli_running = true; + break; + } + } + } } + proc.close(); } if (tachyon_cli_running) { diff --git a/tachyon/files/usr/lib/singbox/servers.uc b/tachyon/files/usr/lib/singbox/servers.uc index a2f51ce1..b7c21815 100644 --- a/tachyon/files/usr/lib/singbox/servers.uc +++ b/tachyon/files/usr/lib/singbox/servers.uc @@ -11,20 +11,14 @@ let int_option = common.int_option; let array_or_empty = common.array_or_empty; function safe_filename(value) { - value = as_string(value); - let result = ""; - for (let i = 0; i < length(value); i++) { - let ch = substr(value, i, 1); - result += match(ch, /^[A-Za-z0-9_.-]$/) ? ch : "_"; - } - return result; + return replace(as_string(value), /[^A-Za-z0-9_.-]/g, "_"); } function string_to_hex(value) { value = as_string(value); let result = ""; for (let i = 0; i < length(value); i++) - result += sprintf("%02x", ord(substr(value, i, 1))); + result += sprintf("%02x", ord(value, i)); return result; } diff --git a/tachyon/files/usr/lib/singbox/urltest.uc b/tachyon/files/usr/lib/singbox/urltest.uc index 4ea34a3c..419c36be 100644 --- a/tachyon/files/usr/lib/singbox/urltest.uc +++ b/tachyon/files/usr/lib/singbox/urltest.uc @@ -38,7 +38,7 @@ function normalized_country_list(values) { } function byte_at(value, index) { - return ord(substr(value, index, 1)); + return ord(value, index); } function regional_indicator_letter(value, index) { @@ -90,14 +90,24 @@ function regex_matching_tag_array(tags, names, regexes) { tags = array_or_empty(tags); names = object_or_empty(names); regexes = array_or_empty(regexes); + + let compiled = []; + for (let pattern in regexes) { + let pat_str = as_string(pattern); + if (pat_str != "") { + try { + push(compiled, regexp(pat_str)); + } catch(e) {} + } + } + let result = []; - for (let tag in tags) { let name = names[tag]; name = name == null || as_string(name) == "" ? tag : as_string(name); - for (let pattern in regexes) { - if (regex_matches(name, pattern)) { + for (let re in compiled) { + if (match(name, re) != null) { push(result, tag); break; } diff --git a/tachyon/files/usr/lib/subscription/parser.uc b/tachyon/files/usr/lib/subscription/parser.uc index fa35b64c..f024a601 100644 --- a/tachyon/files/usr/lib/subscription/parser.uc +++ b/tachyon/files/usr/lib/subscription/parser.uc @@ -6,36 +6,9 @@ function as_string(value) { return value == null ? "" : "" + value; } -function trim(value) { - value = as_string(value); - let start = 0; - let end = length(value); - - while (start < end) { - let c = substr(value, start, 1); - if (c != " " && c != "\t" && c != "\r" && c != "\n") - break; - start++; - } - - while (end > start) { - let c = substr(value, end - 1, 1); - if (c != " " && c != "\t" && c != "\r" && c != "\n") - break; - end--; - } - - return substr(value, start, end - start); -} - function first_non_ws_char(value) { - value = as_string(value); - for (let i = 0; i < length(value); i++) { - let c = substr(value, i, 1); - if (c != " " && c != "\t" && c != "\r" && c != "\n") - return c; - } - return ""; + let m = match(as_string(value), /^[ \t\r\n]*([^ \t\r\n])/); + return m ? m[1] : ""; } function starts_with(value, prefix) { @@ -83,14 +56,7 @@ function is_true(value) { } function is_integer_string(value) { - if (type(value) != "string" || value == "") - return false; - for (let i = 0; i < length(value); i++) { - let code = ord(substr(value, i, 1)); - if (code < 48 || code > 57) - return false; - } - return true; + return type(value) == "string" && match(value, /^[0-9]+$/) != null; } function xhttp_value_present(value) { @@ -1687,39 +1653,14 @@ function metadata_clean_text(value, max, decode_base64) { value = decoded == null ? "" : decoded; } - let cleaned = ""; - let last_space = false; - for (let i = 0; i < length(value); i++) { - let char = substr(value, i, 1); - let code = ord(char); - if (code < 32 || code == 127) - char = " "; - - if (char == " ") { - if (!last_space) - cleaned += " "; - last_space = true; - } - else { - cleaned += char; - last_space = false; - } - } - - cleaned = trim(cleaned); + let cleaned = trim(replace(replace(value, /[\x00-\x1f\x7f]/g, " "), / +/g, " ")); if (cleaned == "") return null; return length(cleaned) > max ? substr(cleaned, 0, max) : cleaned; } function metadata_has_control_or_space(value) { - value = as_string(value); - for (let i = 0; i < length(value); i++) { - let code = ord(substr(value, i, 1)); - if (code <= 32 || code == 127) - return true; - } - return false; + return match(as_string(value), /[\x00-\x20\x7f]/) != null; } function metadata_clean_url(value) { @@ -1733,23 +1674,13 @@ function metadata_clean_url(value) { function metadata_clean_number(value) { value = metadata_trim_text(value); - if (value == "") + if (value == "" || value == null) return null; - for (let i = 0; i < length(value); i++) { - let code = ord(substr(value, i, 1)); - if (code < 48 || code > 57) - return null; - } - return value; + return match(value, /^[0-9]+$/) != null ? value : null; } function metadata_replace_path_separators(value) { - let result = ""; - for (let i = 0; i < length(value); i++) { - let char = substr(value, i, 1); - result += (char == "/" || char == "\\") ? "_" : char; - } - return result; + return replace(as_string(value), /[\/\\]/g, "_"); } function metadata_content_disposition_filename(value) { diff --git a/tachyon/files/usr/lib/subscription/share_link.uc b/tachyon/files/usr/lib/subscription/share_link.uc index 29a8a58a..f12138f7 100644 --- a/tachyon/files/usr/lib/subscription/share_link.uc +++ b/tachyon/files/usr/lib/subscription/share_link.uc @@ -15,14 +15,14 @@ function starts_with(value, prefix) { function uri_encode(value) { value = as_string(value); let result = ""; - for (let i = 0; i < length(value); i++) { - let char = substr(value, i, 1); - let code = ord(char); + let len = length(value); + for (let i = 0; i < len; i++) { + let code = ord(value, i); if ((code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || - char == "-" || char == "_" || char == "." || char == "~") - result += char; + code == 45 || code == 95 || code == 46 || code == 126) + result += chr(code); else result += sprintf("%%%02X", code); } @@ -30,10 +30,7 @@ function uri_encode(value) { } function base64_encode(value) { - let encoded = b64enc(as_string(value)); - while (length(encoded) > 0 && substr(encoded, length(encoded) - 1) == "=") - encoded = substr(encoded, 0, length(encoded) - 1); - return encoded; + return replace(b64enc(as_string(value)), /=+$/, ""); } function host_port(server, port) { diff --git a/tests/luci_dynamiclist_layout.sh b/tests/luci_dynamiclist_layout.sh index 41919666..ec7d412b 100644 --- a/tests/luci_dynamiclist_layout.sh +++ b/tests/luci_dynamiclist_layout.sh @@ -3,12 +3,18 @@ set -eo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SECTION_JS="$ROOT_DIR/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js" +# Normalize SECTION_JS line endings to a temp file to prevent sed parsing failures on CRLF checkouts +SECTION_JS_LF="$(mktemp)" +tr -d '\r' < "$SECTION_JS" > "$SECTION_JS_LF" +SECTION_JS="$SECTION_JS_LF" +trap 'rm -f "$SECTION_JS_LF"' EXIT fail() { printf 'FAIL: %s\n' "$1" >&2 exit 1 } + wrapper_styles="$(sed -n '/^\.fkp-button-add-dynlist > \.add-item {$/,/^}$/p' "$SECTION_JS")" button_styles="$(sed -n '/^\.fkp-button-add-dynlist > \.add-item > \.cbi-button-add {$/,/^}$/p' "$SECTION_JS")" urltest_options="$(sed -n '/^function addUrlTestItemOptions(/,/^function priorityLevelSettingsForValidation(/p' "$SECTION_JS")"