server läuft
This commit is contained in:
1 parent
c8df9d7651
commit
42f0c0accb
21 files changed
+4185
-10
No files matched your search
@@ -0,0 +1,292 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrSyntax: Die Traefik-Regel ist syntaktisch ungültig.
|
||||
ErrSyntax = errors.New("policy: Regel-Syntax ungültig")
|
||||
// ErrUnconstrained: Die Regel ist nicht in jedem Zweig auf eigene Hosts
|
||||
// beschränkt und würde fremden Verkehr abgreifen (Catch-all).
|
||||
ErrUnconstrained = errors.New("policy: Regel nicht auf Hosts beschränkt")
|
||||
// ErrHostRegexp: HostRegexp lässt sich nicht gegen allowed_domains prüfen.
|
||||
ErrHostRegexp = errors.New("policy: HostRegexp nicht erlaubt")
|
||||
)
|
||||
|
||||
// HostsFromRule parst eine Traefik-Regel (Matcher, &&, ||, !, Klammern)
|
||||
// und liefert alle positiv geforderten Hosts in Reihenfolge des Auftretens.
|
||||
//
|
||||
// Die Regel muss in jedem ||-Zweig durch Host(...) eingeschränkt sein:
|
||||
// "Host(`a.de`) || PathPrefix(`/`)" passt sonst auf jeden Host. Negierte
|
||||
// Hosts schränken nicht ein (sie schließen nur aus). Positives HostRegexp
|
||||
// ist nicht erlaubt, weil sich ein Regex nicht gegen die Lizenz prüfen lässt.
|
||||
func HostsFromRule(rule string) ([]string, error) {
|
||||
toks, err := lex(rule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := &parser{toks: toks}
|
||||
root, err := p.parseOr()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.peek().kind != tEOF {
|
||||
return nil, p.errorf("unerwartetes %q", p.peek().text)
|
||||
}
|
||||
constrained, hosts, err := analyze(root, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !constrained {
|
||||
return nil, ErrUnconstrained
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
// analyze ermittelt, ob ein Ausdruck auf Hosts beschränkt ist, und sammelt
|
||||
// die Hosts aus positiven (nicht negierten) Host-Matchern.
|
||||
func analyze(n *node, positive bool) (constrained bool, hosts []string, err error) {
|
||||
switch n.op {
|
||||
case opCall:
|
||||
switch strings.ToLower(n.name) {
|
||||
case "host":
|
||||
if positive {
|
||||
return true, n.args, nil
|
||||
}
|
||||
case "hostregexp":
|
||||
if positive {
|
||||
return false, nil, ErrHostRegexp
|
||||
}
|
||||
}
|
||||
return false, nil, nil
|
||||
case opNot:
|
||||
_, h, err := analyze(n.kids[0], !positive)
|
||||
return false, h, err
|
||||
default: // opAnd, opOr
|
||||
c1, h1, err := analyze(n.kids[0], positive)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
c2, h2, err := analyze(n.kids[1], positive)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if n.op == opAnd {
|
||||
return c1 || c2, append(h1, h2...), nil
|
||||
}
|
||||
return c1 && c2, append(h1, h2...), nil
|
||||
}
|
||||
}
|
||||
|
||||
// --- Lexer ---
|
||||
|
||||
type tokenKind int
|
||||
|
||||
const (
|
||||
tEOF tokenKind = iota
|
||||
tIdent
|
||||
tString
|
||||
tLParen
|
||||
tRParen
|
||||
tComma
|
||||
tAnd
|
||||
tOr
|
||||
tNot
|
||||
)
|
||||
|
||||
type token struct {
|
||||
kind tokenKind
|
||||
text string
|
||||
pos int
|
||||
}
|
||||
|
||||
func lex(s string) ([]token, error) {
|
||||
var toks []token
|
||||
for i := 0; i < len(s); {
|
||||
c := s[i]
|
||||
switch {
|
||||
case c == ' ' || c == '\t' || c == '\n' || c == '\r':
|
||||
i++
|
||||
case c == '(':
|
||||
toks = append(toks, token{tLParen, "(", i})
|
||||
i++
|
||||
case c == ')':
|
||||
toks = append(toks, token{tRParen, ")", i})
|
||||
i++
|
||||
case c == ',':
|
||||
toks = append(toks, token{tComma, ",", i})
|
||||
i++
|
||||
case c == '!':
|
||||
toks = append(toks, token{tNot, "!", i})
|
||||
i++
|
||||
case strings.HasPrefix(s[i:], "&&"):
|
||||
toks = append(toks, token{tAnd, "&&", i})
|
||||
i += 2
|
||||
case strings.HasPrefix(s[i:], "||"):
|
||||
toks = append(toks, token{tOr, "||", i})
|
||||
i += 2
|
||||
case c == '`':
|
||||
end := strings.IndexByte(s[i+1:], '`')
|
||||
if end < 0 {
|
||||
return nil, fmt.Errorf("%w: nicht geschlossener String ab Position %d", ErrSyntax, i)
|
||||
}
|
||||
toks = append(toks, token{tString, s[i+1 : i+1+end], i})
|
||||
i += end + 2
|
||||
case c == '"':
|
||||
var b strings.Builder
|
||||
j := i + 1
|
||||
for ; j < len(s) && s[j] != '"'; j++ {
|
||||
if s[j] == '\\' && j+1 < len(s) {
|
||||
j++
|
||||
}
|
||||
b.WriteByte(s[j])
|
||||
}
|
||||
if j >= len(s) {
|
||||
return nil, fmt.Errorf("%w: nicht geschlossener String ab Position %d", ErrSyntax, i)
|
||||
}
|
||||
toks = append(toks, token{tString, b.String(), i})
|
||||
i = j + 1
|
||||
case isIdentStart(c):
|
||||
j := i
|
||||
for j < len(s) && (isIdentStart(s[j]) || (s[j] >= '0' && s[j] <= '9')) {
|
||||
j++
|
||||
}
|
||||
toks = append(toks, token{tIdent, s[i:j], i})
|
||||
i = j
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: unerwartetes Zeichen %q an Position %d", ErrSyntax, c, i)
|
||||
}
|
||||
}
|
||||
return append(toks, token{tEOF, "Ende", len(s)}), nil
|
||||
}
|
||||
|
||||
func isIdentStart(c byte) bool {
|
||||
return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '_'
|
||||
}
|
||||
|
||||
// --- Parser ---
|
||||
|
||||
type opKind int
|
||||
|
||||
const (
|
||||
opCall opKind = iota
|
||||
opNot
|
||||
opAnd
|
||||
opOr
|
||||
)
|
||||
|
||||
type node struct {
|
||||
op opKind
|
||||
kids []*node
|
||||
name string
|
||||
args []string
|
||||
}
|
||||
|
||||
type parser struct {
|
||||
toks []token
|
||||
pos int
|
||||
}
|
||||
|
||||
func (p *parser) peek() token { return p.toks[p.pos] }
|
||||
|
||||
func (p *parser) next() token {
|
||||
t := p.toks[p.pos]
|
||||
if t.kind != tEOF {
|
||||
p.pos++
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (p *parser) errorf(format string, args ...any) error {
|
||||
return fmt.Errorf("%w: %s an Position %d", ErrSyntax, fmt.Sprintf(format, args...), p.peek().pos)
|
||||
}
|
||||
|
||||
func (p *parser) expect(k tokenKind, what string) error {
|
||||
if p.peek().kind != k {
|
||||
return p.errorf("erwartet %s, gefunden %q", what, p.peek().text)
|
||||
}
|
||||
p.next()
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseOr: and ("||" and)*
|
||||
func (p *parser) parseOr() (*node, error) {
|
||||
left, err := p.parseAnd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for p.peek().kind == tOr {
|
||||
p.next()
|
||||
right, err := p.parseAnd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
left = &node{op: opOr, kids: []*node{left, right}}
|
||||
}
|
||||
return left, nil
|
||||
}
|
||||
|
||||
// parseAnd: unary ("&&" unary)*
|
||||
func (p *parser) parseAnd() (*node, error) {
|
||||
left, err := p.parseUnary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for p.peek().kind == tAnd {
|
||||
p.next()
|
||||
right, err := p.parseUnary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
left = &node{op: opAnd, kids: []*node{left, right}}
|
||||
}
|
||||
return left, nil
|
||||
}
|
||||
|
||||
// parseUnary: "!" unary | "(" or ")" | ident "(" string ("," string)* ")"
|
||||
func (p *parser) parseUnary() (*node, error) {
|
||||
switch p.peek().kind {
|
||||
case tNot:
|
||||
p.next()
|
||||
kid, err := p.parseUnary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &node{op: opNot, kids: []*node{kid}}, nil
|
||||
case tLParen:
|
||||
p.next()
|
||||
inner, err := p.parseOr()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := p.expect(tRParen, "')'"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inner, nil
|
||||
case tIdent:
|
||||
name := p.next().text
|
||||
if err := p.expect(tLParen, "'('"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var args []string
|
||||
for {
|
||||
if p.peek().kind != tString {
|
||||
return nil, p.errorf("erwartet String-Argument, gefunden %q", p.peek().text)
|
||||
}
|
||||
args = append(args, p.next().text)
|
||||
if p.peek().kind != tComma {
|
||||
break
|
||||
}
|
||||
p.next()
|
||||
}
|
||||
if err := p.expect(tRParen, "')'"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &node{op: opCall, name: name, args: args}, nil
|
||||
default:
|
||||
return nil, p.errorf("erwartet Matcher, '(' oder '!', gefunden %q", p.peek().text)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user