mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 20:45:13 +08:00
🐶 Sync 2026-09-26 16:35:25
This commit is contained in:
1 parent
1eb453ca2e
commit
3f39d2b62b
44 files changed
+2741
-2902
No files matched your search
@@ -200,7 +200,11 @@ find_wan_zone_section_state() {
|
||||
_firewall_show=$(uci -q show firewall 2>/dev/null || true)
|
||||
_zones=$(printf '%s\n' "$_firewall_show" \
|
||||
| sed -n 's/^firewall\.\([^.=]*\)=zone$/\1/p')
|
||||
for zone in $_zones; do
|
||||
# Line-wise here-doc (not an unquoted for-loop): @zone[N] is a glob
|
||||
# character class and would expand when cwd contains @zoneN. The
|
||||
# while-read stays in the current shell (here-doc, not a pipeline)
|
||||
# so WAN_ZONE_* mutations and return reach the caller.
|
||||
while IFS= read -r zone || [ -n "$zone" ]; do
|
||||
[ -n "$zone" ] || continue
|
||||
_name=$(uci -q get "firewall.${zone}.name" 2>/dev/null || true)
|
||||
if [ -z "$_name" ]; then
|
||||
@@ -239,7 +243,9 @@ find_wan_zone_section_state() {
|
||||
WAN_ZONE_FOUND="$zone"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
done <<EOF
|
||||
$_zones
|
||||
EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -105,9 +105,16 @@ uci_rule_names() {
|
||||
}
|
||||
|
||||
is_uci_style_name() {
|
||||
# Domain: non-empty UCI-style identifier, every character in
|
||||
# [A-Za-z0-9_-]. That is the map-key set this helper admits
|
||||
# (names parseRuleHint can look up). nft comments can print
|
||||
# anything; skip names outside this set so they do not become
|
||||
# dead map keys. parseRuleHint also allows '.' — not accepted
|
||||
# here. The charset glob is *[!class]* (whole string);
|
||||
# [!class]* only tested the first character.
|
||||
case "$1" in
|
||||
*' '*|*'"'*|*"'"*) return 1 ;;
|
||||
[!A-Za-z0-9_-]*) return 1 ;;
|
||||
''|*' '*|*'"'*|*"'"*) return 1 ;;
|
||||
*[!A-Za-z0-9_-]*) return 1 ;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
@@ -177,6 +184,8 @@ EOF
|
||||
|
||||
# One awk pass: prefix<TAB>comment. Last quoted log prefix / comment on
|
||||
# the line, including \" units (same capture as the former sed).
|
||||
# Raw tabs in prefix/comment become the two-character sequence \t so the
|
||||
# separator tab is unique. Backslashes are not re-encoded.
|
||||
nft_dump_fields() {
|
||||
LC_ALL=C awk '
|
||||
function last_at(s, needle, p, i, found) {
|
||||
@@ -219,17 +228,60 @@ nft_dump_fields() {
|
||||
comment = quoted_at($0, "comment \"")
|
||||
if (prefix == "" && comment == "")
|
||||
next
|
||||
gsub(/\\/, "\\\\", prefix)
|
||||
gsub(/\t/, "\\t", prefix)
|
||||
gsub(/\\/, "\\\\", comment)
|
||||
gsub(/\t/, "\\t", comment)
|
||||
print prefix "\t" comment
|
||||
}
|
||||
' || return 1
|
||||
}
|
||||
|
||||
# Restore nft_dump_fields TSV encoding. Walk left to right: \\ is a
|
||||
# literal backslash, \t is a tab. Other backslash pairs stay as written.
|
||||
# Do not put backslash sequences in case patterns (they are escapes).
|
||||
# Fast-path strings with no backslash: dash ${var#?} walks are O(n^2) and
|
||||
# the map byte-cap tests feed ~60k prefixes with none.
|
||||
# Result is _tsv_out (no fork).
|
||||
_nft_tsv_unescape() {
|
||||
case "$1" in
|
||||
*\\*) ;;
|
||||
*)
|
||||
_tsv_out="$1"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
_tab=$(printf '\t')
|
||||
_in="$1"
|
||||
_out=
|
||||
while [ -n "$_in" ]; do
|
||||
_head=${_in%"${_in#?}"}
|
||||
_in=${_in#?}
|
||||
if [ "$_head" = "\\" ]; then
|
||||
_n=${_in%"${_in#?}"}
|
||||
_in=${_in#?}
|
||||
if [ "$_n" = t ]; then
|
||||
_out="${_out}${_tab}"
|
||||
elif [ "$_n" = "\\" ]; then
|
||||
_out="${_out}\\"
|
||||
else
|
||||
_out="${_out}\\${_n}"
|
||||
fi
|
||||
else
|
||||
_out="${_out}${_head}"
|
||||
fi
|
||||
done
|
||||
_tsv_out="$_out"
|
||||
}
|
||||
|
||||
# pass=labeled|unlabeled|all — labeled before unlabeled so !fw4: beats
|
||||
# an earlier cosmetic for the same prefix under first-wins.
|
||||
# Reads TSV from nft_dump_fields; stops once the map cap trips.
|
||||
map_from_nft_stream() {
|
||||
pass="${1:-all}"
|
||||
_tab=$(printf '\t')
|
||||
_esc_t=$(printf '\\t')
|
||||
_esc_b=$(printf '\\\\')
|
||||
# IFS= keeps a leading tab (empty prefix + comment-only !fw4: line).
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
[ -n "$_map_overflow" ] && break
|
||||
@@ -239,6 +291,10 @@ map_from_nft_stream() {
|
||||
*"$_tab"*) comment="${line#*"$_tab"}" ;;
|
||||
*) comment="" ;;
|
||||
esac
|
||||
_nft_tsv_unescape "$prefix"
|
||||
prefix="$_tsv_out"
|
||||
_nft_tsv_unescape "$comment"
|
||||
comment="$_tsv_out"
|
||||
label=''
|
||||
|
||||
case "$comment" in
|
||||
@@ -666,14 +722,14 @@ resolve_addresses() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Wall-clock budget clock starts at the TRUE function entry: the budget
|
||||
# must bound the whole call — including read_rpc_input (stalled stdin
|
||||
# must not escape the budget) and json parsing. `date +%s` can jump
|
||||
# under NTP sync — accepted (worst case the budget over- or under-runs
|
||||
# by the jump; worker starvation is still prevented).
|
||||
start=$(date +%s)
|
||||
# Optional JSON on $1 (rpcd may also pass argv $3 at the call site); else stdin.
|
||||
input=$(read_rpc_input "$1")
|
||||
# Wall-clock budget starts after the input read and bounds the lookup
|
||||
# loop only. Stalled stdin is not interrupted — production rpcd closes
|
||||
# a finite payload. `date +%s` can jump under NTP sync — accepted
|
||||
# (worst case the budget over- or under-runs by the jump; worker
|
||||
# starvation is still prevented).
|
||||
start=$(date +%s)
|
||||
lookups=0
|
||||
truncated=0
|
||||
OUT=''
|
||||
@@ -719,7 +775,7 @@ resolve_addresses() {
|
||||
truncated=1
|
||||
break
|
||||
fi
|
||||
# Abort cleanly once the whole-call wall-clock budget is spent;
|
||||
# Abort cleanly once the lookup-loop wall-clock budget is spent;
|
||||
# never START a lookup past the budget.
|
||||
now=$(date +%s)
|
||||
if [ $((now - start)) -ge "$RESOLVE_BUDGET" ]; then
|
||||
@@ -793,6 +849,42 @@ run_selftest() {
|
||||
expected=$(printf 'a\\n\\nb')
|
||||
check_eq 'json_escape blank line' "$escaped" "$expected" "expected $expected got $escaped" || return 1
|
||||
|
||||
# Charset applies to every character, not only the first.
|
||||
if ! is_uci_style_name 'Rule'; then
|
||||
echo 'is_uci_style_name: expected accept Rule' >&2
|
||||
return 1
|
||||
fi
|
||||
if ! is_uci_style_name 'Allow-SSH'; then
|
||||
echo 'is_uci_style_name: expected accept Allow-SSH' >&2
|
||||
return 1
|
||||
fi
|
||||
if ! is_uci_style_name '-lead'; then
|
||||
echo 'is_uci_style_name: expected accept -lead' >&2
|
||||
return 1
|
||||
fi
|
||||
if is_uci_style_name 'My Rule'; then
|
||||
echo 'is_uci_style_name: expected reject for space' >&2
|
||||
return 1
|
||||
fi
|
||||
# First character is legal; later $ / ; / . must still fail.
|
||||
# shellcheck disable=SC2016
|
||||
if is_uci_style_name 'rule$x'; then
|
||||
echo 'is_uci_style_name: expected reject rule$x (later-char illegal)' >&2
|
||||
return 1
|
||||
fi
|
||||
if is_uci_style_name 'rule;id'; then
|
||||
echo 'is_uci_style_name: expected reject rule;id (later-char illegal)' >&2
|
||||
return 1
|
||||
fi
|
||||
if is_uci_style_name 'rule.x'; then
|
||||
echo 'is_uci_style_name: expected reject rule.x (later-char illegal)' >&2
|
||||
return 1
|
||||
fi
|
||||
if is_uci_style_name ''; then
|
||||
echo 'is_uci_style_name: expected reject empty' >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if is_resolvable_address 'not an ip'; then
|
||||
echo 'is_resolvable_address: expected reject for spaces' >&2
|
||||
return 1
|
||||
@@ -864,10 +956,17 @@ run_selftest() {
|
||||
# Escaped-quote capture: a log prefix containing \" must not be truncated
|
||||
# at the escaped quote (nft dumps strings with backslash escapes).
|
||||
_tab=$(printf '\t')
|
||||
_esc_t=$(printf '\\t')
|
||||
_esc_b=$(printf '\\\\')
|
||||
got=$(printf '%s\n' 'log prefix "My \" Rule"' | nft_dump_fields)
|
||||
got="${got%%${_tab}*}"
|
||||
if [ "$got" != 'My \" Rule' ]; then
|
||||
echo "escaped-quote prefix capture: expected 'My \\\" Rule' got '$got'" >&2
|
||||
if [ "$got" != 'My \\" Rule' ]; then
|
||||
echo "escaped-quote prefix capture: expected encoded 'My \\\\\" Rule' got '$got'" >&2
|
||||
return 1
|
||||
fi
|
||||
_nft_tsv_unescape "$got"
|
||||
if [ "$_tsv_out" != 'My \" Rule' ]; then
|
||||
echo "escaped-quote prefix unescape: expected 'My \\\" Rule' got '$_tsv_out'" >&2
|
||||
return 1
|
||||
fi
|
||||
# Plain prefix still captures unchanged.
|
||||
@@ -884,6 +983,70 @@ run_selftest() {
|
||||
echo "empty prefix capture: expected '' got '$got'" >&2
|
||||
return 1
|
||||
fi
|
||||
# Guest nft list emits a raw tab inside the quoted prefix
|
||||
# (log prefix "fwlive<TAB>tabbed"). Encode it in the TSV field.
|
||||
got=$(printf '%s\n' "log prefix \"fwlive${_tab}tabbed\"" | nft_dump_fields)
|
||||
case "$got" in
|
||||
*"${_tab}"*"${_tab}"*)
|
||||
echo "tabbed prefix TSV: expected one separator tab, got extra" >&2
|
||||
return 1
|
||||
;;
|
||||
*"${_tab}"*) ;;
|
||||
*)
|
||||
echo "tabbed prefix TSV: missing separator tab, got '$got'" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
got_prefix="${got%%${_tab}*}"
|
||||
if [ "$got_prefix" != "fwlive${_esc_t}tabbed" ]; then
|
||||
echo "tabbed prefix TSV: expected fwlive\\ttabbed got '$got_prefix'" >&2
|
||||
return 1
|
||||
fi
|
||||
_nft_tsv_unescape "$got_prefix"
|
||||
if [ "$_tsv_out" != "fwlive${_tab}tabbed" ]; then
|
||||
echo "tabbed prefix unescape: expected fwlive<TAB>tabbed" >&2
|
||||
return 1
|
||||
fi
|
||||
# Literal backslash-t must round-trip and not become a tab.
|
||||
got=$(printf '%s\n' "log prefix \"fwlive${_esc_t}lit\"" | nft_dump_fields)
|
||||
got_prefix="${got%%${_tab}*}"
|
||||
if [ "$got_prefix" != "fwlive${_esc_b}tlit" ]; then
|
||||
echo "literal \\\\t TSV: expected fwlive\\\\\\\\tlit got '$got_prefix'" >&2
|
||||
return 1
|
||||
fi
|
||||
_nft_tsv_unescape "$got_prefix"
|
||||
if [ "$_tsv_out" != "fwlive${_esc_t}lit" ]; then
|
||||
echo "literal \\\\t unescape: expected fwlive\\\\tlit got '$_tsv_out'" >&2
|
||||
return 1
|
||||
fi
|
||||
# Tabbed prefix plus !fw4: label: map key is the full prefix.
|
||||
got=$(printf '%s\n' "log prefix \"fwlive${_tab}tabbed\" comment \"!fw4: TabRule\"" | nft_dump_fields)
|
||||
OUT=''
|
||||
_map_keys=0
|
||||
_map_overflow=''
|
||||
map_from_nft_stream all <<EOF
|
||||
$got
|
||||
EOF
|
||||
if [ "$OUT" != '"fwlive'"${_esc_t}"'tabbed":"TabRule"' ]; then
|
||||
echo "tabbed prefix map: expected full prefix -> TabRule, got $OUT" >&2
|
||||
return 1
|
||||
fi
|
||||
# Comment-only / !fw4: label path is unchanged when there is no tab.
|
||||
got=$(printf '%s\n' 'comment "!fw4: GoodName"' | nft_dump_fields)
|
||||
if [ "$got" != "${_tab}!fw4: GoodName" ]; then
|
||||
echo "comment-only TSV: expected leading tab + !fw4: GoodName got '$got'" >&2
|
||||
return 1
|
||||
fi
|
||||
OUT=''
|
||||
_map_keys=0
|
||||
_map_overflow=''
|
||||
map_from_nft_stream all <<EOF
|
||||
$got
|
||||
EOF
|
||||
if [ "$OUT" != '"GoodName":"GoodName","goodname":"GoodName"' ]; then
|
||||
echo "comment-only map: expected GoodName mapping, got $OUT" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
got=$(poll_clamp_lines "99999999999999999999")
|
||||
check_eq poll_clamp_lines "$got" "$POLL_LINES_MAX" "over-long expected $POLL_LINES_MAX got $got" || return 1
|
||||
|
||||
Reference in new issue
Block a user