mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 10:37:24 -04: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
 | |
| }
 |