Support of UID EXPUNGE

This commit is contained in:
Michal Horejsek
2021-01-20 13:16:27 +01:00
parent 07c100bd66
commit 1909ceed67
6 changed files with 101 additions and 23 deletions

View File

@ -191,7 +191,20 @@ func (im *imapMailbox) Expunge() error {
im.user.backend.setUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage) im.user.backend.setUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
defer im.user.backend.unsetUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage) defer im.user.backend.unsetUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
return im.storeMailbox.RemoveDeleted() return im.storeMailbox.RemoveDeleted(nil)
}
// UIDExpunge permanently removes messages that have the \Deleted flag set
// and UID passed from SeqSet from the currently selected mailbox.
func (im *imapMailbox) UIDExpunge(seqSet *imap.SeqSet) error {
im.user.backend.setUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
defer im.user.backend.unsetUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
messageIDs, err := im.apiIDsFromSeqSet(true, seqSet)
if err != nil || len(messageIDs) == 0 {
return err
}
return im.storeMailbox.RemoveDeleted(messageIDs)
} }
func (im *imapMailbox) ListQuotas() ([]string, error) { func (im *imapMailbox) ListQuotas() ([]string, error) {

View File

@ -89,7 +89,7 @@ type storeMailboxProvider interface {
MarkMessagesDeleted(apiID []string) error MarkMessagesDeleted(apiID []string) error
MarkMessagesUndeleted(apiID []string) error MarkMessagesUndeleted(apiID []string) error
ImportMessage(msg *pmapi.Message, body []byte, labelIDs []string) error ImportMessage(msg *pmapi.Message, body []byte, labelIDs []string) error
RemoveDeleted() error RemoveDeleted(apiIDs []string) error
} }
type storeMessageProvider interface { type storeMessageProvider interface {

View File

@ -116,40 +116,70 @@ func (os *OrderedSeq) String() string {
// UIDExpunge implements server.Handler but Bridge is not supporting // UIDExpunge implements server.Handler but Bridge is not supporting
// UID EXPUNGE with specific UIDs. // UID EXPUNGE with specific UIDs.
type UIDExpungeMailbox interface {
Expunge() error
UIDExpunge(*imap.SeqSet) error
}
type UIDExpunge struct { type UIDExpunge struct {
expunge *server.Expunge SeqSet *imap.SeqSet
} }
func newUIDExpunge() *UIDExpunge { func newUIDExpunge() *UIDExpunge {
return &UIDExpunge{expunge: &server.Expunge{}} return &UIDExpunge{}
} }
func (e *UIDExpunge) Parse(fields []interface{}) error { func (e *UIDExpunge) Parse(fields []interface{}) error {
if len(fields) < 1 { if len(fields) == 0 {
return e.expunge.Parse(fields) return nil // It could be regular EXPUNGE without arguments.
}
if len(fields) > 1 {
return errors.New("too many arguments")
} }
// RFC4315#section-2.1 seqset, ok := fields[0].(string)
// The UID EXPUNGE command permanently removes all messages that both if !ok {
// have the \Deleted flag set and have a UID that is included in the return errors.New("sequence set must be an atom")
// specified sequence set from the currently selected mailbox. If a }
// message either does not have the \Deleted flag set or has a UID var err error
// that is not included in the specified sequence set, it is not e.SeqSet, err = imap.ParseSeqSet(seqset)
// affected. return err
//
// Current implementation supports only deletion of all messages
// marked as deleted. It will probably need mailbox interface change:
// ExpungeUIDs(seqSet). Not sure how to combine with original
// e.expunge.Handle().
return errors.New("UID EXPUNGE with UIDs is not supported")
} }
func (e *UIDExpunge) Handle(conn server.Conn) error { func (e *UIDExpunge) Handle(conn server.Conn) error {
return e.expunge.Handle(conn) mailbox, err := e.getMailbox(conn)
if err != nil {
return err
}
return mailbox.Expunge()
} }
func (e *UIDExpunge) UidHandle(conn server.Conn) error { //nolint[golint] func (e *UIDExpunge) UidHandle(conn server.Conn) error { //nolint[golint]
return e.expunge.Handle(conn) if e.SeqSet == nil {
return errors.New("missing sequence set")
}
mailbox, err := e.getMailbox(conn)
if err != nil {
return err
}
return mailbox.UIDExpunge(e.SeqSet)
}
func (e *UIDExpunge) getMailbox(conn server.Conn) (UIDExpungeMailbox, error) {
ctx := conn.Context()
if ctx.Mailbox == nil {
return nil, server.ErrNoMailboxSelected
}
if ctx.MailboxReadOnly {
return nil, server.ErrMailboxReadOnly
}
mailbox, ok := ctx.Mailbox.(UIDExpungeMailbox)
if !ok {
return nil, errors.New("UID EXPUNGE is not implemented")
}
return mailbox, nil
} }
type extension struct{} type extension struct{}

View File

@ -208,14 +208,35 @@ func (storeMailbox *Mailbox) MarkMessagesUndeleted(apiIDs []string) error {
// If the mailbox is All Mail or All Sent, it does nothing. // If the mailbox is All Mail or All Sent, it does nothing.
// If the mailbox is Trash or Spam and message is not in any other mailbox, messages is deleted. // If the mailbox is Trash or Spam and message is not in any other mailbox, messages is deleted.
// In all other cases the message is only removed from the mailbox. // In all other cases the message is only removed from the mailbox.
func (storeMailbox *Mailbox) RemoveDeleted() error { // If nil is passed, all messages with \Deleted flag are removed.
// In other cases only messages with \Deleted flag and included in the passed list.
func (storeMailbox *Mailbox) RemoveDeleted(apiIDs []string) error {
storeMailbox.log.Trace("Deleting messages") storeMailbox.log.Trace("Deleting messages")
apiIDs, err := storeMailbox.GetDeletedAPIIDs() deletedAPIIDs, err := storeMailbox.GetDeletedAPIIDs()
if err != nil { if err != nil {
return err return err
} }
if apiIDs == nil {
apiIDs = deletedAPIIDs
} else {
filteredAPIIDs := []string{}
for _, apiID := range apiIDs {
found := false
for _, deletedAPIID := range deletedAPIIDs {
if apiID == deletedAPIID {
found = true
break
}
}
if found {
filteredAPIIDs = append(filteredAPIIDs, apiID)
}
}
apiIDs = filteredAPIIDs
}
if len(apiIDs) == 0 { if len(apiIDs) == 0 {
storeMailbox.log.Debug("List to expunge is empty") storeMailbox.log.Debug("List to expunge is empty")
return nil return nil

View File

@ -99,3 +99,16 @@ Feature: IMAP remove messages from mailbox
And there is IMAP client selected in "All Mail" And there is IMAP client selected in "All Mail"
When IMAP client marks message seq "1" as deleted When IMAP client marks message seq "1" as deleted
Then IMAP response is "IMAP error: NO operation not allowed for 'All Mail' folder" Then IMAP response is "IMAP error: NO operation not allowed for 'All Mail' folder"
Scenario: Expunge specific message only
Given there are 5 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client marks message seq "1" as deleted
Then IMAP response is "OK"
When IMAP client marks message seq "2" as deleted
Then IMAP response is "OK"
When IMAP client sends command "UID EXPUNGE 1"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 4 messages
And message "2" in "INBOX" for "user" is marked as deleted

View File

@ -13,6 +13,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-804 Added GUI notification on silent update installed (promt to restart). * GODT-804 Added GUI notification on silent update installed (promt to restart).
* GODT-275 Added option to disable autoupdates in settings (default autoupdate is enabled). * GODT-275 Added option to disable autoupdates in settings (default autoupdate is enabled).
* GODT-874 Added manual triggers to Updater module. * GODT-874 Added manual triggers to Updater module.
* GODT-851 Added support of UID EXPUNGE.
### Changed ### Changed
* GODT-97 Don't log errors caused by SELECT "". * GODT-97 Don't log errors caused by SELECT "".