mirror of https://github.com/golang/go.git
html/template: check "type" attribute in <script>
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>
This commit is contained in:
parent
f5516559e6
commit
ffd1c781b7
|
|
@ -161,6 +161,47 @@ func TestTypedContent(t *testing.T) {
|
||||||
`greeting=H%69\x26addressee=(World)`,
|
`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{
|
||||||
|
`<b> "foo%" O'Reilly &bar;`,
|
||||||
|
`a[href =~ "//example.com"]#foo`,
|
||||||
|
// Not escaped.
|
||||||
|
`Hello, <b>World</b> &tc!`,
|
||||||
|
` dir="ltr"`,
|
||||||
|
`c && alert("Hello, World!");`,
|
||||||
|
`Hello, World & O'Reilly\x21`,
|
||||||
|
`greeting=H%69&addressee=(World)`,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
`<button onclick='alert("{{.}}")'>`,
|
`<button onclick='alert("{{.}}")'>`,
|
||||||
[]string{
|
[]string{
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,8 @@ type element uint8
|
||||||
const (
|
const (
|
||||||
// elementNone occurs outside a special tag or special element body.
|
// elementNone occurs outside a special tag or special element body.
|
||||||
elementNone element = iota
|
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
|
elementScript
|
||||||
// elementStyle corresponds to the raw text <style> element.
|
// elementStyle corresponds to the raw text <style> element.
|
||||||
elementStyle
|
elementStyle
|
||||||
|
|
@ -319,6 +320,8 @@ const (
|
||||||
attrNone attr = iota
|
attrNone attr = iota
|
||||||
// attrScript corresponds to an event handler attribute.
|
// attrScript corresponds to an event handler attribute.
|
||||||
attrScript
|
attrScript
|
||||||
|
// attrScriptType corresponds to the type attribute in script HTML element
|
||||||
|
attrScriptType
|
||||||
// attrStyle corresponds to the style attribute whose value is CSS.
|
// attrStyle corresponds to the style attribute whose value is CSS.
|
||||||
attrStyle
|
attrStyle
|
||||||
// attrURL corresponds to an attribute whose value is a URL.
|
// attrURL corresponds to an attribute whose value is a URL.
|
||||||
|
|
@ -326,10 +329,11 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var attrNames = [...]string{
|
var attrNames = [...]string{
|
||||||
attrNone: "attrNone",
|
attrNone: "attrNone",
|
||||||
attrScript: "attrScript",
|
attrScript: "attrScript",
|
||||||
attrStyle: "attrStyle",
|
attrScriptType: "attrScriptType",
|
||||||
attrURL: "attrURL",
|
attrStyle: "attrStyle",
|
||||||
|
attrURL: "attrURL",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a attr) String() string {
|
func (a attr) String() string {
|
||||||
|
|
|
||||||
|
|
@ -673,6 +673,8 @@ func contextAfterText(c context, s []byte) (context, int) {
|
||||||
return transitionFunc[c.state](c, s[:i])
|
return transitionFunc[c.state](c, s[:i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We are at the beginning of an attribute value.
|
||||||
|
|
||||||
i := bytes.IndexAny(s, delimEnds[c.delim])
|
i := bytes.IndexAny(s, delimEnds[c.delim])
|
||||||
if i == -1 {
|
if i == -1 {
|
||||||
i = len(s)
|
i = len(s)
|
||||||
|
|
@ -703,13 +705,21 @@ func contextAfterText(c context, s []byte) (context, int) {
|
||||||
}
|
}
|
||||||
return c, len(s)
|
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 {
|
if c.delim != delimSpaceOrTagEnd {
|
||||||
// Consume any quote.
|
// Consume any quote.
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
// On exiting an attribute, we discard all state information
|
// On exiting an attribute, we discard all state information
|
||||||
// except the state and element.
|
// 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.
|
// editActionNode records a change to an action pipeline for later commit.
|
||||||
|
|
|
||||||
|
|
@ -1364,6 +1364,10 @@ func TestEscapeText(t *testing.T) {
|
||||||
`<script type=text/javascript `,
|
`<script type=text/javascript `,
|
||||||
context{state: stateTag, element: elementScript},
|
context{state: stateTag, element: elementScript},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
`<script>`,
|
||||||
|
context{state: stateJS, jsCtx: jsCtxRegexp, element: elementScript},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
`<script>foo`,
|
`<script>foo`,
|
||||||
context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript},
|
context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript},
|
||||||
|
|
@ -1388,6 +1392,14 @@ func TestEscapeText(t *testing.T) {
|
||||||
`<script>document.write("<script>alert(1)</script>");`,
|
`<script>document.write("<script>alert(1)</script>");`,
|
||||||
context{state: stateText},
|
context{state: stateText},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
`<script type="text/template">`,
|
||||||
|
context{state: stateText},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`<script type="notjs">`,
|
||||||
|
context{state: stateText},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
`<Script>`,
|
`<Script>`,
|
||||||
context{state: stateJS, element: elementScript},
|
context{state: stateJS, element: elementScript},
|
||||||
|
|
|
||||||
|
|
@ -362,3 +362,41 @@ func isJSIdentPart(r rune) bool {
|
||||||
}
|
}
|
||||||
return false
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
func BenchmarkJSValEscaperWithNum(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
jsValEscaper(3.141592654)
|
jsValEscaper(3.141592654)
|
||||||
|
|
|
||||||
|
|
@ -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:]),
|
err: errorf(ErrBadHTML, nil, 0, "expected space, attr name, or end of tag, but got %q", s[i:]),
|
||||||
}, len(s)
|
}, len(s)
|
||||||
}
|
}
|
||||||
switch attrType(string(s[i:j])) {
|
|
||||||
case contentTypeURL:
|
attrName := string(s[i:j])
|
||||||
attr = attrURL
|
if c.element == elementScript && attrName == "type" {
|
||||||
case contentTypeCSS:
|
attr = attrScriptType
|
||||||
attr = attrStyle
|
} else {
|
||||||
case contentTypeJS:
|
switch attrType(attrName) {
|
||||||
attr = attrScript
|
case contentTypeURL:
|
||||||
|
attr = attrURL
|
||||||
|
case contentTypeCSS:
|
||||||
|
attr = attrStyle
|
||||||
|
case contentTypeJS:
|
||||||
|
attr = attrScript
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if j == len(s) {
|
if j == len(s) {
|
||||||
state = stateAttrName
|
state = stateAttrName
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -149,10 +156,11 @@ func tAfterName(c context, s []byte) (context, int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var attrStartStates = [...]state{
|
var attrStartStates = [...]state{
|
||||||
attrNone: stateAttr,
|
attrNone: stateAttr,
|
||||||
attrScript: stateJS,
|
attrScript: stateJS,
|
||||||
attrStyle: stateCSS,
|
attrScriptType: stateAttr,
|
||||||
attrURL: stateURL,
|
attrStyle: stateCSS,
|
||||||
|
attrURL: stateURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
// tBeforeValue is the context transition function for stateBeforeValue.
|
// tBeforeValue is the context transition function for stateBeforeValue.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue