GODT-1152: Correctly resolve wildcard sequence/UID set

This commit is contained in:
James Houlahan
2021-04-30 10:35:34 +00:00
committed by Jakub Cuth
parent 323303a98b
commit 27cfda680d
5 changed files with 129 additions and 31 deletions

View File

@ -526,18 +526,6 @@ func (im *imapMailbox) listMessages(isUID bool, seqSet *imap.SeqSet, items []ima
return err return err
} }
// From RFC: UID range of 559:* always includes the UID of the last message
// in the mailbox, even if 559 is higher than any assigned UID value.
// See: https://tools.ietf.org/html/rfc3501#page-61
if isUID && seqSet.Dynamic() && len(apiIDs) == 0 {
l.Debug("Requesting empty UID dynamic fetch, adding latest message")
apiID, err := im.storeMailbox.GetLatestAPIID()
if err != nil {
return nil
}
apiIDs = []string{apiID}
}
input := make([]interface{}, len(apiIDs)) input := make([]interface{}, len(apiIDs))
for i, apiID := range apiIDs { for i, apiID := range apiIDs {
input[i] = apiID input[i] = apiID

View File

@ -36,23 +36,36 @@ import (
func (storeMailbox *Mailbox) GetAPIIDsFromUIDRange(start, stop uint32) (apiIDs []string, err error) { func (storeMailbox *Mailbox) GetAPIIDsFromUIDRange(start, stop uint32) (apiIDs []string, err error) {
err = storeMailbox.db().View(func(tx *bolt.Tx) error { err = storeMailbox.db().View(func(tx *bolt.Tx) error {
b := storeMailbox.txGetIMAPIDsBucket(tx) b := storeMailbox.txGetIMAPIDsBucket(tx)
c := b.Cursor()
// If the start range is a wildcard, the range can only refer to the last message in the mailbox.
if start == 0 {
_, apiID := c.Last()
apiIDs = append(apiIDs, string(apiID))
return nil
}
// Resolve the stop value to be the final UID in the mailbox.
if stop == 0 { if stop == 0 {
// A null stop means no stop. stop = storeMailbox.txGetFinalUID(b)
stop = ^uint32(0) }
// After resolving the stop value, it might be less than start so we sort it.
if start > stop {
start, stop = stop, start
} }
startb := itob(start) startb := itob(start)
stopb := itob(stop) stopb := itob(stop)
c := b.Cursor()
for k, v := c.Seek(startb); k != nil && bytes.Compare(k, stopb) <= 0; k, v = c.Next() { for k, v := c.Seek(startb); k != nil && bytes.Compare(k, stopb) <= 0; k, v = c.Next() {
apiIDs = append(apiIDs, string(v)) apiIDs = append(apiIDs, string(v))
} }
return nil return nil
}) })
return
return apiIDs, err
} }
// GetAPIIDsFromSequenceRange returns API IDs by IMAP sequence number range. // GetAPIIDsFromSequenceRange returns API IDs by IMAP sequence number range.
@ -60,28 +73,47 @@ func (storeMailbox *Mailbox) GetAPIIDsFromSequenceRange(start, stop uint32) (api
err = storeMailbox.db().View(func(tx *bolt.Tx) error { err = storeMailbox.db().View(func(tx *bolt.Tx) error {
b := storeMailbox.txGetIMAPIDsBucket(tx) b := storeMailbox.txGetIMAPIDsBucket(tx)
c := b.Cursor() c := b.Cursor()
// If the start range is a wildcard, the range can only refer to the last message in the mailbox.
if start == 0 {
_, apiID := c.Last()
apiIDs = append(apiIDs, string(apiID))
return nil
}
var i uint32 var i uint32
for k, v := c.First(); k != nil; k, v = c.Next() { for k, v := c.First(); k != nil; k, v = c.Next() {
i++ i++
if i < start { if i < start {
continue continue
} }
if stop > 0 && i > stop { if stop > 0 && i > stop {
break break
} }
apiIDs = append(apiIDs, string(v)) apiIDs = append(apiIDs, string(v))
} }
if stop == 0 && len(apiIDs) == 0 {
if _, apiID := c.Last(); len(apiID) > 0 {
apiIDs = append(apiIDs, string(apiID))
}
}
return nil return nil
}) })
return
return apiIDs, err
} }
// GetLatestAPIID returns the latest message API ID which still exists. // GetLatestAPIID returns the latest message API ID which still exists.
// Info: not the latest IMAP UID which can be already removed. // Info: not the latest IMAP UID which can be already removed.
func (storeMailbox *Mailbox) GetLatestAPIID() (apiID string, err error) { func (storeMailbox *Mailbox) GetLatestAPIID() (apiID string, err error) {
err = storeMailbox.db().View(func(tx *bolt.Tx) error { err = storeMailbox.db().View(func(tx *bolt.Tx) error {
b := storeMailbox.txGetAPIIDsBucket(tx) c := storeMailbox.txGetAPIIDsBucket(tx).Cursor()
c := b.Cursor()
lastAPIID, _ := c.Last() lastAPIID, _ := c.Last()
apiID = string(lastAPIID) apiID = string(lastAPIID)
if apiID == "" { if apiID == "" {
@ -283,3 +315,8 @@ func (storeMailbox *Mailbox) GetUIDByHeader(header *mail.Header) (foundUID uint3
return foundUID return foundUID
} }
func (storeMailbox *Mailbox) txGetFinalUID(b *bolt.Bucket) uint32 {
uid, _ := b.Cursor().Last()
return btoi(uid)
}

View File

@ -56,7 +56,7 @@ func checkMailboxMessageIDs(t *testing.T, m *mocksForStore, mailboxLabel string,
storeAddress := m.store.addresses[addrID1] storeAddress := m.store.addresses[addrID1]
storeMailbox := storeAddress.mailboxes[mailboxLabel] storeMailbox := storeAddress.mailboxes[mailboxLabel]
ids, err := storeMailbox.GetAPIIDsFromSequenceRange(0, uint32(len(wantIDs))) ids, err := storeMailbox.GetAPIIDsFromSequenceRange(1, uint32(len(wantIDs)))
require.Nil(t, err) require.Nil(t, err)
idx := 0 idx := 0

View File

@ -11,7 +11,15 @@ Feature: IMAP fetch messages
Then IMAP response is "OK" Then IMAP response is "OK"
And IMAP response has 10 messages And IMAP response has 10 messages
Scenario: Fetch first few message of inbox Scenario: Fetch of inbox by UID
Given there are 10 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 fetches by UID "1:*"
Then IMAP response is "OK"
And IMAP response has 10 messages
Scenario: Fetch first few messages of inbox
Given there are 10 messages in mailbox "INBOX" for "user" Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user" And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX" And there is IMAP client selected in "INBOX"
@ -19,6 +27,78 @@ Feature: IMAP fetch messages
Then IMAP response is "OK" Then IMAP response is "OK"
And IMAP response has 5 messages And IMAP response has 5 messages
Scenario: Fetch first few messages of inbox
Given there are 10 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 fetches by UID "1:5"
Then IMAP response is "OK"
And IMAP response has 5 messages
Scenario: Fetch last few messages of inbox using wildcard
Given there are 10 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 fetches "6:*"
Then IMAP response is "OK"
And IMAP response has 5 messages
Scenario: Fetch last few messages of inbox using wildcard by UID
Given there are 10 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 fetches by UID "6:*"
Then IMAP response is "OK"
And IMAP response has 5 messages
Scenario: Fetch last message of inbox using wildcard
Given there are 10 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 fetches "*"
Then IMAP response is "OK"
And IMAP response has 1 message
Scenario: Fetch last message of inbox using wildcard by UID
Given there are 10 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 fetches by UID "*"
Then IMAP response is "OK"
And IMAP response has 1 message
Scenario: Fetch backwards range using wildcard
Given there are 10 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 fetches "*:1"
Then IMAP response is "OK"
And IMAP response has 10 messages
Scenario: Fetch backwards range using wildcard by UID
Given there are 10 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 fetches by UID "*:1"
Then IMAP response is "OK"
And IMAP response has 10 messages
Scenario: Fetch overshot range using wildcard returns last message
Given there are 10 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 fetches "20:*"
Then IMAP response is "OK"
And IMAP response has 1 message
Scenario: Fetch overshot range using wildcard by UID returns last message
Given there are 10 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 fetches by UID "20:*"
Then IMAP response is "OK"
And IMAP response has 1 message
Scenario: Fetch of custom mailbox Scenario: Fetch of custom mailbox
Given there are 10 messages in mailbox "Folders/mbox" for "user" Given there are 10 messages in mailbox "Folders/mbox" for "user"
And there is IMAP client logged in as "user" And there is IMAP client logged in as "user"
@ -27,7 +107,8 @@ Feature: IMAP fetch messages
Then IMAP response is "OK" Then IMAP response is "OK"
And IMAP response has 10 messages And IMAP response has 10 messages
Scenario: Fetch of emtpy mailbox # This test is wrong! RFC says it should return "BAD" (GODT-1153).
Scenario: Fetch of empty mailbox
Given there is IMAP client logged in as "user" Given there is IMAP client logged in as "user"
And there is IMAP client selected in "Folders/mbox" And there is IMAP client selected in "Folders/mbox"
When IMAP client fetches "1:*" When IMAP client fetches "1:*"
@ -42,14 +123,6 @@ Feature: IMAP fetch messages
Then IMAP response is "OK" Then IMAP response is "OK"
And IMAP response has 100 messages And IMAP response has 100 messages
Scenario: Fetch returns alsways latest messages
Given there are 10 messages in mailbox "Folders/mbox" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "Folders/mbox"
When IMAP client fetches by UID "11:*"
Then IMAP response is "OK"
And IMAP response has 1 message
Scenario: Fetch returns also messages that are marked as deleted Scenario: Fetch returns also messages that are marked as deleted
Given there are messages in mailbox "Folders/mbox" for "user" Given there are messages in mailbox "Folders/mbox" for "user"
| from | to | subject | body | read | starred | deleted | | from | to | subject | body | read | starred | deleted |

View File

@ -128,7 +128,7 @@ func mailboxForAddressOfUserHasMessages(mailboxName, bddAddressID, bddUserID str
if err != nil { if err != nil {
return internalError(err, "getting store mailbox") return internalError(err, "getting store mailbox")
} }
apiIDs, err := mailbox.GetAPIIDsFromSequenceRange(0, 1000) apiIDs, err := mailbox.GetAPIIDsFromSequenceRange(1, 1000)
if err != nil { if err != nil {
return internalError(err, "getting API IDs from sequence range") return internalError(err, "getting API IDs from sequence range")
} }