1package dmarc
2
3import (
4 "reflect"
5 "testing"
6)
7
8func TestParse(t *testing.T) {
9 // ../rfc/7489:3224
10
11 // bad cases
12 bad := func(s string) {
13 t.Helper()
14 _, _, err := ParseRecord(s)
15 if err == nil {
16 t.Fatalf("got parse success, expected error")
17 }
18 }
19 bad("")
20 bad("v=")
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
38 bad("v=DMARC1; fo=")
39 bad("v=DMARC1; fo=01")
40 bad("v=DMARC1; fo=bad")
41 bad("v=DMARC1; rf=bad-trailing-dash-")
42 bad("v=DMARC1; rf=")
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")
48 bad("v=DMARC1; pct=")
49 bad("v=DMARC1; rua=")
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")
54
55 valid := func(s string, exp Record) {
56 t.Helper()
57
58 r, _, err := ParseRecord(s)
59 if err != nil {
60 t.Fatalf("unexpected error: %s", err)
61 }
62 if !reflect.DeepEqual(r, &exp) {
63 t.Fatalf("got:\n%#v\nexpected:\n%#v", r, &exp)
64 }
65 }
66
67 // Return a record with default values, and overrides from r. Only for the fields used below.
68 record := func(r Record) Record {
69 rr := DefaultRecord
70 if r.Policy != "" {
71 rr.Policy = r.Policy
72 }
73 if r.AggregateReportAddresses != nil {
74 rr.AggregateReportAddresses = r.AggregateReportAddresses
75 }
76 if r.FailureReportAddresses != nil {
77 rr.FailureReportAddresses = r.FailureReportAddresses
78 }
79 if r.Percentage != 0 {
80 rr.Percentage = r.Percentage
81 }
82 return rr
83 }
84
85 valid("v=DMARC1; rua=mailto:mjl@mox.example", record(Record{
86 Policy: "none",
87 AggregateReportAddresses: []URI{
88 {Address: "mailto:mjl@mox.example"},
89 },
90 })) // ../rfc/7489:1407
91 valid("v=DMARC1; p=reject; sp=invalid; rua=mailto:mjl@mox.example", record(Record{
92 Policy: "none",
93 AggregateReportAddresses: []URI{
94 {Address: "mailto:mjl@mox.example"},
95 },
96 })) // ../rfc/7489:1407
97 valid("v=DMARC1; p=none; rua=mailto:dmarc-feedback@example.com", record(Record{
98 Policy: "none",
99 AggregateReportAddresses: []URI{
100 {Address: "mailto:dmarc-feedback@example.com"},
101 },
102 }))
103 valid("v=DMARC1; p=none; rua=mailto:dmarc-feedback@example.com;ruf=mailto:auth-reports@example.com", record(Record{
104 Policy: "none",
105 AggregateReportAddresses: []URI{
106 {Address: "mailto:dmarc-feedback@example.com"},
107 },
108 FailureReportAddresses: []URI{
109 {Address: "mailto:auth-reports@example.com"},
110 },
111 }))
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"},
117 },
118 Percentage: 25,
119 }))
120
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",
122 Record{
123 Version: "DMARC1",
124 Policy: "reject",
125 SubdomainPolicy: "none",
126 ADKIM: "s",
127 ASPF: "s",
128 AggregateReportAddresses: []URI{
129 {Address: "mailto:dmarc-feedback@example.com"},
130 {Address: "mailto:tld-test@thirdparty.example.net", MaxSize: 10, Unit: "m"},
131 },
132 FailureReportAddresses: []URI{
133 {Address: "mailto:auth-reports@example.com"},
134 {Address: "mailto:tld-test@thirdparty.example.net", MaxSize: 0, Unit: "g"},
135 },
136 AggregateReportingInterval: 123,
137 FailureReportingOptions: []string{"0", "1", "d", "s"},
138 ReportingFormat: []string{"afrf", "other"},
139 Percentage: 0,
140 },
141 )
142}
143