From 7349e3db52be16b33e3ea2e97fcb88b9f913c8a7 Mon Sep 17 00:00:00 2001 From: LBeernaertProton <104621424+LBeernaertProton@users.noreply.github.com> Date: Tue, 6 Dec 2022 20:41:13 +0100 Subject: [PATCH] feat(GODT-1970): Soft limits (#242) Add limit checks for mailbox count, messages per mailbox, UID and UIDValidity so that we don't run into issues related exceeding the maximum representable size for each of these data types (uint32). --- builder.go | 6 +- internal/backend/backend.go | 8 +- internal/backend/connector_updates.go | 14 +++- internal/backend/user.go | 7 ++ internal/db/mailbox.go | 27 ++++++ internal/session/session.go | 3 + internal/state/actions.go | 6 +- internal/state/mailbox.go | 18 ++++ internal/state/state.go | 16 +++- internal/state/updates_mailbox.go | 30 ++++++- limits/imap.go | 82 +++++++++++++++++++ option.go | 15 ++++ tests/imap_limits_test.go | 113 ++++++++++++++++++++++++++ tests/server_test.go | 16 ++++ 14 files changed, 351 insertions(+), 10 deletions(-) create mode 100644 limits/imap.go create mode 100644 tests/imap_limits_test.go diff --git a/builder.go b/builder.go index e3d184b3..82558953 100644 --- a/builder.go +++ b/builder.go @@ -2,6 +2,7 @@ package gluon import ( "crypto/tls" + "github.com/ProtonMail/gluon/limits" "io" "os" "path/filepath" @@ -29,6 +30,7 @@ type serverBuilder struct { storeBuilder store.Builder reporter reporter.Reporter disableParallelism bool + imapLimits limits.IMAP } func newBuilder() (*serverBuilder, error) { @@ -37,7 +39,8 @@ func newBuilder() (*serverBuilder, error) { cmdExecProfBuilder: &profiling.NullCmdExecProfilerBuilder{}, storeBuilder: &store.OnDiskStoreBuilder{}, reporter: &reporter.NullReporter{}, - idleBulkTime: time.Duration(500 * time.Millisecond), + idleBulkTime: 500 * time.Millisecond, + imapLimits: limits.DefaultLimits(), }, nil } @@ -60,6 +63,7 @@ func (builder *serverBuilder) build() (*Server, error) { builder.storeBuilder, builder.delim, builder.loginJailTime, + builder.imapLimits, ) if err != nil { return nil, err diff --git a/internal/backend/backend.go b/internal/backend/backend.go index 25b1a420..f6d695e2 100644 --- a/internal/backend/backend.go +++ b/internal/backend/backend.go @@ -3,6 +3,7 @@ package backend import ( "context" "fmt" + "github.com/ProtonMail/gluon/limits" "path/filepath" "sync" "sync/atomic" @@ -44,15 +45,18 @@ type Backend struct { loginErrorCount int32 loginLock sync.Mutex loginWG sync.WaitGroup + + imapLimits limits.IMAP } -func New(dir string, storeBuilder store.Builder, delim string, loginJailTime time.Duration) (*Backend, error) { +func New(dir string, storeBuilder store.Builder, delim string, loginJailTime time.Duration, imapLimits limits.IMAP) (*Backend, error) { return &Backend{ dir: dir, delim: delim, users: make(map[string]*user), storeBuilder: storeBuilder, loginJailTime: loginJailTime, + imapLimits: imapLimits, }, nil } @@ -82,7 +86,7 @@ func (b *Backend) AddUser(ctx context.Context, userID string, conn connector.Con return err } - user, err := newUser(ctx, userID, db, conn, storeBuilder, b.delim) + user, err := newUser(ctx, userID, db, conn, storeBuilder, b.delim, b.imapLimits) if err != nil { return err } diff --git a/internal/backend/connector_updates.go b/internal/backend/connector_updates.go index e7ec13d0..36a7bfd4 100644 --- a/internal/backend/connector_updates.go +++ b/internal/backend/connector_updates.go @@ -71,7 +71,17 @@ func (user *user) applyMailboxCreated(ctx context.Context, update *imap.MailboxC return fmt.Errorf("attempting to create protected mailbox (recovery)") } + if err := user.imapLimits.CheckUIDValidity(user.globalUIDValidity); err != nil { + return err + } + if exists, err := db.ReadResult(ctx, user.db, func(ctx context.Context, client *ent.Client) (bool, error) { + if mailboxCount, err := db.GetMailboxCount(ctx, client); err != nil { + return false, err + } else if err := user.imapLimits.CheckMailBoxCount(mailboxCount); err != nil { + return false, err + } + return db.MailboxExistsWithRemoteID(ctx, client, update.Mailbox.ID) }); err != nil { return err @@ -426,7 +436,7 @@ func (user *user) setMessageMailboxes(ctx context.Context, tx *ent.Tx, messageID // applyMessagesAddedToMailbox adds the messages to the given mailbox. func (user *user) applyMessagesAddedToMailbox(ctx context.Context, tx *ent.Tx, mboxID imap.InternalMailboxID, messageIDs []imap.InternalMessageID) ([]db.UIDWithFlags, error) { - messageUIDs, update, err := state.AddMessagesToMailbox(ctx, tx, mboxID, messageIDs, nil) + messageUIDs, update, err := state.AddMessagesToMailbox(ctx, tx, mboxID, messageIDs, nil, user.imapLimits) if err != nil { return nil, err } @@ -636,7 +646,7 @@ func (user *user) applyMessageUpdated(ctx context.Context, update *imap.MessageU return err } - _, update, err := state.AddMessagesToMailbox(ctx, tx, internalMBoxID, []imap.InternalMessageID{newInternalID}, nil) + _, update, err := state.AddMessagesToMailbox(ctx, tx, internalMBoxID, []imap.InternalMessageID{newInternalID}, nil, user.imapLimits) if err != nil { return err } diff --git a/internal/backend/user.go b/internal/backend/user.go index f8e5c0d3..bab3301f 100644 --- a/internal/backend/user.go +++ b/internal/backend/user.go @@ -3,6 +3,7 @@ package backend import ( "context" "fmt" + "github.com/ProtonMail/gluon/limits" "sync" "github.com/ProtonMail/gluon/connector" @@ -39,6 +40,8 @@ type user struct { globalUIDValidity imap.UID recoveryMailboxID imap.InternalMailboxID + + imapLimits limits.IMAP } func newUser( @@ -48,6 +51,7 @@ func newUser( conn connector.Connector, store store.Store, delimiter string, + imapLimits limits.IMAP, ) (*user, error) { if err := database.Init(ctx); err != nil { return nil, err @@ -85,6 +89,8 @@ func newUser( globalUIDValidity: conn.GetUIDValidity(), recoveryMailboxID: recoveryMBox.ID, + + imapLimits: imapLimits, } if err := user.deleteAllMessagesMarkedDeleted(ctx); err != nil { @@ -209,6 +215,7 @@ func (user *user) newState() (*state.State, error) { newState := state.NewState( newStateUserInterfaceImpl(user, newStateConnectorImpl(user)), user.delimiter, + user.imapLimits, ) user.states[newState.StateID] = newState diff --git a/internal/db/mailbox.go b/internal/db/mailbox.go index a6b556fc..1a74acec 100644 --- a/internal/db/mailbox.go +++ b/internal/db/mailbox.go @@ -427,3 +427,30 @@ func IsMessageInMailbox(ctx context.Context, client *ent.Client, mboxID imap.Int s.Select(uid.MessageColumn) }).Exist(ctx) } + +func GetMailboxCount(ctx context.Context, client *ent.Client) (int, error) { + return client.Mailbox.Query().Count(ctx) +} + +func GetMailboxUID(ctx context.Context, client *ent.Client, mboxID imap.InternalMailboxID) (imap.UID, error) { + mbox, err := client.Mailbox.Query().Where(mailbox.ID(mboxID)).Select(mailbox.FieldUIDNext).Only(ctx) + if err != nil { + return 0, err + } + + return mbox.UIDNext, err +} + +func GetMailboxMessageCountAndUID(ctx context.Context, client *ent.Client, mboxID imap.InternalMailboxID) (int, imap.UID, error) { + messageCount, err := GetMailboxMessageCount(ctx, client, mboxID) + if err != nil { + return 0, 0, err + } + + uid, err := GetMailboxUID(ctx, client, mboxID) + if err != nil { + return 0, 0, err + } + + return messageCount, uid, err +} diff --git a/internal/session/session.go b/internal/session/session.go index 260e21b1..9dcab6cb 100644 --- a/internal/session/session.go +++ b/internal/session/session.go @@ -18,6 +18,7 @@ import ( "github.com/ProtonMail/gluon/internal/backend" "github.com/ProtonMail/gluon/internal/response" "github.com/ProtonMail/gluon/internal/state" + "github.com/ProtonMail/gluon/limits" "github.com/ProtonMail/gluon/liner" "github.com/ProtonMail/gluon/profiling" "github.com/ProtonMail/gluon/reporter" @@ -82,6 +83,8 @@ type Session struct { /// errorCount error counter errorCount int + + imapLimits limits.IMAP } func New( diff --git a/internal/state/actions.go b/internal/state/actions.go index cf09baa0..30139619 100644 --- a/internal/state/actions.go +++ b/internal/state/actions.go @@ -262,7 +262,7 @@ func (state *State) actionAddMessagesToMailbox( st = state } - messageUIDs, update, err := AddMessagesToMailbox(ctx, tx, mboxID.InternalID, internalIDs, st) + messageUIDs, update, err := AddMessagesToMailbox(ctx, tx, mboxID.InternalID, internalIDs, st, state.imapLimits) if err != nil { return nil, err } @@ -286,7 +286,7 @@ func (state *State) actionAddRecoveredMessagesToMailbox( return nil, nil, err } - return AddMessagesToMailbox(ctx, tx, mboxID.InternalID, internalIDs, state) + return AddMessagesToMailbox(ctx, tx, mboxID.InternalID, internalIDs, state, state.imapLimits) } func (state *State) actionImportRecoveredMessage( @@ -536,7 +536,7 @@ func (state *State) actionMoveMessages( return nil, err } - messageUIDs, updates, err := MoveMessagesFromMailbox(ctx, tx, mboxFromID.InternalID, mboxToID.InternalID, internalIDs, state) + messageUIDs, updates, err := MoveMessagesFromMailbox(ctx, tx, mboxFromID.InternalID, mboxToID.InternalID, internalIDs, state, state.imapLimits) if err != nil { return nil, err } diff --git a/internal/state/mailbox.go b/internal/state/mailbox.go index 3af76c0b..fed7c5d4 100644 --- a/internal/state/mailbox.go +++ b/internal/state/mailbox.go @@ -146,6 +146,24 @@ func (m *Mailbox) GetMessagesWithoutFlagCount(flag string) int { } func (m *Mailbox) AppendRegular(ctx context.Context, literal []byte, flags imap.FlagSet, date time.Time) (imap.UID, error) { + if err := m.state.db().Read(ctx, func(ctx context.Context, client *ent.Client) error { + if messageCount, uid, err := db.GetMailboxMessageCountAndUID(ctx, client, m.snap.mboxID.InternalID); err != nil { + return err + } else { + if err := m.state.imapLimits.CheckMailBoxMessageCount(messageCount, 1); err != nil { + return err + } + + if err := m.state.imapLimits.CheckUIDCount(uid, 1); err != nil { + return err + } + } + + return nil + }); err != nil { + return 0, err + } + var appendIntoDrafts bool attr, err := m.Attributes(ctx) diff --git a/internal/state/state.go b/internal/state/state.go index 829249ff..23657c82 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/ProtonMail/gluon/limits" "strings" "sync/atomic" @@ -45,6 +46,8 @@ type State struct { // invalid indicates whether this state became invalid and a clients needs to disconnect. invalid bool + + imapLimits limits.IMAP } var stateIDGenerator int64 @@ -53,7 +56,7 @@ func nextStateID() StateID { return StateID(atomic.AddInt64(&stateIDGenerator, 1)) } -func NewState(user UserInterface, delimiter string) *State { +func NewState(user UserInterface, delimiter string, imapLimits limits.IMAP) *State { return &State{ user: user, StateID: nextStateID(), @@ -61,6 +64,7 @@ func NewState(user UserInterface, delimiter string) *State { snap: nil, delimiter: delimiter, updatesQueue: queue.NewQueuedChannel[Update](32, 128), + imapLimits: imapLimits, } } @@ -164,6 +168,10 @@ func (state *State) Examine(ctx context.Context, name string, fn func(*Mailbox) } func (state *State) Create(ctx context.Context, name string) error { + if err := state.imapLimits.CheckUIDValidity(state.user.GetGlobalUIDValidity()); err != nil { + return err + } + if strings.HasPrefix(strings.ToLower(name), ids.GluonRecoveryMailboxNameLowerCase) { return fmt.Errorf("operation not allowed") } @@ -179,6 +187,12 @@ func (state *State) Create(ctx context.Context, name string) error { } mboxesToCreate, err := db.ReadResult(ctx, state.db(), func(ctx context.Context, client *ent.Client) ([]string, error) { + if mailboxCount, err := db.GetMailboxCount(ctx, client); err != nil { + return nil, err + } else if err := state.imapLimits.CheckMailBoxCount(mailboxCount); err != nil { + return nil, err + } + var mboxesToCreate []string // If the mailbox name is suffixed with the server's hierarchy separator, remove the separator and still create // the mailbox diff --git a/internal/state/updates_mailbox.go b/internal/state/updates_mailbox.go index 8d25e6f5..19abff8c 100644 --- a/internal/state/updates_mailbox.go +++ b/internal/state/updates_mailbox.go @@ -2,6 +2,7 @@ package state import ( "context" + "github.com/ProtonMail/gluon/limits" "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/gluon/internal/db" @@ -19,7 +20,21 @@ func MoveMessagesFromMailbox( mboxFromID, mboxToID imap.InternalMailboxID, messageIDs []imap.InternalMessageID, s *State, + imapLimits limits.IMAP, ) ([]db.UIDWithFlags, []Update, error) { + messageCount, uid, err := db.GetMailboxMessageCountAndUID(ctx, tx.Client(), mboxToID) + if err != nil { + return nil, nil, err + } + + if err := imapLimits.CheckMailBoxMessageCount(messageCount, len(messageIDs)); err != nil { + return nil, nil, err + } + + if err := imapLimits.CheckUIDCount(uid, len(messageIDs)); err != nil { + return nil, nil, err + } + if mboxFromID != mboxToID { if err := db.RemoveMessagesFromMailbox(ctx, tx, messageIDs, mboxFromID); err != nil { return nil, nil, err @@ -50,7 +65,20 @@ func MoveMessagesFromMailbox( } // AddMessagesToMailbox adds the messages to the given mailbox. -func AddMessagesToMailbox(ctx context.Context, tx *ent.Tx, mboxID imap.InternalMailboxID, messageIDs []imap.InternalMessageID, s *State) ([]db.UIDWithFlags, Update, error) { +func AddMessagesToMailbox(ctx context.Context, tx *ent.Tx, mboxID imap.InternalMailboxID, messageIDs []imap.InternalMessageID, s *State, imapLimits limits.IMAP) ([]db.UIDWithFlags, Update, error) { + messageCount, uid, err := db.GetMailboxMessageCountAndUID(ctx, tx.Client(), mboxID) + if err != nil { + return nil, nil, err + } + + if err := imapLimits.CheckMailBoxMessageCount(messageCount, len(messageIDs)); err != nil { + return nil, nil, err + } + + if err := imapLimits.CheckUIDCount(uid, len(messageIDs)); err != nil { + return nil, nil, err + } + messageUIDs, err := db.AddMessagesToMailbox(ctx, tx, messageIDs, mboxID) if err != nil { return nil, nil, err diff --git a/limits/imap.go b/limits/imap.go new file mode 100644 index 00000000..d1ed7979 --- /dev/null +++ b/limits/imap.go @@ -0,0 +1,82 @@ +package limits + +import ( + "errors" + "fmt" + "github.com/ProtonMail/gluon/imap" + "math" +) + +// IMAP contains configurable upper limits that can be enforced by the Gluon server. +type IMAP struct { + maxMailboxCount int + maxMessageCountPerMailbox int + maxUIDValidity int + maxUID int +} + +func (i IMAP) CheckMailBoxCount(mailboxCount int) error { + if mailboxCount >= i.maxMailboxCount { + return ErrMaxMailboxCountReached + } + + return nil +} + +func (i IMAP) CheckMailBoxMessageCount(existingCount int, newCount int) error { + nextMessageCount := existingCount + newCount + + if nextMessageCount > i.maxMessageCountPerMailbox || nextMessageCount < existingCount { + return ErrMaxMailboxMessageCountReached + } + + return nil +} + +func (i IMAP) CheckUIDCount(existingUID imap.UID, newCount int) error { + nextUIDCount := int(existingUID) + newCount + + if nextUIDCount > i.maxUID || nextUIDCount < int(existingUID) { + return ErrMaxUIDReached + } + + return nil +} + +func (i IMAP) CheckUIDValidity(uid imap.UID) error { + if int(uid) >= i.maxUIDValidity { + return ErrMaxUIDValidityReached + } + + return nil +} + +func DefaultLimits() IMAP { + return IMAP{ + maxMailboxCount: math.MaxUint32, + maxMessageCountPerMailbox: math.MaxUint32, + maxUIDValidity: math.MaxUint32, + maxUID: math.MaxUint32, + } +} + +func NewIMAPLimits(maxMailboxCount uint32, maxMessageCount uint32, maxUID imap.UID, maxUIDValidity imap.UID) IMAP { + return IMAP{ + maxMailboxCount: int(maxMailboxCount), + maxMessageCountPerMailbox: int(maxMessageCount), + maxUIDValidity: int(maxUID), + maxUID: int(maxUIDValidity), + } +} + +var ErrMaxMailboxCountReached = fmt.Errorf("max mailbox count reached") +var ErrMaxMailboxMessageCountReached = fmt.Errorf("max mailbox message count reached") +var ErrMaxUIDReached = fmt.Errorf("max UID value reached") +var ErrMaxUIDValidityReached = fmt.Errorf("max UIDValidity value reached") + +func IsIMAPLimitErr(err error) bool { + return errors.Is(err, ErrMaxUIDValidityReached) || + errors.Is(err, ErrMaxMailboxCountReached) || + errors.Is(err, ErrMaxUIDReached) || + errors.Is(err, ErrMaxMailboxMessageCountReached) +} diff --git a/option.go b/option.go index fef706bc..4ccf0325 100644 --- a/option.go +++ b/option.go @@ -2,6 +2,7 @@ package gluon import ( "crypto/tls" + limits2 "github.com/ProtonMail/gluon/limits" "io" "time" @@ -177,3 +178,17 @@ func (withDisableParallelism) config(builder *serverBuilder) { func WithDisableParallelism() Option { return &withDisableParallelism{} } + +type withIMAPLimits struct { + limits limits2.IMAP +} + +func (w withIMAPLimits) config(builder *serverBuilder) { + builder.imapLimits = w.limits +} + +func WithIMAPLimits(limits limits2.IMAP) Option { + return &withIMAPLimits{ + limits: limits, + } +} diff --git a/tests/imap_limits_test.go b/tests/imap_limits_test.go new file mode 100644 index 00000000..26cb839c --- /dev/null +++ b/tests/imap_limits_test.go @@ -0,0 +1,113 @@ +package tests + +import ( + "github.com/ProtonMail/gluon/limits" + goimap "github.com/emersion/go-imap" + "github.com/emersion/go-imap/client" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +func testIMAPLimits() limits.IMAP { + return limits.NewIMAPLimits( + 3, // Needs to be at least 3 due to INBOX and Recovered Messages. + 1, + 2, // Set to 2 so we can create a least on mailbox, delete it and then create a second to trigger the UID check. + 2, // Set to 2 so we can add at least one message, delete it and then add a second message to trigger the UID check. + ) +} + +func TestMaxMailboxLimitRespected(t *testing.T) { + runOneToOneTestClientWithAuth(t, defaultServerOptions(t, withIMAPLimits(testIMAPLimits())), func(client *client.Client, session *testSession) { + require.NoError(t, client.Create("mbox1")) + require.Error(t, client.Create("mbox2")) + }) +} + +func TestMaxMessageLimitRespected_Append(t *testing.T) { + runOneToOneTestClientWithAuth(t, defaultServerOptions(t, withIMAPLimits(testIMAPLimits())), func(client *client.Client, session *testSession) { + require.NoError(t, doAppendWithClient(client, "INBOX", "To: Foo@bar.com", time.Now())) + require.Error(t, doAppendWithClient(client, "INBOX", "To: Bar@bar.com", time.Now())) + }) +} + +func TestMaxUIDLimitRespected_Append(t *testing.T) { + runOneToOneTestClientWithAuth(t, defaultServerOptions(t, withIMAPLimits(testIMAPLimits())), func(client *client.Client, session *testSession) { + require.NoError(t, doAppendWithClient(client, "INBOX", "To: Foo@bar.com", time.Now())) + _, err := client.Select("INBOX", false) + require.NoError(t, err) + require.NoError(t, client.Store(createSeqSet("1"), goimap.AddFlags, []interface{}{goimap.DeletedFlag}, nil)) + require.NoError(t, client.Expunge(nil)) + // Append should fail now as we triggered max UID validity error. + require.Error(t, doAppendWithClient(client, "INBOX", "To: Bar@bar.com", time.Now())) + }) +} + +func TestMaxMessageLimitRespected_Copy(t *testing.T) { + runOneToOneTestClientWithAuth(t, defaultServerOptions(t, withIMAPLimits(testIMAPLimits())), func(client *client.Client, session *testSession) { + require.NoError(t, client.Create("mbox1")) + require.NoError(t, doAppendWithClient(client, "mbox1", "To: Foo@bar.com", time.Now())) + require.NoError(t, doAppendWithClient(client, "INBOX", "To: Bar@bar.com", time.Now())) + _, err := client.Select("INBOX", false) + require.NoError(t, err) + require.Error(t, client.Copy(createSeqSet("1"), "mbox1")) + }) +} + +func TestMaxUIDLimitRespected_Copy(t *testing.T) { + runOneToOneTestClientWithAuth(t, defaultServerOptions(t, withIMAPLimits(testIMAPLimits())), func(client *client.Client, session *testSession) { + require.NoError(t, client.Create("mbox1")) + require.NoError(t, doAppendWithClient(client, "mbox1", "To: Foo@bar.com", time.Now())) + require.NoError(t, doAppendWithClient(client, "INBOX", "To: Bar@bar.com", time.Now())) + + // Delete existing message in mbox1 to trigget UID validity check + _, err := client.Select("mbox1", false) + require.NoError(t, err) + require.NoError(t, client.Store(createSeqSet("1"), goimap.AddFlags, []interface{}{goimap.DeletedFlag}, nil)) + require.NoError(t, client.Expunge(nil)) + + // Try to copy message to mbox + _, err = client.Select("INBOX", false) + require.NoError(t, err) + require.Error(t, client.Copy(createSeqSet("1"), "mbox1")) + }) +} + +func TestMaxMessageLimitRespected_Move(t *testing.T) { + runOneToOneTestClientWithAuth(t, defaultServerOptions(t, withIMAPLimits(testIMAPLimits())), func(client *client.Client, session *testSession) { + require.NoError(t, client.Create("mbox1")) + require.NoError(t, doAppendWithClient(client, "mbox1", "To: Foo@bar.com", time.Now())) + require.NoError(t, doAppendWithClient(client, "INBOX", "To: Bar@bar.com", time.Now())) + _, err := client.Select("INBOX", false) + require.NoError(t, err) + require.Error(t, client.Move(createSeqSet("1"), "mbox1")) + }) +} + +func TestMaxUIDLimitRespected_Move(t *testing.T) { + runOneToOneTestClientWithAuth(t, defaultServerOptions(t, withIMAPLimits(testIMAPLimits())), func(client *client.Client, session *testSession) { + require.NoError(t, client.Create("mbox1")) + require.NoError(t, doAppendWithClient(client, "mbox1", "To: Foo@bar.com", time.Now())) + require.NoError(t, doAppendWithClient(client, "INBOX", "To: Bar@bar.com", time.Now())) + + // Delete existing message in mbox1 to trigget UID validity check + _, err := client.Select("mbox1", false) + require.NoError(t, err) + require.NoError(t, client.Store(createSeqSet("1"), goimap.AddFlags, []interface{}{goimap.DeletedFlag}, nil)) + require.NoError(t, client.Expunge(nil)) + + // Try to copy message to mbox + _, err = client.Select("INBOX", false) + require.NoError(t, err) + require.Error(t, client.Move(createSeqSet("1"), "mbox1")) + }) +} + +func TestMaxUIDValidityLimitRespected(t *testing.T) { + runOneToOneTestClientWithAuth(t, defaultServerOptions(t, withIMAPLimits(testIMAPLimits())), func(client *client.Client, session *testSession) { + require.NoError(t, client.Create("mbox1")) + require.NoError(t, client.Delete("mbox1")) + require.Error(t, client.Create("mbox2")) + }) +} diff --git a/tests/server_test.go b/tests/server_test.go index d000f807..83718bc7 100644 --- a/tests/server_test.go +++ b/tests/server_test.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/hex" "fmt" + "github.com/ProtonMail/gluon/limits" "net" "path/filepath" "testing" @@ -71,6 +72,7 @@ type serverOptions struct { storeBuilder store.Builder connectorBuilder connectorBuilder disableParallelism bool + imapLimits limits.IMAP } func (s *serverOptions) defaultUsername() string { @@ -145,6 +147,14 @@ func (disableParallelism) apply(options *serverOptions) { options.disableParallelism = true } +type imapLimits struct { + limits limits.IMAP +} + +func (m imapLimits) apply(options *serverOptions) { + options.imapLimits = m.limits +} + func withIdleBulkTime(idleBulkTime time.Duration) serverOption { return &idleBulkTimeOption{idleBulkTime: idleBulkTime} } @@ -173,6 +183,10 @@ func withDisableParallelism() serverOption { return &disableParallelism{} } +func withIMAPLimits(limits limits.IMAP) serverOption { + return &imapLimits{limits: limits} +} + func defaultServerOptions(tb testing.TB, modifiers ...serverOption) *serverOptions { options := &serverOptions{ credentials: []credentials{{ @@ -185,6 +199,7 @@ func defaultServerOptions(tb testing.TB, modifiers ...serverOption) *serverOptio idleBulkTime: time.Duration(500 * time.Millisecond), storeBuilder: &store.OnDiskStoreBuilder{}, connectorBuilder: &dummyConnectorBuilder{}, + imapLimits: limits.DefaultLimits(), } for _, op := range modifiers { @@ -231,6 +246,7 @@ func runServer(tb testing.TB, options *serverOptions, tests func(session *testSe gluon.WithIdleBulkTime(options.idleBulkTime), gluon.WithStoreBuilder(options.storeBuilder), gluon.WithReporter(reporter), + gluon.WithIMAPLimits(options.imapLimits), } if options.disableParallelism {