package dmarc import ( "archive/zip" "bytes" "compress/gzip" "encoding/base64" "strings" "testing" ) const sample = ` google.com noreply-dmarc-support@google.com 12345 17000000001700086399 Example.COM rr

none

none100
203.0.113.712 nonepassfail example.combounce.example.com example.coms1pass bounce.example.compass 198.51.100.93 quarantinefailfail example.com example.comsoftfail
` func check(t *testing.T, r *Report) { t.Helper() if r.Org != "google.com" || r.ReportID != "12345" || r.Domain != "example.com" { t.Fatalf("metadata: %+v", r) } if r.Begin.Unix() != 1700000000 || r.End.Unix() != 1700086399 { t.Fatalf("range: %v %v", r.Begin, r.End) } if r.Policy.P != "none" || r.Policy.Pct != 100 || r.Policy.ADKIM != "r" { t.Fatalf("policy: %+v", r.Policy) } if len(r.Records) != 2 { t.Fatalf("records: %d", len(r.Records)) } a, b := r.Records[0], r.Records[1] if a.SourceIP != "203.0.113.7" || a.Count != 12 || !a.Passed() || a.EnvelopeFrom != "bounce.example.com" { t.Fatalf("first record: %+v", a) } if len(a.DKIMAuth) != 1 || a.DKIMAuth[0].Selector != "s1" || len(a.SPFAuth) != 1 { t.Fatalf("auth results: %+v", a) } if b.Passed() || b.Disposition != "quarantine" || b.SPFAuth[0].Result != "softfail" { t.Fatalf("second record: %+v", b) } } func gz(t *testing.T, s string) []byte { var buf bytes.Buffer w := gzip.NewWriter(&buf) w.Write([]byte(s)) w.Close() return buf.Bytes() } func zipped(t *testing.T, s string) []byte { var buf bytes.Buffer w := zip.NewWriter(&buf) f, _ := w.Create("report.xml") f.Write([]byte(s)) w.Close() return buf.Bytes() } func email(contentType, encoding string, body []byte) []byte { b64 := base64.StdEncoding.EncodeToString(body) var wrapped strings.Builder for len(b64) > 76 { wrapped.WriteString(b64[:76] + "\r\n") b64 = b64[76:] } wrapped.WriteString(b64 + "\r\n") return []byte("From: noreply@google.com\r\nSubject: Report Domain: example.com\r\nMIME-Version: 1.0\r\n" + "Content-Type: multipart/mixed; boundary=\"XX\"\r\n\r\n--XX\r\nContent-Type: text/plain\r\n\r\nSee attached.\r\n" + "--XX\r\nContent-Type: " + contentType + "\r\nContent-Transfer-Encoding: " + encoding + "\r\n\r\n" + wrapped.String() + "--XX--\r\n") } func TestParseForms(t *testing.T) { forms := map[string][]byte{ "xml": []byte("\n" + sample), "gzip": gz(t, sample), "zip": zipped(t, sample), "mail+gzip": email("application/gzip", "base64", gz(t, sample)), "mail+zip": email("application/zip; name=r.zip", "base64", zipped(t, sample)), "mail+xml": email("text/xml", "base64", []byte(sample)), "mail+octet": email("application/octet-stream", "base64", gz(t, sample)), } for name, data := range forms { t.Run(name, func(t *testing.T) { r, err := Parse(data) if err != nil { t.Fatal(err) } check(t, r) }) } } func TestParseRejects(t *testing.T) { for name, data := range map[string][]byte{ "empty": nil, "text": []byte("hello, this is not a report"), "other xml": []byte(""), "no id": []byte(strings.Replace(sample, "12345", "", 1)), "mail no att": []byte("Subject: x\r\n\r\njust text\r\n"), "bad gzip": {0x1f, 0x8b, 0, 0}, } { t.Run(name, func(t *testing.T) { if r, err := Parse(data); err == nil { t.Fatalf("expected an error, got %+v", r) } }) } } func TestParseBombIsRefused(t *testing.T) { big := "" + strings.Repeat("x", maxXML+10) + "" if _, err := Parse(gz(t, big)); err == nil || !strings.Contains(err.Error(), "too large") { t.Fatalf("want too large, got %v", err) } } func TestPctDefaultsAndClamps(t *testing.T) { r, err := Parse([]byte(strings.Replace(sample, "100", "25", 1))) if err != nil || r.Policy.Pct != 25 { t.Fatalf("pct 25: %v %+v", err, r) } r, err = Parse([]byte(strings.Replace(sample, "100", "", 1))) if err != nil || r.Policy.Pct != 100 { t.Fatalf("missing pct: %v %+v", err, r) } }