mirror of https://github.com/golang/go.git
html/template: support parsing complex JS template literals
This change undoes the restrictions added in CL 482079, which added a blanket ban on using actions within JS template literal strings, and adds logic to support actions while properly applies contextual escaping based on the correct context within the literal. Since template literals can contain both normal strings, and nested JS contexts, logic is required to properly track those context switches during parsing. ErrJsTmplLit is deprecated, and the GODEBUG flag jstmpllitinterp no longer does anything. Fixes #61619 Change-Id: I0338cc6f663723267b8f7aaacc55aa28f60906f2 Reviewed-on: https://go-review.googlesource.com/c/go/+/507995 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Roland Shoemaker <roland@golang.org> Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
parent
deb8e29000
commit
c9c885f92f
|
|
@ -0,0 +1 @@
|
||||||
|
pkg html/template, const ErrJSTemplate //deprecated #61619
|
||||||
|
|
@ -21,6 +21,8 @@ type context struct {
|
||||||
delim delim
|
delim delim
|
||||||
urlPart urlPart
|
urlPart urlPart
|
||||||
jsCtx jsCtx
|
jsCtx jsCtx
|
||||||
|
jsTmplExprDepth int
|
||||||
|
jsBraceDepth int
|
||||||
attr attr
|
attr attr
|
||||||
element element
|
element element
|
||||||
n parse.Node // for range break/continue
|
n parse.Node // for range break/continue
|
||||||
|
|
@ -120,8 +122,8 @@ const (
|
||||||
stateJSDqStr
|
stateJSDqStr
|
||||||
// stateJSSqStr occurs inside a JavaScript single quoted string.
|
// stateJSSqStr occurs inside a JavaScript single quoted string.
|
||||||
stateJSSqStr
|
stateJSSqStr
|
||||||
// stateJSBqStr occurs inside a JavaScript back quoted string.
|
// stateJSTmplLit occurs inside a JavaScript back quoted string.
|
||||||
stateJSBqStr
|
stateJSTmplLit
|
||||||
// stateJSRegexp occurs inside a JavaScript regexp literal.
|
// stateJSRegexp occurs inside a JavaScript regexp literal.
|
||||||
stateJSRegexp
|
stateJSRegexp
|
||||||
// stateJSBlockCmt occurs inside a JavaScript /* block comment */.
|
// stateJSBlockCmt occurs inside a JavaScript /* block comment */.
|
||||||
|
|
@ -182,7 +184,7 @@ func isInScriptLiteral(s state) bool {
|
||||||
// stateJSHTMLOpenCmt, stateJSHTMLCloseCmt) because their content is already
|
// stateJSHTMLOpenCmt, stateJSHTMLCloseCmt) because their content is already
|
||||||
// omitted from the output.
|
// omitted from the output.
|
||||||
switch s {
|
switch s {
|
||||||
case stateJSDqStr, stateJSSqStr, stateJSBqStr, stateJSRegexp:
|
case stateJSDqStr, stateJSSqStr, stateJSTmplLit, stateJSRegexp:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,10 @@ const (
|
||||||
// Discussion:
|
// Discussion:
|
||||||
// Package html/template does not support actions inside of JS template
|
// Package html/template does not support actions inside of JS template
|
||||||
// literals.
|
// literals.
|
||||||
|
//
|
||||||
|
// Deprecated: ErrJSTemplate is no longer returned when an action is present
|
||||||
|
// in a JS template literal. Actions inside of JS template literals are now
|
||||||
|
// escaped as expected.
|
||||||
ErrJSTemplate
|
ErrJSTemplate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ var funcMap = template.FuncMap{
|
||||||
"_html_template_htmlescaper": htmlEscaper,
|
"_html_template_htmlescaper": htmlEscaper,
|
||||||
"_html_template_jsregexpescaper": jsRegexpEscaper,
|
"_html_template_jsregexpescaper": jsRegexpEscaper,
|
||||||
"_html_template_jsstrescaper": jsStrEscaper,
|
"_html_template_jsstrescaper": jsStrEscaper,
|
||||||
|
"_html_template_jstmpllitescaper": jsTmplLitEscaper,
|
||||||
"_html_template_jsvalescaper": jsValEscaper,
|
"_html_template_jsvalescaper": jsValEscaper,
|
||||||
"_html_template_nospaceescaper": htmlNospaceEscaper,
|
"_html_template_nospaceescaper": htmlNospaceEscaper,
|
||||||
"_html_template_rcdataescaper": rcdataEscaper,
|
"_html_template_rcdataescaper": rcdataEscaper,
|
||||||
|
|
@ -227,16 +228,8 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
|
||||||
c.jsCtx = jsCtxDivOp
|
c.jsCtx = jsCtxDivOp
|
||||||
case stateJSDqStr, stateJSSqStr:
|
case stateJSDqStr, stateJSSqStr:
|
||||||
s = append(s, "_html_template_jsstrescaper")
|
s = append(s, "_html_template_jsstrescaper")
|
||||||
case stateJSBqStr:
|
case stateJSTmplLit:
|
||||||
if debugAllowActionJSTmpl.Value() == "1" {
|
s = append(s, "_html_template_jstmpllitescaper")
|
||||||
debugAllowActionJSTmpl.IncNonDefault()
|
|
||||||
s = append(s, "_html_template_jsstrescaper")
|
|
||||||
} else {
|
|
||||||
return context{
|
|
||||||
state: stateError,
|
|
||||||
err: errorf(ErrJSTemplate, n, n.Line, "%s appears in a JS template literal", n),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case stateJSRegexp:
|
case stateJSRegexp:
|
||||||
s = append(s, "_html_template_jsregexpescaper")
|
s = append(s, "_html_template_jsregexpescaper")
|
||||||
case stateCSS:
|
case stateCSS:
|
||||||
|
|
@ -395,6 +388,9 @@ var redundantFuncs = map[string]map[string]bool{
|
||||||
"_html_template_jsstrescaper": {
|
"_html_template_jsstrescaper": {
|
||||||
"_html_template_attrescaper": true,
|
"_html_template_attrescaper": true,
|
||||||
},
|
},
|
||||||
|
"_html_template_jstmpllitescaper": {
|
||||||
|
"_html_template_attrescaper": true,
|
||||||
|
},
|
||||||
"_html_template_urlescaper": {
|
"_html_template_urlescaper": {
|
||||||
"_html_template_urlnormalizer": true,
|
"_html_template_urlnormalizer": true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ func (x *goodMarshaler) MarshalJSON() ([]byte, error) {
|
||||||
func TestEscape(t *testing.T) {
|
func TestEscape(t *testing.T) {
|
||||||
data := struct {
|
data := struct {
|
||||||
F, T bool
|
F, T bool
|
||||||
C, G, H string
|
C, G, H, I string
|
||||||
A, E []string
|
A, E []string
|
||||||
B, M json.Marshaler
|
B, M json.Marshaler
|
||||||
N int
|
N int
|
||||||
|
|
@ -52,6 +52,7 @@ func TestEscape(t *testing.T) {
|
||||||
U: nil,
|
U: nil,
|
||||||
Z: nil,
|
Z: nil,
|
||||||
W: HTML(`¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
|
W: HTML(`¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
|
||||||
|
I: "${ asd `` }",
|
||||||
}
|
}
|
||||||
pdata := &data
|
pdata := &data
|
||||||
|
|
||||||
|
|
@ -718,6 +719,21 @@ func TestEscape(t *testing.T) {
|
||||||
"<p name=\"{{.U}}\">",
|
"<p name=\"{{.U}}\">",
|
||||||
"<p name=\"\">",
|
"<p name=\"\">",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"JS template lit special characters",
|
||||||
|
"<script>var a = `{{.I}}`</script>",
|
||||||
|
"<script>var a = `\\u0024\\u007b asd \\u0060\\u0060 \\u007d`</script>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"JS template lit special characters, nested lit",
|
||||||
|
"<script>var a = `${ `{{.I}}` }`</script>",
|
||||||
|
"<script>var a = `${ `\\u0024\\u007b asd \\u0060\\u0060 \\u007d` }`</script>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"JS template lit, nested JS",
|
||||||
|
"<script>var a = `${ var a = \"{{\"a \\\" d\"}}\" }`</script>",
|
||||||
|
"<script>var a = `${ var a = \"a \\u0022 d\" }`</script>",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|
@ -976,6 +992,31 @@ func TestErrors(t *testing.T) {
|
||||||
"<script>var a = `${a+b}`</script>`",
|
"<script>var a = `${a+b}`</script>`",
|
||||||
"",
|
"",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"<script>var tmpl = `asd`;</script>",
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var tmpl = `${1}`;</script>",
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var tmpl = `${return ``}`;</script>",
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var tmpl = `${return {{.}} }`;</script>",
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var tmpl = `${ let a = {1:1} {{.}} }`;</script>",
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var tmpl = `asd ${return \"{\"}`;</script>",
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
|
||||||
// Error cases.
|
// Error cases.
|
||||||
{
|
{
|
||||||
"{{if .Cond}}<a{{end}}",
|
"{{if .Cond}}<a{{end}}",
|
||||||
|
|
@ -1122,10 +1163,26 @@ func TestErrors(t *testing.T) {
|
||||||
// html is allowed since it is the last command in the pipeline, but urlquery is not.
|
// html is allowed since it is the last command in the pipeline, but urlquery is not.
|
||||||
`predefined escaper "urlquery" disallowed in template`,
|
`predefined escaper "urlquery" disallowed in template`,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
"<script>var tmpl = `asd {{.}}`;</script>",
|
// "<script>var tmpl = `asd {{.}}`;</script>",
|
||||||
`{{.}} appears in a JS template literal`,
|
// `{{.}} appears in a JS template literal`,
|
||||||
},
|
// },
|
||||||
|
// {
|
||||||
|
// "<script>var v = `${function(){return `{{.V}}+1`}()}`;</script>",
|
||||||
|
// `{{.V}} appears in a JS template literal`,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "<script>var a = `asd ${function(){b = {1:2}; return`{{.}}`}}`</script>",
|
||||||
|
// `{{.}} appears in a JS template literal`,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "<script>var tmpl = `${return `{{.}}`}`;</script>",
|
||||||
|
// `{{.}} appears in a JS template literal`,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "<script>var tmpl = `${return {`{{.}}`}`;</script>",
|
||||||
|
// `{{.}} appears in a JS template literal`,
|
||||||
|
// },
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
|
@ -1349,7 +1406,7 @@ func TestEscapeText(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"<a onclick=\"`foo",
|
"<a onclick=\"`foo",
|
||||||
context{state: stateJSBqStr, delim: delimDoubleQuote, attr: attrScript},
|
context{state: stateJSTmplLit, delim: delimDoubleQuote, attr: attrScript},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`<A ONCLICK="'`,
|
`<A ONCLICK="'`,
|
||||||
|
|
@ -1691,6 +1748,58 @@ func TestEscapeText(t *testing.T) {
|
||||||
`<svg:a svg:onclick="x()">`,
|
`<svg:a svg:onclick="x()">`,
|
||||||
context{},
|
context{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"<script>var a = `",
|
||||||
|
context{state: stateJSTmplLit, element: elementScript},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var a = `${",
|
||||||
|
context{state: stateJS, element: elementScript},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var a = `${}",
|
||||||
|
context{state: stateJSTmplLit, element: elementScript},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var a = `${`",
|
||||||
|
context{state: stateJSTmplLit, element: elementScript},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var a = `${var a = \"",
|
||||||
|
context{state: stateJSDqStr, element: elementScript},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var a = `${var a = \"`",
|
||||||
|
context{state: stateJSDqStr, element: elementScript},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var a = `${var a = \"}",
|
||||||
|
context{state: stateJSDqStr, element: elementScript},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var a = `${``",
|
||||||
|
context{state: stateJS, element: elementScript},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var a = `${`}",
|
||||||
|
context{state: stateJSTmplLit, element: elementScript},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>`${ {} } asd`</script><script>`${ {} }",
|
||||||
|
context{state: stateJSTmplLit, element: elementScript},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var foo = `${ (_ => { return \"x\" })() + \"${",
|
||||||
|
context{state: stateJSDqStr, element: elementScript},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var a = `${ {</script><script>var b = `${ x }",
|
||||||
|
context{state: stateJSTmplLit, element: elementScript, jsCtx: jsCtxDivOp},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<script>var foo = `x` + \"${",
|
||||||
|
context{state: stateJSDqStr, element: elementScript},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,11 @@ func jsStrEscaper(args ...any) string {
|
||||||
return replace(s, jsStrReplacementTable)
|
return replace(s, jsStrReplacementTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func jsTmplLitEscaper(args ...any) string {
|
||||||
|
s, _ := stringify(args...)
|
||||||
|
return replace(s, jsBqStrReplacementTable)
|
||||||
|
}
|
||||||
|
|
||||||
// jsRegexpEscaper behaves like jsStrEscaper but escapes regular expression
|
// jsRegexpEscaper behaves like jsStrEscaper but escapes regular expression
|
||||||
// specials so the result is treated literally when included in a regular
|
// specials so the result is treated literally when included in a regular
|
||||||
// expression literal. /foo{{.X}}bar/ matches the string "foo" followed by
|
// expression literal. /foo{{.X}}bar/ matches the string "foo" followed by
|
||||||
|
|
@ -324,6 +329,31 @@ var jsStrReplacementTable = []string{
|
||||||
'\\': `\\`,
|
'\\': `\\`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// jsBqStrReplacementTable is like jsStrReplacementTable except it also contains
|
||||||
|
// the special characters for JS template literals: $, {, and }.
|
||||||
|
var jsBqStrReplacementTable = []string{
|
||||||
|
0: `\u0000`,
|
||||||
|
'\t': `\t`,
|
||||||
|
'\n': `\n`,
|
||||||
|
'\v': `\u000b`, // "\v" == "v" on IE 6.
|
||||||
|
'\f': `\f`,
|
||||||
|
'\r': `\r`,
|
||||||
|
// Encode HTML specials as hex so the output can be embedded
|
||||||
|
// in HTML attributes without further encoding.
|
||||||
|
'"': `\u0022`,
|
||||||
|
'`': `\u0060`,
|
||||||
|
'&': `\u0026`,
|
||||||
|
'\'': `\u0027`,
|
||||||
|
'+': `\u002b`,
|
||||||
|
'/': `\/`,
|
||||||
|
'<': `\u003c`,
|
||||||
|
'>': `\u003e`,
|
||||||
|
'\\': `\\`,
|
||||||
|
'$': `\u0024`,
|
||||||
|
'{': `\u007b`,
|
||||||
|
'}': `\u007d`,
|
||||||
|
}
|
||||||
|
|
||||||
// jsStrNormReplacementTable is like jsStrReplacementTable but does not
|
// jsStrNormReplacementTable is like jsStrReplacementTable but does not
|
||||||
// overencode existing escapes since this table has no entry for `\`.
|
// overencode existing escapes since this table has no entry for `\`.
|
||||||
var jsStrNormReplacementTable = []string{
|
var jsStrNormReplacementTable = []string{
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func _() {
|
||||||
_ = x[stateJS-10]
|
_ = x[stateJS-10]
|
||||||
_ = x[stateJSDqStr-11]
|
_ = x[stateJSDqStr-11]
|
||||||
_ = x[stateJSSqStr-12]
|
_ = x[stateJSSqStr-12]
|
||||||
_ = x[stateJSBqStr-13]
|
_ = x[stateJSTmplLit-13]
|
||||||
_ = x[stateJSRegexp-14]
|
_ = x[stateJSRegexp-14]
|
||||||
_ = x[stateJSBlockCmt-15]
|
_ = x[stateJSBlockCmt-15]
|
||||||
_ = x[stateJSLineCmt-16]
|
_ = x[stateJSLineCmt-16]
|
||||||
|
|
@ -39,9 +39,9 @@ func _() {
|
||||||
_ = x[stateDead-28]
|
_ = x[stateDead-28]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSBqStrstateJSRegexpstateJSBlockCmtstateJSLineCmtstateJSHTMLOpenCmtstateJSHTMLCloseCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateErrorstateDead"
|
const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSTmplLitstateJSRegexpstateJSBlockCmtstateJSLineCmtstateJSHTMLOpenCmtstateJSHTMLCloseCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateErrorstateDead"
|
||||||
|
|
||||||
var _state_index = [...]uint16{0, 9, 17, 30, 44, 60, 72, 83, 92, 100, 111, 118, 130, 142, 154, 167, 182, 196, 214, 233, 241, 254, 267, 280, 293, 304, 320, 335, 345, 354}
|
var _state_index = [...]uint16{0, 9, 17, 30, 44, 60, 72, 83, 92, 100, 111, 118, 130, 142, 156, 169, 184, 198, 216, 235, 243, 256, 269, 282, 295, 306, 322, 337, 347, 356}
|
||||||
|
|
||||||
func (i state) String() string {
|
func (i state) String() string {
|
||||||
if i >= state(len(_state_index)-1) {
|
if i >= state(len(_state_index)-1) {
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ var transitionFunc = [...]func(context, []byte) (context, int){
|
||||||
stateJS: tJS,
|
stateJS: tJS,
|
||||||
stateJSDqStr: tJSDelimited,
|
stateJSDqStr: tJSDelimited,
|
||||||
stateJSSqStr: tJSDelimited,
|
stateJSSqStr: tJSDelimited,
|
||||||
stateJSBqStr: tJSDelimited,
|
|
||||||
stateJSRegexp: tJSDelimited,
|
stateJSRegexp: tJSDelimited,
|
||||||
|
stateJSTmplLit: tJSTmpl,
|
||||||
stateJSBlockCmt: tBlockCmt,
|
stateJSBlockCmt: tBlockCmt,
|
||||||
stateJSLineCmt: tLineCmt,
|
stateJSLineCmt: tLineCmt,
|
||||||
stateJSHTMLOpenCmt: tLineCmt,
|
stateJSHTMLOpenCmt: tLineCmt,
|
||||||
|
|
@ -270,7 +270,7 @@ func tURL(c context, s []byte) (context, int) {
|
||||||
|
|
||||||
// tJS is the context transition function for the JS state.
|
// tJS is the context transition function for the JS state.
|
||||||
func tJS(c context, s []byte) (context, int) {
|
func tJS(c context, s []byte) (context, int) {
|
||||||
i := bytes.IndexAny(s, "\"`'/<-#")
|
i := bytes.IndexAny(s, "\"`'/{}<-#")
|
||||||
if i == -1 {
|
if i == -1 {
|
||||||
// Entire input is non string, comment, regexp tokens.
|
// Entire input is non string, comment, regexp tokens.
|
||||||
c.jsCtx = nextJSCtx(s, c.jsCtx)
|
c.jsCtx = nextJSCtx(s, c.jsCtx)
|
||||||
|
|
@ -283,7 +283,7 @@ func tJS(c context, s []byte) (context, int) {
|
||||||
case '\'':
|
case '\'':
|
||||||
c.state, c.jsCtx = stateJSSqStr, jsCtxRegexp
|
c.state, c.jsCtx = stateJSSqStr, jsCtxRegexp
|
||||||
case '`':
|
case '`':
|
||||||
c.state, c.jsCtx = stateJSBqStr, jsCtxRegexp
|
c.state, c.jsCtx = stateJSTmplLit, jsCtxRegexp
|
||||||
case '/':
|
case '/':
|
||||||
switch {
|
switch {
|
||||||
case i+1 < len(s) && s[i+1] == '/':
|
case i+1 < len(s) && s[i+1] == '/':
|
||||||
|
|
@ -320,12 +320,67 @@ func tJS(c context, s []byte) (context, int) {
|
||||||
if i+1 < len(s) && s[i+1] == '!' {
|
if i+1 < len(s) && s[i+1] == '!' {
|
||||||
c.state, i = stateJSLineCmt, i+1
|
c.state, i = stateJSLineCmt, i+1
|
||||||
}
|
}
|
||||||
|
case '{':
|
||||||
|
c.jsBraceDepth++
|
||||||
|
case '}':
|
||||||
|
if c.jsTmplExprDepth == 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 {
|
||||||
|
return c, i + 1
|
||||||
|
}
|
||||||
|
c.jsTmplExprDepth--
|
||||||
|
c.jsBraceDepth = 0
|
||||||
|
c.state = stateJSTmplLit
|
||||||
default:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
return c, i + 1
|
return c, i + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tJSTmpl(c context, s []byte) (context, int) {
|
||||||
|
var k int
|
||||||
|
for {
|
||||||
|
i := k + bytes.IndexAny(s[k:], "`\\$")
|
||||||
|
if i < k {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch s[i] {
|
||||||
|
case '\\':
|
||||||
|
i++
|
||||||
|
if i == len(s) {
|
||||||
|
return context{
|
||||||
|
state: stateError,
|
||||||
|
err: errorf(ErrPartialEscape, nil, 0, "unfinished escape sequence in JS string: %q", s),
|
||||||
|
}, len(s)
|
||||||
|
}
|
||||||
|
case '$':
|
||||||
|
if len(s) >= i+2 && s[i+1] == '{' {
|
||||||
|
c.jsTmplExprDepth++
|
||||||
|
c.state = stateJS
|
||||||
|
return c, i + 2
|
||||||
|
}
|
||||||
|
case '`':
|
||||||
|
// end
|
||||||
|
c.state = stateJS
|
||||||
|
return c, i + 1
|
||||||
|
}
|
||||||
|
k = i + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, len(s)
|
||||||
|
}
|
||||||
|
|
||||||
// tJSDelimited is the context transition function for the JS string and regexp
|
// tJSDelimited is the context transition function for the JS string and regexp
|
||||||
// states.
|
// states.
|
||||||
func tJSDelimited(c context, s []byte) (context, int) {
|
func tJSDelimited(c context, s []byte) (context, int) {
|
||||||
|
|
@ -333,8 +388,6 @@ func tJSDelimited(c context, s []byte) (context, int) {
|
||||||
switch c.state {
|
switch c.state {
|
||||||
case stateJSSqStr:
|
case stateJSSqStr:
|
||||||
specials = `\'`
|
specials = `\'`
|
||||||
case stateJSBqStr:
|
|
||||||
specials = "`\\"
|
|
||||||
case stateJSRegexp:
|
case stateJSRegexp:
|
||||||
specials = `\/[]`
|
specials = `\/[]`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue