mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 16:12:24 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			1405 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1405 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package dns
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/fs"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| const maxTok = 512 // Token buffer start size, and growth size amount.
 | |
| 
 | |
| // The maximum depth of $INCLUDE directives supported by the
 | |
| // ZoneParser API.
 | |
| const maxIncludeDepth = 7
 | |
| 
 | |
| // Tokenize a RFC 1035 zone file. The tokenizer will normalize it:
 | |
| // * Add ownernames if they are left blank;
 | |
| // * Suppress sequences of spaces;
 | |
| // * Make each RR fit on one line (_NEWLINE is send as last)
 | |
| // * Handle comments: ;
 | |
| // * Handle braces - anywhere.
 | |
| const (
 | |
| 	// Zonefile
 | |
| 	zEOF = iota
 | |
| 	zString
 | |
| 	zBlank
 | |
| 	zQuote
 | |
| 	zNewline
 | |
| 	zRrtpe
 | |
| 	zOwner
 | |
| 	zClass
 | |
| 	zDirOrigin   // $ORIGIN
 | |
| 	zDirTTL      // $TTL
 | |
| 	zDirInclude  // $INCLUDE
 | |
| 	zDirGenerate // $GENERATE
 | |
| 
 | |
| 	// Privatekey file
 | |
| 	zValue
 | |
| 	zKey
 | |
| 
 | |
| 	zExpectOwnerDir      // Ownername
 | |
| 	zExpectOwnerBl       // Whitespace after the ownername
 | |
| 	zExpectAny           // Expect rrtype, ttl or class
 | |
| 	zExpectAnyNoClass    // Expect rrtype or ttl
 | |
| 	zExpectAnyNoClassBl  // The whitespace after _EXPECT_ANY_NOCLASS
 | |
| 	zExpectAnyNoTTL      // Expect rrtype or class
 | |
| 	zExpectAnyNoTTLBl    // Whitespace after _EXPECT_ANY_NOTTL
 | |
| 	zExpectRrtype        // Expect rrtype
 | |
| 	zExpectRrtypeBl      // Whitespace BEFORE rrtype
 | |
| 	zExpectRdata         // The first element of the rdata
 | |
| 	zExpectDirTTLBl      // Space after directive $TTL
 | |
| 	zExpectDirTTL        // Directive $TTL
 | |
| 	zExpectDirOriginBl   // Space after directive $ORIGIN
 | |
| 	zExpectDirOrigin     // Directive $ORIGIN
 | |
| 	zExpectDirIncludeBl  // Space after directive $INCLUDE
 | |
| 	zExpectDirInclude    // Directive $INCLUDE
 | |
| 	zExpectDirGenerate   // Directive $GENERATE
 | |
| 	zExpectDirGenerateBl // Space after directive $GENERATE
 | |
| )
 | |
| 
 | |
| // ParseError is a parsing error. It contains the parse error and the location in the io.Reader
 | |
| // where the error occurred.
 | |
| type ParseError struct {
 | |
| 	file       string
 | |
| 	err        string
 | |
| 	wrappedErr error
 | |
| 	lex        lex
 | |
| }
 | |
| 
 | |
