mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-03 19:17:29 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			311 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package parse
 | 
						|
 | 
						|
import (
 | 
						|
	"net"
 | 
						|
	"os"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
type parser struct {
 | 
						|
	Dispenser
 | 
						|
	block multiServerBlock // current server block being parsed
 | 
						|
	eof   bool             // if we encounter a valid EOF in a hard place
 | 
						|
}
 | 
						|
 | 
						|
func (p *parser) parseAll() ([]serverBlock, error) {
 | 
						|
	var blocks []serverBlock
 | 
						|
 | 
						|
	for p.Next() {
 | 
						|
		err := p.parseOne()
 | 
						|
		if err != nil {
 | 
						|
			return blocks, err
 | 
						|
		}
 | 
						|
 | 
						|
		// explode the multiServerBlock into multiple serverBlocks
 | 
						|
		for _, addr := range p.block.addresses {
 | 
						|
			blocks = append(blocks, serverBlock{
 | 
						|
				Host:   addr.host,
 | 
						|
				Port:   addr.port,
 | 
						|
				Tokens: p.block.tokens,
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return blocks, nil
 | 
						|
}
 | 
						|
 | 
						|
func (p *parser) parseOne() error {
 | 
						|
	p.block = multiServerBlock{tokens: make(map[string][]token)}
 | 
						|
 | 
						|
	err := p.begin()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (p *parser) begin() error {
 | 
						|
	if len(p.tokens) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	err := p.addresses()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if p.eof {
 | 
						|
		// this happens if the Caddyfile consists of only
 | 
						|
		// a line of addresses and nothing else
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	err = p.blockContents()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (p *parser) addresses() error {
 | 
						|
	var expectingAnother bool
 | 
						|
 | 
						|
	for {
 | 
						|
		tkn, startLine := p.Val(), p.Line()
 | 
						|
 | 
						|
		// Open brace definitely indicates end of addresses
 | 
						|
		if tkn == "{" {
 | 
						|
			if expectingAnother {
 | 
						|
				return p.Errf("Expected another address but had '%s' - check for extra comma", tkn)
 | 
						|
			}
 | 
						|
			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 := standardAddress(tkn)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		p.block.addresses = append(p.block.addresses, address{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
 | 
						|
}
 | 
						|
 | 
						|
func (p *parser) blockContents() error {
 | 
						|
	errOpenCurlyBrace := p.openCurlyBrace()
 | 
						|
	if errOpenCurlyBrace != nil {
 | 
						|
		// single-server configs don't need curly braces
 | 
						|
		p.cursor--
 | 
						|
	}
 | 
						|
 | 
						|
	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
 | 
						|
}
 | 
						|
 | 
						|
// directives parses through all the lines for directives
 | 
						|
// and it expects the next token to be the first
 | 
						|
// directive. It goes until EOF or closing curly brace
 | 
						|
// which ends the server block.
 | 
						|
func (p *parser) directives() error {
 | 
						|
	for p.Next() {
 | 
						|
		// end of server block
 | 
						|
		if p.Val() == "}" {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		// special case: import directive replaces tokens during parse-time
 | 
						|
		if p.Val() == "import" {
 | 
						|
			err := p.doImport()
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// normal case: parse a directive on this line
 | 
						|
		if err := p.directive(); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// doImport swaps out the import directive and its argument
 | 
						|
// (a total of 2 tokens) with the tokens in the file specified.
 | 
						|
// When the function returns, the cursor is on the token before
 | 
						|
// where the import directive was. In other words, call Next()
 | 
						|
// to access the first token that was imported.
 | 
						|
func (p *parser) doImport() error {
 | 
						|
	if !p.NextArg() {
 | 
						|
		return p.ArgErr()
 | 
						|
	}
 | 
						|
	importFile := p.Val()
 | 
						|
	if p.NextArg() {
 | 
						|
		return p.Err("Import allows only one file to import")
 | 
						|
	}
 | 
						|
 | 
						|
	file, err := os.Open(importFile)
 | 
						|
	if err != nil {
 | 
						|
		return p.Errf("Could not import %s - %v", importFile, err)
 | 
						|
	}
 | 
						|
	defer file.Close()
 | 
						|
	importedTokens := allTokens(file)
 | 
						|
 | 
						|
	// Splice out the import directive and its argument (2 tokens total)
 | 
						|
	// and insert the imported tokens.
 | 
						|
	tokensBefore := p.tokens[:p.cursor-1]
 | 
						|
	tokensAfter := p.tokens[p.cursor+1:]
 | 
						|
	p.tokens = append(tokensBefore, append(importedTokens, tokensAfter...)...)
 | 
						|
	p.cursor -= 2
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// directive collects tokens until the directive's scope
 | 
						|
// closes (either end of line or end of curly brace block).
 | 
						|
// It expects the currently-loaded token to be a directive
 | 
						|
// (or } that ends a server block). The collected tokens
 | 
						|
// are loaded into the current server block for later use
 | 
						|
// by directive setup functions.
 | 
						|
func (p *parser) directive() error {
 | 
						|
	dir := p.Val()
 | 
						|
	line := p.Line()
 | 
						|
	nesting := 0
 | 
						|
 | 
						|
	if _, ok := ValidDirectives[dir]; !ok {
 | 
						|
		return p.Errf("Unknown directive '%s'", dir)
 | 
						|
	}
 | 
						|
 | 
						|
	// The directive itself is appended as a relevant token
 | 
						|
	p.block.tokens[dir] = append(p.block.tokens[dir], p.tokens[p.cursor])
 | 
						|
 | 
						|
	for p.Next() {
 | 
						|
		if p.Val() == "{" {
 | 
						|
			nesting++
 | 
						|
		} else if p.Line()+p.numLineBreaks(p.cursor) > line && nesting == 0 {
 | 
						|
			p.cursor-- // read too far
 | 
						|
			break
 | 
						|
		} else if p.Val() == "}" && nesting > 0 {
 | 
						|
			nesting--
 | 
						|
		} else if p.Val() == "}" && nesting == 0 {
 | 
						|
			return p.Err("Unexpected '}' because no matching opening brace")
 | 
						|
		}
 | 
						|
		p.block.tokens[dir] = append(p.block.tokens[dir], p.tokens[p.cursor])
 | 
						|
	}
 | 
						|
 | 
						|
	if nesting > 0 {
 | 
						|
		return p.EofErr()
 | 
						|
	}
 | 
						|
	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.Val() != "{" {
 | 
						|
		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.Val() != "}" {
 | 
						|
		return p.SyntaxErr("}")
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// standardAddress turns the accepted host and port patterns
 | 
						|
// into a format accepted by net.Dial.
 | 
						|
func standardAddress(str string) (host, port string, err error) {
 | 
						|
	var schemePort, splitPort string
 | 
						|
 | 
						|
	if strings.HasPrefix(str, "https://") {
 | 
						|
		schemePort = "https"
 | 
						|
		str = str[8:]
 | 
						|
	} else if strings.HasPrefix(str, "http://") {
 | 
						|
		schemePort = "http"
 | 
						|
		str = str[7:]
 | 
						|
	}
 | 
						|
 | 
						|
	host, splitPort, err = net.SplitHostPort(str)
 | 
						|
	if err != nil {
 | 
						|
		host, splitPort, err = net.SplitHostPort(str + ":") // tack on empty port
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		// ¯\_(ツ)_/¯
 | 
						|
		host = str
 | 
						|
	}
 | 
						|
 | 
						|
	if splitPort != "" {
 | 
						|
		port = splitPort
 | 
						|
	} else {
 | 
						|
		port = schemePort
 | 
						|
	}
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
type (
 | 
						|
	// serverBlock stores tokens by directive name for a
 | 
						|
	// single host:port (address)
 | 
						|
	serverBlock struct {
 | 
						|
		Host, Port string
 | 
						|
		Tokens     map[string][]token // directive name to tokens (including directive)
 | 
						|
	}
 | 
						|
 | 
						|
	// multiServerBlock is the same as serverBlock but for
 | 
						|
	// multiple addresses that share the same tokens
 | 
						|
	multiServerBlock struct {
 | 
						|
		addresses []address
 | 
						|
		tokens    map[string][]token
 | 
						|
	}
 | 
						|
 | 
						|
	address struct {
 | 
						|
		host, port string
 | 
						|
	}
 | 
						|
)
 |