aboutsummaryrefslogtreecommitdiff
path: root/src/html/template
diff options
context:
space:
mode:
authorRoland Shoemaker <roland@golang.org>2023-10-05 06:16:18 -0700
committerRoland Shoemaker <roland@golang.org>2023-10-16 03:29:27 +0000
commit5873bd1d7e87e0686390d71a840a44e2e2c3e11d (patch)
tree72b868931e29760efec9d0d0322e18da16beb9e8 /src/html/template
parentbc9dc8d415fc1ecf8d0d63a01dd17b2b60bf4cf3 (diff)
downloadgo-5873bd1d7e87e0686390d71a840a44e2e2c3e11d.tar.xz
html/template: track brace depth for each nested expression
We need to track the brace depth for each individual nested expression, since a string interpolation expression may be nested inside of an object. e.g. `${ {1:`${}`}}` has brace depths [1, 0] when inside of the inner ${} expression. When we exit the inner expression, we need to reset to the previous brace depth (1) so that we know that the following } closes the object, but not the outer expression. Note that if you write a broken expression (i.e. `${ { }`) escaping will clearly not work as expected (or depending on your interpretation, since it is broken, it will work as expected). Since the JS parser doesn't catch syntax errors, it's up to the user to write a valid template. Updates #61619 Change-Id: I4c33723d12aff49facdcb1134d9ca82b7a0dffc4 Reviewed-on: https://go-review.googlesource.com/c/go/+/532995 Reviewed-by: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/html/template')
-rw-r--r--src/html/template/context.go23
-rw-r--r--src/html/template/escape_test.go14
-rw-r--r--src/html/template/transition.go30
3 files changed, 37 insertions, 30 deletions
diff --git a/src/html/template/context.go b/src/html/template/context.go
index 63d5c31b01..b78f0f7325 100644
--- a/src/html/template/context.go
+++ b/src/html/template/context.go
@@ -17,16 +17,19 @@ import (
// 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 {
diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go
index 91fbfb9a3c..497ead8716 100644
--- a/src/html/template/escape_test.go
+++ b/src/html/template/escape_test.go
@@ -1797,13 +1797,25 @@ func TestEscapeText(t *testing.T) {
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 {
diff --git a/src/html/template/transition.go b/src/html/template/transition.go
index 4aa2920986..d5a05f66da 100644
--- a/src/html/template/transition.go
+++ b/src/html/template/transition.go
@@ -323,29 +323,23 @@ func tJS(c context, s []byte) (context, int) {
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")
@@ -354,7 +348,6 @@ func tJS(c context, s []byte) (context, int) {
}
func tJSTmpl(c context, s []byte) (context, int) {
- c.jsBraceDepth = 0
var k int
for {
i := k + bytes.IndexAny(s[k:], "`\\$")
@@ -372,8 +365,7 @@ func tJSTmpl(c context, s []byte) (context, int) {
}
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
}