package namedconf import ( "strings" "testing" ) const sample = ` // Debian style config include "/etc/bind/rndc.key"; # shell comment /* block comment */ acl "trusted" { 192.168.1.0/24; !192.168.1.66; key "xfer-key"; { 10/8; }; }; controls { inet 127.0.0.1 port 953 allow { 127.0.0.1; } keys { "rndc-key"; }; }; options { directory "/var/cache/bind"; allow-query { any; }; listen-on-v6 { "none"; }; forwarders port 53 { 1.1.1.1; 2606:4700:4700::1111 port 53; }; version "not \"telling\""; }; zone "example.com" IN { type primary; file "/etc/bind/db.example.com"; }; ` func TestParseAndFormat(t *testing.T) { stmts, err := Parse("named.conf", sample) if err != nil { t.Fatal(err) } if len(stmts) != 5 { t.Fatalf("got %d statements", len(stmts)) } if stmts[0].Keyword() != "include" || stmts[0].Arg(0) != "/etc/bind/rndc.key" { t.Errorf("include parsed wrong: %+v", stmts[0]) } acl := stmts[1] if acl.Keyword() != "acl" || acl.Arg(0) != "trusted" { t.Errorf("acl parsed wrong") } elems, err := ParseAMList(acl.FirstBlock()) if err != nil { t.Fatal(err) } var got []string for _, e := range elems { got = append(got, e.String()) } want := "192.168.1.0/24|!192.168.1.66|key xfer-key|{ 10.0.0.0/8; }" if strings.Join(got, "|") != want { t.Errorf("acl elements = %q want %q", strings.Join(got, "|"), want) } opts := FindOne(stmts, "options") ver := FindOne(opts.FirstBlock().Stmts, "version") if ver.Arg(0) != `not "telling"` { t.Errorf("escaped string = %q", ver.Arg(0)) } out := Format(stmts) // Re-parse the output and format again: must be stable. stmts2, err := Parse("out", out) if err != nil { t.Fatalf("re-parse: %v\n%s", err, out) } if out2 := Format(stmts2); out2 != out { t.Errorf("format not stable:\n%s\n---\n%s", out, out2) } if !strings.Contains(out, `version "not \"telling\"";`) { t.Errorf("quoted escape lost:\n%s", out) } z := FindOne(stmts, "zone") if z.Arg(0) != "example.com" || z.Arg(1) != "IN" || FindOne(z.FirstBlock().Stmts, "type").Arg(0) != "primary" { t.Errorf("zone parsed wrong") } if line := z.Pos.Line; line != 17 { t.Errorf("zone line = %d", line) } } func TestParseErrors(t *testing.T) { bad := map[string]string{ `options { directory "x" };`: "missing ';' before '}'", `options { directory "x"; `: "missing '}'", `zone "x" { type primary; }`: "missing ';' at end of statement", `};`: "unexpected '}'", `options { version "abc; };`: "unterminated quoted string", `/* never closed`: "unterminated /* comment", } for src, want := range bad { _, err := Parse("t", src) if err == nil || !strings.Contains(err.Error(), want) { t.Errorf("Parse(%q) error = %v, want %q", src, err, want) } } } func TestAMElements(t *testing.T) { good := map[string]string{ "192.168.1.0/24": "192.168.1.0/24", "!10.0.0.1": "!10.0.0.1", "! 10.0.0.1": "!10.0.0.1", "10/8": "10.0.0.0/8", "192.168/16": "192.168.0.0/16", "2001:db8::/32": "2001:db8::/32", "key xfer": "key xfer", `key "rndc-key"`: "key rndc-key", "any": "any", `"none"`: "none", "LocalHost": "localhost", "trusted": "trusted", "{ 1.2.3.4; any; }": "{ 1.2.3.4; any; }", "10.0.0.5/32": "10.0.0.5", } for in, want := range good { e, err := ParseAMElement(in) if err != nil { t.Errorf("ParseAMElement(%q): %v", in, err) continue } if e.String() != want { t.Errorf("ParseAMElement(%q) = %q want %q", in, e.String(), want) } } bad := []string{"10.0.0.5/8", "300.1.1.1", "10.1", "key", "1.2.3.4 5.6.7.8", "2001:db8::1/200", "fe80::1%eth0", "bad name!"} for _, in := range bad { if e, err := ParseAMElement(in); err == nil { t.Errorf("ParseAMElement(%q) = %q, want error", in, e.String()) } } e, err := ParseAMElement("10.0.0.5/8") if err == nil || !strings.Contains(err.Error(), "did you mean 10.0.0.0/8") { t.Errorf("host bits error = %v (%v)", err, e) } } func TestShadowedAndRefs(t *testing.T) { elems, err := ParseAMListString("192.168.1.0/24; !192.168.1.13; trusted; key k1; { key k2; other; }") if err != nil { t.Fatal(err) } w := Shadowed(elems) if len(w) != 1 || !strings.Contains(w[0], "192.168.1.13") { t.Errorf("Shadowed = %v", w) } acls, keys := AMRefs(elems) if strings.Join(acls, ",") != "trusted,other" || strings.Join(keys, ",") != "k1,k2" { t.Errorf("AMRefs = %v %v", acls, keys) } anyList, _ := ParseAMListString("{ any; }") if !ContainsAny(anyList) { t.Error("ContainsAny(any) false") } neg, _ := ParseAMListString("!10.0.0.1; any") if ContainsAny(neg) { t.Error("ContainsAny with leading negation true") } if w := Shadowed(anyList); len(w) != 0 { t.Errorf("unexpected %v", w) } w2, _ := ParseAMListString("any; 10.0.0.0/8") if len(Shadowed(w2)) != 1 { t.Error("element after any not reported") } } func TestParseOptionValue(t *testing.T) { st, err := ParseOptionValue("allow-query", "{ 10.0.0.0/8; localhost; };") if err != nil { t.Fatal(err) } if got := CompactStmt(st); got != "allow-query { 10.0.0.0/8; localhost; };" { t.Errorf("compact = %q", got) } if _, err := ParseOptionValue("allow-query", "{ 10.0.0.0/8; "); err == nil { t.Error("unbalanced accepted") } if _, err := ParseOptionValue("recursion", "yes; recursion no"); err == nil { t.Error("two statements accepted") } if _, err := ParseOptionValue("recursion", ""); err == nil { t.Error("empty accepted") } } func TestFormatLayout(t *testing.T) { stmts, _ := Parse("", `options { allow-recursion { 10.0.0.0/8; 172.16.0.0/12; 192.168.0.0/16; 100.64.0.0/10; fd00::/8; localhost; }; recursion yes; };`) out := Format(stmts) want := "options {\n\tallow-recursion {\n\t\t10.0.0.0/8;\n\t\t172.16.0.0/12;\n\t\t192.168.0.0/16;\n\t\t100.64.0.0/10;\n\t\tfd00::/8;\n\t\tlocalhost;\n\t};\n\trecursion yes;\n};\n" if out != want { t.Errorf("layout:\n%s\nwant:\n%s", out, want) } }