rfc822: be liberal with invalid address headers

Some email clients format email addresses with quotes around B encoded
names. E.g.:

  "=?utf-8?B?U21pZXRhbnNraSwgV29qY2llY2ggVGFkZXVzeiBpbiBUZWFtcw==?="

This seems non standard. I tried reading the multiple RFCs that describe
the MIME headers encoding but could not find any precise answer to
determine if adding quotes around encoded words is valid or not.

In any case, ParseAddressList completely ignores any encoding when it
encounters a double quote.

When that happens, try to detect it and force decoding a second time
after the quotes have been removed.

Link: https://datatracker.ietf.org/doc/html/rfc2045#section-3
Link: https://datatracker.ietf.org/doc/html/rfc2047#section-2
Link: https://datatracker.ietf.org/doc/html/rfc822#section-3.3
Reported-by: Bence Ferdinandy <bence@ferdinandy.com>
Signed-off-by: Robin Jarry <robin@jarry.cc>
Tested-by: Bence Ferdinandy <bence@ferdinandy.com>
This commit is contained in:
Robin Jarry
2024-07-02 15:58:41 +02:00
parent bee2fc62ac
commit 44b14ebdff
2 changed files with 61 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"mime"
"regexp"
"strings"
"time"
@@ -284,6 +285,13 @@ func parseAddressList(h *mail.Header, key string) ([]*mail.Address, error) {
// errors which are not fatal.
return nil, err
}
for _, addr := range addrs {
// Handle invalid headers with quoted *AND* encoded names
if strings.HasPrefix(addr.Name, "=?") && strings.HasSuffix(addr.Name, "?=") {
d := mime.WordDecoder{CharsetReader: message.CharsetReader}
addr.Name, _ = d.DecodeHeader(addr.Name)
}
}
// If we got at least one address, ignore any returned error.
return addrs, nil
}

View File

@@ -9,6 +9,7 @@ import (
"git.sr.ht/~rjarry/aerc/models"
"github.com/emersion/go-message/mail"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -111,6 +112,58 @@ func TestParseMessageDate(t *testing.T) {
}
}
func TestParseAddressList(t *testing.T) {
header := mail.HeaderFromMap(map[string][]string{
"From": {`"=?utf-8?B?U21pZXRhbnNraSwgV29qY2llY2ggVGFkZXVzeiBpbiBUZWFtcw==?=" <noreply@email.teams.microsoft.com>`},
"To": {`=?UTF-8?q?Oc=C3=A9ane_de_Seazon?= <hello@seazon.fr>`},
"Cc": {`=?utf-8?b?0KjQsNCz0L7QsiDQk9C10L7RgNCz0LjQuSB2aWEgZGlzY3Vzcw==?= <ovs-discuss@openvswitch.org>`},
"Bcc": {`"Foo, Baz Bar" <~foo/baz@bar.org>`},
})
type vector struct {
kind string
header string
name string
email string
}
vectors := []vector{
{
kind: "quoted",
header: "Bcc",
name: "Foo, Baz Bar",
email: "~foo/baz@bar.org",
},
{
kind: "Qencoded",
header: "To",
name: "Océane de Seazon",
email: "hello@seazon.fr",
},
{
kind: "Bencoded",
header: "Cc",
name: "Шагов Георгий via discuss",
email: "ovs-discuss@openvswitch.org",
},
{
kind: "quoted+Bencoded",
header: "From",
name: "Smietanski, Wojciech Tadeusz in Teams",
email: "noreply@email.teams.microsoft.com",
},
}
for _, vec := range vectors {
t.Run(vec.kind, func(t *testing.T) {
addrs, err := parseAddressList(&header, vec.header)
assert.Nil(t, err)
assert.Len(t, addrs, 1)
assert.Equal(t, vec.name, addrs[0].Name)
assert.Equal(t, vec.email, addrs[0].Address)
})
}
}
type mockRawMessage struct {
path string
}