| Age | Commit message (Collapse) | Author | Files | Lines |
|
|
|
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.
|
|
loadOverrides() inserted straight into m_bindings, so two override
lines that normalize to the same QKeySequence (e.g. "y" and "Y", both
"Y" per QKeySequence) silently overwrote each other with zero warning,
contradicting the "a typo cannot bind silently" contract on
knownActions().
Track sequences seen within the current override pass separately from
m_bindings (which already holds the defaults) so overriding a default
key stays silent, but two colliding override lines produce one warning
naming both actions.
|
|
Maps key sequences to action name strings, with hardcoded vim-style
defaults and QSettings-based [keys] overrides. Unknown actions and
unparseable sequences are collected as warnings rather than treated
as fatal, so a typo in the config cannot silently misbind or crash.
Note: QKeySequence::fromString() on Qt 6.11 does not return an empty
sequence for unparseable input (e.g. "NotAKey++") -- it returns a
non-empty sequence whose toString() is empty. Detection uses that
instead of isEmpty().
|
|
|
|
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>
|
|
Qt6/notmuch mail client, GUI counterpart to neomutt. Read and organize
only in v1; compose and send deferred to v2.
Key decisions and their reasoning are recorded in the spec: C++/Qt6 over
PySide6, QtWebEngine over QTextDocument for HTML mail, GMime for parsing,
direct libnotmuch rather than shelling out to mailctl, and calling the
existing sync script rather than reimplementing it so qtmaildir stays
inside the flock guard shared with the hourly cron sync.
Dependency availability verified on the development machine: Qt6 6.11.1
(WebEngine included in the monolithic package), libnotmuch 0.39, GMime
3.2.15. Notably notmuch installs no notmuch.pc, so CMake must locate it
without pkg-config.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|