diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-03 09:09:43 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-03 09:09:43 +0200 |
| commit | e7dceee1f449cafd07a86dfd11ff77f35897bdcf (patch) | |
| tree | b990c85490e8eea3b5f269ff8afdf7a7c3495da4 /src/threadcidmap.h | |
| parent | 255662101a4370197da6a1e64a7a446fd9a80187 (diff) | |
| download | qtmaildir-e7dceee1f449cafd07a86dfd11ff77f35897bdcf.tar.gz qtmaildir-e7dceee1f449cafd07a86dfd11ff77f35897bdcf.zip | |
feat: add MessageView with locked-down web engine profile
Off-the-record profile, JavaScript off, deny-by-default interceptor, and a
page subclass that hands link clicks to the system browser so a message can
never navigate the pane.
Honours the obligation task 5 recorded: the interceptor trusts exactly one
qtmaildir: URL and fails closed otherwise, so setHtml() and setDocumentUrl()
must agree or the pane renders nothing. Rather than pairing those calls at
each site, every load goes through one setDocument() and the URL comes from
a single documentUrl() accessor. Verified against the real interceptor that
this URL is allowed while siblings, subpaths, remote and file: are not.
Three fixes against the drafted version:
- showError() called setHtml() with a base URL but never setDocumentUrl(),
so an error card would have rendered blank. Now impossible to repeat.
- clear() and showError() left the previous thread's inline parts in the
scheme handler and its cids in the interceptor. Both now empty the policy,
so no thread's parts outlive it.
- MessagePage trusted the whole qtmaildir: scheme for typed navigations,
which is the same blanket-trust mistake task 5 removed from the
interceptor. It now matches the exact document URL.
The parts-flattening is extracted into buildThreadCidMap() so it can be
tested without a live profile, and a cidPrefix containing '!' is sanitized
rather than trusted, since Q_ASSERT is compiled out in release and this map
decides which bytes a message can name. The sanitizer escapes '_' before
replacing '!', because a plain replace would map "m0!x" and "m0_x" onto one
key and merge two messages, which is the very collision the namespacing
exists to prevent. Mutation-verified: the naive replace fails the
distinctness test, and dropping the sanitizer trips the assert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/threadcidmap.h')
| -rw-r--r-- | src/threadcidmap.h | 29 |
1 files changed, 29 insertions, 0 deletions
diff --git a/src/threadcidmap.h b/src/threadcidmap.h new file mode 100644 index 0000000..76bd8b9 --- /dev/null +++ b/src/threadcidmap.h @@ -0,0 +1,29 @@ +#pragma once + +#include <QHash> +#include <QList> +#include <QSet> +#include <QString> + +#include "htmlbuilder.h" +#include "mimeparser.h" + +/// The inline parts of a whole thread, keyed by their namespaced cid. +struct ThreadCidMap +{ + QHash<QString, InlinePart> parts; ///< For the scheme handler. + QSet<QString> allowedCids; ///< For the interceptor. Same keys. +}; + +/// Flattens every message's inline parts into one namespaced map. +/// +/// Two messages in a thread commonly share a Content-ID (cid:logo@example.org +/// from the same sender's newsletter template), and the thread renders as one +/// document, so the raw ids would collide and one message would show another's +/// image. Keys are "<prefix>!<content-id>". +/// +/// A cidPrefix containing '!' would break the split that keeps those apart, so +/// it is sanitized here rather than trusted: Q_ASSERT fires in debug builds but +/// is compiled out in release, and this map decides which bytes a message can +/// name. Sanitizing is injective, so two distinct prefixes stay distinct. +ThreadCidMap buildThreadCidMap(const QList<ThreadRenderItem> &items); |
