Skip to content

Commit

Permalink
tpl/internal: Sync go_templates
Browse files Browse the repository at this point in the history
Closes #10411
  • Loading branch information
bep committed Nov 14, 2022
1 parent 58a98c7 commit f6ab955
Show file tree
Hide file tree
Showing 34 changed files with 740 additions and 515 deletions.
2 changes: 1 addition & 1 deletion scripts/fork_go_templates/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
)

func main() {
// The current is built with 41a82aa9c3 text/template/parse: allow space after continue or break
// The current is built with be7068fb0804f661515c678bee9224b90b32869a text/template: correct assignment, not declaration, in range
fmt.Println("Forking ...")
defer fmt.Println("Done ...")

Expand Down
25 changes: 12 additions & 13 deletions tpl/internal/go_templates/fmtsort/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tpl/internal/go_templates/fmtsort/sort_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
5 changes: 2 additions & 3 deletions tpl/internal/go_templates/htmltemplate/clone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
package template

import (
"bytes"
"errors"
"fmt"
"io"
Expand All @@ -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)
Expand All @@ -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))
Expand Down
6 changes: 3 additions & 3 deletions tpl/internal/go_templates/htmltemplate/content_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion tpl/internal/go_templates/htmltemplate/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
132 changes: 63 additions & 69 deletions tpl/internal/go_templates/htmltemplate/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -34,156 +34,151 @@ 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, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!
Hello, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!
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.
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 &lt;i&gt;you&lt;/i&gt;?
<a title='{{.}}'> O&#39;Reilly: How are you?
<a href="/{{.}}"> O&#39;Reilly: How are %3ci%3eyou%3c/i%3e?
<a href="?q={{.}}"> O&#39;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 &lt;i&gt;you&lt;/i&gt;?
<a title='{{.}}'> O&#39;Reilly: How are you?
<a href="/{{.}}"> O&#39;Reilly: How are %3ci%3eyou%3c/i%3e?
<a href="?q={{.}}"> O&#39;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
Expand All @@ -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, &lt;b&gt;World&lt;b&gt;!
Hello, &lt;b&gt;World&lt;b&gt;!
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.
Expand Down
17 changes: 10 additions & 7 deletions tpl/internal/go_templates/htmltemplate/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit f6ab955

Please sign in to comment.