mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-28 03:33:45 +08:00
319 lines
7.9 KiB
Go
319 lines
7.9 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"net"
|
|
"strings"
|
|
)
|
|
|
|
// This file contains the recursive-descent parsing
|
|
// functions.
|
|
|
|
// begin is the top of the recursive-descent parsing.
|
|
// It parses at most one server configuration (an address
|
|
// and its directives).
|
|
func (p *parser) begin() error {
|
|
err := p.addresses()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = p.addressBlock()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// addresses expects that the current token is a
|
|
// "scheme://host:port" combination (the "scheme://"
|
|
// and/or ":port" portions may be omitted). If multiple
|
|
// addresses are specified, they must be space-
|
|
// separated on the same line, or each token must end
|
|
// with a comma.
|
|
func (p *parser) addresses() error {
|
|
var expectingAnother bool
|
|
p.hosts = []hostPort{}
|
|
|
|
// address gets host and port in a format accepted by net.Dial
|
|
address := func(str string) (host, port string, err error) {
|
|
var schemePort string
|
|
|
|
if strings.HasPrefix(str, "https://") {
|
|
schemePort = "https"
|
|
str = str[8:]
|
|
} else if strings.HasPrefix(str, "http://") {
|
|
schemePort = "http"
|
|
str = str[7:]
|
|
} else if !strings.Contains(str, ":") {
|
|
str += ":" + defaultPort
|
|
}
|
|
|
|
host, port, err = net.SplitHostPort(str)
|
|
if err != nil && schemePort != "" {
|
|
host = str
|
|
port = schemePort // assume port from scheme
|
|
err = nil
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
for {
|
|
tkn, startLine := p.tkn(), p.line()
|
|
|
|
// Open brace definitely indicates end of addresses
|
|
if tkn == "{" {
|
|
if expectingAnother {
|
|
return p.err("Syntax", "Expected another address but had '"+tkn+"' - check for extra comma")
|
|
}
|
|
break
|
|
}
|
|
|
|
// Trailing comma indicates another address will follow, which
|
|
// may possibly be on the next line
|
|
if tkn[len(tkn)-1] == ',' {
|
|
tkn = tkn[:len(tkn)-1]
|
|
expectingAnother = true
|
|
} else {
|
|
expectingAnother = false // but we may still see another one on this line
|
|
}
|
|
|
|
// Parse and save this address
|
|
host, port, err := address(tkn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.hosts = append(p.hosts, hostPort{host, port})
|
|
|
|
// Advance token and possibly break out of loop or return error
|
|
hasNext := p.next()
|
|
if expectingAnother && !hasNext {
|
|
return p.eofErr()
|
|
}
|
|
if !expectingAnother && p.line() > startLine {
|
|
break
|
|
}
|
|
if !hasNext {
|
|
p.eof = true
|
|
break // EOF
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// addressBlock leads into parsing directives, including
|
|
// possible opening/closing curly braces around the block.
|
|
// It handles directives enclosed by curly braces and
|
|
// directives not enclosed by curly braces. It is expected
|
|
// that the current token is already the beginning of
|
|
// the address block.
|
|
func (p *parser) addressBlock() error {
|
|
errOpenCurlyBrace := p.openCurlyBrace()
|
|
if errOpenCurlyBrace != nil {
|
|
// meh, single-server configs don't need curly braces
|
|
// but we read a token and we won't consume it; mark it unused
|
|
p.unused = &p.lexer.token
|
|
}
|
|
|
|
// When we enter an address block, we also implicitly
|
|
// enter a path block where the path is all paths ("/")
|
|
p.other = append(p.other, locationContext{
|
|
path: "/",
|
|
directives: make(map[string]*controller),
|
|
})
|
|
p.scope = &p.other[0]
|
|
|
|
if p.eof {
|
|
// this happens if the Caddyfile consists of only
|
|
// a line of addresses and nothing else
|
|
return nil
|
|
}
|
|
|
|
err := p.directives()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Only look for close curly brace if there was an opening
|
|
if errOpenCurlyBrace == nil {
|
|
err = p.closeCurlyBrace()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// openCurlyBrace expects the current token to be an
|
|
// opening curly brace. This acts like an assertion
|
|
// because it returns an error if the token is not
|
|
// a opening curly brace. It does not advance the token.
|
|
func (p *parser) openCurlyBrace() error {
|
|
if p.tkn() != "{" {
|
|
return p.syntaxErr("{")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// closeCurlyBrace expects the current token to be
|
|
// a closing curly brace. This acts like an assertion
|
|
// because it returns an error if the token is not
|
|
// a closing curly brace. It does not advance the token.
|
|
func (p *parser) closeCurlyBrace() error {
|
|
if p.tkn() != "}" {
|
|
return p.syntaxErr("}")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// directives parses through all the directives
|
|
// and it expects the current token to be the first
|
|
// directive. It goes until EOF or closing curly
|
|
// brace which ends the address block.
|
|
func (p *parser) directives() error {
|
|
for p.next() {
|
|
if p.tkn() == "}" {
|
|
// end of address scope
|
|
break
|
|
}
|
|
if p.tkn()[0] == '/' || p.tkn()[0] == '*' {
|
|
// Path scope (a.k.a. location context)
|
|
// Starts with / ('starts with') or * ('ends with').
|
|
|
|
// TODO: The parser can handle the syntax (obviously), but the
|
|
// implementation is incomplete. This is intentional,
|
|
// until we can better decide what kind of feature set we
|
|
// want to support and how exactly we want these location
|
|
// scopes to work. Until this is ready, we leave this
|
|
// syntax undocumented. Some changes will need to be
|
|
// made in parser.go also (the unwrap function) and
|
|
// probably in server.go when we do this... see those TODOs.
|
|
|
|
var scope *locationContext
|
|
|
|
// If the path block is a duplicate, append to existing one
|
|
for i := 0; i < len(p.other); i++ {
|
|
if p.other[i].path == p.tkn() {
|
|
scope = &p.other[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
// Otherwise, for a new path we haven't seen before, create a new context
|
|
if scope == nil {
|
|
scope = &locationContext{
|
|
path: p.tkn(),
|
|
directives: make(map[string]*controller),
|
|
}
|
|
}
|
|
|
|
// Consume the opening curly brace
|
|
if !p.next() {
|
|
return p.eofErr()
|
|
}
|
|
err := p.openCurlyBrace()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Use this path scope as our current context for just a moment
|
|
p.scope = scope
|
|
|
|
// Consume each directive in the path block
|
|
for p.next() {
|
|
err := p.closeCurlyBrace()
|
|
if err == nil {
|
|
break
|
|
}
|
|
|
|
err = p.directive()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Save the new scope and put the current scope back to "/"
|
|
p.other = append(p.other, *scope)
|
|
p.scope = &p.other[0]
|
|
|
|
} else if err := p.directive(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// directive asserts that the current token is either a built-in
|
|
// directive or a registered middleware directive; otherwise an error
|
|
// will be returned. If it is a valid directive, tokens will be
|
|
// collected.
|
|
func (p *parser) directive() error {
|
|
if fn, ok := validDirectives[p.tkn()]; ok {
|
|
// Built-in (standard, or 'core') directive
|
|
err := fn(p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if middlewareRegistered(p.tkn()) {
|
|
// Middleware directive
|
|
err := p.collectTokens()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
return p.err("Syntax", "Unexpected token '"+p.tkn()+"', expecting a valid directive")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// collectTokens consumes tokens until the directive's scope
|
|
// closes (either end of line or end of curly brace block).
|
|
// It creates a controller which is stored in the parser for
|
|
// later use by the middleware.
|
|
func (p *parser) collectTokens() error {
|
|
if p.scope == nil {
|
|
return errors.New("Current scope cannot be nil")
|
|
}
|
|
|
|
directive := p.tkn()
|
|
line := p.line()
|
|
nesting := 0
|
|
cont := newController(p)
|
|
|
|
// Re-use a duplicate directive's controller from before
|
|
// (the parsing logic in the middleware generator must
|
|
// account for multiple occurrences of its directive, even
|
|
// if that means returning an error or overwriting settings)
|
|
if existing, ok := p.scope.directives[directive]; ok {
|
|
cont = existing
|
|
}
|
|
|
|
// The directive is appended as a relevant token
|
|
cont.tokens = append(cont.tokens, p.lexer.token)
|
|
|
|
for p.next() {
|
|
if p.tkn() == "{" {
|
|
nesting++
|
|
} else if p.line() > line && nesting == 0 {
|
|
p.unused = &p.lexer.token
|
|
break
|
|
} else if p.tkn() == "}" && nesting > 0 {
|
|
nesting--
|
|
} else if p.tkn() == "}" && nesting == 0 {
|
|
return p.err("Syntax", "Unexpected '}' because no matching opening brace")
|
|
}
|
|
cont.tokens = append(cont.tokens, p.lexer.token)
|
|
}
|
|
|
|
if nesting > 0 {
|
|
return p.eofErr()
|
|
}
|
|
|
|
p.scope.directives[directive] = cont
|
|
return nil
|
|
}
|