| func (e *ParseError) Error() (s string) {
 | |
| 	if e.file != "" {
 | |
| 		s = e.file + ": "
 | |
| 	}
 | |
| 	if e.err == "" && e.wrappedErr != nil {
 | |
| 		e.err = e.wrappedErr.Error()
 | |
| 	}
 | |
| 	s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " +
 | |
| 		strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (e *ParseError) Unwrap() error { return e.wrappedErr }
 | |
| 
 | |
| type lex struct {
 | |
| 	token  string // text of the token
 | |
| 	err    bool   // when true, token text has lexer error
 | |
| 	value  uint8  // value: zString, _BLANK, etc.
 | |
| 	torc   uint16 // type or class as parsed in the lexer, we only need to look this up in the grammar
 | |
| 	line   int    // line in the file
 | |
| 	column int    // column in the file
 | |
| }
 | |
| 
 | |
| // ttlState describes the state necessary to fill in an omitted RR TTL
 | |
| type ttlState struct {
 | |
| 	ttl           uint32 // ttl is the current default TTL
 | |
| 	isByDirective bool   // isByDirective indicates whether ttl was set by a $TTL directive
 | |
| }
 | |
| 
 | |
| // NewRR reads a string s and returns the first RR.
 | |
| // If s contains no records, NewRR will return nil with no error.
 | |
| //
 | |
| // The class defaults to IN, TTL defaults to 3600, and
 | |
| // origin for resolving relative domain names defaults to the DNS root (.).
 | |
| // Full zone file syntax is supported, including directives like $TTL and $ORIGIN.
 | |
| // All fields of the returned RR are set from the read data, except RR.Header().Rdlength which is set to 0.
 | |
| func NewRR(s string) (RR, error) {
 | |
| 	if len(s) > 0 && s[len(s)-1] != '\n' { // We need a closing newline
 | |
| 		return ReadRR(strings.NewReader(s+"\n"), "")
 | |
| 	}
 | |
| 	return ReadRR(strings.NewReader(s), "")
 | |
| }
 | |
| 
 | |
| // ReadRR reads the RR contained in r.
 | |
| //
 | |
| // The string file is used in error reporting and to resolve relative
 | |
| // $INCLUDE directives.
 | |
| //
 | |
| // See NewRR for more documentation.
 | |
| func ReadRR(r io.Reader, file string) (RR, error) {
 | |
| 	zp := NewZoneParser(r, ".", file)
 | |
| 	zp.SetDefaultTTL(defaultTtl)
 | |
| 	zp.SetIncludeAllowed(true)
 | |
| 	rr, _ := zp.Next()
 | |
| 	return rr, zp.Err()
 | |
| }
 | |
| 
 | |
| // ZoneParser is a parser for an RFC 1035 style zonefile.
 | |
| //
 | |
| // Each parsed RR in the zone is returned sequentially from Next. An
 | |
| // optional comment can be retrieved with Comment.
 | |
| //
 | |
| // The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are all
 | |
| // supported. Although $INCLUDE is disabled by default.
 | |
| // Note that $GENERATE's range support up to a maximum of 65535 steps.
 | |
| //
 | |
| // Basic usage pattern when reading from a string (z) containing the
 | |
| // zone data:
 | |
| //
 | |
| //	zp := NewZoneParser(strings.NewReader(z), "", "")
 | |
| //
 | |
| //	for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
 | |
| //		// Do something with rr
 | |
| //	}
 | |
| //
 | |
| //	if err := zp.Err(); err != nil {
 | |
| //		// log.Println(err)
 | |
| //	}
 | |
| //
 | |
| // Comments specified after an RR (and on the same line!) are
 | |
| // returned too:
 | |
| //
 | |
| //	foo. IN A 10.0.0.1 ; this is a comment
 | |
| //
 | |
| // The text "; this is comment" is returned from Comment. Comments inside
 | |
| // the RR are returned concatenated along with the RR. Comments on a line
 | |
| // by themselves are discarded.
 | |
| //
 | |
| // Callers should not assume all returned data in an Resource Record is
 | |
| // syntactically correct, e.g. illegal base64 in RRSIGs will be returned as-is.
 | |
| type ZoneParser struct {
 | |
| 	c *zlexer
 | |
| 
 | |
| 	parseErr *ParseError
 | |
| 
 | |
| 	origin string
 | |
| 	file   string
 | |
| 
 | |
| 	defttl *ttlState
 | |
| 
 | |
| 	h RR_Header
 | |
| 
 | |
| 	// sub is used to parse $INCLUDE files and $GENERATE directives.
 | |
| 	// Next, by calling subNext, forwards the resulting RRs from this
 | |
| 	// sub parser to the calling code.
 | |
| 	sub  *ZoneParser
 | |
| 	r    io.Reader
 | |
| 	fsys fs.FS
 | |
| 
 | |
| 	includeDepth uint8
 | |
| 
 | |
| 	includeAllowed     bool
 | |
| 	generateDisallowed bool
 | |
| }
 | |
| 
 | |
| // NewZoneParser returns an RFC 1035 style zonefile parser that reads
 | |
| // from r.
 | |
| //
 | |
| // The string file is used in error reporting and to resolve relative
 | |
| // $INCLUDE directives. The string origin is used as the initial
 | |
| // origin, as if the file would start with an $ORIGIN directive.
 | |
| func NewZoneParser(r io.Reader, origin, file string) *ZoneParser {
 | |
| 	var pe *ParseError
 | |
| 	if origin != "" {
 | |
| 		origin = Fqdn(origin)
 | |
| 		if _, ok := IsDomainName(origin); !ok {
 | |
| 			pe = &ParseError{file: file, err: "bad initial origin name"}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return &ZoneParser{
 | |
| 		c: newZLexer(r),
 | |
| 
 | |
| 		parseErr: pe,
 | |
| 
 | |
| 		origin: origin,
 | |
| 		file:   file,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // SetDefaultTTL sets the parsers default TTL to ttl.
 | |
| func (zp *ZoneParser) SetDefaultTTL(ttl uint32) {
 | |
| 	zp.defttl = &ttlState{ttl, false}
 | |
| }
 | |
| 
 | |
| // SetIncludeAllowed controls whether $INCLUDE directives are
 | |
| // allowed. $INCLUDE directives are not supported by default.
 | |
| //
 | |
| // The $INCLUDE directive will open and read from a user controlled
 | |
| // file on the system. Even if the file is not a valid zonefile, the
 | |
| // contents of the file may be revealed in error messages, such as:
 | |
| //
 | |
| //	/etc/passwd: dns: not a TTL: "root:x:0:0:root:/root:/bin/bash" at line: 1:31
 | |
| //	/etc/shadow: dns: not a TTL: "root:$6$<redacted>::0:99999:7:::" at line: 1:125
 | |
| func (zp *ZoneParser) SetIncludeAllowed(v bool) {
 | |
| 	zp.includeAllowed = v
 | |
| }
 | |
| 
 | |
| // SetIncludeFS provides an [fs.FS] to use when looking for the target of
 | |
| // $INCLUDE directives.  ($INCLUDE must still be enabled separately by calling
 | |
| // [ZoneParser.SetIncludeAllowed].)  If fsys is nil, [os.Open] will be used.
 | |
| //
 | |
| // When fsys is an on-disk FS, the ability of $INCLUDE to reach files from
 | |
| // outside its root directory depends upon the FS implementation.  For
 | |
| // instance, [os.DirFS] will refuse to open paths like "../../etc/passwd",
 | |
| // however it will still follow links which may point anywhere on the system.
 | |
| //
 | |
| // FS paths are slash-separated on all systems, even Windows.  $INCLUDE paths
 | |
| // containing other characters such as backslash and colon may be accepted as
 | |
| // valid, but those characters will never be interpreted by an FS
 | |
| // implementation as path element separators.  See [fs.ValidPath] for more
 | |
| // details.
 | |
| func (zp *ZoneParser) SetIncludeFS(fsys fs.FS) {
 | |
| 	zp.fsys = fsys
 | |
| }
 | |
| 
 | |
| // Err returns the first non-EOF error that was encountered by the
 | |
| // ZoneParser.
 | |
| func (zp *ZoneParser) Err() error {
 | |
| 	if zp.parseErr != nil {
 | |
| 		return zp.parseErr
 | |
| 	}
 | |
| 
 | |
| 	if zp.sub != nil {
 | |
| 		if err := zp.sub.Err(); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return zp.c.Err()
 | |
| }
 | |
| 
 | |
| func (zp *ZoneParser) setParseError(err string, l lex) (RR, bool) {
 | |
| 	zp.parseErr = &ParseError{file: zp.file, err: err, lex: l}
 | |
| 	return nil, false
 | |
| }
 | |
| 
 | |
| // Comment returns an optional text comment that occurred alongside
 | |
| // the RR.
 | |
| func (zp *ZoneParser) Comment() string {
 | |
| 	if zp.parseErr != nil {
 | |
| 		return ""
 | |
| 	}
 | |
| 
 | |
| 	if zp.sub != nil {
 | |
| 		return zp.sub.Comment()
 | |
| 	}
 | |
| 
 | |
| 	return zp.c.Comment()
 | |
| }
 | |
| 
 | |
| func (zp *ZoneParser) subNext() (RR, bool) {
 | |
| 	if rr, ok := zp.sub.Next(); ok {
 | |
| 		return rr, true
 | |
| 	}
 | |
| 
 | |
| 	if zp.sub.r != nil {
 | |
| 		if c, ok := zp.sub.r.(io.Closer); ok {
 | |
| 			c.Close()
 | |
| 		}
 | |
| 		zp.sub.r = nil
 | |
| 	}
 | |
| 
 | |
| 	if zp.sub.Err() != nil {
 | |
| 		// We have errors to surface.
 | |
| 		return nil, false
 | |
| 	}
 | |
| 
 | |
| 	zp.sub = nil
 | |
| 	return zp.Next()
 | |
| }
 | |
| 
 | |
| // Next advances the parser to the next RR in the zonefile and
 | |
| // returns the (RR, true). It will return (nil, false) when the
 | |
| // parsing stops, either by reaching the end of the input or an
 | |
| // error. After Next returns (nil, false), the Err method will return
 | |
| // any error that occurred during parsing.
 | |
| func (zp *ZoneParser) Next() (RR, bool) {
 | |
| 	if zp.parseErr != nil {
 | |
| 		return nil, false
 | |
| 	}
 | |
| 	if zp.sub != nil {
 | |
| 		return zp.subNext()
 | |
| 	}
 | |
| 
 | |
| 	// 6 possible beginnings of a line (_ is a space):
 | |
| 	//
 | |
| 	//   0. zRRTYPE                              -> all omitted until the rrtype
 | |
| 	//   1. zOwner _ zRrtype                     -> class/ttl omitted
 | |
| 	//   2. zOwner _ zString _ zRrtype           -> class omitted
 | |
| 	//   3. zOwner _ zString _ zClass  _ zRrtype -> ttl/class
 | |
| 	//   4. zOwner _ zClass  _ zRrtype           -> ttl omitted
 | |
| 	//   5. zOwner _ zClass  _ zString _ zRrtype -> class/ttl (reversed)
 | |
| 	//
 | |
| 	// After detecting these, we know the zRrtype so we can jump to functions
 | |
| 	// handling the rdata for each of these types.
 | |
| 
 | |
| 	st := zExpectOwnerDir // initial state
 | |
| 	h := &zp.h
 | |
| 
 | |
| 	for l, ok := zp.c.Next(); ok; l, ok = zp.c.Next() {
 | |
| 		// zlexer spotted an error already
 | |
| 		if l.err {
 | |
| 			return zp.setParseError(l.token, l)
 | |
| 		}
 | |
| 
 | |
| 		switch st {
 | |
| 		case zExpectOwnerDir:
 | |
| 			// We can also expect a directive, like $TTL or $ORIGIN
 | |
| 			if zp.defttl != nil {
 | |
| 				h.Ttl = zp.defttl.ttl
 | |
| 			}
 | |
| 
 | |
| 			h.Class = ClassINET
 | |
| 
 | |
| 			switch l.value {
 | |
| 			case zNewline:
 | |
| 				st = zExpectOwnerDir
 | |
| 			case zOwner:
 | |
| 				name, ok := toAbsoluteName(l.token, zp.origin)
 | |
| 				if !ok {
 | |
| 					return zp.setParseError("bad owner name", l)
 | |
| 				}
 | |
| 
 | |
| 				h.Name = name
 | |
| 
 | |
| 				st = zExpectOwnerBl
 | |
| 			case zDirTTL:
 | |
| 				st = zExpectDirTTLBl
 | |
| 			case zDirOrigin:
 | |
| 				st = zExpectDirOriginBl
 | |
| 			case zDirInclude:
 | |
| 				st = zExpectDirIncludeBl
 | |
| 			case zDirGenerate:
 | |
| 				st = zExpectDirGenerateBl
 | |
| 			case zRrtpe:
 | |
| 				h.Rrtype = l.torc
 | |
| 
 | |
| 				st = zExpectRdata
 | |
| 			case zClass:
 | |
| 				h.Class = l.torc
 | |
| 
 | |
| 				st = zExpectAnyNoClassBl
 | |
| 			case zBlank:
 | |
| 				// Discard, can happen when there is nothing on the
 | |
| 				// line except the RR type
 | |
| 			case zString:
 | |
| 				ttl, ok := stringToTTL(l.token)
 | |
| 				if !ok {
 | |
| 					return zp.setParseError("not a TTL", l)
 | |
| 				}
 | |
| 
 | |
| 				h.Ttl = ttl
 | |
| 
 | |
| 				if zp.defttl == nil || !zp.defttl.isByDirective {
 | |
| 					zp.defttl = &ttlState{ttl, false}
 | |
| 				}
 | |
| 
 | |
| 				st = zExpectAnyNoTTLBl
 | |
| 			default:
 | |
| 				return zp.setParseError("syntax error at beginning", l)
 | |
| 			}
 | |
| 		case zExpectDirIncludeBl:
 | |
| 			if l.value != zBlank {
 | |
| 				return zp.setParseError("no blank after $INCLUDE-directive", l)
 | |
| 			}
 | |
| 
 | |
| 			st = zExpectDirInclude
 | |
| 		case zExpectDirInclude:
 | |
| 			if l.value != zString {
 | |
| 				return zp.setParseError("expecting $INCLUDE value, not this...", l)
 | |
| 			}
 | |
| 
 | |
| 			neworigin := zp.origin // There may be optionally a new origin set after the filename, if not use current one
 | |
| 			switch l, _ := zp.c.Next(); l.value {
 | |
| 			case zBlank:
 | |
| 				l, _ := zp.c.Next()
 | |
| 				if l.value == zString {
 | |
| 					name, ok := toAbsoluteName(l.token, zp.origin)
 | |
| 					if !ok {
 | |
| 						return zp.setParseError("bad origin name", l)
 | |
| 					}
 | |
| 
 | |
| 					neworigin = name
 | |
| 				}
 | |
| 			case zNewline, zEOF:
 | |
| 				// Ok
 | |
| 			default:
 | |
| 				return zp.setParseError("garbage after $INCLUDE", l)
 | |
| 			}
 | |
| 
 | |
| 			if !zp.includeAllowed {
 | |
| 				return zp.setParseError("$INCLUDE directive not allowed", l)
 | |
| 			}
 | |
| 			if zp.includeDepth >= maxIncludeDepth {
 | |
| 				return zp.setParseError("too deeply nested $INCLUDE", l)
 | |
| 			}
 | |
| 
 | |
| 			// Start with the new file
 | |
| 			includePath := l.token
 | |
| 			var r1 io.Reader
 | |
| 			var e1 error
 | |
| 			if zp.fsys != nil {
 | |
| 				// fs.FS always uses / as separator, even on Windows, so use
 | |
| 				// path instead of filepath here:
 | |
| 				if !path.IsAbs(includePath) {
 | |
| 					includePath = path.Join(path.Dir(zp.file), includePath)
 | |
| 				}
 | |
| 
 | |
| 				// os.DirFS, and probably others, expect all paths to be
 | |
| 				// relative, so clean the path and remove leading / if
 | |
| 				// present:
 | |
| 				includePath = strings.TrimLeft(path.Clean(includePath), "/")
 | |
| 
 | |
| 				r1, e1 = zp.fsys.Open(includePath)
 | |
| 			} else {
 | |
| 				if !filepath.IsAbs(includePath) {
 | |
| 					includePath = filepath.Join(filepath.Dir(zp.file), includePath)
 | |
| 				}
 | |
| 				r1, e1 = os.Open(includePath)
 | |
| 			}
 | |
| 			if e1 != nil {
 | |
| 				var as string
 | |
| 				if includePath != l.token {
 | |
| 					as = fmt.Sprintf(" as `%s'", includePath)
 | |
| 				}
 | |
| 				zp.parseErr = &ParseError{
 | |
| 					file:       zp.file,
 | |
| 					wrappedErr: fmt.Errorf("failed to open `%s'%s: %w", l.token, as, e1),
 | |
| 					lex:        l,
 | |
| 				}
 | |
| 				return nil, false
 | |
| 			}
 | |
| 
 | |
| 			zp.sub = NewZoneParser(r1, neworigin, includePath)
 | |
| 			zp.sub.defttl, zp.sub.includeDepth, zp.sub.r = zp.defttl, zp.includeDepth+1, r1
 | |
| 			zp.sub.SetIncludeAllowed(true)
 | |
| 			zp.sub.SetIncludeFS(zp.fsys)
 | |
| 			return zp.subNext()
 | |
| 		case zExpectDirTTLBl:
 | |
| 			if l.value != zBlank {
 | |
| 				return zp.setParseError("no blank after $TTL-directive", l)
 | |
| 			}
 | |
| 
 | |
| 			st = zExpectDirTTL
 | |
| 		case zExpectDirTTL:
 | |
| 			if l.value != zString {
 | |
| 				return zp.setParseError("expecting $TTL value, not this...", l)
 | |
| 			}
 | |
| 
 | |
| 			if err := slurpRemainder(zp.c); err != nil {
 | |
| 				return zp.setParseError(err.err, err.lex)
 | |
| 			}
 | |
| 
 | |
| 			ttl, ok := stringToTTL(l.token)
 | |
| 			if !ok {
 | |
| 				return zp.setParseError("expecting $TTL value, not this...", l)
 | |
| 			}
 | |
| 
 | |
| 			zp.defttl = &ttlState{ttl, true}
 | |
| 
 | |
| 			st = zExpectOwnerDir
 | |
| 		case zExpectDirOriginBl:
 | |
| 			if l.value != zBlank {
 | |
| 				return zp.setParseError("no blank after $ORIGIN-directive", l)
 | |
| 			}
 | |
| 
 | |
| 			st = zExpectDirOrigin
 | |
| 		case zExpectDirOrigin:
 | |
| 			if l.value != zString {
 | |
| 				return zp.setParseError("expecting $ORIGIN value, not this...", l)
 | |
| 			}
 | |
| 
 | |
| 			if err := slurpRemainder(zp.c); err != nil {
 | |
| 				return zp.setParseError(err.err, err.lex)
 | |
| 			}
 | |
| 
 | |
| 			name, ok := toAbsoluteName(l.token, zp.origin)
 | |
| 			if !ok {
 | |
| 				return zp.setParseError("bad origin name", l)
 | |
| 			}
 | |
| 
 | |
| 			zp.origin = name
 | |
| 
 | |
| 			st = zExpectOwnerDir
 | |
| 		case zExpectDirGenerateBl:
 | |
| 			if l.value != zBlank {
 | |
| 				return zp.setParseError("no blank after $GENERATE-directive", l)
 | |
| 			}
 | |
| 
 | |
| 			st = zExpectDirGenerate
 | |
| 		case zExpectDirGenerate:
 | |
| 			if zp.generateDisallowed {
 | |
| 				return zp.setParseError("nested $GENERATE directive not allowed", l)
 | |
| 			}
 | |
| 			if l.value != zString {
 | |
| 				return zp.setParseError("expecting $GENERATE value, not this...", l)
 | |
| 			}
 | |
| 
 | |
| 			return zp.generate(l)
 | |
| 		case zExpectOwnerBl:
 | |
| 			if l.value != zBlank {
 | |
| 				return zp.setParseError("no blank after owner", l)
 | |
| 			}
 | |
| 
 | |
| 			st = zExpectAny
 | |
| 		case zExpectAny:
 | |
| 			switch l.value {
 | |
| 			case zRrtpe:
 | |
| 				if zp.defttl == nil {
 | |
| 					return zp.setParseError("missing TTL with no previous value", l)
 | |
| 				}
 | |
| 
 | |
| 				h.Rrtype = l.torc
 | |
| 
 | |
| 				st = zExpectRdata
 | |
| 			case zClass:
 | |
| 				h.Class = l.torc
 | |
| 
 | |
| 				st = zExpectAnyNoClassBl
 | |
| 			case zString:
 | |
| 				ttl, ok := stringToTTL(l.token)
 | |
| 				if !ok {
 | |
| 					return zp.setParseError("not a TTL", l)
 | |
| 				}
 | |
| 
 | |
| 				h.Ttl = ttl
 | |
| 
 | |
| 				if zp.defttl == nil || !zp.defttl.isByDirective {
 | |
| 					zp.defttl = &ttlState{ttl, false}
 | |
| 				}
 | |
| 
 | |
| 				st = zExpectAnyNoTTLBl
 | |
| 			default:
 | |
| 				return zp.setParseError("expecting RR type, TTL or class, not this...", l)
 | |
| 			}
 | |
| 		case zExpectAnyNoClassBl:
 | |
| 			if l.value != zBlank {
 | |
| 				return zp.setParseError("no blank before class", l)
 | |
| 			}
 | |
| 
 | |
| 			st = zExpectAnyNoClass
 | |
| 		case zExpectAnyNoTTLBl:
 | |
| 			if l.value != zBlank {
 | |
| 				return zp.setParseError("no blank before TTL", l)
 | |
| 			}
 | |
| 
 | |
| 			st = zExpectAnyNoTTL
 | |
| 		case zExpectAnyNoTTL:
 | |
| 			switch l.value {
 | |
| 			case zClass:
 | |
| 				h.Class = l.torc
 | |
| 
 | |
| 				st = zExpectRrtypeBl
 | |
| 			case zRrtpe:
 | |
| 				h.Rrtype = l.torc
 | |
| 
 | |
| 				st = zExpectRdata
 | |
| 			default:
 | |
| 				return zp.setParseError("expecting RR type or class, not this...", l)
 | |
| 			}
 | |
| 		case zExpectAnyNoClass:
 | |
| 			switch l.value {
 | |
| 			case zString:
 | |
| 				ttl, ok := stringToTTL(l.token)
 | |
| 				if !ok {
 | |
| 					return zp.setParseError("not a TTL", l)
 | |
| 				}
 | |
| 
 | |
| 				h.Ttl = ttl
 | |
| 
 | |
| 				if zp.defttl == nil || !zp.defttl.isByDirective {
 | |
| 					zp.defttl = &ttlState{ttl, false}
 | |
| 				}
 | |
| 
 | |
| 				st = zExpectRrtypeBl
 | |
| 			case zRrtpe:
 | |
| 				h.Rrtype = l.torc
 | |
| 
 | |
| 				st = zExpectRdata
 | |
| 			default:
 | |
| 				return zp.setParseError("expecting RR type or TTL, not this...", l)
 | |
| 			}
 | |
| 		case zExpectRrtypeBl:
 | |
| 			if l.value != zBlank {
 | |
| 				return zp.setParseError("no blank before RR type", l)
 | |
| 			}
 | |
| 
 | |
| 			st = zExpectRrtype
 | |
| 		case zExpectRrtype:
 | |
| 			if l.value != zRrtpe {
 | |
| 				return zp.setParseError("unknown RR type", l)
 | |
| 			}
 | |
| 
 | |
| 			h.Rrtype = l.torc
 | |
| 
 | |
| 			st = zExpectRdata
 | |
| 		case zExpectRdata:
 | |
| 			var (
 | |
| 				rr             RR
 | |
| 				parseAsRFC3597 bool
 | |
| 			)
 | |
| 			if newFn, ok := TypeToRR[h.Rrtype]; ok {
 | |
| 				rr = newFn()
 | |
| 				*rr.Header() = *h
 | |
| 
 | |
| 				// We may be parsing a known RR type using the RFC3597 format.
 | |
| 				// If so, we handle that here in a generic way.
 | |
| 				//
 | |
| 				// This is also true for PrivateRR types which will have the
 | |
| 				// RFC3597 parsing done for them and the Unpack method called
 | |
| 				// to populate the RR instead of simply deferring to Parse.
 | |
| 				if zp.c.Peek().token == "\\#" {
 | |
| 					parseAsRFC3597 = true
 | |
| 				}
 | |
| 			} else {
 | |
| 				rr = &RFC3597{Hdr: *h}
 | |
| 			}
 | |
| 
 | |
| 			_, isPrivate := rr.(*PrivateRR)
 | |
| 			if !isPrivate && zp.c.Peek().token == "" {
 | |
| 				// This is a dynamic update rr.
 | |
| 
 | |
| 				if err := slurpRemainder(zp.c); err != nil {
 | |
| 					return zp.setParseError(err.err, err.lex)
 | |
| 				}
 | |
| 
 | |
| 				return rr, true
 | |
| 			} else if l.value == zNewline {
 | |
| 				return zp.setParseError("unexpected newline", l)
 | |
| 			}
 | |
| 
 | |
| 			parseAsRR := rr
 | |
| 			if parseAsRFC3597 {
 | |
| 				parseAsRR = &RFC3597{Hdr: *h}
 | |
| 			}
 | |
| 
 | |
| 			if err := parseAsRR.parse(zp.c, zp.origin); err != nil {
 | |
| 				// err is a concrete *ParseError without the file field set.
 | |
| 				// The setParseError call below will construct a new
 | |
| 				// *ParseError with file set to zp.file.
 | |
| 
 | |
| 				// err.lex may be nil in which case we substitute our current
 | |
| 				// lex token.
 | |
| 				if err.lex == (lex{}) {
 | |
| 					return zp.setParseError(err.err, l)
 | |
| 				}
 | |
| 
 | |
| 				return zp.setParseError(err.err, err.lex)
 | |
| 			}
 | |
| 
 | |
| 			if parseAsRFC3597 {
 | |
| 				err := parseAsRR.(*RFC3597).fromRFC3597(rr)
 | |
| 				if err != nil {
 | |
| 					return zp.setParseError(err.Error(), l)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return rr, true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// If we get here, we and the h.Rrtype is still zero, we haven't parsed anything, this
 | |
| 	// is not an error, because an empty zone file is still a zone file.
 | |
| 	return nil, false
 | |
| }
 | |
| 
 | |
| type zlexer struct {
 | |
| 	br io.ByteReader
 | |
| 
 | |
| 	readErr error
 | |
| 
 | |
| 	line   int
 | |
| 	column int
 | |
| 
 | |
| 	comBuf  string
 | |
| 	comment string
 | |
| 
 | |
| 	l       lex
 | |
| 	cachedL *lex
 | |
| 
 | |
| 	brace  int
 | |
| 	quote  bool
 | |
| 	space  bool
 | |
| 	commt  bool
 | |
| 	rrtype bool
 | |
| 	owner  bool
 | |
| 
 | |
| 	nextL bool
 | |
| 
 | |
| 	eol bool // end-of-line
 | |
| }
 | |
| 
 | |
| func newZLexer(r io.Reader) *zlexer {
 | |
| 	br, ok := r.(io.ByteReader)
 | |
| 	if !ok {
 | |
| 		br = bufio.NewReaderSize(r, 1024)
 | |
| 	}
 | |
| 
 | |
| 	return &zlexer{
 | |
| 		br: br,
 | |
| 
 | |
| 		line: 1,
 | |
| 
 | |
| 		owner: true,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (zl *zlexer) Err() error {
 | |
| 	if zl.readErr == io.EOF {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return zl.readErr
 | |
| }
 | |
| 
 | |
| // readByte returns the next byte from the input
 | |
| func (zl *zlexer) readByte() (byte, bool) {
 | |
| 	if zl.readErr != nil {
 | |
| 		return 0, false
 | |
| 	}
 | |
| 
 | |
| 	c, err := zl.br.ReadByte()
 | |
| 	if err != nil {
 | |
| 		zl.readErr = err
 | |
| 		return 0, false
 | |
| 	}
 | |
| 
 | |
| 	// delay the newline handling until the next token is delivered,
 | |
| 	// fixes off-by-one errors when reporting a parse error.
 | |
| 	if zl.eol {
 | |
| 		zl.line++
 | |
| 		zl.column = 0
 | |
| 		zl.eol = false
 | |
| 	}
 | |
| 
 | |
| 	if c == '\n' {
 | |
| 		zl.eol = true
 | |
| 	} else {
 | |
| 		zl.column++
 | |
| 	}
 | |
| 
 | |
| 	return c, true
 | |
| }
 | |
| 
 | |
| func (zl *zlexer) Peek() lex {
 | |
| 	if zl.nextL {
 | |
| 		return zl.l
 | |
| 	}
 | |
| 
 | |
| 	l, ok := zl.Next()
 | |
| 	if !ok {
 | |
| 		return l
 | |
| 	}
 | |
| 
 | |
| 	if zl.nextL {
 | |
| 		// Cache l. Next returns zl.cachedL then zl.l.
 | |
| 		zl.cachedL = &l
 | |
| 	} else {
 | |
| 		// In this case l == zl.l, so we just tell Next to return zl.l.
 | |
| 		zl.nextL = true
 | |
| 	}
 | |
| 
 | |
| 	return l
 | |
| }
 | |
| 
 | |
| func (zl *zlexer) Next() (lex, bool) {
 | |
| 	l := &zl.l
 | |
| 	switch {
 | |
| 	case zl.cachedL != nil:
 | |
| 		l, zl.cachedL = zl.cachedL, nil
 | |
| 		return *l, true
 | |
| 	case zl.nextL:
 | |
| 		zl.nextL = false
 | |
| 		return *l, true
 | |
| 	case l.err:
 | |
| 		// Parsing errors should be sticky.
 | |
| 		return lex{value: zEOF}, false
 | |
| 	}
 | |
| 
 | |
| 	var (
 | |
| 		str = make([]byte, maxTok) // Hold string text
 | |
| 		com = make([]byte, maxTok) // Hold comment text
 | |
| 
 | |
| 		stri int // Offset in str (0 means empty)
 | |
| 		comi int // Offset in com (0 means empty)
 | |
| 
 | |
| 		escape bool
 | |
| 	)
 | |
| 
 | |
| 	if zl.comBuf != "" {
 | |
| 		comi = copy(com[:], zl.comBuf)
 | |
| 		zl.comBuf = ""
 | |
| 	}
 | |
| 
 | |
| 	zl.comment = ""
 | |
| 
 | |
| 	for x, ok := zl.readByte(); ok; x, ok = zl.readByte() {
 | |
| 		l.line, l.column = zl.line, zl.column
 | |
| 
 | |
| 		if stri >= len(str) {
 | |
| 			// if buffer length is insufficient, increase it.
 | |
| 			str = append(str[:], make([]byte, maxTok)...)
 | |
| 		}
 | |
| 		if comi >= len(com) {
 | |
| 			// if buffer length is insufficient, increase it.
 | |
| 			com = append(com[:], make([]byte, maxTok)...)
 | |
| 		}
 | |
| 
 | |
| 		switch x {
 | |
| 		case ' ', '\t':
 | |
| 			if escape || zl.quote {
 | |
| 				// Inside quotes or escaped this is legal.
 | |
| 				str[stri] = x
 | |
| 				stri++
 | |
| 
 | |
| 				escape = false
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			if zl.commt {
 | |
| 				com[comi] = x
 | |
| 				comi++
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			var retL lex
 | |
| 			if stri == 0 {
 | |
| 				// Space directly in the beginning, handled in the grammar
 | |
| 			} else if zl.owner {
 | |
| 				// If we have a string and it's the first, make it an owner
 | |
| 				l.value = zOwner
 | |
| 				l.token = string(str[:stri])
 | |
| 
 | |
| 				// escape $... start with a \ not a $, so this will work
 | |
| 				switch strings.ToUpper(l.token) {
 | |
| 				case "$TTL":
 | |
| 					l.value = zDirTTL
 | |
| 				case "$ORIGIN":
 | |
| 					l.value = zDirOrigin
 | |
| 				case "$INCLUDE":
 | |
| 					l.value = zDirInclude
 | |
| 				case "$GENERATE":
 | |
| 					l.value = zDirGenerate
 | |
| 				}
 | |
| 
 | |
| 				retL = *l
 | |
| 			} else {
 | |
| 				l.value = zString
 | |
| 				l.token = string(str[:stri])
 | |
| 
 | |
| 				if !zl.rrtype {
 | |
| 					tokenUpper := strings.ToUpper(l.token)
 | |
| 					if t, ok := StringToType[tokenUpper]; ok {
 | |
| 						l.value = zRrtpe
 | |
| 						l.torc = t
 | |
| 
 | |
| 						zl.rrtype = true
 | |
| 					} else if strings.HasPrefix(tokenUpper, "TYPE") {
 | |
| 						t, ok := typeToInt(l.token)
 | |
| 						if !ok {
 | |
| 							l.token = "unknown RR type"
 | |
| 							l.err = true
 | |
| 							return *l, true
 | |
| 						}
 | |
| 
 | |
| 						l.value = zRrtpe
 | |
| 						l.torc = t
 | |
| 
 | |
| 						zl.rrtype = true
 | |
| 					}
 | |
| 
 | |
| 					if t, ok := StringToClass[tokenUpper]; ok {
 | |
| 						l.value = zClass
 | |
| 						l.torc = t
 | |
| 					} else if strings.HasPrefix(tokenUpper, "CLASS") {
 | |
| 						t, ok := classToInt(l.token)
 | |
| 						if !ok {
 | |
| 							l.token = "unknown class"
 | |
| 							l.err = true
 | |
| 							return *l, true
 | |
| 						}
 | |
| 
 | |
| 						l.value = zClass
 | |
| 						l.torc = t
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				retL = *l
 | |
| 			}
 | |
| 
 | |
| 			zl.owner = false
 | |
| 
 | |
| 			if !zl.space {
 | |
| 				zl.space = true
 | |
| 
 | |
| 				l.value = zBlank
 | |
| 				l.token = " "
 | |
| 
 | |
| 				if retL == (lex{}) {
 | |
| 					return *l, true
 | |
| 				}
 | |
| 
 | |
| 				zl.nextL = true
 | |
| 			}
 | |
| 
 | |
| 			if retL != (lex{}) {
 | |
| 				return retL, true
 | |
| 			}
 | |
| 		case ';':
 | |
| 			if escape || zl.quote {
 | |
| 				// Inside quotes or escaped this is legal.
 | |
| 				str[stri] = x
 | |
| 				stri++
 | |
| 
 | |
| 				escape = false
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			zl.commt = true
 | |
| 			zl.comBuf = ""
 | |
| 
 | |
| 			if comi > 1 {
 | |
| 				// A newline was previously seen inside a comment that
 | |
| 				// was inside braces and we delayed adding it until now.
 | |
| 				com[comi] = ' ' // convert newline to space
 | |
| 				comi++
 | |
| 				if comi >= len(com) {
 | |
| 					l.token = "comment length insufficient for parsing"
 | |
| 					l.err = true
 | |
| 					return *l, true
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			com[comi] = ';'
 | |
| 			comi++
 | |
| 
 | |
| 			if stri > 0 {
 | |
| 				zl.comBuf = string(com[:comi])
 | |
| 
 | |
| 				l.value = zString
 | |
| 				l.token = string(str[:stri])
 | |
| 				return *l, true
 | |
| 			}
 | |
| 		case '\r':
 | |
| 			escape = false
 | |
| 
 | |
| 			if zl.quote {
 | |
| 				str[stri] = x
 | |
| 				stri++
 | |
| 			}
 | |
| 
 | |
| 			// discard if outside of quotes
 | |
| 		case '\n':
 | |
| 			escape = false
 | |
| 
 | |
| 			// Escaped newline
 | |
| 			if zl.quote {
 | |
| 				str[stri] = x
 | |
| 				stri++
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			if zl.commt {
 | |
| 				// Reset a comment
 | |
| 				zl.commt = false
 | |
| 				zl.rrtype = false
 | |
| 
 | |
| 				// If not in a brace this ends the comment AND the RR
 | |
| 				if zl.brace == 0 {
 | |
| 					zl.owner = true
 | |
| 
 | |
| 					l.value = zNewline
 | |
| 					l.token = "\n"
 | |
| 					zl.comment = string(com[:comi])
 | |
| 					return *l, true
 | |
| 				}
 | |
| 
 | |
| 				zl.comBuf = string(com[:comi])
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			if zl.brace == 0 {
 | |
| 				// If there is previous text, we should output it here
 | |
| 				var retL lex
 | |
| 				if stri != 0 {
 | |
| 					l.value = zString
 | |
| 					l.token = string(str[:stri])
 | |
| 
 | |
| 					if !zl.rrtype {
 | |
| 						tokenUpper := strings.ToUpper(l.token)
 | |
| 						if t, ok := StringToType[tokenUpper]; ok {
 | |
| 							zl.rrtype = true
 | |
| 
 | |
| 							l.value = zRrtpe
 | |
| 							l.torc = t
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					retL = *l
 | |
| 				}
 | |
| 
 | |
| 				l.value = zNewline
 | |
| 				l.token = "\n"
 | |
| 
 | |
| 				zl.comment = zl.comBuf
 | |
| 				zl.comBuf = ""
 | |
| 				zl.rrtype = false
 | |
| 				zl.owner = true
 | |
| 
 | |
| 				if retL != (lex{}) {
 | |
| 					zl.nextL = true
 | |
| 					return retL, true
 | |
| 				}
 | |
| 
 | |
| 				return *l, true
 | |
| 			}
 | |
| 		case '\\':
 | |
| 			// comments do not get escaped chars, everything is copied
 | |
| 			if zl.commt {
 | |
| 				com[comi] = x
 | |
| 				comi++
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			// something already escaped must be in string
 | |
| 			if escape {
 | |
| 				str[stri] = x
 | |
| 				stri++
 | |
| 
 | |
| 				escape = false
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			// something escaped outside of string gets added to string
 | |
| 			str[stri] = x
 | |
| 			stri++
 | |
| 
 | |
| 			escape = true
 | |
| 		case '"':
 | |
| 			if zl.commt {
 | |
| 				com[comi] = x
 | |
| 				comi++
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			if escape {
 | |
| 				str[stri] = x
 | |
| 				stri++
 | |
| 
 | |
| 				escape = false
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			zl.space = false
 | |
| 
 | |
| 			// send previous gathered text and the quote
 | |
| 			var retL lex
 | |
| 			if stri != 0 {
 | |
| 				l.value = zString
 | |
| 				l.token = string(str[:stri])
 | |
| 
 | |
| 				retL = *l
 | |
| 			}
 | |
| 
 | |
| 			// send quote itself as separate token
 | |
| 			l.value = zQuote
 | |
| 			l.token = "\""
 | |
| 
 | |
| 			zl.quote = !zl.quote
 | |
| 
 | |
| 			if retL != (lex{}) {
 | |
| 				zl.nextL = true
 | |
| 				return retL, true
 | |
| 			}
 | |
| 
 | |
| 			return *l, true
 | |
| 		case '(', ')':
 | |
| 			if zl.commt {
 | |
| 				com[comi] = x
 | |
| 				comi++
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			if escape || zl.quote {
 | |
| 				// Inside quotes or escaped this is legal.
 | |
| 				str[stri] = x
 | |
| 				stri++
 | |
| 
 | |
| 				escape = false
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			switch x {
 | |
| 			case ')':
 | |
| 				zl.brace--
 | |
| 
 | |
| 				if zl.brace < 0 {
 | |
| 					l.token = "extra closing brace"
 | |
| 					l.err = true
 | |
| 					return *l, true
 | |
| 				}
 | |
| 			case '(':
 | |
| 				zl.brace++
 | |
| 			}
 | |
| 		default:
 | |
| 			escape = false
 | |
| 
 | |
| 			if zl.commt {
 | |
| 				com[comi] = x
 | |
| 				comi++
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			str[stri] = x
 | |
| 			stri++
 | |
| 
 | |
| 			zl.space = false
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if zl.readErr != nil && zl.readErr != io.EOF {
 | |
| 		// Don't return any tokens after a read error occurs.
 | |
| 		return lex{value: zEOF}, false
 | |
| 	}
 | |
| 
 | |
| 	var retL lex
 | |
| 	if stri > 0 {
 | |
| 		// Send remainder of str
 | |
| 		l.value = zString
 | |
| 		l.token = string(str[:stri])
 | |
| 		retL = *l
 | |
| 
 | |
| 		if comi <= 0 {
 | |
| 			return retL, true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if comi > 0 {
 | |
| 		// Send remainder of com
 | |
| 		l.value = zNewline
 | |
| 		l.token = "\n"
 | |
| 		zl.comment = string(com[:comi])
 | |
| 
 | |
| 		if retL != (lex{}) {
 | |
| 			zl.nextL = true
 | |
| 			return retL, true
 | |
| 		}
 | |
| 
 | |
| 		return *l, true
 | |
| 	}
 | |
| 
 | |
| 	if zl.brace != 0 {
 | |
| 		l.token = "unbalanced brace"
 | |
| 		l.err = true
 | |
| 		return *l, true
 | |
| 	}
 | |
| 
 | |
| 	return lex{value: zEOF}, false
 | |
| }
 | |
| 
 | |
| func (zl *zlexer) Comment() string {
 | |
| 	if zl.l.err {
 | |
| 		return ""
 | |
| 	}
 | |
| 
 | |
| 	return zl.comment
 | |
| }
 | |
| 
 | |
| // Extract the class number from CLASSxx
 | |
| func classToInt(token string) (uint16, bool) {
 | |
| 	offset := 5
 | |
| 	if len(token) < offset+1 {
 | |
| 		return 0, false
 | |
| 	}
 | |
| 	class, err := strconv.ParseUint(token[offset:], 10, 16)
 | |
| 	if err != nil {
 | |
| 		return 0, false
 | |
| 	}
 | |
| 	return uint16(class), true
 | |
| }
 | |
| 
 | |
| // Extract the rr number from TYPExxx
 | |
| func typeToInt(token string) (uint16, bool) {
 | |
| 	offset := 4
 | |
| 	if len(token) < offset+1 {
 | |
| 		return 0, false
 | |
| 	}
 | |
| 	typ, err := strconv.ParseUint(token[offset:], 10, 16)
 | |
| 	if err != nil {
 | |
| 		return 0, false
 | |
| 	}
 | |
| 	return uint16(typ), true
 | |
| }
 | |
| 
 | |
| // stringToTTL parses things like 2w, 2m, etc, and returns the time in seconds.
 | |
| func stringToTTL(token string) (uint32, bool) {
 | |
| 	var s, i uint32
 | |
| 	for _, c := range token {
 | |
| 		switch c {
 | |
| 		case 's', 'S':
 | |
| 			s += i
 | |
| 			i = 0
 | |
| 		case 'm', 'M':
 | |
| 			s += i * 60
 | |
| 			i = 0
 | |
| 		case 'h', 'H':
 | |
| 			s += i * 60 * 60
 | |
| 			i = 0
 | |
| 		case 'd', 'D':
 | |
| 			s += i * 60 * 60 * 24
 | |
| 			i = 0
 | |
| 		case 'w', 'W':
 | |
| 			s += i * 60 * 60 * 24 * 7
 | |
| 			i = 0
 | |
| 		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
 | |
| 			i *= 10
 | |
| 			i += uint32(c) - '0'
 | |
| 		default:
 | |
| 			return 0, false
 | |
| 		}
 | |
| 	}
 | |
| 	return s + i, true
 | |
| }
 | |
| 
 | |
| // Parse LOC records' <digits>[.<digits>][mM] into a
 | |
| // mantissa exponent format. Token should contain the entire
 | |
| // string (i.e. no spaces allowed)
 | |
| func stringToCm(token string) (e, m uint8, ok bool) {
 | |
| 	if token[len(token)-1] == 'M' || token[len(token)-1] == 'm' {
 | |
| 		token = token[0 : len(token)-1]
 | |
| 	}
 | |
| 
 | |
| 	var (
 | |
| 		meters, cmeters, val int
 | |
| 		err                  error
 | |
| 	)
 | |
| 	mStr, cmStr, hasCM := strings.Cut(token, ".")
 | |
| 	if hasCM {
 | |
| 		// There's no point in having more than 2 digits in this part, and would rather make the implementation complicated ('123' should be treated as '12').
 | |
| 		// So we simply reject it.
 | |
| 		// We also make sure the first character is a digit to reject '+-' signs.
 | |
| 		cmeters, err = strconv.Atoi(cmStr)
 | |
| 		if err != nil || len(cmStr) > 2 || cmStr[0] < '0' || cmStr[0] > '9' {
 | |
| 			return
 | |
| 		}
 | |
| 		if len(cmStr) == 1 {
 | |
| 			// 'nn.1' must be treated as 'nn-meters and 10cm, not 1cm.
 | |
| 			cmeters *= 10
 | |
| 		}
 | |
| 	}
 | |
| 	// This slightly ugly condition will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm).
 | |
| 	if !hasCM || mStr != "" {
 | |
| 		meters, err = strconv.Atoi(mStr)
 | |
| 		// RFC1876 states the max value is 90000000.00.  The latter two conditions enforce it.
 | |
| 		if err != nil || mStr[0] < '0' || mStr[0] > '9' || meters > 90000000 || (meters == 90000000 && cmeters != 0) {
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if meters > 0 {
 | |
| 		e = 2
 | |
| 		val = meters
 | |
| 	} else {
 | |
| 		e = 0
 | |
| 		val = cmeters
 | |
| 	}
 | |
| 	for val >= 10 {
 | |
| 		e++
 | |
| 		val /= 10
 | |
| 	}
 | |
| 	return e, uint8(val), true
 | |
| }
 | |
| 
 | |
| func toAbsoluteName(name, origin string) (absolute string, ok bool) {
 | |
| 	// check for an explicit origin reference
 | |
| 	if name == "@" {
 | |
| 		// require a nonempty origin
 | |
| 		if origin == "" {
 | |
| 			return "", false
 | |
| 		}
 | |
| 		return origin, true
 | |
| 	}
 | |
| 
 | |
| 	// require a valid domain name
 | |
| 	_, ok = IsDomainName(name)
 | |
| 	if !ok || name == "" {
 | |
| 		return "", false
 | |
| 	}
 | |
| 
 | |
| 	// check if name is already absolute
 | |
| 	if IsFqdn(name) {
 | |
| 		return name, true
 | |
| 	}
 | |
| 
 | |
| 	// require a nonempty origin
 | |
| 	if origin == "" {
 | |
| 		return "", false
 | |
| 	}
 | |
| 	return appendOrigin(name, origin), true
 | |
| }
 | |
| 
 | |
| func appendOrigin(name, origin string) string {
 | |
| 	if origin == "." {
 | |
| 		return name + origin
 | |
| 	}
 | |
| 	return name + "." + origin
 | |
| }
 | |
| 
 | |
| // LOC record helper function
 | |
| func locCheckNorth(token string, latitude uint32) (uint32, bool) {
 | |
| 	if latitude > 90*1000*60*60 {
 | |
| 		return latitude, false
 | |
| 	}
 | |
| 	switch token {
 | |
| 	case "n", "N":
 | |
| 		return LOC_EQUATOR + latitude, true
 | |
| 	case "s", "S":
 | |
| 		return LOC_EQUATOR - latitude, true
 | |
| 	}
 | |
| 	return latitude, false
 | |
| }
 | |
| 
 | |
| // LOC record helper function
 | |
| func locCheckEast(token string, longitude uint32) (uint32, bool) {
 | |
| 	if longitude > 180*1000*60*60 {
 | |
| 		return longitude, false
 | |
| 	}
 | |
| 	switch token {
 | |
| 	case "e", "E":
 | |
| 		return LOC_EQUATOR + longitude, true
 | |
| 	case "w", "W":
 | |
| 		return LOC_EQUATOR - longitude, true
 | |
| 	}
 | |
| 	return longitude, false
 | |
| }
 | |
| 
 | |
| // "Eat" the rest of the "line"
 | |
| func slurpRemainder(c *zlexer) *ParseError {
 | |
| 	l, _ := c.Next()
 | |
| 	switch l.value {
 | |
| 	case zBlank:
 | |
| 		l, _ = c.Next()
 | |
| 		if l.value != zNewline && l.value != zEOF {
 | |
| 			return &ParseError{err: "garbage after rdata", lex: l}
 | |
| 		}
 | |
| 	case zNewline:
 | |
| 	case zEOF:
 | |
| 	default:
 | |
| 		return &ParseError{err: "garbage after rdata", lex: l}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Parse a 64 bit-like ipv6 address: "0014:4fff:ff20:ee64"
 | |
| // Used for NID and L64 record.
 | |
| func stringToNodeID(l lex) (uint64, *ParseError) {
 | |
| 	if len(l.token) < 19 {
 | |
| 		return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
 | |
| 	}
 | |
| 	// There must be three colons at fixes positions, if not its a parse error
 | |
| 	if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' {
 | |
| 		return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
 | |
| 	}
 | |
| 	s := l.token[0:4] + l.token[5:9] + l.token[10:14] + l.token[15:19]
 | |
| 	u, err := strconv.ParseUint(s, 16, 64)
 | |
| 	if err != nil {
 | |
| 		return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
 | |
| 	}
 | |
| 	return u, nil
 | |
| }
 |