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 {