Fix $[...] variable parsing to consume the entire bracket contents

renderVariableName() built the variable name by whitelisting an
identifier charset (letters/digits/._-) via getTokenInclude(), then
threw "invalid variable name." on the first character outside that set
- including a plain space. Real-world CGI form field names aren't
restricted to identifier characters (an HTML <input name="first name">
is completely valid), so $[:first name] failed outright instead of
looking up the field as sent.

Rewrote to exclude only the genuinely structural characters (']', ';',
and the start of a nested $[.../#[...substitution) via getTokenExclude,
so everything else between the brackets - spaces included - is consumed
as part of the name before any lookup happens, rather than stopping
early and erroring. This also let the separate ':' handling branch be
dropped entirely: since ':' is no longer excluded, it now flows through
the same sweep naturally (needed for both the CGI sigil name itself and
the "$[:name:2]" explicit-index suffix).

Verified live: $[:first name] against a real multipart CGI body with a
field literally named "first name" now resolves correctly (previously
threw before ever reaching the lookup). A stray, non-nested '$' or '#'
in a name is kept as literal text rather than erroring. Re-verified
existing sigils are unaffected - plain $[x], $[!x] (forced-global), and
tests/posttest.sh's ^session/:.name3 lookups all still resolve exactly
as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Brad ArantandClaude Sonnet 5 committed 2026-08-23 23:11:20 -07:00
1 parent aaed4cb4da
commit 6ac33601a0
1 file changed
+23 -8
+23 -8
View File
@@ -472,20 +472,35 @@ namespace jet {
}
void Tag::renderVariableName(coreutils::ZString &variable, coreutils::MString &name, coreutils::MString &modifier) {
// Consumes everything between '[' (already eaten by the caller) and the
// matching ']' as the variable name, stopping early only for the
// genuinely structural bits: the ';' modifier separator and a nested
// $[...]/#[...] substitution. Real-world CGI field names are not
// restricted to identifier characters (an HTML form field can be named
// "first name", "cost$", etc.) - excluding only the structural
// characters (rather than whitelisting an identifier charset and
// throwing on anything else) means the full bracket contents are
// always consumed before a lookup happens, matching how a caller
// actually writing $[:field name] expects it to behave.
while(!variable.ifNext("]")) {
name << variable.getTokenInclude("?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-");
name << variable.getTokenExclude("];$#");
if(variable.ifNext(";")) {
renderVariableName(variable, modifier, modifier);
return;
} else if(variable.ifNext(":")) {
name << ":";
} else if(variable.startsWith("$[") || variable.startsWith("#[")) {
name << getVariable(variable);
} else if(variable.ifNext("]"))
return;
else if(!variable.ifNextInclude("?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-"))
throw coreutils::Exception("invalid variable name.");
}
} else if(variable.ifNext("]")) {
return;
} else if(variable.eod()) {
throw coreutils::Exception("unterminated variable reference - missing ']'.");
} else {
// A literal '$' or '#' that isn't the start of a nested
// substitution (getTokenExclude stopped on it defensively, since
// it can't look ahead past a single character) - keep it as
// ordinary name text and continue.
name << variable.nextChar();
}
}
return;
}