p1: /v2/scrape, /v1/fetch, auth, CLI, contract tests
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.
This commit is contained in:
+450
@@ -0,0 +1,450 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user