8func TestParse(t *testing.T) {
12 bad := func(s string) {
14 _, _, err := ParseRecord(s)
16 t.Fatalf("got parse success, expected error")
21 bad("v=DMARC12") // "2" leftover
22 bad("v=DMARC1") // semicolon required
23 bad("v=dmarc1; p=none") // dmarc1 is case-sensitive
24 bad("v=DMARC1 p=none") // missing ;
25 bad("v=DMARC1;") // missing p, no rua
26 bad("v=DMARC1; sp=invalid") // invalid sp, no rua
27 bad("v=DMARC1; sp=reject; p=reject") // p must be directly after v
28 bad("v=DMARC1; p=none; p=none") // dup
29 bad("v=DMARC1; p=none; p=reject") // dup
30 bad("v=DMARC1;;") // missing tag
31 bad("v=DMARC1; adkim=x") // bad value
32 bad("v=DMARC1; aspf=123") // bad value
33 bad("v=DMARC1; ri=") // missing value
34 bad("v=DMARC1; ri=-1") // invalid, must be >= 0
35 bad("v=DMARC1; ri=99999999999999999999999999999999999999") // does not fit in int
36 bad("v=DMARC1; ri=123bad") // leftover data
37 bad("v=DMARC1; ri=bad") // not a number
39 bad("v=DMARC1; fo=01")
40 bad("v=DMARC1; fo=bad")
41 bad("v=DMARC1; rf=bad-trailing-dash-")
43 bad("v=DMARC1; rf=bad.non-alphadigitdash")
44 bad("v=DMARC1; p=badvalue")
45 bad("v=DMARC1; sp=bad")
46 bad("v=DMARC1; pct=110")
47 bad("v=DMARC1; pct=bogus")
50 bad("v=DMARC1; rua=bogus")
51 bad("v=DMARC1; rua=mailto:dmarc-feedback@example.com!")
52 bad("v=DMARC1; rua=mailto:dmarc-feedback@example.com!99999999999999999999999999999999999999999999999")
53 bad("v=DMARC1; rua=mailto:dmarc-feedback@example.com!10p")
55 valid := func(s string, exp Record) {
58 r, _, err := ParseRecord(s)
60 t.Fatalf("unexpected error: %s", err)
62 if !reflect.DeepEqual(r, &exp) {
63 t.Fatalf("got:\n%#v\nexpected:\n%#v", r, &exp)
67 // Return a record with default values, and overrides from r. Only for the fields used below.
68 record := func(r Record) Record {
73 if r.AggregateReportAddresses != nil {
74 rr.AggregateReportAddresses = r.AggregateReportAddresses
76 if r.FailureReportAddresses != nil {
77 rr.FailureReportAddresses = r.FailureReportAddresses
79 if r.Percentage != 0 {
80 rr.Percentage = r.Percentage
85 valid("v=DMARC1; rua=mailto:mjl@mox.example", record(Record{
87 AggregateReportAddresses: []URI{
88 {Address: "mailto:mjl@mox.example"},
91 valid("v=DMARC1; p=reject; sp=invalid; rua=mailto:mjl@mox.example", record(Record{
93 AggregateReportAddresses: []URI{
94 {Address: "mailto:mjl@mox.example"},
97 valid("v=DMARC1; p=none; rua=mailto:dmarc-feedback@example.com", record(Record{
99 AggregateReportAddresses: []URI{
100 {Address: "mailto:dmarc-feedback@example.com"},
103 valid("v=DMARC1; p=none; rua=mailto:dmarc-feedback@example.com;ruf=mailto:auth-reports@example.com", record(Record{
105 AggregateReportAddresses: []URI{
106 {Address: "mailto:dmarc-feedback@example.com"},
108 FailureReportAddresses: []URI{
109 {Address: "mailto:auth-reports@example.com"},
112 valid("v=DMARC1; p=quarantine; rua=mailto:dmarc-feedback@example.com,mailto:tld-test@thirdparty.example.net!10m; pct=25", record(Record{
113 Policy: "quarantine",
114 AggregateReportAddresses: []URI{
115 {Address: "mailto:dmarc-feedback@example.com"},
116 {Address: "mailto:tld-test@thirdparty.example.net", MaxSize: 10, Unit: "m"},
121 valid("V = DMARC1 ; P = reject ;\tSP=none; unknown \t=\t ignored-future-value \t ; adkim=s; aspf=s; rua=mailto:dmarc-feedback@example.com ,\t\tmailto:tld-test@thirdparty.example.net!10m; RUF=mailto:auth-reports@example.com ,\t\tmailto:tld-test@thirdparty.example.net!0G; RI = 123; FO = 0:1:d:s ; RF= afrf : other; Pct = 0",
125 SubdomainPolicy: "none",
128 AggregateReportAddresses: []URI{
129 {Address: "mailto:dmarc-feedback@example.com"},
130 {Address: "mailto:tld-test@thirdparty.example.net", MaxSize: 10, Unit: "m"},
132 FailureReportAddresses: []URI{
133 {Address: "mailto:auth-reports@example.com"},
134 {Address: "mailto:tld-test@thirdparty.example.net", MaxSize: 0, Unit: "g"},
136 AggregateReportingInterval: 123,
137 FailureReportingOptions: []string{"0", "1", "d", "s"},
138 ReportingFormat: []string{"afrf", "other"},