diff options
author | Roland Shoemaker <bracewell@google.com> | 2023-08-03 12:24:13 -0700 |
---|---|---|
committer | Cherry Mui <cherryyz@google.com> | 2023-09-06 14:22:29 +0000 |
commit | 023b542edf38e2a1f87fcefb9f75ff2f99401b4c (patch) | |
tree | 5e3155edbf5c3f3dd45fce471fcc983a92b88968 | |
parent | 612da32fb5e9c1e9641cd55dc269518426057ea9 (diff) | |
download | go-023b542edf38e2a1f87fcefb9f75ff2f99401b4c.tar.gz go-023b542edf38e2a1f87fcefb9f75ff2f99401b4c.zip |
[release-branch.go1.20] html/template: support HTML-like comments in script contexts
Per Appendix B.1.1 of the ECMAScript specification, support HTML-like
comments in script contexts. Also per section 12.5, support hashbang
comments. This brings our parsing in-line with how browsers treat these
comment types.
Thanks to Takeshi Kaneko (GMO Cybersecurity by Ierae, Inc.) for
reporting this issue.
Fixes #62196
Fixes #62395
Fixes CVE-2023-39318
Change-Id: Id512702c5de3ae46cf648e268cb10e1eb392a181
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1976593
Run-TryBot: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/2014620
Reviewed-on: https://go-review.googlesource.com/c/go/+/526098
Run-TryBot: Cherry Mui <cherryyz@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
-rw-r--r-- | src/html/template/context.go | 6 | ||||
-rw-r--r-- | src/html/template/escape.go | 5 | ||||
-rw-r--r-- | src/html/template/escape_test.go | 10 | ||||
-rw-r--r-- | src/html/template/state_string.go | 26 | ||||
-rw-r--r-- | src/html/template/transition.go | 80 |
5 files changed, 84 insertions, 43 deletions
diff --git a/src/html/template/context.go b/src/html/template/context.go index c28fb0c5ea..e07a0c4a02 100644 --- a/src/html/template/context.go +++ b/src/html/template/context.go @@ -128,6 +128,10 @@ const ( stateJSBlockCmt // stateJSLineCmt occurs inside a JavaScript // line comment. stateJSLineCmt + // stateJSHTMLOpenCmt occurs inside a JavaScript <!-- HTML-like comment. + stateJSHTMLOpenCmt + // stateJSHTMLCloseCmt occurs inside a JavaScript --> HTML-like comment. + stateJSHTMLCloseCmt // stateCSS occurs inside a <style> element or style attribute. stateCSS // stateCSSDqStr occurs inside a CSS double quoted string. @@ -155,7 +159,7 @@ const ( // authors & maintainers, not for end-users or machines. func isComment(s state) bool { switch s { - case stateHTMLCmt, stateJSBlockCmt, stateJSLineCmt, stateCSSBlockCmt, stateCSSLineCmt: + case stateHTMLCmt, stateJSBlockCmt, stateJSLineCmt, stateJSHTMLOpenCmt, stateJSHTMLCloseCmt, stateCSSBlockCmt, stateCSSLineCmt: return true } return false diff --git a/src/html/template/escape.go b/src/html/template/escape.go index c262d1698d..36021c0a8d 100644 --- a/src/html/template/escape.go +++ b/src/html/template/escape.go @@ -776,9 +776,12 @@ func (e *escaper) escapeText(c context, n *parse.TextNode) context { if c.state != c1.state && isComment(c1.state) && c1.delim == delimNone { // Preserve the portion between written and the comment start. cs := i1 - 2 - if c1.state == stateHTMLCmt { + if c1.state == stateHTMLCmt || c1.state == stateJSHTMLOpenCmt { // "<!--" instead of "/*" or "//" cs -= 2 + } else if c1.state == stateJSHTMLCloseCmt { + // "-->" instead of "/*" or "//" + cs -= 1 } b.Write(s[written:cs]) written = i1 diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go index f8b2b448f2..f60c875927 100644 --- a/src/html/template/escape_test.go +++ b/src/html/template/escape_test.go @@ -504,6 +504,16 @@ func TestEscape(t *testing.T) { "<script>var a \nd</script>", }, { + "JS HTML-like comments", + "<script>before <!-- beep\nbetween\nbefore-->boop\n</script>", + "<script>before \nbetween\nbefore\n</script>", + }, + { + "JS hashbang comment", + "<script>#! beep\n</script>", + "<script>\n</script>", + }, + { "CSS comments", "<style>p// paragraph\n" + `{border: 1px/* color */{{"#00f"}}}</style>`, diff --git a/src/html/template/state_string.go b/src/html/template/state_string.go index 6fb1a6eeb0..be7a920511 100644 --- a/src/html/template/state_string.go +++ b/src/html/template/state_string.go @@ -25,21 +25,23 @@ func _() { _ = x[stateJSRegexp-14] _ = x[stateJSBlockCmt-15] _ = x[stateJSLineCmt-16] - _ = x[stateCSS-17] - _ = x[stateCSSDqStr-18] - _ = x[stateCSSSqStr-19] - _ = x[stateCSSDqURL-20] - _ = x[stateCSSSqURL-21] - _ = x[stateCSSURL-22] - _ = x[stateCSSBlockCmt-23] - _ = x[stateCSSLineCmt-24] - _ = x[stateError-25] - _ = x[stateDead-26] + _ = x[stateJSHTMLOpenCmt-17] + _ = x[stateJSHTMLCloseCmt-18] + _ = x[stateCSS-19] + _ = x[stateCSSDqStr-20] + _ = x[stateCSSSqStr-21] + _ = x[stateCSSDqURL-22] + _ = x[stateCSSSqURL-23] + _ = x[stateCSSURL-24] + _ = x[stateCSSBlockCmt-25] + _ = x[stateCSSLineCmt-26] + _ = x[stateError-27] + _ = x[stateDead-28] } -const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSBqStrstateJSRegexpstateJSBlockCmtstateJSLineCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateErrorstateDead" +const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSBqStrstateJSRegexpstateJSBlockCmtstateJSLineCmtstateJSHTMLOpenCmtstateJSHTMLCloseCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateErrorstateDead" -var _state_index = [...]uint16{0, 9, 17, 30, 44, 60, 72, 83, 92, 100, 111, 118, 130, 142, 154, 167, 182, 196, 204, 217, 230, 243, 256, 267, 283, 298, 308, 317} +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} func (i state) String() string { if i >= state(len(_state_index)-1) { diff --git a/src/html/template/transition.go b/src/html/template/transition.go index 92eb351906..12aa4c41fe 100644 --- a/src/html/template/transition.go +++ b/src/html/template/transition.go @@ -14,32 +14,34 @@ import ( // the updated context and the number of bytes consumed from the front of the // input. var transitionFunc = [...]func(context, []byte) (context, int){ - stateText: tText, - stateTag: tTag, - stateAttrName: tAttrName, - stateAfterName: tAfterName, - stateBeforeValue: tBeforeValue, - stateHTMLCmt: tHTMLCmt, - stateRCDATA: tSpecialTagEnd, - stateAttr: tAttr, - stateURL: tURL, - stateSrcset: tURL, - stateJS: tJS, - stateJSDqStr: tJSDelimited, - stateJSSqStr: tJSDelimited, - stateJSBqStr: tJSDelimited, - stateJSRegexp: tJSDelimited, - stateJSBlockCmt: tBlockCmt, - stateJSLineCmt: tLineCmt, - stateCSS: tCSS, - stateCSSDqStr: tCSSStr, - stateCSSSqStr: tCSSStr, - stateCSSDqURL: tCSSStr, - stateCSSSqURL: tCSSStr, - stateCSSURL: tCSSStr, - stateCSSBlockCmt: tBlockCmt, - stateCSSLineCmt: tLineCmt, - stateError: tError, + stateText: tText, + stateTag: tTag, + stateAttrName: tAttrName, + stateAfterName: tAfterName, + stateBeforeValue: tBeforeValue, + stateHTMLCmt: tHTMLCmt, + stateRCDATA: tSpecialTagEnd, + stateAttr: tAttr, + stateURL: tURL, + stateSrcset: tURL, + stateJS: tJS, + stateJSDqStr: tJSDelimited, + stateJSSqStr: tJSDelimited, + stateJSBqStr: tJSDelimited, + stateJSRegexp: tJSDelimited, + stateJSBlockCmt: tBlockCmt, + stateJSLineCmt: tLineCmt, + stateJSHTMLOpenCmt: tLineCmt, + stateJSHTMLCloseCmt: tLineCmt, + stateCSS: tCSS, + stateCSSDqStr: tCSSStr, + stateCSSSqStr: tCSSStr, + stateCSSDqURL: tCSSStr, + stateCSSSqURL: tCSSStr, + stateCSSURL: tCSSStr, + stateCSSBlockCmt: tBlockCmt, + stateCSSLineCmt: tLineCmt, + stateError: tError, } var commentStart = []byte("<!--") @@ -263,7 +265,7 @@ func tURL(c context, s []byte) (context, int) { // 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) @@ -293,6 +295,26 @@ func tJS(c context, s []byte) (context, int) { err: errorf(ErrSlashAmbig, nil, 0, "'/' could start a division or regexp: %.32q", s[i:]), }, len(s) } + // ECMAScript supports HTML style comments for legacy reasons, see Appendix + // B.1.1 "HTML-like Comments". The handling of these comments is somewhat + // confusing. Multi-line comments are not supported, i.e. anything on lines + // between the opening and closing tokens is not considered a comment, but + // anything following the opening or closing token, on the same line, is + // ignored. As such we simply treat any line prefixed with "<!--" or "-->" + // as if it were actually prefixed with "//" and move on. + case '<': + if i+3 < len(s) && bytes.Equal(commentStart, s[i:i+4]) { + c.state, i = stateJSHTMLOpenCmt, i+3 + } + case '-': + if i+2 < len(s) && bytes.Equal(commentEnd, s[i:i+3]) { + c.state, i = stateJSHTMLCloseCmt, i+2 + } + // ECMAScript also supports "hashbang" comment lines, see Section 12.5. + case '#': + if i+1 < len(s) && s[i+1] == '!' { + c.state, i = stateJSLineCmt, i+1 + } default: panic("unreachable") } @@ -372,12 +394,12 @@ func tBlockCmt(c context, s []byte) (context, int) { return c, i + 2 } -// tLineCmt is the context transition function for //comment states. +// tLineCmt is the context transition function for //comment states, and the JS HTML-like comment state. func tLineCmt(c context, s []byte) (context, int) { var lineTerminators string var endState state switch c.state { - case stateJSLineCmt: + case stateJSLineCmt, stateJSHTMLOpenCmt, stateJSHTMLCloseCmt: lineTerminators, endState = "\n\r\u2028\u2029", stateJS case stateCSSLineCmt: lineTerminators, endState = "\n\f\r", stateCSS |