package dnsimport ()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.typeParseErrorstruct { 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 TTLtype 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) {iflen() > 0 && [len()-1] != '\n' { // We need a closing newlinereturnReadRR(strings.NewReader(+"\n"), "") }returnReadRR(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.typeZoneParserstruct { 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 *ParseErrorif != "" { = 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:125func ( *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: }returnnil, 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.returnnil, false } .sub = nilreturn .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 {returnnil, 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 := &.hfor , := .c.Next(); ; , = .c.Next() {// zlexer spotted an error alreadyif .err {return .setParseError(.token, ) }switch {casezExpectOwnerDir:// We can also expect a directive, like $TTL or $ORIGINif .defttl != nil { .Ttl = .defttl.ttl } .Class = ClassINETswitch .value {casezNewline: = zExpectOwnerDircasezOwner: , := toAbsoluteName(.token, .origin)if ! {return .setParseError("bad owner name", ) } .Name = = zExpectOwnerBlcasezDirTTL: = zExpectDirTTLBlcasezDirOrigin: = zExpectDirOriginBlcasezDirInclude: = zExpectDirIncludeBlcasezDirGenerate: = zExpectDirGenerateBlcasezRrtpe: .Rrtype = .torc = zExpectRdatacasezClass: .Class = .torc = zExpectAnyNoClassBlcasezBlank:// Discard, can happen when there is nothing on the // line except the RR typecasezString: , := stringToTTL(.token)if ! {return .setParseError("not a TTL", ) } .Ttl = if .defttl == nil || !.defttl.isByDirective { .defttl = &ttlState{, false} } = zExpectAnyNoTTLBldefault:return .setParseError("syntax error at beginning", ) }casezExpectDirIncludeBl:if .value != zBlank {return .setParseError("no blank after $INCLUDE-directive", ) } = zExpectDirIncludecasezExpectDirInclude: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 oneswitch , := .c.Next(); .value {casezBlank: , := .c.Next()if .value == zString { , := toAbsoluteName(.token, .origin)if ! {return .setParseError("bad origin name", ) } = }casezNewline, zEOF:// Okdefault: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 := .tokenvario.Readervarerrorif .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 {varstringif != .token { = fmt.Sprintf(" as `%s'", ) } .parseErr = &ParseError{file: .file,wrappedErr: fmt.Errorf("failed to open `%s'%s: %w", .token, , ),lex: , }returnnil, false } .sub = NewZoneParser(, , ) .sub.defttl, .sub.includeDepth, .sub.r = .defttl, .includeDepth+1, .sub.SetIncludeAllowed(true) .sub.SetIncludeFS(.fsys)return .subNext()casezExpectDirTTLBl:if .value != zBlank {return .setParseError("no blank after $TTL-directive", ) } = zExpectDirTTLcasezExpectDirTTL: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} = zExpectOwnerDircasezExpectDirOriginBl:if .value != zBlank {return .setParseError("no blank after $ORIGIN-directive", ) } = zExpectDirOrigincasezExpectDirOrigin: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 = = zExpectOwnerDircasezExpectDirGenerateBl:if .value != zBlank {return .setParseError("no blank after $GENERATE-directive", ) } = zExpectDirGeneratecasezExpectDirGenerate:if .generateDisallowed {return .setParseError("nested $GENERATE directive not allowed", ) }if .value != zString {return .setParseError("expecting $GENERATE value, not this...", ) }return .generate()casezExpectOwnerBl:if .value != zBlank {return .setParseError("no blank after owner", ) } = zExpectAnycasezExpectAny:switch .value {casezRrtpe:if .defttl == nil {return .setParseError("missing TTL with no previous value", ) } .Rrtype = .torc = zExpectRdatacasezClass: .Class = .torc = zExpectAnyNoClassBlcasezString: , := stringToTTL(.token)if ! {return .setParseError("not a TTL", ) } .Ttl = if .defttl == nil || !.defttl.isByDirective { .defttl = &ttlState{, false} } = zExpectAnyNoTTLBldefault:return .setParseError("expecting RR type, TTL or class, not this...", ) }casezExpectAnyNoClassBl:if .value != zBlank {return .setParseError("no blank before class", ) } = zExpectAnyNoClasscasezExpectAnyNoTTLBl:if .value != zBlank {return .setParseError("no blank before TTL", ) } = zExpectAnyNoTTLcasezExpectAnyNoTTL:switch .value {casezClass: .Class = .torc = zExpectRrtypeBlcasezRrtpe: .Rrtype = .torc = zExpectRdatadefault:return .setParseError("expecting RR type or class, not this...", ) }casezExpectAnyNoClass:switch .value {casezString: , := stringToTTL(.token)if ! {return .setParseError("not a TTL", ) } .Ttl = if .defttl == nil || !.defttl.isByDirective { .defttl = &ttlState{, false} } = zExpectRrtypeBlcasezRrtpe: .Rrtype = .torc = zExpectRdatadefault:return .setParseError("expecting RR type or TTL, not this...", ) }casezExpectRrtypeBl:if .value != zBlank {return .setParseError("no blank before RR type", ) } = zExpectRrtypecasezExpectRrtype:if .value != zRrtpe {return .setParseError("unknown RR type", ) } .Rrtype = .torc = zExpectRdatacasezExpectRdata:var (RRbool )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 } elseif .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.returnnil, 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 {returnnil }return .readErr}// readByte returns the next byte from the inputfunc ( *zlexer) () (byte, bool) {if .readErr != nil {return0, false } , := .br.ReadByte()if != nil { .readErr = return0, 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) { := &.lswitch {case .cachedL != nil: , .cachedL = .cachedL, nilreturn *, truecase .nextL: .nextL = falsereturn *, truecase .err:// Parsing errors should be sticky.returnlex{value: zEOF}, false }var ( = make([]byte, maxTok) // Hold string text = make([]byte, maxTok) // Hold comment textint// 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, .columnif >= 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. [] = ++ = falsebreak }if .commt { [] = ++break }varlexif == 0 {// Space directly in the beginning, handled in the grammar } elseif .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 workswitchstrings.ToUpper(.token) {case"$TTL": .value = zDirTTLcase"$ORIGIN": .value = zDirOrigincase"$INCLUDE": .value = zDirIncludecase"$GENERATE": .value = zDirGenerate } = * } else { .value = zString .token = string([:])if !.rrtype { := strings.ToUpper(.token)if , := StringToType[]; { .value = zRrtpe .torc = .rrtype = true } elseifstrings.HasPrefix(, "TYPE") { , := typeToInt(.token)if ! { .token = "unknown RR type" .err = truereturn *, true } .value = zRrtpe .torc = .rrtype = true }if , := StringToClass[]; { .value = zClass .torc = } elseifstrings.HasPrefix(, "CLASS") { , := classToInt(.token)if ! { .token = "unknown class" .err = truereturn *, true } .value = zClass .torc = } } = * } .owner = falseif !.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. [] = ++ = falsebreak } .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 = truereturn *, true } } [] = ';' ++if > 0 { .comBuf = string([:]) .value = zString .token = string([:])return *, true }case'\r': = falseif .quote { [] = ++ }// discard if outside of quotescase'\n': = false// Escaped newlineif .quote { [] = ++break }if .commt {// Reset a comment .commt = false .rrtype = false// If not in a brace this ends the comment AND the RRif .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 herevarlexif != 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 = trueif != (lex{}) { .nextL = truereturn , true }return *, true }case'\\':// comments do not get escaped chars, everything is copiedif .commt { [] = ++break }// something already escaped must be in stringif { [] = ++ = falsebreak }// something escaped outside of string gets added to string [] = ++ = truecase'"':if .commt { [] = ++break }if { [] = ++ = falsebreak } .space = false// send previous gathered text and the quotevarlexif != 0 { .value = zString .token = string([:]) = * }// send quote itself as separate token .value = zQuote .token = "\"" .quote = !.quoteif != (lex{}) { .nextL = truereturn , true }return *, truecase'(', ')':if .commt { [] = ++break }if || .quote {// Inside quotes or escaped this is legal. [] = ++ = falsebreak }switch {case')': .brace--if .brace < 0 { .token = "extra closing brace" .err = truereturn *, true }case'(': .brace++ }default: = falseif .commt { [] = ++break } [] = ++ .space = false } }if .readErr != nil && .readErr != io.EOF {// Don't return any tokens after a read error occurs.returnlex{value: zEOF}, false }varlexif > 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 = truereturn , true }return *, true }if .brace != 0 { .token = "unbalanced brace" .err = truereturn *, true }returnlex{value: zEOF}, false}func ( *zlexer) () string {if .l.err {return"" }return .comment}// Extract the class number from CLASSxxfunc classToInt( string) (uint16, bool) { := 5iflen() < +1 {return0, false } , := strconv.ParseUint([:], 10, 16)if != nil {return0, false }returnuint16(), true}// Extract the rr number from TYPExxxfunc typeToInt( string) (uint16, bool) { := 4iflen() < +1 {return0, false } , := strconv.ParseUint([:], 10, 16)if != nil {return0, false }returnuint16(), true}// stringToTTL parses things like 2w, 2m, etc, and returns the time in seconds.func stringToTTL( string) (uint32, bool) {var , uint32for , := range {switch {case's', 'S': += = 0case'm', 'M': += * 60 = 0case'h', 'H': += * 60 * 60 = 0case'd', 'D': += * 60 * 60 * 24 = 0case'w', 'W': += * 60 * 60 * 24 * 7 = 0case'0', '1', '2', '3', '4', '5', '6', '7', '8', '9': *= 10 += uint32() - '0'default:return0, 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 ( , , interror ) , , := 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 }iflen() == 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 referenceif == "@" {// require a nonempty originif == "" {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 absoluteifIsFqdn() {return , true }// require a nonempty originif == "" {return"", false }returnappendOrigin(, ), true}func appendOrigin(, string) string {if == "." {return + }return + "." + }// LOC record helper functionfunc locCheckNorth( string, uint32) (uint32, bool) {if > 90*1000*60*60 {return , false }switch {case"n", "N":returnLOC_EQUATOR + , truecase"s", "S":returnLOC_EQUATOR - , true }return , false}// LOC record helper functionfunc locCheckEast( string, uint32) (uint32, bool) {if > 180*1000*60*60 {return , false }switch {case"e", "E":returnLOC_EQUATOR + , truecase"w", "W":returnLOC_EQUATOR - , true }return , false}// "Eat" the rest of the "line"func slurpRemainder( *zlexer) *ParseError { , := .Next()switch .value {casezBlank: , _ = .Next()if .value != zNewline && .value != zEOF {return &ParseError{err: "garbage after rdata", lex: } }casezNewline:casezEOF:default:return &ParseError{err: "garbage after rdata", lex: } }returnnil}// Parse a 64 bit-like ipv6 address: "0014:4fff:ff20:ee64"// Used for NID and L64 record.func stringToNodeID( lex) (uint64, *ParseError) {iflen(.token) < 19 {return0, &ParseError{file: .token, err: "bad NID/L64 NodeID/Locator64", lex: } }// There must be three colons at fixes positions, if not its a parse errorif .token[4] != ':' && .token[9] != ':' && .token[14] != ':' {return0, &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 {return0, &ParseError{file: .token, err: "bad NID/L64 NodeID/Locator64", lex: } }return , nil}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.