diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-02 17:37:48 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-04 12:52:24 +0200 |
| commit | ab4498ba12107529b8545f7d03dfe27433a3ffe2 (patch) | |
| tree | a6de6af4523c6c36136f5b57a69a2fd0e87e3230 /src/requestinterceptor.cpp | |
| parent | 18dffc348f722fc79ed336523982541283bc84e1 (diff) | |
| download | qtmaildir-ab4498ba12107529b8545f7d03dfe27433a3ffe2.tar.gz qtmaildir-ab4498ba12107529b8545f7d03dfe27433a3ffe2.zip | |
feat: add deny-by-default web request interceptor
Diffstat (limited to 'src/requestinterceptor.cpp')
| -rw-r--r-- | src/requestinterceptor.cpp | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/src/requestinterceptor.cpp b/src/requestinterceptor.cpp new file mode 100644 index 0000000..0ce95ca --- /dev/null +++ b/src/requestinterceptor.cpp @@ -0,0 +1,67 @@ +#include "requestinterceptor.h" + +#include <QWebEngineUrlRequestInfo> + +RequestInterceptor::RequestInterceptor(QObject *parent) + : QWebEngineUrlRequestInterceptor(parent) +{ +} + +bool RequestInterceptor::shouldAllow(const QUrl &url) +{ + // QUrl::scheme() always normalizes to lowercase (verified: QUrl("HTTP://x/y") + // .scheme() == "http"), so a lowercase-literal compare cannot be bypassed + // 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 that scheme must pass or nothing renders at all. This is unconditional + // on any path/host because Task 11's scheme handler is the only thing that + // can ever originate a qtmaildir: navigation in the first place; the message + // body cannot cause a request with this scheme, only reference cid:/http(s):. + if (scheme == QLatin1String("qtmaildir")) + return true; + + // Inline parts of the current message only. + if (scheme == QLatin1String("cid")) { + // QUrl keeps a cid: body in path(), not host() or userName(), even + // when it contains '@' (verified empirically: QUrl("cid:logo@example.org") + // .path() == "logo@example.org", host() and userName() are empty). + // path() also returns the percent-decoded form, so a percent-encoded + // id (e.g. "%6Cogo@example.org") compares equal to its decoded form, + // not to some other allowed id: it cannot be used to smuggle a + // foreign id past the allowlist, only to spell an already-legitimate + // id differently. + const QString id = url.path(); + if (m_allowedCids.contains(id)) + return true; + m_blockedAnything = true; + return false; + } + + if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) { + if (m_allowRemote) + return true; + m_blockedAnything = true; + return false; + } + + // Everything else, including file:, javascript:, data:, blob:, about:, + // chrome:, qrc:, filesystem:, protocol-relative URLs (empty scheme with a + // host), and empty/malformed URLs (empty scheme), is denied + // unconditionally. There is no flag that enables it. + m_blockedAnything = true; + return false; +} + +void RequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info) +{ + if (!shouldAllow(info.requestUrl())) + info.block(true); +} + +void RequestInterceptor::resetForNewMessage() +{ + m_allowRemote = false; + m_blockedAnything = false; +} |
