aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/google
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2023-02-22 22:16:50 +0100
committerTaras Madan <tarasmadan@google.com>2023-02-24 12:47:23 +0100
commit4165372ec8fd142475a4e35fd0cf4f8042132208 (patch)
tree21cd62211b4dd80bee469054c5b65db77342333c /vendor/github.com/google
parent2b3ed821a493b8936c8bacfa6f8b4f1c90a00855 (diff)
dependencies: update
set go min requirements to 1.19 update dependencies update vendor
Diffstat (limited to 'vendor/github.com/google')
-rw-r--r--vendor/github.com/google/go-cmp/cmp/compare.go64
-rw-r--r--vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go44
-rw-r--r--vendor/github.com/google/go-cmp/cmp/internal/value/zero.go48
-rw-r--r--vendor/github.com/google/go-cmp/cmp/options.go10
-rw-r--r--vendor/github.com/google/go-cmp/cmp/path.go20
-rw-r--r--vendor/github.com/google/go-cmp/cmp/report_compare.go10
-rw-r--r--vendor/github.com/google/go-cmp/cmp/report_reflect.go11
-rw-r--r--vendor/github.com/google/go-cmp/cmp/report_slices.go25
-rw-r--r--vendor/github.com/google/go-cmp/cmp/report_text.go1
-rw-r--r--vendor/github.com/google/pprof/profile/encode.go76
-rw-r--r--vendor/github.com/google/pprof/profile/filter.go4
-rw-r--r--vendor/github.com/google/pprof/profile/legacy_profile.go1
-rw-r--r--vendor/github.com/google/pprof/profile/merge.go257
-rw-r--r--vendor/github.com/google/pprof/profile/profile.go52
-rw-r--r--vendor/github.com/google/pprof/profile/proto.go19
-rw-r--r--vendor/github.com/google/pprof/profile/prune.go26
-rw-r--r--vendor/github.com/google/safehtml/CONTRIBUTING.md29
-rw-r--r--vendor/github.com/google/safehtml/LICENSE27
-rw-r--r--vendor/github.com/google/safehtml/README.md17
-rw-r--r--vendor/github.com/google/safehtml/doc.go11
-rw-r--r--vendor/github.com/google/safehtml/html.go117
-rw-r--r--vendor/github.com/google/safehtml/identifier.go83
-rw-r--r--vendor/github.com/google/safehtml/init.go58
-rw-r--r--vendor/github.com/google/safehtml/internal/raw/raw.go31
-rw-r--r--vendor/github.com/google/safehtml/internal/safehtmlutil/safehtmlutil.go180
-rw-r--r--vendor/github.com/google/safehtml/internal/template/raw/raw.go16
-rw-r--r--vendor/github.com/google/safehtml/script.go90
-rw-r--r--vendor/github.com/google/safehtml/style.go304
-rw-r--r--vendor/github.com/google/safehtml/stylesheet.go111
-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
-rw-r--r--vendor/github.com/google/safehtml/trustedresourceurl.go195
-rw-r--r--vendor/github.com/google/safehtml/uncheckedconversions/uncheckedconversions.go131
-rw-r--r--vendor/github.com/google/safehtml/url.go127
-rw-r--r--vendor/github.com/google/safehtml/urlset.go167
-rw-r--r--vendor/github.com/google/uuid/go.mod1
58 files changed, 6028 insertions, 223 deletions
diff --git a/vendor/github.com/google/go-cmp/cmp/compare.go b/vendor/github.com/google/go-cmp/cmp/compare.go
index fd2b3a42b..087320da7 100644
--- a/vendor/github.com/google/go-cmp/cmp/compare.go
+++ b/vendor/github.com/google/go-cmp/cmp/compare.go
@@ -13,21 +13,21 @@
//
// The primary features of cmp are:
//
-// • When the default behavior of equality does not suit the needs of the test,
-// custom equality functions can override the equality operation.
-// For example, an equality function may report floats as equal so long as they
-// are within some tolerance of each other.
+// - When the default behavior of equality does not suit the test's needs,
+// custom equality functions can override the equality operation.
+// For example, an equality function may report floats as equal so long as
+// they are within some tolerance of each other.
//
-// • Types that have an Equal method may use that method to determine equality.
-// This allows package authors to determine the equality operation for the types
-// that they define.
+// - Types with an Equal method may use that method to determine equality.
+// This allows package authors to determine the equality operation
+// for the types that they define.
//
-// • If no custom equality functions are used and no Equal method is defined,
-// equality is determined by recursively comparing the primitive kinds on both
-// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
-// fields are not compared by default; they result in panics unless suppressed
-// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly
-// compared using the Exporter option.
+// - If no custom equality functions are used and no Equal method is defined,
+// equality is determined by recursively comparing the primitive kinds on
+// both values, much like reflect.DeepEqual. Unlike reflect.DeepEqual,
+// unexported fields are not compared by default; they result in panics
+// unless suppressed by using an Ignore option (see cmpopts.IgnoreUnexported)
+// or explicitly compared using the Exporter option.
package cmp
import (
@@ -45,25 +45,25 @@ import (
// Equal reports whether x and y are equal by recursively applying the
// following rules in the given order to x and y and all of their sub-values:
//
-// • Let S be the set of all Ignore, Transformer, and Comparer options that
-// remain after applying all path filters, value filters, and type filters.
-// If at least one Ignore exists in S, then the comparison is ignored.
-// If the number of Transformer and Comparer options in S is greater than one,
-// then Equal panics because it is ambiguous which option to use.
-// If S contains a single Transformer, then use that to transform the current
-// values and recursively call Equal on the output values.
-// If S contains a single Comparer, then use that to compare the current values.
-// Otherwise, evaluation proceeds to the next rule.
+// - Let S be the set of all Ignore, Transformer, and Comparer options that
+// remain after applying all path filters, value filters, and type filters.
+// If at least one Ignore exists in S, then the comparison is ignored.
+// If the number of Transformer and Comparer options in S is non-zero,
+// then Equal panics because it is ambiguous which option to use.
+// If S contains a single Transformer, then use that to transform
+// the current values and recursively call Equal on the output values.
+// If S contains a single Comparer, then use that to compare the current values.
+// Otherwise, evaluation proceeds to the next rule.
//
-// • If the values have an Equal method of the form "(T) Equal(T) bool" or
-// "(T) Equal(I) bool" where T is assignable to I, then use the result of
-// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
-// evaluation proceeds to the next rule.
+// - If the values have an Equal method of the form "(T) Equal(T) bool" or
+// "(T) Equal(I) bool" where T is assignable to I, then use the result of
+// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
+// evaluation proceeds to the next rule.
//
-// • Lastly, try to compare x and y based on their basic kinds.
-// Simple kinds like booleans, integers, floats, complex numbers, strings, and
-// channels are compared using the equivalent of the == operator in Go.
-// Functions are only equal if they are both nil, otherwise they are unequal.
+// - Lastly, try to compare x and y based on their basic kinds.
+// Simple kinds like booleans, integers, floats, complex numbers, strings,
+// and channels are compared using the equivalent of the == operator in Go.
+// Functions are only equal if they are both nil, otherwise they are unequal.
//
// Structs are equal if recursively calling Equal on all fields report equal.
// If a struct contains unexported fields, Equal panics unless an Ignore option
@@ -144,7 +144,7 @@ func rootStep(x, y interface{}) PathStep {
// so that they have the same parent type.
var t reflect.Type
if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() {
- t = reflect.TypeOf((*interface{})(nil)).Elem()
+ t = anyType
if vx.IsValid() {
vvx := reflect.New(t).Elem()
vvx.Set(vx)
@@ -639,7 +639,9 @@ type dynChecker struct{ curr, next int }
// Next increments the state and reports whether a check should be performed.
//
// Checks occur every Nth function call, where N is a triangular number:
+//
// 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
+//
// See https://en.wikipedia.org/wiki/Triangular_number
//
// This sequence ensures that the cost of checks drops significantly as
diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
index bc196b16c..a248e5436 100644
--- a/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
+++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
@@ -127,9 +127,9 @@ var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
// This function returns an edit-script, which is a sequence of operations
// needed to convert one list into the other. The following invariants for
// the edit-script are maintained:
-// • eq == (es.Dist()==0)
-// • nx == es.LenX()
-// • ny == es.LenY()
+// - eq == (es.Dist()==0)
+// - nx == es.LenX()
+// - ny == es.LenY()
//
// This algorithm is not guaranteed to be an optimal solution (i.e., one that
// produces an edit-script with a minimal Levenshtein distance). This algorithm
@@ -169,12 +169,13 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// A diagonal edge is equivalent to a matching symbol between both X and Y.
// Invariants:
- // • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
- // • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
+ // - 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
+ // - 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
//
// In general:
- // • fwdFrontier.X < revFrontier.X
- // • fwdFrontier.Y < revFrontier.Y
+ // - fwdFrontier.X < revFrontier.X
+ // - fwdFrontier.Y < revFrontier.Y
+ //
// Unless, it is time for the algorithm to terminate.
fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
@@ -195,19 +196,21 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// computing sub-optimal edit-scripts between two lists.
//
// The algorithm is approximately as follows:
- // • Searching for differences switches back-and-forth between
- // a search that starts at the beginning (the top-left corner), and
- // a search that starts at the end (the bottom-right corner). The goal of
- // the search is connect with the search from the opposite corner.
- // • As we search, we build a path in a greedy manner, where the first
- // match seen is added to the path (this is sub-optimal, but provides a
- // decent result in practice). When matches are found, we try the next pair
- // of symbols in the lists and follow all matches as far as possible.
- // • When searching for matches, we search along a diagonal going through
- // through the "frontier" point. If no matches are found, we advance the
- // frontier towards the opposite corner.
- // • This algorithm terminates when either the X coordinates or the
- // Y coordinates of the forward and reverse frontier points ever intersect.
+ // - Searching for differences switches back-and-forth between
+ // a search that starts at the beginning (the top-left corner), and
+ // a search that starts at the end (the bottom-right corner).
+ // The goal of the search is connect with the search
+ // from the opposite corner.
+ // - As we search, we build a path in a greedy manner,
+ // where the first match seen is added to the path (this is sub-optimal,
+ // but provides a decent result in practice). When matches are found,
+ // we try the next pair of symbols in the lists and follow all matches
+ // as far as possible.
+ // - When searching for matches, we search along a diagonal going through
+ // through the "frontier" point. If no matches are found,
+ // we advance the frontier towards the opposite corner.
+ // - This algorithm terminates when either the X coordinates or the
+ // Y coordinates of the forward and reverse frontier points ever intersect.
// This algorithm is correct even if searching only in the forward direction
// or in the reverse direction. We do both because it is commonly observed
@@ -389,6 +392,7 @@ type point struct{ X, Y int }
func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
// zigzag maps a consecutive sequence of integers to a zig-zag sequence.
+//
// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
func zigzag(x int) int {
if x&1 != 0 {
diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go b/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go
deleted file mode 100644
index 9147a2997..000000000
--- a/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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 value
-
-import (
- "math"
- "reflect"
-)
-
-// IsZero reports whether v is the zero value.
-// This does not rely on Interface and so can be used on unexported fields.
-func IsZero(v reflect.Value) bool {
- switch v.Kind() {
- case reflect.Bool:
- return v.Bool() == false
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return v.Int() == 0
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- return v.Uint() == 0
- case reflect.Float32, reflect.Float64:
- return math.Float64bits(v.Float()) == 0
- case reflect.Complex64, reflect.Complex128:
- return math.Float64bits(real(v.Complex())) == 0 && math.Float64bits(imag(v.Complex())) == 0
- case reflect.String:
- return v.String() == ""
- case reflect.UnsafePointer:
- return v.Pointer() == 0
- case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
- return v.IsNil()
- case reflect.Array:
- for i := 0; i < v.Len(); i++ {
- if !IsZero(v.Index(i)) {
- return false
- }
- }
- return true
- case reflect.Struct:
- for i := 0; i < v.NumField(); i++ {
- if !IsZero(v.Field(i)) {
- return false
- }
- }
- return true
- }
- return false
-}
diff --git a/vendor/github.com/google/go-cmp/cmp/options.go b/vendor/github.com/google/go-cmp/cmp/options.go
index e57b9eb53..1f9ca9c48 100644
--- a/vendor/github.com/google/go-cmp/cmp/options.go
+++ b/vendor/github.com/google/go-cmp/cmp/options.go
@@ -33,6 +33,7 @@ type Option interface {
}
// applicableOption represents the following types:
+//
// Fundamental: ignore | validator | *comparer | *transformer
// Grouping: Options
type applicableOption interface {
@@ -43,6 +44,7 @@ type applicableOption interface {
}
// coreOption represents the following types:
+//
// Fundamental: ignore | validator | *comparer | *transformer
// Filters: *pathFilter | *valuesFilter
type coreOption interface {
@@ -336,9 +338,9 @@ func (tr transformer) String() string {
// both implement T.
//
// The equality function must be:
-// • Symmetric: equal(x, y) == equal(y, x)
-// • Deterministic: equal(x, y) == equal(x, y)
-// • Pure: equal(x, y) does not modify x or y
+// - Symmetric: equal(x, y) == equal(y, x)
+// - Deterministic: equal(x, y) == equal(x, y)
+// - Pure: equal(x, y) does not modify x or y
func Comparer(f interface{}) Option {
v := reflect.ValueOf(f)
if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
@@ -430,7 +432,7 @@ func AllowUnexported(types ...interface{}) Option {
}
// Result represents the comparison result for a single node and
-// is provided by cmp when calling Result (see Reporter).
+// is provided by cmp when calling Report (see Reporter).
type Result struct {
_ [0]func() // Make Result incomparable
flags resultFlags
diff --git a/vendor/github.com/google/go-cmp/cmp/path.go b/vendor/github.com/google/go-cmp/cmp/path.go
index c71003463..a0a588502 100644
--- a/vendor/github.com/google/go-cmp/cmp/path.go
+++ b/vendor/github.com/google/go-cmp/cmp/path.go
@@ -41,13 +41,13 @@ type PathStep interface {
// The type of each valid value is guaranteed to be identical to Type.
//
// In some cases, one or both may be invalid or have restrictions:
- // • For StructField, both are not interface-able if the current field
- // is unexported and the struct type is not explicitly permitted by
- // an Exporter to traverse unexported fields.
- // • For SliceIndex, one may be invalid if an element is missing from
- // either the x or y slice.
- // • For MapIndex, one may be invalid if an entry is missing from
- // either the x or y map.
+ // - For StructField, both are not interface-able if the current field
+ // is unexported and the struct type is not explicitly permitted by
+ // an Exporter to traverse unexported fields.
+ // - For SliceIndex, one may be invalid if an element is missing from
+ // either the x or y slice.
+ // - For MapIndex, one may be invalid if an entry is missing from
+ // either the x or y map.
//
// The provided values must not be mutated.
Values() (vx, vy reflect.Value)
@@ -94,6 +94,7 @@ func (pa Path) Index(i int) PathStep {
// The simplified path only contains struct field accesses.
//
// For example:
+//
// MyMap.MySlices.MyField
func (pa Path) String() string {
var ss []string
@@ -108,6 +109,7 @@ func (pa Path) String() string {
// GoString returns the path to a specific node using Go syntax.
//
// For example:
+//
// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
func (pa Path) GoString() string {
var ssPre, ssPost []string
@@ -159,7 +161,7 @@ func (ps pathStep) String() string {
if ps.typ == nil {
return "<nil>"
}
- s := ps.typ.String()
+ s := value.TypeString(ps.typ, false)
if s == "" || strings.ContainsAny(s, "{}\n") {
return "root" // Type too simple or complex to print
}
@@ -282,7 +284,7 @@ type typeAssertion struct {
func (ta TypeAssertion) Type() reflect.Type { return ta.typ }
func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy }
-func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
+func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", value.TypeString(ta.typ, false)) }
// Transform is a transformation from the parent type to the current type.
type Transform struct{ *transform }
diff --git a/vendor/github.com/google/go-cmp/cmp/report_compare.go b/vendor/github.com/google/go-cmp/cmp/report_compare.go
index 1ef65ac1d..2050bf6b4 100644
--- a/vendor/github.com/google/go-cmp/cmp/report_compare.go
+++ b/vendor/github.com/google/go-cmp/cmp/report_compare.go
@@ -7,8 +7,6 @@ package cmp
import (
"fmt"
"reflect"
-
- "github.com/google/go-cmp/cmp/internal/value"
)
// numContextRecords is the number of surrounding equal records to print.
@@ -117,7 +115,7 @@ func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out
// For leaf nodes, format the value based on the reflect.Values alone.
// As a special case, treat equal []byte as a leaf nodes.
- isBytes := v.Type.Kind() == reflect.Slice && v.Type.Elem() == reflect.TypeOf(byte(0))
+ isBytes := v.Type.Kind() == reflect.Slice && v.Type.Elem() == byteType
isEqualBytes := isBytes && v.NumDiff+v.NumIgnored+v.NumTransformed == 0
if v.MaxDepth == 0 || isEqualBytes {
switch opts.DiffMode {
@@ -248,11 +246,11 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, pt
var isZero bool
switch opts.DiffMode {
case diffIdentical:
- isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY)
+ isZero = r.Value.ValueX.IsZero() || r.Value.ValueY.IsZero()
case diffRemoved:
- isZero = value.IsZero(r.Value.ValueX)
+ isZero = r.Value.ValueX.IsZero()
case diffInserted:
- isZero = value.IsZero(r.Value.ValueY)
+ isZero = r.Value.ValueY.IsZero()
}
if isZero {
continue
diff --git a/vendor/github.com/google/go-cmp/cmp/report_reflect.go b/vendor/github.com/google/go-cmp/cmp/report_reflect.go
index 287b89358..2ab41fad3 100644
--- a/vendor/github.com/google/go-cmp/cmp/report_reflect.go
+++ b/vendor/github.com/google/go-cmp/cmp/report_reflect.go
@@ -16,6 +16,13 @@ import (
"github.com/google/go-cmp/cmp/internal/value"
)
+var (
+ anyType = reflect.TypeOf((*interface{})(nil)).Elem()
+ stringType = reflect.TypeOf((*string)(nil)).Elem()
+ bytesType = reflect.TypeOf((*[]byte)(nil)).Elem()
+ byteType = reflect.TypeOf((*byte)(nil)).Elem()
+)
+
type formatValueOptions struct {
// AvoidStringer controls whether to avoid calling custom stringer
// methods like error.Error or fmt.Stringer.String.
@@ -184,7 +191,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
}
for i := 0; i < v.NumField(); i++ {
vv := v.Field(i)
- if value.IsZero(vv) {
+ if vv.IsZero() {
continue // Elide fields with zero values
}
if len(list) == maxLen {
@@ -205,7 +212,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
}
// Check whether this is a []byte of text data.
- if t.Elem() == reflect.TypeOf(byte(0)) {
+ if t.Elem() == byteType {
b := v.Bytes()
isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) }
if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {
diff --git a/vendor/github.com/google/go-cmp/cmp/report_slices.go b/vendor/github.com/google/go-cmp/cmp/report_slices.go
index 68b5c1ae1..23e444f62 100644
--- a/vendor/github.com/google/go-cmp/cmp/report_slices.go
+++ b/vendor/github.com/google/go-cmp/cmp/report_slices.go
@@ -104,7 +104,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
case t.Kind() == reflect.String:
sx, sy = vx.String(), vy.String()
isString = true
- case t.Kind() == reflect.Slice && t.Elem() == reflect.TypeOf(byte(0)):
+ case t.Kind() == reflect.Slice && t.Elem() == byteType:
sx, sy = string(vx.Bytes()), string(vy.Bytes())
isString = true
case t.Kind() == reflect.Array:
@@ -147,7 +147,10 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
})
efficiencyLines := float64(esLines.Dist()) / float64(len(esLines))
efficiencyBytes := float64(esBytes.Dist()) / float64(len(esBytes))
- isPureLinedText = efficiencyLines < 4*efficiencyBytes
+ quotedLength := len(strconv.Quote(sx + sy))
+ unquotedLength := len(sx) + len(sy)
+ escapeExpansionRatio := float64(quotedLength) / float64(unquotedLength)
+ isPureLinedText = efficiencyLines < 4*efficiencyBytes || escapeExpansionRatio > 1.1
}
}
@@ -171,12 +174,13 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
// differences in a string literal. This format is more readable,
// but has edge-cases where differences are visually indistinguishable.
// This format is avoided under the following conditions:
- // • A line starts with `"""`
- // • A line starts with "..."
- // • A line contains non-printable characters
- // • Adjacent different lines differ only by whitespace
+ // - A line starts with `"""`
+ // - A line starts with "..."
+ // - A line contains non-printable characters
+ // - Adjacent different lines differ only by whitespace
//
// For example:
+ //
// """
// ... // 3 identical lines
// foo
@@ -231,7 +235,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"}
switch t.Kind() {
case reflect.String:
- if t != reflect.TypeOf(string("")) {
+ if t != stringType {
out = opts.FormatType(t, out)
}
case reflect.Slice:
@@ -326,12 +330,12 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
switch t.Kind() {
case reflect.String:
out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
- if t != reflect.TypeOf(string("")) {
+ if t != stringType {
out = opts.FormatType(t, out)
}
case reflect.Slice:
out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
- if t != reflect.TypeOf([]byte(nil)) {
+ if t != bytesType {
out = opts.FormatType(t, out)
}
}
@@ -446,7 +450,6 @@ func (opts formatOptions) formatDiffSlice(
// {NumIdentical: 3},
// {NumInserted: 1},
// ]
-//
func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) {
var prevMode byte
lastStats := func(mode byte) *diffStats {
@@ -503,7 +506,6 @@ func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats)
// {NumIdentical: 8, NumRemoved: 12, NumInserted: 3},
// {NumIdentical: 63},
// ]
-//
func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats {
groups, groupsOrig := groups[:0], groups
for i, ds := range groupsOrig {
@@ -548,7 +550,6 @@ func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStat
// {NumRemoved: 9},
// {NumIdentical: 64}, // incremented by 10
// ]
-//
func cleanupSurroundingIdentical(groups []diffStats, eq func(i, j int) bool) []diffStats {
var ix, iy int // indexes into sequence x and y
for i, ds := range groups {
diff --git a/vendor/github.com/google/go-cmp/cmp/report_text.go b/vendor/github.com/google/go-cmp/cmp/report_text.go
index 0fd46d7ff..388fcf571 100644
--- a/vendor/github.com/google/go-cmp/cmp/report_text.go
+++ b/vendor/github.com/google/go-cmp/cmp/report_text.go
@@ -393,6 +393,7 @@ func (s diffStats) Append(ds diffStats) diffStats {
// String prints a humanly-readable summary of coalesced records.
//
// Example:
+//
// diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields"
func (s diffStats) String() string {
var ss []string
diff --git a/vendor/github.com/google/pprof/profile/encode.go b/vendor/github.com/google/pprof/profile/encode.go
index 96aa271e5..c8a1beb8a 100644
--- a/vendor/github.com/google/pprof/profile/encode.go
+++ b/vendor/github.com/google/pprof/profile/encode.go
@@ -184,12 +184,13 @@ var profileDecoder = []decoder{
// repeated Location location = 4
func(b *buffer, m message) error {
x := new(Location)
- x.Line = make([]Line, 0, 8) // Pre-allocate Line buffer
+ x.Line = b.tmpLines[:0] // Use shared space temporarily
pp := m.(*Profile)
pp.Location = append(pp.Location, x)
err := decodeMessage(b, x)
- var tmp []Line
- x.Line = append(tmp, x.Line...) // Shrink to allocated size
+ b.tmpLines = x.Line[:0]
+ // Copy to shrink size and detach from shared space.
+ x.Line = append([]Line(nil), x.Line...)
return err
},
// repeated Function function = 5
@@ -307,41 +308,52 @@ func (p *Profile) postDecode() error {
st.Unit, err = getString(p.stringTable, &st.unitX, err)
}
+ // Pre-allocate space for all locations.
+ numLocations := 0
for _, s := range p.Sample {
- labels := make(map[string][]string, len(s.labelX))
- numLabels := make(map[string][]int64, len(s.labelX))
- numUnits := make(map[string][]string, len(s.labelX))
- for _, l := range s.labelX {
- var key, value string
- key, err = getString(p.stringTable, &l.keyX, err)
- if l.strX != 0 {
- value, err = getString(p.stringTable, &l.strX, err)
- labels[key] = append(labels[key], value)
- } else if l.numX != 0 || l.unitX != 0 {
- numValues := numLabels[key]
- units := numUnits[key]
- if l.unitX != 0 {
- var unit string
- unit, err = getString(p.stringTable, &l.unitX, err)
- units = padStringArray(units, len(numValues))
- numUnits[key] = append(units, unit)
+ numLocations += len(s.locationIDX)
+ }
+ locBuffer := make([]*Location, numLocations)
+
+ for _, s := range p.Sample {
+ if len(s.labelX) > 0 {
+ labels := make(map[string][]string, len(s.labelX))
+ numLabels := make(map[string][]int64, len(s.labelX))
+ numUnits := make(map[string][]string, len(s.labelX))
+ for _, l := range s.labelX {
+ var key, value string
+ key, err = getString(p.stringTable, &l.keyX, err)
+ if l.strX != 0 {
+ value, err = getString(p.stringTable, &l.strX, err)
+ labels[key] = append(labels[key], value)
+ } else if l.numX != 0 || l.unitX != 0 {
+ numValues := numLabels[key]
+ units := numUnits[key]
+ if l.unitX != 0 {
+ var unit string
+ unit, err = getString(p.stringTable, &l.unitX, err)
+ units = padStringArray(units, len(numValues))
+ numUnits[key] = append(units, unit)
+ }
+ numLabels[key] = append(numLabels[key], l.numX)
}
- numLabels[key] = append(numLabels[key], l.numX)
}
- }
- if len(labels) > 0 {
- s.Label = labels
- }
- if len(numLabels) > 0 {
- s.NumLabel = numLabels
- for key, units := range numUnits {
- if len(units) > 0 {
- numUnits[key] = padStringArray(units, len(numLabels[key]))
+ if len(labels) > 0 {
+ s.Label = labels
+ }
+ if len(numLabels) > 0 {
+ s.NumLabel = numLabels
+ for key, units := range numUnits {
+ if len(units) > 0 {
+ numUnits[key] = padStringArray(units, len(numLabels[key]))
+ }
}
+ s.NumUnit = numUnits
}
- s.NumUnit = numUnits
}
- s.Location = make([]*Location, len(s.locationIDX))
+
+ s.Location = locBuffer[:len(s.locationIDX)]
+ locBuffer = locBuffer[len(s.locationIDX):]
for i, lid := range s.locationIDX {
if lid < uint64(len(locationIds)) {
s.Location[i] = locationIds[lid]
diff --git a/vendor/github.com/google/pprof/profile/filter.go b/vendor/github.com/google/pprof/profile/filter.go
index ea8e66c68..c794b9390 100644
--- a/vendor/github.com/google/pprof/profile/filter.go
+++ b/vendor/github.com/google/pprof/profile/filter.go
@@ -22,6 +22,10 @@ import "regexp"
// samples where at least one frame matches focus but none match ignore.
// Returns true is the corresponding regexp matched at least one sample.
func (p *Profile) FilterSamplesByName(focus, ignore, hide, show *regexp.Regexp) (fm, im, hm, hnm bool) {
+ if focus == nil && ignore == nil && hide == nil && show == nil {
+ fm = true // Missing focus implies a match
+ return
+ }
focusOrIgnore := make(map[uint64]bool)
hidden := make(map[uint64]bool)
for _, l := range p.Location {
diff --git a/vendor/github.com/google/pprof/profile/legacy_profile.go b/vendor/github.com/google/pprof/profile/legacy_profile.go
index 9ba9a77c9..8d07fd6c2 100644
--- a/vendor/github.com/google/pprof/profile/legacy_profile.go
+++ b/vendor/github.com/google/pprof/profile/legacy_profile.go
@@ -865,7 +865,6 @@ func parseThread(b []byte) (*Profile, error) {
// Recognize each thread and populate profile samples.
for !isMemoryMapSentinel(line) {
if strings.HasPrefix(line, "---- no stack trace for") {
- line = ""
break
}
if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 {
diff --git a/vendor/github.com/google/pprof/profile/merge.go b/vendor/github.com/google/pprof/profile/merge.go
index 6fcd11de1..4b66282cb 100644
--- a/vendor/github.com/google/pprof/profile/merge.go
+++ b/vendor/github.com/google/pprof/profile/merge.go
@@ -15,6 +15,7 @@
package profile
import (
+ "encoding/binary"
"fmt"
"sort"
"strconv"
@@ -58,7 +59,7 @@ func Merge(srcs []*Profile) (*Profile, error) {
for _, src := range srcs {
// Clear the profile-specific hash tables
- pm.locationsByID = make(map[uint64]*Location, len(src.Location))
+ pm.locationsByID = makeLocationIDMap(len(src.Location))
pm.functionsByID = make(map[uint64]*Function, len(src.Function))
pm.mappingsByID = make(map[uint64]mapInfo, len(src.Mapping))
@@ -136,7 +137,7 @@ type profileMerger struct {
p *Profile
// Memoization tables within a profile.
- locationsByID map[uint64]*Location
+ locationsByID locationIDMap
functionsByID map[uint64]*Function
mappingsByID map[uint64]mapInfo
@@ -153,6 +154,16 @@ type mapInfo struct {
}
func (pm *profileMerger) mapSample(src *Sample) *Sample {
+ // Check memoization table
+ k := pm.sampleKey(src)
+ if ss, ok := pm.samples[k]; ok {
+ for i, v := range src.Value {
+ ss.Value[i] += v
+ }
+ return ss
+ }
+
+ // Make new sample.
s := &Sample{
Location: make([]*Location, len(src.Location)),
Value: make([]int64, len(src.Value)),
@@ -177,52 +188,98 @@ func (pm *profileMerger) mapSample(src *Sample) *Sample {
s.NumLabel[k] = vv
s.NumUnit[k] = uu
}
- // Check memoization table. Must be done on the remapped location to
- // account for the remapped mapping. Add current values to the
- // existing sample.
- k := s.key()
- if ss, ok := pm.samples[k]; ok {
- for i, v := range src.Value {
- ss.Value[i] += v
- }
- return ss
- }
copy(s.Value, src.Value)
pm.samples[k] = s
pm.p.Sample = append(pm.p.Sample, s)
return s
}
-// key generates sampleKey to be used as a key for maps.
-func (sample *Sample) key() sampleKey {
- ids := make([]string, len(sample.Location))
- for i, l := range sample.Location {
- ids[i] = strconv.FormatUint(l.ID, 16)
+func (pm *profileMerger) sampleKey(sample *Sample) sampleKey {
+ // Accumulate contents into a string.
+ var buf strings.Builder
+ buf.Grow(64) // Heuristic to avoid extra allocs
+
+ // encode a number
+ putNumber := func(v uint64) {
+ var num [binary.MaxVarintLen64]byte
+ n := binary.PutUvarint(num[:], v)
+ buf.Write(num[:n])
+ }
+
+ // encode a string prefixed with its length.
+ putDelimitedString := func(s string) {
+ putNumber(uint64(len(s)))
+ buf.WriteString(s)
+ }
+
+ for _, l := range sample.Location {
+ // Get the location in the merged profile, which may have a different ID.
+ if loc := pm.mapLocation(l); loc != nil {
+ putNumber(loc.ID)
+ }
}
+ putNumber(0) // Delimiter
- labels := make([]string, 0, len(sample.Label))
- for k, v := range sample.Label {
- labels = append(labels, fmt.Sprintf("%q%q", k, v))
+ for _, l := range sortedKeys1(sample.Label) {
+ putDelimitedString(l)
+ values := sample.Label[l]
+ putNumber(uint64(len(values)))
+ for _, v := range values {
+ putDelimitedString(v)
+ }
}
- sort.Strings(labels)
- numlabels := make([]string, 0, len(sample.NumLabel))
- for k, v := range sample.NumLabel {
- numlabels = append(numlabels, fmt.Sprintf("%q%x%x", k, v, sample.NumUnit[k]))
+ for _, l := range sortedKeys2(sample.NumLabel) {
+ putDelimitedString(l)
+ values := sample.NumLabel[l]
+ putNumber(uint64(len(values)))
+ for _, v := range values {
+ putNumber(uint64(v))
+ }
+ units := sample.NumUnit[l]
+ putNumber(uint64(len(units)))
+ for _, v := range units {
+ putDelimitedString(v)
+ }
}
- sort.Strings(numlabels)
- return sampleKey{
- strings.Join(ids, "|"),
- strings.Join(labels, ""),
- strings.Join(numlabels, ""),
+ return sampleKey(buf.String())
+}
+
+type sampleKey string
+
+// sortedKeys1 returns the sorted keys found in a string->[]string map.
+//
+// Note: this is currently non-generic since github pprof runs golint,
+// which does not support generics. When that issue is fixed, it can
+// be merged with sortedKeys2 and made into a generic function.
+func sortedKeys1(m map[string][]string) []string {
+ if len(m) == 0 {
+ return nil
}
+ keys := make([]string, 0, len(m))
+ for k := range m {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ return keys
}
-type sampleKey struct {
- locations string
- labels string
- numlabels string
+// sortedKeys2 returns the sorted keys found in a string->[]int64 map.
+//
+// Note: this is currently non-generic since github pprof runs golint,
+// which does not support generics. When that issue is fixed, it can
+// be merged with sortedKeys1 and made into a generic function.
+func sortedKeys2(m map[string][]int64) []string {
+ if len(m) == 0 {
+ return nil
+ }
+ keys := make([]string, 0, len(m))
+ for k := range m {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ return keys
}
func (pm *profileMerger) mapLocation(src *Location) *Location {
@@ -230,7 +287,7 @@ func (pm *profileMerger) mapLocation(src *Location) *Location {
return nil
}
- if l, ok := pm.locationsByID[src.ID]; ok {
+ if l := pm.locationsByID.get(src.ID); l != nil {
return l
}
@@ -249,10 +306,10 @@ func (pm *profileMerger) mapLocation(src *Location) *Location {
// account for the remapped mapping ID.
k := l.key()
if ll, ok := pm.locations[k]; ok {
- pm.locationsByID[src.ID] = ll
+ pm.locationsByID.set(src.ID, ll)
return ll
}
- pm.locationsByID[src.ID] = l
+ pm.locationsByID.set(src.ID, l)
pm.locations[k] = l
pm.p.Location = append(pm.p.Location, l)
return l
@@ -480,3 +537,131 @@ func (p *Profile) compatible(pb *Profile) error {
func equalValueType(st1, st2 *ValueType) bool {
return st1.Type == st2.Type && st1.Unit == st2.Unit
}
+
+// locationIDMap is like a map[uint64]*Location, but provides efficiency for
+// ids that are densely numbered, which is often the case.
+type locationIDMap struct {
+ dense []*Location // indexed by id for id < len(dense)
+ sparse map[uint64]*Location // indexed by id for id >= len(dense)
+}
+
+func makeLocationIDMap(n int) locationIDMap {
+ return locationIDMap{
+ dense: make([]*Location, n),
+ sparse: map[uint64]*Location{},
+ }
+}
+
+func (lm locationIDMap) get(id uint64) *Location {
+ if id < uint64(len(lm.dense)) {
+ return lm.dense[int(id)]
+ }
+ return lm.sparse[id]
+}
+
+func (lm locationIDMap) set(id uint64, loc *Location) {
+ if id < uint64(len(lm.dense)) {
+ lm.dense[id] = loc
+ return
+ }
+ lm.sparse[id] = loc
+}
+
+// CompatibilizeSampleTypes makes profiles compatible to be compared/merged. It
+// keeps sample types that appear in all profiles only and drops/reorders the
+// sample types as necessary.
+//
+// In the case of sample types order is not the same for given profiles the
+// order is derived from the first profile.
+//
+// Profiles are modified in-place.
+//
+// It returns an error if the sample type's intersection is empty.
+func CompatibilizeSampleTypes(ps []*Profile) error {
+ sTypes := commonSampleTypes(ps)
+ if len(sTypes) == 0 {
+ return fmt.Errorf("profiles have empty common sample type list")
+ }
+ for _, p := range ps {
+ if err := compatibilizeSampleTypes(p, sTypes); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// commonSampleTypes returns sample types that appear in all profiles in the
+// order how they ordered in the first profile.
+func commonSampleTypes(ps []*Profile) []string {
+ if len(ps) == 0 {
+ return nil
+ }
+ sTypes := map[string]int{}
+ for _, p := range ps {
+ for _, st := range p.SampleType {
+ sTypes[st.Type]++
+ }
+ }
+ var res []string
+ for _, st := range ps[0].SampleType {
+ if sTypes[st.Type] == len(ps) {
+ res = append(res, st.Type)
+ }
+ }
+ return res
+}
+
+// compatibilizeSampleTypes drops sample types that are not present in sTypes
+// list and reorder them if needed.
+//
+// It sets DefaultSampleType to sType[0] if it is not in sType list.
+//
+// It assumes that all sample types from the sTypes list are present in the
+// given profile otherwise it returns an error.
+func compatibilizeSampleTypes(p *Profile, sTypes []string) error {
+ if len(sTypes) == 0 {
+ return fmt.Errorf("sample type list is empty")
+ }
+ defaultSampleType := sTypes[0]
+ reMap, needToModify := make([]int, len(sTypes)), false
+ for i, st := range sTypes {
+ if st == p.DefaultSampleType {
+ defaultSampleType = p.DefaultSampleType
+ }
+ idx := searchValueType(p.SampleType, st)
+ if idx < 0 {
+ return fmt.Errorf("%q sample type is not found in profile", st)
+ }
+ reMap[i] = idx
+ if idx != i {
+ needToModify = true
+ }
+ }
+ if !needToModify && len(sTypes) == len(p.SampleType) {
+ return nil
+ }
+ p.DefaultSampleType = defaultSampleType
+ oldSampleTypes := p.SampleType
+ p.SampleType = make([]*ValueType, len(sTypes))
+ for i, idx := range reMap {
+ p.SampleType[i] = oldSampleTypes[idx]
+ }
+ values := make([]int64, len(sTypes))
+ for _, s := range p.Sample {
+ for i, idx := range reMap {
+ values[i] = s.Value[idx]
+ }
+ s.Value = s.Value[:len(values)]
+ copy(s.Value, values)
+ }
+ return nil
+}
+
+func searchValueType(vts []*ValueType, s string) int {
+ for i, vt := range vts {
+ if vt.Type == s {
+ return i
+ }
+ }
+ return -1
+}
diff --git a/vendor/github.com/google/pprof/profile/profile.go b/vendor/github.com/google/pprof/profile/profile.go
index 5a3807f97..60ef7e926 100644
--- a/vendor/github.com/google/pprof/profile/profile.go
+++ b/vendor/github.com/google/pprof/profile/profile.go
@@ -21,7 +21,6 @@ import (
"compress/gzip"
"fmt"
"io"
- "io/ioutil"
"math"
"path/filepath"
"regexp"
@@ -73,9 +72,23 @@ type ValueType struct {
type Sample struct {
Location []*Location
Value []int64
- Label map[string][]string
+ // Label is a per-label-key map to values for string labels.
+ //
+ // In general, having multiple values for the given label key is strongly
+ // discouraged - see docs for the sample label field in profile.proto. The
+ // main reason this unlikely state is tracked here is to make the
+ // decoding->encoding roundtrip not lossy. But we expect that the value
+ // slices present in this map are always of length 1.
+ Label map[string][]string
+ // NumLabel is a per-label-key map to values for numeric labels. See a note
+ // above on handling multiple values for a label.
NumLabel map[string][]int64
- NumUnit map[string][]string
+ // NumUnit is a per-label-key map to the unit names of corresponding numeric
+ // label values. The unit info may be missing even if the label is in
+ // NumLabel, see the docs in profile.proto for details. When the value is
+ // slice is present and not nil, its length must be equal to the length of
+ // the corresponding value slice in NumLabel.
+ NumUnit map[string][]string
locationIDX []uint64
labelX []label
@@ -153,7 +166,7 @@ type Function struct {
// may be a gzip-compressed encoded protobuf or one of many legacy
// profile formats which may be unsupported in the future.
func Parse(r io.Reader) (*Profile, error) {
- data, err := ioutil.ReadAll(r)
+ data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
@@ -168,7 +181,7 @@ func ParseData(data []byte) (*Profile, error) {
if len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err == nil {
- data, err = ioutil.ReadAll(gz)
+ data, err = io.ReadAll(gz)
}
if err != nil {
return nil, fmt.Errorf("decompressing profile: %v", err)
@@ -716,6 +729,35 @@ func (s *Sample) HasLabel(key, value string) bool {
return false
}
+// SetNumLabel sets the specified key to the specified value for all samples in the
+// profile. "unit" is a slice that describes the units that each corresponding member
+// of "values" is measured in (e.g. bytes or seconds). If there is no relevant
+// unit for a given value, that member of "unit" should be the empty string.
+// "unit" must either have the same length as "value", or be nil.
+func (p *Profile) SetNumLabel(key string, value []int64, unit []string) {
+ for _, sample := range p.Sample {
+ if sample.NumLabel == nil {
+ sample.NumLabel = map[string][]int64{key: value}
+ } else {
+ sample.NumLabel[key] = value
+ }
+ if sample.NumUnit == nil {
+ sample.NumUnit = map[string][]string{key: unit}
+ } else {
+ sample.NumUnit[key] = unit
+ }
+ }
+}
+
+// RemoveNumLabel removes all numerical labels associated with the specified key for all
+// samples in the profile.
+func (p *Profile) RemoveNumLabel(key string) {
+ for _, sample := range p.Sample {
+ delete(sample.NumLabel, key)
+ delete(sample.NumUnit, key)
+ }
+}
+
// DiffBaseSample returns true if a sample belongs to the diff base and false
// otherwise.
func (s *Sample) DiffBaseSample() bool {
diff --git a/vendor/github.com/google/pprof/profile/proto.go b/vendor/github.com/google/pprof/profile/proto.go
index 539ad3ab3..a15696ba1 100644
--- a/vendor/github.com/google/pprof/profile/proto.go
+++ b/vendor/github.com/google/pprof/profile/proto.go
@@ -39,11 +39,12 @@ import (
)
type buffer struct {
- field int // field tag
- typ int // proto wire type code for field
- u64 uint64
- data []byte
- tmp [16]byte
+ field int // field tag
+ typ int // proto wire type code for field
+ u64 uint64
+ data []byte
+ tmp [16]byte
+ tmpLines []Line // temporary storage used while decoding "repeated Line".
}
type decoder func(*buffer, message) error
@@ -286,7 +287,6 @@ func decodeInt64s(b *buffer, x *[]int64) error {
if b.typ == 2 {
// Packed encoding
data := b.data
- tmp := make([]int64, 0, len(data)) // Maximally sized
for len(data) > 0 {
var u uint64
var err error
@@ -294,9 +294,8 @@ func decodeInt64s(b *buffer, x *[]int64) error {
if u, data, err = decodeVarint(data); err != nil {
return err
}
- tmp = append(tmp, int64(u))
+ *x = append(*x, int64(u))
}
- *x = append(*x, tmp...)
return nil
}
var i int64
@@ -319,7 +318,6 @@ func decodeUint64s(b *buffer, x *[]uint64) error {
if b.typ == 2 {
data := b.data
// Packed encoding
- tmp := make([]uint64, 0, len(data)) // Maximally sized
for len(data) > 0 {
var u uint64
var err error
@@ -327,9 +325,8 @@ func decodeUint64s(b *buffer, x *[]uint64) error {
if u, data, err = decodeVarint(data); err != nil {
return err
}
- tmp = append(tmp, u)
+ *x = append(*x, u)
}
- *x = append(*x, tmp...)
return nil
}
var u uint64
diff --git a/vendor/github.com/google/pprof/profile/prune.go b/vendor/github.com/google/pprof/profile/prune.go
index 02d21a818..b2f9fd546 100644
--- a/vendor/github.com/google/pprof/profile/prune.go
+++ b/vendor/github.com/google/pprof/profile/prune.go
@@ -62,15 +62,31 @@ func (p *Profile) Prune(dropRx, keepRx *regexp.Regexp) {
prune := make(map[uint64]bool)
pruneBeneath := make(map[uint64]bool)
+ // simplifyFunc can be expensive, so cache results.
+ // Note that the same function name can be encountered many times due
+ // different lines and addresses in the same function.
+ pruneCache := map[string]bool{} // Map from function to whether or not to prune
+ pruneFromHere := func(s string) bool {
+ if r, ok := pruneCache[s]; ok {
+ return r
+ }
+ funcName := simplifyFunc(s)
+ if dropRx.MatchString(funcName) {
+ if keepRx == nil || !keepRx.MatchString(funcName) {
+ pruneCache[s] = true
+ return true
+ }
+ }
+ pruneCache[s] = false
+ return false
+ }
+
for _, loc := range p.Location {
var i int
for i = len(loc.Line) - 1; i >= 0; i-- {
if fn := loc.Line[i].Function; fn != nil && fn.Name != "" {
- funcName := simplifyFunc(fn.Name)
- if dropRx.MatchString(funcName) {
- if keepRx == nil || !keepRx.MatchString(funcName) {
- break
- }
+ if pruneFromHere(fn.Name) {
+ break
}
}
}
diff --git a/vendor/github.com/google/safehtml/CONTRIBUTING.md b/vendor/github.com/google/safehtml/CONTRIBUTING.md
new file mode 100644
index 000000000..22b241cb7
--- /dev/null
+++ b/vendor/github.com/google/safehtml/CONTRIBUTING.md
@@ -0,0 +1,29 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement (CLA). You (or your employer) retain the copyright to your
+contribution; this simply gives us permission to use and redistribute your
+contributions as part of the project. Head over to
+<https://cla.developers.google.com/> to see your current agreements on file or
+to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
+
+## Community Guidelines
+
+This project follows
+[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
diff --git a/vendor/github.com/google/safehtml/LICENSE b/vendor/github.com/google/safehtml/LICENSE
new file mode 100644
index 000000000..dec93b16e
--- /dev/null
+++ b/vendor/github.com/google/safehtml/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2017 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google LLC nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
diff --git a/vendor/github.com/google/safehtml/README.md b/vendor/github.com/google/safehtml/README.md
new file mode 100644
index 000000000..d3c9676d1
--- /dev/null
+++ b/vendor/github.com/google/safehtml/README.md
@@ -0,0 +1,17 @@
+# Safe HTML for Go
+
+`safehtml` provides immutable string-like types that wrap web types such as
+HTML, JavaScript and CSS. These wrappers are safe by construction against XSS
+and similar web vulnerabilities, and they can only be interpolated in safe ways.
+You can read more about our approach to web security in our
+[whitepaper](https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/42934.pdf),
+or this [OWASP talk](https://www.youtube.com/watch?v=ccfEu-Jj0as).
+
+Additional subpackages provide APIs for managing exceptions to the
+safety rules, and a template engine with a syntax and interface that closely
+matches [`html/template`](https://golang.org/pkg/html/template/). You can refer
+to the [godoc](https://pkg.go.dev/github.com/google/safehtml?tab=doc)
+for each (sub)package for the API documentation and code examples.
+More end-to-end demos are available in `example_test.go`.
+
+This is not an officially supported Google product.
diff --git a/vendor/github.com/google/safehtml/doc.go b/vendor/github.com/google/safehtml/doc.go
new file mode 100644
index 000000000..4c5c1bf78
--- /dev/null
+++ b/vendor/github.com/google/safehtml/doc.go
@@ -0,0 +1,11 @@
+// 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 safehtml provides immutable string-like types which represent values that
+// are guaranteed to be safe, by construction or by escaping or sanitization, to use
+// in various HTML contexts and with various DOM APIs.
+//
+package safehtml
diff --git a/vendor/github.com/google/safehtml/html.go b/vendor/github.com/google/safehtml/html.go
new file mode 100644
index 000000000..27c0f337d
--- /dev/null
+++ b/vendor/github.com/google/safehtml/html.go
@@ -0,0 +1,117 @@
+// 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 safehtml
+
+import (
+ "bytes"
+ "html"
+ "unicode"
+
+ "golang.org/x/text/unicode/rangetable"
+)
+
+// An HTML is an immutable string-like type that is safe to use in HTML
+// contexts in DOM APIs and HTML documents.
+//
+// HTML guarantees that its value as a string will not cause untrusted script
+// execution when evaluated as HTML in a browser.
+//
+// Values of this type are guaranteed to be safe to use in HTML contexts,
+// such as assignment to the innerHTML DOM property, or interpolation into an
+// HTML template in HTML PC_DATA context, in the sense that the use will not
+// result in a Cross-site Scripting (XSS) vulnerability.
+type HTML struct {
+ // We declare an HTML not as a string but as a struct wrapping a string
+ // to prevent construction of HTML values through string conversion.
+ str string
+}
+
+// HTMLer is implemented by any value that has an HTML method, which defines the
+// safe HTML format for that value.
+type HTMLer interface {
+ HTML() HTML
+}
+
+// HTMLEscaped returns an HTML whose value is text, with the characters [&<>"'] escaped.
+//
+// text is coerced to interchange valid, so the resulting HTML contains only
+// valid UTF-8 characters which are legal in HTML and XML.
+//
+func HTMLEscaped(text string) HTML {
+ return HTML{escapeAndCoerceToInterchangeValid(text)}
+}
+
+// HTMLConcat returns an HTML which contains, in order, the string representations
+// of the given htmls.
+func HTMLConcat(htmls ...HTML) HTML {
+ var b bytes.Buffer
+ for _, html := range htmls {
+ b.WriteString(html.String())
+ }
+ return HTML{b.String()}
+}
+
+// String returns the string form of the HTML.
+func (h HTML) String() string {
+ return h.str
+}
+
+// escapeAndCoerceToInterchangeValid coerces the string to interchange-valid
+// UTF-8 and then HTML-escapes it.
+func escapeAndCoerceToInterchangeValid(str string) string {
+ return html.EscapeString(coerceToUTF8InterchangeValid(str))
+}
+
+// coerceToUTF8InterchangeValid coerces a string to interchange-valid UTF-8.
+// Illegal UTF-8 bytes are replaced with the Unicode replacement character
+// ('\uFFFD'). C0 and C1 control codes (other than CR LF HT FF) and
+// non-characters are also replaced with the Unicode replacement character.
+func coerceToUTF8InterchangeValid(s string) string {
+ // TODO: Replace this entire function with stdlib function if https://golang.org/issue/25805 gets addressed.
+ runes := make([]rune, 0, len(s))
+ // If s contains any invalid UTF-8 byte sequences, range will have rune
+ // contain the Unicode replacement character and there's no need to call
+ // utf8.ValidRune. I.e. iteration over the string implements
+ // CoerceToStructurallyValid() from C++/Java.
+ // See https://blog.golang.org/strings.
+ for _, rune := range s {
+ if unicode.Is(controlAndNonCharacter, rune) {
+ runes = append(runes, unicode.ReplacementChar)
+ } else {
+ runes = append(runes, rune)
+ }
+ }
+ return string(runes)
+}
+
+// controlAndNonCharacters contains the non-interchange-valid codepoints.
+//
+// See http://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream
+//
+// safehtml functions do a lot of lookups on these tables, so merging them is probably
+// worth it to avoid comparing against both tables each time.
+var controlAndNonCharacter = rangetable.Merge(unicode.Noncharacter_Code_Point, controlChar)
+
+// controlChar contains Unicode control characters disallowed in interchange
+// valid UTF-8. This table is slightly different from unicode.Cc:
+// - Disallows null.
+// - Allows LF, CR, HT, and FF.
+//
+// unicode.C is mentioned in unicode.IsControl; it contains "special" characters
+// which includes at least control characters, surrogate code points, and
+// formatting codepoints (e.g. word joiner). We don't need to exclude all of
+// those. In particular, surrogates are handled by the for loop converting
+// invalid UTF-8 byte sequences to the Unicode replacement character.
+var controlChar = &unicode.RangeTable{
+ R16: []unicode.Range16{
+ {0x0000, 0x0008, 1},
+ {0x000B, 0x000B, 1},
+ {0x000E, 0x001F, 1},
+ {0x007F, 0x009F, 1},
+ },
+ LatinOffset: 4,
+}
diff --git a/vendor/github.com/google/safehtml/identifier.go b/vendor/github.com/google/safehtml/identifier.go
new file mode 100644
index 000000000..ffad26423
--- /dev/null
+++ b/vendor/github.com/google/safehtml/identifier.go
@@ -0,0 +1,83 @@
+// 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 safehtml
+
+import (
+ "fmt"
+ "regexp"
+)
+
+// A Identifier is an immutable string-like type that is safe to use in HTML
+// contexts as an identifier for HTML elements. For example, it is unsafe to
+// insert an untrusted string into a
+//
+// <img name="..."></img>
+//
+// context since the string may be controlled by an attacker who can assign it
+// a value that masks existing DOM properties (i.e. DOM Clobbering). An
+// attacker may also be able to force legitimate Javascript code, which uses
+// document.getElementsByName(...) to read DOM elements, to refer to this
+// element. This may lead to unintended side effects, particularly if that
+// element contains attacker-controlled data. It is, however, safe to use an
+// Identifier in this context since its value is known to be partially or fully
+// under application control.
+//
+// In order to ensure that an attacker cannot influence the Identifier value,
+// an Identifier can only be instantiated from a compile-time constant string
+// literal prefix.
+//
+// Note that Identifier is Go-specific and therefore does not have a Proto form
+// for cross-language use.
+type Identifier struct {
+ // We declare a Identifier not as a string but as a struct wrapping a string
+ // to prevent construction of Identifier values through string conversion.
+ str string
+}
+
+// To minimize the risk of parsing errors, Identifier values must start with an
+// alphabetical rune, and comprise of only alphanumeric, '-', and '_' runes.
+
+// startsWithAlphabetPattern matches strings that start with an alphabetical rune.
+var startsWithAlphabetPattern = regexp.MustCompile(`^[a-zA-Z]`)
+
+// onlyAlphanumericsOrHyphenPattern matches strings that only contain alphanumeric,
+// '-' and '_' runes.
+var onlyAlphanumericsOrHyphenPattern = regexp.MustCompile(`^[-_a-zA-Z0-9]*$`)
+
+// IdentifierFromConstant constructs an Identifier with its underlying identifier
+// set to the given string value, which must be an untyped string constant. It
+// panics if value does not start with an alphabetic rune or contains any
+// non-alphanumeric runes other than '-' and '_'.
+func IdentifierFromConstant(value stringConstant) Identifier {
+ if !startsWithAlphabetPattern.MatchString(string(value)) ||
+ !onlyAlphanumericsOrHyphenPattern.MatchString(string(value)) {
+ panic(fmt.Sprintf("invalid identifier %q", string(value)))
+ }
+ return Identifier{string(value)}
+}
+
+// IdentifierFromConstantPrefix constructs an Identifier with its underlying string
+// set to the string formed by joining prefix, which must be an untyped string
+// constant, and value with a hyphen. It panics if prefix or value contain any
+// non-alphanumeric runes other than '-' and '_', or if prefix does not start with
+// an alphabetic rune.
+func IdentifierFromConstantPrefix(prefix stringConstant, value string) Identifier {
+ prefixString := string(prefix)
+ if !startsWithAlphabetPattern.MatchString(string(prefix)) ||
+ !onlyAlphanumericsOrHyphenPattern.MatchString(string(prefix)) {
+ panic(fmt.Sprintf("invalid prefix %q", string(prefix)))
+ }
+ if !onlyAlphanumericsOrHyphenPattern.MatchString(value) {
+ panic(fmt.Sprintf("value %q contains non-alphanumeric runes", value))
+ }
+ return Identifier{prefixString + "-" + value}
+}
+
+// String returns the string form of the Identifier.
+func (i Identifier) String() string {
+ return i.str
+}
diff --git a/vendor/github.com/google/safehtml/init.go b/vendor/github.com/google/safehtml/init.go
new file mode 100644
index 000000000..d37547d72
--- /dev/null
+++ b/vendor/github.com/google/safehtml/init.go
@@ -0,0 +1,58 @@
+// 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 safehtml
+
+import (
+ "github.com/google/safehtml/internal/raw"
+)
+
+// 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 should only be used in
+// function and method parameters.
+type stringConstant string
+
+// The following functions are used by package uncheckedconversions
+// (via package raw) to create safe HTML types from plain strings.
+
+func htmlRaw(s string) HTML {
+ return HTML{s}
+}
+
+func scriptRaw(s string) Script {
+ return Script{s}
+}
+
+func style(s string) Style {
+ return Style{s}
+}
+
+func styleSheetRaw(s string) StyleSheet {
+ return StyleSheet{s}
+}
+
+func urlRaw(s string) URL {
+ return URL{s}
+}
+
+func trustedResourceURLRaw(s string) TrustedResourceURL {
+ return TrustedResourceURL{s}
+}
+
+func identifierRaw(s string) Identifier {
+ return Identifier{s}
+}
+
+func init() {
+ raw.HTML = htmlRaw
+ raw.Script = scriptRaw
+ raw.Style = style
+ raw.StyleSheet = styleSheetRaw
+ raw.URL = urlRaw
+ raw.TrustedResourceURL = trustedResourceURLRaw
+ raw.Identifier = identifierRaw
+}
diff --git a/vendor/github.com/google/safehtml/internal/raw/raw.go b/vendor/github.com/google/safehtml/internal/raw/raw.go
new file mode 100644
index 000000000..3bedb6a6d
--- /dev/null
+++ b/vendor/github.com/google/safehtml/internal/raw/raw.go
@@ -0,0 +1,31 @@
+// 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 raw provides a coordination point for package safehtml, package
+// uncheckedconversions, package legacyconversions, and package testconversions.
+// raw must only be imported by these four packages.
+package raw
+
+// HTML is the raw constructor for a safehtml.HTML.
+var HTML interface{}
+
+// Script is the raw constructor for a safehtml.Script.
+var Script interface{}
+
+// Style is the raw constructor for a safehtml.Style.
+var Style interface{}
+
+// StyleSheet is the raw constructor for a safehtml.StyleSheet.
+var StyleSheet interface{}
+
+// URL is the raw constructor for a safehtml.URL.
+var URL interface{}
+
+// TrustedResourceURL is the raw constructor for a safehtml.TrustedResourceURL.
+var TrustedResourceURL interface{}
+
+// Identifier is the raw constructor for a safehtml.Identifier.
+var Identifier interface{}
diff --git a/vendor/github.com/google/safehtml/internal/safehtmlutil/safehtmlutil.go b/vendor/github.com/google/safehtml/internal/safehtmlutil/safehtmlutil.go
new file mode 100644
index 000000000..dd8e7fe36
--- /dev/null
+++ b/vendor/github.com/google/safehtml/internal/safehtmlutil/safehtmlutil.go
@@ -0,0 +1,180 @@
+// 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 safehtmlutil contains functions shared by package safehtml and safehtml/template.
+package safehtmlutil
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "regexp"
+)
+
+// IsSafeTrustedResourceURLPrefix returns whether the given prefix is safe to use as a
+// TrustedResourceURL prefix.
+//
+// TrustedResourceURL prefixes must start with one of the following:
+// * `https://<origin>/`
+// * `//<origin>/`
+// * `/<pathStart>`
+// * `about:blank#`
+//
+// `<origin>` must contain only alphanumerics, '.', ':', '[', ']', or '-'.
+// These restrictions do not enforce a well-formed domain name, so '.' and '1.2' are valid.
+//
+// `<pathStart>` is any character except `/` and `\`. Based on
+// https://url.spec.whatwg.org/commit-snapshots/56b74ce7cca8883eab62e9a12666e2fac665d03d/#url-parsing,
+// an initial / which is not followed by another / or \ will end up in the "path state" and from there
+// it can only go to the "fragment state" and "query state".
+func IsSafeTrustedResourceURLPrefix(prefix string) bool {
+ return safeTrustedResourceURLPrefixPattern.MatchString(prefix)
+}
+
+var safeTrustedResourceURLPrefixPattern = regexp.MustCompile(`(?i)^(?:` +
+ `(?:https:)?//[0-9a-z.:\[\]-]+/|` +
+ `/[^/\\]|` +
+ `about:blank#)`)
+
+// URLContainsDoubleDotSegment returns whether the given URL or URL substring
+// contains the double dot-segment ".." (RFC3986 3.3) in its percent-encoded or
+// unencoded form.
+func URLContainsDoubleDotSegment(url string) bool {
+ return urlDoubleDotSegmentPattern.MatchString(url)
+}
+
+var urlDoubleDotSegmentPattern = regexp.MustCompile(`(?i)(?:\.|%2e)(?:\.|%2e)`)
+
+// QueryEscapeURL produces an output that can be embedded in a URL query.
+// The output can be embedded in an HTML attribute without further escaping.
+func QueryEscapeURL(args ...interface{}) string {
+ return urlProcessor(false, Stringify(args...))
+}
+
+// NormalizeURL normalizes URL content so it can be embedded in a quote-delimited
+// string or parenthesis delimited url(...).
+// The normalizer does not encode all HTML specials. Specifically, it does not
+// encode '&' so correct embedding in an HTML attribute requires escaping of
+// '&' to '&amp;'.
+func NormalizeURL(args ...interface{}) string {
+ return urlProcessor(true, Stringify(args...))
+}
+
+// urlProcessor normalizes (when norm is true) or escapes its input to produce
+// a valid hierarchical or opaque URL part.
+func urlProcessor(norm bool, s string) string {
+ var b bytes.Buffer
+ written := 0
+ // The byte loop below assumes that all URLs use UTF-8 as the
+ // content-encoding. This is similar to the URI to IRI encoding scheme
+ // defined in section 3.1 of RFC 3987, and behaves the same as the
+ // EcmaScript builtin encodeURIComponent.
+ // It should not cause any misencoding of URLs in pages with
+ // Content-type: text/html;charset=UTF-8.
+ for i, n := 0, len(s); i < n; i++ {
+ c := s[i]
+ switch c {
+ // Single quote and parens are sub-delims in RFC 3986, but we
+ // escape them so the output can be embedded in single
+ // quoted attributes and unquoted CSS url(...) constructs.
+ // Single quotes are reserved in URLs, but are only used in
+ // the obsolete "mark" rule in an appendix in RFC 3986
+ // so can be safely encoded.
+ case '!', '#', '$', '&', '*', '+', ',', '/', ':', ';', '=', '?', '@', '[', ']':
+ if norm {
+ continue
+ }
+ // Unreserved according to RFC 3986 sec 2.3
+ // "For consistency, percent-encoded octets in the ranges of
+ // ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D),
+ // period (%2E), underscore (%5F), or tilde (%7E) should not be
+ // created by URI producers
+ case '-', '.', '_', '~':
+ continue
+ case '%':
+ // When normalizing do not re-encode valid escapes.
+ if norm && i+2 < len(s) && isHex(s[i+1]) && isHex(s[i+2]) {
+ continue
+ }
+ default:
+ // Unreserved according to RFC 3986 sec 2.3
+ if 'a' <= c && c <= 'z' {
+ continue
+ }
+ if 'A' <= c && c <= 'Z' {
+ continue
+ }
+ if '0' <= c && c <= '9' {
+ continue
+ }
+ }
+ b.WriteString(s[written:i])
+ fmt.Fprintf(&b, "%%%02x", c)
+ written = i + 1
+ }
+ if written == 0 {
+ return s
+ }
+ b.WriteString(s[written:])
+ return b.String()
+}
+
+// isHex reports whether the given character is a hex digit.
+func isHex(c byte) bool {
+ return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'
+}
+
+// Stringify converts its arguments to a string. It is equivalent to
+// fmt.Sprint(args...), except that it deferences all pointers.
+func Stringify(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...)
+}
+
+var (
+ errorType = reflect.TypeOf((*error)(nil)).Elem()
+ fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+)
+
+// indirectToStringerOrError dereferences a as many times
+// as necessary to reach the base type, an implementation of fmt.Stringer,
+// or an implementation of error, and returns a value of that type. It returns
+// nil if a is nil.
+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()
+}
+
+// Indirect returns the value, after dereferencing as many times
+// as necessary to reach the base type (or nil).
+func Indirect(a interface{}) interface{} {
+ if a == nil {
+ return nil
+ }
+ if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr {
+ // Avoid creating a reflect.Value if it's not a pointer.
+ return a
+ }
+ v := reflect.ValueOf(a)
+ for v.Kind() == reflect.Ptr && !v.IsNil() {
+ v = v.Elem()
+ }
+ return v.Interface()
+}
diff --git a/vendor/github.com/google/safehtml/internal/template/raw/raw.go b/vendor/github.com/google/safehtml/internal/template/raw/raw.go
new file mode 100644
index 000000000..b69599bd2
--- /dev/null
+++ b/vendor/github.com/google/safehtml/internal/template/raw/raw.go
@@ -0,0 +1,16 @@
+// 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 raw provides a coordination point for package safehtml/template and
+// package safehtml/template/uncheckedconversions. raw must be imported only by
+// these two packages.
+package raw
+
+// TrustedSource is the raw constructor for a template.TrustedSource.
+var TrustedSource interface{}
+
+// TrustedTemplate is the raw constructor for a template.TrustedTemplate.
+var TrustedTemplate interface{}
diff --git a/vendor/github.com/google/safehtml/script.go b/vendor/github.com/google/safehtml/script.go
new file mode 100644
index 000000000..c9e0fd298
--- /dev/null
+++ b/vendor/github.com/google/safehtml/script.go
@@ -0,0 +1,90 @@
+// 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 safehtml
+
+import (
+ "encoding/json"
+ "fmt"
+ "regexp"
+)
+
+// A Script is an immutable string-like type which represents JavaScript
+// code and guarantees that its value, as a string, will not cause execution
+// of unconstrained attacker controlled code (cross-site scripting) when
+// evaluated as JavaScript in a browser.
+//
+// Script's string representation can safely be interpolated as the
+// content of a script element within HTML, and can safely be passed to DOM
+// properties and functions which expect JavaScript. In these cases, the Script
+// string should not be escaped. Script's string representation can also be safely
+// used as the value for on* attribute handlers in HTML, though the Script string
+// must be escaped before such use.
+//
+// Note that the Script might contain text that is attacker-controlled but
+// that text should have been interpolated with appropriate escaping,
+// sanitization and/or validation into the right location in the script, such
+// that it is highly constrained in its effect (for example, it had to match a
+// set of allowed words).
+//
+// In order to ensure that an attacker cannot influence the Script
+// value, a Script can only be instantiated from compile-time
+// constant string literals or security-reviewed unchecked conversions,
+// but never from arbitrary string values potentially representing untrusted
+// user input.
+type Script struct {
+ // We declare a Script not as a string but as a struct wrapping a string
+ // to prevent construction of Script values through string conversion.
+ str string
+}
+
+// ScriptFromConstant constructs a Script with its underlying script set
+// to the given script, which must be an untyped string constant.
+//
+// No runtime validation or sanitization is performed on script; being under
+// application control, it is simply assumed to comply with the Script
+// contract.
+func ScriptFromConstant(script stringConstant) Script {
+ return Script{string(script)}
+}
+
+// ScriptFromDataAndConstant constructs a Script of the form
+//
+// var name = data; script
+//
+// where name is the supplied variable name, data is the supplied data value
+// encoded as JSON using encoding/json.Marshal, and script is the supplied
+// JavaScript statement or sequence of statements. The supplied name and script
+// must both be untyped string constants. It returns an error if name is not a
+// valid Javascript identifier or JSON encoding fails.
+//
+// No runtime validation or sanitization is performed on script; being under
+// application control, it is simply assumed to comply with the Script
+// contract.
+func ScriptFromDataAndConstant(name stringConstant, data interface{}, script stringConstant) (Script, error) {
+ if !jsIdentifierPattern.MatchString(string(name)) {
+ return Script{}, fmt.Errorf("variable name %q is an invalid Javascript identifier", string(name))
+ }
+ json, err := json.Marshal(data)
+ if err != nil {
+ return Script{}, err
+ }
+ return Script{fmt.Sprintf("var %s = %s;\n%s", name, json, string(script))}, nil
+}
+
+// jsIdentifierPattern matches strings that are valid Javascript identifiers.
+//
+// This pattern accepts only a subset of valid identifiers defined in
+// https://tc39.github.io/ecma262/#sec-names-and-keywords. In particular,
+// it does not match identifiers that contain non-ASCII letters, Unicode
+// escape sequences, and the Unicode format-control characters
+// \u200C (zero-width non-joiner) and \u200D (zero-width joiner).
+var jsIdentifierPattern = regexp.MustCompile(`^[$_a-zA-Z][$_a-zA-Z0-9]+$`)
+
+// String returns the string form of the Script.
+func (s Script) String() string {
+ return s.str
+}
diff --git a/vendor/github.com/google/safehtml/style.go b/vendor/github.com/google/safehtml/style.go
new file mode 100644
index 000000000..c11ac9d96
--- /dev/null
+++ b/vendor/github.com/google/safehtml/style.go
@@ -0,0 +1,304 @@
+// 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 safehtml
+
+import (
+ "bytes"
+ "fmt"
+ "regexp"
+ "strings"
+)
+
+// A Style is an immutable string-like type which represents a sequence of CSS
+// declarations (property_name1: property_value1; property_name2: property_value2; ...)
+// and guarantees that its value will not cause untrusted script execution
+// (cross-site scripting) when evaluated as CSS in a browser.
+//
+// Style's string representation can safely be:
+// * Interpolated as the content of a quoted HTML style attribute. However, the
+// Style string must be HTML-attribute-escaped before interpolation.
+// * Interpolated as the content of a {}-wrapped block within a StyleSheet.
+// '<' runes in the Style string must be CSS-escaped before interpolation.
+// The Style string is also guaranteed not to be able to introduce new
+// properties or elide existing ones.
+// * Interpolated as the content of a {}-wrapped block within an HTML <style>
+// element. '<' runes in the Style string must be CSS-escaped before interpolation.
+// * Assigned to the style property of a DOM node. The Style string should not
+// be escaped before being assigned to the property.
+//
+// In addition, values of this type are composable, that is, for any two Style
+// values |style1| and |style2|, style1.style() + style2.style() is itself a
+// value that satisfies the Style type constraint.
+type Style struct {
+ // We declare a Style not as a string but as a struct wrapping a string
+ // to prevent construction of Style values through string conversion.
+ str string
+}
+
+// StyleFromConstant constructs a Style with its underlying style set to the
+// given style, which must be an untyped string constant, and panics if the
+// style string does not pass basic syntax checks.
+//
+// Users of this function must ensure themselves that the style:
+// * Does not contain unsafe CSS.
+// * Does not contain literal angle brackets. Otherwise, it could be unsafe to
+// place a Style into the contents of a <style> element where it can't be
+// HTML escaped (see http://www.w3.org/International/questions/qa-escapes).
+// For example, if the Style containing
+// "font: 'foo <style/><script>evil</script>'" was interpolated within a
+// <style> tag, it would then break out of the style context into HTML.
+// * Does not end in a property value or property name context.
+// For example, a value of "background:url(\"" or "font-" does not satisfy
+// the Style type contract. This rule is enforced to ensure composability:
+// concatenating two incomplete strings that themselves do not contain unsafe
+// CSS can result in an overall string that does. For example, if
+// "javascript:evil())\"" is appended to "background:url(\"", the resulting
+// string may result in the execution of a malicious script.
+//
+// The style may, however, contain literal single or double quotes (for example,
+// in the "content" property). Therefore, the entire style string must be
+// escaped when used in a style attribute.
+//
+// The following example values comply with Style's type contract:
+// width: 1em;
+// height:1em;
+// width: 1em;height: 1em;
+// background:url('http://url');
+//
+// In addition, the empty string is safe for use in a style attribute.
+//
+// The following example values do NOT comply with this type's contract:
+// background: red --- missing a trailing semi-colon
+// background: --- missing a value and a trailing semi-colon
+// 1em --- missing an attribute name, which provides context
+// for the value
+//
+// See also http://www.w3.org/TR/css3-syntax/.
+func StyleFromConstant(style stringConstant) Style {
+ // TODO: implement UTF-8 interchange-validity checks and blocking of newlines
+ // (including Unicode ones) and other whitespace characters (\t, \f) for Style and other safe types
+ // in this package.
+ if strings.ContainsAny(string(style), "<>") {
+ panic(fmt.Sprintf("style string %q contains angle brackets", style))
+ }
+ if !strings.HasSuffix(string(style), ";") {
+ panic(fmt.Sprintf("style string %q must end with ';'", style))
+ }
+ if !strings.Contains(string(style), ":") {
+ panic(fmt.Sprintf("style string %q must contain at least one ':' to specify a property-value pair", style))
+ }
+ return Style{string(style)}
+}
+
+// String returns the string form of the Style.
+func (s Style) String() string {
+ return s.str
+}
+
+// StyleProperties contains property values for CSS properties whose names are
+// the hyphen-separated form of the field names. These values will be validated
+// by StyleFromProperties before being included in a Style.
+//
+// For example, BackgroundPosition contains the value for the
+// "background-position" property, and Display contains the value for the "display"
+// property.
+//
+type StyleProperties struct {
+ // BackgroundImageURLs contains URL values for the background-image property.
+ // These values val_1, val_2, ..., val_n will be passed through URLSanitized and CSS-escaped in
+ // StyleFromProperties, then interpolated into to a comma-separated list of CSS URLs of the form
+ // url("val_1"), url("val_2"), ..., url("val_n")
+ // See https://www.w3.org/TR/CSS2/syndata.html#value-def-uri and https://drafts.csswg.org/css-backgrounds-3/#layering.
+ BackgroundImageURLs []string
+ // FontFamily values are used, comma-separated, as the font-family property.
+ // * Names starting with a Latin alphabet runes and containing only Latin alphabets and hyphens will be included unquoted.
+ // * Names enclosed in double quote literals (e.g. `"21st Century"`) will be CSS-escaped without the outermost quotes,
+ // then included within double quotes.
+ // * All other names will be CSS-escaped, and included within double quotes.
+ // See https://drafts.csswg.org/css-fonts-3/#font-family-prop.
+ FontFamily []string
+ // Display must consist of only ASCII alphabetic or '-' runes.
+ // Non-conforming values will be replaced by InnocuousPropertyValue in
+ // StyleFromProperties.
+ Display string
+ // The following values can only contain allowed runes, that is, alphanumerics,
+ // space, tab, and the set [+-.!#%_/*]. In addition, comment markers "//", "/*",
+ // and "*/" are disallowed. Non-conforming values will be replaced by
+ // InnocuousPropertyValue in StyleFromProperties.
+ BackgroundColor string
+ BackgroundPosition string
+ BackgroundRepeat string
+ BackgroundSize string
+ Color string
+ Height string
+ Width string
+ Left string
+ Right string
+ Top string
+ Bottom string
+ FontWeight string
+ Padding string
+ // Note: this property might allow clickjacking, but the risk is limited without
+ // the ability to set the position property to "absolute" or "fixed".
+ ZIndex string
+}
+
+// identifierPattern matches a subset of valid <ident-token> values defined in
+// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram. This pattern matches all generic family name
+// keywords defined in https://drafts.csswg.org/css-fonts-3/#family-name-value.
+var identifierPattern = regexp.MustCompile(`^[a-zA-Z][-a-zA-Z]+$`)
+
+// StyleFromProperties constructs a Style containining properties whose values
+// are set in properties. The contents of the returned Style will be of the form
+// property_1:val_1;property2:val_2; ... ;property_n:val_n;
+// This syntax is defined in https://www.w3.org/TR/css-style-attr/.
+//
+// All property values are validated and, if necessary, modified to ensure that their
+// inclusion in a HTML style attribute does not result in untrusted script execution,
+// the addition of new properties, or the removal of existing properties. Please refer
+// to the StyleProperties documentation for validation rules.
+//
+// The constructed Style is guaranteed to fulfill its type contract, but is not
+// guaranteed to be semantically valid CSS.
+func StyleFromProperties(properties StyleProperties) Style {
+ // TODO: if this boilerplate code grows large, consider generating property names from Field names using reflection.
+ var buf bytes.Buffer
+ if len(properties.BackgroundImageURLs) > 0 {
+ buf.WriteString("background-image:")
+ for i, url := range properties.BackgroundImageURLs {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ fmt.Fprintf(&buf, "url(\"%s\")", cssEscapeString(URLSanitized(url).String()))
+ }
+ buf.WriteString(";")
+ }
+ if len(properties.FontFamily) > 0 {
+ buf.WriteString("font-family:")
+ for i, name := range properties.FontFamily {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ if identifierPattern.MatchString(name) {
+ buf.WriteString(name)
+ continue
+ }
+ unescaped := name
+ if len(name) >= 3 && strings.HasPrefix(name, `"`) && strings.HasSuffix(name, `"`) {
+ unescaped = name[1 : len(name)-1]
+ }
+ fmt.Fprintf(&buf, `"%s"`, cssEscapeString(unescaped))
+ }
+ buf.WriteByte(';')
+ }
+ if properties.Display != "" {
+ fmt.Fprintf(&buf, "display:%s;", filter(properties.Display, safeEnumPropertyValuePattern))
+ }
+ if properties.BackgroundColor != "" {
+ fmt.Fprintf(&buf, "background-color:%s;", filter(properties.BackgroundColor, safeRegularPropertyValuePattern))
+ }
+ if properties.BackgroundPosition != "" {
+ fmt.Fprintf(&buf, "background-position:%s;", filter(properties.BackgroundPosition, safeRegularPropertyValuePattern))
+ }
+ if properties.BackgroundRepeat != "" {
+ fmt.Fprintf(&buf, "background-repeat:%s;", filter(properties.BackgroundRepeat, safeRegularPropertyValuePattern))
+ }
+ if properties.BackgroundSize != "" {
+ fmt.Fprintf(&buf, "background-size:%s;", filter(properties.BackgroundSize, safeRegularPropertyValuePattern))
+ }
+ if properties.Color != "" {
+ fmt.Fprintf(&buf, "color:%s;", filter(properties.Color, safeRegularPropertyValuePattern))
+ }
+ if properties.Height != "" {
+ fmt.Fprintf(&buf, "height:%s;", filter(properties.Height, safeRegularPropertyValuePattern))
+ }
+ if properties.Width != "" {
+ fmt.Fprintf(&buf, "width:%s;", filter(properties.Width, safeRegularPropertyValuePattern))
+ }
+ if properties.Left != "" {
+ fmt.Fprintf(&buf, "left:%s;", filter(properties.Left, safeRegularPropertyValuePattern))
+ }
+ if properties.Right != "" {
+ fmt.Fprintf(&buf, "right:%s;", filter(properties.Right, safeRegularPropertyValuePattern))
+ }
+ if properties.Top != "" {
+ fmt.Fprintf(&buf, "top:%s;", filter(properties.Top, safeRegularPropertyValuePattern))
+ }
+ if properties.Bottom != "" {
+ fmt.Fprintf(&buf, "bottom:%s;", filter(properties.Bottom, safeRegularPropertyValuePattern))
+ }
+ if properties.FontWeight != "" {
+ fmt.Fprintf(&buf, "font-weight:%s;", filter(properties.FontWeight, safeRegularPropertyValuePattern))
+ }
+ if properties.Padding != "" {
+ fmt.Fprintf(&buf, "padding:%s;", filter(properties.Padding, safeRegularPropertyValuePattern))
+ }
+ if properties.ZIndex != "" {
+ fmt.Fprintf(&buf, "z-index:%s;", filter(properties.ZIndex, safeRegularPropertyValuePattern))
+ }
+
+ return Style{buf.String()}
+}
+
+// InnocuousPropertyValue is an innocuous property generated by filter when its input unsafe.
+const InnocuousPropertyValue = "zGoSafezInvalidPropertyValue"
+
+// safeRegularPropertyValuePattern matches strings that are safe to use as property values.
+// Specifically, it matches string where every '*' or '/' is followed by end-of-text or a safe rune
+// (i.e. alphanumberics or runes in the set [+-.!#%_ \t]). This regex ensures that the following
+// are disallowed:
+// * "/*" and "*/", which are CSS comment markers.
+// * "//", even though this is not a comment marker in the CSS specification. Disallowing
+// this string minimizes the chance that browser peculiarities or parsing bugs will allow
+// sanitization to be bypassed.
+// * '(' and ')', which can be used to call functions.
+// * ',', since it can be used to inject extra values into a property.
+// * Runes which could be matched on CSS error recovery of a previously malformed token, such as '@'
+// and ':'. See http://www.w3.org/TR/css3-syntax/#error-handling.
+var safeRegularPropertyValuePattern = regexp.MustCompile(`^(?:[*/]?(?:[0-9a-zA-Z+-.!#%_ \t]|$))*$`)
+
+// safeEnumPropertyValuePattern matches strings that are safe to use as enumerated property values.
+// Specifically, it matches strings that contain only alphabetic and '-' runes.
+var safeEnumPropertyValuePattern = regexp.MustCompile(`^[a-zA-Z-]*$`)
+
+// filter returns value if it matches pattern. Otherwise, it returns InnocuousPropertyValue.
+func filter(value string, pattern *regexp.Regexp) string {
+ if !pattern.MatchString(value) {
+ return InnocuousPropertyValue
+ }
+ return value
+}
+
+// cssEscapeString escapes s so that it is safe to put between "" to form a CSS <string-token>.
+// See syntax at https://www.w3.org/TR/css-syntax-3/#string-token-diagram.
+//
+// On top of the escape sequences required in <string-token>, this function also escapes
+// control runes to minimize the risk of these runes triggering browser-specific bugs.
+func cssEscapeString(s string) string {
+ var b bytes.Buffer
+ b.Grow(len(s))
+ // TODO: consider optmizations (e.g. ranging over bytes, batching writes of contiguous sequences of unescaped runes) if
+ // performance becomes an issue.
+ for _, c := range s {
+ switch {
+ case c == '\u0000':
+ // Replace the NULL byte according to https://www.w3.org/TR/css-syntax-3/#input-preprocessing.
+ // We take this extra precaution in case the user agent fails to handle NULL properly.
+ b.WriteString("\uFFFD")
+ case c == '<', // Prevents breaking out of a style element with `</style>`. Escape this in case the Style user forgets to.
+ c == '"', c == '\\', // Must be CSS-escaped in <string-token>. U+000A line feed is handled in the next case.
+ c <= '\u001F', c == '\u007F', // C0 control codes
+ c >= '\u0080' && c <= '\u009F', // C1 control codes
+ c == '\u2028', c == '\u2029': // Unicode newline characters
+ // See CSS escape sequence syntax at https://www.w3.org/TR/css-syntax-3/#escape-diagram.
+ fmt.Fprintf(&b, "\\%06X", c)
+ default:
+ b.WriteRune(c)
+ }
+ }
+ return b.String()
+}
diff --git a/vendor/github.com/google/safehtml/stylesheet.go b/vendor/github.com/google/safehtml/stylesheet.go
new file mode 100644
index 000000000..17de8a517
--- /dev/null
+++ b/vendor/github.com/google/safehtml/stylesheet.go
@@ -0,0 +1,111 @@
+// 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 safehtml
+
+import (
+ "container/list"
+ "fmt"
+ "regexp"
+ "strings"
+)
+
+// A StyleSheet is an immutable string-like type which represents a CSS
+// style sheet and guarantees that its value, as a string, will not cause
+// untrusted script execution (cross-site scripting) when evaluated as CSS
+// in a browser.
+//
+// StyleSheet's string representation can safely be interpolated as the
+// content of a style element within HTML. The StyleSheet string should
+// not be escaped before interpolation.
+type StyleSheet struct {
+ // We declare a StyleSheet not as a string but as a struct wrapping a string
+ // to prevent construction of StyleSheet values through string conversion.
+ str string
+}
+
+// StyleSheetFromConstant constructs a StyleSheet with the
+// underlying stylesheet set to the given styleSheet, which must be an untyped string
+// constant.
+//
+// No runtime validation or sanitization is performed on script; being under
+// application control, it is simply assumed to comply with the StyleSheet
+// contract.
+func StyleSheetFromConstant(styleSheet stringConstant) StyleSheet {
+ return StyleSheet{string(styleSheet)}
+}
+
+// CSSRule constructs a StyleSheet containng a CSS rule of the form:
+// selector{style}
+// It returns an error if selector contains disallowed characters or unbalanced
+// brackets.
+//
+// The constructed StyleSheet value is guaranteed to fulfill its type contract,
+// but is not guaranteed to be semantically valid CSS.
+func CSSRule(selector string, style Style) (StyleSheet, error) {
+ if strings.ContainsRune(selector, '<') {
+ return StyleSheet{}, fmt.Errorf("selector %q contains '<'", selector)
+ }
+ selectorWithoutStrings := cssStringPattern.ReplaceAllString(selector, "")
+ if matches := invalidCSSSelectorRune.FindStringSubmatch(selectorWithoutStrings); matches != nil {
+ return StyleSheet{}, fmt.Errorf("selector %q contains %q, which is disallowed outside of CSS strings", selector, matches[0])
+ }
+ if !hasBalancedBrackets(selectorWithoutStrings) {
+ return StyleSheet{}, fmt.Errorf("selector %q contains unbalanced () or [] brackets", selector)
+ }
+ return StyleSheet{fmt.Sprintf("%s{%s}", selector, style.String())}, nil
+}
+
+var (
+ // cssStringPattern matches a single- or double-quoted CSS string.
+ cssStringPattern = regexp.MustCompile(
+ `"([^"\r\n\f\\]|\\[\s\S])*"|` + // Double-quoted string literal
+ `'([^'\r\n\f\\]|\\[\s\S])*'`) // Single-quoted string literal
+
+ // invalidCSSSelectorRune matches a rune that is not allowed in a CSS3
+ // selector that does not contain string literals.
+ // See https://w3.org/TR/css3-selectors/#selectors.
+ invalidCSSSelectorRune = regexp.MustCompile(`[^-_a-zA-Z0-9#.:* ,>+~[\]()=^$|]`)
+)
+
+// hasBalancedBrackets returns whether s has balanced () and [] brackets.
+func hasBalancedBrackets(s string) bool {
+ stack := list.New()
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ if expected, ok := matchingBrackets[c]; ok {
+ e := stack.Back()
+ if e == nil {
+ return false
+ }
+ // Skip success check for this type assertion since it is trivial to
+ // see that only bytes are pushed onto this stack.
+ if v := e.Value.(byte); v != expected {
+ return false
+ }
+ stack.Remove(e)
+ continue
+ }
+ for _, openBracket := range matchingBrackets {
+ if c == openBracket {
+ stack.PushBack(c)
+ break
+ }
+ }
+ }
+ return stack.Len() == 0
+}
+
+// matchingBrackets[x] is the opening bracket that matches closing bracket x.
+var matchingBrackets = map[byte]byte{
+ ')': '(',
+ ']': '[',
+}
+
+// String returns the string form of the StyleSheet.
+func (s StyleSheet) String() string {
+ return s.str
+}
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
+}
diff --git a/vendor/github.com/google/safehtml/trustedresourceurl.go b/vendor/github.com/google/safehtml/trustedresourceurl.go
new file mode 100644
index 000000000..e31a2fd56
--- /dev/null
+++ b/vendor/github.com/google/safehtml/trustedresourceurl.go
@@ -0,0 +1,195 @@
+// 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 safehtml
+
+import (
+ "fmt"
+ "regexp"
+ "sort"
+ "strings"
+
+ "flag"
+ "github.com/google/safehtml/internal/safehtmlutil"
+)
+
+// A TrustedResourceURL is an immutable string-like type referencing the
+// application’s own, trusted resources. It can be used to safely load scripts,
+// CSS and other sensitive resources without the risk of untrusted code execution.
+// For example, it is unsafe to insert a plain string in a
+//
+// <script src=“...”></script>
+//
+// context since the URL may originate from untrusted user input and the
+// script it is pointing to may thus be controlled by an attacker. It is,
+// however, safe to use a TrustedResourceURL since its value is known to never
+// have left application control.
+//
+// In order to ensure that an attacker cannot influence the TrustedResourceURL
+// value, a TrustedResourceURL can only be instantiated from compile-time
+// constant string literals, command-line flags or a combination of the two,
+// but never from arbitrary string values potentially representing untrusted user input.
+//
+// Additionally, TrustedResourceURLs can be serialized and passed along within
+// the application via protocol buffers. It is the application’s responsibility
+// to ensure that the protocol buffers originate from within the application
+// itself and not from an external entity outside its trust domain.
+//
+// Note that TrustedResourceURLs can also use absolute paths (starting with '/')
+// and relative paths. This allows the same binary to be used for different
+// hosts without hard-coding the hostname in a string literal.
+type TrustedResourceURL struct {
+ // We declare a TrustedResourceURL not as a string but as a struct wrapping a string
+ // to prevent construction of TrustedResourceURL values through string conversion.
+ str string
+}
+
+// TrustedResourceURLWithParams constructs a new TrustedResourceURL with the
+// given key-value pairs added as query parameters.
+//
+// Map entries with empty keys or values are ignored. The order of appended
+// keys is guaranteed to be stable but may differ from the order in input.
+func TrustedResourceURLWithParams(t TrustedResourceURL, params map[string]string) TrustedResourceURL {
+ url := t.str
+ var fragment string
+ if i := strings.IndexByte(url, '#'); i != -1 {
+ // The fragment identifier component will always appear at the end
+ // of the URL after the query segment. It is therefore safe to
+ // trim the fragment from the tail of the URL and re-append it after
+ // all query parameters have been added.
+ // See https://tools.ietf.org/html/rfc3986#appendix-A.
+ fragment = url[i:]
+ url = url[:i]
+ }
+ sep := "?"
+ if i := strings.IndexRune(url, '?'); i != -1 {
+ // The first "?" in a URL indicates the start of the query component.
+ // See https://tools.ietf.org/html/rfc3986#section-3.4
+ if i == len(url)-1 {
+ sep = ""
+ } else {
+ sep = "&"
+ }
+ }
+ stringParams := make([]string, 0, len(params))
+ for k, v := range params {
+ if k == "" || v == "" {
+ continue
+ }
+ stringParam := safehtmlutil.QueryEscapeURL(k) + "=" + safehtmlutil.QueryEscapeURL(v)
+ stringParams = append(stringParams, stringParam)
+ }
+ if len(stringParams) > 0 {
+ sort.Strings(stringParams)
+ url += sep + strings.Join(stringParams, "&")
+ }
+ return TrustedResourceURL{url + fragment}
+}
+
+// TrustedResourceURLFromConstant constructs a TrustedResourceURL with its underlying
+// URL set to the given url, which must be an untyped string constant.
+//
+// No runtime validation or sanitization is performed on url; being under
+// application control, it is simply assumed to comply with the TrustedResourceURL type
+// contract.
+func TrustedResourceURLFromConstant(url stringConstant) TrustedResourceURL {
+ return TrustedResourceURL{string(url)}
+}
+
+// TrustedResourceURLFormatFromConstant constructs a TrustedResourceURL from a
+// format string, which must be an untyped string constant, and string arguments.
+//
+// Arguments are specified as a map of labels, which must contain only alphanumeric
+// and '_' runes, to string values. Each `%{<label>}` marker in the format string is
+// replaced by the string value identified by <label> after it has been URL-escaped.
+// Arguments that do not match any label in the format string are ignored.
+//
+// The format string must have a prefix of one of the following forms:
+// * `https://<origin>/`
+// * `//<origin>/`
+// * `/<pathStart>`
+// * `about:blank#`
+//
+// `<origin>` must contain only alphanumerics, '.', ':', '[', ']', or '-', and
+// `<pathStart>` is any character except `/` and `\`.
+func TrustedResourceURLFormatFromConstant(format stringConstant, args map[string]string) (TrustedResourceURL, error) {
+ return trustedResourceURLFormat(string(format), args)
+}
+
+// TrustedResourceURLFormatFromFlag is a variant of TrustedResourceURLFormatFromConstant
+// that constructs a TrustedResourceURL from a format string, which is given as a flag.Value,
+// and string arguments.
+//
+// See TrustedResourceURLFormatFromConstant for more details about format
+// string markers and validation.
+func TrustedResourceURLFormatFromFlag(format flag.Value, args map[string]string) (TrustedResourceURL, error) {
+ return trustedResourceURLFormat(fmt.Sprint(format.String()), args)
+}
+
+func trustedResourceURLFormat(format string, args map[string]string) (TrustedResourceURL, error) {
+ if !safehtmlutil.IsSafeTrustedResourceURLPrefix(format) {
+ return TrustedResourceURL{}, fmt.Errorf("%q is a disallowed TrustedResourceURL format string", format)
+ }
+ var err error
+ ret := trustedResourceURLFormatMarkerPattern.ReplaceAllStringFunc(format, func(match string) string {
+ argName := match[len("%{") : len(match)-len("}")]
+ argVal, ok := args[argName]
+ if !ok {
+ if err == nil {
+ // Report an error for the first missing argument.
+ err = fmt.Errorf("expected argument named %q", argName)
+ }
+ return ""
+ }
+ if safehtmlutil.URLContainsDoubleDotSegment(argVal) {
+ // Reject values 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.
+ err = fmt.Errorf(`argument %q with value %q must not contain ".."`, argName, argVal)
+ return ""
+ }
+ // QueryEscapeURL escapes some non-reserved characters in the path
+ // segment (e.g. '/' and '?') in order to prevent the injection of any new path
+ // segments or URL components.
+ return safehtmlutil.QueryEscapeURL(argVal)
+ })
+ return TrustedResourceURL{ret}, err
+}
+
+// trustedResourceURLFormatMarkerPattern matches markers in TrustedResourceURLFormat
+// format strings.
+var trustedResourceURLFormatMarkerPattern = regexp.MustCompile(`%{[[:word:]]+}`)
+
+// TrustedResourceURLFromFlag returns a TrustedResourceURL 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 TrustedResourceURLFromFlag(value flag.Value) TrustedResourceURL {
+ return TrustedResourceURL{fmt.Sprint(value.String())}
+}
+
+// String returns the string form of the TrustedResourceURL.
+func (t TrustedResourceURL) String() string {
+ return t.str
+}
+
+// TrustedResourceURLAppend URL-escapes a string and appends it to the TrustedResourceURL.
+//
+// This function can only be used if the TrustedResourceURL has a prefix of one of the following
+// forms:
+// * `https://<origin>/`
+// * `//<origin>/`
+// * `/<pathStart>`
+// * `about:blank#`
+//
+// `<origin>` must contain only alphanumerics, '.', ':', '[', ']', or '-', and
+// `<pathStart>` is any character except `/` and `\`.
+func TrustedResourceURLAppend(t TrustedResourceURL, s string) (TrustedResourceURL, error) {
+ if !safehtmlutil.IsSafeTrustedResourceURLPrefix(t.str) {
+ return TrustedResourceURL{}, fmt.Errorf("cannot append to TrustedResourceURL %q because it has an unsafe prefix", t)
+ }
+ return TrustedResourceURL{t.str + safehtmlutil.QueryEscapeURL(s)}, nil
+}
diff --git a/vendor/github.com/google/safehtml/uncheckedconversions/uncheckedconversions.go b/vendor/github.com/google/safehtml/uncheckedconversions/uncheckedconversions.go
new file mode 100644
index 000000000..1b753a52d
--- /dev/null
+++ b/vendor/github.com/google/safehtml/uncheckedconversions/uncheckedconversions.go
@@ -0,0 +1,131 @@
+// 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 uncheckedconversions provides functions to create values of package
+// safehtml types from plain strings. Use of these functions could potentially
+// result in instances of safe HTML types that violate their type contracts,
+// and hence result in security vulnerabilties.
+//
+// Avoid use of the functions in this file whenever possible; instead prefer to
+// create instances of package safehtml types using inherently safe builders or
+// template systems.
+//
+// Example appropriate uses include:
+// * Wrapping the result of general-purpose or application-specific content
+// sanitizer libraries.
+// * Wrapping the result of rendering strictly contextually autoescaping
+// templates (assuming the template's autoescaping implementation is indeed
+// strict enough to support the type contract).
+package uncheckedconversions
+
+import (
+ "github.com/google/safehtml/internal/raw"
+ "github.com/google/safehtml"
+)
+
+var html = raw.HTML.(func(string) safehtml.HTML)
+var script = raw.Script.(func(string) safehtml.Script)
+var style = raw.Style.(func(string) safehtml.Style)
+var styleSheet = raw.StyleSheet.(func(string) safehtml.StyleSheet)
+var url = raw.URL.(func(string) safehtml.URL)
+var trustedResourceURL = raw.TrustedResourceURL.(func(string) safehtml.TrustedResourceURL)
+var identifier = raw.Identifier.(func(string) safehtml.Identifier)
+
+// HTMLFromStringKnownToSatisfyTypeContract converts a string into a HTML.
+//
+func HTMLFromStringKnownToSatisfyTypeContract(s string) safehtml.HTML {
+ return html(s)
+}
+
+// ScriptFromStringKnownToSatisfyTypeContract converts a string into a Script.
+//
+// Users of this function must ensure themselves that the string does not
+// contain unsafe script. Note in particular that '<' is dangerous, even when
+// inside JavaScript strings, and so should always be forbidden or JavaScript
+// escaped in user controlled input. For example, if
+// "</script><script>evil</script>" were interpolated inside a JavaScript
+// string,it would break out of the context of the original script element and
+// "evil" would execute. Also note that within an HTML script (raw text)
+// element, HTML character references, such as "&lt;" are not allowed. See
+// http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements.
+func ScriptFromStringKnownToSatisfyTypeContract(s string) safehtml.Script {
+ return script(s)
+}
+
+// StyleFromStringKnownToSatisfyTypeContract converts a string into a Style.
+//
+// Users of thie function must ensure themselves that the string:
+// * Does not contain unsafe CSS.
+// * Does not contain literal angle brackets. Otherwise, it could be unsafe to
+// place a Style into the contents of a <style> element where it can't be
+// HTML escaped (see http://www.w3.org/International/questions/qa-escapes).
+// For example, if the Style containing
+// "font: 'foo <style/><script>evil</script>'" was interpolated within a
+// <style> tag, it would then break out of the style context into HTML.
+// * Does not end in a property value or property name context.
+// For example, a value of "background:url(\"" or "font-" does not satisfy
+// the Style type contract. This rule is enforced to ensure composability:
+// concatenating two incomplete strings that themselves do not contain unsafe
+// CSS can result in an overall string that does. For example, if
+// "javascript:evil())\"" is appended to "background:url(\"", the resulting
+// string may result in the execution of a malicious script.
+//
+// The string may, however, contain literal single or double quotes (for example,
+// in the "content" property). Therefore, the entire style string must be
+// escaped when used in a style attribute.
+//
+// The following example values comply with Style's type contract:
+// width: 1em;
+// height:1em;
+// width: 1em;height: 1em;
+// background:url('http://url');
+//
+// In addition, the empty string is safe for use in a style attribute.
+//
+// The following example values do NOT comply with this type's contract:
+// background: red --- missing a trailing semi-colon
+// background: --- missing a value and a trailing semi-colon
+// 1em --- missing an attribute name, which provides context
+// for the value
+//
+// See also http://www.w3.org/TR/css3-syntax/.
+func StyleFromStringKnownToSatisfyTypeContract(s string) safehtml.Style {
+ return style(s)
+}
+
+// StyleSheetFromStringKnownToSatisfyTypeContract converts a string into a StyleSheet.
+//
+// Users of this function must ensure themselves that the string does not
+// contain unsafe script. Note in particular that '<' is dangerous, even when
+// inside CSS strings, and so should always be forbidden or CSS-escaped in
+// user controlled input. For example, if
+// "</style><script>evil</script>" were interpolated inside a CSS string, it
+// would break out of the context of the original style element and "evil" would
+// execute. Also note that within an HTML style (raw text) element, HTML
+// character references, such as "&lt;", are not allowed.See
+// http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements
+// (Similar considerations apply to the style element.)
+func StyleSheetFromStringKnownToSatisfyTypeContract(s string) safehtml.StyleSheet {
+ return styleSheet(s)
+}
+
+// URLFromStringKnownToSatisfyTypeContract converts a string into a URL.
+//
+func URLFromStringKnownToSatisfyTypeContract(s string) safehtml.URL {
+ return url(s)
+}
+
+// TrustedResourceURLFromStringKnownToSatisfyTypeContract converts a string into a TrustedResourceURL.
+//
+func TrustedResourceURLFromStringKnownToSatisfyTypeContract(s string) safehtml.TrustedResourceURL {
+ return trustedResourceURL(s)
+}
+
+// IdentifierFromStringKnownToSatisfyTypeContract converts a string into a Identifier.
+//
+func IdentifierFromStringKnownToSatisfyTypeContract(s string) safehtml.Identifier {
+ return identifier(s)
+}
diff --git a/vendor/github.com/google/safehtml/url.go b/vendor/github.com/google/safehtml/url.go
new file mode 100644
index 000000000..6e772219c
--- /dev/null
+++ b/vendor/github.com/google/safehtml/url.go
@@ -0,0 +1,127 @@
+// 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 safehtml
+
+import (
+ "regexp"
+ "strings"
+)
+
+// A URL is an immutable string-like type that is safe to use in URL contexts in
+// DOM APIs and HTML documents.
+//
+// URL guarantees that its value as a string will not cause untrusted script execution
+// when evaluated as a hyperlink URL in a browser.
+//
+// Values of this type are guaranteed to be safe to use in URL/hyperlink contexts,
+// such as assignment to URL-valued DOM properties, in the sense that the use
+// will not result in a Cross-site Scripting (XSS) vulnerability. Similarly, URLs can
+// be interpolated into the URL context of an HTML template (e.g. inside a href attribute).
+// However, appropriate HTML-escaping must still be applied.
+//
+// Note that this type's contract does not imply any guarantees regarding the resource
+// the URL refers to. In particular, URLs are not safe to use in a context
+// where the referred-to resource is interpreted as trusted code, e.g., as the src of
+// a script tag. For safely loading trusted resources, use the TrustedResourceURL type.
+type URL struct {
+ // We declare a URL not as a string but as a struct wrapping a string
+ // to prevent construction of URL values through string conversion.
+ str string
+}
+
+// InnocuousURL is an innocuous URL generated by URLSanitized when passed an unsafe URL.
+//
+// about:invalid is registered in http://www.w3.org/TR/css3-values/#about-invalid,
+// and "references a non-existent document with a generic error condition. It can be
+// used when a URI is necessary, but the default value shouldn't be resolveable as any
+// type of document."
+//
+// http://tools.ietf.org/html/rfc6694#section-2.1 permits about URLs to contain
+// a fragment, which is not to be considered when determining if an about URL is
+// well-known.
+const InnocuousURL = "about:invalid#zGoSafez"
+
+// URLSanitized returns a URL whose value is url, validating that the input string matches
+// a pattern of commonly used safe URLs. If url fails validation, this method returns a
+// URL containing InnocuousURL.
+//
+// url may be a URL with the http, https, ftp or mailto scheme, or a relative URL,
+// i.e. a URL without a scheme. Specifically, a relative URL may be scheme-relative,
+// absolute-path-relative, or path-relative. See
+// http://url.spec.whatwg.org/#concept-relative-url.
+//
+// url may also be a base64 data URL with an allowed audio, image or video MIME type.
+//
+// No attempt is made at validating that the URL percent-decodes to structurally valid or
+// interchange-valid UTF-8 since the percent-decoded representation is unsafe to use in an
+// HTML context regardless of UTF-8 validity.
+func URLSanitized(url string) URL {
+ if !isSafeURL(url) {
+ return URL{InnocuousURL}
+ }
+ return URL{url}
+}
+
+// safeURLPattern matches URLs that
+// (a) Start with a scheme in an allowlist (http, https, mailto, ftp); or
+// (b) Contain no scheme. To ensure that the URL cannot be interpreted as a
+// disallowed scheme URL, ':' may only appear after one of the runes [/?#].
+//
+// The origin (RFC 6454) in which a URL is loaded depends on
+// its scheme. We assume that the scheme used by the current document is HTTPS, HTTP, or
+// something equivalent. We allow relative URLs unless in a particularly sensitive context
+// called a "TrustedResourceUrl" context. In a non-TrustedResourceURL context we allow absolute
+// URLs whose scheme is on a white-list.
+//
+// The position of the first colon (':') character determines whether a URL is absolute or relative.
+// Looking at the prefix leading up to the first colon allows us to identify relative and absolute URLs,
+// extract the scheme, and minimize the risk of a user-agent concluding a URL specifies a scheme not in
+// our allowlist.
+//
+// According to RFC 3986 Section 3, the normative interpretation of the canonicial WHATWG specification
+// (https://url.spec.whatwg.org/#url-scheme-string), colons can appear in a URL in these locations:
+// * A colon after a non-empty run of (ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )) ends a scheme.
+// If the colon after the scheme is not followed by "//" then any subsequent colons are part
+// of an opaque URI body.
+// * Otherwise, a colon after a hash (#) must be in the fragment.
+// * Otherwise, a colon after a (?) must be in the query.
+// * Otherwise, a colon after a single solidus ("/") must be in the path.
+// * Otherwise, a colon after a double solidus ("//") must be in the authority (before port).
+// * Otherwise, a colon after a valid protocol must be in the opaque part of the URL.
+var safeURLPattern = regexp.MustCompile(`^(?:(?:https?|mailto|ftp):|[^:/?#]*(?:[/?#]|$))`)
+
+// dataURLPattern matches base-64 data URLs (RFC 2397), with the first capture group being the media type
+// specification given as a MIME type.
+//
+// Note: this pattern does not match data URLs containig media type specifications with optional parameters,
+// such as `data:text/javascript;charset=UTF-8;base64,...`. This is ok since this pattern only needs to
+// match audio, image and video MIME types in its capture group.
+var dataURLPattern = regexp.MustCompile(`^data:([^;,]*);base64,[a-z0-9+/]+=*$`)
+
+// safeMIMETypePattern matches MIME types that are safe to include in a data URL.
+var safeMIMETypePattern = regexp.MustCompile(`^(?:audio/(?:3gpp2|3gpp|aac|midi|mp3|mp4|mpeg|oga|ogg|opus|x-m4a|x-matroska|x-wav|wav|webm)|image/(?:bmp|gif|jpeg|jpg|png|tiff|webp|x-icon)|video/(?:mpeg|mp4|ogg|webm|x-matroska))$`)
+
+// isSafeURL matches url to a subset of URLs that will not cause script execution if used in
+// a URL context within a HTML document. Specifically, this method returns true if url:
+// (a) Starts with a scheme in the default allowlist (http, https, mailto, ftp); or
+// (b) Contains no scheme. To ensure that the URL cannot be interpreted as a
+// disallowed scheme URL, the runes ':', and '&' may only appear
+// after one of the runes [/?#].
+func isSafeURL(url string) bool {
+ // Ignore case.
+ url = strings.ToLower(url)
+ if safeURLPattern.MatchString(url) {
+ return true
+ }
+ submatches := dataURLPattern.FindStringSubmatch(url)
+ return len(submatches) == 2 && safeMIMETypePattern.MatchString(submatches[1])
+}
+
+// String returns the string form of the URL.
+func (u URL) String() string {
+ return u.str
+}
diff --git a/vendor/github.com/google/safehtml/urlset.go b/vendor/github.com/google/safehtml/urlset.go
new file mode 100644
index 000000000..8d74a7732
--- /dev/null
+++ b/vendor/github.com/google/safehtml/urlset.go
@@ -0,0 +1,167 @@
+// 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 safehtml
+
+import (
+ "bytes"
+ "strconv"
+)
+
+// https://infra.spec.whatwg.org/#ascii-whitespace
+// ASCII whitespace is U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, or U+0020 SPACE.
+var asciiWhitespace [256]bool
+
+// Metacharacters that affect parsing of srcset values.
+var srcsetMetachars [256]bool
+
+func init() {
+ asciiWhitespace['\t'] = true
+ asciiWhitespace[' '] = true
+ asciiWhitespace['\n'] = true
+ asciiWhitespace['\f'] = true
+ asciiWhitespace['\r'] = true
+
+ srcsetMetachars['\t'] = true
+ srcsetMetachars[' '] = true
+ srcsetMetachars['\n'] = true
+ srcsetMetachars['\f'] = true
+ srcsetMetachars['\r'] = true
+ srcsetMetachars[','] = true
+}
+
+// URLSetSanitized returns a safe srcset by individually vetting each
+// substring that specifies a URL.
+//
+// https://html.spec.whatwg.org/multipage/images.html#srcset-attributes
+func URLSetSanitized(str string) URLSet {
+ var buffer bytes.Buffer
+
+ for len(str) != 0 {
+ // Consume one image candidate
+ var url, metadata string
+ _, str = consumeIn(str, asciiWhitespace)
+ url, str = consumeNotIn(str, asciiWhitespace)
+ _, str = consumeIn(str, asciiWhitespace)
+ metadata, str = consumeNotIn(str, srcsetMetachars)
+ _, str = consumeIn(str, asciiWhitespace)
+
+ // Append sanitized content onto buffer.
+ if len(url) != 0 && isSafeURL(url) && isOptionalSrcMetadataWellFormed(metadata) {
+ if buffer.Len() != 0 {
+ // The space before the comma is necessary because
+ // a comma adjacent to a URL will attach to it.
+ buffer.WriteString(" , ")
+ }
+ // URL may contain commas. Disambiguate.
+ appendURLToSet(url, &buffer)
+ if len(metadata) != 0 {
+ buffer.WriteByte(' ')
+ buffer.WriteString(metadata)
+ }
+ }
+
+ // Consume any trailing comma
+ if len(str) == 0 || str[0] != ',' {
+ break
+ }
+ str = str[1:]
+ }
+
+ if buffer.Len() == 0 {
+ return URLSet{InnocuousURL}
+ }
+
+ return URLSet{buffer.String()}
+}
+
+// appendURLToSet appends a URL so that it does not start or end with a comma
+//
+// https://html.spec.whatwg.org/multipage/images.html#srcset-attributes
+// parsing step 2 which says:
+// """
+// A valid non-empty URL that does not start or end with a U+002C COMMA character (,),
+// referencing a non-interactive, optionally animated, image resource that is neither
+// paged nor scripted
+// """
+//
+// Simply replacing all commas would break data:image/png;base64,IMAGECONTENT
+// Note: This breaks data URLs with empty content since they end with a comma.
+// We could handle that case by appending a '#'.
+func appendURLToSet(url string, buffer *bytes.Buffer) {
+ n := len(url)
+ left, right := 0, n
+ if url[left] == ',' {
+ buffer.WriteString("%2c")
+ left++
+ }
+ commaAtEnd := false
+ if left < right && url[right-1] == ',' {
+ commaAtEnd = true
+ right--
+ }
+ buffer.WriteString(url[left:right])
+ if commaAtEnd {
+ buffer.WriteString("%2c")
+ }
+}
+
+// consumeNotIn uses bytes in str as bit indices in mask to find
+// the least index >= left whose byte corresponds to a zero bit.
+func consumeNotIn(str string, mask [256]bool) (consumed, rest string) {
+ i, n := 0, len(str)
+ for ; i < n; i++ {
+ if mask[str[i]] {
+ return str[0:i], str[i:n]
+ }
+ }
+ return str, ""
+}
+
+// consumeIn is like consumeNotIn but treats mask as inverted.
+func consumeIn(str string, mask [256]bool) (consumed, rest string) {
+ for i, n := 0, len(str); i < n; i++ {
+ if !mask[str[i]] {
+ return str[0:i], str[i:n]
+ }
+ }
+ return str, ""
+}
+
+// isOptionalSrcMetadataWellFormed is true when its input is empty and
+// when it is a floating point number optionally followed by an ASCII letter.
+func isOptionalSrcMetadataWellFormed(metadata string) bool {
+ // srcset for both image candidates (<img srcset>) and
+ // the proposal for script allow a number and an optional letter
+ // afterwards.
+ n := len(metadata)
+ if n == 0 {
+ // Metadata is optional
+ return true
+ }
+ metadataPrefix := metadata
+ if last := metadata[n-1] | 32; 'a' <= last && last <= 'z' {
+ metadataPrefix = metadata[0 : n-1]
+ }
+ // This overmatches
+ // html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-floating-point-number
+ // but is sufficient.
+ _, err := strconv.ParseFloat(metadataPrefix, 64)
+ return err == nil
+}
+
+// URLSet corresponds to the value of a srcset attribute outside a
+// TrustedResourceURL context.
+type URLSet struct {
+ // We declare a URLSet not as a string but as a struct wrapping a string
+ // to prevent construction of URL values through string conversion.
+ str string
+}
+
+// String returns the string content of a URLSet
+func (s URLSet) String() string {
+ return s.str
+}
diff --git a/vendor/github.com/google/uuid/go.mod b/vendor/github.com/google/uuid/go.mod
deleted file mode 100644
index fc84cd79d..000000000
--- a/vendor/github.com/google/uuid/go.mod
+++ /dev/null
@@ -1 +0,0 @@
-module github.com/google/uuid