9func TestParse(t *testing.T) {
10 intptr := func(v int) *int {
14 mustParseIP := func(s string) net.IP {
17 t.Fatalf("bad ip %q", s)
22 test := func(txt string, expRecord *Record) {
24 valid := expRecord != nil
25 r, _, err := ParseRecord(txt)
26 if valid && err != nil {
27 t.Fatalf("expected success, got err %s, txt %q", err, txt)
29 if !valid && err == nil {
30 t.Fatalf("expected error, got record %#v, txt %q", r, txt)
32 if valid && !reflect.DeepEqual(r, expRecord) {
33 t.Fatalf("unexpected record:\ngot: %v\nexpected: %v, txt %q", r, expRecord, txt)
38 test("v=spf1", &Record{Version: "spf1"})
39 test("v=SPF1", &Record{Version: "spf1"})
40 test("V=spf1 ", &Record{Version: "spf1"})
41 test("V=spf1 all Include:example.org a ?a -a +a ~a a:x a:x/0 a:x/24//64 a:x//64 mx mx:x ptr ptr:x IP4:10.0.0.1 ip4:0.0.0.0/0 ip4:10.0.0.1/24 ip6:2001:db8::1 ip6:2001:db8::1/128 exists:x REDIRECT=x exp=X Other=x",
44 Directives: []Directive{
46 {Mechanism: "include", DomainSpec: "example.org"},
48 {Qualifier: "?", Mechanism: "a"},
49 {Qualifier: "-", Mechanism: "a"},
50 {Qualifier: "+", Mechanism: "a"},
51 {Qualifier: "~", Mechanism: "a"},
52 {Mechanism: "a", DomainSpec: "x"},
53 {Mechanism: "a", DomainSpec: "x", IP4CIDRLen: intptr(0)},
54 {Mechanism: "a", DomainSpec: "x", IP4CIDRLen: intptr(24), IP6CIDRLen: intptr(64)},
55 {Mechanism: "a", DomainSpec: "x", IP6CIDRLen: intptr(64)},
57 {Mechanism: "mx", DomainSpec: "x"},
59 {Mechanism: "ptr", DomainSpec: "x"},
60 {Mechanism: "ip4", IP: mustParseIP("10.0.0.1"), IPstr: "10.0.0.1/32"},
61 {Mechanism: "ip4", IP: mustParseIP("0.0.0.0"), IPstr: "0.0.0.0/0", IP4CIDRLen: intptr(0)},
62 {Mechanism: "ip4", IP: mustParseIP("10.0.0.1"), IPstr: "10.0.0.1/24", IP4CIDRLen: intptr(24)},
63 {Mechanism: "ip6", IP: mustParseIP("2001:db8::1"), IPstr: "2001:db8::1/128"},
64 {Mechanism: "ip6", IP: mustParseIP("2001:db8::1"), IPstr: "2001:db8::1/128", IP6CIDRLen: intptr(128)},
65 {Mechanism: "exists", DomainSpec: "x"},
74 test("V=spf1 -all", &Record{Version: "spf1", Directives: []Directive{{Qualifier: "-", Mechanism: "all"}}})
75 test("v=spf1 !", nil) // Invalid character.
76 test("v=spf1 ?redirect=bogus", nil)
77 test("v=spf1 redirect=mox.example redirect=mox2.example", nil) // Duplicate redirect.
78 test("v=spf1 exp=mox.example exp=mox2.example", nil) // Duplicate exp.
79 test("v=spf1 ip4:10.0.0.256", nil) // Invalid address.
80 test("v=spf1 ip6:2001:db8:::1", nil) // Invalid address.
81 test("v=spf1 ip4:10.0.0.1/33", nil) // IPv4 prefix >32.
82 test("v=spf1 ip6:2001:db8::1/129", nil) // IPv6 prefix >128.
83 test("v=spf1 a:mox.example/33", nil) // IPv4 prefix >32.
84 test("v=spf1 a:mox.example//129", nil) // IPv6 prefix >128.
85 test("v=spf1 a:mox.example//129", nil) // IPv6 prefix >128.
86 test("v=spf1 exists:%%.%{l1r+}.%{d}",
89 Directives: []Directive{
90 {Mechanism: "exists", DomainSpec: "%%.%{l1r+}.%{d}"},
94 test("v=spf1 exists:%{l1r+}..", nil) // Empty toplabel in domain-end.
95 test("v=spf1 exists:%{l1r+}._.", nil) // Invalid toplabel in domain-end.
96 test("v=spf1 exists:%{l1r+}.123.", nil) // Invalid toplabel in domain-end.
97 test("v=spf1 exists:%{l1r+}.bad-.", nil) // Invalid toplabel in domain-end.
98 test("v=spf1 exists:%{l1r+}.-bad.", nil) // Invalid toplabel in domain-end.
99 test("v=spf1 exists:%{l1r+}./.", nil) // Invalid toplabel in domain-end.
100 test("v=spf1 exists:%{x}", nil) // Unknown macro-letter.
101 test("v=spf1 exists:%{s0}", nil) // Invalid digits.
102 test("v=spf1 exists:%{ir}.%{l1r+}.%{d}",
105 Directives: []Directive{
106 {Mechanism: "exists", DomainSpec: "%{ir}.%{l1r+}.%{d}"},
111 orig := `V=SPF1 all Include:example.org a ?a -a +a ~a a:x a:x/0 a:x/24//64 a:x//64 mx mx:x ptr ptr:x IP4:10.0.0.1 ip4:0.0.0.0/0 ip4:10.0.0.1/24 ip6:2001:db8::1 ip6:2001:db8::1/128 exists:x REDIRECT=x exp=X Other=x`
112 exp := `v=spf1 all include:example.org a ?a -a +a ~a a:x a:x/0 a:x/24//64 a:x//64 mx mx:x ptr ptr:x ip4:10.0.0.1 ip4:0.0.0.0/0 ip4:10.0.0.1/24 ip6:2001:db8::1 ip6:2001:db8::1/128 exists:x redirect=x exp=X Other=x`
113 r, _, err := ParseRecord(orig)
115 t.Fatalf("parsing original: %s", err)
117 record, err := r.Record()
119 t.Fatalf("making dns record: %s", err)
122 t.Fatalf("packing dns record, got %q, expected %q", record, exp)
126func FuzzParseRecord(f *testing.F) {
129 f.Add(`V=SPF1 all Include:example.org a ?a -a +a ~a a:x a:x/0 a:x/24//64 a:x//64 mx mx:x ptr ptr:x IP4:10.0.0.1 ip4:0.0.0.0/0 ip4:10.0.0.1/24 ip6:2001:db8::1 ip6:2001:db8::1/128 exists:x REDIRECT=x exp=X Other=x`)
130 f.Fuzz(func(t *testing.T, s string) {
131 r, _, err := ParseRecord(s)
133 if _, err := r.Record(); err != nil {
134 t.Errorf("r.Record for %s, %#v: %s", s, r, err)