From 5de81471ebcac21dbf8c5d781cd1b5f1df931bb8 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 3 Aug 2026 09:43:07 +0200 Subject: fix: render the message pane at all Clicking a thread left the pane blank. Two independent bugs, both from the same false premise: that setHtml() navigates to the base URL it is given. It does not. setHtml() navigates to a data: URL carrying the markup and applies the base URL afterwards, purely as the document's origin. Verified empirically on Qt 6.11. Built on that wrong assumption were: - MessagePage::acceptNavigationRequest compared the navigation's URL against documentUrl() and rejected everything else, so the document load was refused. It now accepts a typed main-frame navigation, which is one we initiated ourselves. - RequestInterceptor exempted exactly the qtmaildir: base URL and denied everything else, so the data: document load was blocked too. The interceptor fix is scoped to ResourceTypeMainFrame rather than allowing the data: scheme outright. A blanket allow would have been a real hole: a message body can write or an iframe, and the existing dataSchemeBlocked test in test_interceptor.cpp was right to fail when that was tried. Sub-resource data: URLs remain denied. Note this was never working. The drafted version had the same defect in a different spelling (it compared url.scheme() rather than the whole URL, and would have rejected the data: navigation just the same), and task 11 shipped with no runtime test to catch it. test_messageview.cpp now pins all three facts: the document loads, its text reaches the page, and a data: image inside a hostile body stays blocked. Co-Authored-By: Claude Opus 5 --- docs/manual-verification.md | 12 +++- src/messageview.cpp | 16 +++-- src/requestinterceptor.cpp | 26 ++++++- tests/CMakeLists.txt | 1 + tests/test_messageview.cpp | 161 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 tests/test_messageview.cpp diff --git a/docs/manual-verification.md b/docs/manual-verification.md index 607d7e8..6683f6c 100644 --- a/docs/manual-verification.md +++ b/docs/manual-verification.md @@ -31,7 +31,7 @@ databases. | 1 | Startup shows no configuration warnings with a valid config | **FAIL, then fixed** | | 2 | `tag:inbox` count matches `notmuch count --output=threads` | **PASS** | | 3 | A large query paints the first rows within a second | **PASS** | -| 4 | A new query discards the running one's results | PENDING | +| 4 | A new query discards the running one's results | **PASS** | | 5 | A malformed query (`tag:`) reports an error and does not crash | **PASS, item reworded** | | 6 | Selecting a thread renders every message, oldest first | PENDING | | 7 | Unmatched messages appear as one-line stubs | PENDING | @@ -90,6 +90,16 @@ Query `*` over the whole database, 36,335 threads: The first screenful is available essentially immediately and the rest fills in behind, which is what the batching exists for. +## Item 4: PASS + +Typed `*`, then `tag:unread` while the first query was still filling. The +list switched cleanly to 136 unread threads with no leftover rows from the +41,000-thread result set and no wrong intermediate count. The generation +counter discards superseded batches as designed. + +The maintainer's note that `*` "loaded almost quicker than I could type" +matches the item 3 measurement: 21 ms to the first batch. + ## Item 5: PASS, but the item was wrong The checklist assumed `tag:` is malformed and should raise an error. It is diff --git a/src/messageview.cpp b/src/messageview.cpp index 4e7586e..f8dc4ab 100644 --- a/src/messageview.cpp +++ b/src/messageview.cpp @@ -31,10 +31,18 @@ protected: bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) override { - // setHtml() arrives as a typed navigation to our own base URL. Matching - // the exact URL rather than the scheme keeps this consistent with the - // interceptor, which deliberately refuses to trust qtmaildir: wholesale. - if (type == NavigationTypeTyped && url == MessageView::documentUrl()) + // setHtml() does NOT navigate to the base URL it is given: it + // navigates to a data: URL carrying the markup, and applies the base + // URL afterwards as the document's origin. Verified empirically on Qt + // 6.11; an earlier version of this function compared against + // documentUrl() here and rejected every document load, so nothing + // rendered at all. + // + // A typed main-frame navigation is therefore one we initiated + // ourselves, and is accepted on that basis. This is not the security + // boundary: RequestInterceptor still vets every request the document + // goes on to make, including the qtmaildir: origin itself. + if (type == NavigationTypeTyped && isMainFrame) return true; if (type == NavigationTypeLinkClicked) { diff --git a/src/requestinterceptor.cpp b/src/requestinterceptor.cpp index 8fd2419..6267b64 100644 --- a/src/requestinterceptor.cpp +++ b/src/requestinterceptor.cpp @@ -14,8 +14,13 @@ bool RequestInterceptor::shouldAllow(const QUrl &url) // by unusual casing, in either the allow or the deny direction. const QString scheme = url.scheme(); - // The document itself is loaded via setHtml() with a qtmaildir: base URL, - // so a request for exactly that URL must pass or nothing renders at all. + // data: is never allowed here. It is permitted for the main-frame + // document only, which is handled in interceptRequest() where the resource + // type is known: a message body can put data: in or + //