Files
opencloud/vendor/github.com/a8m/envsubst/parse/parse.go
Thomas Müller bdbba929d0 feat: add CSP and other security related headers in the oCIS proxy service (#8777)
* feat: add CSP and other security related headers in the oCIS proxy service

* fix: consolidate security related headers - drop middleware.Secure

* fix: use github.com/DeepDiver1975/secure

* fix: acceptance tests

* feat: support env var replacements in csp.yaml
2024-04-26 09:10:35 +02:00

190 lines
4.0 KiB
Go

// Most of the code in this package taken from golang/text/template/parse
package parse
import (
"errors"
"strings"
)
// A mode value is a set of flags (or 0). They control parser behavior.
type Mode int
// Mode for parser behaviour
const (
Quick Mode = iota // stop parsing after first error encoutered and return
AllErrors // report all errors
)
// The restrictions option controls the parsring restriction.
type Restrictions struct {
NoUnset bool
NoEmpty bool
NoDigit bool
}
// Restrictions specifier
var (
Relaxed = &Restrictions{false, false, false}
NoEmpty = &Restrictions{false, true, false}
NoUnset = &Restrictions{true, false, false}
Strict = &Restrictions{true, true, false}
)
// Parser type initializer
type Parser struct {
Name string // name of the processing template
Env Env
Restrict *Restrictions
Mode Mode
// parsing state;
lex *lexer
token [3]item // three-token lookahead
peekCount int
nodes []Node
}
// New allocates a new Parser with the given name.
func New(name string, env []string, r *Restrictions) *Parser {
return &Parser{
Name: name,
Env: Env(env),
Restrict: r,
}
}
// Parse parses the given string.
func (p *Parser) Parse(text string) (string, error) {
p.lex = lex(text, p.Restrict.NoDigit)
// Build internal array of all unset or empty vars here
var errs []error
// clean parse state
p.nodes = make([]Node, 0)
p.peekCount = 0
if err := p.parse(); err != nil {
switch p.Mode {
case Quick:
return "", err
case AllErrors:
errs = append(errs, err)
}
}
var out string
for _, node := range p.nodes {
s, err := node.String()
if err != nil {
switch p.Mode {
case Quick:
return "", err
case AllErrors:
errs = append(errs, err)
}
}
out += s
}
if len(errs) > 0 {
var b strings.Builder
for i, err := range errs {
if i > 0 {
b.WriteByte('\n')
}
b.WriteString(err.Error())
}
return "", errors.New(b.String())
}
return out, nil
}
// parse is the top-level parser for the template.
// It runs to EOF and return an error if something isn't right.
func (p *Parser) parse() error {
Loop:
for {
switch t := p.next(); t.typ {
case itemEOF:
break Loop
case itemError:
return p.errorf(t.val)
case itemVariable:
varNode := NewVariable(strings.TrimPrefix(t.val, "$"), p.Env, p.Restrict)
p.nodes = append(p.nodes, varNode)
case itemLeftDelim:
if p.peek().typ == itemVariable {
n, err := p.action()
if err != nil {
return err
}
p.nodes = append(p.nodes, n)
continue
}
fallthrough
default:
textNode := NewText(t.val)
p.nodes = append(p.nodes, textNode)
}
}
return nil
}
// Parse substitution. first item is a variable.
func (p *Parser) action() (Node, error) {
var expType itemType
var defaultNode Node
varNode := NewVariable(p.next().val, p.Env, p.Restrict)
Loop:
for {
switch t := p.next(); t.typ {
case itemRightDelim:
break Loop
case itemError:
return nil, p.errorf(t.val)
case itemVariable:
defaultNode = NewVariable(strings.TrimPrefix(t.val, "$"), p.Env, p.Restrict)
case itemText:
n := NewText(t.val)
Text:
for {
switch p.peek().typ {
case itemRightDelim, itemError, itemEOF:
break Text
default:
// patch to accept all kind of chars
n.Text += p.next().val
}
}
defaultNode = n
default:
expType = t.typ
}
}
return &SubstitutionNode{NodeSubstitution, expType, varNode, defaultNode}, nil
}
func (p *Parser) errorf(s string) error {
return errors.New(s)
}
// next returns the next token.
func (p *Parser) next() item {
if p.peekCount > 0 {
p.peekCount--
} else {
p.token[0] = p.lex.nextItem()
}
return p.token[p.peekCount]
}
// backup backs the input stream up one token.
func (p *Parser) backup() {
p.peekCount++
}
// peek returns but does not consume the next token.
func (p *Parser) peek() item {
if p.peekCount > 0 {
return p.token[p.peekCount-1]
}
p.peekCount = 1
p.token[0] = p.lex.nextItem()
return p.token[0]
}