Skip to content

Commit 2295466

Browse files
authored
Merge pull request #6 from airplanes-live/fix/extract-key-backslash-grammar
Fix extract_key to match bash double-quote escape grammar
2 parents 0c65be3 + bf37085 commit 2295466

2 files changed

Lines changed: 75 additions & 4 deletions

File tree

helpers/migrate-config.sh

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,30 @@ extract_key() {
9595

9696
case "$raw" in
9797
'"'*)
98-
# Double-quoted: handle \\, \", \$, \` escapes; reject if
99-
# closing quote is missing.
100-
local result="" i len=${#raw} escaped=0 closed=0 c
98+
# Double-quoted: bash double-quote grammar only treats `\` as
99+
# an escape when followed by `\`, `"`, `$`, or backtick. For
100+
# any other character the backslash is preserved literally
101+
# (so `USER="a\xb"` sources to the 4-byte value a\xb). Reject
102+
# if the closing quote is missing. Note: this parser is
103+
# single-line; line-continuation (`\` + newline) is not
104+
# supported — multi-line values are out of scope for the
105+
# boot-config file format.
106+
local result="" i len=${#raw} escaped=0 closed=0 c next
101107
for (( i=1; i<len; i++ )); do
102108
c="${raw:$i:1}"
103109
if (( escaped )); then
104110
result+="$c"
105111
escaped=0
106112
elif [[ "$c" == '\' ]]; then
107-
escaped=1
113+
next="${raw:$((i+1)):1}"
114+
case "$next" in
115+
'\'|'"'|'$'|'`')
116+
escaped=1
117+
;;
118+
*)
119+
result+='\'
120+
;;
121+
esac
108122
elif [[ "$c" == '"' ]]; then
109123
closed=1
110124
break

test/migrate-config-test.sh

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,60 @@ test_crlf_tolerated() {
220220
echo "PASS: CRLF tolerated"
221221
}
222222

223+
# --- extract_key bash double-quote grammar edge cases ---
224+
225+
test_extract_key_preserves_literal_backslash() {
226+
# File contains the literal text: USER="abc\xyz"
227+
# Bash double-quote: \x is NOT in the escape set ({\, ", $, `}), so
228+
# the sourced value is the 7-byte literal abc\xyz. extract_key must
229+
# preserve the backslash and round-trip it through emit so sourcing
230+
# the migrated file yields the same value.
231+
local f
232+
f="$(with_file "case_extract_backslash_literal" 'USER="abc\xyz"
233+
')"
234+
run_migrator "$f"
235+
local got
236+
got="$(
237+
# shellcheck source=/dev/null
238+
. "$f" && printf '%s' "$MLAT_USER"
239+
)" || fail "sourcing migrated file failed: $(cat "$f")"
240+
assert_eq 'abc\xyz' "$got" "literal backslash preserved in MLAT_USER"
241+
echo "PASS: extract_key preserves literal backslash for \\X non-escape"
242+
}
243+
244+
test_extract_key_resolves_recognized_escapes() {
245+
# File contains: USER="a\\b\$c" (8 bytes between quotes)
246+
# Bash double-quote: \\ → \, \$ → $. Sourced value: a\b$c.
247+
local f
248+
f="$(with_file "case_extract_escapes" 'USER="a\\b\$c"
249+
')"
250+
run_migrator "$f"
251+
local got
252+
got="$(
253+
# shellcheck source=/dev/null
254+
. "$f" && printf '%s' "$MLAT_USER"
255+
)" || fail "sourcing migrated file failed: $(cat "$f")"
256+
assert_eq 'a\b$c' "$got" "recognized backslash escapes resolved"
257+
echo "PASS: extract_key resolves recognized escapes (\\\\, \\\$)"
258+
}
259+
260+
test_extract_key_escaped_close_quote_no_real_close() {
261+
# File contains: USER="abc\" with newline after the escaped quote.
262+
# The trailing \" is the escape for a literal " (still inside the
263+
# string), so there is no real close-quote on this line. extract_key
264+
# must reject as malformed and leave the file untouched.
265+
local f rc=0
266+
f="$(with_file "case_extract_escaped_close" 'USER="abc\"
267+
DUMP1090=yes
268+
')"
269+
local before
270+
before="$(cat "$f")"
271+
run_migrator "$f" 2>/dev/null || rc=$?
272+
[[ "$rc" -ne 0 ]] || fail "escaped close-quote not rejected"
273+
assert_file_eq "$before" "$f" "malformed file unchanged"
274+
echo "PASS: extract_key rejects escaped close-quote (no real close)"
275+
}
276+
223277
# --- MLAT_MARKER → MLAT_PRIVATE migration (co-emit, MLAT_MARKER preserved) ---
224278

225279
test_marker_no_emits_private_true_alongside() {
@@ -443,6 +497,9 @@ main() {
443497
test_missing_file_is_noop
444498
test_backup_not_overwritten
445499
test_crlf_tolerated
500+
test_extract_key_preserves_literal_backslash
501+
test_extract_key_resolves_recognized_escapes
502+
test_extract_key_escaped_close_quote_no_real_close
446503
test_marker_no_emits_private_true_alongside
447504
test_marker_yes_emits_private_false_alongside
448505
test_marker_uppercase_tolerated

0 commit comments

Comments
 (0)