mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-23 22:27:38 -05:00
208 lines
5.8 KiB
Go
208 lines
5.8 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/mholt/caddy/middleware"
|
|
)
|
|
|
|
type (
|
|
// parser is a type which can parse config files.
|
|
parser struct {
|
|
filename string // the name of the file that we're parsing
|
|
lexer lexer // the lexer that is giving us tokens from the raw input
|
|
hosts []hostPort // the list of host:port combinations current tokens apply to
|
|
cfg Config // each virtual host gets one Config; this is the one we're currently building
|
|
cfgs []Config // after a Config is created, it may need to be copied for multiple hosts
|
|
other []locationContext // tokens to be 'parsed' later by middleware generators
|
|
scope *locationContext // the current location context (path scope) being populated
|
|
unused *token // sometimes a token will be read but not immediately consumed
|
|
eof bool // if we encounter a valid EOF in a hard place
|
|
}
|
|
|
|
// locationContext represents a location context
|
|
// (path block) in a config file. If no context
|
|
// is explicitly defined, the default location
|
|
// context is "/".
|
|
locationContext struct {
|
|
path string
|
|
directives map[string]*controller
|
|
}
|
|
|
|
// hostPort just keeps a hostname and port together
|
|
hostPort struct {
|
|
host, port string
|
|
}
|
|
)
|
|
|
|
// newParser makes a new parser and prepares it for parsing, given
|
|
// the input to parse.
|
|
func newParser(file *os.File) (*parser, error) {
|
|
stat, err := file.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p := &parser{filename: stat.Name()}
|
|
p.lexer.load(file)
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// Parse parses the configuration file. It produces a slice of Config
|
|
// structs which can be used to create and configure server instances.
|
|
func (p *parser) parse() ([]Config, error) {
|
|
var configs []Config
|
|
|
|
for p.lexer.next() {
|
|
err := p.parseOne()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
configs = append(configs, p.cfgs...)
|
|
}
|
|
|
|
return configs, nil
|
|
}
|
|
|
|
// nextArg loads the next token if it is on the same line.
|
|
// Returns true if a token was loaded; false otherwise.
|
|
func (p *parser) nextArg() bool {
|
|
if p.unused != nil {
|
|
return false
|
|
}
|
|
line, tkn := p.line(), p.lexer.token
|
|
if p.next() {
|
|
if p.line() > line {
|
|
p.unused = &tkn
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// next loads the next token and returns true if a token
|
|
// was loaded; false otherwise.
|
|
func (p *parser) next() bool {
|
|
if p.unused != nil {
|
|
p.unused = nil
|
|
return true
|
|
} else {
|
|
return p.lexer.next()
|
|
}
|
|
}
|
|
|
|
// parseOne parses the contents of a configuration
|
|
// file for a single Config object (each server or
|
|
// virtualhost instance gets their own Config struct),
|
|
// which is until the next address/server block.
|
|
// Call this only when you know that the lexer has another
|
|
// another token and you're not in another server
|
|
// block already.
|
|
func (p *parser) parseOne() error {
|
|
p.cfgs = []Config{}
|
|
p.cfg = Config{
|
|
Middleware: make(map[string][]middleware.Middleware),
|
|
}
|
|
p.other = []locationContext{}
|
|
|
|
err := p.begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = p.unwrap()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Make a copy of the config for each
|
|
// address that will be using it
|
|
for _, hostport := range p.hosts {
|
|
cfgCopy := p.cfg
|
|
cfgCopy.Host = hostport.host
|
|
cfgCopy.Port = hostport.port
|
|
p.cfgs = append(p.cfgs, cfgCopy)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// unwrap gets the middleware generators from the middleware
|
|
// package in the order in which they are registered, and
|
|
// executes the top-level functions (the generator function)
|
|
// to expose the second layers which are the actual middleware.
|
|
// This function should be called only after p has filled out
|
|
// p.other and the entire server block has already been consumed.
|
|
func (p *parser) unwrap() error {
|
|
if len(p.other) == 0 {
|
|
// no middlewares were invoked
|
|
return nil
|
|
}
|
|
|
|
for _, directive := range registry.ordered {
|
|
// TODO: For now, we only support the first and default path scope ("/", held in p.other[0])
|
|
// but when we implement support for path scopes, we will have to change this logic
|
|
// to loop over them and order them. We need to account for situations where multiple
|
|
// path scopes overlap, regex (??), etc...
|
|
if disp, ok := p.other[0].directives[directive]; ok {
|
|
if generator, ok := registry.directiveMap[directive]; ok {
|
|
mid, err := generator(disp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if mid != nil {
|
|
// TODO: Again, we assume the default path scope here...
|
|
p.cfg.Middleware[p.other[0].path] = append(p.cfg.Middleware[p.other[0].path], mid)
|
|
}
|
|
} else {
|
|
return errors.New("No middleware bound to directive '" + directive + "'")
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// tkn is shorthand to get the text/value of the current token.
|
|
func (p *parser) tkn() string {
|
|
if p.unused != nil {
|
|
return p.unused.text
|
|
}
|
|
return p.lexer.token.text
|
|
}
|
|
|
|
// line is shorthand to get the line number of the current token.
|
|
func (p *parser) line() int {
|
|
if p.unused != nil {
|
|
return p.unused.line
|
|
}
|
|
return p.lexer.token.line
|
|
}
|
|
|
|
// syntaxErr creates a syntax error which explains what was
|
|
// found and expected.
|
|
func (p *parser) syntaxErr(expected string) error {
|
|
return p.err("Syntax", fmt.Sprintf("Unexpected token '%s', expecting '%s'", p.tkn(), expected))
|
|
}
|
|
|
|
// syntaxErr creates a syntax error that explains that there
|
|
// weren't enough arguments on the line.
|
|
func (p *parser) argErr() error {
|
|
return p.err("Syntax", "Unexpected line break after '"+p.tkn()+"' (missing arguments?)")
|
|
}
|
|
|
|
// eofErr creates a syntax error describing an unexpected EOF.
|
|
func (p *parser) eofErr() error {
|
|
return p.err("Syntax", "Unexpected EOF")
|
|
}
|
|
|
|
// err creates an error with a custom message msg: "{{kind}} error: {{msg}}". The
|
|
// file name and line number are included in the error message.
|
|
func (p *parser) err(kind, msg string) error {
|
|
msg = fmt.Sprintf("%s:%d - %s error: %s", p.filename, p.line(), kind, msg)
|
|
return errors.New(msg)
|
|
}
|