You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Conversation threading, and the reply chain that could have taken the server down
THREAD and UID THREAD (RFC 5256), both algorithms, advertised as
THREAD=ORDEREDSUBJECT THREAD=REFERENCES and riding the same bounded search
machinery as SORT.
REFERENCES is the actual algorithm rather than subject grouping wearing its
name. Containers are linked by Message-ID / References / In-Reply-To, with
In-Reply-To consulted only when References is absent; a message's own References
line authoritatively breaks a parent that somebody else's line asserted; cycles
are refused rather than linked; dummies are pruned except at the root, where a
dummy with two or more children is what holds two replies together when the
message they both answer never arrived. Threads are then grouped by RFC 2.1 base
subject - the whole step machine, so "[fwd: Re: [list] x]" reduces the way the
RFC says rather than the way one regular expression can - and sorted by sent
date. ORDEREDSUBJECT is the flat approximation the RFC requires of anyone
advertising THREAD at all.
Not gated behind a setting, unlike SORT, and the asymmetry is deliberate: the
work is bounded by the same per-command ceilings as SEARCH, and a gate would
have cost a seeded settings row on every backend plus a schema bump - a missing
row reports HM5015 on every read rather than defaulting quietly - to guard
something already bounded.
An adversarial pass over 11 passing tests found three defects, and the one that
mattered was measured rather than argued. Prune_ and SortChildrenRecursively_
recurse once per tree level on a 1MB default worker stack; a probe compiled
against those exact frames survived 6000 levels and terminated with
STATUS_STACK_OVERFLOW at 8000, while the old cycle guard permitted chains near
10000. A stack overflow is not reliably catchable, so that was a crash of the
whole service, reachable by any authenticated user willing to mail themselves a
long enough reply chain. Links are now refused past depth 512, which bounds all
three recursive walks at their source.
The header pass was uninterruptible: the ceiling was checked after BuildThreads
returned, which can only disown a result the connection thread has already spent
minutes producing. The predicate is handed in now and stops before the next file
is opened.
And a Date header that was present but unparseable fell through to the 1899 OLE
epoch, which the comparator reads as a raw value without consulting the validity
flag - so the most malformed message in a conversation sorted to the front of
it. RFC 5256 2.2's INTERNALDATE fallback now covers unparseable as well as
absent.
Two pre-existing weaknesses of exactly these shapes are recorded in the roadmap
rather than silently inherited: SORT checks its ceiling only after its own
uninterruptible header pass, and IMAPSort::CacheHeaderFields_ tests the Date
header for emptiness rather than validity.
13 tests assert exact trees - chains, forks, dummy roots, subject grouping, UID
mode against UIDs read back rather than assumed, BAD for an unadvertised
algorithm, a same-connection stale-state control, a 600-message chain that must
not crash, and the INTERNALDATE fallback. The fixture earned itself twice: it
caught a stray space between root threads, which a threading client would parse
as an error, and its own first date assertion was wrong rather than the code -
crafted headers dated today at 10:xx against a suite running at 09:xx.
Full suite: 1490/1490.
Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,7 +29,7 @@ Mail protocols
29
29
--------------
30
30
31
31
***SMTP** with PIPELINING, ENHANCEDSTATUSCODES, 8BITMIME, SIZE, CHUNKING/BDAT (RFC 3030), DSN delivery status notifications (RFC 3461/3464) and SMTPUTF8/EAI for internationalised addresses.
32
-
***IMAP4rev1**, plus **IMAP4rev2** (RFC 9051) advertised with its behavioural deltas implemented — a few of the extensions rev2 folds in (LIST-STATUS, non-synchronising literals, BINARY) are still missing — with IDLE, MOVE (RFC 6851), UIDPLUS (RFC 4315), CONDSTORE/QRESYNC (RFC 7162), SEARCHRES (RFC 5182), ESEARCH (RFC 4731), SORT, ACL, NAMESPACE, ID (RFC 2971), SPECIAL-USE (RFC 6154, including explicit designation via `CREATE ... (USE (\Sent))`) and QUOTA.
32
+
***IMAP4rev1**, plus **IMAP4rev2** (RFC 9051) advertised with its behavioural deltas implemented — a few of the extensions rev2 folds in (LIST-STATUS, non-synchronising literals, BINARY) are still missing — with IDLE, MOVE (RFC 6851), UIDPLUS (RFC 4315), CONDSTORE/QRESYNC (RFC 7162), SEARCHRES (RFC 5182), ESEARCH (RFC 4731), SORT and THREAD (RFC 5256, both ORDEREDSUBJECT and REFERENCES), ACL, NAMESPACE, ID (RFC 2971), SPECIAL-USE (RFC 6154, including explicit designation via `CREATE ... (USE (\Sent))`) and QUOTA.
33
33
***POP3**, including retrieval from external POP3 accounts on a schedule.
34
34
***Public folders**, shared across accounts with per-user ACLs.
Three things stand out and are worth naming rather than leaving to be inferred.
78
78
**Storage and the administration surface are the best-covered areas**, and the
@@ -329,7 +329,7 @@ the source, not from documentation.
329
329
330
330
### IMAP
331
331
332
-
57 shipped · 0 underway · 18 not started · 3 deferred
332
+
58 shipped · 0 underway · 17 not started · 3 deferred
333
333
334
334
|| Capability | Detail |
335
335
|:-:|---|---|
@@ -407,7 +407,7 @@ the source, not from documentation.
407
407
| ⬜ | REPLACE (RFC 8508) | Not implemented and not advertised; there is no REPLACE or UID REPLACE command, so clients must emulate draft updates with APPEND + STORE \Deleted + EXPUNGE. |
408
408
| ⬜ | RFC 9208 QUOTA (QUOTA=RES-STORAGE / RES-MESSAGE, SETQUOTA) | Not implemented. The bare "QUOTA" capability atom is advertised rather than the RFC 9208 QUOTA=RES-* form, there is no SETQUOTA, no per-mailbox quota roots and no OVERQUOTA response code. Listed in the roadmap's IMAP-extension backlog. |
409
409
| ⬜ | SAVEDATE (RFC 8514) | Not implemented. No SAVEDATE FETCH item and no SAVEDATE/SAVEDBEFORE/SAVEDSINCE/SAVEDATESUPPORTED search keys. Listed in the roadmap's IMAP-extension backlog. |
410
-
| ⬜ | THREAD (RFC 5256) | Not implemented and not advertised. No THREAD command in the dispatch table and no reference anywhere in the source. SORT ships from the same RFC but THREAD does not… |
410
+
| ✅ | THREAD (RFC 5256) | **Shipped 15 August 2026, both algorithms.** `THREAD` and `UID THREAD` advertise `THREAD=ORDEREDSUBJECT THREAD=REFERENCES` and ride the same bounded search machinery as SORT. REFERENCES is the full RFC algorithm rather than a subject approximation wearing its name: containers are linked by `Message-ID`/`References`/`In-Reply-To` (falling back to In-Reply-To only when References is absent), a message's own References line authoritatively breaks a parent another message asserted, cycles are refused, dummies are pruned except at the root where they hold orphaned siblings together, threads are then grouped by RFC 2.1 base subject - the full step machine, so `[fwd: Re: [list] x]` reduces the way the RFC says - and sorted by sent date with the INTERNALDATE fallback. **An adversarial pass found three defects the 11 passing tests did not.** A deep reply chain overflowed the 1MB worker stack: `Prune_` and `SortChildrenRecursively_` recurse per level, and a probe compiled against those exact frames survived 6000 levels and died with `STATUS_STACK_OVERFLOW` at 8000 - a whole-service crash any authenticated user could reach by mailing themselves a long enough chain, now bounded by a 512 depth cap at link time. The header pass was uninterruptible, so `IMAPSearchTimeout` could only disown a result the connection thread had already spent minutes producing; the ceiling is now handed in and stops the pass before the next file opens. And a Date header that was present but unparseable fell through to the 1899 OLE epoch and sorted to the FRONT of its thread, because the comparator reads the raw value and not the validity flag - RFC 5256 2.2's INTERNALDATE fallback now covers unparseable as well as absent. 13 tests assert exact trees. **Two pre-existing weaknesses of the same shape are recorded, not silently inherited:** SORT checks its ceiling only after its own uninterruptible header pass, and `IMAPSort::CacheHeaderFields_` tests the Date header for emptiness rather than validity. |
411
411
| ⬜ | UNAUTHENTICATE (RFC 8437) | Not implemented and not advertised; there is no way to return an authenticated session to the not-authenticated state for connection reuse. |
412
412
| ⬜ | URLAUTH (RFC 4467) / BURL | Not implemented and not advertised; no GENURLAUTH, URLFETCH or RESETKEY commands and no IMAP URL parser. |
413
413
| ✅ | WITHIN (RFC 5032) OLDER / YOUNGER search keys | Implemented and advertised. Both compare the message's internal date against (now - n seconds) using the whole timestamp rather than the calendar date, which is the point of the extension and what makes "YOUNGER 3600" answerable at all; relative-age searches no longer have to be rewritten as absolute BEFORE/SINCE. The interval is validated as an nz-number and clamped at a century, so a zero, negative or non-numeric interval is BAD instead of silently becoming "0 seconds ago". |
0 commit comments