diff --git a/fe-app-tachyon/src/helpers/parseValueList.ts b/fe-app-tachyon/src/helpers/parseValueList.ts index 62680cec..541b422d 100644 --- a/fe-app-tachyon/src/helpers/parseValueList.ts +++ b/fe-app-tachyon/src/helpers/parseValueList.ts @@ -1,7 +1,13 @@ export function parseValueList(value: string): string[] { return value - .split(/\n/) - .map((line) => line.split('//')[0].split('#')[0]) + .split(/\r?\n/) + .map((line) => { + const stripped = line.replace( + /^(full|keyword|regex):[ \t]*(\/\/|#).*$/, + '', + ); + return stripped.split('//')[0].split('#')[0]; + }) .join(' ') .split(/[,\s]+/) .map((s) => s.trim()) diff --git a/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js b/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js index 102f661c..4164d5f6 100644 --- a/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js +++ b/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js @@ -1046,7 +1046,13 @@ function validateProxyUrl(url) { // src/helpers/parseValueList.ts function parseValueList(value) { - return value.split(/\n/).map((line) => line.split("//")[0].split("#")[0]).join(" ").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean); + return value.split(/\r?\n/).map((line) => { + const stripped = line.replace( + /^(full|keyword|regex):[ \t]*(\/\/|#).*$/, + "" + ); + return stripped.split("//")[0].split("#")[0]; + }).join(" ").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean); } // src/helpers/getProxyUrlName.ts diff --git a/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js b/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js index c04555e6..0384afdb 100644 --- a/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js +++ b/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js @@ -5521,8 +5521,9 @@ function parseCommentAwareListTokens(value) { let offset = 0; lines.forEach((line, index) => { - const hashIndex = line.indexOf("#"); - const slashIndex = line.indexOf("//"); + const cleanLine = line.replace(/^(full|keyword|regex):[ \t]*(\/\/|#).*$/, ""); + const hashIndex = cleanLine.indexOf("#"); + const slashIndex = cleanLine.indexOf("//"); let commentIndex = -1; if (hashIndex >= 0 && slashIndex >= 0) { @@ -5533,7 +5534,7 @@ function parseCommentAwareListTokens(value) { commentIndex = slashIndex; } - const source = commentIndex >= 0 ? line.slice(0, commentIndex) : line; + const source = commentIndex >= 0 ? cleanLine.slice(0, commentIndex) : cleanLine; const matcher = /[^,\s]+/g; let match; diff --git a/tachyon/files/usr/lib/config/migration.uc b/tachyon/files/usr/lib/config/migration.uc index d7ce00d3..52fea5b5 100644 --- a/tachyon/files/usr/lib/config/migration.uc +++ b/tachyon/files/usr/lib/config/migration.uc @@ -10,6 +10,7 @@ let core_url = require("core.url"); let constants_module = require("core.constants"); let singbox_constants_module = require("singbox.constants"); let domain_config = require("config.domain"); +let rule_config = require("config.rule"); let subscription_share_link = require("subscription.share_link"); let as_string = common.as_string; @@ -925,7 +926,9 @@ function migrate_connection_section(ctx, section, constants, legacy_connection_k } function strip_list_comment(line) { - line = replace(as_string(line), /[[:space:]]*\/\/.*$/, ""); + line = replace(as_string(line), /^(full|keyword|regex):[[:space:]]*\/\/.*$/, ""); + line = replace(line, /^(full|keyword|regex):[[:space:]]*#.*$/, ""); + line = replace(line, /[[:space:]]*\/\/.*$/, ""); return replace(line, /[[:space:]]*#.*$/, ""); } @@ -1004,7 +1007,11 @@ function has_domain_condition_prefix(value) { function add_values_with_prefix(result, seen, values, prefix) { for (let value in values) { value = trim(as_string(value)); - add_unique_value(result, seen, has_domain_condition_prefix(value) ? value : prefix + value); + if (value == "") + continue; + let candidate = has_domain_condition_prefix(value) ? value : prefix + value; + if (rule_config.prefixed_domain_kind_value(candidate) != null) + add_unique_value(result, seen, candidate); } } @@ -1038,10 +1045,14 @@ function migrate_combined_domain_conditions(ctx, section) { let values = []; let seen = {}; - for (let value in list_option(section, "domain_suffix")) - add_unique_value(values, seen, value); - for (let value in raw_text_condition_values(section, "domain_suffix_text")) - add_unique_value(values, seen, value); + for (let value in list_option(section, "domain_suffix")) { + if (rule_config.prefixed_domain_kind_value(value) != null) + add_unique_value(values, seen, value); + } + for (let value in raw_text_condition_values(section, "domain_suffix_text")) { + if (rule_config.prefixed_domain_kind_value(value) != null) + add_unique_value(values, seen, value); + } add_domain_values_with_prefix(values, seen, section, "domain", "full:", "domains"); add_domain_values_with_prefix(values, seen, section, "domain_keyword", "keyword:", "generic"); diff --git a/tachyon/files/usr/lib/config/rule.uc b/tachyon/files/usr/lib/config/rule.uc index 643fc30f..ffdd9646 100644 --- a/tachyon/files/usr/lib/config/rule.uc +++ b/tachyon/files/usr/lib/config/rule.uc @@ -7,7 +7,9 @@ let common = require("core.common"); let as_string = common.as_string; function strip_list_comment(line) { - line = replace(as_string(line), /[[:space:]]*\/\/.*$/, ""); + line = replace(as_string(line), /^(full|keyword|regex):[[:space:]]*\/\/.*$/, ""); + line = replace(line, /^(full|keyword|regex):[[:space:]]*#.*$/, ""); + line = replace(line, /[[:space:]]*\/\/.*$/, ""); return replace(line, /[[:space:]]*#.*$/, ""); } @@ -72,7 +74,7 @@ function prefixed_domain_kind_value(value) { if (prefix != "") body = strip_repeated_domain_prefix(prefix, body); - if (body == "") + if (body == "" || substr(body, 0, 2) == "//" || substr(body, 0, 1) == "#") return null; if (prefix == "") { diff --git a/tachyon/files/usr/lib/config/validator.uc b/tachyon/files/usr/lib/config/validator.uc index 5d3aafed..3614c6d0 100644 --- a/tachyon/files/usr/lib/config/validator.uc +++ b/tachyon/files/usr/lib/config/validator.uc @@ -1203,14 +1203,17 @@ function validate_combined_domain_value(value, section) { if (as_string(value) == "" || combined_domain_valid(value)) return; - fail_validation("Invalid domain condition '" + value + "' in rule '" + section + "'. Use plain domains or full:, keyword:, regex: prefixes. Aborted."); + log_message("Ignoring invalid domain condition '" + value + "' in rule '" + section + "'. Use plain domains or full:, keyword:, regex: prefixes.", "warn"); } function validate_combined_domain_text_value(value, section) { - if (as_string(value) == "" || combined_domain_text_valid(value)) + if (as_string(value) == "") return; - fail_validation("Invalid domain conditions in rule '" + section + "'. Use plain domains or full:, keyword:, regex: prefixes. Aborted."); + for (let item in rule_config.text_list_values(value, "comma-space")) { + if (!combined_domain_valid(item)) + log_message("Ignoring invalid domain condition '" + item + "' in rule '" + section + "'. Use plain domains or full:, keyword:, regex: prefixes.", "warn"); + } } function validate_service_value(service, context) { diff --git a/tachyon/files/usr/lib/core/helpers.uc b/tachyon/files/usr/lib/core/helpers.uc index 96d18361..0d0a408b 100644 --- a/tachyon/files/usr/lib/core/helpers.uc +++ b/tachyon/files/usr/lib/core/helpers.uc @@ -383,7 +383,9 @@ function normalize_strategy_whitespace(value) { } function strip_list_comment(line) { - line = replace(as_string(line), /[[:space:]]*\/\/.*$/, ""); + line = replace(as_string(line), /^(full|keyword|regex):[[:space:]]*\/\/.*$/, ""); + line = replace(line, /^(full|keyword|regex):[[:space:]]*#.*$/, ""); + line = replace(line, /[[:space:]]*\/\/.*$/, ""); return replace(line, /[[:space:]]*#.*$/, ""); } diff --git a/tachyon/files/usr/lib/diagnostics/fuzzer.uc b/tachyon/files/usr/lib/diagnostics/fuzzer.uc index e29c99e4..ab5c6cc3 100644 --- a/tachyon/files/usr/lib/diagnostics/fuzzer.uc +++ b/tachyon/files/usr/lib/diagnostics/fuzzer.uc @@ -2245,7 +2245,16 @@ function run_probe(engine, args_str, target_key, custom_url) { filter_prefix += "--filter-udp=443 --payload=quic_initial "; } - let spawn_cmd = sprintf("cd /tmp && %s --qnum=%d --fwmark=%s %s%s%s%s --pidfile=%s --daemon 2>%s", bin, qnum, FUZZER_FWMARK, lua_init_flags, blob_flags, filter_prefix, args_str, pid_path, shell_quote(stderr_log)); + let fwmark_flag = ""; + if (is_z2) { + if (index(args_str, "--fwmark") < 0) + fwmark_flag = sprintf("--fwmark=%s ", FUZZER_FWMARK); + } else { + if (index(args_str, "--dpi-desync-fwmark") < 0) + fwmark_flag = sprintf("--dpi-desync-fwmark=%s ", FUZZER_FWMARK); + } + + let spawn_cmd = sprintf("cd /tmp && %s --qnum=%d %s%s%s%s%s --pidfile=%s --daemon 2>%s", bin, qnum, fwmark_flag, lua_init_flags, blob_flags, filter_prefix, args_str, pid_path, shell_quote(stderr_log)); system(common.background_command(spawn_cmd)); let pid_running = false; diff --git a/tachyon/files/usr/lib/nft/apply.uc b/tachyon/files/usr/lib/nft/apply.uc index 683fcd85..4d241e57 100644 --- a/tachyon/files/usr/lib/nft/apply.uc +++ b/tachyon/files/usr/lib/nft/apply.uc @@ -140,7 +140,9 @@ function log_fatal(message) { } function strip_list_comment(line) { - line = replace(as_string(line), /[[:space:]]*\/\/.*$/, ""); + line = replace(as_string(line), /^(full|keyword|regex):[[:space:]]*\/\/.*$/, ""); + line = replace(line, /^(full|keyword|regex):[[:space:]]*#.*$/, ""); + line = replace(line, /[[:space:]]*\/\/.*$/, ""); return replace(line, /[[:space:]]*#.*$/, ""); } diff --git a/tests/config_validator_runtime.sh b/tests/config_validator_runtime.sh index 97c11878..560b7250 100644 --- a/tests/config_validator_runtime.sh +++ b/tests/config_validator_runtime.sh @@ -522,4 +522,29 @@ fi printf '%s\n' "$output" | grep -Fq "ByeDPI listen address and port are assigned" || fail "executable byedpi provider: unexpected message '$output'" +# ─── Domain condition validation resilience ───────────────────────────────── +cat >"$WORK_DIR/domain_resilience.json" <<'JSON' +{ + "settings": { + ".name": "settings", + ".type": "settings", + "dns_server": [ "77.88.8.8" ], + "bootstrap_dns_server": [ "77.88.8.8" ] + }, + "section": [ + { + ".name": "resilient_rule", + ".type": "section", + "enabled": "1", + "action": "bypass", + "domain": "valid.example.com\nfull://\nfull:=====\nfull:(не\nanother.example.org" + } + ] +} +JSON + +if ! TACHYON_LIB="$TACHYON_LIB" ucode -L "$TACHYON_LIB" "$VALIDATOR" validate-runtime-fixture "$WORK_DIR/domain_resilience.json" "{}" >/dev/null 2>&1; then + fail "Validator must not abort startup on invalid domain conditions in rules" +fi + printf 'config validator runtime checks passed\n'