package mail import ( "bytes" "io" "mime" "mime/multipart" "mime/quotedprintable" "net/mail" "strings" "testing" ) // The envelope carries the bare address. "Vantage " as MAIL FROM left // rspamd with no envelope sender, and mailcow skipped DKIM signing. func TestAddrSpecStripsDisplayName(t *testing.T) { for in, want := range map[string]string{ "Vantage ": "support@example.com", "support@example.com": "support@example.com", " a@example.com ": "a@example.com", "not an address": "not an address", } { if got := addrSpec(in); got != want { t.Errorf("addrSpec(%q) = %q, want %q", in, got, want) } } } // Every part must declare quoted-printable and put only 7-bit bytes on the // wire. Raw UTF-8 under an implied 7bit encoding scored R_BAD_CTE_7BIT in // rspamd. func TestEnvelopePartsAreQuotedPrintable(t *testing.T) { s := Sender{From: "Vantage "} m := message{ To: "a@example.com", Subject: "Smith & Sons · restored", Text: "VANTAGE · Account\nBilling has resumed.", HTML: "

VANTAGE · Account

" + strings.Repeat("x", 2000) + "

", } raw, err := s.envelope(m) if err != nil { t.Fatal(err) } for i, c := range raw { if c > 0x7e && c != '\r' && c != '\n' { t.Fatalf("byte %d is 0x%x: the wire must be 7-bit", i, c) } } for _, line := range strings.Split(string(raw), "\r\n") { if len(line) > 998 { t.Fatalf("line of %d bytes exceeds SMTP's 998", len(line)) } } msg, err := mail.ReadMessage(bytes.NewReader(raw)) if err != nil { t.Fatal(err) } _, params, err := mime.ParseMediaType(msg.Header.Get("Content-Type")) if err != nil { t.Fatal(err) } r := multipart.NewReader(msg.Body, params["boundary"]) want := []string{m.Text, m.HTML} for i := range want { p, err := r.NextRawPart() if err != nil { t.Fatalf("part %d: %v", i, err) } if cte := p.Header.Get("Content-Transfer-Encoding"); cte != "quoted-printable" { t.Fatalf("part %d Content-Transfer-Encoding = %q", i, cte) } // NextPart would decode for us; NextRawPart keeps the check honest by // decoding here, so a missing header cannot pass by accident. body, err := io.ReadAll(quotedprintable.NewReader(p)) if err != nil { t.Fatal(err) } // Quoted-printable text mode writes line breaks as CRLF, which is the // canonical form on the wire. if strings.ReplaceAll(string(body), "\r\n", "\n") != want[i] { t.Fatalf("part %d decodes to %q", i, body) } } } func TestEnvelopeWritesExtraHeadersSanitised(t *testing.T) { s := Sender{From: "Vantage "} raw, err := s.envelope(message{ To: "a@example.com", Subject: "s", Text: "t", HTML: "

h

", Headers: map[string]string{ "List-Unsubscribe-Post": "List-Unsubscribe=One-Click", "List-Unsubscribe": "\r\nBcc: evil@example.com", }, }) if err != nil { t.Fatal(err) } msg, err := mail.ReadMessage(bytes.NewReader(raw)) if err != nil { t.Fatal(err) } if got := msg.Header.Get("List-Unsubscribe-Post"); got != "List-Unsubscribe=One-Click" { t.Fatalf("List-Unsubscribe-Post = %q", got) } if got := msg.Header.Get("Bcc"); got != "" { t.Fatalf("header injection: Bcc = %q", got) } if !strings.HasPrefix(msg.Header.Get("List-Unsubscribe"), "") { t.Fatalf("List-Unsubscribe = %q", msg.Header.Get("List-Unsubscribe")) } }