~rjarry/aerc

4f866e6894ef5731193ee88a2c5a5b00c1113de4 — Julian Swagemakers a month ago fc31a5c
imap: strip whitespace from Message-Id and In-Reply-To

Outlook.com is generating fairly long Message-IDs and using folding[0]
to put the ID on a new line. This is resulting in the Message-ID to
contain a leading white space, which is preventing `TrimPrefix` from
removing the less than symbol. When replying the In-Reply-To header will
then contain "< <message-id>" which is not desired and prevents email
clients or lists form correctly associating the email replied to. For
example lore.kernel.org[1]. Trimming tabs, newlines, and spaces from
Message-ID resolves the issue.

[0]: https://datatracker.ietf.org/doc/html/rfc822#section-3.1.1
[1]: https://lore.kernel.org/git/D4U1RWVWEW5D.2T853XSBO1FPA@swagemakers.org/#t

Changelog-fixed: Remove unwanted less than symbol from In-Reply-To
 header when Message-ID uses folding.
Signed-off-by: Julian Swagemakers <julian@swagemakers.org>
Acked-by: Robin Jarry <robin@jarry.cc>

2 files changed, 59 insertions(+), 7 deletions(-)

M worker/imap/imap.go
A worker/imap/imap_test.go
M worker/imap/imap.go => worker/imap/imap.go +8 -7
@@ 50,11 50,6 @@ func translateEnvelope(e *imap.Envelope) *models.Envelope {
		return nil
	}

	// we strip the msgid of "<>" in order to be more compatible with go-message
	// which wants to handle msgids without the markers
	msgID := strings.TrimSuffix(strings.TrimPrefix(e.MessageId, "<"), ">")
	inReplyTo := strings.TrimSuffix(strings.TrimPrefix(e.InReplyTo, "<"), ">")

	return &models.Envelope{
		Date:      e.Date,
		Subject:   e.Subject,


@@ 63,11 58,17 @@ func translateEnvelope(e *imap.Envelope) *models.Envelope {
		To:        translateAddresses(e.To),
		Cc:        translateAddresses(e.Cc),
		Bcc:       translateAddresses(e.Bcc),
		MessageId: msgID,
		InReplyTo: inReplyTo,
		MessageId: translateMessageID(e.MessageId),
		InReplyTo: translateMessageID(e.InReplyTo),
	}
}

func translateMessageID(messageID string) string {
	// Strip away unwanted characters, go-message expects the message id
	// without brackets, spaces, tabs and new lines.
	return strings.Trim(messageID, "<> \t\r\n")
}

func translateAddresses(addrs []*imap.Address) []*mail.Address {
	var converted []*mail.Address
	for _, addr := range addrs {

A worker/imap/imap_test.go => worker/imap/imap_test.go +51 -0
@@ 0,0 1,51 @@
package imap

import (
	"testing"
	"time"

	"git.sr.ht/~rjarry/aerc/models"
	"github.com/emersion/go-message/mail"

	"github.com/emersion/go-imap"
	"github.com/stretchr/testify/assert"
)

func TestTranslateEnvelope(t *testing.T) {
	date, _ := time.Parse("2010-01-31", "1992-10-24")
	givenAddress := imap.Address{
		PersonalName: "PERSONAL_NAME",
		AtDomainList: "AT_DOMAIN_LIST",
		MailboxName:  "MAILBOX_NAME",
		HostName:     "HOST_NAME",
	}
	givenMessageID := " \r\n\r  \t <initial-message-id@with-leading-space>\t\r"
	given := imap.Envelope{
		Date:      date,
		Subject:   "Test Subject",
		From:      []*imap.Address{&givenAddress},
		ReplyTo:   []*imap.Address{&givenAddress},
		To:        []*imap.Address{&givenAddress},
		Cc:        []*imap.Address{&givenAddress},
		Bcc:       []*imap.Address{&givenAddress},
		MessageId: givenMessageID,
		InReplyTo: givenMessageID,
	}
	expectedMessageID := "initial-message-id@with-leading-space"
	expectedAddress := mail.Address{
		Name:    "PERSONAL_NAME",
		Address: "MAILBOX_NAME@HOST_NAME",
	}
	expected := models.Envelope{
		Date:      date,
		Subject:   "Test Subject",
		From:      []*mail.Address{&expectedAddress},
		ReplyTo:   []*mail.Address{&expectedAddress},
		To:        []*mail.Address{&expectedAddress},
		Cc:        []*mail.Address{&expectedAddress},
		Bcc:       []*mail.Address{&expectedAddress},
		MessageId: expectedMessageID,
		InReplyTo: expectedMessageID,
	}
	assert.Equal(t, &expected, translateEnvelope(&given))
}