diff options
| author | Taras Madan <tarasmadan@google.com> | 2023-02-22 22:16:50 +0100 |
|---|---|---|
| committer | Taras Madan <tarasmadan@google.com> | 2023-02-24 12:47:23 +0100 |
| commit | 4165372ec8fd142475a4e35fd0cf4f8042132208 (patch) | |
| tree | 21cd62211b4dd80bee469054c5b65db77342333c /vendor/github.com/google | |
| parent | 2b3ed821a493b8936c8bacfa6f8b4f1c90a00855 (diff) | |
dependencies: update
set go min requirements to 1.19
update dependencies
update vendor
Diffstat (limited to 'vendor/github.com/google')
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 '&'. +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"> + </a><script>alert('pwned')</script><a> + </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) 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) 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("<") + 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("Hi!")"> + // 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 "&" 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
". + 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 "<" 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 "<", 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 |
