Single Fetcher.Fetch on the Guard that holds DenyPrefixes. skipTlsVerification is ignored. No 502. keys add writes the secret only to --out. Release binary has no -test-allow-prefix.
451 lines
14 KiB
Go
451 lines
14 KiB
Go
package tracker
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
"github.com/pelletier/go-toml/v2/unstable"
|
|
)
|
|
|
|
type keyKind uint8
|
|
|
|
const (
|
|
invalidKind keyKind = iota
|
|
// valueKind is a regular value (scalar, array, or inline table). It
|
|
// cannot be extended.
|
|
valueKind
|
|
// kvTableKind is a table created implicitly by a dotted key. It can only
|
|
// be extended by other dotted keys.
|
|
kvTableKind
|
|
// tableKind is a table created by a [header]. The explicit flag tells
|
|
// whether the table was created by its own header (true) or as an
|
|
// intermediate step of a longer key (false).
|
|
tableKind
|
|
// arrayTableKind is an array of tables created by [[header]].
|
|
arrayTableKind
|
|
// anonymousKind is an entry that cannot be looked up by name. It serves
|
|
// as the parent of the content of inline tables stored inside arrays.
|
|
anonymousKind
|
|
)
|
|
|
|
func (k keyKind) String() string {
|
|
switch k {
|
|
case invalidKind:
|
|
return "invalid"
|
|
case valueKind:
|
|
return "value"
|
|
case kvTableKind:
|
|
return "kv-table"
|
|
case tableKind:
|
|
return "table"
|
|
case arrayTableKind:
|
|
return "array-table"
|
|
case anonymousKind:
|
|
return "anonymous"
|
|
}
|
|
panic("missing keyKind string mapping")
|
|
}
|
|
|
|
// entry represents a node that has been seen in the document. Its size has a
|
|
// direct impact on the performance of unmarshaling documents: keep it as
|
|
// small as possible.
|
|
type entry struct {
|
|
parent int32
|
|
kind keyKind
|
|
explicit bool
|
|
name []byte
|
|
}
|
|
|
|
// SeenTracker tracks which keys have been seen with which TOML type to flag
|
|
// duplicates and mismatches according to the spec.
|
|
//
|
|
// Each node in the visited tree is represented by an entry. Each entry has
|
|
// an identifier, which is provided by a counter. Entries are stored in the
|
|
// array entries. As new nodes are discovered (referenced for the first time
|
|
// in the TOML document), entries are created and appended to the array. An
|
|
// entry points to its parent using its id.
|
|
//
|
|
// To find whether a given key (sequence of []byte) has already been visited,
|
|
// the entries are linearly searched, looking for one with the right name and
|
|
// parent id.
|
|
//
|
|
// Given that all keys appear in the document after their parent, it is
|
|
// guaranteed that all descendants of a node are stored after the node, this
|
|
// speeds up the search process.
|
|
//
|
|
// When encountering [[array tables]], the descendants of that node are removed
|
|
// to allow that branch of the tree to be "rediscovered". To maintain the
|
|
// invariant above, the deletion process needs to keep the order of entries.
|
|
// This results in more copies in that case.
|
|
type SeenTracker struct {
|
|
entries []entry
|
|
currentTable int32
|
|
|
|
// scratch buffers for clear()
|
|
removedBuf []bool
|
|
remapBuf []int32
|
|
}
|
|
|
|
// Reset brings the tracker to its initial state, with just a root table, so
|
|
// that it can be reused across documents.
|
|
func (s *SeenTracker) Reset() {
|
|
s.reset()
|
|
}
|
|
|
|
// reset brings the tracker to its initial state, with just a root table.
|
|
func (s *SeenTracker) reset() {
|
|
s.entries = append(s.entries[:0], entry{
|
|
parent: -1,
|
|
kind: tableKind,
|
|
})
|
|
s.currentTable = 0
|
|
}
|
|
|
|
// find returns the id of the entry with the given parent and name, or -1.
|
|
// Anonymous entries are never returned.
|
|
func (s *SeenTracker) find(parent int32, name []byte) int32 {
|
|
// Children always appear after their parent.
|
|
for i := int(parent) + 1; i < len(s.entries); i++ {
|
|
e := &s.entries[i]
|
|
if e.parent == parent && e.kind != anonymousKind && bytes.Equal(e.name, name) {
|
|
return int32(i) //nolint:gosec // entry counts are bounded by document size
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// create appends a new entry and returns its id.
|
|
func (s *SeenTracker) create(parent int32, name []byte, kind keyKind, explicit bool) int32 {
|
|
id := int32(len(s.entries)) //nolint:gosec // entry counts are bounded by document size
|
|
s.entries = append(s.entries, entry{
|
|
parent: parent,
|
|
kind: kind,
|
|
explicit: explicit,
|
|
name: name,
|
|
})
|
|
return id
|
|
}
|
|
|
|
// clear removes all the descendants of the entry with the given id, keeping
|
|
// the order of the remaining entries.
|
|
func (s *SeenTracker) clear(id int32) {
|
|
// Compute which entries are removed. Given that children always appear
|
|
// after their parent, a single forward pass is enough.
|
|
if cap(s.removedBuf) < len(s.entries) {
|
|
s.removedBuf = make([]bool, len(s.entries))
|
|
s.remapBuf = make([]int32, len(s.entries))
|
|
}
|
|
removed := s.removedBuf[:len(s.entries)]
|
|
remap := s.remapBuf[:len(s.entries)]
|
|
for i := range removed {
|
|
removed[i] = false
|
|
}
|
|
|
|
n := int32(0)
|
|
for i := 0; i < len(s.entries); i++ {
|
|
parent := s.entries[i].parent
|
|
if parent >= 0 && (parent == id && s.entries[i].kind != invalidKind || removed[parent]) {
|
|
removed[i] = true
|
|
continue
|
|
}
|
|
remap[i] = n
|
|
if int32(i) != n { //nolint:gosec // entry counts are bounded by document size
|
|
e := s.entries[i]
|
|
e.parent = remap[e.parent]
|
|
s.entries[n] = e
|
|
}
|
|
n++
|
|
}
|
|
s.entries = s.entries[:n]
|
|
}
|
|
|
|
// CheckExpression takes a top-level node and checks that it does not contain
|
|
// keys that have been seen in previous calls, and validates that types are
|
|
// consistent. It returns true if it is the first time this node's key is
|
|
// seen. Useful to clear array tables on first use.
|
|
func (s *SeenTracker) CheckExpression(node *unstable.Node) (bool, error) {
|
|
if len(s.entries) == 0 {
|
|
s.reset()
|
|
}
|
|
switch node.Kind {
|
|
case unstable.KeyValue:
|
|
return false, s.checkKeyValue(s.currentTable, node)
|
|
case unstable.Table:
|
|
return s.checkTable(node)
|
|
case unstable.ArrayTable:
|
|
return s.checkArrayTable(node)
|
|
default:
|
|
return false, fmt.Errorf("toml: unexpected expression kind %s", node.Kind)
|
|
}
|
|
}
|
|
|
|
// CheckTable validates a [table] header given the decoded parts of its key.
|
|
// It mirrors checkTable but is driven directly from the key parts instead of
|
|
// an AST, for callers that decode without building one. It returns whether the
|
|
// table is seen for the first time.
|
|
func (s *SeenTracker) CheckTable(parts [][]byte) (bool, error) {
|
|
parent := int32(0)
|
|
for k := 0; k < len(parts); k++ {
|
|
name := parts[k]
|
|
if k == len(parts)-1 {
|
|
// Final part of the key.
|
|
i := s.find(parent, name)
|
|
if i < 0 {
|
|
i = s.create(parent, name, tableKind, true)
|
|
s.currentTable = i
|
|
return true, nil
|
|
}
|
|
e := &s.entries[i]
|
|
switch e.kind {
|
|
case tableKind:
|
|
if e.explicit {
|
|
return false, fmt.Errorf("toml: table %s already exists", name)
|
|
}
|
|
e.explicit = true
|
|
s.currentTable = i
|
|
return false, nil
|
|
case kvTableKind:
|
|
return false, fmt.Errorf("toml: table %s already exists as defined by a dotted key", name)
|
|
case arrayTableKind:
|
|
return false, fmt.Errorf("toml: table %s already exists as an array of tables", name)
|
|
default:
|
|
return false, fmt.Errorf("toml: key %s should be a table, not a %s", name, e.kind)
|
|
}
|
|
}
|
|
|
|
i := s.find(parent, name)
|
|
if i < 0 {
|
|
i = s.create(parent, name, tableKind, false)
|
|
} else {
|
|
switch s.entries[i].kind {
|
|
case tableKind, arrayTableKind, kvTableKind:
|
|
// Tables created by dotted keys can receive new sub-tables,
|
|
// but cannot be redefined (handled by the last-part case).
|
|
default:
|
|
return false, fmt.Errorf("toml: key %s already exists as a value", name)
|
|
}
|
|
}
|
|
parent = i
|
|
}
|
|
panic("unreachable: table expression without key")
|
|
}
|
|
|
|
// CheckArrayTable validates a [[array table]] header given the decoded parts
|
|
// of its key. It mirrors checkArrayTable but is driven directly from the key
|
|
// parts. It returns whether the array table is seen for the first time.
|
|
func (s *SeenTracker) CheckArrayTable(parts [][]byte) (bool, error) {
|
|
parent := int32(0)
|
|
for k := 0; k < len(parts); k++ {
|
|
name := parts[k]
|
|
if k == len(parts)-1 {
|
|
i := s.find(parent, name)
|
|
if i < 0 {
|
|
i = s.create(parent, name, arrayTableKind, true)
|
|
s.currentTable = i
|
|
return true, nil
|
|
}
|
|
if s.entries[i].kind != arrayTableKind {
|
|
return false, fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", name, s.entries[i].kind)
|
|
}
|
|
// Make the descendants of this array table re-discoverable for
|
|
// the new element.
|
|
s.clear(i)
|
|
s.currentTable = i
|
|
return false, nil
|
|
}
|
|
|
|
i := s.find(parent, name)
|
|
if i < 0 {
|
|
i = s.create(parent, name, tableKind, false)
|
|
} else {
|
|
switch s.entries[i].kind {
|
|
case tableKind, arrayTableKind, kvTableKind:
|
|
// Tables created by dotted keys can receive new sub-tables,
|
|
// but cannot be redefined (handled by the last-part case).
|
|
default:
|
|
return false, fmt.Errorf("toml: key %s already exists as a value", name)
|
|
}
|
|
}
|
|
parent = i
|
|
}
|
|
panic("unreachable: array table expression without key")
|
|
}
|
|
|
|
// CheckKeyValue validates the (possibly dotted) key of a key-value under the
|
|
// current table, WITHOUT validating its value. It returns the id of the leaf
|
|
// entry, so the caller can validate a container value with CheckValueUnder.
|
|
func (s *SeenTracker) CheckKeyValue(parts [][]byte) (int32, error) {
|
|
parent := s.currentTable
|
|
for k := 0; k < len(parts); k++ {
|
|
name := parts[k]
|
|
if k == len(parts)-1 {
|
|
if i := s.find(parent, name); i >= 0 {
|
|
return -1, fmt.Errorf("toml: key %s is already defined", name)
|
|
}
|
|
return s.create(parent, name, valueKind, false), nil
|
|
}
|
|
|
|
i := s.find(parent, name)
|
|
if i < 0 {
|
|
i = s.create(parent, name, kvTableKind, false)
|
|
} else if s.entries[i].kind != kvTableKind {
|
|
return -1, fmt.Errorf("toml: key %s is already defined", name)
|
|
}
|
|
parent = i
|
|
}
|
|
panic("unreachable: key-value expression without key")
|
|
}
|
|
|
|
// CheckValueUnder validates the content of a value stored under the given
|
|
// entry (typically the leaf returned by CheckKeyValue): inline tables cannot
|
|
// contain duplicate keys, including in the inline tables and arrays they
|
|
// contain.
|
|
func (s *SeenTracker) CheckValueUnder(parent int32, value *unstable.Node) error {
|
|
return s.checkValue(parent, value)
|
|
}
|
|
|
|
func (s *SeenTracker) checkTable(node *unstable.Node) (bool, error) {
|
|
parent := int32(0)
|
|
|
|
it := node.Key()
|
|
// Handle the intermediate parts of the key.
|
|
for it.Next() {
|
|
part := it.Node()
|
|
name := part.Data
|
|
if it.IsLast() {
|
|
// Final part of the key.
|
|
i := s.find(parent, name)
|
|
if i < 0 {
|
|
i = s.create(parent, name, tableKind, true)
|
|
s.currentTable = i
|
|
return true, nil
|
|
}
|
|
e := &s.entries[i]
|
|
switch e.kind {
|
|
case tableKind:
|
|
if e.explicit {
|
|
return false, fmt.Errorf("toml: table %s already exists", name)
|
|
}
|
|
e.explicit = true
|
|
s.currentTable = i
|
|
return false, nil
|
|
case kvTableKind:
|
|
return false, fmt.Errorf("toml: table %s already exists as defined by a dotted key", name)
|
|
case arrayTableKind:
|
|
return false, fmt.Errorf("toml: table %s already exists as an array of tables", name)
|
|
default:
|
|
return false, fmt.Errorf("toml: key %s should be a table, not a %s", name, e.kind)
|
|
}
|
|
}
|
|
|
|
i := s.find(parent, name)
|
|
if i < 0 {
|
|
i = s.create(parent, name, tableKind, false)
|
|
} else {
|
|
switch s.entries[i].kind {
|
|
case tableKind, arrayTableKind, kvTableKind:
|
|
// Tables created by dotted keys can receive new sub-tables,
|
|
// but cannot be redefined (handled by the last-part case).
|
|
default:
|
|
return false, fmt.Errorf("toml: key %s already exists as a value", name)
|
|
}
|
|
}
|
|
parent = i
|
|
}
|
|
panic("unreachable: table expression without key")
|
|
}
|
|
|
|
func (s *SeenTracker) checkArrayTable(node *unstable.Node) (bool, error) {
|
|
parent := int32(0)
|
|
|
|
it := node.Key()
|
|
for it.Next() {
|
|
part := it.Node()
|
|
name := part.Data
|
|
if it.IsLast() {
|
|
i := s.find(parent, name)
|
|
if i < 0 {
|
|
i = s.create(parent, name, arrayTableKind, true)
|
|
s.currentTable = i
|
|
return true, nil
|
|
}
|
|
if s.entries[i].kind != arrayTableKind {
|
|
return false, fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", name, s.entries[i].kind)
|
|
}
|
|
// Make the descendants of this array table re-discoverable for
|
|
// the new element.
|
|
s.clear(i)
|
|
// Note: clear cannot move i because i comes before all its
|
|
// descendants.
|
|
s.currentTable = i
|
|
return false, nil
|
|
}
|
|
|
|
i := s.find(parent, name)
|
|
if i < 0 {
|
|
i = s.create(parent, name, tableKind, false)
|
|
} else {
|
|
switch s.entries[i].kind {
|
|
case tableKind, arrayTableKind, kvTableKind:
|
|
// Tables created by dotted keys can receive new sub-tables,
|
|
// but cannot be redefined (handled by the last-part case).
|
|
default:
|
|
return false, fmt.Errorf("toml: key %s already exists as a value", name)
|
|
}
|
|
}
|
|
parent = i
|
|
}
|
|
panic("unreachable: array table expression without key")
|
|
}
|
|
|
|
func (s *SeenTracker) checkKeyValue(parent int32, node *unstable.Node) error {
|
|
it := node.Key()
|
|
for it.Next() {
|
|
part := it.Node()
|
|
name := part.Data
|
|
if it.IsLast() {
|
|
if i := s.find(parent, name); i >= 0 {
|
|
return fmt.Errorf("toml: key %s is already defined", name)
|
|
}
|
|
id := s.create(parent, name, valueKind, false)
|
|
return s.checkValue(id, node.Value())
|
|
}
|
|
|
|
i := s.find(parent, name)
|
|
if i < 0 {
|
|
i = s.create(parent, name, kvTableKind, false)
|
|
} else if s.entries[i].kind != kvTableKind {
|
|
return fmt.Errorf("toml: key %s is already defined", name)
|
|
}
|
|
parent = i
|
|
}
|
|
panic("unreachable: key-value expression without key")
|
|
}
|
|
|
|
// checkValue verifies the content of a value: inline tables cannot contain
|
|
// duplicate keys, including in the inline tables and arrays they contain.
|
|
func (s *SeenTracker) checkValue(id int32, value *unstable.Node) error {
|
|
switch value.Kind {
|
|
case unstable.InlineTable:
|
|
it := value.Children()
|
|
for it.Next() {
|
|
if err := s.checkKeyValue(id, it.Node()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case unstable.Array:
|
|
it := value.Children()
|
|
for it.Next() {
|
|
elem := it.Node()
|
|
if elem.Kind == unstable.InlineTable || elem.Kind == unstable.Array {
|
|
elemID := s.create(id, nil, anonymousKind, false)
|
|
if err := s.checkValue(elemID, elem); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
default:
|
|
}
|
|
return nil
|
|
}
|