package mailfetch import ( "net/textproto" "strings" "testing" ) // realForwardedHeaders mirrors the header set seen on a real forwarded message // (AGENTS.md §13, message id 619). X-Pm-Original-To must win and Delivered-To // must never be used. func TestSourceAddressXPMOriginalToWins(t *testing.T) { h := textproto.MIMEHeader{} h.Set("X-Pm-Original-To", "Hello@where-woof.com") h.Set("To", "Hello@where-woof.com") h.Set("Return-Path", "") h.Set("Delivered-To", "samuelrolfe@gmail.com") addr, header := SourceAddress(h) if addr != "Hello@where-woof.com" { t.Errorf("address = %q, want %q", addr, "Hello@where-woof.com") } if header != "X-Pm-Original-To" { t.Errorf("header = %q, want %q", header, "X-Pm-Original-To") } } func TestSourceAddressFallsBackToTo(t *testing.T) { h := textproto.MIMEHeader{} h.Set("To", "Second Choice ") h.Set("Delivered-To", "samuelrolfe@gmail.com") addr, header := SourceAddress(h) if addr != "second@example.test" { t.Errorf("address = %q, want %q", addr, "second@example.test") } if header != "To" { t.Errorf("header = %q, want %q", header, "To") } } func TestSourceAddressFallsBackToReceivedFor(t *testing.T) { h := textproto.MIMEHeader{} h.Add("Received", "by mx.example.test with ESMTPS id abc") h.Add("Received", "from mail.purelymail.com by mx.example.test for ; Mon, 1 Jan 2026 00:00:00 +0000") h.Set("Return-Path", "") addr, header := SourceAddress(h) if addr != "relay@where-woof.com" { t.Errorf("address = %q, want %q", addr, "relay@where-woof.com") } if header != "Received" { t.Errorf("header = %q, want %q", header, "Received") } } func TestSourceAddressFallsBackToReturnPath(t *testing.T) { h := textproto.MIMEHeader{} h.Set("Return-Path", "") addr, header := SourceAddress(h) if addr != "sender@example.test" { t.Errorf("address = %q, want %q", addr, "sender@example.test") } if header != "Return-Path" { t.Errorf("header = %q, want %q", header, "Return-Path") } } func TestSourceAddressIgnoresDeliveredTo(t *testing.T) { h := textproto.MIMEHeader{} h.Set("Delivered-To", "samuelrolfe@gmail.com") addr, header := SourceAddress(h) if addr != "" || header != "" { t.Errorf("SourceAddress() = (%q, %q), want empty: Delivered-To must not be used", addr, header) } } func TestParseMessagePlain(t *testing.T) { raw := strings.Join([]string{ "From: favrolfe@gmail.com", "To: Hello@where-woof.com", "X-Pm-Original-To: Hello@where-woof.com", "Subject: =?UTF-8?Q?Caf=C3=A9_meeting?=", "Date: Mon, 02 Oct 2006 15:04:05 -0700", "Content-Type: text/plain; charset=utf-8", "Content-Transfer-Encoding: quoted-printable", "", "Hello there=2C this is a test.", }, "\r\n") pm, err := ParseMessage([]byte(raw)) if err != nil { t.Fatalf("ParseMessage() error = %v", err) } if pm.Subject != "Café meeting" { t.Errorf("Subject = %q, want %q", pm.Subject, "Café meeting") } if pm.SourceAddress != "Hello@where-woof.com" || pm.SourceHeader != "X-Pm-Original-To" { t.Errorf("source = (%q, %q), want (Hello@where-woof.com, X-Pm-Original-To)", pm.SourceAddress, pm.SourceHeader) } if !strings.Contains(pm.BodyText, "Hello there, this is a test.") { t.Errorf("BodyText = %q, want quoted-printable decoded text", pm.BodyText) } } func TestParseMessageMultipartPrefersPlain(t *testing.T) { raw := strings.Join([]string{ "From: sender@example.test", "To: Hello@where-woof.com", "Subject: multipart", "MIME-Version: 1.0", `Content-Type: multipart/alternative; boundary="BOUND"`, "", "--BOUND", "Content-Type: text/plain; charset=utf-8", "", "plain part wins", "--BOUND", "Content-Type: text/html; charset=utf-8", "", "

html part loses

", "--BOUND--", "", }, "\r\n") pm, err := ParseMessage([]byte(raw)) if err != nil { t.Fatalf("ParseMessage() error = %v", err) } if !strings.Contains(pm.BodyText, "plain part wins") { t.Errorf("BodyText = %q, want the text/plain part", pm.BodyText) } if strings.Contains(pm.BodyText, "html part") { t.Errorf("BodyText = %q, must not contain the HTML part", pm.BodyText) } } func TestParseMessageHTMLOnlyIsStripped(t *testing.T) { raw := strings.Join([]string{ "From: sender@example.test", "Subject: html only", "Content-Type: text/html; charset=utf-8", "", "

Hello

World & friends

", }, "\r\n") pm, err := ParseMessage([]byte(raw)) if err != nil { t.Fatalf("ParseMessage() error = %v", err) } if !strings.Contains(pm.BodyText, "Hello") || !strings.Contains(pm.BodyText, "World & friends") { t.Errorf("BodyText = %q, want stripped HTML text", pm.BodyText) } if strings.Contains(pm.BodyText, "<") { t.Errorf("BodyText = %q, want no tags", pm.BodyText) } } func TestHeaderAddressParsing(t *testing.T) { cases := []struct{ in, want string }{ {"Hello@where-woof.com", "Hello@where-woof.com"}, {"", "Hello@where-woof.com"}, {"Sam Rolfe ", "sam@example.test"}, {"a@example.test, b@example.test", "a@example.test"}, {"", ""}, } for _, tc := range cases { if got := headerAddress(tc.in); got != tc.want { t.Errorf("headerAddress(%q) = %q, want %q", tc.in, got, tc.want) } } }