/* * qtmaildir - a Qt6 mail client for notmuch-indexed Maildirs * Copyright (C) 2026 Danilo M. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "htmlbuilder.h" #include namespace { const char *kStyle = R"CSS( body { font-family: sans-serif; font-size: 10pt; margin: 12px; } pre.plain { white-space: pre-wrap; word-wrap: break-word; font-family: monospace; margin: 0; } span.quote { color: #4a6f8a; } .message { border-top: 1px solid #bbb; padding: 10px 0; } .message:first-child { border-top: none; } .msg-header { font-size: 9pt; color: #555; margin-bottom: 8px; } .msg-header .who { font-weight: bold; color: #000; } .stub { font-size: 9pt; color: #666; padding: 4px 0; border-top: 1px solid #ddd; } )CSS"; } // namespace QString HtmlBuilder::renderPlain(const QString &text) { QString out; out += QStringLiteral("
");

    const QStringList lines = text.split(QLatin1Char('\n'));
    for (int i = 0; i < lines.size(); ++i) {
        const QString &line = lines.at(i);
        const bool quoted = line.startsWith(QLatin1Char('>'));

        if (quoted)
            out += QStringLiteral("");
        out += line.toHtmlEscaped();
        if (quoted)
            out += QStringLiteral("");

        if (i + 1 < lines.size())
            out += QLatin1Char('\n');
    }

    out += QStringLiteral("
"); return out; } QString HtmlBuilder::document(const QString &bodyHtml) { return QStringLiteral( "" "%2") .arg(QString::fromUtf8(kStyle), bodyHtml); } QString HtmlBuilder::namespaceCids(const QString &html, const QString &prefix) { if (prefix.isEmpty()) return html; // The '!' separator is only unambiguous if prefix itself never contains // one: the FIRST '!' in "cid:!" is always taken as the // separator, so a hostile Content-ID containing '!' only extends the id // half, never collides with a different prefix. This concatenation is // performed independently in two places (here and // CidSchemeHandler::namespacedKey); neither trusts the other to have // checked it, so both assert it directly. Q_ASSERT is compiled out in // release builds — the property that matters there is pinned by // TestHtmlBuilder::namespacedKeyRejectsPrefixContainingSeparator instead. Q_ASSERT_X(!prefix.contains(QLatin1Char('!')), "HtmlBuilder::namespaceCids", "cidPrefix must never contain '!': it is the separator, and a " "prefix containing one would make the split ambiguous"); // This runs on the sender's raw, unescaped HTML markup (not on text that // has been through toHtmlEscaped()), so no double-escaping happens here; // it is purely a URL rewrite over the existing markup. // // Two independent patterns are needed: // // 1. Attribute values: src=, href=, background=, poster= (the common // real-world attributes that can carry a cid: reference in HTML // email), quoted with " or ', quoted the other way, or entirely // unquoted ( is valid HTML and unquoted references are // seen in the wild). Attribute name is matched case-insensitively and // whitespace/newlines are tolerated around '='. // // 2. CSS url(cid:...): appears both inside a style="" attribute value // and inside a