From 027274e2e832817e98adcad005a2536c46cc09b4 Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Wed, 7 Jun 2023 10:17:37 +0200 Subject: [PATCH] fix(GODT-2683): Reduce validation checks when appending into Drafts --- internal/session/handle_append.go | 19 +++++++++++++---- internal/state/mailbox.go | 10 +++++++++ rfc5322/validation.go | 24 ++++++++++++++++++++++ tests/append_test.go | 34 +++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/internal/session/handle_append.go b/internal/session/handle_append.go index 5b3b0426..ded5197e 100644 --- a/internal/session/handle_append.go +++ b/internal/session/handle_append.go @@ -26,11 +26,22 @@ func (s *Session) handleAppend(ctx context.Context, tag string, cmd *command.App return response.Bad(tag).WithError(err) } - if err := rfc5322.ValidateMessageHeaderFields(cmd.Literal); err != nil { - return response.Bad(tag).WithError(err) - } - if err := s.state.AppendOnlyMailbox(ctx, nameUTF8, func(mailbox state.AppendOnlyMailbox, isSameMBox bool) error { + isDrafts, err := mailbox.IsDrafts(ctx) + if err != nil { + return err + } + + if !isDrafts { + if err := rfc5322.ValidateMessageHeaderFields(cmd.Literal); err != nil { + return response.Bad(tag).WithError(err) + } + } else { + if err := rfc5322.ValidateMessageHeaderFieldsDrafts(cmd.Literal); err != nil { + return response.Bad(tag).WithError(err) + } + } + messageUID, err := mailbox.Append(ctx, cmd.Literal, flags, cmd.DateTime) if err != nil { if shouldReportIMAPCommandError(err) { diff --git a/internal/state/mailbox.go b/internal/state/mailbox.go index 3746584e..92316c0d 100644 --- a/internal/state/mailbox.go +++ b/internal/state/mailbox.go @@ -37,6 +37,7 @@ type AppendOnlyMailbox interface { Append(ctx context.Context, literal []byte, flags imap.FlagSet, date time.Time) (imap.UID, error) Flush(ctx context.Context, permitExpunge bool) ([]response.Response, error) UIDValidity() imap.UID + IsDrafts(ctx context.Context) (bool, error) } func newMailbox(mbox *db.Mailbox, state *State, snap *snapshot) *Mailbox { @@ -251,6 +252,15 @@ func (m *Mailbox) Append(ctx context.Context, literal []byte, flags imap.FlagSet return uid, err } +func (m *Mailbox) IsDrafts(ctx context.Context) (bool, error) { + attrs, err := m.Attributes(ctx) + if err != nil { + return false, err + } + + return attrs.Contains(imap.AttrDrafts), nil +} + // Copy copies the messages represented by the given sequence set into the mailbox with the given name. // If the context is a UID context, the sequence set refers to message UIDs. // If no items are copied the response object will be nil. diff --git a/rfc5322/validation.go b/rfc5322/validation.go index 136b2d99..a0c765b5 100644 --- a/rfc5322/validation.go +++ b/rfc5322/validation.go @@ -72,3 +72,27 @@ func ValidateMessageHeaderFields(literal []byte) error { return nil } + +// ValidateMessageHeaderFieldsDrafts checks the headers of message to verify that at least a valid From header is +// present. +func ValidateMessageHeaderFieldsDrafts(literal []byte) error { + headerBytes, _ := rfc822.Split(literal) + + header, err := rfc822.NewHeader(headerBytes) + if err != nil { + return err + } + + // Check for from. + value := header.Get("From") + if len(value) == 0 { + return fmt.Errorf("%w: Required header field 'From' not found or empty", ErrInvalidMessage) + } + + // Check if From is a multi address. If so, a sender filed must be present and non-empty. + if _, err := ParseAddressList(value); err != nil { + return fmt.Errorf("%w: failed to parse From header: %v", ErrInvalidMessage, err) + } + + return nil +} diff --git a/tests/append_test.go b/tests/append_test.go index 2585910c..4001ecb6 100644 --- a/tests/append_test.go +++ b/tests/append_test.go @@ -449,3 +449,37 @@ func TestGODT2007AppendInternalIDPresentOnDeletedMessage(t *testing.T) { } }) } + +func TestAppendIntoDraftsWithFromOnly(t *testing.T) { + const ( + literalWithFrom = `From: Foo@bar` + literalWithoutFrom = `To: Foo@bar` + literalValid = `From: Foo@bar +Date: Wed, 26 Apr 2023 08:25:16 +0200 +` + ) + + runOneToOneTestClientWithAuth(t, defaultServerOptions(t), func(client *client.Client, s *testSession) { + + s.mailboxCreatedWithAttributes("user", []string{"Drafts"}, imap.NewFlagSet(imap.AttrDrafts)) + s.flush("user") + + { + require.NoError(t, doAppendWithClient(client, "Drafts", literalWithFrom, time.Now())) + require.NoError(t, doAppendWithClient(client, "INBOX", literalValid, time.Now())) + require.Error(t, doAppendWithClient(client, "Drafts", literalWithoutFrom, time.Now())) + require.Error(t, doAppendWithClient(client, "INBOX", literalWithoutFrom, time.Now())) + } + + { + status, err := client.Status("Drafts", []goimap.StatusItem{goimap.StatusMessages}) + require.NoError(t, err) + require.Equal(t, uint32(1), status.Messages, "Expected message count does not match") + } + { + status, err := client.Status("INBOX", []goimap.StatusItem{goimap.StatusMessages}) + require.NoError(t, err) + require.Equal(t, uint32(1), status.Messages, "Expected message count does not match") + } + }) +}