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 }