diff --git a/framework/src/crypto.cpp b/framework/src/crypto.cpp index a87934fd..1fce199d 100644 --- a/framework/src/crypto.cpp +++ b/framework/src/crypto.cpp @@ -1,457 +1,462 @@ /* Copyright (c) 2009 Constantin Berzan Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi Copyright (c) 2017 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "crypto.h" #include "framework/src/errors.h" #include #include #include #include #include using namespace Crypto; QDebug operator<< (QDebug d, const Key &key) { d << key.fingerprint; return d; } QDebug operator<< (QDebug d, const Error &error) { d << error.errorCode() << gpgme_strerror(error.errorCode()); return d; } namespace Crypto { struct Data { Data(const QByteArray &buffer) { const bool copy = false; const gpgme_error_t e = gpgme_data_new_from_mem(&data, buffer.constData(), buffer.size(), int(copy)); if (e) { qWarning() << "Failed to copy data?" << e; } } ~Data() { gpgme_data_release(data); } gpgme_data_t data; }; } static gpgme_error_t checkEngine(CryptoProtocol protocol) { gpgme_check_version(0); const gpgme_protocol_t p = protocol == CMS ? GPGME_PROTOCOL_CMS : GPGME_PROTOCOL_OpenPGP; return gpgme_engine_check_version(p); } static std::pair createForProtocol(CryptoProtocol proto) { if (auto e = checkEngine(proto)) { qWarning() << "GPG Engine check failed." << e; return std::make_pair(e, nullptr); } gpgme_ctx_t ctx = 0; if (auto e = gpgme_new(&ctx)) { return std::make_pair(e, nullptr); } switch (proto) { case OpenPGP: if (auto e = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP)) { gpgme_release(ctx); return std::make_pair(e, nullptr); } break; case CMS: if (auto e = gpgme_set_protocol(ctx, GPGME_PROTOCOL_CMS)) { gpgme_release(ctx); return std::make_pair(e, nullptr); } break; default: Q_ASSERT(false); return std::make_pair(1, nullptr); } //We want the output to always be ASCII armored gpgme_set_armor(ctx, 1); return std::make_pair(GPG_ERR_NO_ERROR, ctx); } namespace Crypto { struct Context { Context(CryptoProtocol protocol = OpenPGP) { gpgme_error_t code; std::tie(code, context) = createForProtocol(protocol); error = Error{code}; } ~Context() { gpgme_release(context); } operator bool() const { return !error; } Error error; gpgme_ctx_t context; }; } static QByteArray toBA(gpgme_data_t out) { size_t length = 0; auto data = gpgme_data_release_and_get_mem (out, &length); auto outdata = QByteArray{data, static_cast(length)}; gpgme_free(data); return outdata; } static std::vector copyRecipients(gpgme_decrypt_result_t result) { std::vector recipients; for (gpgme_recipient_t r = result->recipients ; r ; r = r->next) { recipients.push_back({QByteArray{r->keyid}, {r->status}}); } return recipients; } static std::vector copySignatures(gpgme_verify_result_t result) { std::vector signatures; for (gpgme_signature_t is = result->signatures ; is ; is = is->next) { Signature sig; sig.fingerprint = QByteArray{is->fpr}; sig.creationTime.setTime_t(is->timestamp); sig.summary = is->summary; sig.status = {is->status}; sig.validity = is->validity; sig.validity_reason = is->validity_reason; signatures.push_back(sig); } return signatures; } VerificationResult Crypto::verifyDetachedSignature(CryptoProtocol protocol, const QByteArray &signature, const QByteArray &text) { Context context{protocol}; if (!context) { qWarning() << "Failed to create context " << context.error; return {{}, context.error}; } auto ctx = context.context; auto err = gpgme_op_verify(ctx, Data{signature}.data, Data{text}.data, 0); gpgme_verify_result_t res = gpgme_op_verify_result(ctx); return {copySignatures(res), {err}}; } VerificationResult Crypto::verifyOpaqueSignature(CryptoProtocol protocol, const QByteArray &signature, QByteArray &outdata) { Context context{protocol}; if (!context) { qWarning() << "Failed to create context " << context.error; return VerificationResult{{}, context.error}; } auto ctx = context.context; gpgme_data_t out; const gpgme_error_t e = gpgme_data_new(&out); Q_ASSERT(!e); auto err = gpgme_op_verify(ctx, Data{signature}.data, 0, out); VerificationResult result{{}, {err}}; if (gpgme_verify_result_t res = gpgme_op_verify_result(ctx)) { result.signatures = copySignatures(res); } outdata = toBA(out); return result; } std::pair Crypto::decryptAndVerify(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata) { Context context{protocol}; if (!context) { qWarning() << "Failed to create context " << context.error; return std::make_pair(DecryptionResult{{}, context.error}, VerificationResult{{}, context.error}); } auto ctx = context.context; gpgme_data_t out; if (gpgme_error_t e = gpgme_data_new(&out)) { qWarning() << "Failed to allocated data" << e; } auto err = gpgme_op_decrypt_verify(ctx, Data{ciphertext}.data, out); if (err) { qWarning() << "Failed to decrypt and verify" << Error{err}; //We make sure we don't return any plain-text if the decryption failed to prevent EFAIL if (err == GPG_ERR_DECRYPT_FAILED) { return std::make_pair(DecryptionResult{{}, {err}}, VerificationResult{{}, {err}}); } } VerificationResult verificationResult{{}, {err}}; if (gpgme_verify_result_t res = gpgme_op_verify_result(ctx)) { verificationResult.signatures = copySignatures(res); } DecryptionResult decryptionResult{{}, {err}}; if (gpgme_decrypt_result_t res = gpgme_op_decrypt_result(ctx)) { decryptionResult.recipients = copyRecipients(res); } outdata = toBA(out); return std::make_pair(decryptionResult, verificationResult); } -ImportResult Crypto::importKeys(CryptoProtocol protocol, const QByteArray &certData) +ImportResult Crypto::importKey(CryptoProtocol protocol, const QByteArray &certData) { Context context{protocol}; if (!context) { qWarning() << "Failed to create context " << context.error; return {0, 0, 0}; } if (gpgme_op_import(context.context, Data{certData}.data)) { qWarning() << "Import failed"; return {0, 0, 0}; } if (auto result = gpgme_op_import_result(context.context)) { return {result->considered, result->imported, result->unchanged}; } else { return {0, 0, 0}; } } -static KeyListResult listKeys(CryptoProtocol protocol, const std::vector &patterns, bool secretOnly, int keyListMode) +static KeyListResult listKeys(CryptoProtocol protocol, const std::vector &patterns, bool secretOnly, int keyListMode, bool importKeys) { Context context{protocol}; if (!context) { qWarning() << "Failed to create context " << context.error; return {{}, context.error}; } auto ctx = context.context; gpgme_set_keylist_mode(ctx, keyListMode); KeyListResult result; result.error = {GPG_ERR_NO_ERROR}; auto zeroTerminatedPatterns = patterns; zeroTerminatedPatterns.push_back(0); if (patterns.size() > 1) { if (auto err = gpgme_op_keylist_ext_start(ctx, const_cast(zeroTerminatedPatterns.data()), int(secretOnly), 0)) { result.error = {err}; qWarning() << "Error while listing keys:" << result.error; } } else if (patterns.size() == 1) { - if (auto err = gpgme_op_keylist_start(ctx, zeroTerminatedPatterns.data()[0], int(secretOnly))) { + if (auto err = gpgme_op_keylist_start(ctx, zeroTerminatedPatterns[0], int(secretOnly))) { result.error = {err}; qWarning() << "Error while listing keys:" << result.error; } } else { - qWarning() << "Listing all"; if (auto err = gpgme_op_keylist_start(ctx, 0, int(secretOnly))) { result.error = {err}; qWarning() << "Error while listing keys:" << result.error; } } + std::vector listedKeys; while (true) { gpgme_key_t key; if (auto err = gpgme_op_keylist_next(ctx, &key)) { Error error{err}; if (error.errorCode() != GPG_ERR_EOF) { result.error = error; qWarning() << "Error after listing keys" << result.error; } break; } + + listedKeys.push_back(key); + Key k; if (key->subkeys) { k.keyId = QByteArray{key->subkeys->keyid}; k.shortKeyId = k.keyId.right(8); k.fingerprint = QByteArray{key->subkeys->fpr}; } for (gpgme_user_id_t uid = key->uids ; uid ; uid = uid->next) { k.userIds.push_back(UserId{QByteArray{uid->name}, QByteArray{uid->email}, QByteArray{uid->uid}}); } k.isExpired = key->expired; result.keys.push_back(k); } gpgme_op_keylist_end(ctx); + + if (importKeys && !listedKeys.empty()) { + listedKeys.push_back(0); + if (auto err = gpgme_op_import_keys(ctx, const_cast(listedKeys.data()))) { + qWarning() << "Error while importing keys" << Error{err}; + } + } return result; } /** * Get the given `key` in the armor format. */ Expected Crypto::exportPublicKey(const Key &key) { Context context; if (!context) { return makeUnexpected(Error{context.error}); } gpgme_data_t out; const gpgme_error_t e = gpgme_data_new(&out); Q_ASSERT(!e); qDebug() << "Exporting public key:" << key.keyId; if (auto err = gpgme_op_export(context.context, key.keyId, 0, out)) { return makeUnexpected(Error{err}); } return toBA(out); } Expected Crypto::signAndEncrypt(const QByteArray &content, const std::vector &encryptionKeys, const std::vector &signingKeys) { Context context; if (!context) { return makeUnexpected(Error{context.error}); } qWarning() << "Encrypting to " << encryptionKeys.size(); for (const auto &signingKey : signingKeys) { //TODO do we have to free those again? gpgme_key_t key; if (auto e = gpgme_get_key(context.context, signingKey.fingerprint, &key, /*secret*/ false)) { qWarning() << "Failed to retrive signing key " << signingKey.fingerprint << e; } else { gpgme_signers_add(context.context, key); } } gpgme_key_t * const keys = new gpgme_key_t[encryptionKeys.size() + 1]; gpgme_key_t * keys_it = keys; for (const auto &k : encryptionKeys) { gpgme_key_t key; if (auto e = gpgme_get_key(context.context, k.fingerprint, &key, /*secret*/ false)) { qWarning() << "Failed to retrive key " << k.fingerprint << e; } else { *keys_it++ = key; } } *keys_it++ = 0; gpgme_data_t out; const gpgme_error_t e = gpgme_data_new(&out); Q_ASSERT(!e); gpgme_error_t err = !signingKeys.empty() ? gpgme_op_encrypt_sign(context.context, keys, GPGME_ENCRYPT_ALWAYS_TRUST, Data{content}.data, out) : gpgme_op_encrypt(context.context, keys, GPGME_ENCRYPT_ALWAYS_TRUST, Data{content}.data, out); delete[] keys; if (err) { qWarning() << "Encryption failed:" << Error{err}; return makeUnexpected(Error{err}); } return toBA(out); } Expected> Crypto::sign(const QByteArray &content, const std::vector &signingKeys) { Context context; if (!context) { return makeUnexpected(Error{context.error}); } for (const auto &signingKey : signingKeys) { //TODO do we have to free those again? gpgme_key_t key; if (auto e = gpgme_get_key(context.context, signingKey.fingerprint, &key, /*secret*/ false)) { qWarning() << "Failed to retrive signing key " << signingKey.fingerprint << e; } else { gpgme_signers_add(context.context, key); } } gpgme_data_t out; const gpgme_error_t e = gpgme_data_new(&out); Q_ASSERT(!e); if (auto err = gpgme_op_sign(context.context, Data{content}.data, out, GPGME_SIG_MODE_DETACH)) { qWarning() << "Signing failed:" << Error{err}; return makeUnexpected(Error{err}); } const QByteArray algo = [&] { if (gpgme_sign_result_t res = gpgme_op_sign_result(context.context)) { for (gpgme_new_signature_t is = res->signatures ; is ; is = is->next) { return QByteArray{gpgme_hash_algo_name(is->hash_algo)}; } } return QByteArray{}; }(); // RFC 3156 Section 5: // Hash-symbols are constructed [...] by converting the text name to lower // case and prefixing it with the four characters "pgp-". const auto micAlg = (QString("pgp-") + algo).toLower(); return std::pair{toBA(out), micAlg}; } -ImportResult Crypto::importKey(const QByteArray &pkey) -{ - return importKeys(OpenPGP, pkey); -} - std::vector Crypto::findKeys(const QStringList &patterns, bool findPrivate, bool remote) { QByteArrayList list; std::transform(patterns.constBegin(), patterns.constEnd(), std::back_inserter(list), [] (const QString &s) { return s.toUtf8(); }); std::vector pattern; std::transform(list.constBegin(), list.constEnd(), std::back_inserter(pattern), [] (const QByteArray &s) { return s.constData(); }); - const KeyListResult res = listKeys(OpenPGP, pattern, findPrivate, remote ? GPGME_KEYLIST_MODE_EXTERN : GPGME_KEYLIST_MODE_LOCAL); + const KeyListResult res = listKeys(OpenPGP, pattern, findPrivate, remote ? GPGME_KEYLIST_MODE_EXTERN : GPGME_KEYLIST_MODE_LOCAL, remote); if (res.error) { qWarning() << "Failed to lookup keys: " << res.error; return {}; } qDebug() << "got keys:" << res.keys.size(); for (const auto &key : res.keys) { qDebug() << "isexpired:" << key.isExpired; for (const auto &userId : key.userIds) { qDebug() << "userID:" << userId.email; } } return res.keys; } diff --git a/framework/src/crypto.h b/framework/src/crypto.h index 45340522..eacc7e52 100644 --- a/framework/src/crypto.h +++ b/framework/src/crypto.h @@ -1,125 +1,125 @@ /* Copyright (c) 2016 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #pragma once #include "errors.h" #include #include #include #include #include #include namespace Crypto { enum CryptoProtocol { UnknownProtocol, OpenPGP, CMS }; #ifndef KUBE_EXPORT #define KUBE_EXPORT #endif struct KUBE_EXPORT UserId { QByteArray name; QByteArray email; QByteArray id; }; struct KUBE_EXPORT Key { QByteArray keyId; QByteArray shortKeyId; QByteArray fingerprint; bool isExpired = false; std::vector userIds; }; struct KUBE_EXPORT Error { gpgme_error_t error; gpgme_err_code_t errorCode() const { return gpgme_err_code(error); } operator bool() const { return error != GPG_ERR_NO_ERROR; } }; struct KUBE_EXPORT Signature { QByteArray fingerprint; gpgme_sigsum_t summary; Error status; gpgme_validity_t validity; gpgme_error_t validity_reason; QDateTime creationTime; }; struct KUBE_EXPORT VerificationResult { std::vector signatures; Error error; }; struct KUBE_EXPORT Recipient { QByteArray keyId; Error status; }; struct KUBE_EXPORT DecryptionResult { std::vector recipients; Error error; }; struct KUBE_EXPORT KeyListResult { std::vector keys; Error error; }; std::vector KUBE_EXPORT findKeys(const QStringList &filter, bool findPrivate = false, bool remote = false); Expected KUBE_EXPORT exportPublicKey(const Key &key); struct KUBE_EXPORT ImportResult { int considered; int imported; int unchanged; }; -ImportResult KUBE_EXPORT importKeys(CryptoProtocol protocol, const QByteArray &certData); -ImportResult KUBE_EXPORT importKey(const QByteArray &key); +ImportResult KUBE_EXPORT importKey(CryptoProtocol protocol, const QByteArray &certData); +ImportResult KUBE_EXPORT importKey(CryptoProtocol protocol, const Key &key); /** * Sign the given content and returns the signing data and the algorithm used * for integrity check in the "pgp-" format. */ Expected> KUBE_EXPORT sign(const QByteArray &content, const std::vector &signingKeys); Expected KUBE_EXPORT signAndEncrypt(const QByteArray &content, const std::vector &encryptionKeys, const std::vector &signingKeys); std::pair KUBE_EXPORT decryptAndVerify(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata); VerificationResult KUBE_EXPORT verifyDetachedSignature(CryptoProtocol protocol, const QByteArray &signature, const QByteArray &outdata); VerificationResult KUBE_EXPORT verifyOpaqueSignature(CryptoProtocol protocol, const QByteArray &signature, QByteArray &outdata); }; Q_DECLARE_METATYPE(Crypto::Key); QDebug KUBE_EXPORT operator<< (QDebug d, const Crypto::Key &); QDebug KUBE_EXPORT operator<< (QDebug d, const Crypto::Error &); diff --git a/framework/src/domain/mime/attachmentmodel.cpp b/framework/src/domain/mime/attachmentmodel.cpp index 12f7ca74..9d74ed96 100644 --- a/framework/src/domain/mime/attachmentmodel.cpp +++ b/framework/src/domain/mime/attachmentmodel.cpp @@ -1,262 +1,261 @@ /* Copyright (c) 2016 Sandro Knauß Copyright (c) 2017 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "attachmentmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include QString sizeHuman(float size) { QStringList list; list << "KB" << "MB" << "GB" << "TB"; QStringListIterator i(list); QString unit("Bytes"); while(size >= 1024.0 && i.hasNext()) { unit = i.next(); size /= 1024.0; } if (unit == "Bytes") { return QString().setNum(size) + " " + unit; } else { return QString().setNum(size,'f',2)+" "+unit; } } class AttachmentModelPrivate { public: AttachmentModelPrivate(AttachmentModel *q_ptr, const std::shared_ptr &parser); AttachmentModel *q; std::shared_ptr mParser; QVector mAttachments; }; AttachmentModelPrivate::AttachmentModelPrivate(AttachmentModel* q_ptr, const std::shared_ptr& parser) : q(q_ptr) , mParser(parser) { mAttachments = mParser->collectAttachmentParts(); } AttachmentModel::AttachmentModel(std::shared_ptr parser) : d(std::unique_ptr(new AttachmentModelPrivate(this, parser))) { } AttachmentModel::~AttachmentModel() { } QHash AttachmentModel::roleNames() const { QHash roles; roles[TypeRole] = "type"; roles[NameRole] = "name"; roles[SizeRole] = "size"; roles[IconRole] = "iconName"; roles[IsEncryptedRole] = "encrypted"; roles[IsSignedRole] = "signed"; return roles; } QModelIndex AttachmentModel::index(int row, int column, const QModelIndex &) const { if (row < 0 || column != 0) { return QModelIndex(); } if (row < d->mAttachments.size()) { return createIndex(row, column, d->mAttachments.at(row).data()); } return QModelIndex(); } QVariant AttachmentModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { switch (role) { case Qt::DisplayRole: return QString("root"); } return QVariant(); } if (index.internalPointer()) { const auto part = static_cast(index.internalPointer()); Q_ASSERT(part); auto node = part->node(); if (!node) { qWarning() << "no content for attachment"; return {}; } QMimeDatabase mimeDb; const auto mimetype = mimeDb.mimeTypeForName(QString::fromLatin1(part->mimeType())); const auto content = node->encodedContent(); switch(role) { case TypeRole: return mimetype.name(); case NameRole: return part->filename(); case IconRole: return mimetype.iconName(); case SizeRole: return sizeHuman(content.size()); case IsEncryptedRole: return part->encryptions().size() > 0; case IsSignedRole: return part->signatures().size() > 0; } } return QVariant(); } static QString saveAttachmentToDisk(const QModelIndex &index, const QString &path, bool readonly = false) { if (index.internalPointer()) { const auto part = static_cast(index.internalPointer()); Q_ASSERT(part); auto node = part->node(); auto data = node->decodedContent(); //This is necessary to store messages embedded messages (EncapsulatedRfc822MessagePart) if (data.isEmpty()) { data = node->encodedContent(); } if (part->isText()) { // convert CRLF to LF before writing text attachments to disk data = KMime::CRLFtoLF(data); } auto fname = path + part->filename(); //A file with that name already exists, we assume it's the right file if (QFileInfo{fname}.exists()) { return fname; } QFile f(fname); if (!f.open(QIODevice::ReadWrite)) { qWarning() << "Failed to write attachment to file:" << fname << " Error: " << f.errorString(); Kube::Fabric::Fabric{}.postMessage("notification", {{"message", QObject::tr("Failed to save attachment.")}}); return {}; } f.write(data); if (readonly) { // make file read-only so that nobody gets the impression that he migh edit attached files f.setPermissions(QFileDevice::ReadUser); } f.close(); qInfo() << "Wrote attachment to file: " << fname; return fname; } return {}; } bool AttachmentModel::saveAttachmentToDisk(const QModelIndex &index) { auto downloadDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); if (downloadDir.isEmpty()) { downloadDir = "~"; } downloadDir += "/kube/"; QDir{}.mkpath(downloadDir); auto path = ::saveAttachmentToDisk(index, downloadDir); if (path.isEmpty()) { return false; } Kube::Fabric::Fabric{}.postMessage("notification", {{"message", tr("Saved the attachment to disk: ") + path}}); return true; } bool AttachmentModel::openAttachment(const QModelIndex &index) { auto downloadDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation)+ "/kube/"; QDir{}.mkpath(downloadDir); const auto filePath = ::saveAttachmentToDisk(index, downloadDir, true); if (!filePath.isEmpty()) { if (!QDesktopServices::openUrl(QUrl("file://" + filePath))) { Kube::Fabric::Fabric{}.postMessage("notification", {{"message", tr("Failed to open attachment.")}}); return false; } return true; } Kube::Fabric::Fabric{}.postMessage("notification", {{"message", tr("Failed to save attachment for opening.")}}); return false; } bool AttachmentModel::importPublicKey(const QModelIndex &index) { Q_ASSERT(index.internalPointer()); const auto part = static_cast(index.internalPointer()); Q_ASSERT(part); - auto pkey = part->node()->decodedContent(); - auto result = Crypto::importKey(pkey); + auto result = Crypto::importKey(Crypto::OpenPGP, part->node()->decodedContent()); bool success = true; QString message; if(result.considered == 0) { message = tr("No keys were found in this attachment"); success = false; } else { message = tr("%n Key(s) imported", "", result.imported); if(result.unchanged != 0) { message += "\n" + tr("%n Key(s) were already imported", "", result.unchanged); } } Kube::Fabric::Fabric{}.postMessage("notification", {{"message", message}}); return success; } QModelIndex AttachmentModel::parent(const QModelIndex &) const { return QModelIndex(); } int AttachmentModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return d->mAttachments.size(); } return 0; } int AttachmentModel::columnCount(const QModelIndex &) const { return 1; } diff --git a/framework/src/domain/mime/mimetreeparser/messagepart.cpp b/framework/src/domain/mime/mimetreeparser/messagepart.cpp index 256b7588..3f4cdd19 100644 --- a/framework/src/domain/mime/mimetreeparser/messagepart.cpp +++ b/framework/src/domain/mime/mimetreeparser/messagepart.cpp @@ -1,1091 +1,1090 @@ /* Copyright (c) 2015 Sandro Knauß Copyright (c) 2017 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagepart.h" #include "mimetreeparser_debug.h" #include "cryptohelper.h" #include "objecttreeparser.h" #include "utils.h" #include #include #include using namespace MimeTreeParser; using namespace Crypto; //------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 : mNodesToDelete) { delete n; } } /* QByteArray MailMime::cid() const { if (!d->mNode || !d->mNode->contentID()) { return QByteArray(); } return d->mNode->contentID()->identifier(); } */ /* bool MailMime::isFirstTextPart() const { if (!d->mNode || !d->mNode->topLevel()) { return false; } return (d->mNode->topLevel()->textContent() == d->mNode); } bool MailMime::isFirstPart() const { if (!d->mNode || !d->mNode->parent()) { return false; } return (d->mNode->parent()->contents().first() == d->mNode); } bool MailMime::isTopLevelPart() const { if (!d->mNode) { return false; } return (d->mNode->topLevel() == d->mNode); } */ 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 QString(); } const auto cd = mNode->contentDisposition(false); if (!cd) { return QString(); } return cd->filename(); } static KMime::Headers::ContentType *contentType(KMime::Content *node) { if (node) { return node->contentType(false); } return nullptr; } QByteArray MessagePart::charset() const { if (auto ct = contentType(mNode)) { return ct->charset(); } return mNode->defaultCharset(); } 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(); foreach (const auto &part, 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(); bindLifetime(tempNode); if (!tempNode->head().isEmpty()) { tempNode->contentDescription()->from7BitString("temporary node"); } parseInternal(tempNode); } QString MessagePart::renderInternalText() const { QString text; foreach (const auto &mp, subParts()) { text += mp->text(); } return text; } void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart) { messagePart->setParentPart(this); mBlocks.append(messagePart); } const QVector &MessagePart::subParts() const { return mBlocks; } bool MessagePart::hasSubParts() const { return !mBlocks.isEmpty(); } QVector MessagePart::signatures() const { QVector list; if (auto sig = dynamic_cast(const_cast(this))) { list << sig; } auto parent = parentPart(); while (parent) { if (auto sig = dynamic_cast(parent)) { list << sig; } parent = parent->parentPart(); } return list; } QVector MessagePart::encryptions() const { QVector list; if (auto sig = dynamic_cast(const_cast(this))) { list << sig; } auto parent = parentPart(); while (parent) { if (auto sig = dynamic_cast(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; } //-----MessagePartList---------------------- MessagePartList::MessagePartList(ObjectTreeParser *otp, KMime::Content *node) : MessagePart(otp, QString(), node) { } MessagePartList::~MessagePartList() { } 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_LOG) << "not a valid node"; return; } parseContent(); } TextMessagePart::~TextMessagePart() { } void TextMessagePart::parseContent() { const auto aCodec = mOtp->codecFor(mNode); const QString &fromAddress = mOtp->nodeHelper()->fromAsString(mNode); mSignatureState = KMMsgNotSigned; mEncryptionState = KMMsgNotEncrypted; auto body = mNode->decodedContent(); const auto blocks = prepareMessageForDecryption(body); const auto cryptProto = OpenPGP; if (!blocks.isEmpty()) { /* 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->toUnicode(block.text())))); } else if (block.type() == PgpMessageBlock) { KMime::Content *content = new KMime::Content; content->setBody(block.text()); content->parse(); EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr, content)); mp->bindLifetime(content); mp->setIsEncrypted(true); appendSubPart(mp); } else if (block.type() == ClearsignedBlock) { KMime::Content *content = new KMime::Content; content->setBody(block.text()); content->parse(); SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr, content)); mp->bindLifetime(content); mp->setIsSigned(true); appendSubPart(mp); } else { continue; } const auto mp = subParts().last().staticCast(); const PartMetaData *messagePart(mp->partMetaData()); if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) { mp->setText(aCodec->toUnicode(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) { } AttachmentMessagePart::~AttachmentMessagePart() { } //-----HtmlMessageBlock---------------------- HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node) : MessagePart(otp, QString(), node) { if (!mNode) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } const QByteArray partBody(mNode->decodedContent()); mBodyHTML = mOtp->codecFor(mNode)->toUnicode(partBody); } HtmlMessagePart::~HtmlMessagePart() { } QString HtmlMessagePart::text() const { return mBodyHTML; } bool HtmlMessagePart::isHtml() const { return true; } //-----MimeMessageBlock---------------------- MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart) : MessagePart(otp, QString(), node) { if (!mNode) { qCWarning(MIMETREEPARSER_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 = findTypeInDirectChilds(mNode, "text/calendar")) { mChildParts[Util::MultipartIcal] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataIcal, true)); } if (auto dataText = findTypeInDirectChilds(mNode, "text/plain")) { mChildParts[Util::MultipartPlain] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataText, true)); } if (auto dataHtml = findTypeInDirectChilds(mNode, "text/html")) { mChildParts[Util::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. if (auto data = findTypeInDirectChilds(mNode, "multipart/related")) { const auto parts = data->contents(); for (int i = 0; i < parts.size(); i++) { const auto p = parts.at(i); if (i == 0 ) { mChildParts[Util::MultipartHtml] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, p, true)); } else if (KMime::isAttachment(p)) { appendSubPart(MimeMessagePart::Ptr(new MimeMessagePart(otp, p, true))); } } //Same for multipart/mixed } else if (auto data = findTypeInDirectChilds(mNode, "multipart/mixed")) { //FIXME: This doesn't populate mChildParts but instead attaches subparts. That way we at least process all parts as we should. parseInternal(data); } } } AlternativeMessagePart::~AlternativeMessagePart() { } QList AlternativeMessagePart::availableModes() { return mChildParts.keys(); } QString AlternativeMessagePart::text() const { if (mChildParts.contains(Util::MultipartPlain)) { return mChildParts[Util::MultipartPlain]->text(); } return QString(); } bool AlternativeMessagePart::isHtml() const { return mChildParts.contains(Util::MultipartHtml); } QString AlternativeMessagePart::plaintextContent() const { return text(); } QString AlternativeMessagePart::htmlContent() const { if (mChildParts.contains(Util::MultipartHtml)) { return mChildParts[Util::MultipartHtml]->text(); } else { return plaintextContent(); } } //-----CertMessageBlock---------------------- CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, const CryptoProtocol cryptoProto) : MessagePart(otp, QString(), node) , mProtocol(cryptoProto) { if (!mNode) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } } CertMessagePart::~CertMessagePart() { } void CertMessagePart::import() { - const QByteArray certData = mNode->decodedContent(); - importKeys(mProtocol, certData); + importKey(mProtocol, mNode->decodedContent()); } QString CertMessagePart::text() const { return QString(); } //-----SignedMessageBlock--------------------- SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp, const QString &text, const CryptoProtocol cryptoProto, const QString &fromAddress, KMime::Content *node, KMime::Content *signedData) : MessagePart(otp, text, node) , mProtocol(cryptoProto) , mFromAddress(fromAddress) , mSignedData(signedData) { mMetaData.isSigned = true; mMetaData.isGoodSignature = false; mMetaData.status = tr("Wrong Crypto Plug-In."); } SignedMessagePart::~SignedMessagePart() { } void SignedMessagePart::setIsSigned(bool isSigned) { mMetaData.isSigned = isSigned; } bool SignedMessagePart::isSigned() const { return mMetaData.isSigned; } 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); } void SignedMessagePart::sigStatusToMetaData(const Signature &signature) { mMetaData.isGoodSignature = signature.status.errorCode() == GPG_ERR_NO_ERROR; if (!mMetaData.isGoodSignature) { if (signature.status.errorCode() == GPG_ERR_NO_PUBKEY) { qWarning() << "No public key to verify signature."; } else { qWarning() << "Is no good signature" << signature.status; } } // save extended signature status flags auto summary = signature.summary; mMetaData.keyMissing = summary & GPGME_SIGSUM_KEY_MISSING; mMetaData.keyExpired = summary & GPGME_SIGSUM_KEY_EXPIRED; mMetaData.keyRevoked = summary & GPGME_SIGSUM_KEY_REVOKED; mMetaData.sigExpired = summary & GPGME_SIGSUM_SIG_EXPIRED; mMetaData.crlMissing = summary & GPGME_SIGSUM_CRL_MISSING; mMetaData.crlTooOld = summary & GPGME_SIGSUM_CRL_TOO_OLD; Key key; if (mMetaData.isGoodSignature) { // Search for the key by its fingerprint so that we can check for trust etc. const auto keys = findKeys({signature.fingerprint}); if (keys.size() > 1) { // Should not happen qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint; } if (keys.empty()) { // Should not happen at this point qCWarning(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint; } else { key = keys[0]; } } mMetaData.keyId = key.keyId; if (mMetaData.keyId.isEmpty()) { mMetaData.keyId = signature.fingerprint; } mMetaData.keyIsTrusted = signature.validity == GPGME_VALIDITY_FULL || signature.validity == GPGME_VALIDITY_ULTIMATE; if (!key.userIds.empty()) { mMetaData.signer = prettifyDN(key.userIds[0].id); } for (const auto &userId : key.userIds) { QString email = QString::fromUtf8(userId.email); // ### work around gpgme 0.3.QString text() const Q_DECL_OVERRIDE;x / cryptplug bug where the // ### email addresses are specified as angle-addr, not addr-spec: if (email.startsWith(QLatin1Char('<')) && email.endsWith(QLatin1Char('>'))) { email = email.mid(1, email.length() - 2); } if (!email.isEmpty()) { mMetaData.signerMailAddresses.append(email); } } mMetaData.creationTime = signature.creationTime; if (mMetaData.signer.isEmpty()) { if (!key.userIds.empty()) { mMetaData.signer = prettifyDN(key.userIds[0].name); } if (!mMetaData.signerMailAddresses.empty()) { if (mMetaData.signer.isEmpty()) { mMetaData.signer = mMetaData.signerMailAddresses.front(); } else { mMetaData.signer += QLatin1String(" <") + mMetaData.signerMailAddresses.front() + QLatin1Char('>'); } } } } void SignedMessagePart::startVerification() { if (mSignedData) { const QByteArray cleartext = KMime::LFtoCRLF(mSignedData->encodedContent()); //The case for pkcs7 if (mNode == mSignedData) { startVerificationDetached(cleartext, nullptr, {}); } else { if (mNode) { startVerificationDetached(cleartext, mSignedData, mNode->decodedContent()); } else { //The case for clearsigned above startVerificationDetached(cleartext, nullptr, {}); } } } } void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature) { mMetaData.isEncrypted = false; mMetaData.isDecryptable = false; if (textNode) { parseInternal(textNode); } mMetaData.isSigned = false; mMetaData.status = tr("Wrong Crypto Plug-In."); if (!signature.isEmpty()) { setVerificationResult(verifyDetachedSignature(mProtocol, signature, text), false, text); } else { QByteArray outdata; setVerificationResult(verifyOpaqueSignature(mProtocol, text, outdata), false, outdata); } if (!mMetaData.isSigned) { mMetaData.creationTime = QDateTime(); } } void SignedMessagePart::setVerificationResult(const VerificationResult &result, bool parseText, const QByteArray &plainText) { auto signatures = result.signatures; // FIXME // mMetaData.auditLogError = result.error; if (!signatures.empty()) { mMetaData.isSigned = true; sigStatusToMetaData(signatures.front()); if (mNode && parseText) { mOtp->mNodeHelper->setPartMetaData(mNode, mMetaData); } if (!plainText.isEmpty() && parseText) { parseInternal(plainText); } } } 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 CryptoProtocol cryptoProto, const QString &fromAddress, KMime::Content *node, KMime::Content *encryptedNode) : MessagePart(otp, text, node) , mProtocol(cryptoProto) , mFromAddress(fromAddress) , mEncryptedNode(encryptedNode) { mMetaData.isSigned = false; mMetaData.isGoodSignature = false; mMetaData.isEncrypted = false; mMetaData.isDecryptable = false; mMetaData.status = tr("Wrong Crypto Plug-In."); } EncryptedMessagePart::~EncryptedMessagePart() { } void EncryptedMessagePart::setIsEncrypted(bool encrypted) { mMetaData.isEncrypted = encrypted; } bool EncryptedMessagePart::isEncrypted() const { return mMetaData.isEncrypted; } bool EncryptedMessagePart::isDecryptable() const { return mMetaData.isDecryptable; } void EncryptedMessagePart::startDecryption(const QByteArray &text, const QTextCodec *aCodec) { KMime::Content *content = new KMime::Content; content->setBody(text); content->parse(); bindLifetime(content); startDecryption(content); if (mMetaData.isDecryptable) { const auto codec = aCodec ? aCodec : mOtp->codecFor(mNode); const auto decoded = codec->toUnicode(mDecryptedData); if (hasSubParts()) { auto _mp = (subParts()[0]).dynamicCast(); if (_mp) { _mp->setText(decoded); } else { setText(decoded); } } else { setText(decoded); } } } bool EncryptedMessagePart::okDecryptMIME(KMime::Content &data) { mError = NoError; mMetaData.errorText.clear(); //FIXME // mMetaData.auditLogError = GpgME::Error(); mMetaData.auditLog.clear(); const QByteArray ciphertext = data.decodedContent(); QByteArray plainText; DecryptionResult decryptResult; VerificationResult verifyResult; std::tie(decryptResult, verifyResult) = decryptAndVerify(mProtocol, ciphertext, plainText); mMetaData.isSigned = verifyResult.signatures.size() > 0; if (verifyResult.signatures.size() > 0) { //We simply attach a signed message part to indicate that this content is also signed auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, QString::fromUtf8(plainText), mProtocol, mFromAddress, nullptr, nullptr)); subPart->setVerificationResult(verifyResult, true, plainText); appendSubPart(subPart); } if (decryptResult.error && mMetaData.isSigned) { //Only a signed part mMetaData.isEncrypted = false; mDecryptedData = plainText; return true; } if (mMetaData.isEncrypted) { mMetaData.keyId = [&] { foreach (const auto &recipient, decryptResult.recipients) { if (recipient.status.errorCode() != GPG_ERR_NO_SECKEY) { return recipient.keyId; } } return QByteArray{}; }(); } if (!decryptResult.error) { mDecryptedData = plainText; setText(QString::fromUtf8(mDecryptedData.constData())); } else { const auto errorCode = decryptResult.error.errorCode(); mMetaData.isEncrypted = errorCode != GPG_ERR_NO_DATA; qWarning() << "Failed to decrypt : " << decryptResult.error; const bool noSecretKeyAvilable = mMetaData.keyId.isEmpty(); bool passphraseError = errorCode == GPG_ERR_CANCELED || errorCode == GPG_ERR_NO_SECKEY; //We only get a decryption failed error when we enter the wrong passphrase.... if (!passphraseError && !noSecretKeyAvilable) { passphraseError = true; } if(noSecretKeyAvilable) { mError = NoKeyError; mMetaData.errorText = tr("Could not decrypt the data. ") + tr("No key found for recepients."); } else if (passphraseError) { mError = PassphraseError; // mMetaData.errorText = QString::fromLocal8Bit(decryptResult.error().asString()); } else { mError = UnknownError; mMetaData.errorText = tr("Could not decrypt the data. "); // + tr("Error: %1").arg(QString::fromLocal8Bit(decryptResult.error().asString())); } return false; } return true; } void EncryptedMessagePart::startDecryption(KMime::Content *data) { if (!data) { data = mEncryptedNode; if (!data) { data = mNode; } } mMetaData.isEncrypted = true; mMetaData.isDecryptable = okDecryptMIME(*data); if (!mMetaData.isDecryptable) { setText(QString::fromUtf8(mDecryptedData.constData())); } // if (mMetaData.isEncrypted && !decryptMessage()) { // mMetaData.isDecryptable = true; // } if (mNode && !mMetaData.isSigned) { mOtp->mNodeHelper->setPartMetaData(mNode, mMetaData); parseInternal(mDecryptedData); } } 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(); if (_mp) { return _mp->text(); } else { return MessagePart::text(); } } else { 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.isSigned = false; mMetaData.isEncapsulatedRfc822Message = true; mOtp->nodeHelper()->setPartMetaData(mNode, mMetaData); if (!mMessage) { qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!"; return; } parseInternal(message.data()); } EncapsulatedRfc822MessagePart::~EncapsulatedRfc822MessagePart() { } 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) { } HeadersPart::~HeadersPart() { }