| Age | Commit message (Collapse) | Author | Files | Lines |
|
The spec deferred NotmuchWorker to manual verification on the grounds
that testing it needs a real notmuch database. Building a fake one in a
temporary directory answers that objection, so Task 8 now gets real
coverage.
This is the only code in the project that writes to a notmuch index, so
a bug there corrupts real mail state. It was the wrong place to have the
weakest verification.
Task 13's manual checklist stays, as confirmation against real data
rather than as the only coverage.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
The interceptor previously trusted the whole qtmaildir: scheme, so a
hostile message body referencing qtmaildir://anything would have been
allowed. That made the interceptor's correctness depend on the scheme
handler in a different, later task.
It now trusts only the exact base URL passed to setHtml() and fails
closed when none is set. Records the resulting obligation on MessageView
to call setDocumentUrl() with that same URL.
Also corrects the attachment path guard to a separator-aware comparison
and notes that it is unreachable defence-in-depth: safeFilename() runs
first, so no caller-supplied name can reach it. A test driving saveTo()
expecting refusal cannot pass; test safeFilename() instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
Attachment::saveTo()'s escape guard compared paths with a bare
QString::startsWith(), which is not a path-boundary test: "/tmp/safe-evil"
textually starts with "/tmp/safe", so a sibling directory whose name merely
extends the target's name would incorrectly pass as contained within it.
Extract the check into Attachment::isPathInsideDirectory(), comparing
QDir::cleanPath()'d absolute paths and requiring an exact match or a prefix
ending at a '/' boundary. Not exploitable today since safeFilename() always
reduces the name to a bare basename before saveTo() builds the target, so
the guard is unreachable via saveTo()'s public interface; comments on both
now say so plainly instead of implying it is currently load-bearing.
Add pathInsideDirectoryRejectsSiblingPrefix, testing the guard directly
(independent of safeFilename(), which would mask a broken guard by never
producing an escaping path), and safeFilenameStripsPathComponents, testing
the sanitiser that actually stops traversal today.
|
|
Accounts use [account.work] rather than [account/work]: QSettings' INI
backend treats "/" as its own hierarchical group separator, so a literal
slash in a section header parses as a nested group and trips
QSettings::FormatError, silently breaking childGroups() enumeration. A
dot carries no such meaning and keeps the format flat.
Saved-query order is alphabetical (QSettings::childKeys() sorts), not
file order; documented in code and tests rather than left to a false
assumption.
|
|
Both were narrowed in the first draft and are now in scope. Newsletter
threads make whole-thread rendering fundamental, and bulk archive is the
main thing a GUI buys over neomutt.
Two consequences fell out of thread rendering and are recorded in both
documents. The thread renders as one document in one web view rather than
one view per message, since a view each would spawn a Chromium render
process each. Sharing a document makes cid: references collide when two
messages use the same Content-ID, which is common across newsletters from
one sender, so every reference is namespaced to cid:<prefix>!<id>.
Deciding which messages render expanded needs the query, so loadThread now
intersects it with the thread and MessageRef carries a matched flag.
Batch tagging resolves thread ids to message ids on the worker in one
combined query. Undo stores thread ids and re-resolves, staying correct
after the selection moves.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
Fourteen TDD tasks building from pure-logic units outward: keymap, config,
MIME parsing, and the request interceptor are fully tested first, then the
notmuch layer, then the UI that wires them together.
Seven test binaries, up from the three the spec named; config, htmlbuilder,
thread model, and sync each earned one. NotmuchWorker stays untested by
design, with Task 13 as the compensating manual checklist against a real
database.
Two narrowings from the spec are recorded rather than silently dropped:
thread rendering shows the newest message instead of a flat list of all
messages, and tagging applies to the loaded thread rather than every
selected one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|