mox/spf/parse_test.go

139 lines
5.3 KiB
Go
Raw Normal View History

2023-01-30 16:27:06 +03:00
package spf
import (
"net"
"reflect"
"testing"
)
func TestParse(t *testing.T) {
intptr := func(v int) *int {
return &v
}
mustParseIP := func(s string) net.IP {
ip := net.ParseIP(s)
if ip == nil {
t.Fatalf("bad ip %q", s)
}
return ip
}
test := func(txt string, expRecord *Record) {
t.Helper()
valid := expRecord != nil
r, _, err := ParseRecord(txt)
if valid && err != nil {
t.Fatalf("expected success, got err %s, txt %q", err, txt)
}
if !valid && err == nil {
t.Fatalf("expected error, got record %#v, txt %q", r, txt)
}
if valid && !reflect.DeepEqual(r, expRecord) {
t.Fatalf("unexpected record:\ngot: %v\nexpected: %v, txt %q", r, expRecord, txt)
}
}
test("", nil)
test("v=spf1", &Record{Version: "spf1"})
test("v=SPF1", &Record{Version: "spf1"})
test("V=spf1 ", &Record{Version: "spf1"})
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",
&Record{
Version: "spf1",
Directives: []Directive{
{Mechanism: "all"},
{Mechanism: "include", DomainSpec: "example.org"},
{Mechanism: "a"},
{Qualifier: "?", Mechanism: "a"},
{Qualifier: "-", Mechanism: "a"},
{Qualifier: "+", Mechanism: "a"},
{Qualifier: "~", Mechanism: "a"},
{Mechanism: "a", DomainSpec: "x"},
{Mechanism: "a", DomainSpec: "x", IP4CIDRLen: intptr(0)},
{Mechanism: "a", DomainSpec: "x", IP4CIDRLen: intptr(24), IP6CIDRLen: intptr(64)},
{Mechanism: "a", DomainSpec: "x", IP6CIDRLen: intptr(64)},
{Mechanism: "mx"},
{Mechanism: "mx", DomainSpec: "x"},
{Mechanism: "ptr"},
{Mechanism: "ptr", DomainSpec: "x"},
{Mechanism: "ip4", IP: mustParseIP("10.0.0.1"), IPstr: "10.0.0.1/32"},
{Mechanism: "ip4", IP: mustParseIP("0.0.0.0"), IPstr: "0.0.0.0/0", IP4CIDRLen: intptr(0)},
{Mechanism: "ip4", IP: mustParseIP("10.0.0.1"), IPstr: "10.0.0.1/24", IP4CIDRLen: intptr(24)},
{Mechanism: "ip6", IP: mustParseIP("2001:db8::1"), IPstr: "2001:db8::1/128"},
{Mechanism: "ip6", IP: mustParseIP("2001:db8::1"), IPstr: "2001:db8::1/128", IP6CIDRLen: intptr(128)},
{Mechanism: "exists", DomainSpec: "x"},
},
Redirect: "x",
Explanation: "X",
Other: []Modifier{
{"Other", "x"},
},
},
)
test("V=spf1 -all", &Record{Version: "spf1", Directives: []Directive{{Qualifier: "-", Mechanism: "all"}}})
test("v=spf1 !", nil) // Invalid character.
test("v=spf1 ?redirect=bogus", nil)
test("v=spf1 redirect=mox.example redirect=mox2.example", nil) // Duplicate redirect.
test("v=spf1 exp=mox.example exp=mox2.example", nil) // Duplicate exp.
test("v=spf1 ip4:10.0.0.256", nil) // Invalid address.
test("v=spf1 ip6:2001:db8:::1", nil) // Invalid address.
test("v=spf1 ip4:10.0.0.1/33", nil) // IPv4 prefix >32.
test("v=spf1 ip6:2001:db8::1/129", nil) // IPv6 prefix >128.
test("v=spf1 a:mox.example/33", nil) // IPv4 prefix >32.
test("v=spf1 a:mox.example//129", nil) // IPv6 prefix >128.
test("v=spf1 a:mox.example//129", nil) // IPv6 prefix >128.
test("v=spf1 exists:%%.%{l1r+}.%{d}",
&Record{
Version: "spf1",
Directives: []Directive{
{Mechanism: "exists", DomainSpec: "%%.%{l1r+}.%{d}"},
},
},
)
test("v=spf1 exists:%{l1r+}..", nil) // Empty toplabel in domain-end.
test("v=spf1 exists:%{l1r+}._.", nil) // Invalid toplabel in domain-end.
test("v=spf1 exists:%{l1r+}.123.", nil) // Invalid toplabel in domain-end.
test("v=spf1 exists:%{l1r+}.bad-.", nil) // Invalid toplabel in domain-end.
test("v=spf1 exists:%{l1r+}.-bad.", nil) // Invalid toplabel in domain-end.
test("v=spf1 exists:%{l1r+}./.", nil) // Invalid toplabel in domain-end.
test("v=spf1 exists:%{x}", nil) // Unknown macro-letter.
test("v=spf1 exists:%{s0}", nil) // Invalid digits.
test("v=spf1 exists:%{ir}.%{l1r+}.%{d}",
&Record{
Version: "spf1",
Directives: []Directive{
{Mechanism: "exists", DomainSpec: "%{ir}.%{l1r+}.%{d}"},
},
},
)
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`
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`
r, _, err := ParseRecord(orig)
if err != nil {
t.Fatalf("parsing original: %s", err)
}
record, err := r.Record()
if err != nil {
t.Fatalf("making dns record: %s", err)
}
if record != exp {
t.Fatalf("packing dns record, got %q, expected %q", record, exp)
}
}
func FuzzParseRecord(f *testing.F) {
f.Add("")
f.Add("v=spf1")
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`)
f.Fuzz(func(t *testing.T, s string) {
r, _, err := ParseRecord(s)
if err == nil {
if _, err := r.Record(); err != nil {
t.Errorf("r.Record for %s, %#v: %s", s, r, err)
}
}
})
}