aboutsummaryrefslogtreecommitdiff
path: root/src/html
diff options
context:
space:
mode:
Diffstat (limited to 'src/html')
-rw-r--r--src/html/template/attr_string.go5
-rw-r--r--src/html/template/context.go8
-rw-r--r--src/html/template/element_string.go5
-rw-r--r--src/html/template/escape.go14
-rw-r--r--src/html/template/escape_test.go32
-rw-r--r--src/html/template/state_string.go8
-rw-r--r--src/html/template/transition.go47
7 files changed, 106 insertions, 13 deletions
diff --git a/src/html/template/attr_string.go b/src/html/template/attr_string.go
index 51c3f26208..7159fa9cba 100644
--- a/src/html/template/attr_string.go
+++ b/src/html/template/attr_string.go
@@ -14,11 +14,12 @@ func _() {
_ = x[attrStyle-3]
_ = x[attrURL-4]
_ = x[attrSrcset-5]
+ _ = x[attrMetaContent-6]
}
-const _attr_name = "attrNoneattrScriptattrScriptTypeattrStyleattrURLattrSrcset"
+const _attr_name = "attrNoneattrScriptattrScriptTypeattrStyleattrURLattrSrcsetattrMetaContent"
-var _attr_index = [...]uint8{0, 8, 18, 32, 41, 48, 58}
+var _attr_index = [...]uint8{0, 8, 18, 32, 41, 48, 58, 73}
func (i attr) String() string {
if i >= attr(len(_attr_index)-1) {
diff --git a/src/html/template/context.go b/src/html/template/context.go
index b78f0f7325..8b3af2feab 100644
--- a/src/html/template/context.go
+++ b/src/html/template/context.go
@@ -156,6 +156,10 @@ const (
// stateError is an infectious error state outside any valid
// HTML/CSS/JS construct.
stateError
+ // stateMetaContent occurs inside a HTML meta element content attribute.
+ stateMetaContent
+ // stateMetaContentURL occurs inside a "url=" tag in a HTML meta element content attribute.
+ stateMetaContentURL
// stateDead marks unreachable code after a {{break}} or {{continue}}.
stateDead
)
@@ -267,6 +271,8 @@ const (
elementTextarea
// elementTitle corresponds to the RCDATA <title> element.
elementTitle
+ // elementMeta corresponds to the HTML <meta> element.
+ elementMeta
)
//go:generate stringer -type attr
@@ -288,4 +294,6 @@ const (
attrURL
// attrSrcset corresponds to a srcset attribute.
attrSrcset
+ // attrMetaContent corresponds to the content attribute in meta HTML element.
+ attrMetaContent
)
diff --git a/src/html/template/element_string.go b/src/html/template/element_string.go
index db286655aa..bdf9da7b9d 100644
--- a/src/html/template/element_string.go
+++ b/src/html/template/element_string.go
@@ -13,11 +13,12 @@ func _() {
_ = x[elementStyle-2]
_ = x[elementTextarea-3]
_ = x[elementTitle-4]
+ _ = x[elementMeta-5]
}
-const _element_name = "elementNoneelementScriptelementStyleelementTextareaelementTitle"
+const _element_name = "elementNoneelementScriptelementStyleelementTextareaelementTitleelementMeta"
-var _element_index = [...]uint8{0, 11, 24, 36, 51, 63}
+var _element_index = [...]uint8{0, 11, 24, 36, 51, 63, 74}
func (i element) String() string {
if i >= element(len(_element_index)-1) {
diff --git a/src/html/template/escape.go b/src/html/template/escape.go
index 1f963e61b4..d8e1b8cb54 100644
--- a/src/html/template/escape.go
+++ b/src/html/template/escape.go
@@ -166,6 +166,8 @@ func (e *escaper) escape(c context, n parse.Node) context {
var debugAllowActionJSTmpl = godebug.New("jstmpllitinterp")
+var htmlmetacontenturlescape = godebug.New("htmlmetacontenturlescape")
+
// escapeAction escapes an action template node.
func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
if len(n.Pipe.Decl) != 0 {
@@ -223,6 +225,18 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
default:
panic(c.urlPart.String())
}
+ case stateMetaContent:
+ // Handled below in delim check.
+ case stateMetaContentURL:
+ if htmlmetacontenturlescape.Value() != "0" {
+ s = append(s, "_html_template_urlfilter")
+ } else {
+ // We don't have a great place to increment this, since it's hard to
+ // know if we actually escape any urls in _html_template_urlfilter,
+ // since it has no information about what context it is being
+ // executed in etc. This is probably the best we can do.
+ htmlmetacontenturlescape.IncNonDefault()
+ }
case stateJS:
s = append(s, "_html_template_jsvalescaper")
// A slash after a value starts a div operator.
diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go
index 003060e90f..43781f38a5 100644
--- a/src/html/template/escape_test.go
+++ b/src/html/template/escape_test.go
@@ -8,6 +8,7 @@ import (
"bytes"
"encoding/json"
"fmt"
+ "internal/testenv"
"os"
"strings"
"testing"
@@ -734,6 +735,16 @@ func TestEscape(t *testing.T) {
"<script>var a = `${ var a = \"{{\"a \\\" d\"}}\" }`</script>",
"<script>var a = `${ var a = \"a \\u0022 d\" }`</script>",
},
+ {
+ "meta content attribute url",
+ `<meta http-equiv="refresh" content="asd; url={{"javascript:alert(1)"}}; asd; url={{"vbscript:alert(1)"}}; asd">`,
+ `<meta http-equiv="refresh" content="asd; url=#ZgotmplZ; asd; url=#ZgotmplZ; asd">`,
+ },
+ {
+ "meta content string",
+ `<meta http-equiv="refresh" content="{{"asd: 123"}}">`,
+ `<meta http-equiv="refresh" content="asd: 123">`,
+ },
}
for _, test := range tests {
@@ -1016,6 +1027,14 @@ func TestErrors(t *testing.T) {
"<script>var tmpl = `asd ${return \"{\"}`;</script>",
``,
},
+ {
+ `{{if eq "" ""}}<meta>{{end}}`,
+ ``,
+ },
+ {
+ `{{if eq "" ""}}<meta content="url={{"asd"}}">{{end}}`,
+ ``,
+ },
// Error cases.
{
@@ -2198,3 +2217,16 @@ func TestAliasedParseTreeDoesNotOverescape(t *testing.T) {
t.Fatalf(`Template "foo" and "bar" rendered %q and %q respectively, expected equal values`, got1, got2)
}
}
+
+func TestMetaContentEscapeGODEBUG(t *testing.T) {
+ testenv.SetGODEBUG(t, "htmlmetacontenturlescape=0")
+ tmpl := Must(New("").Parse(`<meta http-equiv="refresh" content="asd; url={{"javascript:alert(1)"}}; asd; url={{"vbscript:alert(1)"}}; asd">`))
+ var b strings.Builder
+ if err := tmpl.Execute(&b, nil); err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ want := `<meta http-equiv="refresh" content="asd; url=javascript:alert(1); asd; url=vbscript:alert(1); asd">`
+ if got := b.String(); got != want {
+ t.Fatalf("got %q, want %q", got, want)
+ }
+}
diff --git a/src/html/template/state_string.go b/src/html/template/state_string.go
index eed1e8bcc0..f5a70b2231 100644
--- a/src/html/template/state_string.go
+++ b/src/html/template/state_string.go
@@ -36,12 +36,14 @@ func _() {
_ = x[stateCSSBlockCmt-25]
_ = x[stateCSSLineCmt-26]
_ = x[stateError-27]
- _ = x[stateDead-28]
+ _ = x[stateMetaContent-28]
+ _ = x[stateMetaContentURL-29]
+ _ = x[stateDead-30]
}
-const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSTmplLitstateJSRegexpstateJSBlockCmtstateJSLineCmtstateJSHTMLOpenCmtstateJSHTMLCloseCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateErrorstateDead"
+const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSTmplLitstateJSRegexpstateJSBlockCmtstateJSLineCmtstateJSHTMLOpenCmtstateJSHTMLCloseCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateErrorstateMetaContentstateMetaContentURLstateDead"
-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}
+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, 363, 382, 391}
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 c430389a34..7fbab1df7b 100644
--- a/src/html/template/transition.go
+++ b/src/html/template/transition.go
@@ -23,6 +23,8 @@ var transitionFunc = [...]func(context, []byte) (context, int){
stateRCDATA: tSpecialTagEnd,
stateAttr: tAttr,
stateURL: tURL,
+ stateMetaContent: tMetaContent,
+ stateMetaContentURL: tMetaContentURL,
stateSrcset: tURL,
stateJS: tJS,
stateJSDqStr: tJSDelimited,
@@ -83,6 +85,7 @@ var elementContentType = [...]state{
elementStyle: stateCSS,
elementTextarea: stateRCDATA,
elementTitle: stateRCDATA,
+ elementMeta: stateText,
}
// tTag is the context transition function for the tag state.
@@ -93,6 +96,11 @@ func tTag(c context, s []byte) (context, int) {
return c, len(s)
}
if s[i] == '>' {
+ // Treat <meta> specially, because it doesn't have an end tag, and we
+ // want to transition into the correct state/element for it.
+ if c.element == elementMeta {
+ return context{state: stateText, element: elementNone}, i + 1
+ }
return context{
state: elementContentType[c.element],
element: c.element,
@@ -113,6 +121,8 @@ func tTag(c context, s []byte) (context, int) {
attrName := strings.ToLower(string(s[i:j]))
if c.element == elementScript && attrName == "type" {
attr = attrScriptType
+ } else if c.element == elementMeta && attrName == "content" {
+ attr = attrMetaContent
} else {
switch attrType(attrName) {
case contentTypeURL:
@@ -162,12 +172,13 @@ func tAfterName(c context, s []byte) (context, int) {
}
var attrStartStates = [...]state{
- attrNone: stateAttr,
- attrScript: stateJS,
- attrScriptType: stateAttr,
- attrStyle: stateCSS,
- attrURL: stateURL,
- attrSrcset: stateSrcset,
+ attrNone: stateAttr,
+ attrScript: stateJS,
+ attrScriptType: stateAttr,
+ attrStyle: stateCSS,
+ attrURL: stateURL,
+ attrSrcset: stateSrcset,
+ attrMetaContent: stateMetaContent,
}
// tBeforeValue is the context transition function for stateBeforeValue.
@@ -203,6 +214,7 @@ var specialTagEndMarkers = [...][]byte{
elementStyle: []byte("style"),
elementTextarea: []byte("textarea"),
elementTitle: []byte("title"),
+ elementMeta: []byte(""),
}
var (
@@ -612,6 +624,28 @@ func tError(c context, s []byte) (context, int) {
return c, len(s)
}
+// tMetaContent is the context transition function for the meta content attribute state.
+func tMetaContent(c context, s []byte) (context, int) {
+ for i := 0; i < len(s); i++ {
+ if i+3 <= len(s)-1 && bytes.Equal(bytes.ToLower(s[i:i+4]), []byte("url=")) {
+ c.state = stateMetaContentURL
+ return c, i + 4
+ }
+ }
+ return c, len(s)
+}
+
+// tMetaContentURL is the context transition function for the "url=" part of a meta content attribute state.
+func tMetaContentURL(c context, s []byte) (context, int) {
+ for i := 0; i < len(s); i++ {
+ if s[i] == ';' {
+ c.state = stateMetaContent
+ return c, i + 1
+ }
+ }
+ return c, len(s)
+}
+
// eatAttrName returns the largest j such that s[i:j] is an attribute name.
// It returns an error if s[i:] does not look like it begins with an
// attribute name, such as encountering a quote mark without a preceding
@@ -638,6 +672,7 @@ var elementNameMap = map[string]element{
"style": elementStyle,
"textarea": elementTextarea,
"title": elementTitle,
+ "meta": elementMeta,
}
// asciiAlpha reports whether c is an ASCII letter.