summaryrefslogtreecommitdiffstats
path: root/src/requestinterceptor.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-02 17:37:48 +0200
committerDanilo M. <danix@danix.xyz>2026-08-02 17:37:48 +0200
commit3e540e8b2c79006524ad11d3629361bde663db27 (patch)
tree24592b2059a81d806486a9d295c900776e9cc503 /src/requestinterceptor.cpp
parentd774f0e94e7c5a7864ca585c5b3493ee9e33fcf6 (diff)
downloadqtmaildir-3e540e8b2c79006524ad11d3629361bde663db27.tar.gz
qtmaildir-3e540e8b2c79006524ad11d3629361bde663db27.zip
feat: add deny-by-default web request interceptor
Diffstat (limited to 'src/requestinterceptor.cpp')
-rw-r--r--src/requestinterceptor.cpp67
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;
+}