Skip to content

Commit 4c7a393

Browse files
committed
midb: process deletions without delay
IMAP clients get really confused if a message is expunged but then reappears as a supposedly new message, but carrying an old UID. Suppose the state of the IMAP INBOX is: ``` a fetch 1:* (uid) * 1 FETCH (UID 2359) * 2 FETCH (UID 2360) * 3 FETCH (UID 2361) * 4 FETCH (UID 2362) * 5 FETCH (UID 2363) * 6 FETCH (UID 2364) * 7 FETCH (UID 2365) * 8 FETCH (UID 2366) * 9 FETCH (UID 2367) * 10 FETCH (UID 2368) * 10 EXISTS ``` Deleting 1-5 may produce a response like so: ``` ``` a store 1:5 +flags \deleted * 1 FETCH (FLAGS (\Deleted)) * 2 FETCH (FLAGS (\Deleted)) * 3 FETCH (FLAGS (\Deleted)) * 4 FETCH (FLAGS (\Deleted)) * 5 FETCH (FLAGS (\Deleted)) a OK STORE completed a expunge * 5 EXPUNGE #uid2363 * 4 EXPUNGE #uid2362 * 3 EXPUNGE #uid2361 * 2 EXPUNGE #uid2360 * 1 EXPUNGE #uid2359 * 8 EXISTS * 0 RECENT a OK EXPUNGE completed ``` After the EXPUNGE status lines, the IMAP client view is that INBOX contains five messages (uids 2364-2368). ``8 EXISTS`` then indicates that three new messages have come into existence. Per RFC 3501 §2.3.1.1, these must necessarily have higher UIDs >2368. This condition is violated as a result of midb's delayed M-DELE processing (the IMAP server's view is as follows): ``` a fetch 1:* (uid) * 1 FETCH (UID 2361) * 2 FETCH (UID 2362) * 3 FETCH (UID 2363) * 4 FETCH (UID 2364) * 5 FETCH (UID 2365) * 6 FETCH (UID 2366) * 7 FETCH (UID 2367) * 8 FETCH (UID 2368) ``` References: DESK-3220, DESK-3345
1 parent c1780c3 commit 4c7a393

File tree

3 files changed

+44
-3
lines changed

3 files changed

+44
-3
lines changed

doc/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Fixes:
1414
recurrences
1515
* oxcical: fix wrong BYMONTH calculation for MONTHNTH recurrences being
1616
exported to iCal
17+
* midb, imap: make EXPUNGE synchronous so that old UIDs don't reappear in
18+
a subsequent FETCH
1719

1820

1921
Gromox 2.46 (2025-05-28)

exch/midb/mail_engine.cpp

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2233,7 +2233,6 @@ static int me_minst(int argc, char **argv, int sockd) try
22332233
*/
22342234
static int me_mdele(int argc, char **argv, int sockd)
22352235
{
2236-
int i;
22372236
BOOL b_partial;
22382237
EID_ARRAY message_ids;
22392238

@@ -2254,7 +2253,9 @@ static int me_mdele(int argc, char **argv, int sockd)
22542253
" folder_id FROM messages WHERE mid_string=?");
22552254
if (pstmt == nullptr)
22562255
return MIDB_E_SQLPREP;
2257-
for (i=3; i<argc; i++) {
2256+
2257+
/* Translate midb-MIDs into Exch-MIDs. */
2258+
for (int i = 3; i < argc; ++i) {
22582259
sqlite3_reset(pstmt);
22592260
sqlite3_bind_text(pstmt, 1, argv[i], -1, SQLITE_STATIC);
22602261
if (SQLITE_ROW != pstmt.step() ||
@@ -2264,10 +2265,43 @@ static int me_mdele(int argc, char **argv, int sockd)
22642265
rop_util_make_eid_ex(1, sqlite3_column_int64(pstmt, 0));
22652266
}
22662267
pstmt.finalize();
2267-
pidb.reset();
22682268
if (!exmdb_client->delete_messages(argv[1], CP_ACP, nullptr,
22692269
rop_util_make_eid_ex(1, folder_id), &message_ids, TRUE, &b_partial))
22702270
return MIDB_E_MDB_DELETEMESSAGES;
2271+
2272+
/*
2273+
* exmdb notifications may only arrive belatedly (or not at all),
2274+
* therefore we should explicitly drop the messages from midb.sqlite
2275+
* _now_, lest
2276+
*
2277+
* - the *same* midb client asking for a message listing after M-DELE
2278+
* could see messages again that really ought to be gone
2279+
* - the *same* IMAP client asking for a message listing after EXPUNGE
2280+
* could see messages with IMAPUIDs that were just deleted, which can
2281+
* upset clients.
2282+
*
2283+
* While resynchronization can still "bring back" those messages (and
2284+
* break midb client expectations), resync via RSYM/RSYF is considered
2285+
* a debugging tool, and resync-upon-first-open of midb.sqlite is when
2286+
* no clients are connected.
2287+
*/
2288+
auto pidb_transact = gx_sql_begin(pidb->psqlite, txn_mode::write);
2289+
if (!pidb_transact)
2290+
return false;
2291+
pstmt = gx_sql_prep(pidb->psqlite, "DELETE FROM messages WHERE mid_string=?");
2292+
if (pstmt == nullptr)
2293+
return MIDB_E_SQLPREP;
2294+
for (int i = 3; i < argc; ++i) {
2295+
pstmt.reset();
2296+
pstmt.bind_text(1, argv[i]);
2297+
if (pstmt.step() != SQLITE_DONE)
2298+
/* ignore */;
2299+
}
2300+
pstmt.finalize();
2301+
if (pidb_transact.commit() != SQLITE_OK)
2302+
/* ignore */;
2303+
pidb.reset();
2304+
22712305
return cmd_write(sockd, "TRUE\r\n");
22722306
}
22732307

mra/imap/parser.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,6 +1281,11 @@ void imap_parser_echo_modify(imap_context *pcontext, STREAM *pstream)
12811281
hl_hold.unlock();
12821282

12831283
imap_parser_echo_expunges(*pcontext, pstream, f_expunged);
1284+
/*
1285+
* 3501 §7: "the server checks the mailbox for new messages as part of
1286+
* command execution. […] If new messages are found, the server sends
1287+
* untagged EXISTS and RECENT responses."
1288+
*/
12841289
if (pcontext->contents.refresh(*pcontext, pcontext->selected_folder,
12851290
f_expunged.size() > 0) == 0) {
12861291
auto outlen = gx_snprintf(buff, std::size(buff),

0 commit comments

Comments
 (0)