package mailx_test import ( "mime" "strings" "testing" netmail "net/mail" "pkg.jfrech.com/brief/mailx" ) type TestCase struct { DisplayName string AddrSpec string NameAddr string } func testCases() []TestCase { return []TestCase{ TestCase{ "", "a@b.c", "", }, TestCase{ "d e", "a@b.c", "\"d e\" ", }, TestCase{ "def", "a@b.c", "\"def\" ", }, TestCase{ "\"", "quote@example.com", "\"\\\"\" ", }, TestCase{ "\"", "\"@example.com", "\"\\\"\" <\"\\\"\"@example.com>", }, } } func TestAddress(t *testing.T) { for j, tc := range testCases() { nameAddr := mailx.FormatAddress(mailx.Address{tc.DisplayName, tc.AddrSpec}) if nameAddr != tc.NameAddr { t.Errorf("tcs[%d]:\n got %q,\n want %q", j, nameAddr, tc.NameAddr) } } } func FuzzAddress(f *testing.F) { // positive seeds for _, tc := range testCases() { f.Add(tc.DisplayName, tc.AddrSpec) } // negative seeds f.Add("", "0") f.Add("0", " \x82@0") f.Add("0", "=??B??=@0") f.Add("\x14", "0@0") f.Fuzz(func(t *testing.T, displayName, addrSpec string) { if roundtrip, err := netmail.ParseAddress((&netmail.Address{displayName, addrSpec}).String()); err != nil { t.SkipNow() } else if displayName != roundtrip.Name || addrSpec != roundtrip.Address { t.SkipNow() } // NOTE(jfrech): 2024-05-13: Annoyingly, [net/mail.Address.String] // declares (cf. https://pkg.go.dev/net/mail@go1.22.3#Address.String // [2024-05-13]) in its doc comment // how it only "=?charset?B/Q?encoded?="-encodes non-ASCII characters, // meaning literal display names which appear to be q-atoms // are incorrectly not encoded themselves as q-atoms. // // The following .String() could behave in a parsable manner: // // Unfortunately, err != nil // _, err := mail.ParseAddress((&mail.Address{"a", "=??B??=@c"}).String()) if strings.Contains(addrSpec, "?=") { t.SkipNow() } // nameAddrNetmail := (&netmail.Address{displayName, addrSpec}).String() nameAddrNetmailDecoded, err := new(mime.WordDecoder).DecodeHeader(nameAddrNetmail) if err != nil { t.Fatalf("displayName=%q, addrSpec=%q, nameAddrNetmail=%q, err=%v", displayName, addrSpec, nameAddrNetmail, err) } nameAddrBriefmailx := mailx.FormatAddress(mailx.Address{displayName, addrSpec}) eq := func(s, z string) bool { a, err := netmail.ParseAddress(s) if err != nil { t.Errorf("netmail.ParseAddress(%q): %v", s, err) return false } b, err := netmail.ParseAddress(s) if err != nil { t.Errorf("netmail.ParseAddress(%q): %v", z, err) return false } return *a == *b } if !eq(nameAddrNetmail, nameAddrNetmailDecoded) || !eq(nameAddrNetmail, nameAddrBriefmailx) { t.Errorf("mismatch:\n nameAddrNetmail = %q,\n nameAddrNetmailDecoded = %q,\n nameAddrBriefmailx = %q", nameAddrNetmail, nameAddrNetmailDecoded, nameAddrBriefmailx) } /* addr0 := &netmail.Address{displayName, addrSpec} addr1, err := netmail.ParseAddress(addr0.String()) if err != nil { t.Fatalf("netmail.Parse(%q): %v", addr0.String(), err) } t.Logf("addr0.String() = %s", addr0.String()) t.Logf("addr1.String() = %s", addr1.String()) if *addr0 != *addr1 { t.Errorf("&netmail.Address{%q, %q} decayed into &netmail.Address{%q, %q}", addr0.Name, addr0.Address, addr1.Name, addr1.Address) } */ }) }