From 016fde8d783247129613b16b60dbe14e2a855b51 Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Thu, 22 Aug 2024 16:03:52 +0200 Subject: [PATCH] fix parsing message headers with addresses that need double quotes we are using Go's net/mail to parse message headers. it can parse addresses, and properly decodes email addresses with double quotes (e.g. " "@example.com). however, it gives us an address without the double quotes in the localpart, effectively an invalid address. we now have a workaround to parse such not-quite-addresses. for issue #199 reported by gene-hightower, thanks for reporting! --- message/part.go | 4 ++-- message/part_test.go | 7 +++++++ sendmail.go | 6 +++++- smtp/address.go | 15 +++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/message/part.go b/message/part.go index 42b2a04..00d2a91 100644 --- a/message/part.go +++ b/message/part.go @@ -497,9 +497,9 @@ func parseAddressList(log mlog.Log, h mail.Header, k string) []Address { for _, a := range l { // todo: parse more fully according to ../rfc/5322:959 var user, host string - addr, err := smtp.ParseAddress(a.Address) + addr, err := smtp.ParseNetMailAddress(a.Address) if err != nil { - log.Infox("parsing address (continuing)", err, slog.Any("address", a.Address)) + log.Infox("parsing address (continuing)", err, slog.Any("netmailaddress", a.Address)) } else { user = addr.Localpart.String() host = addr.Domain.ASCII diff --git a/message/part_test.go b/message/part_test.go index 6827377..4a42e3d 100644 --- a/message/part_test.go +++ b/message/part_test.go @@ -596,3 +596,10 @@ func TestEmbedded2(t *testing.T) { _, err = EnsurePart(pkglog.Logger, false, bytes.NewReader(buf), int64(len(buf))) tfail(t, err, nil) } + +func TestNetMailAddress(t *testing.T) { + const s = "From: \" \"@example.com\r\n\r\nbody\r\n" + p, err := EnsurePart(pkglog.Logger, false, strings.NewReader(s), int64(len(s))) + tcheck(t, err, "parse") + tcompare(t, p.Envelope.From, []Address{{"", `" "`, "example.com"}}) +} diff --git a/sendmail.go b/sendmail.go index 88d8f17..06e8a08 100644 --- a/sendmail.go +++ b/sendmail.go @@ -201,7 +201,11 @@ binary should be setgid that group: if len(addrs) != 1 { log.Fatalf("only single address allowed in To header") } - recipient = addrs[0].Address + addr, err := smtp.ParseNetMailAddress(addrs[0].Address) + if err != nil { + log.Fatalf("parsing address: %v", err) + } + recipient = addr.Pack(false) } } if k == "to" { diff --git a/smtp/address.go b/smtp/address.go index e2a93c7..27777f3 100644 --- a/smtp/address.go +++ b/smtp/address.go @@ -176,6 +176,21 @@ func ParseAddress(s string) (address Address, err error) { return Address{lp, d}, err } +// ParseNetMailAddress parses a not-quite-valid address as found in +// net/mail.Address.Address. +// +// net/mail does parse quoted addresses properly, but stores the localpart +// unquoted. So an address `" "@example.com` would be stored as ` @example.com`, +// which we would fail to parse without special attention. +func ParseNetMailAddress(a string) (address Address, err error) { + i := strings.LastIndex(a, "@") + if i < 0 { + return Address{}, fmt.Errorf("%w: missing @", ErrBadAddress) + } + addrStr := Localpart(a[:i]).String() + "@" + a[i+1:] + return ParseAddress(addrStr) +} + var ErrBadLocalpart = errors.New("invalid localpart") // ParseLocalpart parses the local part.