diff options
Diffstat (limited to 'core/parse/parsing.go')
-rw-r--r-- | core/parse/parsing.go | 388 |
1 files changed, 0 insertions, 388 deletions
diff --git a/core/parse/parsing.go b/core/parse/parsing.go deleted file mode 100644 index 7be7d6714..000000000 --- a/core/parse/parsing.go +++ /dev/null @@ -1,388 +0,0 @@ -package parse - -import ( - "fmt" - "net" - "os" - "path/filepath" - "strings" - - "github.com/miekg/dns" -) - -type parser struct { - Dispenser - block ServerBlock // current server block being parsed - eof bool // if we encounter a valid EOF in a hard place - checkDirectives bool // if true, directives must be known -} - -func (p *parser) parseAll() ([]ServerBlock, error) { - var blocks []ServerBlock - - for p.Next() { - err := p.parseOne() - if err != nil { - return blocks, err - } - if len(p.block.Addresses) > 0 { - blocks = append(blocks, p.block) - } - } - - return blocks, nil -} - -func (p *parser) parseOne() error { - p.block = ServerBlock{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 Corefile 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 := replaceEnvVars(p.Val()) - - // special case: import directive replaces tokens during parse-time - if tkn == "import" && p.isNewLine() { - err := p.doImport() - if err != nil { - return err - } - continue - } - - // 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 - } - - if tkn != "" { // empty token possible if user typed "" in Corefile - // 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 - addr, err := standardAddress(tkn) - if err != nil { - return err - } - p.block.Addresses = append(p.block.Addresses, addr) - } - - // Advance token and possibly break out of loop or return error - hasNext := p.Next() - if expectingAnother && !hasNext { - return p.EOFErr() - } - if !hasNext { - p.eof = true - break // EOF - } - if !expectingAnother && p.isNewLine() { - break - } - } - - 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 - } - p.cursor-- // cursor is advanced when we continue, so roll back one more - 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 specified file -// or globbing pattern. 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 { - // syntax check - if !p.NextArg() { - return p.ArgErr() - } - importPattern := p.Val() - if p.NextArg() { - return p.Err("Import takes only one argument (glob pattern or file)") - } - - // do glob - matches, err := filepath.Glob(importPattern) - if err != nil { - return p.Errf("Failed to use import pattern %s: %v", importPattern, err) - } - if len(matches) == 0 { - return p.Errf("No files matching import pattern %s", importPattern) - } - - // splice out the import directive and its argument (2 tokens total) - tokensBefore := p.tokens[:p.cursor-1] - tokensAfter := p.tokens[p.cursor+1:] - - // collect all the imported tokens - var importedTokens []token - for _, importFile := range matches { - newTokens, err := p.doSingleImport(importFile) - if err != nil { - return err - } - importedTokens = append(importedTokens, newTokens...) - } - - // splice the imported tokens in the place of the import statement - // and rewind cursor so Next() will land on first imported token - p.tokens = append(tokensBefore, append(importedTokens, tokensAfter...)...) - p.cursor-- - - return nil -} - -// doSingleImport lexes the individual file at importFile and returns -// its tokens or an error, if any. -func (p *parser) doSingleImport(importFile string) ([]token, error) { - file, err := os.Open(importFile) - if err != nil { - return nil, p.Errf("Could not import %s: %v", importFile, err) - } - defer file.Close() - importedTokens := allTokens(file) - - // Tack the filename onto these tokens so errors show the imported file's name - filename := filepath.Base(importFile) - for i := 0; i < len(importedTokens); i++ { - importedTokens[i].file = filename - } - - return importedTokens, 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() - nesting := 0 - - if p.checkDirectives { - 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.isNewLine() && 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.tokens[p.cursor].text = replaceEnvVars(p.tokens[p.cursor].text) - 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 parses an address string into a structured format with separate -// host, and port portions, as well as the original input string. -func standardAddress(str string) (address, error) { - var err error - - // first check for scheme and strip it off - input := str - - // separate host and port - host, port, err := net.SplitHostPort(str) - if err != nil { - host, port, err = net.SplitHostPort(str + ":") - // no error check here; return err at end of function - } - - if len(host) > 255 { - return address{}, fmt.Errorf("specified address is too long: %d > 255", len(host)) - } - _, d := dns.IsDomainName(host) - if !d { - return address{}, fmt.Errorf("host is not a valid domain: %s", host) - } - - // see if we can set port based off scheme - if port == "" { - port = "53" - } - - return address{Original: input, Host: strings.ToLower(dns.Fqdn(host)), Port: port}, err -} - -// replaceEnvVars replaces environment variables that appear in the token -// and understands both the $UNIX and %WINDOWS% syntaxes. -func replaceEnvVars(s string) string { - s = replaceEnvReferences(s, "{%", "%}") - s = replaceEnvReferences(s, "{$", "}") - return s -} - -// replaceEnvReferences performs the actual replacement of env variables -// in s, given the placeholder start and placeholder end strings. -func replaceEnvReferences(s, refStart, refEnd string) string { - index := strings.Index(s, refStart) - for index != -1 { - endIndex := strings.Index(s, refEnd) - if endIndex != -1 { - ref := s[index : endIndex+len(refEnd)] - s = strings.Replace(s, ref, os.Getenv(ref[len(refStart):len(ref)-len(refEnd)]), -1) - } else { - return s - } - index = strings.Index(s, refStart) - } - return s -} - -type ( - // ServerBlock associates tokens with a list of addresses - // and groups tokens by directive name. - ServerBlock struct { - Addresses []address - Tokens map[string][]token - } - - address struct { - Original, Host, Port string - } -) - -// HostList converts the list of addresses that are -// associated with this server block into a slice of -// strings, where each address is as it was originally -// read from the input. -func (sb ServerBlock) HostList() []string { - sbHosts := make([]string, len(sb.Addresses)) - for j, addr := range sb.Addresses { - sbHosts[j] = addr.Original - } - return sbHosts -} |