]> Cypherpunks repositories - gostls13.git/commitdiff
html/template: check "type" attribute in <script>
authorNodir Turakulov <nodir@google.com>
Sat, 5 Sep 2015 13:38:13 +0000 (06:38 -0700)
committerRuss Cox <rsc@golang.org>
Thu, 29 Sep 2016 20:12:31 +0000 (20:12 +0000)
Currently any script tag is treated as a javascript container, although
<script type="text/template"> must not be. Check "type" attribute of
"script" tag. If it is present and it is not a JS MIME type, do not
transition to elementScript state.

Fixes #12149, where // inside text template was treated as regexp.
Fixes #6701

Change-Id: I8fc9e504f7280bdd800f40383c061853665ac8a2
Reviewed-on: https://go-review.googlesource.com/14336
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
src/html/template/content_test.go
src/html/template/context.go
src/html/template/escape.go
src/html/template/escape_test.go
src/html/template/js.go
src/html/template/js_test.go
src/html/template/transition.go

index e698328693e211eea7d53347d4a11f276622ba55..0b4365c83ba392d64c80f90e878ea68252e9366d 100644 (file)
@@ -161,6 +161,47 @@ func TestTypedContent(t *testing.T) {
                                `greeting=H%69\x26addressee=(World)`,
                        },
                },
