diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2022-11-14 19:13:09 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2022-11-14 22:31:50 +0100 |
commit | f6ab9553f4c0429586fc9221d1779c460cf4922a (patch) | |
tree | 0f4efed30fb9750b800a4865c5065285bbc4d1fc /tpl | |
parent | 58a98c7758f90a16df51e4fee9ead0233157a1e4 (diff) |
tpl/internal: Sync go_templates
Closes #10411
Diffstat (limited to 'tpl')
33 files changed, 739 insertions, 514 deletions
diff --git a/tpl/internal/go_templates/fmtsort/sort.go b/tpl/internal/go_templates/fmtsort/sort.go index 34c1f477f..278a89bd7 100644 --- a/tpl/internal/go_templates/fmtsort/sort.go +++ b/tpl/internal/go_templates/fmtsort/sort.go @@ -36,19 +36,18 @@ func (o *SortedMap) Swap(i, j int) { // // The ordering rules are more general than with Go's < operator: // -// - when applicable, nil compares low -// - ints, floats, and strings order by < -// - NaN compares less than non-NaN floats -// - bool compares false before true -// - complex compares real, then imag -// - pointers compare by machine address -// - channel values compare by machine address -// - structs compare each field in turn -// - arrays compare each element in turn. -// Otherwise identical arrays compare by length. -// - interface values compare first by reflect.Type describing the concrete type -// and then by concrete value as described in the previous rules. -// +// - when applicable, nil compares low +// - ints, floats, and strings order by < +// - NaN compares less than non-NaN floats +// - bool compares false before true +// - complex compares real, then imag +// - pointers compare by machine address +// - channel values compare by machine address +// - structs compare each field in turn +// - arrays compare each element in turn. +// Otherwise identical arrays compare by length. +// - interface values compare first by reflect.Type describing the concrete type +// and then by concrete value as described in the previous rules. func Sort(mapValue reflect.Value) *SortedMap { if mapValue.Type().Kind() != reflect.Map { return nil diff --git a/tpl/internal/go_templates/fmtsort/sort_test.go b/tpl/internal/go_templates/fmtsort/sort_test.go index a05e8a3c3..064b09107 100644 --- a/tpl/internal/go_templates/fmtsort/sort_test.go +++ b/tpl/internal/go_templates/fmtsort/sort_test.go @@ -147,7 +147,7 @@ func sprint(data any) string { } b.WriteString(sprintKey(key)) b.WriteRune(':') - b.WriteString(fmt.Sprint(om.Value[i])) + fmt.Fprint(b, om.Value[i]) } return b.String() } diff --git a/tpl/internal/go_templates/htmltemplate/clone_test.go b/tpl/internal/go_templates/htmltemplate/clone_test.go index 553f656b5..7db335b5b 100644 --- a/tpl/internal/go_templates/htmltemplate/clone_test.go +++ b/tpl/internal/go_templates/htmltemplate/clone_test.go @@ -8,7 +8,6 @@ package template import ( - "bytes" "errors" "fmt" "io" @@ -26,7 +25,7 @@ func TestAddParseTreeHTML(t *testing.T) { t.Fatal(err) } added := Must(root.AddParseTree("b", tree["b"])) - b := new(bytes.Buffer) + b := new(strings.Builder) err = added.ExecuteTemplate(b, "a", "1>0") if err != nil { t.Fatal(err) @@ -43,7 +42,7 @@ func TestClone(t *testing.T) { // In the t2 template, it will be in a JavaScript context. // In the t3 template, it will be in a CSS context. const tmpl = `{{define "a"}}{{template "lhs"}}{{.}}{{template "rhs"}}{{end}}` - b := new(bytes.Buffer) + b := new(strings.Builder) // Create an incomplete template t0. t0 := Must(New("t0").Parse(tmpl)) diff --git a/tpl/internal/go_templates/htmltemplate/content_test.go b/tpl/internal/go_templates/htmltemplate/content_test.go index 29221a4ad..fac4774cc 100644 --- a/tpl/internal/go_templates/htmltemplate/content_test.go +++ b/tpl/internal/go_templates/htmltemplate/content_test.go @@ -284,7 +284,7 @@ func TestTypedContent(t *testing.T) { []string{ `#ZgotmplZ`, `#ZgotmplZ`, - // Commas are not esacped + // Commas are not escaped. `Hello,#ZgotmplZ`, // Leading spaces are not percent escapes. ` dir=%22ltr%22`, @@ -389,7 +389,7 @@ func TestTypedContent(t *testing.T) { tmpl := Must(New("x").Parse(test.input)) pre := strings.Index(test.input, "{{.}}") post := len(test.input) - (pre + 5) - var b bytes.Buffer + var b strings.Builder for i, x := range data { b.Reset() if err := tmpl.Execute(&b, x); err != nil { @@ -423,7 +423,7 @@ func (s *errorer) Error() string { func TestStringer(t *testing.T) { s := &myStringer{3} - b := new(bytes.Buffer) + b := new(strings.Builder) tmpl := Must(New("x").Parse("{{.}}")) if err := tmpl.Execute(b, s); err != nil { t.Fatal(err) diff --git a/tpl/internal/go_templates/htmltemplate/context.go b/tpl/internal/go_templates/htmltemplate/context.go index c28e08dce..146a95d03 100644 --- a/tpl/internal/go_templates/htmltemplate/context.go +++ b/tpl/internal/go_templates/htmltemplate/context.go @@ -80,7 +80,9 @@ func (c context) mangle(templateName string) string { // HTML5 parsing algorithm because a single token production in the HTML // grammar may contain embedded actions in a template. For instance, the quoted // HTML attribute produced by -// <div title="Hello {{.World}}"> +// +// <div title="Hello {{.World}}"> +// // is a single token in HTML's grammar but in a template spans several nodes. type state uint8 diff --git a/tpl/internal/go_templates/htmltemplate/doc.go b/tpl/internal/go_templates/htmltemplate/doc.go index b6a1504f8..8422b4921 100644 --- a/tpl/internal/go_templates/htmltemplate/doc.go +++ b/tpl/internal/go_templates/htmltemplate/doc.go @@ -12,14 +12,14 @@ The documentation here focuses on the security features of the package. For information about how to program the templates themselves, see the documentation for text/template. -Introduction +# Introduction This package wraps package text/template so you can share its template API to parse and execute HTML templates safely. - tmpl, err := template.New("name").Parse(...) - // Error checking elided - err = tmpl.Execute(out, data) + tmpl, err := template.New("name").Parse(...) + // Error checking elided + err = tmpl.Execute(out, data) If successful, tmpl will now be injection-safe. Otherwise, err is an error defined in the docs for ErrorCode. @@ -34,38 +34,37 @@ provided below. Example - import template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate" - ... - t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) - err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>") + import template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate" + ... + t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) + err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>") produces - Hello, <script>alert('you have been pwned')</script>! + Hello, <script>alert('you have been pwned')</script>! but the contextual autoescaping in html/template - import template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate" - ... - t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) - err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>") + import template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate" + ... + t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) + err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>") produces safe, escaped HTML output - Hello, <script>alert('you have been pwned')</script>! + Hello, <script>alert('you have been pwned')</script>! - -Contexts +# Contexts This package understands HTML, CSS, JavaScript, and URIs. It adds sanitizing functions to each simple action pipeline, so given the excerpt - <a href="/search?q={{.}}">{{.}}</a> + <a href="/search?q={{.}}">{{.}}</a> At parse time each {{.}} is overwritten to add escaping functions as necessary. In this case it becomes - <a href="/search?q={{. | urlescaper | attrescaper}}">{{. | htmlescaper}}</a> + <a href="/search?q={{. | urlescaper | attrescaper}}">{{. | htmlescaper}}</a> where urlescaper, attrescaper, and htmlescaper are aliases for internal escaping functions. @@ -73,117 +72,113 @@ functions. For these internal escaping functions, if an action pipeline evaluates to a nil interface value, it is treated as though it were an empty string. -Namespaced and data- attributes +# Namespaced and data- attributes Attributes with a namespace are treated as if they had no namespace. Given the excerpt - <a my:href="{{.}}"></a> + <a my:href="{{.}}"></a> At parse time the attribute will be treated as if it were just "href". So at parse time the template becomes: - <a my:href="{{. | urlescaper | attrescaper}}"></a> + <a my:href="{{. | urlescaper | attrescaper}}"></a> Similarly to attributes with namespaces, attributes with a "data-" prefix are treated as if they had no "data-" prefix. So given - <a data-href="{{.}}"></a> + <a data-href="{{.}}"></a> At parse time this becomes - <a data-href="{{. | urlescaper | attrescaper}}"></a> + <a data-href="{{. | urlescaper | attrescaper}}"></a> If an attribute has both a namespace and a "data-" prefix, only the namespace will be removed when determining the context. For example - <a my:data-href="{{.}}"></a> + <a my:data-href="{{.}}"></a> This is handled as if "my:data-href" was just "data-href" and not "href" as it would be if the "data-" prefix were to be ignored too. Thus at parse time this becomes just - <a my:data-href="{{. | attrescaper}}"></a> + <a my:data-href="{{. | attrescaper}}"></a> As a special case, attributes with the namespace "xmlns" are always treated as containing URLs. Given the excerpts - <a xmlns:title="{{.}}"></a> - <a xmlns:href="{{.}}"></a> - <a xmlns:onclick="{{.}}"></a> + <a xmlns:title="{{.}}"></a> + <a xmlns:href="{{.}}"></a> + <a xmlns:onclick="{{.}}"></a> At parse time they become: - <a xmlns:title="{{. | urlescaper | attrescaper}}"></a> - <a xmlns:href="{{. | urlescaper | attrescaper}}"></a> - <a xmlns:onclick="{{. | urlescaper | attrescaper}}"></a> + <a xmlns:title="{{. | urlescaper | attrescaper}}"></a> + <a xmlns:href="{{. | urlescaper | attrescaper}}"></a> + <a xmlns:onclick="{{. | urlescaper | attrescaper}}"></a> -Errors +# Errors See the documentation of ErrorCode for details. - -A fuller picture +# A fuller picture The rest of this package comment may be skipped on first reading; it includes details necessary to understand escaping contexts and error messages. Most users will not need to understand these details. - -Contexts +# Contexts Assuming {{.}} is `O'Reilly: How are <i>you</i>?`, the table below shows how {{.}} appears when used in the context to the left. - Context {{.}} After - {{.}} O'Reilly: How are <i>you</i>? - <a title='{{.}}'> O'Reilly: How are you? - <a href="/{{.}}"> O'Reilly: How are %3ci%3eyou%3c/i%3e? - <a href="?q={{.}}"> O'Reilly%3a%20How%20are%3ci%3e...%3f - <a onx='f("{{.}}")'> O\x27Reilly: How are \x3ci\x3eyou...? - <a onx='f({{.}})'> "O\x27Reilly: How are \x3ci\x3eyou...?" - <a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f + Context {{.}} After + {{.}} O'Reilly: How are <i>you</i>? + <a title='{{.}}'> O'Reilly: How are you? + <a href="/{{.}}"> O'Reilly: How are %3ci%3eyou%3c/i%3e? + <a href="?q={{.}}"> O'Reilly%3a%20How%20are%3ci%3e...%3f + <a onx='f("{{.}}")'> O\x27Reilly: How are \x3ci\x3eyou...? + <a onx='f({{.}})'> "O\x27Reilly: How are \x3ci\x3eyou...?" + <a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f If used in an unsafe context, then the value might be filtered out: - Context {{.}} After - <a href="{{.}}"> #ZgotmplZ + Context {{.}} After + <a href="{{.}}"> #ZgotmplZ since "O'Reilly:" is not an allowed protocol like "http:". - If {{.}} is the innocuous word, `left`, then it can appear more widely, - Context {{.}} After - {{.}} left - <a title='{{.}}'> left - <a href='{{.}}'> left - <a href='/{{.}}'> left - <a href='?dir={{.}}'> left - <a style="border-{{.}}: 4px"> left - <a style="align: {{.}}"> left - <a style="background: '{{.}}'> left - <a style="background: url('{{.}}')> left - <style>p.{{.}} {color:red}</style> left + Context {{.}} After + {{.}} left + <a title='{{.}}'> left + <a href='{{.}}'> left + <a href='/{{.}}'> left + <a href='?dir={{.}}'> left + <a style="border-{{.}}: 4px"> left + <a style="align: {{.}}"> left + <a style="background: '{{.}}'> left + <a style="background: url('{{.}}')> left + <style>p.{{.}} {color:red}</style> left Non-string values can be used in JavaScript contexts. If {{.}} is - struct{A,B string}{ "foo", "bar" } + struct{A,B string}{ "foo", "bar" } in the escaped template - <script>var pair = {{.}};</script> + <script>var pair = {{.}};</script> then the template output is - <script>var pair = {"A": "foo", "B": "bar"};</script> + <script>var pair = {"A": "foo", "B": "bar"};</script> See package json to understand how non-string content is marshaled for embedding in JavaScript contexts. - -Typed Strings +# Typed Strings By default, this package assumes that all pipelines produce a plain text string. It adds escaping pipeline stages necessary to correctly and safely embed that @@ -197,24 +192,23 @@ exempted from escaping. The template - Hello, {{.}}! + Hello, {{.}}! can be invoked with - tmpl.Execute(out, template.HTML(`<b>World</b>`)) + tmpl.Execute(out, template.HTML(`<b>World</b>`)) to produce - Hello, <b>World</b>! + Hello, <b>World</b>! instead of the - Hello, <b>World<b>! + Hello, <b>World<b>! that would have been produced if {{.}} was a regular string. - -Security Model +# Security Model https://rawgit.com/mikesamuel/sanitized-jquery-templates/trunk/safetemplate.html#problem_definition defines "safe" as used by this package. diff --git a/tpl/internal/go_templates/htmltemplate/error.go b/tpl/internal/go_templates/htmltemplate/error.go index 21c86a9ef..916b41a82 100644 --- a/tpl/internal/go_templates/htmltemplate/error.go +++ b/tpl/internal/go_templates/htmltemplate/error.go @@ -33,14 +33,17 @@ type ErrorCode int // // Output: "ZgotmplZ" // Example: -// <img src="{{.X}}"> -// where {{.X}} evaluates to `javascript:...` +// +// <img src="{{.X}}"> +// where {{.X}} evaluates to `javascript:...` +// // Discussion: -// "ZgotmplZ" is a special value that indicates that unsafe content reached a -// CSS or URL context at runtime. The output of the example will be -// <img src="#ZgotmplZ"> -// If the data comes from a trusted source, use content types to exempt it -// from filtering: URL(`javascript:...`). +// +// "ZgotmplZ" is a special value that indicates that unsafe content reached a +// CSS or URL context at runtime. The output of the example will be +// <img src="#ZgotmplZ"> +// If the data comes from a trusted source, use content types to exempt it +// from filtering: URL(`javascript:...`). const ( // OK indicates the lack of an error. OK ErrorCode = iota diff --git a/tpl/internal/go_templates/htmltemplate/escape.go b/tpl/internal/go_templates/htmltemplate/escape.go index 488894416..3aac865ef 100644 --- a/tpl/internal/go_templates/htmltemplate/escape.go +++ b/tpl/internal/go_templates/htmltemplate/escape.go @@ -412,13 +412,19 @@ func newIdentCmd(identifier string, pos parse.Pos) *parse.CommandNode { // nudge returns the context that would result from following empty string // transitions from the input context. // For example, parsing: -// `<a href=` +// +// `<a href=` +// // will end in context{stateBeforeValue, attrURL}, but parsing one extra rune: -// `<a href=x` +// +// `<a href=x` +// // will end in context{stateURL, delimSpaceOrTagEnd, ...}. // There are two transitions that happen when the 'x' is seen: // (1) Transition from a before-value state to a start-of-value state without -// consuming any character. +// +// consuming any character. +// // (2) Consume 'x' and transition past the first value character. // In this case, nudging produces the context after (1) happens. func nudge(c context) context { diff --git a/tpl/internal/go_templates/htmltemplate/escape_test.go b/tpl/internal/go_templates/htmltemplate/escape_test.go index adf160b5d..a08ea57ef 100644 --- a/tpl/internal/go_templates/htmltemplate/escape_test.go +++ b/tpl/internal/go_templates/htmltemplate/escape_test.go @@ -693,7 +693,7 @@ func TestEscape(t *testing.T) { t.Errorf("%s: tree not set properly", test.name) continue } - b := new(bytes.Buffer) + b := new(strings.Builder) if err := tmpl.Execute(b, data); err != nil { t.Errorf("%s: template execution failed: %s", test.name, err) continue @@ -740,7 +740,7 @@ func TestEscapeMap(t *testing.T) { }, } { tmpl := Must(New("").Parse(test.input)) - b := new(bytes.Buffer) + b := new(strings.Builder) if err := tmpl.Execute(b, data); err != nil { t.Errorf("%s: template execution failed: %s", test.desc, err) continue @@ -882,7 +882,7 @@ func TestEscapeSet(t *testing.T) { t.Errorf("error parsing %q: %v", source, err) continue } - var b bytes.Buffer + var b strings.Builder if err := tmpl.ExecuteTemplate(&b, "main", data); err != nil { t.Errorf("%q executing %v", err.Error(), tmpl.Lookup("main")) @@ -1833,7 +1833,7 @@ func TestIndirectPrint(t *testing.T) { bp := &b bpp := &bp tmpl := Must(New("t").Parse(`{{.}}`)) - var buf bytes.Buffer + var buf strings.Builder err := tmpl.Execute(&buf, ap) if err != nil { t.Errorf("Unexpected error: %s", err) @@ -1876,7 +1876,7 @@ func TestPipeToMethodIsEscaped(t *testing.T) { t.Errorf("panicked: %v\n", panicValue) } }() - var b bytes.Buffer + var b strings.Builder tmpl.Execute(&b, Issue7379(0)) return b.String() } @@ -1909,7 +1909,7 @@ func TestIdempotentExecute(t *testing.T) { Parse(`{{define "main"}}< |