diff --git a/Tag.cpp b/Tag.cpp index 14a4485..45d0be0 100644 --- a/Tag.cpp +++ b/Tag.cpp @@ -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; }