| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366 |
- package dns
- import (
- "bufio"
- "fmt"
- "io"
- "os"
- "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
- lex lex
- }
- func (e *ParseError) Error() (s string) {
- if e.file != "" {
- s = e.file + ": "
- }
- s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " +
- strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column)
- return
- }
- 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 the RR contained in the string s. Only the first RR is returned.
- // If s contains no records, NewRR will return nil with no error.
- //
- // The class defaults to IN and TTL defaults to 3600. The full zone file syntax
- // like $TTL, $ORIGIN, etc. is supported. All fields of the returned RR are
- // set, 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
- osFile *os.File
- 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, "bad initial origin name", lex{}}
- }
- }
- 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
- }
- // 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{zp.file, err, 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.osFile != nil {
- zp.sub.osFile.Close()
- zp.sub.osFile = 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
- if !filepath.IsAbs(includePath) {
- includePath = filepath.Join(filepath.Dir(zp.file), includePath)
- }
- r1, e1 := os.Open(includePath)
- if e1 != nil {
- var as string
- if !filepath.IsAbs(l.token) {
- as = fmt.Sprintf(" as `%s'", includePath)
- }
- msg := fmt.Sprintf("failed to open `%s'%s: %v", l.token, as, e1)
- return zp.setParseError(msg, l)
- }
- zp.sub = NewZoneParser(r1, neworigin, includePath)
- zp.sub.defttl, zp.sub.includeDepth, zp.sub.osFile = zp.defttl, zp.includeDepth+1, r1
- zp.sub.SetIncludeAllowed(true)
- 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.
- // TODO(tmthrgd): Previously slurpRemainder was only called
- // for certain RR types, which may have been important.
- 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]
- }
- s := strings.SplitN(token, ".", 2)
- var meters, cmeters, val int
- var err error
- switch len(s) {
- case 2:
- if cmeters, err = strconv.Atoi(s[1]); err != nil {
- return
- }
- // 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.
- if len(s[1]) > 2 || s[1][0] < '0' || s[1][0] > '9' {
- return
- }
- if len(s[1]) == 1 {
- // 'nn.1' must be treated as 'nn-meters and 10cm, not 1cm.
- cmeters *= 10
- }
- if s[0] == "" {
- // This will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm).
- break
- }
- fallthrough
- case 1:
- if meters, err = strconv.Atoi(s[0]); err != nil {
- return
- }
- // RFC1876 states the max value is 90000000.00. The latter two conditions enforce it.
- if s[0][0] < '0' || s[0][0] > '9' || meters > 90000000 || (meters == 90000000 && cmeters != 0) {
- return
- }
- case 0:
- // huh?
- return 0, 0, false
- }
- ok = true
- if meters > 0 {
- e = 2
- val = meters
- } else {
- e = 0
- val = cmeters
- }
- for val >= 10 {
- e++
- val /= 10
- }
- m = uint8(val)
- return
- }
- 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{"", "garbage after rdata", l}
- }
- case zNewline:
- case zEOF:
- default:
- return &ParseError{"", "garbage after rdata", 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{l.token, "bad NID/L64 NodeID/Locator64", 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{l.token, "bad NID/L64 NodeID/Locator64", 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{l.token, "bad NID/L64 NodeID/Locator64", l}
- }
- return u, nil
- }
|