ui/HtmlDocumentDelivery.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "HtmlDocumentDelivery.h" | ||
| 2 | |||
| 3 | #include <QBuffer> | ||
| 4 | #include <utility> | ||
| 5 | #include <QUuid> | ||
| 6 | #include <QWebEngineUrlRequestJob> | ||
| 7 | #include <QWebEngineUrlScheme> | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | // Qt requires scheme registration before QApplication, including in test and | ||
| 11 | // runtime-probe entry points. The linked reader translation unit registers | ||
| 12 | // only the syntax; no page, profile, handler or renderer is constructed here. | ||
| 13 | 53 | [[maybe_unused]] const bool registered = [] { | |
| 14 |
2/2✓ Branch 2 → 3 taken 53 times.
✓ Branch 3 → 4 taken 53 times.
|
53 | QWebEngineUrlScheme scheme(HtmlDocumentDelivery::Scheme); |
| 15 |
1/1✓ Branch 5 → 6 taken 53 times.
|
53 | scheme.setSyntax(QWebEngineUrlScheme::Syntax::Host); |
| 16 |
1/1✓ Branch 7 → 8 taken 53 times.
|
53 | scheme.setFlags(QWebEngineUrlScheme::NoAccessAllowed); |
| 17 | // Opaque origins prevent decoded images from an earlier opt-in bypassing | ||
| 18 | // the next document's request policy. Keep CSP; grant no local-file access, CORS, | ||
| 19 | // JavaScript fetch, service-worker or source-view privileges. The private | ||
| 20 | // profile and current token constrain access to this one display lease. | ||
| 21 |
1/1✓ Branch 8 → 9 taken 53 times.
|
53 | QWebEngineUrlScheme::registerScheme(scheme); |
| 22 | 53 | return true; | |
| 23 | 53 | }(); | |
| 24 | } | ||
| 25 | |||
| 26 | ✗ | bool HtmlDocumentDelivery::fitsDataUrl(const QByteArray &html) { | |
| 27 | // Chromium caps URL length at 2 MiB. Reserve room for the MIME prefix and | ||
| 28 | // count escaping without allocating another percent-encoded payload. | ||
| 29 | ✗ | constexpr qsizetype limit = 2 * 1024 * 1024 - 1024; | |
| 30 | ✗ | if (html.size() > limit) return false; | |
| 31 | ✗ | if (html.size() <= limit / 3) return true; | |
| 32 | ✗ | qsizetype encoded = 0; | |
| 33 | ✗ | for (unsigned char byte : html) { | |
| 34 | ✗ | const bool unreserved = (byte >= 'a' && byte <= 'z') || | |
| 35 | ✗ | (byte >= 'A' && byte <= 'Z') || (byte >= '0' && byte <= '9') || | |
| 36 | ✗ | byte == '-' || byte == '_' || byte == '.' || byte == '~'; | |
| 37 | ✗ | encoded += unreserved ? 1 : 3; | |
| 38 | ✗ | if (encoded > limit) return false; | |
| 39 | } | ||
| 40 | ✗ | return true; | |
| 41 | } | ||
| 42 | |||
| 43 | ✗ | QUrl HtmlDocumentDelivery::publish(QByteArray html) { | |
| 44 | ✗ | clear(); | |
| 45 | ✗ | if (html.isEmpty() || html.size() > MaxDocumentBytes) return {}; | |
| 46 | ✗ | m_html = std::move(html); | |
| 47 | ✗ | m_url = QUrl(QStringLiteral("mailjd-html://document/") + | |
| 48 | ✗ | QUuid::createUuid().toString(QUuid::WithoutBraces)); | |
| 49 | ✗ | return m_url; | |
| 50 | } | ||
| 51 | |||
| 52 | 8691 | void HtmlDocumentDelivery::clear() { | |
| 53 |
1/1✓ Branch 2 → 3 taken 8691 times.
|
8691 | m_url = QUrl(); |
| 54 | 8691 | m_html.clear(); | |
| 55 | 8691 | } | |
| 56 | |||
| 57 | ✗ | void HtmlDocumentDelivery::requestStarted(QWebEngineUrlRequestJob *request) { | |
| 58 | ✗ | if (request->requestMethod() != QByteArrayLiteral("GET") || | |
| 59 | ✗ | m_url.isEmpty() || request->requestUrl() != m_url) { | |
| 60 | ✗ | request->fail(QWebEngineUrlRequestJob::RequestDenied); | |
| 61 | ✗ | return; | |
| 62 | } | ||
| 63 | // QByteArray shares the immutable payload. The request owns its read cursor | ||
| 64 | // and keeps the bytes alive only until Chromium finishes/cancels the job. | ||
| 65 | ✗ | auto *buffer = new QBuffer(request); | |
| 66 | ✗ | buffer->setData(m_html); | |
| 67 | ✗ | buffer->open(QIODevice::ReadOnly); | |
| 68 | ✗ | request->reply(QByteArrayLiteral("text/html"), buffer); | |
| 69 | } | ||
| 70 |