Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34553619
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
57 KB
Subscribers
None
View Options
diff --git a/src/core/bodypartformatter_impl.cpp b/src/core/bodypartformatter_impl.cpp
index 39d0df1..4ff71c2 100644
--- a/src/core/bodypartformatter_impl.cpp
+++ b/src/core/bodypartformatter_impl.cpp
@@ -1,392 +1,392 @@
// SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org>
// SPDX-License-Identifier: GPL-2.0-only
#include "mimetreeparser_core_debug.h"
#include "bodypartformatter.h"
#include "bodypartformatterbasefactory.h"
#include "bodypartformatterbasefactory_p.h"
#include "messagepart.h"
#include "objecttreeparser.h"
#include "utils.h"
#include <KMime/Content>
#include <QGpgME/Protocol>
using namespace MimeTreeParser;
using namespace MimeTreeParser::Interface;
-
+using namespace Qt::Literals::StringLiterals;
namespace MimeTreeParser
{
class AnyTypeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
};
class MessageRfc822BodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
public:
MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const override
{
return MessagePart::Ptr(new EncapsulatedRfc822MessagePart(objectTreeParser, node, node->bodyAsMessage()));
}
};
class HeadersBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
public:
MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const override
{
return MessagePart::Ptr(new HeadersPart(objectTreeParser, node));
}
};
class MultiPartRelatedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
public:
QList<MessagePart::Ptr> processList(ObjectTreeParser *objectTreeParser, KMime::Content *node) const override
{
if (node->contents().isEmpty()) {
return {};
}
// We rely on the order of the parts.
// Theoretically there could also be a Start parameter which would break this..
// https://tools.ietf.org/html/rfc2387#section-4
// We want to display attachments even if displayed inline.
QList<MessagePart::Ptr> list;
list.append(MimeMessagePart::Ptr(new MimeMessagePart(objectTreeParser, node->contents().at(0), true)));
for (int i = 1; i < node->contents().size(); i++) {
auto p = node->contents().at(i);
if (KMime::isAttachment(p)) {
list.append(MimeMessagePart::Ptr(new MimeMessagePart(objectTreeParser, p, true)));
}
}
return list;
}
};
class MultiPartMixedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
public:
MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const override
{
auto contents = node->contents();
if (contents.isEmpty()) {
return {};
}
bool isActuallyMixedEncrypted = false;
for (const auto &content : std::as_const(contents)) {
if (content->contentType()->mimeType() == "application/pgp-encrypted"_ba || content->contentType()->mimeType() == "application/pkcs7-mime"_ba) {
isActuallyMixedEncrypted = true;
}
}
if (isActuallyMixedEncrypted) {
// Remove explaination
contents.erase(std::remove_if(contents.begin(),
contents.end(),
[](const auto content) {
return content->contentType()->mimeType() == "text/plain";
}),
contents.end());
if (contents.count() == 1 && contents[0]->contentType()->mimeType() == "application/pkcs7-mime"_ba) {
auto data = findTypeInDirectChildren(node, "application/pkcs7-mime"_ba);
auto mp = EncryptedMessagePart::Ptr(new EncryptedMessagePart(objectTreeParser, data->decodedText(), QGpgME::smime(), node, data));
mp->setIsEncrypted(true);
return mp;
}
if (contents.count() == 2 && contents[1]->contentType()->mimeType() == "application/octet-stream"_ba) {
KMime::Content *data = findTypeInDirectChildren(node, "application/octet-stream"_ba);
auto mp = EncryptedMessagePart::Ptr(new EncryptedMessagePart(objectTreeParser, data->decodedText(), QGpgME::openpgp(), node, data));
mp->setIsEncrypted(true);
return mp;
}
}
// we need the intermediate part to preserve the headers (necessary for with protected headers using multipart mixed)
auto part = MessagePart::Ptr(new MessagePart(objectTreeParser, {}, node));
part->appendSubPart(MimeMessagePart::Ptr(new MimeMessagePart(objectTreeParser, contents.at(0), false)));
return part;
}
};
class ApplicationPGPEncryptedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
public:
MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const override
{
if (node->decodedContent().trimmed() != "Version: 1"_ba) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "Unknown PGP Version String:" << node->decodedContent().trimmed();
}
if (!node->parent()) {
return MessagePart::Ptr();
}
KMime::Content *data = findTypeInDirectChildren(node->parent(), "application/octet-stream"_ba);
if (!data) {
return MessagePart::Ptr(); // new MimeMessagePart(objectTreeParser, node));
}
EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(objectTreeParser, data->decodedText(), QGpgME::openpgp(), node, data));
mp->setIsEncrypted(true);
return mp;
}
};
class ApplicationPkcs7MimeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
public:
MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const override
{
if (node->head().isEmpty()) {
return MessagePart::Ptr();
}
const QString smimeType = node->contentType()->parameter("smime-type").toLower();
if (smimeType == QLatin1StringView("certs-only")) {
return CertMessagePart::Ptr(new CertMessagePart(objectTreeParser, node, QGpgME::smime()));
}
bool isSigned = (smimeType == QLatin1StringView("signed-data"));
bool isEncrypted = (smimeType == QLatin1StringView("enveloped-data"));
// Analyze "signTestNode" node to find/verify a signature.
// If zero part.objectTreeParser verification was successfully done after
// decrypting via recursion by insertAndParseNewChildNode().
KMime::Content *signTestNode = isEncrypted ? nullptr : node;
// We try decrypting the content
// if we either *know* that it is an encrypted message part
// or there is neither signed nor encrypted parameter.
MessagePart::Ptr mp;
if (!isSigned) {
if (isEncrypted) {
qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime == S/MIME TYPE: enveloped (encrypted) data";
} else {
qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime - type unknown - enveloped (encrypted) data ?";
}
auto _mp = EncryptedMessagePart::Ptr(new EncryptedMessagePart(objectTreeParser, node->decodedText(), QGpgME::smime(), node));
mp = _mp;
_mp->setIsEncrypted(true);
// PartMetaData *messagePart(_mp->partMetaData());
// if (!part.source()->decryptMessage()) {
// isEncrypted = true;
signTestNode = nullptr; // PENDING(marc) to be abs. sure, we'd need to have to look at the content
// } else {
// _mp->startDecryption();
// if (messagePart->isDecryptable) {
// qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime - encryption found - enveloped (encrypted) data !";
// isEncrypted = true;
// part.nodeHelper()->setEncryptionState(node, KMMsgFullyEncrypted);
// signTestNode = nullptr;
// } else {
// // decryption failed, which could be because the part was encrypted but
// // decryption failed, or because we didn't know if it was encrypted, tried,
// // and failed. If the message was not actually encrypted, we continue
// // assuming it's signed
// if (_mp->passphraseError() || (smimeType.isEmpty() && messagePart->isEncrypted)) {
// isEncrypted = true;
// signTestNode = nullptr;
// }
// if (isEncrypted) {
// qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime - ERROR: COULD NOT DECRYPT enveloped data !";
// } else {
// qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime - NO encryption found";
// }
// }
// }
}
// We now try signature verification if necessarry.
if (signTestNode) {
if (isSigned) {
qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime == S/MIME TYPE: opaque signed data";
} else {
qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime - type unknown - opaque signed data ?";
}
return SignedMessagePart::Ptr(new SignedMessagePart(objectTreeParser, QGpgME::smime(), nullptr, signTestNode));
}
return mp;
}
};
class MultiPartAlternativeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
public:
MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const override
{
if (node->contents().isEmpty()) {
return MessagePart::Ptr();
}
AlternativeMessagePart::Ptr mp(new AlternativeMessagePart(objectTreeParser, node));
if (mp->mChildParts.isEmpty()) {
return MimeMessagePart::Ptr(new MimeMessagePart(objectTreeParser, node->contents().at(0)));
}
return mp;
}
};
class MultiPartEncryptedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
public:
MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const override
{
if (node->contents().isEmpty()) {
Q_ASSERT(false);
return MessagePart::Ptr();
}
const QGpgME::Protocol *protocol = nullptr;
/*
ATTENTION: This code is to be replaced by the new 'auto-detect' feature. --------------------------------------
*/
KMime::Content *data = findTypeInDirectChildren(node, "application/octet-stream"_ba);
if (data) {
protocol = QGpgME::openpgp();
} else {
data = findTypeInDirectChildren(node, "application/pkcs7-mime"_ba);
if (data) {
protocol = QGpgME::smime();
}
}
/*
---------------------------------------------------------------------------------------------------------------
*/
if (!data) {
return MessagePart::Ptr(new MimeMessagePart(objectTreeParser, node->contents().at(0)));
}
EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(objectTreeParser, data->decodedText(), protocol, node, data));
mp->setIsEncrypted(true);
return mp;
}
};
class MultiPartSignedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
public:
static const QGpgME::Protocol *detectProtocol(const QString &protocolContentType_, const QString &signatureContentType)
{
auto protocolContentType = protocolContentType_;
if (protocolContentType.isEmpty()) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "Message doesn't set the protocol for the multipart/signed content-type, "
"using content-type of the signature:"
<< signatureContentType;
protocolContentType = signatureContentType;
}
const QGpgME::Protocol *protocol = nullptr;
if (protocolContentType == QLatin1StringView("application/pkcs7-signature")
|| protocolContentType == QLatin1StringView("application/x-pkcs7-signature")) {
protocol = QGpgME::smime();
} else if (protocolContentType == QLatin1StringView("application/pgp-signature")
|| protocolContentType == QLatin1StringView("application/x-pgp-signature")) {
protocol = QGpgME::openpgp();
}
return protocol;
}
MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const override
{
if (node->contents().size() != 2) {
qCDebug(MIMETREEPARSER_CORE_LOG) << "mulitpart/signed must have exactly two child parts!" << Qt::endl << "processing as multipart/mixed";
if (!node->contents().isEmpty()) {
return MessagePart::Ptr(new MimeMessagePart(objectTreeParser, node->contents().at(0)));
} else {
return MessagePart::Ptr();
}
}
KMime::Content *signedData = node->contents().at(0);
KMime::Content *signature = node->contents().at(1);
Q_ASSERT(signedData);
Q_ASSERT(signature);
auto protocol = detectProtocol(node->contentType()->parameter("protocol").toLower(), QLatin1StringView(signature->contentType()->mimeType().toLower()));
if (!protocol) {
return MessagePart::Ptr(new MimeMessagePart(objectTreeParser, signedData));
}
return SignedMessagePart::Ptr(new SignedMessagePart(objectTreeParser, protocol, signature, signedData));
}
};
class TextHtmlBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
public:
MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const override
{
return HtmlMessagePart::Ptr(new HtmlMessagePart(objectTreeParser, node));
}
};
class TextPlainBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
public:
MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const override
{
if (KMime::isAttachment(node)) {
return AttachmentMessagePart::Ptr(new AttachmentMessagePart(objectTreeParser, node));
}
return TextMessagePart::Ptr(new TextMessagePart(objectTreeParser, node));
}
};
} // anon namespace
void BodyPartFormatterBaseFactoryPrivate::messageviewer_create_builtin_bodypart_formatters()
{
auto any = new AnyTypeBodyPartFormatter;
auto textPlain = new TextPlainBodyPartFormatter;
auto pkcs7 = new ApplicationPkcs7MimeBodyPartFormatter;
auto pgp = new ApplicationPGPEncryptedBodyPartFormatter;
auto html = new TextHtmlBodyPartFormatter;
auto headers = new HeadersBodyPartFormatter;
auto multipartAlternative = new MultiPartAlternativeBodyPartFormatter;
auto multipartMixed = new MultiPartMixedBodyPartFormatter;
auto multipartSigned = new MultiPartSignedBodyPartFormatter;
auto multipartEncrypted = new MultiPartEncryptedBodyPartFormatter;
auto message = new MessageRfc822BodyPartFormatter;
auto multipartRelated = new MultiPartRelatedBodyPartFormatter;
insert("application", "octet-stream", any);
insert("application", "pgp", textPlain);
insert("application", "pkcs7-mime", pkcs7);
insert("application", "x-pkcs7-mime", pkcs7);
insert("application", "pgp-encrypted", pgp);
insert("application", "*", any);
insert("text", "html", html);
insert("text", "rtf", any);
insert("text", "plain", textPlain);
insert("text", "rfc822-headers", headers);
insert("text", "*", textPlain);
insert("image", "*", any);
insert("message", "rfc822", message);
insert("message", "*", any);
insert("multipart", "alternative", multipartAlternative);
insert("multipart", "encrypted", multipartEncrypted);
insert("multipart", "signed", multipartSigned);
insert("multipart", "related", multipartRelated);
insert("multipart", "*", multipartMixed);
insert("*", "*", any);
}
diff --git a/src/core/objecttreeparser.cpp b/src/core/objecttreeparser.cpp
index ac836a1..949f325 100644
--- a/src/core/objecttreeparser.cpp
+++ b/src/core/objecttreeparser.cpp
@@ -1,491 +1,491 @@
// This file is part of KMail, the KDE mail client.
// SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org>
// SPDX-FileCopyrightText: 2002-2004 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
// SPDX-FileCopyrightText: 2009 Andras Mantia <andras@kdab.net>
// SPDX-FileCopyrightText: 2015 Sandro Knauß <sknauss@kde.org>
// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsystems.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "objecttreeparser.h"
#include "bodypartformatterbasefactory.h"
#include "bodypartformatter.h"
#include <KMime/Message>
#include <QByteArray>
#include <QDebug>
#include <QMimeDatabase>
#include <QRegularExpression>
#include <QStringDecoder>
#include <QTextStream>
#include <QUrl>
using namespace MimeTreeParser;
-
+using namespace Qt::Literals::StringLiterals;
/*
* Collect message parts bottom up.
* Filter to avoid evaluating a subtree.
* Select parts to include it in the result set. Selecting a part in a branch will keep any parent parts from being selected.
*/
static QList<MessagePart::Ptr> collect(MessagePart::Ptr start,
const std::function<bool(const MessagePart::Ptr &)> &evaluateSubtree,
const std::function<bool(const MessagePart::Ptr &)> &select)
{
auto ptr = start.dynamicCast<MessagePart>();
Q_ASSERT(ptr);
MessagePart::List list;
if (evaluateSubtree(ptr)) {
for (const auto &p : ptr->subParts()) {
list << ::collect(p, evaluateSubtree, select);
}
}
// Don't consider this part if we already selected a subpart
if (list.isEmpty()) {
if (select(ptr)) {
list << start;
}
}
return list;
}
QString ObjectTreeParser::plainTextContent()
{
QString content;
if (mParsedPart) {
auto plainParts = ::collect(
mParsedPart,
[](const MessagePart::Ptr &) {
return true;
},
[](const MessagePart::Ptr &part) {
if (part->isAttachment()) {
return false;
}
if (dynamic_cast<MimeTreeParser::TextMessagePart *>(part.data())) {
return true;
}
if (dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(part.data())) {
return true;
}
return false;
});
for (const auto &part : plainParts) {
content += part->text();
}
}
return content;
}
QString ObjectTreeParser::htmlContent()
{
QString content;
if (mParsedPart) {
MessagePart::List contentParts = ::collect(
mParsedPart,
[](const MessagePart::Ptr &) {
return true;
},
[](const MessagePart::Ptr &part) {
if (dynamic_cast<MimeTreeParser::HtmlMessagePart *>(part.data())) {
return true;
}
if (dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(part.data())) {
return true;
}
return false;
});
for (const auto &part : contentParts) {
if (auto p = dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(part.data())) {
content += p->htmlContent();
} else {
content += part->text();
}
}
}
return content;
}
bool ObjectTreeParser::hasEncryptedParts() const
{
bool result = false;
::collect(
mParsedPart,
[](const MessagePart::Ptr &) {
return true;
},
[&result](const MessagePart::Ptr &part) {
if (const auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart *>(part.data())) {
result = true;
}
return false;
});
return result;
}
bool ObjectTreeParser::hasSignedParts() const
{
bool result = false;
::collect(
mParsedPart,
[](const MessagePart::Ptr &) {
return true;
},
[&result](const MessagePart::Ptr &part) {
if (const auto enc = dynamic_cast<MimeTreeParser::SignedMessagePart *>(part.data())) {
result = true;
}
return false;
});
return result;
}
static void print(QTextStream &stream, KMime::Content *node, const QString prefix = {})
{
QByteArray mediaType("text");
QByteArray subType("plain");
if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() && !node->contentType()->subType().isEmpty()) {
mediaType = node->contentType()->mediaType();
subType = node->contentType()->subType();
}
stream << prefix << "! " << mediaType << subType << " isAttachment: " << KMime::isAttachment(node) << "\n";
const auto contents = node->contents();
for (const auto nodeContent : contents) {
print(stream, nodeContent, prefix + QLatin1StringView(" "));
}
}
static void print(QTextStream &stream, const MessagePart &messagePart, const QByteArray pre = {})
{
stream << pre << "# " << messagePart.metaObject()->className() << " isAttachment: " << messagePart.isAttachment() << "\n";
const auto subParts = messagePart.subParts();
for (const auto &subPart : subParts) {
print(stream, *subPart, pre + " ");
}
}
QString ObjectTreeParser::structureAsString() const
{
QString string;
QTextStream stream{&string};
if (mTopLevelContent) {
::print(stream, mTopLevelContent);
}
if (mParsedPart) {
::print(stream, *mParsedPart);
}
return string;
}
void ObjectTreeParser::print()
{
qInfo().noquote() << structureAsString();
}
static KMime::Content *find(KMime::Content *node, const std::function<bool(KMime::Content *)> &select)
{
QByteArray mediaType("text");
QByteArray subType("plain");
if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() && !node->contentType()->subType().isEmpty()) {
mediaType = node->contentType()->mediaType();
subType = node->contentType()->subType();
}
if (select(node)) {
return node;
}
const auto contents = node->contents();
for (const auto nodeContent : contents) {
if (const auto content = find(nodeContent, select)) {
return content;
}
}
return nullptr;
}
KMime::Content *ObjectTreeParser::find(const std::function<bool(KMime::Content *)> &select)
{
return ::find(mTopLevelContent, select);
}
MessagePart::List ObjectTreeParser::collectContentParts()
{
return collectContentParts(mParsedPart);
}
MessagePart::List ObjectTreeParser::collectContentParts(MessagePart::Ptr start)
{
return ::collect(
start,
[start](const MessagePart::Ptr &part) {
// Ignore the top-level
if (start.data() == part.data()) {
return true;
}
if (auto encapsulatedPart = part.dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>()) {
return false;
}
return true;
},
[start](const MessagePart::Ptr &part) {
if (const auto attachment = dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(part.data())) {
return attachment->mimeType() == "text/calendar"_ba;
} else if (const auto text = dynamic_cast<MimeTreeParser::TextMessagePart *>(part.data())) {
auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart *>(text->parentPart());
if (enc && enc->error()) {
return false;
}
return true;
} else if (dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(part.data())) {
return true;
} else if (dynamic_cast<MimeTreeParser::HtmlMessagePart *>(part.data())) {
// Don't if we have an alternative part as parent
return true;
} else if (dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(part.data())) {
if (start.data() == part.data()) {
return false;
}
return true;
} else if (const auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart *>(part.data())) {
if (enc->error()) {
return true;
}
// If we have a textpart with encrypted and unencrypted subparts we want to return the textpart
if (dynamic_cast<MimeTreeParser::TextMessagePart *>(enc->parentPart())) {
return false;
}
} else if (const auto sig = dynamic_cast<MimeTreeParser::SignedMessagePart *>(part.data())) {
// Signatures without subparts already contain the text
return !sig->hasSubParts();
}
return false;
});
}
MessagePart::List ObjectTreeParser::collectAttachmentParts()
{
MessagePart::List contentParts = ::collect(
mParsedPart,
[](const MessagePart::Ptr &) {
return true;
},
[](const MessagePart::Ptr &part) {
return part->isAttachment();
});
return contentParts;
}
/*
* This naive implementation assumes that there is an encrypted part wrapping a signature.
* For other cases we would have to process both recursively (I think?)
*/
void ObjectTreeParser::decryptAndVerify()
{
// We first decrypt
::collect(
mParsedPart,
[](const MessagePart::Ptr &) {
return true;
},
[](const MessagePart::Ptr &part) {
if (const auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart *>(part.data())) {
enc->startDecryption();
}
return false;
});
// And then verify the available signatures
::collect(
mParsedPart,
[](const MessagePart::Ptr &) {
return true;
},
[](const MessagePart::Ptr &part) {
if (const auto enc = dynamic_cast<MimeTreeParser::SignedMessagePart *>(part.data())) {
enc->startVerification();
}
return false;
});
}
QString ObjectTreeParser::resolveCidLinks(const QString &html)
{
auto text = html;
static const auto regex = QRegularExpression(QLatin1StringView("(src)\\s*=\\s*(\"|')(cid:[^\"']+)\\2"));
auto it = regex.globalMatchView(text);
while (it.hasNext()) {
const auto match = it.next();
const auto link = QUrl(match.captured(3));
auto cid = link.path();
auto mailMime = const_cast<KMime::Content *>(find([=](KMime::Content *content) {
if (!content || !content->contentID(false)) {
return false;
}
return QString::fromLatin1(content->contentID(false)->identifier()) == cid;
}));
if (mailMime) {
const auto contentType = mailMime->contentType(false);
if (!contentType) {
qWarning() << "No content type, skipping";
continue;
}
QMimeDatabase mimeDb;
const auto mimetype = mimeDb.mimeTypeForName(QString::fromLatin1(contentType->mimeType())).name();
if (mimetype.startsWith(QLatin1StringView("image/"))) {
// We reencode to base64 below.
const auto data = mailMime->decodedContent();
if (data.isEmpty()) {
qWarning() << "Attachment is empty.";
continue;
}
text.replace(match.captured(0), QString::fromLatin1("src=\"data:%1;base64,%2\"").arg(mimetype, QString::fromLatin1(data.toBase64())));
}
} else {
qWarning() << "Failed to find referenced attachment: " << cid;
}
}
return text;
}
//-----------------------------------------------------------------------------
void ObjectTreeParser::parseObjectTree(const QByteArray &mimeMessage)
{
const auto mailData = KMime::CRLFtoLF(mimeMessage);
mMsg = KMime::Message::Ptr(new KMime::Message);
mMsg->setContent(mailData);
mMsg->parse();
// We avoid using mMsg->contentType()->charset(), because that will just return kmime's defaultCharset(), ISO-8859-1
const auto charset = mMsg->contentType()->parameter("charset").toLatin1();
if (charset.isEmpty()) {
mMsg->contentType()->setCharset("us-ascii");
}
parseObjectTree(mMsg.data());
}
void ObjectTreeParser::parseObjectTree(KMime::Content *node)
{
mTopLevelContent = node;
mParsedPart = parseObjectTreeInternal(node, false);
}
MessagePart::Ptr ObjectTreeParser::parsedPart() const
{
return mParsedPart;
}
/*
* This will lookup suitable formatters based on the type,
* and let them generate a list of parts.
* If the formatter generated a list of parts, then those are taken, otherwise we move on to the next match.
*/
MessagePart::List ObjectTreeParser::processType(KMime::Content *node, const QByteArray &mediaType, const QByteArray &subType)
{
static MimeTreeParser::BodyPartFormatterBaseFactory factory;
const auto sub = factory.subtypeRegistry(mediaType.constData());
const auto range = sub.equal_range(subType.constData());
for (auto it = range.first; it != range.second; ++it) {
const auto formatter = it->second;
if (!formatter) {
continue;
}
const auto list = formatter->processList(this, node);
if (!list.isEmpty()) {
return list;
}
}
return {};
}
MessagePart::Ptr ObjectTreeParser::parseObjectTreeInternal(KMime::Content *node, bool onlyOneMimePart)
{
if (!node) {
return MessagePart::Ptr();
}
auto parsedPart = MessagePart::Ptr(new MessagePartList(this, node));
parsedPart->setIsRoot(node->isTopLevel());
const auto contents = node->parent() ? node->parent()->contents() : KMime::Content::List{node};
for (int i = contents.indexOf(node); i < contents.size(); ++i) {
node = contents.at(i);
QByteArray mediaType("text");
QByteArray subType("plain");
if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() && !node->contentType()->subType().isEmpty()) {
mediaType = node->contentType()->mediaType();
subType = node->contentType()->subType();
}
auto messageParts = [&] {
// Try the specific type handler
{
auto list = processType(node, mediaType, subType);
if (!list.isEmpty()) {
return list;
}
}
// Fallback to the generic handler
{
auto list = processType(node, mediaType, "*"_ba);
if (!list.isEmpty()) {
return list;
}
}
// Fallback to the default handler
return defaultHandling(node);
}();
for (const auto &part : messageParts) {
parsedPart->appendSubPart(part);
}
if (onlyOneMimePart) {
break;
}
}
return parsedPart;
}
QList<MessagePart::Ptr> ObjectTreeParser::defaultHandling(KMime::Content *node)
{
if (node->contentType()->mimeType() == "application/octet-stream"_ba
&& (node->contentType()->name().endsWith(QLatin1StringView("p7m")) || node->contentType()->name().endsWith(QLatin1StringView("p7s"))
|| node->contentType()->name().endsWith(QLatin1StringView("p7c")))) {
auto list = processType(node, "application", "pkcs7-mime");
if (!list.isEmpty()) {
return list;
}
}
return {AttachmentMessagePart::Ptr(new AttachmentMessagePart(this, node))};
}
QByteArray ObjectTreeParser::codecNameFor(KMime::Content *node) const
{
if (!node) {
return "UTF-8"_ba;
}
QByteArray charset = node->contentType()->charset().toLower();
// utf-8 is a superset of us-ascii, so we don't lose anything if we use it instead
// utf-8 is used so widely nowadays that it is a good idea to use it to fix issues with broken clients.
if (charset == "us-ascii"_ba) {
charset = "utf-8"_ba;
}
if (!charset.isEmpty()) {
if (const QStringDecoder c(charset.constData()); c.isValid()) {
return charset;
}
}
// no charset means us-ascii (RFC 2045), so using local encoding should
// be okay
return "UTF-8"_ba;
}
diff --git a/src/core/partmodel.cpp b/src/core/partmodel.cpp
index 5efb92d..344f792 100644
--- a/src/core/partmodel.cpp
+++ b/src/core/partmodel.cpp
@@ -1,654 +1,654 @@
// SPDX-FileCopyrightText: 2016 Sandro Knauß <knauss@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "partmodel.h"
#include "enums.h"
#include "htmlutils.h"
#include "mimetreeparser_core_debug.h"
#include "objecttreeparser.h"
#include "utils.h"
#include <KLocalizedString>
#include <Libkleo/Compliance>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <QDebug>
#include <QGpgME/Protocol>
#include <QRegularExpression>
#include <QStringLiteral>
#include <QTextDocument>
#include <gpgme++/verificationresult.h>
-
+using namespace Qt::Literals::StringLiterals;
static std::optional<GpgME::Signature> signatureFromMessagePart(MimeTreeParser::MessagePart *messagePart)
{
const auto signatureState = messagePart->signatureState();
const bool messageIsSigned = signatureState == MimeTreeParser::KMMsgPartiallySigned || signatureState == MimeTreeParser::KMMsgFullySigned;
if (!messageIsSigned) {
return std::nullopt;
}
const auto signatureParts = messagePart->signatures();
Q_ASSERT(!signatureParts.isEmpty());
if (signatureParts.empty()) {
return std::nullopt;
}
const auto signaturePart = signatureParts.front(); // TODO add support for multiple signature
const auto signatures = signaturePart->partMetaData()->verificationResult.signatures();
Q_ASSERT(!signatures.empty());
if (signatures.empty()) {
return std::nullopt;
}
const auto signature = signatures.front(); // TODO add support for multiple signature
return signature;
}
// We return a pair containing the trimmed string, as well as a boolean indicating whether the string was trimmed or not
std::pair<QString, bool> PartModel::trim(const QString &text)
{
// The delimiters have <p>.? prefixed including the .? because sometimes we get a byte order mark <feff> (seen with user-agent:
// Microsoft-MacOutlook/10.1d.0.190908) We match both regulard withspace with \s and non-breaking spaces with \u00A0
const QList<QRegularExpression> delimiters{
// English
QRegularExpression{u"<p>.?-+Original(\\s|\u00A0)Message-+"_s, QRegularExpression::CaseInsensitiveOption},
// The remainder is not quoted
QRegularExpression{u"<p>.?On.*wrote:"_s, QRegularExpression::CaseInsensitiveOption},
// The remainder is quoted
QRegularExpression{u"> On.*wrote:"_s, QRegularExpression::CaseInsensitiveOption},
// German
// Forwarded
QRegularExpression{u"<p>.?Von:.*</p>"_s, QRegularExpression::CaseInsensitiveOption},
// Reply
QRegularExpression{u"<p>.?Am.*schrieb.*:</p>"_s, QRegularExpression::CaseInsensitiveOption},
// Signature
QRegularExpression{u"<p>.?--(\\s|\u00A0)<br>"_s, QRegularExpression::CaseInsensitiveOption},
};
for (const auto &expression : delimiters) {
auto i = expression.globalMatchView(text);
while (i.hasNext()) {
const auto match = i.next();
const int startOffset = match.capturedStart(0);
// This is a very simplistic detection for an inline reply where we would have the patterns before the actual message content.
// We simply ignore anything we find within the first few lines.
if (startOffset >= 5) {
return {text.mid(0, startOffset), true};
} else {
// Search for the next delimiter
continue;
}
}
}
return {text, false};
}
static QString addCss(const QString &s)
{
// Get the default font from QApplication
static const QString fontFamily = QFont{}.family();
// overflow:hidden ensures no scrollbars are ever shown.
static const QString css = u"<style>\n"_s
+ QStringLiteral(
"body {\n"
" overflow:hidden;\n"
" font-family: \"%1\" ! important;\n"
" color: #31363b ! important;\n"
" background-color: #fcfcfc ! important\n"
"}\n")
.arg(fontFamily)
+ QStringLiteral("blockquote { \n"
" border-left: 2px solid #bdc3c7 ! important;\n"
"}\n")
+ u"</style>"_s;
const QString header = QLatin1StringView(
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
"<html><head><title></title>")
+ css + QLatin1StringView("</head>\n<body>\n");
return header + s + u"</body></html>"_s;
}
class PartModelPrivate
{
public:
PartModelPrivate(PartModel *q_ptr, const std::shared_ptr<MimeTreeParser::ObjectTreeParser> &parser)
: q(q_ptr)
, mParser(parser)
{
collectContents();
}
~PartModelPrivate() = default;
void checkPart(const MimeTreeParser::MessagePart::Ptr part)
{
mMimeTypeCache[part.data()] = part->mimeType();
// Extract the content of the part and
mContents.insert(part.data(), extractContent(part.data()));
}
// Recursively find encapsulated messages
void findEncapsulated(const MimeTreeParser::EncapsulatedRfc822MessagePart::Ptr &e)
{
mEncapsulatedParts[e.data()] = mParser->collectContentParts(e);
for (const auto &subPart : std::as_const(mEncapsulatedParts[e.data()])) {
checkPart(subPart);
mParents[subPart.data()] = e.data();
if (auto encapsulatedSub = subPart.dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>()) {
findEncapsulated(encapsulatedSub);
}
}
}
QVariant extractContent(MimeTreeParser::MessagePart *messagePart)
{
if (auto alternativePart = dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(messagePart)) {
if (alternativePart->availableModes().contains(MimeTreeParser::AlternativeMessagePart::MultipartIcal)) {
return alternativePart->icalContent();
}
}
auto preprocessPlaintext = [&](const QString &text) {
// Reduce consecutive new lines to never exceed 2
auto cleaned = text;
cleaned.replace(QRegularExpression(u"[\n\r]{2,}"_s), u"\n\n"_s);
// We always do rich text (so we get highlighted links and stuff).
const auto html = Qt::convertFromPlainText(cleaned, Qt::WhiteSpaceNormal);
if (trimMail) {
const auto result = PartModel::trim(html);
isTrimmed = result.second;
Q_EMIT q->trimMailChanged();
return MimeTreeParser::linkify(result.first);
}
return MimeTreeParser::linkify(html);
};
if (messagePart->isHtml()) {
if (dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(messagePart)) {
containsHtmlAndPlain = true;
Q_EMIT q->containsHtmlChanged();
if (!showHtml) {
return preprocessPlaintext(messagePart->plaintextContent());
}
}
return addCss(mParser->resolveCidLinks(messagePart->htmlContent()));
}
if (auto attachmentPart = dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(messagePart)) {
auto node = attachmentPart->node();
if (node && mMimeTypeCache[attachmentPart] == "text/calendar"_ba) {
return messagePart->text();
}
}
return preprocessPlaintext(messagePart->text());
}
QVariant contentForPart(MimeTreeParser::MessagePart *messagePart) const
{
return mContents.value(messagePart);
}
void collectContents()
{
mEncapsulatedParts.clear();
mParents.clear();
mContents.clear();
containsHtmlAndPlain = false;
isTrimmed = false;
const auto parts = mParser->collectContentParts();
MimeTreeParser::MessagePart::List filteredParts;
for (const auto &part : parts) {
if (part->node()) {
const auto contentType = part->node()->contentType();
if (contentType && contentType->hasParameter("protected-headers")) {
const auto contentDisposition = part->node()->contentDisposition();
if (contentDisposition && contentDisposition->disposition() == KMime::Headers::CDinline) {
continue;
}
}
}
filteredParts << part;
}
for (const auto &part : std::as_const(filteredParts)) {
checkPart(part);
if (auto encapsulatedPart = part.dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>()) {
findEncapsulated(encapsulatedPart);
}
}
for (const auto &part : std::as_const(filteredParts)) {
if (mMimeTypeCache[part.data()] == "text/calendar"_ba) {
mParts.prepend(part);
} else {
mParts.append(part);
}
}
}
PartModel *const q;
MimeTreeParser::MessagePart::List mParts;
QHash<MimeTreeParser::MessagePart *, QByteArray> mMimeTypeCache;
QHash<MimeTreeParser::MessagePart *, MimeTreeParser::MessagePart::List> mEncapsulatedParts;
QHash<MimeTreeParser::MessagePart *, MimeTreeParser::MessagePart *> mParents;
QMap<MimeTreeParser::MessagePart *, QVariant> mContents;
std::shared_ptr<MimeTreeParser::ObjectTreeParser> mParser;
bool showHtml{false};
bool containsHtmlAndPlain{false};
bool trimMail{false};
bool isTrimmed{false};
};
PartModel::PartModel(std::shared_ptr<MimeTreeParser::ObjectTreeParser> parser)
: d(std::unique_ptr<PartModelPrivate>(new PartModelPrivate(this, parser)))
{
}
PartModel::~PartModel() = default;
void PartModel::setShowHtml(bool html)
{
if (d->showHtml != html) {
beginResetModel();
d->showHtml = html;
d->collectContents();
endResetModel();
Q_EMIT showHtmlChanged();
}
}
bool PartModel::showHtml() const
{
return d->showHtml;
}
void PartModel::setTrimMail(bool trim)
{
if (d->trimMail != trim) {
beginResetModel();
d->trimMail = trim;
d->collectContents();
endResetModel();
Q_EMIT trimMailChanged();
}
}
bool PartModel::trimMail() const
{
return d->trimMail;
}
bool PartModel::isTrimmed() const
{
return d->isTrimmed;
}
bool PartModel::containsHtml() const
{
return d->containsHtmlAndPlain;
}
QHash<int, QByteArray> PartModel::roleNames() const
{
return {
{TypeRole, "type"_ba},
{ContentRole, "content"_ba},
{IsEmbeddedRole, "isEmbedded"_ba},
{SidebarSecurityLevelRole, "sidebarSecurityLevel"_ba},
{EncryptionSecurityLevelRole, "encryptionSecurityLevel"_ba},
{SignatureSecurityLevelRole, "signatureSecurityLevel"_ba},
{EncryptionSecurityLevelRole, "encryptionSecurityLevel"_ba},
{ErrorType, "errorType"_ba},
{ErrorString, "errorString"_ba},
{IsErrorRole, "error"_ba},
{SenderRole, "sender"_ba},
{SignatureDetailsRole, "signatureDetails"_ba},
{SignatureIconNameRole, "signatureIconName"_ba},
{EncryptionDetails, "encryptionDetails"_ba},
{EncryptionIconNameRole, "encryptionIconName"_ba},
{DateRole, "date"_ba},
};
}
QModelIndex PartModel::index(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column != 0) {
return QModelIndex();
}
if (parent.isValid()) {
const auto part = static_cast<MimeTreeParser::MessagePart *>(parent.internalPointer());
auto encapsulatedPart = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(part);
if (encapsulatedPart) {
const auto parts = d->mEncapsulatedParts[encapsulatedPart];
if (row < parts.size()) {
return createIndex(row, column, parts.at(row).data());
}
}
return QModelIndex();
}
if (row < d->mParts.size()) {
return createIndex(row, column, d->mParts.at(row).data());
}
return QModelIndex();
}
SignatureInfo encryptionInfo(MimeTreeParser::MessagePart *messagePart)
{
SignatureInfo signatureInfo;
const auto encryptions = messagePart->encryptions();
if (encryptions.size() > 1) {
qWarning() << "Can't deal with more than one encryption";
}
for (const auto &encryptionPart : encryptions) {
signatureInfo.keyId = encryptionPart->partMetaData()->keyId;
signatureInfo.cryptoProto = encryptionPart->cryptoProto();
signatureInfo.decryptRecipients = encryptionPart->decryptRecipients();
}
return signatureInfo;
};
template<typename T>
const T *findHeader(KMime::Content *content)
{
if (!content) {
return {};
}
auto header = content->header<T>();
if (header || !content->parent()) {
return header;
}
return findHeader<T>(content->parent());
}
PartModel::SecurityLevel PartModel::signatureSecurityLevel(MimeTreeParser::MessagePart *messagePart)
{
auto signature = signatureFromMessagePart(messagePart);
if (!signature) {
return SecurityLevel::Unknow;
}
const auto summary = signature->summary();
if (summary & GpgME::Signature::Summary::Red) {
return SecurityLevel::Bad;
}
if (summary & GpgME::Signature::Summary::Valid) {
return SecurityLevel::Good;
}
return SecurityLevel::NotSoGood;
}
QString PartModel::signatureDetails(MimeTreeParser::MessagePart *messagePart)
{
auto signature = signatureFromMessagePart(messagePart);
if (!signature) {
return QString{};
}
// guess sender from mime node or parent node
auto from = findHeader<KMime::Headers::From>(messagePart->node());
if (from) {
const auto mailboxes = from->mailboxes();
if (!mailboxes.isEmpty()) {
auto mailBox = mailboxes.front();
if (mailBox.hasAddress()) {
return Kleo::Formatting::prettySignature(*signature, QString::fromUtf8(mailboxes.front().address()));
}
}
}
return Kleo::Formatting::prettySignature(*signature, {});
}
QVariant PartModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (index.internalPointer()) {
const auto messagePart = static_cast<MimeTreeParser::MessagePart *>(index.internalPointer());
// qWarning() << "Found message part " << messagePart->metaObject()->className() << messagePart->partMetaData()->status << messagePart->error();
Q_ASSERT(messagePart);
switch (role) {
case Qt::DisplayRole:
return u"Content%1"_s;
case SenderRole: {
if (auto e = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(messagePart)) {
return e->from();
}
return {};
}
case DateRole: {
if (auto e = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(messagePart)) {
return e->date();
}
return {};
}
case TypeRole: {
if (messagePart->error()) {
return QVariant::fromValue(Types::Error);
}
if (dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(messagePart)) {
return QVariant::fromValue(Types::Encapsulated);
}
if (auto alternativePart = dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(messagePart)) {
if (alternativePart->availableModes().contains(MimeTreeParser::AlternativeMessagePart::MultipartIcal)) {
return QVariant::fromValue(Types::Ical);
}
}
if (auto attachmentPart = dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(messagePart)) {
auto node = attachmentPart->node();
if (!node) {
qWarning() << "no content for attachment";
return {};
}
if (d->mMimeTypeCache[attachmentPart] == "text/calendar"_ba) {
return QVariant::fromValue(Types::Ical);
}
}
if (!d->showHtml && d->containsHtmlAndPlain) {
return QVariant::fromValue(Types::Plain);
}
// For simple html we don't need a browser
auto complexHtml = [&] {
if (messagePart->isHtml()) {
const auto text = messagePart->htmlContent();
if (text.contains(u"<!DOCTYPE html PUBLIC"_s)) {
// We can probably deal with this if it adheres to the strict dtd
//(that's what our composer produces as well)
if (!text.contains(u"http://www.w3.org/TR/REC-html40/strict.dtd"_s)) {
return true;
}
}
// Blockquotes don't support any styling which would be necessary so they become readable.
if (text.contains(u"blockquote"_s)) {
return true;
}
// Media queries are too advanced
if (text.contains(u"@media"_s)) {
return true;
}
// auto css properties are not supported e.g margin-left: auto;
if (text.contains(u": auto;"_s)) {
return true;
}
return false;
} else {
return false;
}
}();
if (complexHtml) {
return QVariant::fromValue(Types::Html);
}
return QVariant::fromValue(Types::Plain);
}
case IsEmbeddedRole:
return false;
case IsErrorRole:
return messagePart->error();
case ContentRole:
return d->contentForPart(messagePart);
case SidebarSecurityLevelRole: {
const auto signature = index.data(SignatureSecurityLevelRole).value<SecurityLevel>();
const auto encryption = index.data(EncryptionSecurityLevelRole).value<SecurityLevel>();
if (signature == SecurityLevel::Bad || encryption == SecurityLevel::Bad) {
return SecurityLevel::Bad;
}
if (signature == SecurityLevel::NotSoGood || encryption == SecurityLevel::NotSoGood) {
return SecurityLevel::NotSoGood;
}
if (signature == SecurityLevel::Good || encryption == SecurityLevel::Good) {
return SecurityLevel::Good;
}
return SecurityLevel::Unknow;
}
case SignatureSecurityLevelRole:
// Color displayed for the signature info box
return signatureSecurityLevel(messagePart);
case EncryptionSecurityLevelRole: {
// Color displayed for the encryption info box
const auto encryption = messagePart->encryptionState();
const bool messageIsEncrypted = encryption == MimeTreeParser::KMMsgPartiallyEncrypted || encryption == MimeTreeParser::KMMsgFullyEncrypted;
if (messagePart->error()) {
return SecurityLevel::Bad;
}
return messageIsEncrypted ? SecurityLevel::Good : SecurityLevel::Unknow;
}
case EncryptionIconNameRole: {
const auto encryption = messagePart->encryptionState();
const bool messageIsEncrypted = encryption == MimeTreeParser::KMMsgPartiallyEncrypted || encryption == MimeTreeParser::KMMsgFullyEncrypted;
if (messagePart->error()) {
return u"data-error"_s;
}
return messageIsEncrypted ? u"mail-encrypted"_s : QString();
}
case SignatureIconNameRole: {
auto signature = signatureFromMessagePart(messagePart);
if (!signature) {
return QString{};
}
const auto summary = signature->summary();
if (summary & GpgME::Signature::Valid) {
return u"mail-signed"_s;
} else if (summary & GpgME::Signature::Red) {
return u"data-error"_s;
} else {
return u"data-warning"_s;
}
}
case SignatureDetailsRole:
return signatureDetails(messagePart);
case EncryptionDetails:
return QVariant::fromValue(encryptionInfo(messagePart));
case ErrorType:
return messagePart->error();
case ErrorString: {
switch (messagePart->error()) {
case MimeTreeParser::MessagePart::NoKeyError: {
if (auto encryptedMessagePart = dynamic_cast<MimeTreeParser::EncryptedMessagePart *>(messagePart)) {
if (encryptedMessagePart->isNoSecKey()) {
QString errorMessage;
if (encryptedMessagePart->cryptoProto() == QGpgME::smime()) {
errorMessage += i18ndc("mimetreeparser", "@info:status", "You cannot decrypt this message.");
} else {
errorMessage += i18ndc("mimetreeparser", "@info:status", "You cannot decrypt this message.");
}
if (!encryptedMessagePart->decryptRecipients().empty()) {
errorMessage += QLatin1Char(' ')
+ i18ndcp("mimetreeparser",
"@info:status",
"The message is encrypted for the following recipient:",
"The message is encrypted for the following recipients:",
encryptedMessagePart->decryptRecipients().size());
errorMessage +=
MimeTreeParser::decryptRecipientsToHtml(encryptedMessagePart->decryptRecipients(), encryptedMessagePart->cryptoProto());
}
return errorMessage;
}
}
}
return messagePart->errorString();
case MimeTreeParser::MessagePart::PassphraseError:
return i18ndc("mimetreeparser", "@info:status", "Wrong passphrase.");
case MimeTreeParser::MessagePart::UnknownError:
break;
default:
break;
}
return messagePart->errorString();
}
}
}
return QVariant();
}
QModelIndex PartModel::parent(const QModelIndex &index) const
{
if (index.isValid()) {
if (auto indexPart = static_cast<MimeTreeParser::MessagePart *>(index.internalPointer())) {
for (const auto &part : std::as_const(d->mParts)) {
if (part.data() == indexPart) {
return QModelIndex();
}
}
const auto parentPart = d->mParents[indexPart];
Q_ASSERT(parentPart);
int row = 0;
const auto parts = d->mEncapsulatedParts[parentPart];
for (const auto &part : parts) {
if (part.data() == indexPart) {
break;
}
row++;
}
return createIndex(row, 0, parentPart);
}
return {};
}
return {};
}
int PartModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
const auto part = static_cast<MimeTreeParser::MessagePart *>(parent.internalPointer());
auto encapsulatedPart = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(part);
if (encapsulatedPart) {
const auto parts = d->mEncapsulatedParts[encapsulatedPart];
return parts.size();
}
return 0;
}
return d->mParts.count();
}
int PartModel::columnCount(const QModelIndex &) const
{
return 1;
}
#include "moc_partmodel.cpp"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Jan 15, 9:39 PM (6 h, 22 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
69/c3/b3a63bbba7a2f5d1abb32fd79ea6
Attached To
rMTP MIME Tree Parser
Event Timeline
Log In to Comment