]> Cypherpunks.ru repositories - gostls13.git/commitdiff
[release-branch.go1.21] html/template: support HTML-like comments in script contexts
authorRoland Shoemaker <bracewell@google.com>
Thu, 3 Aug 2023 19:24:13 +0000 (12:24 -0700)
committerCherry Mui <cherryyz@google.com>
Wed, 6 Sep 2023 14:20:08 +0000 (14:20 +0000)
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 #62396
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/+/2014618
Reviewed-on: https://go-review.googlesource.com/c/go/+/526096
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Cherry Mui <cherryyz@google.com>

src/html/template/context.go
src/html/template/escape.go
src/html/template/escape_test.go
src/html/template/state_string.go
src/html/template/transition.go

index c28fb0c5ea8ef30ddbcb4ffec6d0bef34756ad76..e07a0c4a027d06fd1ae74241fc50bd70a287dddc 100644 (file)
@@ -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
index 80d40feab685c7fd4ff61705979cdfe4eeb49990..473564c37ad5c939f8a2e15bf0783571cb20240f 100644 (file)
@@ -777,9 +777,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
index f8b2b448f2dfa7394053fd75b48b76cd9fefef80..f60c875927011df9e94874565e360759c79c39f4 100644 (file)
@@ -503,6 +503,16 @@ func TestEscape(t *testing.T) {
                        "<script>var a/*b*///c\nd</script>",
                        "<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" +
index 6fb1a6eeb0e746a4b2257bd6623ab96f28dc7f84..be7a9205111f238c6d86fabd40a750247251e96a 100644 (file)
@@ -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) {
index 3b9fbfb68f536d1858863f6bc7dab8d90a92d69e..d8ff18abb08200a769f56b83af1311c7181fa13d 100644 (file)
@@ -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