aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/google/safehtml/template
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/google/safehtml/template')
-rw-r--r--vendor/github.com/google/safehtml/template/context.go183
-rw-r--r--vendor/github.com/google/safehtml/template/delim_string.go16
-rw-r--r--vendor/github.com/google/safehtml/template/doc.go291
-rw-r--r--vendor/github.com/google/safehtml/template/error.go280
-rw-r--r--vendor/github.com/google/safehtml/template/escape.go884
-rw-r--r--vendor/github.com/google/safehtml/template/init.go28
-rw-r--r--vendor/github.com/google/safehtml/template/sanitize.go258
-rw-r--r--vendor/github.com/google/safehtml/template/sanitizers.go599
-rw-r--r--vendor/github.com/google/safehtml/template/state_string.go16
-rw-r--r--vendor/github.com/google/safehtml/template/template.go651
-rw-r--r--vendor/github.com/google/safehtml/template/testdata/dir1/parsefiles_t1.tmpl1
-rw-r--r--vendor/github.com/google/safehtml/template/testdata/dir2/parsefiles_t2.tmpl1
-rw-r--r--vendor/github.com/google/safehtml/template/testdata/glob_t0.tmpl1
-rw-r--r--vendor/github.com/google/safehtml/template/testdata/glob_t1.tmpl1
-rw-r--r--vendor/github.com/google/safehtml/template/testdata/glob_t2.tmpl1
-rw-r--r--vendor/github.com/google/safehtml/template/testdata/helpers_t1.tmpl1
-rw-r--r--vendor/github.com/google/safehtml/template/testdata/helpers_t2.tmpl1
-rw-r--r--vendor/github.com/google/safehtml/template/testdata/share_t0.tmpl1
-rw-r--r--vendor/github.com/google/safehtml/template/testdata/share_t1.tmpl1
-rw-r--r--vendor/github.com/google/safehtml/template/transition.go312
-rw-r--r--vendor/github.com/google/safehtml/template/trustedfs.go98
-rw-r--r--vendor/github.com/google/safehtml/template/trustedsource.go105
-rw-r--r--vendor/github.com/google/safehtml/template/trustedtemplate.go36
-rw-r--r--vendor/github.com/google/safehtml/template/url.go122
24 files changed, 3888 insertions, 0 deletions
diff --git a/vendor/github.com/google/safehtml/template/context.go b/vendor/github.com/google/safehtml/template/context.go
new file mode 100644
index 000000000..dd7886dc6
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/context.go
@@ -0,0 +1,183 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+ "strings"
+)
+
+// context describes the state an HTML parser must be in when it reaches the
+// portion of HTML produced by evaluating a particular template node.
+//
+// The zero value of type Context is the start context for a template that
+// produces an HTML fragment as defined at
+// http://www.w3.org/TR/html5/syntax.html#the-end
+// where the context element is null.
+type context struct {
+ state state
+ delim delim
+ element element
+ attr attr
+ err *Error
+ // scriptType is the lowercase value of the "type" attribute inside the current "script"
+ // element (see https://dev.w3.org/html5/spec-preview/the-script-element.html#attr-script-type).
+ // This field will be empty if the parser is currently not in a script element,
+ // the type attribute has not already been parsed in the current element, or if the
+ // value of the type attribute cannot be determined at parse time.
+ scriptType string
+ // linkRel is the value of the "rel" attribute inside the current "link"
+ // element (see https://html.spec.whatwg.org/multipage/semantics.html#attr-link-rel).
+ // This value has been normalized to lowercase with exactly one space between tokens
+ // and exactly one space at start and end, so that a lookup of any token foo can
+ // be performed by searching for the substring " foo ".
+ // This field will be empty if the parser is currently not in a link element,
+ // the rel attribute has not already been parsed in the current element, or if the
+ // value of the rel attribute cannot be determined at parse time.
+ linkRel string
+}
+
+// eq returns whether Context c is equal to Context d.
+func (c context) eq(d context) bool {
+ return c.state == d.state &&
+ c.delim == d.delim &&
+ c.element.eq(d.element) &&
+ c.attr.eq(d.attr) &&
+ c.err == d.err &&
+ c.scriptType == d.scriptType &&
+ c.linkRel == d.linkRel
+}
+
+// state describes a high-level HTML parser state.
+//
+// It bounds the top of the element stack, and by extension the HTML insertion
+// mode, but also contains state that does not correspond to anything in the
+// 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}}">
+// is a single token in HTML's grammar but in a template spans several nodes.
+type state uint8
+
+//go:generate stringer -type state
+
+const (
+ // stateText is parsed character data. An HTML parser is in
+ // this state when its parse position is outside an HTML tag,
+ // directive, comment, and special element body.
+ stateText state = iota
+ // stateSpecialElementBody occurs inside a specal HTML element body.
+ stateSpecialElementBody
+ // stateTag occurs before an HTML attribute or the end of a tag.
+ stateTag
+ // stateAttrName occurs inside an attribute name.
+ // It occurs between the ^'s in ` ^name^ = value`.
+ stateAttrName
+ // stateAfterName occurs after an attr name has ended but before any
+ // equals sign. It occurs between the ^'s in ` name^ ^= value`.
+ stateAfterName
+ // stateBeforeValue occurs after the equals sign but before the value.
+ // It occurs between the ^'s in ` name =^ ^value`.
+ stateBeforeValue
+ // stateHTMLCmt occurs inside an <!-- HTML comment -->.
+ stateHTMLCmt
+ // stateAttr occurs inside an HTML attribute whose content is text.
+ stateAttr
+ // stateError is an infectious error state outside any valid
+ // HTML/CSS/JS construct.
+ stateError
+)
+
+// isComment reports whether a state contains content meant for template
+// authors & maintainers, not for end-users or machines.
+func isComment(s state) bool {
+ switch s {
+ case stateHTMLCmt:
+ return true
+ }
+ return false
+}
+
+// isInTag reports whether s occurs solely inside an HTML tag.
+func isInTag(s state) bool {
+ switch s {
+ case stateTag, stateAttrName, stateAfterName, stateBeforeValue, stateAttr:
+ return true
+ }
+ return false
+}
+
+// delim is the delimiter that will end the current HTML attribute.
+type delim uint8
+
+//go:generate stringer -type delim
+
+const (
+ // delimNone occurs outside any attribute.
+ delimNone delim = iota
+ // delimDoubleQuote occurs when a double quote (") closes the attribute.
+ delimDoubleQuote
+ // delimSingleQuote occurs when a single quote (') closes the attribute.
+ delimSingleQuote
+ // delimSpaceOrTagEnd occurs when a space or right angle bracket (>)
+ // closes the attribute.
+ delimSpaceOrTagEnd
+)
+
+type element struct {
+ // name is the lowercase name of the element. If context joining has occurred, name
+ // will be arbitrarily assigned the element name from one of the joined contexts.
+ name string
+ // names contains all possible names the element could assume because of context joining.
+ // For example, after joining the contexts in the "if" and "else" branches of
+ // {{if .C}}<img{{else}}<audio{{end}} src="/some/path">`,
+ // names will contain "img" and "audio".
+ // names can also contain empty strings, which represent joined contexts with no element name.
+ // names will be empty if no context joining occurred.
+ names []string
+}
+
+// eq reports whether a and b have the same name. All other fields are ignored.
+func (e element) eq(d element) bool {
+ return e.name == d.name
+}
+
+// String returns the string representation of the element.
+func (e element) String() string {
+ return "element" + strings.Title(e.name)
+}
+
+// attr represents the attribute that the parser is in, that is,
+// starting from stateAttrName until stateTag/stateText (exclusive).
+type attr struct {
+ // name is the lowercase name of the attribute. If context joining has occurred, name
+ // will be arbitrarily assigned the attribute name from one of the joined contexts.
+ name string
+ // value is the value of the attribute. If context joining has occurred, value
+ // will be arbitrarily assigned the attribute value from one of the joined contexts.
+ // If there are multiple actions in the attribute value, value will contain the
+ // concatenation of all values seen so far. For example, in
+ // <a name="foo{{.X}}bar{{.Y}}">
+ // value is "foo" at "{{.X}}" and "foobar" at "{{.Y}}".
+ value string
+ // ambiguousValue indicates whether value contains an ambiguous value due to context-joining.
+ ambiguousValue bool
+ // names contains all possible names the attribute could assume because of context joining.
+ // For example, after joining the contexts in the "if" and "else" branches of
+ // <a {{if .C}}title{{else}}name{{end}}="foo">
+ // names will contain "title" and "name".
+ // names can also contain empty strings, which represent joined contexts with no attribute name.
+ // names will be empty if no context joining occurred.
+ names []string
+}
+
+// eq reports whether a and b have the same name. All other fields are ignored.
+func (a attr) eq(b attr) bool {
+ return a.name == b.name
+}
+
+// String returns the string representation of the attr.
+func (a attr) String() string {
+ return "attr" + strings.Title(a.name)
+}
diff --git a/vendor/github.com/google/safehtml/template/delim_string.go b/vendor/github.com/google/safehtml/template/delim_string.go
new file mode 100644
index 000000000..0ef2c2510
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/delim_string.go
@@ -0,0 +1,16 @@
+// Code generated by "stringer -type Delim"; DO NOT EDIT
+
+package template
+
+import "fmt"
+
+const _Delim_name = "DelimNoneDelimDoubleQuoteDelimSingleQuoteDelimSpaceOrTagEnd"
+
+var _Delim_index = [...]uint8{0, 9, 25, 41, 59}
+
+func (i delim) String() string {
+ if i >= delim(len(_Delim_index)-1) {
+ return fmt.Sprintf("delim(%d)", i)
+ }
+ return _Delim_name[_Delim_index[i]:_Delim_index[i+1]]
+}
diff --git a/vendor/github.com/google/safehtml/template/doc.go b/vendor/github.com/google/safehtml/template/doc.go
new file mode 100644
index 000000000..fab552b25
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/doc.go
@@ -0,0 +1,291 @@
+// Copyright (c) 2017 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+/*
+Package template (safehtml/template) implements data-driven templates for
+generating HTML output safe against code injection. It provides an interface
+similar to that of package html/template, but produces HTML output that is more
+secure. Therefore, it should be used instead of html/template to render HTML.
+
+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.
+
+
+Basic usage
+
+This package provides an API almost identical to that of text/template and
+html/template to parse and execute HTML templates safely.
+
+ tmpl := template.Must(template.New("name").Parse(`<div>Hello {{.}}</div>`))
+ err := tmpl.Execute(out, data)
+
+If successful, out will contain code-injection-safe HTML. Otherwise, err's
+string representation will describe the error that occurred.
+
+Elements of data might be modified at run time before being included in out, or
+rejected completely if such a conversion is not possible. Pass values of
+appropriate types from package safehtml to ensure that they are included in the
+template's HTML output in their expected form. More details are provided below
+in "Contextual autosanitization" and "Sanitization contexts".
+
+
+Security improvements
+
+safehtml/template produces HTML more resistant to code injection than
+html/template because it:
+ * Allows values of types only from package safehtml to bypass run-time
+ sanitization. These types represent values that are known---by construction
+ or by run-time sanitization---to be safe for use in various HTML contexts
+ without being processed by certain sanitization functions.
+ * Does not attempt to escape CSS or JavaScript. Instead of attempting to
+ parse and escape these complex languages, safehtml/template allows values
+ of only the appropriate types from package safehtml (e.g. safehtml.Style,
+ safehtml.Script) to be used in these contexts, since they are already
+ guaranteed to be safe.
+ * Emits an error if user data is interpolated in unsafe contexts, such as
+ within disallowed elements or unquoted attribute values.
+ * Only loads templates from trusted sources. This ensures that the contents
+ of the template are always under programmer control. More details are
+ provided below in "Trusted template sources".
+ * Differentiates between URLs that load code and those that do not. URLs in
+ the former category must be supplied to the template as values of type
+ safehtml.TrustedResourceURL, whose type contract promises that the URL
+ identifies a trustworthy resource. URLs in the latter category can be
+ sanitized at run time.
+
+
+Threat model
+
+safehtml/template assumes that programmers are trustworthy. Therefore, data
+fully under programmer control, such as string literals, are considered safe.
+The types from package safehtml are designed around this same assumption, so
+their type contracts are trusted by this package.
+
+safehtml/template considers all other data values untrustworthy and
+conservatively assumes that such values could result in a code-injection
+vulnerability if included verbatim in HTML.
+
+
+Trusted template sources
+
+safehtml/template loads templates only from trusted sources. Therefore, template
+text, file paths, and file patterns passed to Parse* functions and methods must
+be entirely under programmer control.
+
+This constraint is enforced by using unexported string types for the parameters
+of Parse* functions and methods, such as trustedFilePattern for ParseGlob.
+The only values that may be assigned to these types (and thus provided as
+arguments) are untyped string constants such as string literals, which are
+always under programmer control.
+
+
+Contextual autosanitization
+
+Code injection vulnerabilities, such as cross-site scripting (XSS), occur when
+untrusted data values are embedded in a HTML document. For example,
+
+ import "text/template"
+ ...
+ var t = template.Must(template.New("foo").Parse(`<a href="{{ .X }}">{{ .Y }}</a>`))
+ func renderHTML(x, y string) string {
+ var out bytes.Buffer
+ err := t.Execute(&out, struct{ X, Y string }{x, y})
+ // Error checking elided
+ return out.String()
+ }
+
+If x and y originate from user-provided data, an attacker who controls these
+strings could arrange for them to contain the following values:
+
+ x = "javascript:evil()"
+ y = "</a><script>alert('pwned')</script><a>"
+
+which will cause renderHTML to return the following unsafe HTML:
+
+ <a href="javascript:evil()"></a><script>alert('pwned')</script><a></a>
+
+To prevent such vulnerabilities, untrusted data must be sanitized before being
+included in HTML. A sanitization function takes untrusted data and returns a
+string that will not create a code-injection vulnerability in the destination
+context. The function might return the input unchanged if it deems it safe,
+escape special runes in the input's string representation to prevent them from
+triggering undesired state changes in the HTML parser, or entirely replace the
+input by an innocuous string (also known as "filtering"). If none of these
+conversions are possible, the sanitization function aborts template processing.
+
+safehtml/template contextually autosanitizes untrusted data by adding
+appropriate sanitization functions to template actions to ensure that the
+action output is safe to include in the HTML context in which the action
+appears. For example, in
+
+ import "safehtml/template"
+ ...
+ var t = template.Must(template.New("foo").Parse(`<a href="{{ .X }}">{{ .Y }}</a>`))
+ func renderHTML(x, y string) string {
+ var out bytes.Buffer
+ err := t.Execute(&out, struct{ X, Y string }{x, y})
+ // Error checking elided
+ return out.String()
+ }
+
+the contextual autosanitizer rewrites the template to
+
+ <a href="{{ .X | _sanitizeTrustedResourceURLOrURL | _sanitizeHTML }}">{{ .Y | _sanitizeHTML }}</a>
+
+so that the template produces the following safe, sanitized HTML output (split
+across multiple lines for clarity):
+
+ <a href="about:invalid#zGoSafez">
+ &lt;/a&gt;&lt;script&gt;alert(&#39;pwned&#39;)&lt;/script&gt;&lt;a&gt;
+ </a>
+
+Similar template systems such as html/template, Soy, and Angular, refer to this
+functionality as "contextual autoescaping". safehtml/template uses the term
+"autosanitization" instead of "autoescaping" since "sanitization" broadly
+captures the operations of escaping and filtering.
+
+
+Sanitization contexts
+
+The types of sanitization functions inserted into an action depend on the
+action's sanitization context, which is determined by its surrounding text.
+The following table describes these sanitization contexts.
+
+ +--------------------+----------------------------------+------------------------------+-----------------------+
+ | Context | Examples | Safe types | Run-time sanitizer |
+ |--------------------+----------------------------------+------------------------------+-----------------------+
+ | HTMLContent | Hello {{.}} | safehtml.HTML | safehtml.HTMLEscaped |
+ | | <title>{{.}}</title> | | |
+ +--------------------------------------------------------------------------------------------------------------+
+ | HTMLValOnly | <iframe srcdoc="{{.}}"></iframe> | safehtml.HTML* | N/A |
+ +--------------------------------------------------------------------------------------------------------------+
+ | URL | <q cite="{{.}}">Cite</q> | safehtml.URL | safehtml.URLSanitized |
+ +--------------------------------------------------------------------------------------------------------------+
+ | URL or | <a href="{{.}}">Link</a> | safehtml.URL | safehtml.URLSanitized |
+ | TrustedResourceURL | | safehtml.TrustedResourceURL | |
+ +--------------------------------------------------------------------------------------------------------------+
+ | TrustedResourceURL | <script src="{{.}}"></script> | safehtml.TrustedResourceURL† | N/A |
+ +--------------------------------------------------------------------------------------------------------------+
+ | Script | <script>{{.}}</script> | safehtml.Script* | N/A |
+ +--------------------------------------------------------------------------------------------------------------+
+ | Style | <p style="{{.}}">Paragraph</p> | safehtml.Style* | N/A |
+ +--------------------------------------------------------------------------------------------------------------+
+ | Stylesheet | <style>{{.}}</style> | safehtml.StyleSheet* | N/A |
+ +--------------------------------------------------------------------------------------------------------------+
+ | Identifier | <h1 id="{{.}}">Hello</h1> | safehtml.Identifier* | N/A |
+ +--------------------------------------------------------------------------------------------------------------+
+ | Enumerated value | <a target="{{.}}">Link</a> | Allowed string values | N/A |
+ | | | ("_self" or "_blank" for | |
+ | | | the given example) | |
+ +--------------------------------------------------------------------------------------------------------------+
+ | None | <h1 class="{{.}}">Hello</h1> | N/A (any type allowed) | N/A (any type |
+ | | | | allowed) |
+ +--------------------+----------------------------------+------------------------------+-----------------------+
+ *: Values only of this type are allowed in this context. Other values will trigger a run-time error.
+ †: If the action is a prefix of the attribute value, values only of this type are allowed.
+ Otherwise, values of any type are allowed. See "Substitutions in URLs" for more details.
+
+For each context, the function named in "Run-time sanitizer" is called to
+sanitize the output of the action. However, if the action outputs a value of
+any of the types listed in "Safe types", the run-time sanitizer is not called.
+For example, in
+
+ <title>{{ .X }}</title>
+
+if X is a string value, a HTML sanitizer that calls safehtml.HTMLEscaped will be
+added to the action to sanitize X.
+
+ // _sanitizeHTML calls safehtml.HTMLEscaped.
+ <title>{{ .X | _sanitizeHTML }}</title>
+
+However, if X is a safehtml.HTML value, _sanitizeHTML will not change its
+value, since safehtml.HTML values are already safe to use in HTML contexts.
+Therefore, the string contents of X will bypass context-specific
+sanitization (in this case, HTML escaping) and appear unchanged in the
+template's HTML output. Note that in attribute value contexts, HTML escaping
+will always take place, whether or not context-specific sanitization is
+performed. More details can be found at the end of this section.
+
+In certain contexts, the autosanitizer allows values only of that context's
+"Safe types". Any other values will trigger an error and abort template
+processing. For example, the template
+
+ <style>{{ .X }}</style>
+
+triggers a run-time error if X is not a safehtml.StyleSheet. Otherwise, the
+string form of X will appear unchanged in the output. The only exception to
+this behavior is in TrustedResourceURL sanitization contexts, where actions may
+output data of any type if the action occurs after a safe attribute value prefix.
+More details can be found below in "Substitutions in URLs".
+
+
+Unconditional sanitization
+
+In attribute value contexts, action outputs are always HTML-escaped after
+context-specific sanitization to ensure that the attribute values cannot change
+change the structure of the surrounding HTML tag. In URL or TrustedResourceURL
+sanitization contexts, action outputs are additionally URL-normalized to reduce
+the likelihood of downstream URL-parsing bugs. For example, the template
+
+ <a href="{{ .X }}">Link</a>
+ <p id="{{ .Y }}">Text</p>
+
+is rewritten by the autosanitizer into
+
+ // _sanitizeHTML calls safehtml.HTMLEscaped.
+ <a href="{{ .X | _sanitizeTrustedResourceURLOrURL | _normalizeURL | _sanitizeHTML }}">Link</a>
+ <p id="{{ .Y | _sanitizeIdentifier | _sanitizeHTML }}">Text</p>
+
+Even if X is a safehtml.URL or safehtml.TrustedResourceURL value, which
+remains unchanged after _sanitizeTrustedResourceURLOrURL, X will still be
+URL-normalized and HTML-escaped. Likewise, Y will still be HTML-escaped even if
+its string form is left unchanged by _sanitizeIdentifier.
+
+
+Substitutions in URLs
+
+Values of any type may be substituted into attribute values in URL and
+TrustedResourceURL sanitization contexts only if the action is preceded by a
+safe URL prefix. For example, in
+
+ <q cite="http://www.foo.com/{{ .PathComponent }}">foo</q>
+
+Since "http://www.foo.com/" is a safe URL prefix, PathComponent can safely be
+interpolated into this URL sanitization context after URL normalization.
+Similarly, in
+
+ <script src="https://www.bar.com/{{ .PathComponent }}"></script>
+
+Since "https://www.bar.com/" is a safe TrustedResourceURL prefix, PathComponent
+can safely be interpolated into this TrustedResourceURL sanitization context
+after URL escaping. Substitutions after a safe TrustedResourceURL prefix are
+escaped instead of normalized to prevent the injection of any new URL
+components, including additional path components. URL escaping also takes place
+in URL sanitization contexts where the substitutions occur in the query or
+fragment part of the URL, such as in:
+
+ <a href="/foo?q={{ .Query }}&hl={{ .LangCode }}">Link</a>
+
+A URL prefix is considered safe in a URL sanitization context if it does
+not end in an incomplete HTML character reference (e.g. https&#1) or incomplete
+percent-encoding character triplet (e.g. /fo%6), does not contain whitespace or control
+characters, and one of the following is true:
+ * The prefix has a safe scheme (i.e. http, https, mailto, or ftp).
+ * The prefix has the data scheme with base64 encoding and an allowed audio, image,
+ or video MIME type (e.g. data:img/jpeg;base64, data:video/mp4;base64).
+ * The prefix has no scheme at all, and cannot be interpreted as a scheme prefix (e.g. /path).
+
+A URL prefix is considered safe in a TrustedResourceURL sanitization context if it does
+not end in an incomplete HTML character reference (e.g. https&#1) or incomplete
+percent-encoding character triplet (e.g. /fo%6), does not contain white space or control
+characters, and one of the following is true:
+ * The prefix has the https scheme and contains a domain name (e.g. https://www.foo.com).
+ * The prefix is scheme-relative and contains a domain name (e.g. //www.foo.com/).
+ * The prefix is path-absolute and contains a path (e.g. /path).
+ * The prefix is "about:blank".
+*/
+package template
diff --git a/vendor/github.com/google/safehtml/template/error.go b/vendor/github.com/google/safehtml/template/error.go
new file mode 100644
index 000000000..fe7821433
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/error.go
@@ -0,0 +1,280 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+ "fmt"
+ "text/template/parse"
+)
+
+// Error describes a problem encountered during template Escaping.
+type Error struct {
+ // ErrorCode describes the kind of error.
+ ErrorCode ErrorCode
+ // Node is the node that caused the problem, if known.
+ // If not nil, it overrides Name and Line.
+ Node parse.Node
+ // Name is the name of the template in which the error was encountered.
+ Name string
+ // Line is the line number of the error in the template source or 0.
+ Line int
+ // Description is a human-readable description of the problem.
+ Description string
+}
+
+// ErrorCode is a code for a kind of error.
+type ErrorCode int
+
+// We define codes for each error that manifests while escaping templates, but
+// escaped templates may also fail at runtime.
+//
+// Output: "ZgotmplZ"
+// Example:
+// <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:...`).
+const (
+ // OK indicates the lack of an error.
+ OK ErrorCode = iota
+
+ // ErrAmbigContext: "... appears in an ambiguous context within a URL"
+ // Example:
+ // <a href="
+ // {{if .C}}
+ // /path/
+ // {{else}}
+ // /search?q=
+ // {{end}}
+ // {{.X}}
+ // ">
+ // Discussion:
+ // {{.X}} is in an ambiguous URL context since, depending on {{.C}},
+ // it may be either a URL suffix or a query parameter.
+ // Moving {{.X}} into the condition removes the ambiguity:
+ // <a href="{{if .C}}/path/{{.X}}{{else}}/search?q={{.X}}">
+ ErrAmbigContext
+
+ // ErrBadHTML: "expected space, attr name, or end of tag, but got ...",
+ // "... in unquoted attr", "... in attribute name"
+ // Example:
+ // <a href = /search?q=foo>
+ // <href=foo>
+ // <form na<e=...>
+ // <option selected<
+ // Discussion:
+ // This is often due to a typo in an HTML element, but some runes
+ // are banned in tag names, attribute names, and unquoted attribute
+ // values because they can tickle parser ambiguities.
+ // Quoting all attributes is the best policy.
+ ErrBadHTML
+
+ // ErrBranchEnd: "{{if}} branches end in different contexts"
+ // Example:
+ // {{if .C}}<a href="{{end}}{{.X}}
+ // Discussion:
+ // Package html/template statically examines each path through an
+ // {{if}}, {{range}}, or {{with}} to escape any following pipelines.
+ // The example is ambiguous since {{.X}} might be an HTML text node,
+ // or a URL prefix in an HTML attribute. The context of {{.X}} is
+ // used to figure out how to escape it, but that context depends on
+ // the run-time value of {{.C}} which is not statically known.
+ //
+ // The problem is usually something like missing quotes or angle
+ // brackets, or can be avoided by refactoring to put the two contexts
+ // into different branches of an if, range or with. If the problem
+ // is in a {{range}} over a collection that should never be empty,
+ // adding a dummy {{else}} can help.
+ ErrBranchEnd
+
+ // ErrEndContext: "... ends in a non-text context: ..."
+ // Examples:
+ // <div
+ // <div title="no close quote>
+ // <script>f()
+ // Discussion:
+ // Executed templates should produce a DocumentFragment of HTML.
+ // Templates that end without closing tags will trigger this error.
+ // Templates that should not be used in an HTML context or that
+ // produce incomplete Fragments should not be executed directly.
+ //
+ // {{define "main"}} <script>{{template "helper"}}</script> {{end}}
+ // {{define "helper"}} document.write(' <div title=" ') {{end}}
+ //
+ // "helper" does not produce a valid document fragment, so should
+ // not be Executed directly.
+ ErrEndContext
+
+ // ErrNoSuchTemplate: "no such template ..."
+ // Examples:
+ // {{define "main"}}<div {{template "attrs"}}>{{end}}
+ // {{define "attrs"}}href="{{.URL}}"{{end}}
+ // Discussion:
+ // Package html/template looks through template calls to compute the
+ // context.
+ // Here the {{.URL}} in "attrs" must be treated as a URL when called
+ // from "main", but you will get this error if "attrs" is not defined
+ // when "main" is parsed.
+ ErrNoSuchTemplate
+
+ // ErrOutputContext: "cannot compute output context for template ..."
+ // Examples:
+ // {{define "t"}}{{if .T}}{{template "t" .T}}{{end}}{{.H}}",{{end}}
+ // Discussion:
+ // A recursive template does not end in the same context in which it
+ // starts, and a reliable output context cannot be computed.
+ // Look for typos in the named template.
+ // If the template should not be called in the named start context,
+ // look for calls to that template in unexpected contexts.
+ // Maybe refactor recursive templates to not be recursive.
+ ErrOutputContext
+
+ // ErrPartialCharset: "unfinished JS regexp charset in ..."
+ // Example:
+ // <script>var pattern = /foo[{{.Chars}}]/</script>
+ // Discussion:
+ // Package html/template does not support interpolation into regular
+ // expression literal character sets.
+ ErrPartialCharset
+
+ // ErrPartialEscape: "unfinished escape sequence in ..."
+ // Example:
+ // <script>alert("\{{.X}}")</script>
+ // Discussion:
+ // Package html/template does not support actions following a
+ // backslash.
+ // This is usually an error and there are better solutions; for
+ // example
+ // <script>alert("{{.X}}")</script>
+ // should work, and if {{.X}} is a partial escape sequence such as
+ // "xA0", mark the whole sequence as safe content: JSStr(`\xA0`)
+ ErrPartialEscape
+
+ // ErrRangeLoopReentry: "on range loop re-entry: ..."
+ // Example:
+ // <script>var x = [{{range .}}'{{.}},{{end}}]</script>
+ // Discussion:
+ // If an iteration through a range would cause it to end in a
+ // different context than an earlier pass, there is no single context.
+ // In the example, there is missing a quote, so it is not clear
+ // whether {{.}} is meant to be inside a JS string or in a JS value
+ // context. The second iteration would produce something like
+ //
+ // <script>var x = ['firstValue,'secondValue]</script>
+ ErrRangeLoopReentry
+
+ // ErrSlashAmbig: '/' could start a division or regexp.
+ // Example:
+ // <script>
+ // {{if .C}}var x = 1{{end}}
+ // /-{{.N}}/i.test(x) ? doThis : doThat();
+ // </script>
+ // Discussion:
+ // The example above could produce `var x = 1/-2/i.test(s)...`
+ // in which the first '/' is a mathematical division operator or it
+ // could produce `/-2/i.test(s)` in which the first '/' starts a
+ // regexp literal.
+ // Look for missing semicolons inside branches, and maybe add
+ // parentheses to make it clear which interpretation you intend.
+ ErrSlashAmbig
+
+ // ErrPredefinedEscaper: "predefined escaper ... disallowed in template"
+ // Example:
+ // <div class={{. | html}}>Hello<div>
+ // Discussion:
+ // Package html/template already contextually escapes all pipelines to
+ // produce HTML output safe against code injection. Manually escaping
+ // pipeline output using the predefined escapers "html" or "urlquery" is
+ // unnecessary, and may affect the correctness or safety of the escaped
+ // pipeline output in Go 1.8 and earlier.
+ //
+ // In most cases, such as the given example, this error can be resolved by
+ // simply removing the predefined escaper from the pipeline and letting the
+ // contextual autoescaper handle the escaping of the pipeline. In other
+ // instances, where the predefined escaper occurs in the middle of a
+ // pipeline where subsequent commands expect escaped input, e.g.
+ // {{.X | html | makeALink}}
+ // where makeALink does
+ // return `<a href="`+input+`">link</a>`
+ // consider refactoring the surrounding template to make use of the
+ // contextual autoescaper, i.e.
+ // <a href="{{.X}}">link</a>
+ //
+ // To ease migration to Go 1.9 and beyond, "html" and "urlquery" will
+ // continue to be allowed as the last command in a pipeline. However, if the
+ // pipeline occurs in an unquoted attribute value context, "html" is
+ // disallowed. Avoid using "html" and "urlquery" entirely in new templates.
+ ErrPredefinedEscaper
+
+ // ErrEscapeAction: "cannot escape action ..."
+ // Discussion:
+ // Error returned while escaping an action using EscaperForContext.
+ // Refer to error message for more details.
+ // TODO: remove this error type and replace it with more informative sanitization errors.
+ ErrEscapeAction
+
+ // ErrCSPCompatibility: `"javascript:" URI disallowed for CSP compatibility`,
+ // "inline event handler ... is disallowed for CSP compatibility
+ // Examples:
+ // <span onclick="doThings();">A thing.</span>
+ // <a href="javascript:linkClicked()">foo</a>
+ // Discussion:
+ // Inline event handlers (onclick="...", onerror="...") and
+ // <a href="javascript:..."> links can be used to run scripts,
+ // so an attacker who finds an XSS bug could inject such HTML
+ // and execute malicious JavaScript. These patterns must be
+ // refactored into safer alternatives for compatibility with
+ // Content Security Policy (CSP).
+ //
+ // For example, the following HTML that contains an inline event handler:
+ // <script> function doThings() { ... } </script>
+ // <span onclick="doThings();">A thing.</span>
+ // can be refactored into:
+ // <span id="things">A thing.</span>
+ // <script nonce="${nonce}">
+ // document.addEventListener('DOMContentLoaded', function () {
+ // document.getElementById('things')
+ // .addEventListener('click', function doThings() { ... });
+ // });
+ // </script>
+ //
+ // Likewise, the following HTML containng a javascript: URI:
+ // <a href="javascript:linkClicked()">foo</a>
+ // can be refactored into:
+ // <a id="foo">foo</a>
+ // <script nonce="${nonce}">
+ // document.addEventListener('DOMContentLoaded', function () {
+ // document.getElementById('foo')
+ // .addEventListener('click', linkClicked);
+ // });
+ // </script>
+ ErrCSPCompatibility
+ // All JS templates inside script literals have to be balanced; otherwise a concatenation such as
+ // <script>alert(`x{{.data}}`</script> can contain XSS if data contains user-controlled escaped strings (e.g. as JSON).
+ ErrUnbalancedJsTemplate
+)
+
+func (e *Error) Error() string {
+ switch {
+ case e.Node != nil:
+ loc, _ := (*parse.Tree)(nil).ErrorContext(e.Node)
+ return fmt.Sprintf("html/template:%s: %s", loc, e.Description)
+ case e.Line != 0:
+ return fmt.Sprintf("html/template:%s:%d: %s", e.Name, e.Line, e.Description)
+ case e.Name != "":
+ return fmt.Sprintf("html/template:%s: %s", e.Name, e.Description)
+ }
+ return "html/template: " + e.Description
+}
+
+// errorf creates an error given a format string f and args.
+// The template Name still needs to be supplied.
+func errorf(k ErrorCode, node parse.Node, line int, f string, args ...interface{}) *Error {
+ return &Error{k, node, "", line, fmt.Sprintf(f, args...)}
+}
diff --git a/vendor/github.com/google/safehtml/template/escape.go b/vendor/github.com/google/safehtml/template/escape.go
new file mode 100644
index 000000000..8a9d53dd5
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/escape.go
@@ -0,0 +1,884 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+ "bytes"
+ "fmt"
+ "html"
+ "reflect"
+ "strings"
+ "text/template"
+ "text/template/parse"
+)
+
+// TODO: remove all unused escaping logic inherited from html/template.
+// TODO: replace "escape" with "sanitize" in file names and contents to maintain consistency with safehtml/template docs.
+
+// escapeTemplate rewrites the named template, which must be
+// associated with t, to guarantee that the output of any of the named
+// templates is properly escaped. If no error is returned, then the named templates have
+// been modified. Otherwise the named templates have been rendered
+// unusable.
+func escapeTemplate(tmpl *Template, node parse.Node, name string) error {
+ c, _ := tmpl.esc.escapeTree(context{}, node, name, 0)
+ var err error
+ if c.err != nil {
+ err, c.err.Name = c.err, name
+ } else if c.state != stateText {
+ err = &Error{ErrEndContext, nil, name, 0, fmt.Sprintf("ends in a non-text context: %+v", c)}
+ }
+ if err != nil {
+ // Prevent execution of unsafe templates.
+ if t := tmpl.set[name]; t != nil {
+ t.escapeErr = err
+ t.text.Tree = nil
+ t.Tree = nil
+ }
+ return err
+ }
+ tmpl.esc.commit()
+ if t := tmpl.set[name]; t != nil {
+ t.escapeErr = errEscapeOK
+ t.Tree = t.text.Tree
+ }
+ return nil
+}
+
+// evalArgs formats the list of arguments into a string. It is equivalent to
+// fmt.Sprint(args...), except that it deferences all pointers.
+func evalArgs(args ...interface{}) string {
+ // Optimization for simple common case of a single string argument.
+ if len(args) == 1 {
+ if s, ok := args[0].(string); ok {
+ return s
+ }
+ }
+ for i, arg := range args {
+ args[i] = indirectToStringerOrError(arg)
+ }
+ return fmt.Sprint(args...)
+}
+
+// escaper collects type inferences about templates and changes needed to make
+// templates injection safe.
+type escaper struct {
+ // ns is the nameSpace that this escaper is associated with.
+ ns *nameSpace
+ // output[templateName] is the output context for a templateName that
+ // has been mangled to include its input context.
+ output map[string]context
+ // derived[c.mangle(name)] maps to a template derived from the template
+ // named name templateName for the start context c.
+ derived map[string]*template.Template
+ // called[templateName] is a set of called mangled template names.
+ called map[string]bool
+ // xxxNodeEdits are the accumulated edits to apply during commit.
+ // Such edits are not applied immediately in case a template set
+ // executes a given template in different escaping contexts.
+ actionNodeEdits map[*parse.ActionNode][]string
+ templateNodeEdits map[*parse.TemplateNode]string
+ textNodeEdits map[*parse.TextNode][]byte
+}
+
+// makeEscaper creates a blank escaper for the given set.
+func makeEscaper(n *nameSpace) escaper {
+ return escaper{
+ n,
+ map[string]context{},
+ map[string]*template.Template{},
+ map[string]bool{},
+ map[*parse.ActionNode][]string{},
+ map[*parse.TemplateNode]string{},
+ map[*parse.TextNode][]byte{},
+ }
+}
+
+// escape escapes a template node.
+func (e *escaper) escape(c context, n parse.Node) context {
+ switch n := n.(type) {
+ case *parse.ActionNode:
+ return e.escapeAction(c, n)
+ case *parse.IfNode:
+ return e.escapeBranch(c, &n.BranchNode, "if")
+ case *parse.ListNode:
+ return e.escapeList(c, n)
+ case *parse.RangeNode:
+ return e.escapeBranch(c, &n.BranchNode, "range")
+ case *parse.TemplateNode:
+ return e.escapeTemplate(c, n)
+ case *parse.TextNode:
+ return e.escapeText(c, n)
+ case *parse.WithNode:
+ return e.escapeBranch(c, &n.BranchNode, "with")
+ }
+ panic("escaping " + n.String() + " is unimplemented")
+}
+
+// escapeAction escapes an action template node.
+func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
+ if len(n.Pipe.Decl) != 0 {
+ // A local variable assignment, not an interpolation.
+ return c
+ }
+ c = nudge(c)
+ // Check for disallowed use of predefined escapers in the pipeline.
+ for pos, idNode := range n.Pipe.Cmds {
+ node, ok := idNode.Args[0].(*parse.IdentifierNode)
+ if !ok {
+ // A predefined escaper "esc" will never be found as an identifier in a
+ // Chain or Field node, since:
+ // - "esc.x ..." is invalid, since predefined escapers return strings, and
+ // strings do not have methods, keys or fields.
+ // - "... .esc" is invalid, since predefined escapers are global functions,
+ // not methods or fields of any types.
+ // Therefore, it is safe to ignore these two node types.
+ continue
+ }
+ ident := node.Ident
+ if _, ok := predefinedEscapers[ident]; ok {
+ if pos < len(n.Pipe.Cmds)-1 ||
+ c.state == stateAttr && c.delim == delimSpaceOrTagEnd && ident == "html" {
+ return context{
+ state: stateError,
+ err: errorf(ErrPredefinedEscaper, n, n.Line, "predefined escaper %q disallowed in template", ident),
+ }
+ }
+ }
+ }
+ switch c.state {
+ case stateError:
+ return c
+ case stateAttrName, stateTag:
+ c.state = stateAttrName
+ }
+ // TODO: integrate sanitizerForContext into escapeAction.
+ s, err := sanitizerForContext(c)
+ if err != nil {
+ return context{
+ state: stateError,
+ // TODO: return sanitization-specific errors.
+ err: errorf(ErrEscapeAction, n, n.Line, "cannot escape action %v: %s", n, err),
+ }
+ }
+ e.editActionNode(n, s)
+ return c
+}
+
+// ensurePipelineContains ensures that the pipeline ends with the commands with
+// the identifiers in s in order. If the pipeline ends with a predefined escaper
+// (i.e. "html" or "urlquery"), merge it with the identifiers in s.c
+func ensurePipelineContains(p *parse.PipeNode, s []string) {
+ if len(s) == 0 {
+ // Do not rewrite pipeline if we have no escapers to insert.
+ return
+ }
+ // Precondition: p.Cmds contains at most one predefined escaper and the
+ // escaper will be present at p.Cmds[len(p.Cmds)-1]. This precondition is
+ // always true because of the checks in escapeAction.
+ pipelineLen := len(p.Cmds)
+ if pipelineLen > 0 {
+ lastCmd := p.Cmds[pipelineLen-1]
+ if idNode, ok := lastCmd.Args[0].(*parse.IdentifierNode); ok {
+ if esc := idNode.Ident; predefinedEscapers[esc] {
+ // Pipeline ends with a predefined escaper.
+ if len(p.Cmds) == 1 && len(lastCmd.Args) > 1 {
+ // Special case: pipeline is of the form {{ esc arg1 arg2 ... argN }},
+ // where esc is the predefined escaper, and arg1...argN are its arguments.
+ // Convert this into the equivalent form
+ // {{ _eval_args_ arg1 arg2 ... argN | esc }}, so that esc can be easily
+ // merged with the escapers in s.
+ lastCmd.Args[0] = parse.NewIdentifier(evalArgsFuncName).SetTree(nil).SetPos(lastCmd.Args[0].Position())
+ p.Cmds = append(p.Cmds, newIdentCmd(esc, p.Position()))
+ pipelineLen++
+ }
+ // If any of the commands in s that we are about to insert is equivalent
+ // to the predefined escaper, use the predefined escaper instead.
+ dup := false
+ for i, escaper := range s {
+ if escFnsEq(esc, escaper) {
+ s[i] = idNode.Ident
+ dup = true
+ }
+ }
+ if dup {
+ // The predefined escaper will already be inserted along with the
+ // escapers in s, so do not copy it to the rewritten pipeline.
+ pipelineLen--
+ }
+ }
+ }
+ }
+ // Rewrite the pipeline, creating the escapers in s at the end of the pipeline.
+ newCmds := make([]*parse.CommandNode, pipelineLen, pipelineLen+len(s))
+ copy(newCmds, p.Cmds)
+ for _, name := range s {
+ newCmds = append(newCmds, newIdentCmd(name, p.Position()))
+ }
+ p.Cmds = newCmds
+}
+
+// predefinedEscapers contains template predefined escapers that are equivalent
+// to some contextual escapers. Keep in sync with equivEscapers.
+var predefinedEscapers = map[string]bool{
+ "html": true,
+ "urlquery": true,
+}
+
+// equivEscapers matches contextual escapers to equivalent predefined
+// template escapers.
+var equivEscapers = map[string]string{
+ // The following pairs of HTML escapers provide equivalent security
+ // guarantees, since they all escape '\000', '\'', '"', '&', '<', and '>'.
+ sanitizeHTMLFuncName: "html",
+ sanitizeRCDATAFuncName: "html",
+ // These two URL escapers produce URLs safe for embedding in a URL query by
+ // percent-encoding all the reserved characters specified in RFC 3986 Section
+ // 2.2
+ queryEscapeURLFuncName: "urlquery",
+ // The normalizer function is not actually equivalent to urlquery; urlquery is
+ // stricter as it escapes reserved characters (e.g. '#'), while the normalizer
+ // function does not. It is therefore only safe to replace the normalizer with
+ // with urlquery (this happens in ensurePipelineContains), but not the other
+ // way around. We keep this entry around to preserve the behavior of templates
+ // written before Go 1.9, which might depend on this substitution taking place.
+ normalizeURLFuncName: "urlquery",
+}
+
+// escFnsEq reports whether the two escaping functions are equivalent.
+func escFnsEq(a, b string) bool {
+ return normalizeEscFn(a) == normalizeEscFn(b)
+}
+
+// normalizeEscFn(a) is equal to normalizeEscFn(b) for any pair of names of
+// escaper functions a and b that are equivalent.
+func normalizeEscFn(e string) string {
+ if norm := equivEscapers[e]; norm != "" {
+ return norm
+ }
+ return e
+}
+
+// newIdentCmd produces a command containing a single identifier node.
+func newIdentCmd(identifier string, pos parse.Pos) *parse.CommandNode {
+ return &parse.CommandNode{
+ NodeType: parse.NodeCommand,
+ Args: []parse.Node{parse.NewIdentifier(identifier).SetTree(nil).SetPos(pos)}, // TODO: SetTree.
+ Pos: pos,
+ }
+}
+
+// nudge returns the context that would result from following empty string
+// transitions from the input context.
+// For example, parsing:
+// `<a href=`
+// will end in context{stateBeforeValue, AttrURL}, but parsing one extra rune:
+// `<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.
+// (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 {
+ switch c.state {
+ case stateTag:
+ // In `<foo {{.}}`, the action should emit an attribute.
+ c.state = stateAttrName
+ case stateBeforeValue:
+ // In `<foo bar={{.}}`, the action is an undelimited value.
+ c.state, c.delim = stateAttr, delimSpaceOrTagEnd
+ case stateAfterName:
+ // In `<foo bar {{.}}`, the action is an attribute name.
+ c.state = stateAttrName
+ }
+ return c
+}
+
+// join joins the two contexts of a branch template node. The result is an
+// error context if either of the input contexts are error contexts, or if the
+// input contexts differ.
+func join(a, b context, node parse.Node, nodeName string) context {
+ if a.state == stateError {
+ return a
+ }
+ if b.state == stateError {
+ return b
+ }
+
+ // Accumulate the result of context-joining elements and attributes in a, since the
+ // contents of a are always returned.
+ a.element.names = joinNames(a.element.name, b.element.name, a.element.names, b.element.names)
+ a.attr.names = joinNames(a.attr.name, b.attr.name, a.attr.names, b.attr.names)
+ if a.attr.value != b.attr.value {
+ a.attr.ambiguousValue = true
+ }
+
+ if a.eq(b) {
+ return a
+ }
+
+ c := a
+ c.element.name = b.element.name
+ if c.eq(b) {
+ // The contexts differ only by their element names. The element names from the conditional
+ // branches that are accumulated in c.element.names will be checked during action sanitization
+ // to ensure that they do not lead to different sanitization contexts.
+ return c
+ }
+
+ c = a
+ c.attr.name = b.attr.name
+ if c.eq(b) {
+ // The contexts differ only by their attribute name. The attribute names from the conditional
+ // branches that are accumulated in c.attr.names will be checked during action sanitization
+ // to ensure that they do not lead to different sanitization contexts.
+ return c
+ }
+
+ // Allow a nudged context to join with an unnudged one.
+ // This means that
+ // <p title={{if .C}}{{.}}{{end}}
+ // ends in an unquoted value state even though the else branch
+ // ends in stateBeforeValue.
+ if c, d := nudge(a), nudge(b); !(c.eq(a) && d.eq(b)) {
+ if e := join(c, d, node, nodeName); e.state != stateError {
+ return e
+ }
+ }
+
+ return context{
+ state: stateError,
+ err: errorf(ErrBranchEnd, node, 0, "{{%s}} branches end in different contexts: %v, %v", nodeName, a, b),
+ }
+}
+
+// joinNames returns the slice of all possible names that an element or attr could
+// assume after context joining the element or attr containing aName and aNames with the
+// element or attr containing bName and bNames.
+func joinNames(aName, bName string, aNames, bNames []string) []string {
+ var ret []string
+ if aName != bName {
+ ret = append(ret, aName, bName)
+ }
+ aNamesSet := make(map[string]bool)
+ for _, name := range aNames {
+ aNamesSet[name] = true
+ }
+ for _, name := range bNames {
+ if !aNamesSet[name] {
+ ret = append(ret, name)
+ }
+ }
+ return ret
+}
+
+// escapeBranch escapes a branch template node: "if", "range" and "with".
+func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context {
+ c0 := e.escapeList(c, n.List)
+ if nodeName == "range" && c0.state != stateError {
+ // The "true" branch of a "range" node can execute multiple times.
+ // We check that executing n.List once results in the same context
+ // as executing n.List twice.
+ c1, _ := e.escapeListConditionally(c0, n.List, nil)
+ c0 = join(c0, c1, n, nodeName)
+ if c0.state == stateError {
+ // Make clear that this is a problem on loop re-entry
+ // since developers tend to overlook that branch when
+ // debugging templates.
+ c0.err.Line = n.Line
+ c0.err.Description = "on range loop re-entry: " + c0.err.Description
+ return c0
+ }
+ }
+ c1 := e.escapeList(c, n.ElseList)
+ return join(c0, c1, n, nodeName)
+}
+
+// escapeList escapes a list template node.
+func (e *escaper) escapeList(c context, n *parse.ListNode) context {
+ if n == nil {
+ return c
+ }
+ for _, m := range n.Nodes {
+ c = e.escape(c, m)
+ }
+ return c
+}
+
+// escapeListConditionally escapes a list node but only preserves edits and
+// inferences in e if the inferences and output context satisfy filter.
+// It returns the best guess at an output context, and the result of the filter
+// which is the same as whether e was updated.
+func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) {
+ e1 := makeEscaper(e.ns)
+ // Make type inferences available to f.
+ for k, v := range e.output {
+ e1.output[k] = v
+ }
+ c = e1.escapeList(c, n)
+ ok := filter != nil && filter(&e1, c)
+ if ok {
+ // Copy inferences and edits from e1 back into e.
+ for k, v := range e1.output {
+ e.output[k] = v
+ }
+ for k, v := range e1.derived {
+ e.derived[k] = v
+ }
+ for k, v := range e1.called {
+ e.called[k] = v
+ }
+ for k, v := range e1.actionNodeEdits {
+ e.editActionNode(k, v)
+ }
+ for k, v := range e1.templateNodeEdits {
+ e.editTemplateNode(k, v)
+ }
+ for k, v := range e1.textNodeEdits {
+ e.editTextNode(k, v)
+ }
+ }
+ return c, ok
+}
+
+// escapeTemplate escapes a {{template}} call node.
+func (e *escaper) escapeTemplate(c context, n *parse.TemplateNode) context {
+ c, name := e.escapeTree(c, n, n.Name, n.Line)
+ if name != n.Name {
+ e.editTemplateNode(n, name)
+ }
+ return c
+}
+
+// mangle produces an identifier that includes a suffix that distinguishes it
+// from template names mangled with different contexts.
+func mangle(c context, templateName string) string {
+ // The mangled name for the default context is the input templateName.
+ if c.state == stateText {
+ return templateName
+ }
+ s := templateName + "$htmltemplate_" + c.state.String()
+ if c.delim != 0 {
+ s += "_" + c.delim.String()
+ }
+ if c.attr.name != "" {
+ s += "_" + c.attr.String()
+ }
+ if c.element.name != "" {
+ s += "_" + c.element.String()
+ }
+ return s
+}
+
+// escapeTree escapes the named template starting in the given context as
+// necessary and returns its output context.
+func (e *escaper) escapeTree(c context, node parse.Node, name string, line int) (context, string) {
+ // Mangle the template name with the input context to produce a reliable
+ // identifier.
+ dname := mangle(c, name)
+ e.called[dname] = true
+ if out, ok := e.output[dname]; ok {
+ // Already escaped.
+ return out, dname
+ }
+ t := e.template(name)
+ if t == nil {
+ // Two cases: The template exists but is empty, or has never been mentioned at
+ // all. Distinguish the cases in the error messages.
+ if e.ns.set[name] != nil {
+ return context{
+ state: stateError,
+ err: errorf(ErrNoSuchTemplate, node, line, "%q is an incomplete or empty template", name),
+ }, dname
+ }
+ return context{
+ state: stateError,
+ err: errorf(ErrNoSuchTemplate, node, line, "no such template %q", name),
+ }, dname
+ }
+ if dname != name {
+ // Use any template derived during an earlier call to escapeTemplate
+ // with different top level templates, or clone if necessary.
+ dt := e.template(dname)
+ if dt == nil {
+ dt = template.New(dname)
+ dt.Tree = t.Tree.Copy()
+ dt.Tree.Name = dname
+ e.derived[dname] = dt
+ }
+ t = dt
+ }
+ return e.computeOutCtx(c, t), dname
+}
+
+// computeOutCtx takes a template and its start context and computes the output
+// context while storing any inferences in e.
+func (e *escaper) computeOutCtx(c context, t *template.Template) context {
+ // Propagate context over the body.
+ c1, ok := e.escapeTemplateBody(c, t)
+ if !ok {
+ // Look for a fixed point by assuming c1 as the output context.
+ if c2, ok2 := e.escapeTemplateBody(c1, t); ok2 {
+ c1, ok = c2, true
+ }
+ // Use c1 as the error context if neither assumption worked.
+ }
+ if !ok && c1.state != stateError {
+ return context{
+ state: stateError,
+ err: errorf(ErrOutputContext, t.Tree.Root, 0, "cannot compute output context for template %s", t.Name()),
+ }
+ }
+ return c1
+}
+
+// escapeTemplateBody escapes the given template assuming the given output
+// context, and returns the best guess at the output context and whether the
+// assumption was correct.
+func (e *escaper) escapeTemplateBody(c context, t *template.Template) (context, bool) {
+ filter := func(e1 *escaper, c1 context) bool {
+ if c1.state == stateError {
+ // Do not update the input escaper, e.
+ return false
+ }
+ if !e1.called[t.Name()] {
+ // If t is not recursively called, then c1 is an
+ // accurate output context.
+ return true
+ }
+ // c1 is accurate if it matches our assumed output context.
+ return c.eq(c1)
+ }
+ // We need to assume an output context so that recursive template calls
+ // take the fast path out of escapeTree instead of infinitely recursing.
+ // Naively assuming that the input context is the same as the output
+ // works >90% of the time.
+ e.output[t.Name()] = c
+ return e.escapeListConditionally(c, t.Tree.Root, filter)
+}
+
+// delimEnds maps each delim to a string of characters that terminate it.
+var delimEnds = [...]string{
+ delimDoubleQuote: `"`,
+ delimSingleQuote: "'",
+ // Determined empirically by running the below in various browsers.
+ // var div = document.createElement("DIV");
+ // for (var i = 0; i < 0x10000; ++i) {
+ // div.innerHTML = "<span title=x" + String.fromCharCode(i) + "-bar>";
+ // if (div.getElementsByTagName("SPAN")[0].title.indexOf("bar") < 0)
+ // document.write("<p>U+" + i.toString(16));
+ // }
+ delimSpaceOrTagEnd: " \t\n\f\r>",
+}
+
+var doctypeBytes = []byte("<!DOCTYPE")
+
+// escapeText escapes a text template node.
+func (e *escaper) escapeText(c context, n *parse.TextNode) context {
+ s, written, i, b := n.Text, 0, 0, new(bytes.Buffer)
+ if e.ns.cspCompatible && bytes.Contains(s, []byte("javascript:")) {
+ // This substring search is not perfect, but it is unlikely that this substring will
+ // exist in template text for any other reason than to specify a javascript URI.
+ return context{
+ state: stateError,
+ err: errorf(ErrCSPCompatibility, n, 0, `"javascript:" URI disallowed for CSP compatibility`),
+ }
+ }
+ for i != len(s) {
+ if e.ns.cspCompatible && strings.HasPrefix(c.attr.name, "on") {
+ return context{
+ state: stateError,
+ err: errorf(ErrCSPCompatibility, n, 0, "inline event handler %q is disallowed for CSP compatibility", c.attr.name),
+ }
+ }
+ c1, nread := contextAfterText(c, s[i:])
+ i1 := i + nread
+ sc, err := sanitizationContextForElementContent(c.element.name)
+ if c.state == stateText || err == nil && sc == sanitizationContextRCDATA {
+ end := i1
+ if c1.state != c.state {
+ for j := end - 1; j >= i; j-- {
+ if s[j] == '<' {
+ end = j
+ break
+ }
+ }
+ }
+ for j := i; j < end; j++ {
+ if s[j] == '<' && !bytes.HasPrefix(bytes.ToUpper(s[j:]), doctypeBytes) {
+ b.Write(s[written:j])
+ b.WriteString("&lt;")
+ written = j + 1
+ }
+ }
+ } else if isComment(c.state) && c.delim == delimNone {
+ written = i1
+ }
+ if c.state == stateSpecialElementBody && c.element.name == "script" {
+ if err := isJsTemplateBalanced(bytes.NewBuffer(s)); err != nil {
+ return context{
+ state: stateError,
+ err: errorf(ErrUnbalancedJsTemplate, n, 0, "Mixing template systems can cause security vulnerabilites. Therefore, there can be no safehtml/template insertion points or actions inside an ES6 template, and all ES6 templates must be closed: %v", err.Error()),
+ }
+ }
+ }
+
+ if c.state != c1.state && isComment(c1.state) && c1.delim == delimNone {
+ // Preserve the portion between written and the comment start.
+ cs := i1 - 2
+ if c1.state == stateHTMLCmt {
+ // "<!--" instead of "/*" or "//"
+ cs -= 2
+ }
+ b.Write(s[written:cs])
+ written = i1
+ }
+ if i == i1 && c.state == c1.state {
+ panic(fmt.Sprintf("infinite loop from %v to %v on %q..%q", c, c1, s[:i], s[i:]))
+ }
+ c, i = c1, i1
+ }
+
+ if written != 0 && c.state != stateError {
+ if !isComment(c.state) || c.delim != delimNone {
+ b.Write(n.Text[written:])
+ }
+ e.editTextNode(n, b.Bytes())
+ }
+ return c
+}
+
+// contextAfterText starts in context c, consumes some tokens from the front of
+// s, then returns the context after those tokens and the unprocessed suffix.
+func contextAfterText(c context, s []byte) (context, int) {
+ if c.delim == delimNone {
+ c1, i := tSpecialTagEnd(c, s)
+ if i == 0 {
+ // A special end tag (`</script>`) has been seen and
+ // all content preceding it has been consumed.
+ return c1, 0
+ }
+ // Consider all content up to any end tag.
+ 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)
+ }
+ if c.delim == delimSpaceOrTagEnd {
+ // http://www.w3.org/TR/html5/syntax.html#attribute-value-(unquoted)-state
+ // lists the runes below as error characters.
+ // Error out because HTML parsers may differ on whether
+ // "<a id= onclick=f(" ends inside id's or onclick's value,
+ // "<a class=`foo " ends inside a value,
+ // "<a style=font:'Arial'" needs open-quote fixup.
+ // IE treats '`' as a quotation character.
+ if j := bytes.IndexAny(s[:i], "\"'<=`"); j >= 0 {
+ return context{
+ state: stateError,
+ err: errorf(ErrBadHTML, nil, 0, "%q in unquoted attr: %q", s[j:j+1], s[:i]),
+ }, len(s)
+ }
+ }
+ if i == len(s) {
+ c.attr.value += string(s)
+ // Remain inside the attribute.
+ // Decode the value so non-HTML rules can easily handle
+ // <button onclick="alert(&quot;Hi!&quot;)">
+ // without having to entity decode token boundaries.
+ for u := []byte(html.UnescapeString(string(s))); len(u) != 0; {
+ c1, i1 := transitionFunc[c.state](c, u)
+ c, u = c1, u[i1:]
+ }
+ return c, len(s)
+ }
+
+ // On exiting an attribute, we discard all state information
+ // except the state, element, scriptType, and linkRel.
+ ret := context{
+ state: stateTag,
+ element: c.element,
+ scriptType: c.scriptType,
+ linkRel: c.linkRel,
+ }
+ // Save the script element's type attribute value if we are parsing it for the first time.
+ if c.state == stateAttr && c.element.name == "script" && c.attr.name == "type" {
+ ret.scriptType = strings.ToLower(string(s[:i]))
+ }
+ // Save the link element's rel attribute value if we are parsing it for the first time.
+ if c.state == stateAttr && c.element.name == "link" && c.attr.name == "rel" {
+ ret.linkRel = " " + strings.Join(strings.Fields(strings.TrimSpace(strings.ToLower(string(s[:i])))), " ") + " "
+ }
+ if c.delim != delimSpaceOrTagEnd {
+ // Consume any quote.
+ i++
+ }
+ return ret, i
+}
+
+// editActionNode records a change to an action pipeline for later commit.
+func (e *escaper) editActionNode(n *parse.ActionNode, cmds []string) {
+ if _, ok := e.actionNodeEdits[n]; ok {
+ panic(fmt.Sprintf("node %s shared between templates", n))
+ }
+ e.actionNodeEdits[n] = cmds
+}
+
+// editTemplateNode records a change to a {{template}} callee for later commit.
+func (e *escaper) editTemplateNode(n *parse.TemplateNode, callee string) {
+ if _, ok := e.templateNodeEdits[n]; ok {
+ panic(fmt.Sprintf("node %s shared between templates", n))
+ }
+ e.templateNodeEdits[n] = callee
+}
+
+// editTextNode records a change to a text node for later commit.
+func (e *escaper) editTextNode(n *parse.TextNode, text []byte) {
+ if _, ok := e.textNodeEdits[n]; ok {
+ panic(fmt.Sprintf("node %s shared between templates", n))
+ }
+ e.textNodeEdits[n] = text
+}
+
+// commit applies changes to actions and template calls needed to contextually
+// autoescape content and adds any derived templates to the set.
+func (e *escaper) commit() {
+ for name := range e.output {
+ e.template(name).Funcs(funcs)
+ }
+ // Any template from the name space associated with this escaper can be used
+ // to add derived templates to the underlying text/template name space.
+ tmpl := e.arbitraryTemplate()
+ for _, t := range e.derived {
+ if _, err := tmpl.text.AddParseTree(t.Name(), t.Tree); err != nil {
+ panic("error adding derived template")
+ }
+ }
+ for n, s := range e.actionNodeEdits {
+ ensurePipelineContains(n.Pipe, s)
+ }
+ for n, name := range e.templateNodeEdits {
+ n.Name = name
+ }
+ for n, s := range e.textNodeEdits {
+ n.Text = s
+ }
+ // Reset state that is specific to this commit so that the same changes are
+ // not re-applied to the template on subsequent calls to commit.
+ e.called = make(map[string]bool)
+ e.actionNodeEdits = make(map[*parse.ActionNode][]string)
+ e.templateNodeEdits = make(map[*parse.TemplateNode]string)
+ e.textNodeEdits = make(map[*parse.TextNode][]byte)
+}
+
+// template returns the named template given a mangled template name.
+func (e *escaper) template(name string) *template.Template {
+ // Any template from the name space associated with this escaper can be used
+ // to look up templates in the underlying text/template name space.
+ t := e.arbitraryTemplate().text.Lookup(name)
+ if t == nil {
+ t = e.derived[name]
+ }
+ return t
+}
+
+// arbitraryTemplate returns an arbitrary template from the name space
+// associated with e and panics if no templates are found.
+func (e *escaper) arbitraryTemplate() *Template {
+ for _, t := range e.ns.set {
+ return t
+ }
+ panic("no templates in name space")
+}
+
+var (
+ errorType = reflect.TypeOf((*error)(nil)).Elem()
+ fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+)
+
+// indirectToStringerOrError returns the value, after dereferencing as many times
+// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer
+// or error,
+func indirectToStringerOrError(a interface{}) interface{} {
+ if a == nil {
+ return nil
+ }
+ v := reflect.ValueOf(a)
+ for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() {
+ v = v.Elem()
+ }
+ return v.Interface()
+}
+
+var (
+ jsTemplateSeparator = []byte("`")
+ jsTemplateExprStart = []byte("${")
+ jsTemplateExprEnd = []byte("}")
+)
+
+// Determine if a string has unbalanced (open) JS templates.
+func isJsTemplateBalanced(s *bytes.Buffer) error {
+ for {
+ index := bytes.Index(s.Bytes(), jsTemplateSeparator)
+ if index == -1 {
+ return nil
+ }
+ s.Next(index + 1)
+ err := consumeJsTemplate(s)
+ if err != nil {
+ return err
+ }
+ }
+}
+
+// s is a JS template string (without the opening `); this function consumes s up to
+// the matching closing `.
+func consumeJsTemplate(s *bytes.Buffer) error {
+ for {
+ templateEnd := bytes.Index(s.Bytes(), jsTemplateSeparator)
+ exprStart := bytes.Index(s.Bytes(), jsTemplateExprStart)
+ if templateEnd == -1 {
+ return fmt.Errorf("Missing closing ` in JS template")
+ }
+ if exprStart != -1 && exprStart < templateEnd {
+ err := consumeJsTemplateExpr(s)
+ if err != nil {
+ return err
+ }
+ return consumeJsTemplate(s)
+ } else {
+ // The template expression occurs after this template, e.g. "`foo``bar${test}`".
+ s.Next(templateEnd + 1)
+ return nil
+ }
+ }
+}
+
+// s is a Js Template expression (starting with "${"). This function consumes up to and including the matching closing "}".
+func consumeJsTemplateExpr(s *bytes.Buffer) error {
+ for {
+ exprEnd := bytes.Index(s.Bytes(), jsTemplateExprEnd)
+ if exprEnd == -1 {
+ // Template expression isn't closed
+ return fmt.Errorf("Missing closing } in JS template")
+ }
+ nestedTemplateStart := bytes.Index(s.Bytes(), jsTemplateSeparator)
+ if nestedTemplateStart != -1 && nestedTemplateStart < exprEnd {
+ s.Next(nestedTemplateStart + 1)
+ err := consumeJsTemplate(s)
+ if err != nil {
+ return err
+ }
+ return consumeJsTemplateExpr(s)
+ } else {
+ s.Next(exprEnd + 1)
+ return nil
+ }
+ }
+}
diff --git a/vendor/github.com/google/safehtml/template/init.go b/vendor/github.com/google/safehtml/template/init.go
new file mode 100644
index 000000000..c9fa2de1d
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/init.go
@@ -0,0 +1,28 @@
+// Copyright (c) 2017 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+package template
+
+import (
+ "github.com/google/safehtml/internal/template/raw"
+)
+
+// The following functions are used by package uncheckedconversions
+// (via package raw) to create TrustedSource and TrustedTemplate values
+// from plain strings.
+
+func trustedSourceRaw(s string) TrustedSource {
+ return TrustedSource{s}
+}
+
+func trustedTemplateRaw(s string) TrustedTemplate {
+ return TrustedTemplate{s}
+}
+
+func init() {
+ raw.TrustedSource = trustedSourceRaw
+ raw.TrustedTemplate = trustedTemplateRaw
+}
diff --git a/vendor/github.com/google/safehtml/template/sanitize.go b/vendor/github.com/google/safehtml/template/sanitize.go
new file mode 100644
index 000000000..c75e345e1
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/sanitize.go
@@ -0,0 +1,258 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+)
+
+// sanitizerForContext returns an ordered list of function names that will be called to
+// sanitize data values found in the HTML context defined by c.
+func sanitizerForContext(c context) ([]string, error) {
+ switch c.state {
+ case stateTag, stateAttrName, stateAfterName:
+ return nil, fmt.Errorf("actions must not affect element or attribute names")
+ case stateHTMLCmt:
+ return []string{sanitizeHTMLCommentFuncName}, nil
+ }
+ if len(c.element.names) == 0 && c.element.name == "" && c.state == stateText {
+ // Not in an HTML element.
+ return []string{sanitizeHTMLFuncName}, nil
+ }
+ if c.attr.name != "" || len(c.attr.names) > 0 {
+ // We are in an attribute value context.
+ if c.delim != delimDoubleQuote && c.delim != delimSingleQuote {
+ // TODO: consider disallowing single-quoted or unquoted attribute values completely, even in hardcoded template text.
+ return nil, fmt.Errorf("unquoted attribute values disallowed")
+ }
+ return sanitizersForAttributeValue(c)
+ }
+ // Otherwise, we are in an element content context.
+ elementContentSanitizer, err := sanitizerForElementContent(c)
+ return appendIfNotEmpty([]string{}, elementContentSanitizer), err
+}
+
+// appendIfNotEmpty appends the given strings that are non-empty to the given slice.
+func appendIfNotEmpty(slice []string, strings ...string) []string {
+ for _, s := range strings {
+ if s != "" {
+ slice = append(slice, s)
+ }
+ }
+ return slice
+}
+
+// sanitizersForAttributeValue returns a list of names of functions that will be
+// called in order to sanitize data values found the HTML attribtue value context c.
+func sanitizersForAttributeValue(c context) ([]string, error) {
+ // Ensure that all combinations of element and attribute names for this context results
+ // in the same attribute value sanitization context.
+ var elems, attrs []string
+ if len(c.element.names) == 0 {
+ elems = []string{c.element.name}
+ } else {
+ elems = c.element.names
+ }
+ if len(c.attr.names) == 0 {
+ attrs = []string{c.attr.name}
+ } else {
+ attrs = c.attr.names
+ }
+ var sc0 sanitizationContext
+ var elem0, attr0 string
+ for i, elem := range elems {
+ for j, attr := range attrs {
+ sc, err := sanitizationContextForAttrVal(elem, attr, c.linkRel)
+ if err != nil {
+ if len(elems) == 1 && len(attrs) == 1 {
+ return nil, err
+ }
+ return nil, fmt.Errorf(`conditional branch with {element=%q, attribute=%q} results in sanitization error: %s`, elem, attr, err)
+ }
+ if i == 0 && j == 0 {
+ sc0, elem0, attr0 = sc, elem, attr
+ continue
+ }
+ if sc != sc0 {
+ return nil, fmt.Errorf(
+ `conditional branches end in different attribute value sanitization contexts: {element=%q, attribute=%q} has sanitization context %q, {element=%q, attribute=%q} has sanitization context %q`,
+ elem0, attr0, sc0, elem, attr, sc)
+ }
+ }
+ }
+ if sc0.isEnum() && c.attr.value != "" {
+ return nil, fmt.Errorf("partial substitutions are disallowed in the %q attribute value context of a %q element", c.attr.name, c.element.name)
+ }
+ if sc0 == sanitizationContextStyle && c.attr.value != "" {
+ if err := validateDoesNotEndsWithCharRefPrefix(c.attr.value); err != nil {
+ return nil, fmt.Errorf("action cannot be interpolated into the %q attribute value of this %q element: %s", c.attr.name, c.element.name, err)
+ }
+ }
+ // ret is a stack of sanitizer names that will be built in reverse.
+ var ret []string
+ // All attribute values must be HTML-escaped at run time by sanitizeHTML to eliminate
+ // any HTML markup that can cause the HTML parser to transition out of the attribute value state.
+ // These attribute values will later be HTML-unescaped by the HTML parser in the browser.
+ ret = append(ret, sanitizeHTMLFuncName)
+ sanitizer := sc0.sanitizerName()
+ if !sc0.isURLorTrustedResourceURL() {
+ return reverse(appendIfNotEmpty(ret, sanitizer)), nil
+ }
+ urlAttrValPrefix := c.attr.value
+ if urlAttrValPrefix == "" {
+ // Attribute value prefixes in URL or TrustedResourceURL sanitization contexts
+ // must sanitized and normalized.
+ return reverse(appendIfNotEmpty(ret, normalizeURLFuncName, sanitizer)), nil
+ }
+ // Action occurs after a URL or TrustedResourceURL prefix.
+ if c.attr.ambiguousValue {
+ return nil, fmt.Errorf("actions must not occur after an ambiguous URL prefix in the %q attribute value context of a %q element", c.attr.name, c.element.name)
+ }
+ validator, ok := urlPrefixValidators[sc0]
+ if !ok {
+ return nil, fmt.Errorf("cannot validate attribute value prefix %q in the %q sanitization context", c.attr.value, sc0)
+ }
+ if err := validator(c.attr.value); err != nil {
+ return nil, fmt.Errorf("action cannot be interpolated into the %q URL attribute value of this %q element: %s", c.attr.name, c.element.name, err)
+ }
+ switch {
+ case sc0 == sanitizationContextTrustedResourceURL:
+ // Untrusted data that occurs anywhere after TrustedResourceURL prefix must be query-escaped
+ // to prevent the injection of any new path segments or URL components. Moreover, they must
+ // not contain any ".." dot-segments.
+ ret = append(ret, queryEscapeURLFuncName, validateTrustedResourceURLSubstitutionFuncName)
+ case strings.ContainsAny(urlAttrValPrefix, "#?"):
+ // For URLs, we only escape in the query or fragment part to prevent the injection of new query
+ // parameters or fragments.
+ ret = append(ret, queryEscapeURLFuncName)
+ default:
+ ret = append(ret, normalizeURLFuncName)
+ }
+ return reverse(ret), nil
+}
+
+// reverse reverses s and returns it.
+func reverse(s []string) []string {
+ for head, tail := 0, len(s)-1; head < tail; head, tail = head+1, tail-1 {
+ s[head], s[tail] = s[tail], s[head]
+ }
+ return s
+}
+
+// sanitizationContextForAttrVal returns the sanitization context for attr when it
+// appears within element.
+func sanitizationContextForAttrVal(element, attr, linkRel string) (sanitizationContext, error) {
+ if element == "link" && attr == "href" {
+ // Special case: safehtml.URL values are allowed in a link element's href attribute if that element's
+ // rel attribute possesses certain values.
+ relVals := strings.Fields(linkRel)
+ for _, val := range relVals {
+ if urlLinkRelVals[val] {
+ return sanitizationContextTrustedResourceURLOrURL, nil
+ }
+ }
+ }
+ if dataAttributeNamePattern.MatchString(attr) {
+ // Special case: data-* attributes are specified by HTML5 to hold custom data private to
+ // the page or application; they should not be interpreted by browsers. Therefore, no
+ // sanitization is required for these attribute values.
+ return sanitizationContextNone, nil
+ }
+ if sc, ok := elementSpecificAttrValSanitizationContext[attr][element]; ok {
+ return sc, nil
+ }
+ sc, isAllowedAttr := globalAttrValSanitizationContext[attr]
+ _, isAllowedElement := elementContentSanitizationContext[element]
+ if isAllowedAttr && (isAllowedElement || allowedVoidElements[element]) {
+ // Only sanitize attributes that appear in elements whose semantics are known.
+ // Thes attributes might have different semantics in other standard or custom
+ // elements that our sanitization policy does not handle correctly.
+ return sc, nil
+ }
+ return 0, fmt.Errorf("actions must not occur in the %q attribute value context of a %q element", attr, element)
+}
+
+// dataAttributeNamePattern matches valid data attribute names.
+// This pattern is conservative and matches only a subset of the valid names defined in
+// https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes
+var dataAttributeNamePattern = regexp.MustCompile(`^data-[a-z_][-a-z0-9_]*$`)
+
+// endsWithCharRefPrefixPattern matches strings that end in an incomplete
+// HTML character reference.
+//
+// See https://html.spec.whatwg.org/multipage/syntax.html#character-references.
+var endsWithCharRefPrefixPattern = regexp.MustCompile(
+ `&(?:[[:alpha:]][[:alnum:]]*|#(?:[xX][[:xdigit:]]*|[[:digit:]]*))?$`)
+
+// validateDoesNotEndsWithCharRefPrefix returns an error only if the given prefix ends
+// with an incomplete HTML character reference.
+func validateDoesNotEndsWithCharRefPrefix(prefix string) error {
+ if endsWithCharRefPrefixPattern.MatchString(prefix) {
+ return fmt.Errorf(`prefix %q ends with an incomplete HTML character reference; did you mean "&amp;" instead of "&"?`, prefix)
+ }
+ return nil
+}
+
+// sanitizerForElementContent returns the name of the function that will be called
+// to sanitize data values found in the HTML element content context c.
+func sanitizerForElementContent(c context) (string, error) {
+ // Ensure that all other possible element names for this context result in the same
+ // element content sanitization context.
+ var elems []string
+ if len(c.element.names) == 0 {
+ elems = []string{c.element.name}
+ } else {
+ elems = c.element.names
+ }
+ var sc0 sanitizationContext
+ var elem0 string
+ for i, elem := range elems {
+ var sc sanitizationContext
+ var err error
+ if elem == "" {
+ // Special case: an empty element name represents a context outside of a HTML element.
+ sc = sanitizationContextHTML
+ } else {
+ sc, err = sanitizationContextForElementContent(elem)
+ }
+ if err != nil {
+ if len(elems) == 1 {
+ return "", err
+ }
+ return "", fmt.Errorf(`conditional branch with element %q results in sanitization error: %s`, elem, err)
+ }
+ if i == 0 {
+ sc0, elem0 = sc, elem
+ continue
+ }
+ if sc != sc0 {
+ return "",
+ fmt.Errorf(`conditional branches end in different element content sanitization contexts: element %q has sanitization context %q, element %q has sanitization context %q`,
+ elem0, sc0, elem, sc)
+ }
+ }
+ return sc0.sanitizerName(), nil
+}
+
+// sanitizationContextForElementContent returns the element content sanitization context for the given element.
+func sanitizationContextForElementContent(element string) (sanitizationContext, error) {
+ sc, ok := elementContentSanitizationContext[element]
+ if !ok {
+ return 0, fmt.Errorf("actions must not occur in the element content context of a %q element", element)
+ }
+ return sc, nil
+}
+
+// sanitizeHTMLComment returns the empty string regardless of input.
+// Comment content does not correspond to any parsed structure or
+// human-readable content, so the simplest and most secure policy is to drop
+// content interpolated into comments.
+// This approach is equally valid whether or not static comment content is
+// removed from the template.
+func sanitizeHTMLComment(_ ...interface{}) string {
+ return ""
+}
diff --git a/vendor/github.com/google/safehtml/template/sanitizers.go b/vendor/github.com/google/safehtml/template/sanitizers.go
new file mode 100644
index 000000000..782e931b8
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/sanitizers.go
@@ -0,0 +1,599 @@
+// Copyright (c) 2017 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+package template
+
+import (
+ "fmt"
+ "text/template"
+
+ "github.com/google/safehtml/internal/safehtmlutil"
+ "github.com/google/safehtml"
+)
+
+// sanitizationContext determines what type of sanitization to perform
+// on a template action.
+type sanitizationContext uint8
+
+const (
+ _ = iota
+ sanitizationContextAsyncEnum
+ sanitizationContextDirEnum
+ sanitizationContextHTML
+ sanitizationContextHTMLValOnly
+ sanitizationContextIdentifier
+ sanitizationContextLoadingEnum
+ sanitizationContextNone
+ sanitizationContextRCDATA
+ sanitizationContextScript
+ sanitizationContextStyle
+ sanitizationContextStyleSheet
+ sanitizationContextTargetEnum
+ sanitizationContextTrustedResourceURL
+ sanitizationContextTrustedResourceURLOrURL
+ sanitizationContextURL
+ sanitizationContextURLSet
+)
+
+// String returns the string representation of sanitizationContext s.
+func (s sanitizationContext) String() string {
+ if int(s) >= len(sanitizationContextInfo) {
+ return fmt.Sprintf("invalid sanitization context %d", s)
+ }
+ return sanitizationContextInfo[s].name
+}
+
+// sanitizerName returns the name of the sanitizer to call in sanitizationContext s.
+// It returns an empty string if no sanitization is required in s.
+func (s sanitizationContext) sanitizerName() string {
+ if int(s) >= len(sanitizationContextInfo) {
+ return fmt.Sprintf("invalid sanitization context %d", s)
+ }
+ return sanitizationContextInfo[s].sanitizerName
+}
+
+// isEnum reports reports whether s is a sanitization context for enumerated values.
+func (s sanitizationContext) isEnum() bool {
+ return s == sanitizationContextAsyncEnum || s == sanitizationContextDirEnum || s == sanitizationContextLoadingEnum || s == sanitizationContextTargetEnum
+}
+
+// isURLorTrustedResourceURL reports reports whether s is a sanitization context for URL or TrustedResourceURL values.
+func (s sanitizationContext) isURLorTrustedResourceURL() bool {
+ return s == sanitizationContextTrustedResourceURL || s == sanitizationContextTrustedResourceURLOrURL || s == sanitizationContextURL
+}
+
+// sanitizationContextInfo[x] contains the name for sanitization context x and the
+// name of the sanitizer to call in that context.
+// If sanitizationContextInfo[x].sanitizerName is empty, then no sanitizer needs
+// to be called in x.
+var sanitizationContextInfo = [...]struct {
+ name, sanitizerName string
+}{
+ sanitizationContextAsyncEnum: {"AsyncEnum", sanitizeAsyncEnumFuncName},
+ sanitizationContextDirEnum: {"DirEnum", sanitizeDirEnumFuncName},
+ sanitizationContextHTML: {"HTML", sanitizeHTMLFuncName},
+ sanitizationContextHTMLValOnly: {"HTMLValOnly", sanitizeHTMLValOnlyFuncName},
+ sanitizationContextIdentifier: {"Identifier", sanitizeIdentifierFuncName},
+ sanitizationContextLoadingEnum: {"LoadingEnum", sanitizeLoadingEnumFuncName},
+ sanitizationContextNone: {"None", ""},
+ sanitizationContextRCDATA: {"RCDATA", sanitizeRCDATAFuncName},
+ sanitizationContextScript: {"Script", sanitizeScriptFuncName},
+ sanitizationContextStyle: {"Style", sanitizeStyleFuncName},
+ sanitizationContextStyleSheet: {"StyleSheet", sanitizeStyleSheetFuncName},
+ sanitizationContextTargetEnum: {"TargetEnum", sanitizeTargetEnumFuncName},
+ sanitizationContextTrustedResourceURL: {"TrustedResourceURL", sanitizeTrustedResourceURLFuncName},
+ sanitizationContextTrustedResourceURLOrURL: {"TrustedResourceURLOrURL", sanitizeTrustedResourceURLOrURLFuncName},
+ sanitizationContextURL: {"URL", sanitizeURLFuncName},
+ sanitizationContextURLSet: {"URLSet", sanitizeURLSetFuncName},
+}
+
+var funcs = template.FuncMap{
+ queryEscapeURLFuncName: safehtmlutil.QueryEscapeURL,
+ normalizeURLFuncName: safehtmlutil.NormalizeURL,
+ validateTrustedResourceURLSubstitutionFuncName: validateTrustedResourceURLSubstitution,
+ evalArgsFuncName: evalArgs,
+ sanitizeHTMLCommentFuncName: sanitizeHTMLComment,
+ sanitizeAsyncEnumFuncName: sanitizeAsyncEnum,
+ sanitizeDirEnumFuncName: sanitizeDirEnum,
+ sanitizeHTMLFuncName: sanitizeHTML,
+ sanitizeHTMLValOnlyFuncName: sanitizeHTMLValOnly,
+ sanitizeIdentifierFuncName: sanitizeIdentifier,
+ sanitizeLoadingEnumFuncName: sanitizeLoadingEnum,
+ sanitizeRCDATAFuncName: sanitizeRCDATA,
+ sanitizeScriptFuncName: sanitizeScript,
+ sanitizeStyleFuncName: sanitizeStyle,
+ sanitizeStyleSheetFuncName: sanitizeStyleSheet,
+ sanitizeTargetEnumFuncName: sanitizeTargetEnum,
+ sanitizeTrustedResourceURLFuncName: sanitizeTrustedResourceURL,
+ sanitizeTrustedResourceURLOrURLFuncName: sanitizeTrustedResourceURLOrURL,
+ sanitizeURLFuncName: sanitizeURL,
+ sanitizeURLSetFuncName: sanitizeURLSet,
+}
+
+const (
+ queryEscapeURLFuncName = "_queryEscapeURL"
+ normalizeURLFuncName = "_normalizeURL"
+ validateTrustedResourceURLSubstitutionFuncName = "_validateTrustedResourceURLSubstitution"
+ evalArgsFuncName = "_evalArgs"
+ sanitizeHTMLCommentFuncName = "_sanitizeHTMLComment"
+ sanitizeAsyncEnumFuncName = "_sanitizeAsyncEnum"
+ sanitizeDirEnumFuncName = "_sanitizeDirEnum"
+ sanitizeHTMLFuncName = "_sanitizeHTML"
+ sanitizeHTMLValOnlyFuncName = "_sanitizeHTMLValOnly"
+ sanitizeIdentifierFuncName = "_sanitizeIdentifier"
+ sanitizeLoadingEnumFuncName = "_sanitizeLoadingEnum"
+ sanitizeRCDATAFuncName = "_sanitizeRCDATA"
+ sanitizeScriptFuncName = "_sanitizeScript"
+ sanitizeStyleFuncName = "_sanitizeStyle"
+ sanitizeStyleSheetFuncName = "_sanitizeStyleSheet"
+ sanitizeTargetEnumFuncName = "_sanitizeTargetEnum"
+ sanitizeTrustedResourceURLFuncName = "_sanitizeTrustedResourceURL"
+ sanitizeTrustedResourceURLOrURLFuncName = "_sanitizeTrustedResourceURLOrURL"
+ sanitizeURLFuncName = "_sanitizeURL"
+ sanitizeURLSetFuncName = "_sanitizeURLSet"
+)
+
+// urlLinkRelVals contains values for a link element's rel attribute that indicate that the same link
+// element's href attribute may contain a safehtml.URL value.
+var urlLinkRelVals = map[string]bool{
+ "alternate": true,
+ "author": true,
+ "bookmark": true,
+ "canonical": true,
+ "cite": true,
+ "dns-prefetch": true,
+ "help": true,
+ "icon": true,
+ "license": true,
+ "next": true,
+ "preconnect": true,
+ "prefetch": true,
+ "preload": true,
+ "prerender": true,
+ "prev": true,
+ "search": true,
+ "subresource": true,
+}
+
+// elementSpecificAttrValSanitizationContext[x][y] is the sanitization context for
+// attribute x when it appears within element y.
+var elementSpecificAttrValSanitizationContext = map[string]map[string]sanitizationContext{
+ "accept": {
+ "input": sanitizationContextNone,
+ },
+ "action": {
+ "form": sanitizationContextURL,
+ },
+ "defer": {
+ "script": sanitizationContextNone,
+ },
+ "formaction": {
+ "button": sanitizationContextURL,
+ "input": sanitizationContextURL,
+ },
+ "formmethod": {
+ "button": sanitizationContextNone,
+ "input": sanitizationContextNone,
+ },
+ "href": {
+ "a": sanitizationContextTrustedResourceURLOrURL,
+ "area": sanitizationContextTrustedResourceURLOrURL,
+ },
+ "method": {
+ "form": sanitizationContextNone,
+ },
+ "pattern": {
+ "input": sanitizationContextNone,
+ },
+ "readonly": {
+ "input": sanitizationContextNone,
+ "textarea": sanitizationContextNone,
+ },
+ "src": {
+ "audio": sanitizationContextTrustedResourceURLOrURL,
+ "img": sanitizationContextTrustedResourceURLOrURL,
+ "input": sanitizationContextTrustedResourceURLOrURL,
+ "source": sanitizationContextTrustedResourceURLOrURL,
+ "video": sanitizationContextTrustedResourceURLOrURL,
+ },
+ "srcdoc": {
+ "iframe": sanitizationContextHTMLValOnly,
+ },
+}
+
+// globalAttrValSanitizationContext[x] is the sanitization context for attribute x when
+// it appears within any element not in the key set of elementSpecificAttrValSanitizationContext[x].
+var globalAttrValSanitizationContext = map[string]sanitizationContext{
+ "align": sanitizationContextNone,
+ "alt": sanitizationContextNone,
+ "aria-activedescendant": sanitizationContextIdentifier,
+ "aria-atomic": sanitizationContextNone,
+ "aria-autocomplete": sanitizationContextNone,
+ "aria-busy": sanitizationContextNone,
+ "aria-checked": sanitizationContextNone,
+ "aria-controls": sanitizationContextIdentifier,
+ "aria-current": sanitizationContextNone,
+ "aria-disabled": sanitizationContextNone,
+ "aria-dropeffect": sanitizationContextNone,
+ "aria-expanded": sanitizationContextNone,
+ "aria-haspopup": sanitizationContextNone,
+ "aria-hidden": sanitizationContextNone,
+ "aria-invalid": sanitizationContextNone,
+ "aria-label": sanitizationContextNone,
+ "aria-labelledby": sanitizationContextIdentifier,
+ "aria-level": sanitizationContextNone,
+ "aria-live": sanitizationContextNone,
+ "aria-multiline": sanitizationContextNone,
+ "aria-multiselectable": sanitizationContextNone,
+ "aria-orientation": sanitizationContextNone,
+ "aria-owns": sanitizationContextIdentifier,
+ "aria-posinset": sanitizationContextNone,
+ "aria-pressed": sanitizationContextNone,
+ "aria-readonly": sanitizationContextNone,
+ "aria-relevant": sanitizationContextNone,
+ "aria-required": sanitizationContextNone,
+ "aria-selected": sanitizationContextNone,
+ "aria-setsize": sanitizationContextNone,
+ "aria-sort": sanitizationContextNone,
+ "aria-valuemax": sanitizationContextNone,
+ "aria-valuemin": sanitizationContextNone,
+ "aria-valuenow": sanitizationContextNone,
+ "aria-valuetext": sanitizationContextNone,
+ "async": sanitizationContextAsyncEnum,
+ "autocapitalize": sanitizationContextNone,
+ "autocomplete": sanitizationContextNone,
+ "autocorrect": sanitizationContextNone,
+ "autofocus": sanitizationContextNone,
+ "autoplay": sanitizationContextNone,
+ "bgcolor": sanitizationContextNone,
+ "border": sanitizationContextNone,
+ "cellpadding": sanitizationContextNone,
+ "cellspacing": sanitizationContextNone,
+ "checked": sanitizationContextNone,
+ "cite": sanitizationContextURL,
+ "class": sanitizationContextNone,
+ "color": sanitizationContextNone,
+ "cols": sanitizationContextNone,
+ "colspan": sanitizationContextNone,
+ "contenteditable": sanitizationContextNone,
+ "controls": sanitizationContextNone,
+ "datetime": sanitizationContextNone,
+ "dir": sanitizationContextDirEnum,
+ "disabled": sanitizationContextNone,
+ "download": sanitizationContextNone,
+ "draggable": sanitizationContextNone,
+ "enctype": sanitizationContextNone,
+ "face": sanitizationContextNone,
+ "for": sanitizationContextIdentifier,
+ "formenctype": sanitizationContextNone,
+ "frameborder": sanitizationContextNone,
+ "height": sanitizationContextNone,
+ "hidden": sanitizationContextNone,
+ "href": sanitizationContextTrustedResourceURL,
+ "hreflang": sanitizationContextNone,
+ "id": sanitizationContextIdentifier,
+ "ismap": sanitizationContextNone,
+ "itemid": sanitizationContextNone,
+ "itemprop": sanitizationContextNone,
+ "itemref": sanitizationContextNone,
+ "itemscope": sanitizationContextNone,
+ "itemtype": sanitizationContextNone,
+ "label": sanitizationContextNone,
+ "lang": sanitizationContextNone,
+ "list": sanitizationContextIdentifier,
+ "loading": sanitizationContextLoadingEnum,
+ "loop": sanitizationContextNone,
+ "max": sanitizationContextNone,
+ "maxlength": sanitizationContextNone,
+ "media": sanitizationContextNone,
+ "min": sanitizationContextNone,
+ "minlength": sanitizationContextNone,
+ "multiple": sanitizationContextNone,
+ "muted": sanitizationContextNone,
+ "name": sanitizationContextIdentifier,
+ "nonce": sanitizationContextNone,
+ "open": sanitizationContextNone,
+ "placeholder": sanitizationContextNone,
+ "poster": sanitizationContextURL,
+ "preload": sanitizationContextNone,
+ "rel": sanitizationContextNone,
+ "required": sanitizationContextNone,
+ "reversed": sanitizationContextNone,
+ "role": sanitizationContextNone,
+ "rows": sanitizationContextNone,
+ "rowspan": sanitizationContextNone,
+ "selected": sanitizationContextNone,
+ "shape": sanitizationContextNone,
+ "size": sanitizationContextNone,
+ "sizes": sanitizationContextNone,
+ "slot": sanitizationContextNone,
+ "span": sanitizationContextNone,
+ "spellcheck": sanitizationContextNone,
+ "src": sanitizationContextTrustedResourceURL,
+ "srcset": sanitizationContextURLSet,
+ "start": sanitizationContextNone,
+ "step": sanitizationContextNone,
+ "style": sanitizationContextStyle,
+ "summary": sanitizationContextNone,
+ "tabindex": sanitizationContextNone,
+ "target": sanitizationContextTargetEnum,
+ "title": sanitizationContextNone,
+ "translate": sanitizationContextNone,
+ "type": sanitizationContextNone,
+ "valign": sanitizationContextNone,
+ "value": sanitizationContextNone,
+ "width": sanitizationContextNone,
+ "wrap": sanitizationContextNone,
+}
+
+// elementContentSanitizationContext maps element names to element content sanitization contexts.
+var elementContentSanitizationContext = map[string]sanitizationContext{
+ "a": sanitizationContextHTML,
+ "abbr": sanitizationContextHTML,
+ "address": sanitizationContextHTML,
+ "article": sanitizationContextHTML,
+ "aside": sanitizationContextHTML,
+ "audio": sanitizationContextHTML,
+ "b": sanitizationContextHTML,
+ "bdi": sanitizationContextHTML,
+ "bdo": sanitizationContextHTML,
+ "blockquote": sanitizationContextHTML,
+ "body": sanitizationContextHTML,
+ "button": sanitizationContextHTML,
+ "canvas": sanitizationContextHTML,
+ "caption": sanitizationContextHTML,
+ "center": sanitizationContextHTML,
+ "cite": sanitizationContextHTML,
+ "code": sanitizationContextHTML,
+ "colgroup": sanitizationContextHTML,
+ "command": sanitizationContextHTML,
+ "data": sanitizationContextHTML,
+ "datalist": sanitizationContextHTML,
+ "dd": sanitizationContextHTML,
+ "del": sanitizationContextHTML,
+ "details": sanitizationContextHTML,
+ "dfn": sanitizationContextHTML,
+ "dialog": sanitizationContextHTML,
+ "div": sanitizationContextHTML,
+ "dl": sanitizationContextHTML,
+ "dt": sanitizationContextHTML,
+ "em": sanitizationContextHTML,
+ "fieldset": sanitizationContextHTML,
+ "figcaption": sanitizationContextHTML,
+ "figure": sanitizationContextHTML,
+ "font": sanitizationContextHTML,
+ "footer": sanitizationContextHTML,
+ "form": sanitizationContextHTML,
+ "frame": sanitizationContextHTML,
+ "frameset": sanitizationContextHTML,
+ "h1": sanitizationContextHTML,
+ "h2": sanitizationContextHTML,
+ "h3": sanitizationContextHTML,
+ "h4": sanitizationContextHTML,
+ "h5": sanitizationContextHTML,
+ "h6": sanitizationContextHTML,
+ "head": sanitizationContextHTML,
+ "header": sanitizationContextHTML,
+ "html": sanitizationContextHTML,
+ "i": sanitizationContextHTML,
+ "iframe": sanitizationContextHTML,
+ "ins": sanitizationContextHTML,
+ "kbd": sanitizationContextHTML,
+ "label": sanitizationContextHTML,
+ "legend": sanitizationContextHTML,
+ "lh": sanitizationContextHTML,
+ "li": sanitizationContextHTML,
+ "main": sanitizationContextHTML,
+ "map": sanitizationContextHTML,
+ "mark": sanitizationContextHTML,
+ "menu": sanitizationContextHTML,
+ "meter": sanitizationContextHTML,
+ "nav": sanitizationContextHTML,
+ "noscript": sanitizationContextHTML,
+ "ol": sanitizationContextHTML,
+ "optgroup": sanitizationContextHTML,
+ "option": sanitizationContextHTML,
+ "output": sanitizationContextHTML,
+ "p": sanitizationContextHTML,
+ "picture": sanitizationContextHTML,
+ "pre": sanitizationContextHTML,
+ "progress": sanitizationContextHTML,
+ "q": sanitizationContextHTML,
+ "rb": sanitizationContextHTML,
+ "rp": sanitizationContextHTML,
+ "rt": sanitizationContextHTML,
+ "rtc": sanitizationContextHTML,
+ "ruby": sanitizationContextHTML,
+ "s": sanitizationContextHTML,
+ "samp": sanitizationContextHTML,
+ "script": sanitizationContextScript,
+ "section": sanitizationContextHTML,
+ "select": sanitizationContextHTML,
+ "slot": sanitizationContextHTML,
+ "small": sanitizationContextHTML,
+ "span": sanitizationContextHTML,
+ "strong": sanitizationContextHTML,
+ "style": sanitizationContextStyleSheet,
+ "sub": sanitizationContextHTML,
+ "summary": sanitizationContextHTML,
+ "sup": sanitizationContextHTML,
+ "table": sanitizationContextHTML,
+ "tbody": sanitizationContextHTML,
+ "td": sanitizationContextHTML,
+ "textarea": sanitizationContextRCDATA,
+ "tfoot": sanitizationContextHTML,
+ "th": sanitizationContextHTML,
+ "thead": sanitizationContextHTML,
+ "time": sanitizationContextHTML,
+ "title": sanitizationContextRCDATA,
+ "tr": sanitizationContextHTML,
+ "u": sanitizationContextHTML,
+ "ul": sanitizationContextHTML,
+ "var": sanitizationContextHTML,
+ "video": sanitizationContextHTML,
+}
+
+// allowedVoidElements is a set of names of void elements actions may appear in.
+var allowedVoidElements = map[string]bool{
+ "area": true,
+ "br": true,
+ "col": true,
+ "hr": true,
+ "img": true,
+ "input": true,
+ "link": true,
+ "param": true,
+ "source": true,
+ "track": true,
+ "wbr": true,
+}
+
+var sanitizeAsyncEnumValues = map[string]bool{
+ "async": true,
+}
+
+func sanitizeAsyncEnum(args ...interface{}) (string, error) {
+ input := safehtmlutil.Stringify(args...)
+ if sanitizeAsyncEnumValues[input] {
+ return input, nil
+ }
+ return "", fmt.Errorf(`expected one of the following strings: ["async"]`)
+}
+
+var sanitizeDirEnumValues = map[string]bool{
+ "auto": true,
+ "ltr": true,
+ "rtl": true,
+}
+
+func sanitizeDirEnum(args ...interface{}) (string, error) {
+ input := safehtmlutil.Stringify(args...)
+ if sanitizeDirEnumValues[input] {
+ return input, nil
+ }
+ return "", fmt.Errorf(`expected one of the following strings: ["auto" "ltr" "rtl"]`)
+}
+
+func sanitizeHTML(args ...interface{}) (string, error) {
+ if len(args) > 0 {
+ if safeTypeValue, ok := safehtmlutil.Indirect(args[0]).(safehtml.HTML); ok {
+ return safeTypeValue.String(), nil
+ }
+ }
+ input := safehtmlutil.Stringify(args...)
+ return safehtml.HTMLEscaped(input).String(), nil
+}
+
+func sanitizeHTMLValOnly(args ...interface{}) (string, error) {
+ if len(args) > 0 {
+ if safeTypeValue, ok := safehtmlutil.Indirect(args[0]).(safehtml.HTML); ok {
+ return safeTypeValue.String(), nil
+ }
+ }
+ return "", fmt.Errorf(`expected a safehtml.HTML value`)
+}
+
+func sanitizeIdentifier(args ...interface{}) (string, error) {
+ if len(args) > 0 {
+ if safeTypeValue, ok := safehtmlutil.Indirect(args[0]).(safehtml.Identifier); ok {
+ return safeTypeValue.String(), nil
+ }
+ }
+ return "", fmt.Errorf(`expected a safehtml.Identifier value`)
+}
+
+var sanitizeLoadingEnumValues = map[string]bool{
+ "eager": true,
+ "lazy": true,
+}
+
+func sanitizeLoadingEnum(args ...interface{}) (string, error) {
+ input := safehtmlutil.Stringify(args...)
+ if sanitizeLoadingEnumValues[input] {
+ return input, nil
+ }
+ return "", fmt.Errorf(`expected one of the following strings: ["eager" "lazy"]`)
+}
+
+func sanitizeRCDATA(args ...interface{}) (string, error) {
+ input := safehtmlutil.Stringify(args...)
+ return safehtml.HTMLEscaped(input).String(), nil
+}
+
+func sanitizeScript(args ...interface{}) (string, error) {
+ if len(args) > 0 {
+ if safeTypeValue, ok := safehtmlutil.Indirect(args[0]).(safehtml.Script); ok {
+ return safeTypeValue.String(), nil
+ }
+ }
+ return "", fmt.Errorf(`expected a safehtml.Script value`)
+}
+
+func sanitizeStyle(args ...interface{}) (string, error) {
+ if len(args) > 0 {
+ if safeTypeValue, ok := safehtmlutil.Indirect(args[0]).(safehtml.Style); ok {
+ return safeTypeValue.String(), nil
+ }
+ }
+ return "", fmt.Errorf(`expected a safehtml.Style value`)
+}
+
+func sanitizeStyleSheet(args ...interface{}) (string, error) {
+ if len(args) > 0 {
+ if safeTypeValue, ok := safehtmlutil.Indirect(args[0]).(safehtml.StyleSheet); ok {
+ return safeTypeValue.String(), nil
+ }
+ }
+ return "", fmt.Errorf(`expected a safehtml.StyleSheet value`)
+}
+
+var sanitizeTargetEnumValues = map[string]bool{
+ "_blank": true,
+ "_self": true,
+}
+
+func sanitizeTargetEnum(args ...interface{}) (string, error) {
+ input := safehtmlutil.Stringify(args...)
+ if sanitizeTargetEnumValues[input] {
+ return input, nil
+ }
+ return "", fmt.Errorf(`expected one of the following strings: ["_blank" "_self"]`)
+}
+
+func sanitizeTrustedResourceURL(args ...interface{}) (string, error) {
+ if len(args) > 0 {
+ if safeTypeValue, ok := safehtmlutil.Indirect(args[0]).(safehtml.TrustedResourceURL); ok {
+ return safeTypeValue.String(), nil
+ }
+ }
+ return "", fmt.Errorf(`expected a safehtml.TrustedResourceURL value`)
+}
+
+func sanitizeTrustedResourceURLOrURL(args ...interface{}) (string, error) {
+ if len(args) > 0 {
+ switch v := safehtmlutil.Indirect(args[0]).(type) {
+ case safehtml.TrustedResourceURL, safehtml.URL:
+ return safehtmlutil.Stringify(v), nil
+ }
+ }
+ input := safehtmlutil.Stringify(args...)
+ return safehtml.URLSanitized(input).String(), nil
+}
+
+func sanitizeURL(args ...interface{}) (string, error) {
+ if len(args) > 0 {
+ if safeTypeValue, ok := safehtmlutil.Indirect(args[0]).(safehtml.URL); ok {
+ return safeTypeValue.String(), nil
+ }
+ }
+ input := safehtmlutil.Stringify(args...)
+ return safehtml.URLSanitized(input).String(), nil
+}
+
+func sanitizeURLSet(args ...interface{}) (string, error) {
+ input := safehtmlutil.Stringify(args...)
+ return safehtml.URLSetSanitized(input).String(), nil
+}
diff --git a/vendor/github.com/google/safehtml/template/state_string.go b/vendor/github.com/google/safehtml/template/state_string.go
new file mode 100644
index 000000000..afbbf5a0d
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/state_string.go
@@ -0,0 +1,16 @@
+// Code generated by "stringer -type State"; DO NOT EDIT
+
+package template
+
+import "fmt"
+
+const _State_name = "StateTextStateSpecialElementBodyStateTagStateAttrNameStateAfterNameStateBeforeValueStateHTMLCmtStateAttrStateError"
+
+var _State_index = [...]uint16{0, 9, 32, 40, 53, 67, 83, 95, 104, 114}
+
+func (i state) String() string {
+ if i >= state(len(_State_index)-1) {
+ return fmt.Sprintf("state(%d)", i)
+ }
+ return _State_name[_State_index[i]:_State_index[i+1]]
+}
diff --git a/vendor/github.com/google/safehtml/template/template.go b/vendor/github.com/google/safehtml/template/template.go
new file mode 100644
index 000000000..efd0ef610
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/template.go
@@ -0,0 +1,651 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "path/filepath"
+ "sync"
+ "text/template"
+ "text/template/parse"
+
+ "log"
+ "github.com/google/safehtml"
+ "github.com/google/safehtml/uncheckedconversions"
+)
+
+// Template is a specialized Template from "text/template" that produces a safe
+// HTML document fragment.
+type Template struct {
+ // Sticky error if escaping fails, or errEscapeOK if succeeded.
+ escapeErr error
+ // We could embed the text/template field, but it's safer not to because
+ // we need to keep our version of the name space and the underlying
+ // template's in sync.
+ text *template.Template
+ // The underlying template's parse tree, updated to be HTML-safe.
+ Tree *parse.Tree
+ *nameSpace // common to all associated templates
+}
+
+// errEscapeOK is a sentinel value used to indicate valid escaping.
+var errEscapeOK = fmt.Errorf("template escaped correctly")
+
+// nameSpace is the data structure shared by all templates in an association.
+type nameSpace struct {
+ mu sync.Mutex
+ set map[string]*Template
+ escaped bool
+ // cspCompatible indicates whether inline event handlers and
+ // javascript: URIs are disallowed in templates in this namespace.
+ cspCompatible bool
+ esc escaper
+}
+
+// Templates returns a slice of the templates associated with t, including t
+// itself.
+func (t *Template) Templates() []*Template {
+ ns := t.nameSpace
+ ns.mu.Lock()
+ defer ns.mu.Unlock()
+ // Return a slice so we don't expose the map.
+ m := make([]*Template, 0, len(ns.set))
+ for _, v := range ns.set {
+ m = append(m, v)
+ }
+ return m
+}
+
+// Option sets options for the template. Options are described by
+// strings, either a simple string or "key=value". There can be at
+// most one equals sign in an option string. If the option string
+// is unrecognized or otherwise invalid, Option panics.
+//
+// Known options:
+//
+// missingkey: Control the behavior during execution if a map is
+// indexed with a key that is not present in the map.
+// "missingkey=default" or "missingkey=invalid"
+// The default behavior: Do nothing and continue execution.
+// If printed, the result of the index operation is the string
+// "<no value>".
+// "missingkey=zero"
+// The operation returns the zero value for the map type's element.
+// "missingkey=error"
+// Execution stops immediately with an error.
+//
+func (t *Template) Option(opt ...string) *Template {
+ t.text.Option(opt...)
+ return t
+}
+
+// checkCanParse checks whether it is OK to parse templates.
+// If not, it returns an error.
+func (t *Template) checkCanParse() error {
+ if t == nil {
+ return nil
+ }
+ t.nameSpace.mu.Lock()
+ defer t.nameSpace.mu.Unlock()
+ if t.nameSpace.escaped {
+ return fmt.Errorf("html/template: cannot Parse after Execute")
+ }
+ return nil
+}
+
+// escape escapes all associated templates.
+func (t *Template) escape() error {
+ t.nameSpace.mu.Lock()
+ defer t.nameSpace.mu.Unlock()
+ t.nameSpace.escaped = true
+ if t.escapeErr == nil {
+ if t.Tree == nil {
+ return fmt.Errorf("template: %q is an incomplete or empty template", t.Name())
+ }
+ if err := escapeTemplate(t, t.text.Root, t.Name()); err != nil {
+ return err
+ }
+ } else if t.escapeErr != errEscapeOK {
+ return t.escapeErr
+ }
+ return nil
+}
+
+// Execute applies a parsed template to the specified data object,
+// writing the output to wr.
+// If an error occurs executing the template or writing its output,
+// execution stops, but partial results may already have been written to
+// the output writer.
+// A template may be executed safely in parallel, although if parallel
+// executions share a Writer the output may be interleaved.
+func (t *Template) Execute(wr io.Writer, data interface{}) error {
+ if err := t.escape(); err != nil {
+ return err
+ }
+ return t.text.Execute(wr, data)
+}
+
+// ExecuteToHTML applies a parsed template to the specified data object,
+// returning the output as a safehtml.HTML value.
+// A template may be executed safely in parallel.
+func (t *Template) ExecuteToHTML(data interface{}) (safehtml.HTML, error) {
+ var buf bytes.Buffer
+ if err := t.Execute(&buf, data); err != nil {
+ return safehtml.HTML{}, err
+ }
+ return uncheckedconversions.HTMLFromStringKnownToSatisfyTypeContract(buf.String()), nil
+}
+
+// MustParseAndExecuteToHTML is a helper that returns the safehtml.HTML value produced
+// by parsing text as a template body and executing it with no data. Any errors
+// encountered parsing or executing the template are fatal. This function is intended
+// to produce safehtml.HTML values from static HTML snippets such as
+//
+// html := MustParseAndExecuteToHTML("<b>Important</b>")
+//
+// To guarantee that the template body is never controlled by an attacker, text
+// must be an untyped string constant, which is always under programmer control.
+func MustParseAndExecuteToHTML(text stringConstant) safehtml.HTML {
+ t, err := New("").Parse(text)
+ if err != nil {
+ log.Fatal(err)
+ }
+ html, err := t.ExecuteToHTML(nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return html
+}
+
+// ExecuteTemplate applies the template associated with t that has the given
+// name to the specified data object and writes the output to wr.
+// If an error occurs executing the template or writing its output,
+// execution stops, but partial results may already have been written to
+// the output writer.
+// A template may be executed safely in parallel, although if parallel
+// executions share a Writer the output may be interleaved.
+func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
+ tmpl, err := t.lookupAndEscapeTemplate(name)
+ if err != nil {
+ return err
+ }
+ return tmpl.text.Execute(wr, data)
+}
+
+// ExecuteTemplateToHTML applies the template associated with t that has
+// the given name to the specified data object and returns the output as
+// a safehtml.HTML value.
+// A template may be executed safely in parallel.
+func (t *Template) ExecuteTemplateToHTML(name string, data interface{}) (safehtml.HTML, error) {
+ var buf bytes.Buffer
+ if err := t.ExecuteTemplate(&buf, name, data); err != nil {
+ return safehtml.HTML{}, err
+ }
+ return uncheckedconversions.HTMLFromStringKnownToSatisfyTypeContract(buf.String()), nil
+}
+
+// lookupAndEscapeTemplate guarantees that the template with the given name
+// is escaped, or returns an error if it cannot be. It returns the named
+// template.
+func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err error) {
+ t.nameSpace.mu.Lock()
+ defer t.nameSpace.mu.Unlock()
+ t.nameSpace.escaped = true
+ tmpl = t.set[name]
+ if tmpl == nil {
+ return nil, fmt.Errorf("html/template: %q is undefined", name)
+ }
+ if tmpl.escapeErr != nil && tmpl.escapeErr != errEscapeOK {
+ return nil, tmpl.escapeErr
+ }
+ if tmpl.text.Tree == nil || tmpl.text.Root == nil {
+ return nil, fmt.Errorf("html/template: %q is an incomplete template", name)
+ }
+ if t.text.Lookup(name) == nil {
+ panic("html/template internal error: template escaping out of sync")
+ }
+ if tmpl.escapeErr == nil {
+ err = escapeTemplate(tmpl, tmpl.text.Root, name)
+ }
+ return tmpl, err
+}
+
+// DefinedTemplates returns a string listing the defined templates,
+// prefixed by the string "; defined templates are: ". If there are none,
+// it returns the empty string. Used to generate an error message.
+func (t *Template) DefinedTemplates() string {
+ return t.text.DefinedTemplates()
+}
+
+// Parse parses text as a template body for t.
+// Named template definitions ({{define ...}} or {{block ...}} statements) in text
+// define additional templates associated with t and are removed from the
+// definition of t itself.
+//
+// Templates can be redefined in successive calls to Parse,
+// before the first use of Execute on t or any associated template.
+// A template definition with a body containing only white space and comments
+// is considered empty and will not replace an existing template's body.
+// This allows using Parse to add new named template definitions without
+// overwriting the main template body.
+//
+// To guarantee that the template body is never controlled by an attacker, text
+// must be an untyped string constant, which is always under programmer control.
+func (t *Template) Parse(text stringConstant) (*Template, error) {
+ if err := t.checkCanParse(); err != nil {
+ return nil, err
+ }
+
+ ret, err := t.text.Parse(string(text))
+ if err != nil {
+ return nil, err
+ }
+
+ // In general, all the named templates might have changed underfoot.
+ // Regardless, some new ones may have been defined.
+ // The template.Template set has been updated; update ours.
+ t.nameSpace.mu.Lock()
+ defer t.nameSpace.mu.Unlock()
+ for _, v := range ret.Templates() {
+ name := v.Name()
+ tmpl := t.set[name]
+ if tmpl == nil {
+ tmpl = t.new(name)
+ }
+ tmpl.text = v
+ tmpl.Tree = v.Tree
+ }
+ return t, nil
+}
+
+// ParseFromTrustedTemplate parses tmpl as a template body for t.
+// Named template definitions ({{define ...}} or {{block ...}} statements) in text
+// define additional templates associated with t and are removed from the
+// definition of t itself.
+//
+// Templates can be redefined in successive calls to ParseFromTrustedTemplate,
+// before the first use of Execute on t or any associated template.
+// A template definition with a body containing only white space and comments
+// is considered empty and will not replace an existing template's body.
+// This allows using ParseFromTrustedTemplate to add new named template definitions without
+// overwriting the main template body.
+//
+// To guarantee that the template body is never controlled by an attacker, tmpl
+// is a TrustedTemplate, which is always under programmer control.
+func (t *Template) ParseFromTrustedTemplate(tmpl TrustedTemplate) (*Template, error) {
+ return t.Parse(stringConstant(tmpl.String()))
+}
+
+// Clone returns a duplicate of the template, including all associated
+// templates. The actual representation is not copied, but the name space of
+// associated templates is, so further calls to Parse in the copy will add
+// templates to the copy but not to the original. Clone can be used to prepare
+// common templates and use them with variant definitions for other templates
+// by adding the variants after the clone is made.
+//
+// It returns an error if t has already been executed.
+func (t *Template) Clone() (*Template, error) {
+ t.nameSpace.mu.Lock()
+ defer t.nameSpace.mu.Unlock()
+ if t.escapeErr != nil {
+ return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
+ }
+ textClone, err := t.text.Clone()
+ if err != nil {
+ return nil, err
+ }
+ ns := &nameSpace{set: make(map[string]*Template)}
+ ns.esc = makeEscaper(ns)
+ ret := &Template{
+ nil,
+ textClone,
+ textClone.Tree,
+ ns,
+ }
+ ret.set[ret.Name()] = ret
+ for _, x := range textClone.Templates() {
+ name := x.Name()
+ src := t.set[name]
+ if src == nil || src.escapeErr != nil {
+ return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
+ }
+ x.Tree = x.Tree.Copy()
+ ret.set[name] = &Template{
+ nil,
+ x,
+ x.Tree,
+ ret.nameSpace,
+ }
+ }
+ // Return the template associated with the name of this template.
+ return ret.set[ret.Name()], nil
+}
+
+// New allocates a new HTML template with the given name.
+func New(name string) *Template {
+ ns := &nameSpace{set: make(map[string]*Template)}
+ ns.esc = makeEscaper(ns)
+ tmpl := &Template{
+ nil,
+ template.New(name),
+ nil,
+ ns,
+ }
+ tmpl.set[name] = tmpl
+ return tmpl
+}
+
+// New allocates a new HTML template associated with the given one
+// and with the same delimiters. The association, which is transitive,
+// allows one template to invoke another with a {{template}} action.
+//
+// If a template with the given name already exists, the new HTML template
+// will replace it. The existing template will be reset and disassociated with
+// t.
+func (t *Template) New(name string) *Template {
+ t.nameSpace.mu.Lock()
+ defer t.nameSpace.mu.Unlock()
+ return t.new(name)
+}
+
+// new is the implementation of New, without the lock.
+func (t *Template) new(name string) *Template {
+ tmpl := &Template{
+ nil,
+ t.text.New(name),
+ nil,
+ t.nameSpace,
+ }
+ if existing, ok := tmpl.set[name]; ok {
+ emptyTmpl := New(existing.Name())
+ *existing = *emptyTmpl
+ }
+ tmpl.set[name] = tmpl
+ return tmpl
+}
+
+// Name returns the name of the template.
+func (t *Template) Name() string {
+ return t.text.Name()
+}
+
+// FuncMap is the type of the map defining the mapping from names to
+// functions. Each function must have either a single return value, or two
+// return values of which the second has type error. In that case, if the
+// second (error) argument evaluates to non-nil during execution, execution
+// terminates and Execute returns that error. FuncMap has the same base type
+// as FuncMap in "text/template", copied here so clients need not import
+// "text/template".
+type FuncMap map[string]interface{}
+
+// Funcs adds the elements of the argument map to the template's function map.
+// It must be called before the template is parsed.
+// It panics if a value in the map is not a function with appropriate return
+// type. However, it is legal to overwrite elements of the map. The return
+// value is the template, so calls can be chained.
+func (t *Template) Funcs(funcMap FuncMap) *Template {
+ t.text.Funcs(template.FuncMap(funcMap))
+ return t
+}
+
+// CSPCompatible causes this template to check template text for
+// Content Security Policy (CSP) compatibility. The template will return errors
+// at execution time if inline event handler attribute names or javascript:
+// URIs are found in template text.
+//
+// For example, the following templates will cause errors:
+// <span onclick="doThings();">A thing.</span> // inline event handler "onclick"
+// <a href="javascript:linkClicked()">foo</a> // javascript: URI present
+func (t *Template) CSPCompatible() *Template {
+ t.nameSpace.mu.Lock()
+ t.nameSpace.cspCompatible = true
+ t.nameSpace.mu.Unlock()
+ return t
+}
+
+// Delims sets the action delimiters to the specified strings, to be used in
+// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
+// definitions will inherit the settings. An empty delimiter stands for the
+// corresponding default: {{ or }}.
+// The return value is the template, so calls can be chained.
+func (t *Template) Delims(left, right string) *Template {
+ t.text.Delims(left, right)
+ return t
+}
+
+// Lookup returns the template with the given name that is associated with t,
+// or nil if there is no such template.
+func (t *Template) Lookup(name string) *Template {
+ t.nameSpace.mu.Lock()
+ defer t.nameSpace.mu.Unlock()
+ return t.set[name]
+}
+
+// Must is a helper that wraps a call to a function returning (*Template, error)
+// and panics if the error is non-nil. It is intended for use in variable initializations
+// such as
+// var t = template.Must(template.New("name").Parse("html"))
+func Must(t *Template, err error) *Template {
+ if err != nil {
+ panic(err)
+ }
+ return t
+}
+
+// stringConstant is an unexported string type. Users of this package cannot
+// create values of this type except by passing an untyped string constant to
+// functions which expect a stringConstant. This type must be used only in
+// function and method parameters.
+type stringConstant string
+
+func stringConstantsToStrings(strs []stringConstant) []string {
+ ret := make([]string, 0, len(strs))
+ for _, s := range strs {
+ ret = append(ret, string(s))
+ }
+ return ret
+}
+
+// ParseFiles creates a new Template and parses the template definitions from
+// the named files. The returned template's name will have the (base) name and
+// (parsed) contents of the first file. There must be at least one file.
+// If an error occurs, parsing stops and the returned *Template is nil.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+// For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template
+// named "foo", while "a/foo" is unavailable.
+//
+// To guarantee that filepaths, and thus template bodies, are never controlled by
+// an attacker, filenames must be untyped string constants, which are always under
+// programmer control.
+func ParseFiles(filenames ...stringConstant) (*Template, error) {
+ return parseFiles(nil, readFileOS, stringConstantsToStrings(filenames)...)
+}
+
+// ParseFilesFromTrustedSources creates a new Template and parses the template definitions from
+// the named files. The returned template's name will have the (base) name and
+// (parsed) contents of the first file. There must be at least one file.
+// If an error occurs, parsing stops and the returned *Template is nil.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+// For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template
+// named "foo", while "a/foo" is unavailable.
+//
+// To guarantee that filepaths, and thus template bodies, are never controlled by
+// an attacker, filenames must be trusted sources, which are always under programmer
+// or application control.
+func ParseFilesFromTrustedSources(filenames ...TrustedSource) (*Template, error) {
+ return parseFiles(nil, readFileOS, trustedSourcesToStrings(filenames)...)
+}
+
+// ParseFiles parses the named files and associates the resulting templates with
+// t. If an error occurs, parsing stops and the returned template is nil;
+// otherwise it is t. There must be at least one file.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+//
+// ParseFiles returns an error if t or any associated template has already been executed.
+//
+// To guarantee that filepaths, and thus template bodies, are never controlled by
+// an attacker, filenames must be untyped string constants, which are always under
+// programmer control.
+func (t *Template) ParseFiles(filenames ...stringConstant) (*Template, error) {
+ return parseFiles(t, readFileOS, stringConstantsToStrings(filenames)...)
+}
+
+// ParseFilesFromTrustedSources parses the named files and associates the resulting templates with
+// t. If an error occurs, parsing stops and the returned template is nil;
+// otherwise it is t. There must be at least one file.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+//
+// ParseFilesFromTrustedSources returns an error if t or any associated template has already been executed.
+//
+// To guarantee that filepaths, and thus template bodies, are never controlled by
+// an attacker, filenames must be trusted sources, which are always under programmer
+// or application control.
+func (t *Template) ParseFilesFromTrustedSources(filenames ...TrustedSource) (*Template, error) {
+ return parseFiles(t, readFileOS, trustedSourcesToStrings(filenames)...)
+}
+
+// parseFiles is the helper for the method and function. If the argument
+// template is nil, it is created from the first file.
+// readFile takes a filename and returns the file's basename and contents.
+func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) {
+ if err := t.checkCanParse(); err != nil {
+ return nil, err
+ }
+
+ if len(filenames) == 0 {
+ // Not really a problem, but be consistent.
+ return nil, fmt.Errorf("html/template: no files named in call to ParseFiles")
+ }
+ for _, filename := range filenames {
+ name, b, err := readFile(filename)
+ if err != nil {
+ return nil, err
+ }
+ s := stringConstant(b)
+ // First template becomes return value if not already defined,
+ // and we use that one for subsequent New calls to associate
+ // all the templates together. Also, if this file has the same name
+ // as t, this file becomes the contents of t, so
+ // t, err := New(name).Funcs(xxx).ParseFiles(name)
+ // works. Otherwise we create a new template associated with t.
+ var tmpl *Template
+ if t == nil {
+ t = New(name)
+ }
+ if name == t.Name() {
+ tmpl = t
+ } else {
+ tmpl = t.New(name)
+ }
+ _, err = tmpl.Parse(s)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return t, nil
+}
+
+// Copied with minor changes from
+// https://go.googlesource.com/go/+/refs/tags/go1.17.1/src/text/template/helper.go.
+func readFileOS(file string) (string, []byte, error) {
+ name := filepath.Base(file)
+ b, err := ioutil.ReadFile(file)
+ return name, b, err
+}
+
+// ParseGlob creates a new Template and parses the template definitions from the
+// files identified by the pattern, which must match at least one file. The
+// returned template will have the (base) name and (parsed) contents of the
+// first file matched by the pattern. ParseGlob is equivalent to calling
+// ParseFiles with the list of files matched by the pattern.
+//
+// To guarantee that the pattern, and thus the template bodies, is never controlled by
+// an attacker, pattern must be an untyped string constant, which is always under
+// programmer control.
+func ParseGlob(pattern stringConstant) (*Template, error) {
+ return parseGlob(nil, string(pattern))
+}
+
+// ParseGlobFromTrustedSource creates a new Template and parses the template definitions from the
+// files identified by the pattern, which must match at least one file. The
+// returned template will have the (base) name and (parsed) contents of the
+// first file matched by the pattern. ParseGlobFromTrustedSource is equivalent to calling
+// ParseFilesFromTrustedSources with the list of files matched by the pattern.
+//
+// To guarantee that the pattern, and thus the template bodies, is never controlled by
+// an attacker, pattern must be a trusted source, which is always under programmer or
+// application control.
+func ParseGlobFromTrustedSource(pattern TrustedSource) (*Template, error) {
+ return parseGlob(nil, pattern.String())
+}
+
+// ParseGlob parses the template definitions in the files identified by the
+// pattern and associates the resulting templates with t. The pattern is
+// processed by filepath.Glob and must match at least one file. ParseGlob is
+// equivalent to calling t.ParseFiles with the list of files matched by the
+// pattern.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+//
+// ParseGlob returns an error if t or any associated template has already been executed.
+//
+// To guarantee that the pattern, and thus the template bodies, is never controlled by
+// an attacker, pattern must be an untyped string constant, which is always under
+// programmer control.
+func (t *Template) ParseGlob(pattern stringConstant) (*Template, error) {
+ return parseGlob(t, string(pattern))
+}
+
+// ParseGlobFromTrustedSource parses the template definitions in the files identified by the
+// pattern and associates the resulting templates with t. The pattern is
+// processed by filepath.Glob and must match at least one file. ParseGlob is
+// equivalent to calling t.ParseFiles with the list of files matched by the
+// pattern.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+//
+// ParseGlobFromTrustedSource returns an error if t or any associated template has already been executed.
+//
+// To guarantee that the pattern, and thus the template bodies, is never controlled by
+// an attacker, pattern must be a trusted source, which is always under programmer or
+// application control.
+func (t *Template) ParseGlobFromTrustedSource(pattern TrustedSource) (*Template, error) {
+ return parseGlob(t, pattern.String())
+}
+
+// parseGlob is the implementation of the function and method ParseGlob.
+func parseGlob(t *Template, pattern string) (*Template, error) {
+ if err := t.checkCanParse(); err != nil {
+ return nil, err
+ }
+ filenames, err := filepath.Glob(pattern)
+ if err != nil {
+ return nil, err
+ }
+ if len(filenames) == 0 {
+ return nil, fmt.Errorf("html/template: pattern matches no files: %#q", pattern)
+ }
+ return parseFiles(t, readFileOS, filenames...)
+}
+
+// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
+// and whether the value has a meaningful truth value. This is the definition of
+// truth used by if and other such actions.
+func IsTrue(val interface{}) (truth, ok bool) {
+ return template.IsTrue(val)
+}
diff --git a/vendor/github.com/google/safehtml/template/testdata/dir1/parsefiles_t1.tmpl b/vendor/github.com/google/safehtml/template/testdata/dir1/parsefiles_t1.tmpl
new file mode 100644
index 000000000..9eaed387d
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/testdata/dir1/parsefiles_t1.tmpl
@@ -0,0 +1 @@
+T1 invokes T2: ({{template "T2"}}) \ No newline at end of file
diff --git a/vendor/github.com/google/safehtml/template/testdata/dir2/parsefiles_t2.tmpl b/vendor/github.com/google/safehtml/template/testdata/dir2/parsefiles_t2.tmpl
new file mode 100644
index 000000000..44e506732
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/testdata/dir2/parsefiles_t2.tmpl
@@ -0,0 +1 @@
+{{define "T2"}}This is T2{{end}} \ No newline at end of file
diff --git a/vendor/github.com/google/safehtml/template/testdata/glob_t0.tmpl b/vendor/github.com/google/safehtml/template/testdata/glob_t0.tmpl
new file mode 100644
index 000000000..cf01de40a
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/testdata/glob_t0.tmpl
@@ -0,0 +1 @@
+T0 invokes T1: ({{template "T1"}}) \ No newline at end of file
diff --git a/vendor/github.com/google/safehtml/template/testdata/glob_t1.tmpl b/vendor/github.com/google/safehtml/template/testdata/glob_t1.tmpl
new file mode 100644
index 000000000..7b5b2f399
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/testdata/glob_t1.tmpl
@@ -0,0 +1 @@
+{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}} \ No newline at end of file
diff --git a/vendor/github.com/google/safehtml/template/testdata/glob_t2.tmpl b/vendor/github.com/google/safehtml/template/testdata/glob_t2.tmpl
new file mode 100644
index 000000000..44e506732
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/testdata/glob_t2.tmpl
@@ -0,0 +1 @@
+{{define "T2"}}This is T2{{end}} \ No newline at end of file
diff --git a/vendor/github.com/google/safehtml/template/testdata/helpers_t1.tmpl b/vendor/github.com/google/safehtml/template/testdata/helpers_t1.tmpl
new file mode 100644
index 000000000..7b5b2f399
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/testdata/helpers_t1.tmpl
@@ -0,0 +1 @@
+{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}} \ No newline at end of file
diff --git a/vendor/github.com/google/safehtml/template/testdata/helpers_t2.tmpl b/vendor/github.com/google/safehtml/template/testdata/helpers_t2.tmpl
new file mode 100644
index 000000000..44e506732
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/testdata/helpers_t2.tmpl
@@ -0,0 +1 @@
+{{define "T2"}}This is T2{{end}} \ No newline at end of file
diff --git a/vendor/github.com/google/safehtml/template/testdata/share_t0.tmpl b/vendor/github.com/google/safehtml/template/testdata/share_t0.tmpl
new file mode 100644
index 000000000..4932a1816
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/testdata/share_t0.tmpl
@@ -0,0 +1 @@
+T0 ({{.}} version) invokes T1: ({{template `T1`}})
diff --git a/vendor/github.com/google/safehtml/template/testdata/share_t1.tmpl b/vendor/github.com/google/safehtml/template/testdata/share_t1.tmpl
new file mode 100644
index 000000000..7b5b2f399
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/testdata/share_t1.tmpl
@@ -0,0 +1 @@
+{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}} \ No newline at end of file
diff --git a/vendor/github.com/google/safehtml/template/transition.go b/vendor/github.com/google/safehtml/template/transition.go
new file mode 100644
index 000000000..e0882e489
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/transition.go
@@ -0,0 +1,312 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+ "bytes"
+ "strings"
+)
+
+// transitionFunc is the array of context transition functions for text nodes.
+// A transition function takes a context and template text input, and returns
+// the updated context and the number of bytes consumed from the front of the
+// input.
+var transitionFunc = [...]func(context, []byte) (context, int){
+ stateText: tText,
+ stateSpecialElementBody: tSpecialTagEnd,
+ stateTag: tTag,
+ stateAttrName: tAttrName,
+ stateAfterName: tAfterName,
+ stateBeforeValue: tBeforeValue,
+ stateHTMLCmt: tHTMLCmt,
+ stateAttr: tAttr,
+ stateError: tError,
+}
+
+var commentStart = []byte("<!--")
+var commentEnd = []byte("-->")
+
+// tText is the context transition function for the text state.
+func tText(c context, s []byte) (context, int) {
+ k := 0
+ for {
+ i := k + bytes.IndexByte(s[k:], '<')
+ if i < k || i+1 == len(s) {
+ return c, len(s)
+ } else if i+4 <= len(s) && bytes.Equal(commentStart, s[i:i+4]) {
+ return context{state: stateHTMLCmt}, i + 4
+ }
+ i++
+ end := false
+ if s[i] == '/' {
+ if i+1 == len(s) {
+ return c, len(s)
+ }
+ end, i = true, i+1
+ }
+ j, e := eatTagName(s, i)
+ if j != i {
+ // We've found an HTML tag.
+ ret := context{state: stateTag}
+ // Element name not needed if we are at the end of the element.
+ if !end {
+ ret.element = e
+ }
+ return ret, j
+ }
+ k = j
+ }
+}
+
+// specialElements contains the names of elements whose bodies are treated
+// differently by the parser and escaper from stateText.
+var specialElements = map[string]bool{
+ "script": true,
+ "style": true,
+ "textarea": true,
+ "title": true,
+}
+
+// voidElements contains the names of all void elements.
+// https://www.w3.org/TR/html5/syntax.html#void-elements
+var voidElements = map[string]bool{
+ "area": true,
+ "base": true,
+ "br": true,
+ "col": true,
+ "embed": true,
+ "hr": true,
+ "img": true,
+ "input": true,
+ "keygen": true,
+ "link": true,
+ "meta": true,
+ "param": true,
+ "source": true,
+ "track": true,
+ "wbr": true,
+}
+
+// tTag is the context transition function for the tag state.
+func tTag(c context, s []byte) (context, int) {
+ // Find the attribute name.
+ i := eatWhiteSpace(s, 0)
+ if i == len(s) {
+ return c, len(s)
+ }
+ if s[i] == '>' {
+ ret := context{
+ state: stateText,
+ element: c.element,
+ scriptType: c.scriptType,
+ linkRel: c.linkRel,
+ }
+ if specialElements[c.element.name] {
+ ret.state = stateSpecialElementBody
+ }
+ if c.element.name != "" && voidElements[c.element.name] {
+ // Special case: end of start tag of a void element.
+ // Discard unnecessary state, since this element have no content.
+ ret.element = element{}
+ ret.scriptType = ""
+ ret.linkRel = ""
+ }
+ return ret, i + 1
+ }
+ j, err := eatAttrName(s, i)
+ if err != nil {
+ return context{state: stateError, err: err}, len(s)
+ }
+ state := stateTag
+ if i == j {
+ return context{
+ state: stateError,
+ err: errorf(ErrBadHTML, nil, 0, "expected space, attr name, or end of tag, but got %q", s[i:]),
+ }, len(s)
+ }
+
+ if j == len(s) {
+ state = stateAttrName
+ } else {
+ state = stateAfterName
+ }
+ return context{
+ state: state,
+ element: c.element,
+ attr: attr{name: strings.ToLower(string(s[i:j]))},
+ linkRel: c.linkRel,
+ }, j
+}
+
+// tAttrName is the context transition function for stateAttrName.
+func tAttrName(c context, s []byte) (context, int) {
+ i, err := eatAttrName(s, 0)
+ if err != nil {
+ return context{state: stateError, err: err}, len(s)
+ } else if i != len(s) {
+ c.state = stateAfterName
+ }
+ return c, i
+}
+
+// tAfterName is the context transition function for stateAfterName.
+func tAfterName(c context, s []byte) (context, int) {
+ // Look for the start of the value.
+ i := eatWhiteSpace(s, 0)
+ if i == len(s) {
+ return c, len(s)
+ } else if s[i] != '=' {
+ // Occurs due to tag ending '>', and valueless attribute.
+ c.state = stateTag
+ return c, i
+ }
+ c.state = stateBeforeValue
+ // Consume the "=".
+ return c, i + 1
+}
+
+// tBeforeValue is the context transition function for stateBeforeValue.
+func tBeforeValue(c context, s []byte) (context, int) {
+ i := eatWhiteSpace(s, 0)
+ if i == len(s) {
+ return c, len(s)
+ }
+ // Find the attribute delimiter.
+ // TODO: consider disallowing single-quoted or unquoted attribute values completely, even in hardcoded template text.
+ delim := delimSpaceOrTagEnd
+ switch s[i] {
+ case '\'':
+ delim, i = delimSingleQuote, i+1
+ case '"':
+ delim, i = delimDoubleQuote, i+1
+ }
+ c.state, c.delim = stateAttr, delim
+ return c, i
+}
+
+// tHTMLCmt is the context transition function for stateHTMLCmt.
+func tHTMLCmt(c context, s []byte) (context, int) {
+ if i := bytes.Index(s, commentEnd); i != -1 {
+ return context{}, i + 3
+ }
+ return c, len(s)
+}
+
+var (
+ specialTagEndPrefix = []byte("</")
+ tagEndSeparators = []byte("> \t\n\f/")
+)
+
+// tSpecialTagEnd is the context transition function for raw text, RCDATA
+// script data, and stylesheet element states.
+func tSpecialTagEnd(c context, s []byte) (context, int) {
+ if specialElements[c.element.name] {
+ if i := indexTagEnd(s, []byte(c.element.name)); i != -1 {
+ return context{}, i
+ }
+ }
+ return c, len(s)
+}
+
+// indexTagEnd finds the index of a special tag end in a case insensitive way, or returns -1
+func indexTagEnd(s []byte, tag []byte) int {
+ res := 0
+ plen := len(specialTagEndPrefix)
+ for len(s) > 0 {
+ // Try to find the tag end prefix first
+ i := bytes.Index(s, specialTagEndPrefix)
+ if i == -1 {
+ return i
+ }
+ s = s[i+plen:]
+ // Try to match the actual tag if there is still space for it
+ if len(tag) <= len(s) && bytes.EqualFold(tag, s[:len(tag)]) {
+ s = s[len(tag):]
+ // Check the tag is followed by a proper separator
+ if len(s) > 0 && bytes.IndexByte(tagEndSeparators, s[0]) != -1 {
+ return res + i
+ }
+ res += len(tag)
+ }
+ res += i + plen
+ }
+ return -1
+}
+
+// tAttr is the context transition function for the attribute state.
+func tAttr(c context, s []byte) (context, int) {
+ return c, len(s)
+}
+
+// tError is the context transition function for the error state.
+func tError(c context, s []byte) (context, int) {
+ 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
+// equals sign.
+func eatAttrName(s []byte, i int) (int, *Error) {
+ for j := i; j < len(s); j++ {
+ switch s[j] {
+ case ' ', '\t', '\n', '\f', '\r', '=', '>':
+ return j, nil
+ case '\'', '"', '<':
+ // These result in a parse warning in HTML5 and are
+ // indicative of serious problems if seen in an attr
+ // name in a template.
+ return -1, errorf(ErrBadHTML, nil, 0, "%q in attribute name: %.32q", s[j:j+1], s)
+ default:
+ // No-op.
+ }
+ }
+ return len(s), nil
+}
+
+// asciiAlpha reports whether c is an ASCII letter.
+func asciiAlpha(c byte) bool {
+ return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z'
+}
+
+// asciiAlphaNum reports whether c is an ASCII letter or digit.
+func asciiAlphaNum(c byte) bool {
+ return asciiAlpha(c) || '0' <= c && c <= '9'
+}
+
+// eatTagName returns the largest j such that s[i:j] is a tag name and the tag name.
+func eatTagName(s []byte, i int) (int, element) {
+ if i == len(s) || !asciiAlpha(s[i]) {
+ return i, element{}
+ }
+ j := i + 1
+ for j < len(s) {
+ x := s[j]
+ if asciiAlphaNum(x) {
+ j++
+ continue
+ }
+ // Allow "x-y" or "x:y" but not "x-", "-y", or "x--y".
+ if (x == ':' || x == '-') && j+1 < len(s) && asciiAlphaNum(s[j+1]) {
+ j += 2
+ continue
+ }
+ break
+ }
+ return j, element{name: strings.ToLower(string(s[i:j]))}
+}
+
+// eatWhiteSpace returns the largest j such that s[i:j] is white space.
+func eatWhiteSpace(s []byte, i int) int {
+ for j := i; j < len(s); j++ {
+ switch s[j] {
+ case ' ', '\t', '\n', '\f', '\r':
+ // No-op.
+ default:
+ return j
+ }
+ }
+ return len(s)
+}
diff --git a/vendor/github.com/google/safehtml/template/trustedfs.go b/vendor/github.com/google/safehtml/template/trustedfs.go
new file mode 100644
index 000000000..80db11824
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/trustedfs.go
@@ -0,0 +1,98 @@
+// Copyright (c) 2021 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+//go:build go1.16
+// +build go1.16
+
+package template
+
+import (
+ "embed"
+ "fmt"
+ "io/fs"
+ "os"
+ "path"
+)
+
+// A TrustedFS is an immutable type referencing a filesystem (fs.FS)
+// under application control.
+//
+// In order to ensure that an attacker cannot influence the TrustedFS value, a
+// TrustedFS can be instantiated in only two ways. One way is from an embed.FS
+// with TrustedFSFromEmbed. It is assumed that embedded filesystems are under
+// the programmer's control. The other way is from a TrustedSource using
+// TrustedFSFromTrustedSource, in which case the guarantees and caveats of
+// TrustedSource apply.
+type TrustedFS struct {
+ fsys fs.FS
+}
+
+// TrustedFSFromEmbed constructs a TrustedFS from an embed.FS.
+func TrustedFSFromEmbed(fsys embed.FS) TrustedFS {
+ return TrustedFS{fsys: fsys}
+}
+
+// TrustedFSFromTrustedSource constructs a TrustedFS from the string in the
+// TrustedSource, which should refer to a directory.
+func TrustedFSFromTrustedSource(ts TrustedSource) TrustedFS {
+ return TrustedFS{fsys: os.DirFS(ts.src)}
+}
+
+// Sub returns a TrustedFS at a subdirectory of the receiver.
+// It works by calling fs.Sub on the receiver's fs.FS.
+func (tf TrustedFS) Sub(dir TrustedSource) (TrustedFS, error) {
+ subfs, err := fs.Sub(tf.fsys, dir.String())
+ return TrustedFS{fsys: subfs}, err
+}
+
+// ParseFS is like ParseFiles or ParseGlob but reads from the TrustedFS
+// instead of the host operating system's file system.
+// It accepts a list of glob patterns.
+// (Note that most file names serve as glob patterns matching only themselves.)
+//
+// The same behaviors listed for ParseFiles() apply to ParseFS too (e.g. using the base name
+// of the file as the template name).
+func ParseFS(tfs TrustedFS, patterns ...string) (*Template, error) {
+ return parseFS(nil, tfs.fsys, patterns)
+}
+
+// ParseFS is like ParseFiles or ParseGlob but reads from the TrustedFS
+// instead of the host operating system's file system.
+// It accepts a list of glob patterns.
+// (Note that most file names serve as glob patterns matching only themselves.)
+//
+// The same behaviors listed for ParseFiles() apply to ParseFS too (e.g. using the base name
+// of the file as the template name).
+func (t *Template) ParseFS(tfs TrustedFS, patterns ...string) (*Template, error) {
+ return parseFS(t, tfs.fsys, patterns)
+}
+
+// Copied from
+// https://go.googlesource.com/go/+/refs/tags/go1.17.1/src/text/template/helper.go.
+func parseFS(t *Template, fsys fs.FS, patterns []string) (*Template, error) {
+ var filenames []string
+ for _, pattern := range patterns {
+ list, err := fs.Glob(fsys, pattern)
+ if err != nil {
+ return nil, err
+ }
+ if len(list) == 0 {
+ return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
+ }
+ filenames = append(filenames, list...)
+ }
+ return parseFiles(t, readFileFS(fsys), filenames...)
+}
+
+// Copied with minor changes from
+// https://go.googlesource.com/go/+/refs/tags/go1.17.1/src/text/template/helper.go.
+func readFileFS(fsys fs.FS) func(string) (string, []byte, error) {
+ return func(file string) (string, []byte, error) {
+ name := path.Base(file)
+ b, err := fs.ReadFile(fsys, file)
+ return name, b, err
+ }
+}
diff --git a/vendor/github.com/google/safehtml/template/trustedsource.go b/vendor/github.com/google/safehtml/template/trustedsource.go
new file mode 100644
index 000000000..f64263948
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/trustedsource.go
@@ -0,0 +1,105 @@
+// Copyright (c) 2017 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+package template
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "flag"
+)
+
+// A TrustedSource is an immutable string-like type referencing
+// trusted template files under application control. It can be passed to
+// template-parsing functions and methods to safely load templates
+// without the risk of untrusted template execution.
+//
+// In order to ensure that an attacker cannot influence the TrustedSource
+// value, a TrustedSource can be instantiated only from untyped string
+// constants, command-line flags, and other application-controlled strings, but
+// never from arbitrary string values potentially representing untrusted user input.
+//
+// Note that TrustedSource's constructors cannot truly guarantee that the
+// templates it references are not attacker-controlled; it can guarantee only that
+// the path to the template itself is under application control. Users of these
+// constructors must ensure themselves that TrustedSource never references
+// attacker-controlled files or directories that contain such files.
+type TrustedSource struct {
+ // We declare a TrustedSource not as a string but as a struct wrapping a string
+ // to prevent construction of TrustedSource values through string conversion.
+ src string
+}
+
+// TrustedSourceFromConstant constructs a TrustedSource with its underlying
+// src set to the given src, which must be an untyped string constant.
+//
+// No runtime validation or sanitization is performed on src; being under
+// application control, it is simply assumed to comply with the TrustedSource type
+// contract.
+func TrustedSourceFromConstant(src stringConstant) TrustedSource {
+ return TrustedSource{string(src)}
+}
+
+// TrustedSourceFromConstantDir constructs a TrustedSource calling path/filepath.Join on
+// an application-controlled directory path, which must be an untyped string constant,
+// a TrustedSource, and a dynamic filename. It returns an error if filename contains
+// filepath or list separators, since this might cause the resulting path to reference a
+// file outside of the given directory.
+//
+// dir or src may be empty if either of these path segments are not required.
+func TrustedSourceFromConstantDir(dir stringConstant, src TrustedSource, filename string) (TrustedSource, error) {
+ if i := strings.IndexAny(filename, string([]rune{filepath.Separator, filepath.ListSeparator})); i != -1 {
+ return TrustedSource{}, fmt.Errorf("filename %q must not contain the separator %q", filename, filename[i])
+ }
+ if filename == ".." {
+ return TrustedSource{}, fmt.Errorf("filename must not be the special name %q", filename)
+ }
+ return TrustedSource{filepath.Join(string(dir), src.String(), filename)}, nil
+}
+
+// TrustedSourceJoin is a wrapper around path/filepath.Join that returns a
+// TrustedSource formed by joining the given path elements into a single path,
+// adding an OS-specific path separator if necessary.
+func TrustedSourceJoin(elem ...TrustedSource) TrustedSource {
+ return TrustedSource{filepath.Join(trustedSourcesToStrings(elem)...)}
+}
+
+// TrustedSourceFromFlag returns a TrustedSource containing the string
+// representation of the retrieved value of the flag.
+//
+// In a server setting, flags are part of the application's deployment
+// configuration and are hence considered application-controlled.
+func TrustedSourceFromFlag(value flag.Value) TrustedSource {
+ return TrustedSource{fmt.Sprint(value.String())}
+}
+
+// TrustedSourceFromEnvVar is a wrapper around os.Getenv that
+// returns a TrustedSource containing the value of the environment variable
+// named by the key. It returns the value, which will be empty if the variable
+// is not present. To distinguish between an empty value and an unset value,
+// use os.LookupEnv.
+//
+// In a server setting, environment variables are part of the application's
+// deployment configuration and are hence considered application-controlled.
+func TrustedSourceFromEnvVar(key stringConstant) TrustedSource {
+ return TrustedSource{os.Getenv(string(key))}
+}
+
+// String returns the string form of the TrustedSource.
+func (t TrustedSource) String() string {
+ return t.src
+}
+
+func trustedSourcesToStrings(paths []TrustedSource) []string {
+ ret := make([]string, 0, len(paths))
+ for _, p := range paths {
+ ret = append(ret, p.String())
+ }
+ return ret
+}
diff --git a/vendor/github.com/google/safehtml/template/trustedtemplate.go b/vendor/github.com/google/safehtml/template/trustedtemplate.go
new file mode 100644
index 000000000..bd3b1b46a
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/trustedtemplate.go
@@ -0,0 +1,36 @@
+// Copyright (c) 2017 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+package template
+
+// A TrustedTemplate is an immutable string-like type containing a
+// safehtml/template template body. It can be safely loaded as template
+// text without the risk of untrusted template execution.
+//
+// In order to ensure that an attacker cannot influence the TrustedTemplate
+// value, a TrustedTemplate can be instantiated only from untyped string constants,
+// and never from arbitrary string values potentially representing untrusted user input.
+//
+type TrustedTemplate struct {
+ // We declare a TrustedTemplate not as a string but as a struct wrapping a string
+ // to prevent construction of TrustedTemplate values through string conversion.
+ tmpl string
+}
+
+// MakeTrustedTemplate constructs a TrustedTemplate with its underlying
+// tmpl set to the given tmpl, which must be an untyped string constant.
+//
+// No runtime validation or sanitization is performed on tmpl; being under
+// application control, it is simply assumed to comply with the TrustedTemplate type
+// contract.
+func MakeTrustedTemplate(tmpl stringConstant) TrustedTemplate {
+ return TrustedTemplate{string(tmpl)}
+}
+
+// String returns the string form of the TrustedTemplate.
+func (t TrustedTemplate) String() string {
+ return t.tmpl
+}
diff --git a/vendor/github.com/google/safehtml/template/url.go b/vendor/github.com/google/safehtml/template/url.go
new file mode 100644
index 000000000..f63475fcf
--- /dev/null
+++ b/vendor/github.com/google/safehtml/template/url.go
@@ -0,0 +1,122 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+ "fmt"
+ "html"
+ "regexp"
+ "strings"
+
+ "github.com/google/safehtml/internal/safehtmlutil"
+ "github.com/google/safehtml"
+)
+
+// urlPrefixValidators maps URL and TrustedResourceURL sanitization contexts to functions return an error
+// if the given string is unsafe to use as a URL prefix in that sanitization context.
+var urlPrefixValidators = map[sanitizationContext]func(string) error{
+ sanitizationContextURL: validateURLPrefix,
+ sanitizationContextTrustedResourceURLOrURL: validateURLPrefix,
+ sanitizationContextTrustedResourceURL: validateTrustedResourceURLPrefix,
+}
+
+// startsWithFullySpecifiedSchemePattern matches strings that have a fully-specified scheme component.
+// See RFC 3986 Section 3.
+var startsWithFullySpecifiedSchemePattern = regexp.MustCompile(
+ `^[[:alpha:]](?:[[:alnum:]]|[+.-])*:`)
+
+// validateURLPrefix validates if the given non-empty prefix is a safe safehtml.URL prefix.
+//
+// Prefixes are considered unsafe if they end in an incomplete HTML character reference
+// or percent-encoding character triplet.
+//
+// If the prefix contains a fully-specified scheme component, it is considered safe only if
+// it starts with an allowed scheme. See safehtml.URLSanitized for more details.
+//
+// Otherwise, the prefix is safe only if it contains '/', '?', or '#', since the presence of any
+// of these runes ensures that this prefix, when combined with some arbitrary suffix, cannot be
+// interpreted as a part of a scheme.
+func validateURLPrefix(prefix string) error {
+ decoded, err := decodeURLPrefix(prefix)
+ if err != nil {
+ return err
+ }
+ switch {
+ case startsWithFullySpecifiedSchemePattern.MatchString(decoded):
+ if safehtml.URLSanitized(decoded).String() != decoded {
+ return fmt.Errorf("URL prefix %q contains an unsafe scheme", prefix)
+ }
+ case !strings.ContainsAny(decoded, "/?#"):
+ // If the URL prefix does not already have a ':' scheme delimiter, and does not contain
+ // '/', '?', or '#', any ':' following this prefix will be intepreted as a scheme
+ // delimiter, causing this URL prefix to be interpreted as being part of a scheme.
+ // e.g. `<a href="java{{ "script:" }}alert(1)>`
+ return fmt.Errorf("URL prefix %q is unsafe; it might be interpreted as part of a scheme", prefix)
+ }
+ return nil
+}
+
+// validateTrustedResourceURLPrefix validates if the given non-empty prefix is a safe
+// safehtml.TrustedResourceURL prefix.
+//
+// Prefixes are considered unsafe if they end in an incomplete HTML character reference
+// or percent-encoding character triplet.
+//
+// See safehtmlutil.IsSafeTrustedResourceURLPrefix for details on how the prefix is validated.
+func validateTrustedResourceURLPrefix(prefix string) error {
+ decoded, err := decodeURLPrefix(prefix)
+ if err != nil {
+ return err
+ }
+ if !safehtmlutil.IsSafeTrustedResourceURLPrefix(decoded) {
+ return fmt.Errorf("%q is a disallowed TrustedResourceURL prefix", prefix)
+ }
+ return nil
+}
+
+// endsWithPercentEncodingPrefixPattern matches strings that end in an incomplete
+// URL percent encoding triplet.
+//
+// See https://tools.ietf.org/html/rfc3986#section-2.1.
+var endsWithPercentEncodingPrefixPattern = regexp.MustCompile(
+ `%[[:xdigit:]]?$`)
+
+// containsWhitespaceOrControlPattern matches strings that contain ASCII whitespace
+// or control characters.
+var containsWhitespaceOrControlPattern = regexp.MustCompile(`[[:space:]]|[[:cntrl:]]`)
+
+// decodeURLPrefix returns the given prefix after it has been HTML-unescaped.
+// It returns an error if the prefix:
+// * ends in an incomplete HTML character reference before HTML-unescaping,
+// * ends in an incomplete percent-encoding character triplet after HTML-unescaping, or
+// * contains whitespace before or after HTML-unescaping.
+func decodeURLPrefix(prefix string) (string, error) {
+ if containsWhitespaceOrControlPattern.MatchString(prefix) {
+ return "", fmt.Errorf("URL prefix %q contains whitespace or control characters", prefix)
+ }
+ if err := validateDoesNotEndsWithCharRefPrefix(prefix); err != nil {
+ return "", fmt.Errorf("URL %s", err)
+ }
+ decoded := html.UnescapeString(prefix)
+ // Check again for whitespace that might have previously been masked by a HTML reference,
+ // such as in "javascript&NewLine;".
+ if containsWhitespaceOrControlPattern.MatchString(decoded) {
+ return "", fmt.Errorf("URL prefix %q contains whitespace or control characters", prefix)
+ }
+ if endsWithPercentEncodingPrefixPattern.MatchString(decoded) {
+ return "", fmt.Errorf("URL prefix %q ends with an incomplete percent-encoding character triplet", prefix)
+ }
+ return decoded, nil
+}
+
+func validateTrustedResourceURLSubstitution(args ...interface{}) (string, error) {
+ input := safehtmlutil.Stringify(args...)
+ if safehtmlutil.URLContainsDoubleDotSegment(input) {
+ // Reject substitutions containing the ".." dot-segment to prevent the final TrustedResourceURL from referencing
+ // a resource higher up in the path name hierarchy than the path specified in the prefix.
+ return "", fmt.Errorf(`cannot substitute %q after TrustedResourceURL prefix: ".." is disallowed`, input)
+ }
+ return input, nil
+}