diff --git a/generate_test.go b/generate_test.go index 481c9b28..cd161c1f 100644 --- a/generate_test.go +++ b/generate_test.go @@ -62,22 +62,23 @@ $GENERATE 0-1/0 dhcp-${0,4,d} A 10.0.0.$ {`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d ) $GENERATE 0-1 $$INCLUDE ` + tmpdir + string(filepath.Separator) + `${0,4,d}.conf `, false}, -{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d ) + {`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d ) $GENERATE 0-1 dhcp-${0,4,d} A 10.0.0.$ $GENERATE 0-2 dhcp-${0,4,d} A 10.1.0.$ `, false}, } -Outer: + for i := range tests { - for tok := range ParseZone(strings.NewReader(tests[i].zone), "test.", "test") { - if tok.Error != nil { - if !tests[i].fail { - t.Errorf("expected \n\n%s\nto be parsed, but got %v", tests[i].zone, tok.Error) - } - continue Outer - } + z := NewZoneParser(strings.NewReader(tests[i].zone), "test.", "test") + z.SetIncludeAllowed(true) + + for _, ok := z.Next(); ok; _, ok = z.Next() { } - if tests[i].fail { + + err := z.Err() + if err != nil && !tests[i].fail { + t.Errorf("expected \n\n%s\nto be parsed, but got %v", tests[i].zone, err) + } else if err == nil && tests[i].fail { t.Errorf("expected \n\n%s\nto fail, but got no error", tests[i].zone) } } diff --git a/parse_test.go b/parse_test.go index 93129fa9..a6b82757 100644 --- a/parse_test.go +++ b/parse_test.go @@ -546,25 +546,25 @@ $TTL 314 example.com. DNAME 10 ; TTL=314 after second $TTL ` reCaseFromComment := regexp.MustCompile(`TTL=(\d+)\s+(.*)`) - records := ParseZone(strings.NewReader(zone), "", "") + z := NewZoneParser(strings.NewReader(zone), "", "") var i int - for record := range records { + + for rr, ok := z.Next(); ok; rr, ok = z.Next() { i++ - if record.Error != nil { - t.Error(record.Error) - continue - } - expected := reCaseFromComment.FindStringSubmatch(record.Comment) + expected := reCaseFromComment.FindStringSubmatch(z.Comment()) if len(expected) != 3 { t.Errorf("regexp didn't match for record %d", i) continue } expectedTTL, _ := strconv.ParseUint(expected[1], 10, 32) - ttl := record.RR.Header().Ttl + ttl := rr.Header().Ttl if ttl != uint32(expectedTTL) { t.Errorf("%s: expected TTL %d, got %d", expected[2], expectedTTL, ttl) } } + if err := z.Err(); err != nil { + t.Error(err) + } if i != 10 { t.Errorf("expected %d records, got %d", 5, i) } @@ -603,16 +603,12 @@ func TestRelativeNameErrors(t *testing.T) { }, } for _, errorCase := range badZones { - entries := ParseZone(strings.NewReader(errorCase.zoneContents), "", "") - for entry := range entries { - if entry.Error == nil { - t.Errorf("%s: expected error, got nil", errorCase.label) - continue - } - err := entry.Error.err - if err != errorCase.expectedErr { - t.Errorf("%s: expected error `%s`, got `%s`", errorCase.label, errorCase.expectedErr, err) - } + z := NewZoneParser(strings.NewReader(errorCase.zoneContents), "", "") + z.Next() + if err := z.Err(); err == nil { + t.Errorf("%s: expected error, got nil", errorCase.label) + } else if !strings.Contains(err.Error(), errorCase.expectedErr) { + t.Errorf("%s: expected error `%s`, got `%s`", errorCase.label, errorCase.expectedErr, err) } } } @@ -719,9 +715,13 @@ func TestRfc1982(t *testing.T) { } func TestEmpty(t *testing.T) { - for range ParseZone(strings.NewReader(""), "", "") { + z := NewZoneParser(strings.NewReader(""), "", "") + for _, ok := z.Next(); ok; _, ok = z.Next() { t.Errorf("should be empty") } + if err := z.Err(); err != nil { + t.Error("got an error when it shouldn't") + } } func TestLowercaseTokens(t *testing.T) { @@ -889,18 +889,20 @@ foo. IN DNSKEY 256 3 5 AwEAAb+8l ; this is comment 6 foo. IN NSEC miek.nl. TXT RRSIG NSEC; this is comment 7 foo. IN TXT "THIS IS TEXT MAN"; this is comment 8 ` - for x := range ParseZone(strings.NewReader(zone), ".", "") { - if x.Error == nil { - if x.Comment != "" { - if _, ok := comments[x.Comment]; !ok { - t.Errorf("wrong comment %q", x.Comment) - } + z := NewZoneParser(strings.NewReader(zone), ".", "") + for _, ok := z.Next(); ok; _, ok = z.Next() { + if z.Comment() != "" { + if _, okC := comments[z.Comment()]; !okC { + t.Errorf("wrong comment %q", z.Comment()) } } } + if err := z.Err(); err != nil { + t.Error("got an error when it shouldn't") + } } -func TestParseZoneComments(t *testing.T) { +func TestZoneParserComments(t *testing.T) { for i, test := range []struct { zone string comments []string @@ -975,24 +977,25 @@ func TestParseZoneComments(t *testing.T) { r := strings.NewReader(test.zone) var j int - for r := range ParseZone(r, "", "") { - if r.Error != nil { - t.Fatal(r.Error) - } - + z := NewZoneParser(r, "", "") + for rr, ok := z.Next(); ok; rr, ok = z.Next() { if j >= len(test.comments) { t.Fatalf("too many records for zone %d at %d record, expected %d", i, j+1, len(test.comments)) } - if r.Comment != test.comments[j] { - t.Errorf("invalid comment for record %d:%d %v / %v", i, j, r.RR, r.Error) + if z.Comment() != test.comments[j] { + t.Errorf("invalid comment for record %d:%d %v", i, j, rr) t.Logf("expected %q", test.comments[j]) - t.Logf("got %q", r.Comment) + t.Logf("got %q", z.Comment()) } j++ } + if err := z.Err(); err != nil { + t.Fatal(err) + } + if j != len(test.comments) { t.Errorf("too few records for zone %d, got %d, expected %d", i, j, len(test.comments)) } diff --git a/privaterr_test.go b/privaterr_test.go index fa5ff48f..d362b891 100644 --- a/privaterr_test.go +++ b/privaterr_test.go @@ -158,9 +158,11 @@ func TestPrivateZoneParser(t *testing.T) { defer dns.PrivateHandleRemove(TypeVERSION) r := strings.NewReader(smallzone) - for x := range dns.ParseZone(r, ".", "") { - if err := x.Error; err != nil { - t.Fatal(err) - } + z := dns.NewZoneParser(r, ".", "") + + for _, ok := z.Next(); ok; _, ok = z.Next() { + } + if err := z.Err(); err != nil { + t.Fatal(err) } } diff --git a/scan.go b/scan.go index e52f43c9..e18566fc 100644 --- a/scan.go +++ b/scan.go @@ -87,16 +87,6 @@ type lex struct { column int // column in the file } -// Token holds the token that are returned when a zone file is parsed. -type Token struct { - // The scanned resource record when error is not nil. - RR - // When an error occurred, this has the error specifics. - Error *ParseError - // A potential comment positioned after the RR and on the same line. - Comment string -} - // ttlState describes the state necessary to fill in an omitted RR TTL type ttlState struct { ttl uint32 // ttl is the current default TTL @@ -130,70 +120,6 @@ func ReadRR(r io.Reader, file string) (RR, error) { return rr, zp.Err() } -// ParseZone reads a RFC 1035 style zonefile from r. It returns -// Tokens on the returned channel, each consisting of either a -// parsed RR and optional comment or a nil RR and an error. The -// channel is closed by ParseZone when the end of r is reached. -// -// 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. -// -// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are all -// supported. Note that $GENERATE's range support up to a maximum of -// of 65535 steps. -// -// Basic usage pattern when reading from a string (z) containing the -// zone data: -// -// for x := range dns.ParseZone(strings.NewReader(z), "", "") { -// if x.Error != nil { -// // log.Println(x.Error) -// } else { -// // Do something with x.RR -// } -// } -// -// 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 in Token.Comment. -// Comments inside the RR are returned concatenated along with the -// RR. Comments on a line by themselves are discarded. -// -// To prevent memory leaks it is important to always fully drain the -// returned channel. If an error occurs, it will always be the last -// Token sent on the channel. -// -// Deprecated: New users should prefer the ZoneParser API. -func ParseZone(r io.Reader, origin, file string) chan *Token { - t := make(chan *Token, 10000) - go parseZone(r, origin, file, t) - return t -} - -func parseZone(r io.Reader, origin, file string, t chan *Token) { - defer close(t) - - zp := NewZoneParser(r, origin, file) - zp.SetIncludeAllowed(true) - - for rr, ok := zp.Next(); ok; rr, ok = zp.Next() { - t <- &Token{RR: rr, Comment: zp.Comment()} - } - - if err := zp.Err(); err != nil { - pe, ok := err.(*ParseError) - if !ok { - pe = &ParseError{file: file, err: err.Error()} - } - - t <- &Token{Error: pe} - } -} - // ZoneParser is a parser for an RFC 1035 style zonefile. // // Each parsed RR in the zone is returned sequentially from Next. An diff --git a/scan_test.go b/scan_test.go index 327ba3a2..578deba2 100644 --- a/scan_test.go +++ b/scan_test.go @@ -9,7 +9,7 @@ import ( "testing" ) -func TestParseZoneGenerate(t *testing.T) { +func TestZoneParserGenerate(t *testing.T) { zone := "$ORIGIN example.org.\n$GENERATE 10-12 foo${2,3,d} IN A 127.0.0.$" wantRRs := []RR{ @@ -17,22 +17,21 @@ func TestParseZoneGenerate(t *testing.T) { &A{Hdr: RR_Header{Name: "foo013.example.org."}, A: net.ParseIP("127.0.0.11")}, &A{Hdr: RR_Header{Name: "foo014.example.org."}, A: net.ParseIP("127.0.0.12")}, } + wantIdx := 0 - tok := ParseZone(strings.NewReader(zone), "", "") - for x := range tok { + z := NewZoneParser(strings.NewReader(zone), "", "") + + for rr, ok := z.Next(); ok; rr, ok = z.Next() { if wantIdx >= len(wantRRs) { t.Fatalf("expected %d RRs, but got more", len(wantRRs)) } - if x.Error != nil { - t.Fatalf("expected no error, but got %s", x.Error) - } - if got, want := x.RR.Header().Name, wantRRs[wantIdx].Header().Name; got != want { + if got, want := rr.Header().Name, wantRRs[wantIdx].Header().Name; got != want { t.Fatalf("expected name %s, but got %s", want, got) } - a, ok := x.RR.(*A) - if !ok { - t.Fatalf("expected *A RR, but got %T", x.RR) + a, okA := rr.(*A) + if !okA { + t.Fatalf("expected *A RR, but got %T", rr) } if got, want := a.A, wantRRs[wantIdx].(*A).A; !got.Equal(want) { t.Fatalf("expected A with IP %v, but got %v", got, want) @@ -40,12 +39,16 @@ func TestParseZoneGenerate(t *testing.T) { wantIdx++ } + if err := z.Err(); err != nil { + t.Fatalf("expected no error, but got %s", err) + } + if wantIdx != len(wantRRs) { t.Errorf("too few records, expected %d, got %d", len(wantRRs), wantIdx) } } -func TestParseZoneInclude(t *testing.T) { +func TestZoneParserInclude(t *testing.T) { tmpfile, err := ioutil.TempFile("", "dns") if err != nil { @@ -63,18 +66,19 @@ func TestParseZoneInclude(t *testing.T) { zone := "$ORIGIN example.org.\n$INCLUDE " + tmpfile.Name() + "\nbar\tIN\tA\t127.0.0.2" var got int - tok := ParseZone(strings.NewReader(zone), "", "") - for x := range tok { - if x.Error != nil { - t.Fatalf("expected no error, but got %s", x.Error) - } - switch x.RR.Header().Name { + z := NewZoneParser(strings.NewReader(zone), "", "") + z.SetIncludeAllowed(true) + for rr, ok := z.Next(); ok; _, ok = z.Next() { + switch rr.Header().Name { case "foo.example.org.", "bar.example.org.": default: - t.Fatalf("expected foo.example.org. or bar.example.org., but got %s", x.RR.Header().Name) + t.Fatalf("expected foo.example.org. or bar.example.org., but got %s", rr.Header().Name) } got++ } + if err := z.Err(); err != nil { + t.Fatalf("expected no error, but got %s", err) + } if expected := 2; got != expected { t.Errorf("failed to parse zone after include, expected %d records, got %d", expected, got) @@ -82,17 +86,15 @@ func TestParseZoneInclude(t *testing.T) { os.Remove(tmpfile.Name()) - tok = ParseZone(strings.NewReader(zone), "", "") - for x := range tok { - if x.Error == nil { - t.Fatalf("expected first token to contain an error but it didn't") - } - if !strings.Contains(x.Error.Error(), "failed to open") || - !strings.Contains(x.Error.Error(), tmpfile.Name()) || - !strings.Contains(x.Error.Error(), "no such file or directory") { - t.Fatalf(`expected error to contain: "failed to open", %q and "no such file or directory" but got: %s`, - tmpfile.Name(), x.Error) - } + z = NewZoneParser(strings.NewReader(zone), "", "") + z.SetIncludeAllowed(true) + z.Next() + if err := z.Err(); err == nil || + !strings.Contains(err.Error(), "failed to open") || + !strings.Contains(err.Error(), tmpfile.Name()) || + !strings.Contains(err.Error(), "no such file or directory") { + t.Fatalf(`expected error to contain: "failed to open", %q and "no such file or directory" but got: %s`, + tmpfile.Name(), err) } } @@ -274,16 +276,6 @@ foo. IN NSEC miek.nl. TXT RRSIG NSEC; this is comment 7 foo. IN TXT "THIS IS TEXT MAN"; this is comment 8 ` -func BenchmarkParseZone(b *testing.B) { - for n := 0; n < b.N; n++ { - for tok := range ParseZone(strings.NewReader(benchZone), "example.org.", "") { - if tok.Error != nil { - b.Fatal(tok.Error) - } - } - } -} - func BenchmarkZoneParser(b *testing.B) { for n := 0; n < b.N; n++ { zp := NewZoneParser(strings.NewReader(benchZone), "example.org.", "")