fix bug in imapserver with rename of inbox, and add consistency checks

renaming inbox is special. the mailbox isn't renamed, but its messages moved to
a new mailbox. we weren't updating the destination mailbox uidnext with the new
messages. the fix not only sets the uidnext correctly, but also renumbers the
uids, starting at 1.

this also adds a consistency check for message uids and mailbox uidnexts, and
for mailbox uidvalidity account nextuidvalidity in "mox verifydata".

this also adds command "mox fixuidmeta" (not listed) that fixes up mailbox uidnext
and account uidvalidity. and command "mox reassignuids" that will renumber the
uids for either one or all mailboxes in an account.
This commit is contained in:
Mechiel Lukkien
2023-06-30 17:19:29 +02:00
parent 1e049a087d
commit 3173da5497
5 changed files with 222 additions and 13 deletions

View File

@ -66,11 +66,21 @@ func TestRename(t *testing.T) {
tc.transactf("ok", `list "" "k*" return (subscribed)`)
tc.xuntagged(imapclient.UntaggedList{Separator: '/', Mailbox: "k"}, imapclient.UntaggedList{Flags: []string{"\\Subscribed"}, Separator: '/', Mailbox: "k/l"}, imapclient.UntaggedList{Separator: '/', Mailbox: "k/l/m"})
// Renaming inbox keeps inbox in existence and does not rename children.
// Renaming inbox keeps inbox in existence, moves messages, and does not rename children.
tc.transactf("ok", "create inbox/a")
// To check if UIDs are renumbered properly, we add UIDs 1 and 2. Expunge 1,
// keeping only 2. Then rename the inbox, which should renumber UID 2 in the old
// inbox to UID 1 in the newly created mailbox.
tc.transactf("ok", "append inbox (\\deleted) \" 1-Jan-2022 10:10:00 +0100\" {1+}\r\nx")
tc.transactf("ok", "append inbox (label1) \" 1-Jan-2022 10:10:00 +0100\" {1+}\r\nx")
tc.transactf("ok", `select inbox`)
tc.transactf("ok", "expunge")
tc.transactf("ok", "rename inbox minbox")
tc.transactf("ok", `list "" (inbox inbox/a minbox)`)
tc.xuntagged(imapclient.UntaggedList{Separator: '/', Mailbox: "Inbox"}, imapclient.UntaggedList{Separator: '/', Mailbox: "Inbox/a"}, imapclient.UntaggedList{Separator: '/', Mailbox: "minbox"})
tc.transactf("ok", `select minbox`)
tc.transactf("ok", `uid fetch 1:* flags`)
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(1), imapclient.FetchFlags{"label1"}}})
// Renaming to new hiearchy that does not have any subscribes.
tc.transactf("ok", "rename minbox w/w")

View File

@ -2060,9 +2060,9 @@ func (c *conn) cmdRename(tag, cmd string, p *parser) {
uidval, err := c.account.NextUIDValidity(tx)
xcheckf(err, "next uid validity")
// Inbox is very special case. Unlike other mailboxes, its children are not moved. And
// unlike a regular move, its messages are moved to a newly created mailbox.
// We do indeed create a new destination mailbox and actually move the messages.
// Inbox is very special. Unlike other mailboxes, its children are not moved. And
// unlike a regular move, its messages are moved to a newly created mailbox. We do
// indeed create a new destination mailbox and actually move the messages.
// ../rfc/9051:2101
if src == "Inbox" {
exists, err := c.account.MailboxExists(tx, dst)
@ -2087,23 +2087,33 @@ func (c *conn) cmdRename(tag, cmd string, p *parser) {
err = tx.Insert(&dstMB)
xcheckf(err, "create new destination mailbox")
var messages []store.Message
// Move existing messages, with their ID's and on-disk files intact, to the new
// mailbox.
var oldUIDs []store.UID
q := bstore.QueryTx[store.Message](tx)
q.FilterNonzero(store.Message{MailboxID: srcMB.ID})
q.Gather(&messages)
_, err = q.UpdateNonzero(store.Message{MailboxID: dstMB.ID})
q.SortAsc("UID")
err = q.ForEach(func(m store.Message) error {
oldUIDs = append(oldUIDs, m.UID)
m.MailboxID = dstMB.ID
m.UID = dstMB.UIDNext
dstMB.UIDNext++
if err := tx.Update(&m); err != nil {
return fmt.Errorf("updating message to move to new mailbox: %w", err)
}
return nil
})
xcheckf(err, "moving messages from inbox to destination mailbox")
uids := make([]store.UID, len(messages))
for i, m := range messages {
uids[i] = m.UID
}
err = tx.Update(&dstMB)
xcheckf(err, "updating uidnext in destination mailbox")
var dstFlags []string
if tx.Get(&store.Subscription{Name: dstMB.Name}) == nil {
dstFlags = []string{`\Subscribed`}
}
changes = []store.Change{
store.ChangeRemoveUIDs{MailboxID: srcMB.ID, UIDs: uids},
store.ChangeRemoveUIDs{MailboxID: srcMB.ID, UIDs: oldUIDs},
store.ChangeAddMailbox{Name: dstMB.Name, Flags: dstFlags},
// todo: in future, we could announce all messages. no one is listening now though.
}