+               {
+                       `<script type="text/javascript">alert("{{.}}")</script>`,
+                       []string{
+                               `\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`,
+                               `a[href =~ \x22\/\/example.com\x22]#foo`,
+                               `Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`,
+                               ` dir=\x22ltr\x22`,
+                               `c \x26\x26 alert(\x22Hello, World!\x22);`,
+                               // Escape sequence not over-escaped.
+                               `Hello, World \x26 O\x27Reilly\x21`,
+                               `greeting=H%69\x26addressee=(World)`,
+                       },
+               },
+               {
+                       `<script type="text/javascript">alert({{.}})</script>`,
+                       []string{
+                               `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
+                               `"a[href =~ \"//example.com\"]#foo"`,
+                               `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
+                               `" dir=\"ltr\""`,
+                               // Not escaped.
+                               `c && alert("Hello, World!");`,
+                               // Escape sequence not over-escaped.
+                               `"Hello, World & O'Reilly\x21"`,
+                               `"greeting=H%69\u0026addressee=(World)"`,
+                       },
+               },
+               {
+                       // Not treated as JS. The output is same as for <div>{{.}}</div>
+                       `<script type="text/template">{{.}}</script>`,
+                       []string{
+                               `&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
+                               `a[href =~ &#34;//example.com&#34;]#foo`,
+                               // Not escaped.
+                               `Hello, <b>World</b> &amp;tc!`,
+                               ` dir=&#34;ltr&#34;`,
+                               `c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
+                               `Hello, World &amp; O&#39;Reilly\x21`,
+                               `greeting=H%69&amp;addressee=(World)`,
+                       },
+               },
                {
                        `<button onclick='alert("{{.}}")'>`,
                        []string{
index c90fc1fda5d5a876004ceaaecf1c6c85d1aa4304..37a3faf88b9c02ea40ef79664952c16fa6e03d22 100644 (file)
@@ -285,7 +285,8 @@ type element uint8
 const (
        // elementNone occurs outside a special tag or special element body.
        elementNone element = iota
-       // elementScript corresponds to the raw text <script> element.
+       // elementScript corresponds to the raw text <script> element
+       // with JS MIME type or no type attribute.
        elementScript
        // elementStyle corresponds to the raw text <style> element.
        elementStyle
@@ -319,6 +320,8 @@ const (
        attrNone attr = iota
        // attrScript corresponds to an event handler attribute.
        attrScript
+       // attrScriptType corresponds to the type attribute in script HTML element
+       attrScriptType
        // attrStyle corresponds to the style attribute whose value is CSS.
        attrStyle
        // attrURL corresponds to an attribute whose value is a URL.
@@ -326,10 +329,11 @@ const (
 )
 
 var attrNames = [...]string{
-       attrNone:   "attrNone",
-       attrScript: "attrScript",
-       attrStyle:  "attrStyle",
-       attrURL:    "attrURL",
+       attrNone:       "attrNone",
+       attrScript:     "attrScript",
+       attrScriptType: "attrScriptType",
+       attrStyle:      "attrStyle",
+       attrURL:        "attrURL",
 }
 
 func (a attr) String() string {
index 8f2fe460de61d9664fafe2b574051dfba2a99ca6..dcc0b8a531a95c3ef592e6b74d2ebb8c64818749 100644 (file)
@@ -673,6 +673,8 @@ func contextAfterText(c context, s []byte) (context, int) {
                return transitionFunc[c.state](c, s[:i])
        }
 
+       // We are at the beginning of an attribute value.
+
        i := bytes.IndexAny(s, delimEnds[c.delim])
        if i == -1 {
                i = len(s)
@@ -703,13 +705,21 @@ func contextAfterText(c context, s []byte) (context, int) {
                }
                return c, len(s)
        }
+
+       element := c.element
+
+       // If this is a non-JS "type" attribute inside "script" tag, do not treat the contents as JS.
+       if c.state == stateAttr && c.element == elementScript && c.attr == attrScriptType && !isJSType(string(s[:i])) {
+               element = elementNone
+       }
+
        if c.delim != delimSpaceOrTagEnd {
                // Consume any quote.
                i++
        }
        // On exiting an attribute, we discard all state information
        // except the state and element.
-       return context{state: stateTag, element: c.element}, i
+       return context{state: stateTag, element: element}, i
 }
 
 // editActionNode records a change to an action pipeline for later commit.
index 023ee57d597c4f321a282f7f059a074b9e2b6684..b7ccd8542575caffdbd255d2426c1a8ef26a0b12 100644 (file)
@@ -1364,6 +1364,10 @@ func TestEscapeText(t *testing.T) {
                        `<script type=text/javascript `,
                        context{state: stateTag, element: elementScript},
                },
+               {
+                       `<script>`,
+                       context{state: stateJS, jsCtx: jsCtxRegexp, element: elementScript},
+               },
                {
                        `<script>foo`,
                        context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript},
@@ -1388,6 +1392,14 @@ func TestEscapeText(t *testing.T) {
                        `<script>document.write("<script>alert(1)</script>");`,
                        context{state: stateText},
                },
+               {
+                       `<script type="text/template">`,
+                       context{state: stateText},
+               },
+               {
+                       `<script type="notjs">`,
+                       context{state: stateText},
+               },
                {
                        `<Script>`,
                        context{state: stateJS, element: elementScript},
index f6d166b311360765ff887f5bdf5487e797097226..8e58f463ee87261ffbcef4b5517b31e911980215 100644 (file)
@@ -362,3 +362,41 @@ func isJSIdentPart(r rune) bool {
        }
        return false
 }
+
+// isJSType returns true if the given MIME type should be considered JavaScript.
+//
+// It is used to determine whether a script tag with a type attribute is a javascript container.
+func isJSType(mimeType string) bool {
+       // per
+       //   http://www.w3.org/TR/html5/scripting-1.html#attr-script-type
+       //   https://tools.ietf.org/html/rfc7231#section-3.1.1
+       //   http://tools.ietf.org/html/rfc4329#section-3
+
+       // discard parameters
+       if i := strings.Index(mimeType, ";"); i >= 0 {
+               mimeType = mimeType[:i]
+       }
+       mimeType = strings.TrimSpace(mimeType)
+       switch mimeType {
+       case
+               "application/ecmascript",
+               "application/javascript",
+               "application/x-ecmascript",
+               "application/x-javascript",
+               "text/ecmascript",
+               "text/javascript",
+               "text/javascript1.0",
+               "text/javascript1.1",
+               "text/javascript1.2",
+               "text/javascript1.3",
+               "text/javascript1.4",
+               "text/javascript1.5",
+               "text/jscript",
+               "text/livescript",
+               "text/x-ecmascript",
+               "text/x-javascript":
+               return true
+       default:
+               return false
+       }
+}
index 7af7997de9b6c3dbf8c7016238370965d910c42b..58fc37ae3a4d46cad2aa563246cbe5badf88d362 100644 (file)
@@ -332,6 +332,24 @@ func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
        }
 }
 
+func TestIsJsMimeType(t *testing.T) {
+       tests := []struct {
+               in  string
+               out bool
+       }{
+               {"application/javascript;version=1.8", true},
+               {"application/javascript;version=1.8;foo=bar", true},
+               {"application/javascript/version=1.8", false},
+               {"text/javascript", true},
+       }
+
+       for _, test := range tests {
+               if isJSType(test.in) != test.out {
+                       t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out)
+               }
+       }
+}
+
 func BenchmarkJSValEscaperWithNum(b *testing.B) {
        for i := 0; i < b.N; i++ {
                jsValEscaper(3.141592654)
index aefe0355af4c73c471db2eae8b2a5ca9f9fbb41d..4a4716d782043d22a79a44c5733a524a583cb323 100644 (file)
@@ -105,14 +105,21 @@ func tTag(c context, s []byte) (context, int) {
                        err:   errorf(ErrBadHTML, nil, 0, "expected space, attr name, or end of tag, but got %q", s[i:]),
                }, len(s)
        }
-       switch attrType(string(s[i:j])) {
-       case contentTypeURL:
-               attr = attrURL
-       case contentTypeCSS:
-               attr = attrStyle
-       case contentTypeJS:
-               attr = attrScript
+
+       attrName := string(s[i:j])
+       if c.element == elementScript && attrName == "type" {
+               attr = attrScriptType
+       } else {
+               switch attrType(attrName) {
+               case contentTypeURL:
+                       attr = attrURL
+               case contentTypeCSS:
+                       attr = attrStyle
+               case contentTypeJS:
+                       attr = attrScript
+               }
        }
+
        if j == len(s) {
                state = stateAttrName
        } else {
@@ -149,10 +156,11 @@ func tAfterName(c context, s []byte) (context, int) {
 }
 
 var attrStartStates = [...]state{
-       attrNone:   stateAttr,
-       attrScript: stateJS,
-       attrStyle:  stateCSS,
-       attrURL:    stateURL,
+       attrNone:       stateAttr,
+       attrScript:     stateJS,
+       attrScriptType: stateAttr,
+       attrStyle:      stateCSS,
+       attrURL:        stateURL,
 }
 
 // tBeforeValue is the context transition function for stateBeforeValue.