package dns

import (
	
	
	
	
	
	
	
	
	
)

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 ( *ParseError) () ( string) {
	if .file != "" {
		 = .file + ": "
	}
	if .err == "" && .wrappedErr != nil {
		.err = .wrappedErr.Error()
	}
	 += "dns: " + .err + ": " + strconv.QuoteToASCII(.lex.token) + " at line: " +
		strconv.Itoa(.lex.line) + ":" + strconv.Itoa(.lex.column)
	return
}

func ( *ParseError) () error { return .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.
// Is you need a partial resource record with no rdata - for instance - for dynamic updates, see the [ANY]
// documentation.
func ( string) (RR, error) {
	if len() > 0 && [len()-1] != '\n' { // We need a closing newline
		return ReadRR(strings.NewReader(+"\n"), "")
	}
	return ReadRR(strings.NewReader(), "")
}

// 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 ( io.Reader,  string) (RR, error) {
	 := NewZoneParser(, ".", )
	.SetDefaultTTL(defaultTtl)
	.SetIncludeAllowed(true)
	,  := .Next()
	return , .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 ( io.Reader, ,  string) *ZoneParser {
	var  *ParseError
	if  != "" {
		 = Fqdn()
		if ,  := IsDomainName(); ! {
			 = &ParseError{file: , err: "bad initial origin name"}
		}
	}

	return &ZoneParser{
		c: newZLexer(),

		parseErr: ,

		origin: ,
		file:   ,
	}
}

// SetDefaultTTL sets the parsers default TTL to ttl.
func ( *ZoneParser) ( uint32) {
	.defttl = &ttlState{, 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 ( *ZoneParser) ( bool) {
	.includeAllowed = 
}

// 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 ( *ZoneParser) ( fs.FS) {
	.fsys = 
}

// Err returns the first non-EOF error that was encountered by the
// ZoneParser.
func ( *ZoneParser) () error {
	if .parseErr != nil {
		return .parseErr
	}

	if .sub != nil {
		if  := .sub.();  != nil {
			return 
		}
	}

	return .c.Err()
}

func ( *ZoneParser) ( string,  lex) (RR, bool) {
	.parseErr = &ParseError{file: .file, err: , lex: }
	return nil, false
}

// Comment returns an optional text comment that occurred alongside
// the RR.
func ( *ZoneParser) () string {
	if .parseErr != nil {
		return ""
	}

	if .sub != nil {
		return .sub.()
	}

	return .c.Comment()
}

func ( *ZoneParser) () (RR, bool) {
	if ,  := .sub.Next();  {
		return , true
	}

	if .sub.r != nil {
		if ,  := .sub.r.(io.Closer);  {
			.Close()
		}
		.sub.r = nil
	}

	if .sub.Err() != nil {
		// We have errors to surface.
		return nil, false
	}

	.sub = nil
	return .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 ( *ZoneParser) () (RR, bool) {
	if .parseErr != nil {
		return nil, false
	}
	if .sub != nil {
		return .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.

	 := zExpectOwnerDir // initial state
	 := &.h

	for ,  := .c.Next(); ; ,  = .c.Next() {
		// zlexer spotted an error already
		if .err {
			return .setParseError(.token, )
		}

		switch  {
		case zExpectOwnerDir:
			// We can also expect a directive, like $TTL or $ORIGIN
			if .defttl != nil {
				.Ttl = .defttl.ttl
			}

			.Class = ClassINET

			switch .value {
			case zNewline:
				 = zExpectOwnerDir
			case zOwner:
				,  := toAbsoluteName(.token, .origin)
				if ! {
					return .setParseError("bad owner name", )
				}

				.Name = 

				 = zExpectOwnerBl
			case zDirTTL:
				 = zExpectDirTTLBl
			case zDirOrigin:
				 = zExpectDirOriginBl
			case zDirInclude:
				 = zExpectDirIncludeBl
			case zDirGenerate:
				 = zExpectDirGenerateBl
			case zRrtpe:
				.Rrtype = .torc

				 = zExpectRdata
			case zClass:
				.Class = .torc

				 = zExpectAnyNoClassBl
			case zBlank:
				// Discard, can happen when there is nothing on the
				// line except the RR type
			case zString:
				,  := stringToTTL(.token)
				if ! {
					return .setParseError("not a TTL", )
				}

				.Ttl = 

				if .defttl == nil || !.defttl.isByDirective {
					.defttl = &ttlState{, false}
				}

				 = zExpectAnyNoTTLBl
			default:
				return .setParseError("syntax error at beginning", )
			}
		case zExpectDirIncludeBl:
			if .value != zBlank {
				return .setParseError("no blank after $INCLUDE-directive", )
			}

			 = zExpectDirInclude
		case zExpectDirInclude:
			if .value != zString {
				return .setParseError("expecting $INCLUDE value, not this...", )
			}

			 := .origin // There may be optionally a new origin set after the filename, if not use current one
			switch ,  := .c.Next(); .value {
			case zBlank:
				,  := .c.Next()
				if .value == zString {
					,  := toAbsoluteName(.token, .origin)
					if ! {
						return .setParseError("bad origin name", )
					}

					 = 
				}
			case zNewline, zEOF:
				// Ok
			default:
				return .setParseError("garbage after $INCLUDE", )
			}

			if !.includeAllowed {
				return .setParseError("$INCLUDE directive not allowed", )
			}
			if .includeDepth >= maxIncludeDepth {
				return .setParseError("too deeply nested $INCLUDE", )
			}

			// Start with the new file
			 := .token
			var  io.Reader
			var  error
			if .fsys != nil {
				// fs.FS always uses / as separator, even on Windows, so use
				// path instead of filepath here:
				if !path.IsAbs() {
					 = path.Join(path.Dir(.file), )
				}

				// os.DirFS, and probably others, expect all paths to be
				// relative, so clean the path and remove leading / if
				// present:
				 = strings.TrimLeft(path.Clean(), "/")

				,  = .fsys.Open()
			} else {
				if !filepath.IsAbs() {
					 = filepath.Join(filepath.Dir(.file), )
				}
				,  = os.Open()
			}
			if  != nil {
				var  string
				if  != .token {
					 = fmt.Sprintf(" as `%s'", )
				}
				.parseErr = &ParseError{
					file:       .file,
					wrappedErr: fmt.Errorf("failed to open `%s'%s: %w", .token, , ),
					lex:        ,
				}
				return nil, false
			}

			.sub = NewZoneParser(, , )
			.sub.defttl, .sub.includeDepth, .sub.r = .defttl, .includeDepth+1, 
			.sub.SetIncludeAllowed(true)
			.sub.SetIncludeFS(.fsys)
			return .subNext()
		case zExpectDirTTLBl:
			if .value != zBlank {
				return .setParseError("no blank after $TTL-directive", )
			}

			 = zExpectDirTTL
		case zExpectDirTTL:
			if .value != zString {
				return .setParseError("expecting $TTL value, not this...", )
			}

			if  := slurpRemainder(.c);  != nil {
				return .setParseError(.err, .lex)
			}

			,  := stringToTTL(.token)
			if ! {
				return .setParseError("expecting $TTL value, not this...", )
			}

			.defttl = &ttlState{, true}

			 = zExpectOwnerDir
		case zExpectDirOriginBl:
			if .value != zBlank {
				return .setParseError("no blank after $ORIGIN-directive", )
			}

			 = zExpectDirOrigin
		case zExpectDirOrigin:
			if .value != zString {
				return .setParseError("expecting $ORIGIN value, not this...", )
			}

			if  := slurpRemainder(.c);  != nil {
				return .setParseError(.err, .lex)
			}

			,  := toAbsoluteName(.token, .origin)
			if ! {
				return .setParseError("bad origin name", )
			}

			.origin = 

			 = zExpectOwnerDir
		case zExpectDirGenerateBl:
			if .value != zBlank {
				return .setParseError("no blank after $GENERATE-directive", )
			}

			 = zExpectDirGenerate
		case zExpectDirGenerate:
			if .generateDisallowed {
				return .setParseError("nested $GENERATE directive not allowed", )
			}
			if .value != zString {
				return .setParseError("expecting $GENERATE value, not this...", )
			}

			return .generate()
		case zExpectOwnerBl:
			if .value != zBlank {
				return .setParseError("no blank after owner", )
			}

			 = zExpectAny
		case zExpectAny:
			switch .value {
			case zRrtpe:
				if .defttl == nil {
					return .setParseError("missing TTL with no previous value", )
				}

				.Rrtype = .torc

				 = zExpectRdata
			case zClass:
				.Class = .torc

				 = zExpectAnyNoClassBl
			case zString:
				,  := stringToTTL(.token)
				if ! {
					return .setParseError("not a TTL", )
				}

				.Ttl = 

				if .defttl == nil || !.defttl.isByDirective {
					.defttl = &ttlState{, false}
				}

				 = zExpectAnyNoTTLBl
			default:
				return .setParseError("expecting RR type, TTL or class, not this...", )
			}
		case zExpectAnyNoClassBl:
			if .value != zBlank {
				return .setParseError("no blank before class", )
			}

			 = zExpectAnyNoClass
		case zExpectAnyNoTTLBl:
			if .value != zBlank {
				return .setParseError("no blank before TTL", )
			}

			 = zExpectAnyNoTTL
		case zExpectAnyNoTTL:
			switch .value {
			case zClass:
				.Class = .torc

				 = zExpectRrtypeBl
			case zRrtpe:
				.Rrtype = .torc

				 = zExpectRdata
			default:
				return .setParseError("expecting RR type or class, not this...", )
			}
		case zExpectAnyNoClass:
			switch .value {
			case zString:
				,  := stringToTTL(.token)
				if ! {
					return .setParseError("not a TTL", )
				}

				.Ttl = 

				if .defttl == nil || !.defttl.isByDirective {
					.defttl = &ttlState{, false}
				}

				 = zExpectRrtypeBl
			case zRrtpe:
				.Rrtype = .torc

				 = zExpectRdata
			default:
				return .setParseError("expecting RR type or TTL, not this...", )
			}
		case zExpectRrtypeBl:
			if .value != zBlank {
				return .setParseError("no blank before RR type", )
			}

			 = zExpectRrtype
		case zExpectRrtype:
			if .value != zRrtpe {
				return .setParseError("unknown RR type", )
			}

			.Rrtype = .torc

			 = zExpectRdata
		case zExpectRdata:
			var (
				             RR
				 bool
			)
			if ,  := TypeToRR[.Rrtype];  {
				 = ()
				*.Header() = *

				// 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 .c.Peek().token == "\\#" {
					 = true
				}
			} else {
				 = &RFC3597{Hdr: *}
			}

			,  := .(*PrivateRR)
			if ! && .c.Peek().token == "" {
				// This is a dynamic update rr.

				if  := slurpRemainder(.c);  != nil {
					return .setParseError(.err, .lex)
				}

				return , true
			} else if .value == zNewline {
				return .setParseError("unexpected newline", )
			}

			 := 
			if  {
				 = &RFC3597{Hdr: *}
			}

			if  := .parse(.c, .origin);  != 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 .lex == (lex{}) {
					return .setParseError(.err, )
				}

				return .setParseError(.err, .lex)
			}

			if  {
				 := .(*RFC3597).fromRFC3597()
				if  != nil {
					return .setParseError(.Error(), )
				}
			}

			return , 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( io.Reader) *zlexer {
	,  := .(io.ByteReader)
	if ! {
		 = bufio.NewReaderSize(, 1024)
	}

	return &zlexer{
		br: ,

		line: 1,

		owner: true,
	}
}

func ( *zlexer) () error {
	if .readErr == io.EOF {
		return nil
	}

	return .readErr
}

// readByte returns the next byte from the input
func ( *zlexer) () (byte, bool) {
	if .readErr != nil {
		return 0, false
	}

	,  := .br.ReadByte()
	if  != nil {
		.readErr = 
		return 0, false
	}

	// delay the newline handling until the next token is delivered,
	// fixes off-by-one errors when reporting a parse error.
	if .eol {
		.line++
		.column = 0
		.eol = false
	}

	if  == '\n' {
		.eol = true
	} else {
		.column++
	}

	return , true
}

func ( *zlexer) () lex {
	if .nextL {
		return .l
	}

	,  := .Next()
	if ! {
		return 
	}

	if .nextL {
		// Cache l. Next returns zl.cachedL then zl.l.
		.cachedL = &
	} else {
		// In this case l == zl.l, so we just tell Next to return zl.l.
		.nextL = true
	}

	return 
}

func ( *zlexer) () (lex, bool) {
	 := &.l
	switch {
	case .cachedL != nil:
		, .cachedL = .cachedL, nil
		return *, true
	case .nextL:
		.nextL = false
		return *, true
	case .err:
		// Parsing errors should be sticky.
		return lex{value: zEOF}, false
	}

	var (
		 = make([]byte, maxTok) // Hold string text
		 = make([]byte, maxTok) // Hold comment text

		 int // Offset in str (0 means empty)
		 int // Offset in com (0 means empty)

		 bool
	)

	if .comBuf != "" {
		 = copy([:], .comBuf)
		.comBuf = ""
	}

	.comment = ""

	for ,  := .readByte(); ; ,  = .readByte() {
		.line, .column = .line, .column

		if  >= len() {
			// if buffer length is insufficient, increase it.
			 = append([:], make([]byte, maxTok)...)
		}
		if  >= len() {
			// if buffer length is insufficient, increase it.
			 = append([:], make([]byte, maxTok)...)
		}

		switch  {
		case ' ', '\t':
			if  || .quote {
				// Inside quotes or escaped this is legal.
				[] = 
				++

				 = false
				break
			}

			if .commt {
				[] = 
				++
				break
			}

			var  lex
			if  == 0 {
				// Space directly in the beginning, handled in the grammar
			} else if .owner {
				// If we have a string and it's the first, make it an owner
				.value = zOwner
				.token = string([:])

				// escape $... start with a \ not a $, so this will work
				switch strings.ToUpper(.token) {
				case "$TTL":
					.value = zDirTTL
				case "$ORIGIN":
					.value = zDirOrigin
				case "$INCLUDE":
					.value = zDirInclude
				case "$GENERATE":
					.value = zDirGenerate
				}

				 = *
			} else {
				.value = zString
				.token = string([:])

				if !.rrtype {
					 := strings.ToUpper(.token)
					if ,  := StringToType[];  {
						.value = zRrtpe
						.torc = 

						.rrtype = true
					} else if strings.HasPrefix(, "TYPE") {
						,  := typeToInt(.token)
						if ! {
							.token = "unknown RR type"
							.err = true
							return *, true
						}

						.value = zRrtpe
						.torc = 

						.rrtype = true
					}

					if ,  := StringToClass[];  {
						.value = zClass
						.torc = 
					} else if strings.HasPrefix(, "CLASS") {
						,  := classToInt(.token)
						if ! {
							.token = "unknown class"
							.err = true
							return *, true
						}

						.value = zClass
						.torc = 
					}
				}

				 = *
			}

			.owner = false

			if !.space {
				.space = true

				.value = zBlank
				.token = " "

				if  == (lex{}) {
					return *, true
				}

				.nextL = true
			}

			if  != (lex{}) {
				return , true
			}
		case ';':
			if  || .quote {
				// Inside quotes or escaped this is legal.
				[] = 
				++

				 = false
				break
			}

			.commt = true
			.comBuf = ""

			if  > 1 {
				// A newline was previously seen inside a comment that
				// was inside braces and we delayed adding it until now.
				[] = ' ' // convert newline to space
				++
				if  >= len() {
					.token = "comment length insufficient for parsing"
					.err = true
					return *, true
				}
			}

			[] = ';'
			++

			if  > 0 {
				.comBuf = string([:])

				.value = zString
				.token = string([:])
				return *, true
			}
		case '\r':
			 = false

			if .quote {
				[] = 
				++
			}

			// discard if outside of quotes
		case '\n':
			 = false

			// Escaped newline
			if .quote {
				[] = 
				++
				break
			}

			if .commt {
				// Reset a comment
				.commt = false
				.rrtype = false

				// If not in a brace this ends the comment AND the RR
				if .brace == 0 {
					.owner = true

					.value = zNewline
					.token = "\n"
					.comment = string([:])
					return *, true
				}

				.comBuf = string([:])
				break
			}

			if .brace == 0 {
				// If there is previous text, we should output it here
				var  lex
				if  != 0 {
					.value = zString
					.token = string([:])

					if !.rrtype {
						 := strings.ToUpper(.token)
						if ,  := StringToType[];  {
							.rrtype = true

							.value = zRrtpe
							.torc = 
						}
					}

					 = *
				}

				.value = zNewline
				.token = "\n"

				.comment = .comBuf
				.comBuf = ""
				.rrtype = false
				.owner = true

				if  != (lex{}) {
					.nextL = true
					return , true
				}

				return *, true
			}
		case '\\':
			// comments do not get escaped chars, everything is copied
			if .commt {
				[] = 
				++
				break
			}

			// something already escaped must be in string
			if  {
				[] = 
				++

				 = false
				break
			}

			// something escaped outside of string gets added to string
			[] = 
			++

			 = true
		case '"':
			if .commt {
				[] = 
				++
				break
			}

			if  {
				[] = 
				++

				 = false
				break
			}

			.space = false

			// send previous gathered text and the quote
			var  lex
			if  != 0 {
				.value = zString
				.token = string([:])

				 = *
			}

			// send quote itself as separate token
			.value = zQuote
			.token = "\""

			.quote = !.quote

			if  != (lex{}) {
				.nextL = true
				return , true
			}

			return *, true
		case '(', ')':
			if .commt {
				[] = 
				++
				break
			}

			if  || .quote {
				// Inside quotes or escaped this is legal.
				[] = 
				++

				 = false
				break
			}

			switch  {
			case ')':
				.brace--

				if .brace < 0 {
					.token = "extra closing brace"
					.err = true
					return *, true
				}
			case '(':
				.brace++
			}
		default:
			 = false

			if .commt {
				[] = 
				++
				break
			}

			[] = 
			++

			.space = false
		}
	}

	if .readErr != nil && .readErr != io.EOF {
		// Don't return any tokens after a read error occurs.
		return lex{value: zEOF}, false
	}

	var  lex
	if  > 0 {
		// Send remainder of str
		.value = zString
		.token = string([:])
		 = *

		if  <= 0 {
			return , true
		}
	}

	if  > 0 {
		// Send remainder of com
		.value = zNewline
		.token = "\n"
		.comment = string([:])

		if  != (lex{}) {
			.nextL = true
			return , true
		}

		return *, true
	}

	if .brace != 0 {
		.token = "unbalanced brace"
		.err = true
		return *, true
	}

	return lex{value: zEOF}, false
}

func ( *zlexer) () string {
	if .l.err {
		return ""
	}

	return .comment
}

// Extract the class number from CLASSxx
func classToInt( string) (uint16, bool) {
	 := 5
	if len() < +1 {
		return 0, false
	}
	,  := strconv.ParseUint([:], 10, 16)
	if  != nil {
		return 0, false
	}
	return uint16(), true
}

// Extract the rr number from TYPExxx
func typeToInt( string) (uint16, bool) {
	 := 4
	if len() < +1 {
		return 0, false
	}
	,  := strconv.ParseUint([:], 10, 16)
	if  != nil {
		return 0, false
	}
	return uint16(), true
}

// stringToTTL parses things like 2w, 2m, etc, and returns the time in seconds.
func stringToTTL( string) (uint32, bool) {
	var ,  uint32
	for ,  := range  {
		switch  {
		case 's', 'S':
			 += 
			 = 0
		case 'm', 'M':
			 +=  * 60
			 = 0
		case 'h', 'H':
			 +=  * 60 * 60
			 = 0
		case 'd', 'D':
			 +=  * 60 * 60 * 24
			 = 0
		case 'w', 'W':
			 +=  * 60 * 60 * 24 * 7
			 = 0
		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
			 *= 10
			 += uint32() - '0'
		default:
			return 0, false
		}
	}
	return  + , 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( string) (,  uint8,  bool) {
	if [len()-1] == 'M' || [len()-1] == 'm' {
		 = [0 : len()-1]
	}

	var (
		, ,  int
		                  error
	)
	, ,  := strings.Cut(, ".")
	if  {
		// 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.
		,  = strconv.Atoi()
		if  != nil || len() > 2 || [0] < '0' || [0] > '9' {
			return
		}
		if len() == 1 {
			// 'nn.1' must be treated as 'nn-meters and 10cm, not 1cm.
			 *= 10
		}
	}
	// This slightly ugly condition will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm).
	if ! ||  != "" {
		,  = strconv.Atoi()
		// RFC1876 states the max value is 90000000.00.  The latter two conditions enforce it.
		if  != nil || [0] < '0' || [0] > '9' ||  > 90000000 || ( == 90000000 &&  != 0) {
			return
		}
	}

	if  > 0 {
		 = 2
		 = 
	} else {
		 = 0
		 = 
	}
	for  >= 10 {
		++
		 /= 10
	}
	return , uint8(), true
}

func toAbsoluteName(,  string) ( string,  bool) {
	// check for an explicit origin reference
	if  == "@" {
		// require a nonempty origin
		if  == "" {
			return "", false
		}
		return , true
	}

	// this can happen when we have a comment after a RR that has a domain, '...   MX 20 ; this is wrong'.
	// technically a newline can be in a domain name, but this is clearly an error and the newline only shows
	// because of the scanning and the comment.
	if  == "\n" {
		return "", false
	}

	// require a valid domain name
	_,  = IsDomainName()
	if ! ||  == "" {
		return "", false
	}

	// check if name is already absolute
	if IsFqdn() {
		return , true
	}

	// require a nonempty origin
	if  == "" {
		return "", false
	}
	return appendOrigin(, ), true
}

func appendOrigin(,  string) string {
	if  == "." {
		return  + 
	}
	return  + "." + 
}

// LOC record helper function
func locCheckNorth( string,  uint32) (uint32, bool) {
	if  > 90*1000*60*60 {
		return , false
	}
	switch  {
	case "n", "N":
		return LOC_EQUATOR + , true
	case "s", "S":
		return LOC_EQUATOR - , true
	}
	return , false
}

// LOC record helper function
func locCheckEast( string,  uint32) (uint32, bool) {
	if  > 180*1000*60*60 {
		return , false
	}
	switch  {
	case "e", "E":
		return LOC_EQUATOR + , true
	case "w", "W":
		return LOC_EQUATOR - , true
	}
	return , false
}

// "Eat" the rest of the "line"
func slurpRemainder( *zlexer) *ParseError {
	,  := .Next()
	switch .value {
	case zBlank:
		, _ = .Next()
		if .value != zNewline && .value != zEOF {
			return &ParseError{err: "garbage after rdata", lex: }
		}
	case zNewline:
	case zEOF:
	default:
		return &ParseError{err: "garbage after rdata", lex: }
	}
	return nil
}

// Parse a 64 bit-like ipv6 address: "0014:4fff:ff20:ee64"
// Used for NID and L64 record.
func stringToNodeID( lex) (uint64, *ParseError) {
	if len(.token) < 19 {
		return 0, &ParseError{file: .token, err: "bad NID/L64 NodeID/Locator64", lex: }
	}
	// There must be three colons at fixes positions, if not its a parse error
	if .token[4] != ':' && .token[9] != ':' && .token[14] != ':' {
		return 0, &ParseError{file: .token, err: "bad NID/L64 NodeID/Locator64", lex: }
	}
	 := .token[0:4] + .token[5:9] + .token[10:14] + .token[15:19]
	,  := strconv.ParseUint(, 16, 64)
	if  != nil {
		return 0, &ParseError{file: .token, err: "bad NID/L64 NodeID/Locator64", lex: }
	}
	return , nil
}