Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F26446704
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
64 KB
Subscribers
None
View Options
diff --git a/src/core/messagepart.cpp b/src/core/messagepart.cpp
index 55f79b6..b3747de 100644
--- a/src/core/messagepart.cpp
+++ b/src/core/messagepart.cpp
@@ -1,983 +1,975 @@
// SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com>
// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "messagepart.h"
#include "cryptohelper.h"
#include "mimetreeparser_core_debug.h"
#include "objecttreeparser.h"
#include "utils.h"
#include <KLocalizedString>
#include <KMime/Content>
#include <Libkleo/Compliance>
#include <Libkleo/KeyCache>
#include <QGpgME/DN>
#include <QGpgME/DecryptVerifyJob>
#include <QGpgME/Protocol>
#include <QGpgME/VerifyDetachedJob>
#include <QGpgME/VerifyOpaqueJob>
#include <QStringDecoder>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <gpgme.h>
#include <libkleo/formatting.h>
using namespace MimeTreeParser;
//------MessagePart-----------------------
MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text, KMime::Content *node)
: mText(text)
, mOtp(otp)
, mParentPart(nullptr)
, mNode(node) // only null for messagepartlist
, mError(NoError)
, mRoot(false)
{
}
MessagePart::~MessagePart()
{
for (auto n : std::as_const(mNodesToDelete)) {
delete n;
}
}
MessagePart::Disposition MessagePart::disposition() const
{
if (!mNode) {
return Invalid;
}
const auto cd = mNode->contentDisposition(false);
if (!cd) {
return Invalid;
}
switch (cd->disposition()) {
case KMime::Headers::CDinline:
return Inline;
case KMime::Headers::CDattachment:
return Attachment;
default:
return Invalid;
}
}
QString MessagePart::filename() const
{
if (!mNode) {
return {};
}
if (const auto cd = mNode->contentDisposition(false)) {
const auto name = cd->filename();
// Allow for a fallback for mails that have a ContentDisposition header, but don't set the filename anyways.
// Not the recommended way, but exists.
if (!name.isEmpty()) {
return name;
}
}
if (const auto ct = mNode->contentType(false)) {
return ct->name();
}
return {};
}
static KMime::Headers::ContentType *contentType(KMime::Content *node)
{
if (node) {
return node->contentType(false);
}
return nullptr;
}
QByteArray MessagePart::charset() const
{
if (!mNode) {
return QByteArrayLiteral("us-ascii");
}
if (auto ct = contentType(mNode)) {
return ct->charset();
}
// Per rfc2045 us-ascii is the default
return QByteArrayLiteral("us-ascii");
}
QByteArray MessagePart::mimeType() const
{
if (auto ct = contentType(mNode)) {
return ct->mimeType();
}
return {};
}
bool MessagePart::isText() const
{
if (auto ct = contentType(mNode)) {
return ct->isText();
}
return false;
}
MessagePart::Error MessagePart::error() const
{
return mError;
}
QString MessagePart::errorString() const
{
return mMetaData.errorText;
}
PartMetaData *MessagePart::partMetaData()
{
return &mMetaData;
}
bool MessagePart::isAttachment() const
{
if (mNode) {
return KMime::isAttachment(mNode);
}
return false;
}
KMime::Content *MessagePart::node() const
{
return mNode;
}
void MessagePart::setIsRoot(bool root)
{
mRoot = root;
}
bool MessagePart::isRoot() const
{
return mRoot;
}
QString MessagePart::text() const
{
return mText;
}
void MessagePart::setText(const QString &text)
{
mText = text;
}
bool MessagePart::isHtml() const
{
return false;
}
MessagePart *MessagePart::parentPart() const
{
return mParentPart;
}
void MessagePart::setParentPart(MessagePart *parentPart)
{
mParentPart = parentPart;
}
QString MessagePart::htmlContent() const
{
return text();
}
QString MessagePart::plaintextContent() const
{
return text();
}
void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart)
{
auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart);
mRoot = subMessagePart->isRoot();
for (const auto &part : std::as_const(subMessagePart->subParts())) {
appendSubPart(part);
}
}
void MessagePart::parseInternal(const QByteArray &data)
{
auto tempNode = new KMime::Content();
const auto lfData = KMime::CRLFtoLF(data);
// We have to deal with both bodies and full parts. In inline encrypted/signed parts we can have nested parts,
// or just plain-text, and both ends up here. setContent defaults to setting only the header, so we have to avoid this.
if (lfData.contains("\n\n")) {
tempNode->setContent(lfData);
} else {
tempNode->setBody(lfData);
}
tempNode->parse();
tempNode->contentType()->setCharset(charset());
bindLifetime(tempNode);
if (!tempNode->head().isEmpty()) {
tempNode->contentDescription()->from7BitString(QByteArrayLiteral("temporary node"));
}
parseInternal(tempNode);
}
QString MessagePart::renderInternalText() const
{
QString text;
for (const auto &mp : subParts()) {
text += mp->text();
}
return text;
}
void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart)
{
messagePart->setParentPart(this);
mBlocks.append(messagePart);
}
const QList<MessagePart::Ptr> &MessagePart::subParts() const
{
return mBlocks;
}
bool MessagePart::hasSubParts() const
{
return !mBlocks.isEmpty();
}
QList<SignedMessagePart *> MessagePart::signatures() const
{
QList<SignedMessagePart *> list;
if (auto sig = dynamic_cast<SignedMessagePart *>(const_cast<MessagePart *>(this))) {
list << sig;
}
auto parent = parentPart();
while (parent) {
if (auto sig = dynamic_cast<SignedMessagePart *>(parent)) {
list << sig;
}
parent = parent->parentPart();
}
return list;
}
QList<EncryptedMessagePart *> MessagePart::encryptions() const
{
QList<EncryptedMessagePart *> list;
if (auto sig = dynamic_cast<EncryptedMessagePart *>(const_cast<MessagePart *>(this))) {
list << sig;
}
auto parent = parentPart();
while (parent) {
if (auto sig = dynamic_cast<EncryptedMessagePart *>(parent)) {
list << sig;
}
parent = parent->parentPart();
}
return list;
}
KMMsgEncryptionState MessagePart::encryptionState() const
{
if (!encryptions().isEmpty()) {
return KMMsgFullyEncrypted;
}
return KMMsgNotEncrypted;
}
KMMsgSignatureState MessagePart::signatureState() const
{
if (!signatures().isEmpty()) {
return KMMsgFullySigned;
}
return KMMsgNotSigned;
}
void MessagePart::bindLifetime(KMime::Content *node)
{
mNodesToDelete << node;
}
KMime::Headers::Base *MimeTreeParser::MessagePart::header(const char *header) const
{
if (node() && node()->hasHeader(header)) {
return node()->headerByType(header);
}
if (auto parent = parentPart()) {
return parent->header(header);
}
return nullptr;
}
//-----MessagePartList----------------------
MessagePartList::MessagePartList(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
}
QString MessagePartList::text() const
{
return renderInternalText();
}
QString MessagePartList::plaintextContent() const
{
return QString();
}
QString MessagePartList::htmlContent() const
{
return QString();
}
//-----TextMessageBlock----------------------
TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePartList(otp, node)
, mSignatureState(KMMsgSignatureStateUnknown)
, mEncryptionState(KMMsgEncryptionStateUnknown)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
return;
}
parseContent();
}
void TextMessagePart::parseContent()
{
mSignatureState = KMMsgNotSigned;
mEncryptionState = KMMsgNotEncrypted;
const auto blocks = prepareMessageForDecryption(mNode->decodedContent());
// We also get blocks for unencrypted messages
if (!blocks.isEmpty()) {
auto aCodec = QStringDecoder(mOtp->codecNameFor(mNode).constData());
const auto cryptProto = QGpgME::openpgp();
/* The (overall) signature/encrypted status is broken
* if one unencrypted part is at the beginning or in the middle
* because mailmain adds an unencrypted part at the end this should not break the overall status
*
* That's why we first set the tmp status and if one crypted/signed block comes afterwards, than
* the status is set to unencryped
*/
bool fullySignedOrEncrypted = true;
bool fullySignedOrEncryptedTmp = true;
for (const auto &block : blocks) {
if (!fullySignedOrEncryptedTmp) {
fullySignedOrEncrypted = false;
}
if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) {
fullySignedOrEncryptedTmp = false;
appendSubPart(MessagePart::Ptr(new MessagePart(mOtp, aCodec.decode(KMime::CRLFtoLF(block.text())))));
} else if (block.type() == PgpMessageBlock) {
auto content = new KMime::Content;
content->setBody(block.text());
content->parse();
content->contentType()->setCharset(charset());
EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, content, content, false));
mp->bindLifetime(content);
mp->setIsEncrypted(true);
appendSubPart(mp);
} else if (block.type() == ClearsignedBlock) {
auto content = new KMime::Content;
content->setBody(block.text());
content->parse();
content->contentType()->setCharset(charset());
SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, cryptProto, nullptr, content, false));
mp->bindLifetime(content);
appendSubPart(mp);
} else {
continue;
}
const auto mp = subParts().last().staticCast<MessagePart>();
const PartMetaData *messagePart(mp->partMetaData());
if (!messagePart->isEncrypted && !messagePart->isSigned() && !block.text().trimmed().isEmpty()) {
mp->setText(aCodec.decode(KMime::CRLFtoLF(block.text())));
}
if (messagePart->isEncrypted) {
mEncryptionState = KMMsgPartiallyEncrypted;
}
if (messagePart->isSigned()) {
mSignatureState = KMMsgPartiallySigned;
}
}
// Do we have an fully Signed/Encrypted Message?
if (fullySignedOrEncrypted) {
if (mSignatureState == KMMsgPartiallySigned) {
mSignatureState = KMMsgFullySigned;
}
if (mEncryptionState == KMMsgPartiallyEncrypted) {
mEncryptionState = KMMsgFullyEncrypted;
}
}
}
}
KMMsgEncryptionState TextMessagePart::encryptionState() const
{
if (mEncryptionState == KMMsgNotEncrypted) {
return MessagePart::encryptionState();
}
return mEncryptionState;
}
KMMsgSignatureState TextMessagePart::signatureState() const
{
if (mSignatureState == KMMsgNotSigned) {
return MessagePart::signatureState();
}
return mSignatureState;
}
//-----AttachmentMessageBlock----------------------
AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: TextMessagePart(otp, node)
{
}
//-----HtmlMessageBlock----------------------
HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
return;
}
setText(QStringDecoder(mOtp->codecNameFor(mNode).constData()).decode(KMime::CRLFtoLF(mNode->decodedContent())));
}
//-----MimeMessageBlock----------------------
MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart)
: MessagePart(otp, QString(), node)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
return;
}
parseInternal(mNode, onlyOneMimePart);
}
MimeMessagePart::~MimeMessagePart()
{
}
QString MimeMessagePart::text() const
{
return renderInternalText();
}
QString MimeMessagePart::plaintextContent() const
{
return QString();
}
QString MimeMessagePart::htmlContent() const
{
return QString();
}
//-----AlternativeMessagePart----------------------
AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
if (auto dataIcal = findTypeInDirectChildren(mNode, "text/calendar")) {
mChildParts[MultipartIcal] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataIcal, true));
}
if (auto dataText = findTypeInDirectChildren(mNode, "text/plain")) {
mChildParts[MultipartPlain] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataText, true));
}
if (auto dataHtml = findTypeInDirectChildren(mNode, "text/html")) {
mChildParts[MultipartHtml] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataHtml, true));
} else {
// If we didn't find the HTML part as the first child of the multipart/alternative, it might
// be that this is a HTML message with images, and text/plain and multipart/related are the
// immediate children of this multipart/alternative node.
// In this case, the HTML node is a child of multipart/related.
// In the case of multipart/related we don't expect multiple html parts, it is usually used to group attachments
// with html content.
//
// In any case, this is not a complete implementation of MIME, but an approximation for the kind of mails we actually see in the wild.
auto data = [&] {
if (auto d = findTypeInDirectChildren(mNode, "multipart/related")) {
return d;
}
return findTypeInDirectChildren(mNode, "multipart/mixed");
}();
if (data) {
QString htmlContent;
const auto parts = data->contents();
for (auto p : parts) {
if ((!p->contentType()->isEmpty()) && (p->contentType()->mimeType() == "text/html")) {
htmlContent += MimeMessagePart(mOtp, p, true).text();
} else if (KMime::isAttachment(p)) {
appendSubPart(MimeMessagePart::Ptr(new MimeMessagePart(otp, p, true)));
}
}
mChildParts[MultipartHtml] = MessagePart::Ptr(new MessagePart(mOtp, htmlContent, nullptr));
}
}
}
AlternativeMessagePart::~AlternativeMessagePart()
{
}
QList<AlternativeMessagePart::HtmlMode> AlternativeMessagePart::availableModes()
{
return mChildParts.keys();
}
QString AlternativeMessagePart::text() const
{
if (mChildParts.contains(MultipartPlain)) {
return mChildParts[MultipartPlain]->text();
}
return QString();
}
bool AlternativeMessagePart::isHtml() const
{
return mChildParts.contains(MultipartHtml);
}
QString AlternativeMessagePart::plaintextContent() const
{
return text();
}
QString AlternativeMessagePart::htmlContent() const
{
if (mChildParts.contains(MultipartHtml)) {
return mChildParts[MultipartHtml]->text();
} else {
return plaintextContent();
}
}
QString AlternativeMessagePart::icalContent() const
{
if (mChildParts.contains(MultipartIcal)) {
return mChildParts[MultipartIcal]->text();
}
return {};
}
//-----CertMessageBlock----------------------
CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, QGpgME::Protocol *cryptoProto)
: MessagePart(otp, QString(), node)
, mCryptoProto(cryptoProto)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
return;
}
}
CertMessagePart::~CertMessagePart()
{
}
QString CertMessagePart::text() const
{
return QString();
}
//-----SignedMessageBlock---------------------
SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp,
const QGpgME::Protocol *cryptoProto,
KMime::Content *node,
KMime::Content *signedData,
bool parseAfterDecryption)
: MessagePart(otp, {}, node)
, mParseAfterDecryption(parseAfterDecryption)
, mCryptoProto(cryptoProto)
, mSignedData(signedData)
{
mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In.");
- qWarning() << "created" << this;
}
-SignedMessagePart::~SignedMessagePart()
-{
- qWarning() << "desctrued" << this;
-}
+SignedMessagePart::~SignedMessagePart() = default;
static QString prettifyDN(const char *uid)
{
// We used to use QGpgME::DN::prettyDN here. But I'm not sure what we actually need it for.
return QString::fromUtf8(uid);
}
const QGpgME::Protocol *SignedMessagePart::cryptoProto() const
{
return mCryptoProto;
}
void SignedMessagePart::startVerification()
{
if (!mSignedData) {
return;
}
mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In.");
mMetaData.isEncrypted = false;
mMetaData.isDecryptable = false;
auto codec = QStringDecoder(mOtp->codecNameFor(mSignedData).constData());
// If we have a mNode, this is a detached signature
if (mNode) {
const auto signature = mNode->decodedContent();
// This is necessary in case the original data contained CRLF's. Otherwise the signature will not match the data (since KMIME normalizes to LF)
const QByteArray signedData = KMime::LFtoCRLF(mSignedData->encodedContent());
const auto job = mCryptoProto->verifyDetachedJob();
setVerificationResult(job->exec(signature, signedData), signedData);
job->deleteLater();
setText(codec.decode(KMime::CRLFtoLF(signedData)));
} else {
QByteArray outdata;
const auto job = mCryptoProto->verifyOpaqueJob();
setVerificationResult(job->exec(mSignedData->decodedContent(), outdata), outdata);
job->deleteLater();
setText(codec.decode(KMime::CRLFtoLF(outdata)));
}
-
- if (!mMetaData.isSigned()) {
- mMetaData.creationTime = QDateTime();
- }
}
void SignedMessagePart::setVerificationResult(const GpgME::VerificationResult &result, const QByteArray &signedData)
{
mMetaData.verificationResult = result;
qWarning() << "set mMetaData.verificationResult" << this;
if (mMetaData.isSigned()) {
if (!signedData.isEmpty() && mParseAfterDecryption) {
parseInternal(signedData);
}
}
}
QString SignedMessagePart::plaintextContent() const
{
if (!mNode) {
return MessagePart::text();
} else {
return QString();
}
}
QString SignedMessagePart::htmlContent() const
{
if (!mNode) {
return MessagePart::text();
} else {
return QString();
}
}
//-----CryptMessageBlock---------------------
EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp,
const QString &text,
const QGpgME::Protocol *cryptoProto,
KMime::Content *node,
KMime::Content *encryptedNode,
bool parseAfterDecryption)
: MessagePart(otp, text, node)
, mParseAfterDecryption(parseAfterDecryption)
, mPassphraseError(false)
, mNoSecKey(false)
, mDecryptMessage(false)
, mCryptoProto(cryptoProto)
, mEncryptedNode(encryptedNode)
{
mMetaData.isEncrypted = false;
mMetaData.isDecryptable = false;
mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In.");
}
void EncryptedMessagePart::setIsEncrypted(bool encrypted)
{
mMetaData.isEncrypted = encrypted;
}
bool EncryptedMessagePart::isEncrypted() const
{
return mMetaData.isEncrypted;
}
const QGpgME::Protocol *EncryptedMessagePart::cryptoProto() const
{
return mCryptoProto;
}
void EncryptedMessagePart::setDecryptMessage(bool decrypt)
{
mDecryptMessage = decrypt;
}
bool EncryptedMessagePart::decryptMessage() const
{
return mDecryptMessage;
}
bool EncryptedMessagePart::isDecryptable() const
{
return mMetaData.isDecryptable;
}
bool EncryptedMessagePart::isNoSecKey() const
{
return mNoSecKey;
}
bool EncryptedMessagePart::passphraseError() const
{
return mPassphraseError;
}
bool EncryptedMessagePart::decrypt(KMime::Content &data)
{
mError = NoError;
mMetaData.errorText.clear();
// FIXME
// mMetaData.auditLogError = GpgME::Error();
mMetaData.auditLog.clear();
const QByteArray ciphertext = data.decodedContent();
QByteArray plainText;
auto job = mCryptoProto->decryptVerifyJob();
const std::pair<GpgME::DecryptionResult, GpgME::VerificationResult> p = job->exec(ciphertext, plainText);
job->deleteLater();
auto decryptResult = p.first;
auto verifyResult = p.second;
mMetaData.verificationResult = verifyResult;
// Normalize CRLF's
plainText = KMime::CRLFtoLF(plainText);
auto codec = QStringDecoder(mOtp->codecNameFor(&data).constData());
const auto decoded = codec.decode(plainText);
if (partMetaData()->isSigned()) {
// We simply attach a signed message part to indicate that this content is also signed
// We're forwarding mNode to not loose the encoding information
auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, mCryptoProto, mNode, nullptr));
subPart->setText(decoded);
subPart->setVerificationResult(verifyResult, plainText);
appendSubPart(subPart);
}
mDecryptRecipients.clear();
bool cannotDecrypt = false;
bool bDecryptionOk = !decryptResult.error();
for (const auto &recipient : decryptResult.recipients()) {
if (!recipient.status()) {
bDecryptionOk = true;
}
GpgME::Key key;
key = Kleo::KeyCache::instance()->findByKeyIDOrFingerprint(recipient.keyID());
if (key.isNull()) {
auto ret = Kleo::KeyCache::instance()->findSubkeysByKeyID({recipient.keyID()});
if (ret.size() == 1) {
key = ret.front().parent();
}
if (key.isNull()) {
qCDebug(MIMETREEPARSER_CORE_LOG) << "Found no Key for KeyID " << recipient.keyID();
}
}
mDecryptRecipients.emplace_back(recipient, key);
}
if (!bDecryptionOk && partMetaData()->isSigned()) {
// Only a signed part
partMetaData()->isEncrypted = false;
bDecryptionOk = true;
mDecryptedData = plainText;
} else {
mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_BAD_PASSPHRASE;
mMetaData.isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA;
if (decryptResult.error().isCanceled()) {
setDecryptMessage(false);
}
partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString());
if (Kleo::DeVSCompliance::isCompliant()) {
partMetaData()->isCompliant = decryptResult.isDeVs();
partMetaData()->compliance = Kleo::DeVSCompliance::name(decryptResult.isDeVs());
} else {
partMetaData()->isCompliant = true;
}
if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) {
partMetaData()->keyId = decryptResult.recipient(0).keyID();
}
if (bDecryptionOk) {
mDecryptedData = plainText;
} else {
mNoSecKey = true;
const auto decryRecipients = decryptResult.recipients();
for (const GpgME::DecryptionResult::Recipient &recipient : decryRecipients) {
mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY);
}
if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly
mPassphraseError = true;
}
}
}
if (!bDecryptionOk) {
QString cryptPlugLibName;
mError = UnknownError;
if (mCryptoProto) {
cryptPlugLibName = mCryptoProto->name();
}
if (mNoSecKey) {
mError = NoKeyError;
}
if (mPassphraseError) {
mError = PassphraseError;
}
if (!mCryptoProto) {
partMetaData()->errorText = i18n("No appropriate crypto plug-in was found.");
} else if (cannotDecrypt) {
partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName);
} else if (!passphraseError()) {
partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1StringView("<br />")
+ i18n("Error: %1", partMetaData()->errorText);
}
}
return bDecryptionOk;
}
void EncryptedMessagePart::startDecryption(KMime::Content *data)
{
mMetaData.isEncrypted = true;
mMetaData.isDecryptable = decrypt(*data);
if (mParseAfterDecryption && !mMetaData.isSigned()) {
parseInternal(mDecryptedData);
} else {
setText(QString::fromUtf8(mDecryptedData.constData()));
}
}
void EncryptedMessagePart::startDecryption()
{
if (mEncryptedNode) {
startDecryption(mEncryptedNode);
} else {
startDecryption(mNode);
}
}
std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> EncryptedMessagePart::decryptRecipients() const
{
return mDecryptRecipients;
}
QString EncryptedMessagePart::plaintextContent() const
{
if (!mNode) {
return MessagePart::text();
} else {
return QString();
}
}
QString EncryptedMessagePart::htmlContent() const
{
if (!mNode) {
return MessagePart::text();
} else {
return QString();
}
}
QString EncryptedMessagePart::text() const
{
if (hasSubParts()) {
auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
if (_mp) {
return _mp->text();
}
}
return MessagePart::text();
}
EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message)
: MessagePart(otp, QString(), node)
, mMessage(message)
{
mMetaData.isEncrypted = false;
mMetaData.isEncapsulatedRfc822Message = true;
if (!mMessage) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "Node is of type message/rfc822 but doesn't have a message!";
return;
}
parseInternal(message.data());
}
QString EncapsulatedRfc822MessagePart::text() const
{
return renderInternalText();
}
QString EncapsulatedRfc822MessagePart::from() const
{
if (auto from = mMessage->from(false)) {
return from->asUnicodeString();
}
return {};
}
QDateTime EncapsulatedRfc822MessagePart::date() const
{
if (auto date = mMessage->date(false)) {
return date->dateTime();
}
return {};
}
HeadersPart::HeadersPart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
}
#include "moc_messagepart.cpp"
diff --git a/src/core/partmetadata.h b/src/core/partmetadata.h
index c07b867..1821578 100644
--- a/src/core/partmetadata.h
+++ b/src/core/partmetadata.h
@@ -1,58 +1,56 @@
// SPDX-FileCopyrightText: 2002-2003 Karl-Heinz Zimmer <khz@kde.org>
// SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QDateTime>
#include <QStringList>
#include <gpgme++/context.h>
#include <gpgme++/verificationresult.h>
namespace MimeTreeParser
{
class PartMetaData
{
public:
PartMetaData()
: isEncrypted(false)
, isDecryptable(false)
, inProgress(false)
, technicalProblem(false)
, isEncapsulatedRfc822Message(false)
, isCompliant(false)
, keyRevoked(false)
{
}
inline bool isSigned() const
{
return verificationResult.signatures().size() > 0;
}
GpgME::VerificationResult verificationResult;
QString signClass;
QString signer;
QStringList signerMailAddresses;
QByteArray keyId;
- QString keyTrust;
QString status; // to be used for unknown plug-ins
int status_code = 0; // = GPGME_SIG_STAT_NONE; to be used for i18n of OpenPGP and S/MIME CryptPlugs
QString errorText;
- QDateTime creationTime;
QString decryptionError;
QString auditLog;
QString compliance; // textual representation of compliance status; empty if compliance isn't enforced
GpgME::Error auditLogError;
bool isEncrypted : 1;
bool isDecryptable : 1;
bool inProgress : 1;
bool technicalProblem : 1;
bool isEncapsulatedRfc822Message : 1;
bool isCompliant : 1; // corresponds to the isDeVS flag of signature or decryption result
bool keyRevoked : 1;
};
}
diff --git a/src/core/partmodel.cpp b/src/core/partmodel.cpp
index 7484330..76700d4 100644
--- a/src/core/partmodel.cpp
+++ b/src/core/partmodel.cpp
@@ -1,892 +1,667 @@
// 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 <verificationresult.h>
-std::optional<GpgME::Signature> signatureFromMessagePart(MimeTreeParser::MessagePart *messagePart)
+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;
}
-static QString formatValidSignatureWithTrustLevel(const GpgME::UserID &id)
-{
- if (id.isNull()) {
- return QString();
- }
- switch (id.validity()) {
- case GpgME::UserID::Marginal:
- return i18n("The signature is valid but the trust in the certificate's validity is only marginal.");
- case GpgME::UserID::Full:
- return i18n("The signature is valid and the certificate's validity is fully trusted.");
- case GpgME::UserID::Ultimate:
- return i18n("The signature is valid and the certificate's validity is ultimately trusted.");
- case GpgME::UserID::Never:
- return i18n("The signature is valid but the certificate's validity is <em>not trusted</em>.");
- case GpgME::UserID::Unknown:
- return i18n("The signature is valid but the certificate's validity is unknown.");
- case GpgME::UserID::Undefined:
- default:
- return i18n("The signature is valid but the certificate's validity is undefined.");
- }
-}
-
-static QString renderKeyLink(const QString &fpr, const QString &text)
-{
- return QStringLiteral("<a href=\"key:%1\">%2</a>").arg(fpr, text);
-}
-
-static QString renderKey(const GpgME::Key &key)
-{
- if (key.isNull()) {
- return i18n("Unknown certificate");
- }
-
- if (key.primaryFingerprint() && strlen(key.primaryFingerprint()) > 16 && key.numUserIDs()) {
- const QString text = QStringLiteral("%1 (%2)")
- .arg(Kleo::Formatting::prettyNameAndEMail(key).toHtmlEscaped())
- .arg(Kleo::Formatting::prettyID(QString::fromLocal8Bit(key.primaryFingerprint()).right(16).toLatin1().constData()));
- return renderKeyLink(QLatin1StringView(key.primaryFingerprint()), text);
- }
-
- return renderKeyLink(QLatin1StringView(key.primaryFingerprint()), Kleo::Formatting::prettyID(key.primaryFingerprint()));
-}
-
-static QString formatDate(const QDateTime &dt)
-{
- return QLocale().toString(dt);
-}
-
-static QString formatSigningInformation(const GpgME::Signature &sig, const GpgME::Key &key)
-{
- if (sig.isNull()) {
- return QString();
- }
- const QDateTime dt = sig.creationTime() != 0 ? QDateTime::fromSecsSinceEpoch(quint32(sig.creationTime())) : QDateTime();
- QString text;
-
- if (key.isNull()) {
- return text += i18n("With unavailable certificate:") + QStringLiteral("<br>ID: 0x%1").arg(QString::fromLatin1(sig.fingerprint()).toUpper());
- }
-
- text += i18n("With certificate: %1", renderKey(key));
-
- if (Kleo::DeVSCompliance::isCompliant()) {
- text += (QStringLiteral("<br/>")
- + (sig.isDeVs() ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
- "The signature is %1",
- Kleo::DeVSCompliance::name(true))
- : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
- "The signature <b>is not</b> %1.",
- Kleo::DeVSCompliance::name(true))));
- }
-
- return text;
-}
-
-static QString signatureSummaryToString(GpgME::Signature::Summary summary)
-{
- if (summary & GpgME::Signature::None) {
- return i18n("Error: Signature not verified");
- } else if (summary & GpgME::Signature::Valid || summary & GpgME::Signature::Green) {
- return i18n("Good signature");
- } else if (summary & GpgME::Signature::KeyRevoked) {
- return i18n("Signing certificate was revoked");
- } else if (summary & GpgME::Signature::KeyExpired) {
- return i18n("Signing certificate is expired");
- } else if (summary & GpgME::Signature::KeyMissing) {
- return i18n("Certificate is not available");
- } else if (summary & GpgME::Signature::SigExpired) {
- return i18n("Signature expired");
- } else if (summary & GpgME::Signature::CrlMissing) {
- return i18n("CRL missing");
- } else if (summary & GpgME::Signature::CrlTooOld) {
- return i18n("CRL too old");
- } else if (summary & GpgME::Signature::BadPolicy) {
- return i18n("Bad policy");
- } else if (summary & GpgME::Signature::SysError) {
- return i18n("System error"); // ### retrieve system error details?
- } else if (summary & GpgME::Signature::Red) {
- return i18n("Bad signature");
- }
- return QString();
-}
-
-static bool addrspec_equal(const KMime::Types::AddrSpec &lhs, const KMime::Types::AddrSpec &rhs, Qt::CaseSensitivity cs)
-{
- return lhs.localPart.compare(rhs.localPart, cs) == 0 && lhs.domain.compare(rhs.domain, Qt::CaseInsensitive) == 0;
-}
-
-static std::string stripAngleBrackets(const std::string &str)
-{
- if (str.empty()) {
- return str;
- }
- if (str[0] == '<' && str[str.size() - 1] == '>') {
- return str.substr(1, str.size() - 2);
- }
- return str;
-}
-
-static std::string email(const GpgME::UserID &uid)
-{
- if (uid.parent().protocol() == GpgME::OpenPGP) {
- if (const char *const email = uid.email()) {
- return stripAngleBrackets(email);
- } else {
- return std::string();
- }
- }
-
- Q_ASSERT(uid.parent().protocol() == GpgME::CMS);
-
- if (const char *const id = uid.id())
- if (*id == '<') {
- return stripAngleBrackets(id);
- } else {
- return Kleo::DN(id)[QStringLiteral("EMAIL")].trimmed().toUtf8().constData();
- }
- else {
- return std::string();
- }
-}
-
-static bool mailbox_equal(const KMime::Types::Mailbox &lhs, const KMime::Types::Mailbox &rhs, Qt::CaseSensitivity cs)
-{
- return addrspec_equal(lhs.addrSpec(), rhs.addrSpec(), cs);
-}
-
-static KMime::Types::Mailbox mailbox(const GpgME::UserID &uid)
-{
- const std::string e = email(uid);
- KMime::Types::Mailbox mbox;
- if (!e.empty()) {
- mbox.setAddress(e.c_str());
- }
- return mbox;
-}
-
-static GpgME::UserID findUserIDByMailbox(const GpgME::Key &key, const KMime::Types::Mailbox &mbox)
-{
- const auto userIDs{key.userIDs()};
- for (const GpgME::UserID &id : userIDs) {
- if (mailbox_equal(mailbox(id), mbox, Qt::CaseInsensitive)) {
- return id;
- }
- }
- return {};
-}
-
-static QString formatSignature(const GpgME::Signature &sig, const GpgME::Key &key, const KMime::Types::Mailbox &sender)
-{
- if (sig.isNull()) {
- return QString();
- }
-
- const QString text = formatSigningInformation(sig, key) + QLatin1StringView("<br/>");
-
- // Green
- if (sig.summary() & GpgME::Signature::Valid) {
- const GpgME::UserID id = findUserIDByMailbox(key, sender);
- return text + formatValidSignatureWithTrustLevel(!id.isNull() ? id : key.userID(0));
- }
-
- // Red
- if ((sig.summary() & GpgME::Signature::Red)) {
- const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary()));
- if (sig.summary() & GpgME::Signature::SysError) {
- return ret + QStringLiteral(" (%1)").arg(Kleo::Formatting::errorAsString(sig.status()));
- }
- return ret;
- }
-
- // Key missing
- if ((sig.summary() & GpgME::Signature::KeyMissing)) {
- return text + i18n("You can search the certificate on a keyserver or import it from a file.");
- }
-
- // Yellow
- if ((sig.validity() & GpgME::Signature::Validity::Undefined) //
- || (sig.validity() & GpgME::Signature::Validity::Unknown) //
- || (sig.summary() == GpgME::Signature::Summary::None)) {
- return text
- + (key.protocol() == GpgME::OpenPGP
- ? i18n("The used key is not certified by you or any trusted person.")
- : i18n("The used certificate is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown."));
- }
-
- // Catch all fall through
- const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary()));
- if (sig.summary() & GpgME::Signature::SysError) {
- return ret + QStringLiteral(" (%1)").arg(Kleo::Formatting::errorAsString(sig.status()));
- }
- return ret;
-}
-
// 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{QStringLiteral("<p>.?-+Original(\\s|\u00A0)Message-+"), QRegularExpression::CaseInsensitiveOption},
// The remainder is not quoted
QRegularExpression{QStringLiteral("<p>.?On.*wrote:"), QRegularExpression::CaseInsensitiveOption},
// The remainder is quoted
QRegularExpression{QStringLiteral("> On.*wrote:"), QRegularExpression::CaseInsensitiveOption},
// German
// Forwarded
QRegularExpression{QStringLiteral("<p>.?Von:.*</p>"), QRegularExpression::CaseInsensitiveOption},
// Reply
QRegularExpression{QStringLiteral("<p>.?Am.*schrieb.*:</p>"), QRegularExpression::CaseInsensitiveOption},
// Signature
QRegularExpression{QStringLiteral("<p>.?--(\\s|\u00A0)<br>"), 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 = QStringLiteral("<style>\n")
+ 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")
+ QStringLiteral("</style>");
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 + QStringLiteral("</body></html>");
}
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(QStringLiteral("[\n\r]{2,}")), QStringLiteral("\n\n"));
// 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] == QByteArrayLiteral("text/calendar")) {
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()] == QByteArrayLiteral("text/calendar")) {
mParts.prepend(part);
} else {
mParts.append(part);
}
}
}
PartModel *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, QByteArrayLiteral("type")},
{ContentRole, QByteArrayLiteral("content")},
{IsEmbeddedRole, QByteArrayLiteral("isEmbedded")},
{SidebarSecurityLevelRole, QByteArrayLiteral("sidebarSecurityLevel")},
{EncryptionSecurityLevelRole, QByteArrayLiteral("encryptionSecurityLevel")},
{SignatureSecurityLevelRole, QByteArrayLiteral("signatureSecurityLevel")},
{EncryptionSecurityLevelRole, QByteArrayLiteral("encryptionSecurityLevel")},
{ErrorType, QByteArrayLiteral("errorType")},
{ErrorString, QByteArrayLiteral("errorString")},
{IsErrorRole, QByteArrayLiteral("error")},
{SenderRole, QByteArrayLiteral("sender")},
{SignatureDetailsRole, QByteArrayLiteral("signatureDetails")},
{SignatureIconNameRole, QByteArrayLiteral("signatureIconName")},
{EncryptionDetails, QByteArrayLiteral("encryptionDetails")},
{EncryptionIconNameRole, QByteArrayLiteral("encryptionIconName")},
{DateRole, QByteArrayLiteral("date")},
};
}
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)
{
auto header = content->header<T>();
if (header || !content->parent()) {
return header;
}
return findHeader<T>(content->parent());
}
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 QStringLiteral("Content%1");
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] == QByteArrayLiteral("text/calendar")) {
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(QStringLiteral("<!DOCTYPE html PUBLIC"))) {
// We can probably deal with this if it adheres to the strict dtd
//(that's what our composer produces as well)
if (!text.contains(QStringLiteral("http://www.w3.org/TR/REC-html40/strict.dtd"))) {
return true;
}
}
// Blockquotes don't support any styling which would be necessary so they become readable.
if (text.contains(QStringLiteral("blockquote"))) {
return true;
}
// Media queries are too advanced
if (text.contains(QStringLiteral("@media"))) {
return true;
}
// auto css properties are not supported e.g margin-left: auto;
if (text.contains(QStringLiteral(": auto;"))) {
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
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;
}
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 QStringLiteral("data-error");
}
return messageIsEncrypted ? QStringLiteral("mail-encrypted") : QString();
}
case SignatureIconNameRole: {
auto signature = signatureFromMessagePart(messagePart);
if (!signature) {
return QString{};
}
const auto summary = signature->summary();
if (summary & GpgME::Signature::Valid) {
return QStringLiteral("mail-signed");
} else if (summary & GpgME::Signature::Red) {
return QStringLiteral("data-error");
} else {
return QStringLiteral("data-warning");
}
}
case SignatureDetailsRole: {
auto signature = signatureFromMessagePart(messagePart);
if (!signature) {
return QString{};
}
GpgME::Key key = signature->key();
if (key.isNull()) {
key = Kleo::KeyCache::instance()->findByFingerprint(signature->fingerprint());
}
if (key.isNull() && signature->fingerprint()) {
// try to find a subkey that was used for signing;
// assumes that the key ID is the last 16 characters of the fingerprint
const auto fpr = std::string_view{signature->fingerprint()};
const auto keyID = std::string{fpr, fpr.size() - 16, 16};
const auto subkeys = Kleo::KeyCache::instance()->findSubkeysByKeyID({keyID});
if (subkeys.size() > 0) {
key = subkeys[0].parent();
}
}
if (key.isNull()) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "Found no key or subkey for fingerprint" << signature->fingerprint();
}
QStringList senderUserId;
for (int i = 0, count = key.userIDs().size(); i < count; i++) {
senderUserId << QString::fromUtf8(key.userID(i).email());
}
// 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 formatSignature(*signature, key, mailboxes.front());
+ return Kleo::Formatting::prettySignature(*signature, mailboxes.front());
}
}
}
-
- // use first non empty userId as fallback
- KMime::Types::Mailbox mailBox;
- for (int i = 0, count = key.userIDs().size(); i < count; i++) {
- const auto address = QString::fromUtf8(key.userID(1).email());
- if (!address.isEmpty()) {
- mailBox.fromUnicodeString(address);
- break;
- }
- }
-
- return formatSignature(*signature, key, mailBox);
+ return Kleo::Formatting::prettySignature(*signature, {});
}
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, Jul 17, 1:42 AM (3 h, 8 s)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
a4/e7/39c82b26ab441ebb13c73f6fa4f2
Attached To
rMTP MIME Tree Parser
Event Timeline
Log In to Comment