// https://www.w3.org/TR/html5/syntax.html#the-end
// where the context element is null.
type context struct {
- state state
- delim delim
- urlPart urlPart
- jsCtx jsCtx
- jsTmplExprDepth int
- jsBraceDepth int
- attr attr
- element element
- n parse.Node // for range break/continue
- err *Error
+ state state
+ delim delim
+ urlPart urlPart
+ jsCtx jsCtx
+ // jsBraceDepth contains the current depth, for each JS template literal
+ // string interpolation expression, of braces we've seen. This is used to
+ // determine if the next } will close a JS template literal string
+ // interpolation expression or not.
+ jsBraceDepth []int
+ attr attr
+ element element
+ n parse.Node // for range break/continue
+ err *Error
}
func (c context) String() string {
context{state: stateJS, element: elementScript, jsCtx: jsCtxDivOp},
},
{
- "<script>`${ { `` }`",
+ "<script>`${ { `` }",
context{state: stateJS, element: elementScript},
},
{
"<script>`${ { }`",
context{state: stateJSTmplLit, element: elementScript},
},
+ {
+ "<script>var foo = `${ foo({ a: { c: `${",
+ context{state: stateJS, element: elementScript},
+ },
+ {
+ "<script>var foo = `${ foo({ a: { c: `${ {{.}} }` }, b: ",
+ context{state: stateJS, element: elementScript},
+ },
+ {
+ "<script>`${ `}",
+ context{state: stateJSTmplLit, element: elementScript},
+ },
}
for _, test := range tests {
case '{':
// We only care about tracking brace depth if we are inside of a
// template literal.
- if c.jsTmplExprDepth == 0 {
+ if len(c.jsBraceDepth) == 0 {
return c, i + 1
}
- c.jsBraceDepth++
+ c.jsBraceDepth[len(c.jsBraceDepth)-1]++
case '}':
- if c.jsTmplExprDepth == 0 {
+ if len(c.jsBraceDepth) == 0 {
return c, i + 1
}
- for j := 0; j <= i; j++ {
- switch s[j] {
- case '\\':
- j++
- case '{':
- c.jsBraceDepth++
- case '}':
- c.jsBraceDepth--
- }
- }
- if c.jsBraceDepth >= 0 {
+ // There are no cases where a brace can be escaped in the JS context
+ // that are not syntax errors, it seems. Because of this we can just
+ // count "\}" as "}" and move on, the script is already broken as
+ // fully fledged parsers will just fail anyway.
+ c.jsBraceDepth[len(c.jsBraceDepth)-1]--
+ if c.jsBraceDepth[len(c.jsBraceDepth)-1] >= 0 {
return c, i + 1
}
- c.jsTmplExprDepth--
- c.jsBraceDepth = 0
+ c.jsBraceDepth = c.jsBraceDepth[:len(c.jsBraceDepth)-1]
c.state = stateJSTmplLit
default:
panic("unreachable")
}
func tJSTmpl(c context, s []byte) (context, int) {
- c.jsBraceDepth = 0
var k int
for {
i := k + bytes.IndexAny(s[k:], "`\\$")
}
case '$':
if len(s) >= i+2 && s[i+1] == '{' {
- c.jsTmplExprDepth++
- c.jsBraceDepth = 0
+ c.jsBraceDepth = append(c.jsBraceDepth, 0)
c.state = stateJS
return c, i + 2
}