src/html/template/context.go | 2 ++
src/html/template/error.go | 13 +++++++++++++
src/html/template/escape.go | 11 +++++++++++
src/html/template/escape_test.go | 66 ++++++++++++++++++++++++++++++-----------------------
src/html/template/js.go | 2 ++
src/html/template/js_test.go | 2 +-
src/html/template/jsctx_string.go | 9 +++++++++
src/html/template/state_string.go | 37 +++++++++++++++++++++++++++++++++++--
src/html/template/transition.go | 7 ++++++-
diff --git a/src/html/template/context.go b/src/html/template/context.go
index a97c8be56f958e4894e0dc1d16efb8a16f745e89..c28fb0c5ea8ef30ddbcb4ffec6d0bef34756ad76 100644
--- a/src/html/template/context.go
+++ b/src/html/template/context.go
@@ -120,6 +120,8 @@ // stateJSDqStr occurs inside a JavaScript double quoted string.
stateJSDqStr
// stateJSSqStr occurs inside a JavaScript single quoted string.
stateJSSqStr
+ // stateJSBqStr occurs inside a JavaScript back quoted string.
+ stateJSBqStr
// stateJSRegexp occurs inside a JavaScript regexp literal.
stateJSRegexp
// stateJSBlockCmt occurs inside a JavaScript /* block comment */.
diff --git a/src/html/template/error.go b/src/html/template/error.go
index 5c51f772cbde36f4b08e217d94def0f21a13e896..d7d6f5b3ab589ea1e44f235ba7e2f2716906a8a4 100644
--- a/src/html/template/error.go
+++ b/src/html/template/error.go
@@ -214,6 +214,19 @@ // continue to be allowed as the last command in a pipeline. However, if the
// pipeline occurs in an unquoted attribute value context, "html" is
// disallowed. Avoid using "html" and "urlquery" entirely in new templates.
ErrPredefinedEscaper
+
+ // errJSTmplLit: "... appears in a JS template literal"
+ // Example:
+ //
+ // Discussion:
+ // Package html/template does not support actions inside of JS template
+ // literals.
+ //
+ // TODO(rolandshoemaker): we cannot add this as an exported error in a minor
+ // release, since it is backwards incompatible with the other minor
+ // releases. As such we need to leave it unexported, and then we'll add it
+ // in the next major release.
+ errJSTmplLit
)
func (e *Error) Error() string {
diff --git a/src/html/template/escape.go b/src/html/template/escape.go
index 54fbcdca333549cedae379361a3e9ac84597bed7..3d4cc19b5da85c22859b1182d053b1a19adba2ef 100644
--- a/src/html/template/escape.go
+++ b/src/html/template/escape.go
@@ -8,6 +8,7 @@ import (
"bytes"
"fmt"
"html"
+ "internal/godebug"
"io"
"text/template"
"text/template/parse"
@@ -223,6 +224,16 @@ // A slash after a value starts a div operator.
c.jsCtx = jsCtxDivOp
case stateJSDqStr, stateJSSqStr:
s = append(s, "_html_template_jsstrescaper")
+ case stateJSBqStr:
+ debugAllowActionJSTmpl := godebug.Get("jstmpllitinterp")
+ if debugAllowActionJSTmpl == "1" {
+ s = append(s, "_html_template_jsstrescaper")
+ } else {
+ return context{
+ state: stateError,
+ err: errorf(errJSTmplLit, n, n.Line, "%s appears in a JS template literal", n),
+ }
+ }
case stateJSRegexp:
s = append(s, "_html_template_jsregexpescaper")
case stateCSS:
diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go
index 58f3f271b7a5c39cb5ce8bc6e9f5edae7444393d..972b00b921d35be8eeec5a462b6d7657826a0ea6 100644
--- a/src/html/template/escape_test.go
+++ b/src/html/template/escape_test.go
@@ -681,35 +681,31 @@ },
}
for _, test := range tests {
- tmpl := New(test.name)
- tmpl = Must(tmpl.Parse(test.input))
- // Check for bug 6459: Tree field was not set in Parse.
- if tmpl.Tree != tmpl.text.Tree {
- t.Errorf("%s: tree not set properly", test.name)
- continue
- }
- b := new(bytes.Buffer)
- if err := tmpl.Execute(b, data); err != nil {
- t.Errorf("%s: template execution failed: %s", test.name, err)
- continue
- }
- if w, g := test.output, b.String(); w != g {
- t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.name, w, g)
- continue
- }
- b.Reset()
- if err := tmpl.Execute(b, pdata); err != nil {
- t.Errorf("%s: template execution failed for pointer: %s", test.name, err)
- continue
- }
- if w, g := test.output, b.String(); w != g {
- t.Errorf("%s: escaped output for pointer: want\n\t%q\ngot\n\t%q", test.name, w, g)
- continue
- }
- if tmpl.Tree != tmpl.text.Tree {
- t.Errorf("%s: tree mismatch", test.name)
- continue
- }
+ t.Run(test.name, func(t *testing.T) {
+ tmpl := New(test.name)
+ tmpl = Must(tmpl.Parse(test.input))
+ // Check for bug 6459: Tree field was not set in Parse.
+ if tmpl.Tree != tmpl.text.Tree {
+ t.Fatalf("%s: tree not set properly", test.name)
+ }
+ b := new(strings.Builder)
+ if err := tmpl.Execute(b, data); err != nil {
+ t.Fatalf("%s: template execution failed: %s", test.name, err)
+ }
+ if w, g := test.output, b.String(); w != g {
+ t.Fatalf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.name, w, g)
+ }
+ b.Reset()
+ if err := tmpl.Execute(b, pdata); err != nil {
+ t.Fatalf("%s: template execution failed for pointer: %s", test.name, err)
+ }
+ if w, g := test.output, b.String(); w != g {
+ t.Fatalf("%s: escaped output for pointer: want\n\t%q\ngot\n\t%q", test.name, w, g)
+ }
+ if tmpl.Tree != tmpl.text.Tree {
+ t.Fatalf("%s: tree mismatch", test.name)
+ }
+ })
}
}
@@ -934,6 +930,10 @@ "",
},
{
"{{range .Items}}{{if .X}}{{break}}{{end}}{{end}}",
+ "",
+ },
+ {
+ "`",
"",
},
// Error cases.
@@ -1082,6 +1082,10 @@ `Hello, {{. | urlquery | html}}!`,
// html is allowed since it is the last command in the pipeline, but urlquery is not.
`predefined escaper "urlquery" disallowed in template`,
},
+ {
+ "",
+ `{{.}} appears in a JS template literal`,
+ },
}
for _, test := range tests {
buf := new(bytes.Buffer)
@@ -1302,6 +1306,10 @@ },
{
`= state(len(_state_index)-1) {
diff --git a/src/html/template/transition.go b/src/html/template/transition.go
index 06df679330db2c49d1bfc7d2369f2bc2a40f164a..92eb3519063c9ea0fd17eb8344939ab4871d8ba8 100644
--- a/src/html/template/transition.go
+++ b/src/html/template/transition.go
@@ -27,6 +27,7 @@ stateSrcset: tURL,
stateJS: tJS,
stateJSDqStr: tJSDelimited,
stateJSSqStr: tJSDelimited,
+ stateJSBqStr: tJSDelimited,
stateJSRegexp: tJSDelimited,
stateJSBlockCmt: tBlockCmt,
stateJSLineCmt: tLineCmt,
@@ -262,7 +263,7 @@ }
// tJS is the context transition function for the JS state.
func tJS(c context, s []byte) (context, int) {
- i := bytes.IndexAny(s, `"'/`)
+ i := bytes.IndexAny(s, "\"`'/")
if i == -1 {
// Entire input is non string, comment, regexp tokens.
c.jsCtx = nextJSCtx(s, c.jsCtx)
@@ -274,6 +275,8 @@ case '"':
c.state, c.jsCtx = stateJSDqStr, jsCtxRegexp
case '\'':
c.state, c.jsCtx = stateJSSqStr, jsCtxRegexp
+ case '`':
+ c.state, c.jsCtx = stateJSBqStr, jsCtxRegexp
case '/':
switch {
case i+1 < len(s) && s[i+1] == '/':
@@ -303,6 +306,8 @@ specials := `\"`
switch c.state {
case stateJSSqStr:
specials = `\'`
+ case stateJSBqStr:
+ specials = "`\\"
case stateJSRegexp:
specials = `\/[]`
}