diff --git a/framework/src/domain/mime/mimetreeparser/messagepart.cpp b/framework/src/domain/mime/mimetreeparser/messagepart.cpp index 995ea811..541efd7f 100644 --- a/framework/src/domain/mime/mimetreeparser/messagepart.cpp +++ b/framework/src/domain/mime/mimetreeparser/messagepart.cpp @@ -1,1163 +1,1162 @@ /* 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 #include #include #include #include #include #include #include #include using namespace MimeTreeParser; static GpgME::Data fromBA(const QByteArray &ba) { return {ba.data(), static_cast(ba.size()), false}; } static QSharedPointer gpgContext(GpgME::Protocol protocol) { GpgME::initializeLibrary(); auto error = GpgME::checkEngine(protocol); if (error) { qWarning() << "Engine check failed: " << error.asString(); } auto ctx = QSharedPointer(GpgME::Context::createForProtocol(protocol)); Q_ASSERT(ctx); return ctx; } //------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); } } 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; const auto blocks = prepareMessageForDecryption(mNode->decodedContent()); const auto cryptProto = GpgME::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) { KMime::Content *dataIcal = findTypeInDirectChilds(mNode, "text/calendar"); KMime::Content *dataHtml = findTypeInDirectChilds(mNode, "text/html"); KMime::Content *dataText = findTypeInDirectChilds(mNode, "text/plain"); if (!dataHtml) { // 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. dataHtml = findTypeInDirectChilds(mNode, "multipart/related"); // Still not found? Stupid apple mail actually puts the attachments inside of the // multipart/alternative, which is wrong. Therefore we also have to look for multipart/mixed // here. // Do this only when prefering HTML mail, though, since otherwise the attachments are hidden // when displaying plain text. if (!dataHtml) { dataHtml = findTypeInDirectChilds(mNode, "multipart/mixed"); } } if (dataIcal) { mChildNodes[Util::MultipartIcal] = dataIcal; } if (dataText) { mChildNodes[Util::MultipartPlain] = dataText; } if (dataHtml) { mChildNodes[Util::MultipartHtml] = dataHtml; } if (mChildNodes.isEmpty()) { qCWarning(MIMETREEPARSER_LOG) << "no valid nodes"; return; } QMapIterator i(mChildNodes); while (i.hasNext()) { i.next(); mChildParts[i.key()] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, i.value(), true)); } } 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 GpgME::Protocol cryptoProto) : MessagePart(otp, QString(), node) , mCryptoProto(cryptoProto) { if (!mNode) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } } CertMessagePart::~CertMessagePart() { } void CertMessagePart::import() { const QByteArray certData = mNode->decodedContent(); auto ctx = gpgContext(mCryptoProto); const auto result = ctx->importKeys(fromBA(certData)); } QString CertMessagePart::text() const { return QString(); } //-----SignedMessageBlock--------------------- SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp, const QString &text, const GpgME::Protocol 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.keyTrust = GpgME::Signature::Unknown; mMetaData.status = tr("Wrong Crypto Plug-In."); mMetaData.status_code = GPGME_SIG_STAT_NONE; } SignedMessagePart::~SignedMessagePart() { } void SignedMessagePart::setIsSigned(bool isSigned) { mMetaData.isSigned = isSigned; } bool SignedMessagePart::isSigned() const { return mMetaData.isSigned; } static int signatureToStatus(const GpgME::Signature &sig) { switch (sig.status().code()) { case GPG_ERR_NO_ERROR: return GPGME_SIG_STAT_GOOD; case GPG_ERR_BAD_SIGNATURE: return GPGME_SIG_STAT_BAD; case GPG_ERR_NO_PUBKEY: return GPGME_SIG_STAT_NOKEY; case GPG_ERR_NO_DATA: return GPGME_SIG_STAT_NOSIG; case GPG_ERR_SIG_EXPIRED: return GPGME_SIG_STAT_GOOD_EXP; case GPG_ERR_KEY_EXPIRED: return GPGME_SIG_STAT_GOOD_EXPKEY; default: return GPGME_SIG_STAT_ERROR; } } QString prettifyDN(const char *uid) { return QGpgME::DN(uid).prettyDN(); } static GpgME::KeyListResult listKeys(GpgME::Context * ctx, const char *pattern, bool secretOnly, std::vector &keys) { if (const GpgME::Error err = ctx->startKeyListing(pattern, secretOnly)) { return GpgME::KeyListResult( 0, err ); } GpgME::Error err; do { keys.push_back( ctx->nextKey(err)); } while ( !err ); keys.pop_back(); const GpgME::KeyListResult result = ctx->endKeyListing(); ctx->cancelPendingOperation(); return result; } void SignedMessagePart::sigStatusToMetaData(const GpgME::Signature &signature) { GpgME::Key key; mMetaData.status_code = signatureToStatus(signature); mMetaData.isGoodSignature = mMetaData.status_code & GPGME_SIG_STAT_GOOD; // save extended signature status flags mMetaData.sigSummary = signature.summary(); if (mMetaData.isGoodSignature && !key.keyID()) { auto ctx = gpgContext(mProtocol); // Search for the key by its fingerprint so that we can check for trust etc. std::vector found_keys; auto res = listKeys(ctx.data(), signature.fingerprint(), false, found_keys); if (res.error()) { qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << signature.fingerprint(); } if (found_keys.size() > 1) { // Should not happen qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint(); } if (found_keys.empty()) { // Should not happen at this point qCWarning(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint(); } else { key = found_keys[0]; } } if (key.keyID()) { mMetaData.keyId = key.keyID(); } if (mMetaData.keyId.isEmpty()) { mMetaData.keyId = signature.fingerprint(); } mMetaData.keyTrust = signature.validity(); if (key.numUserIDs() > 0 && key.userID(0).id()) { mMetaData.signer = prettifyDN(key.userID(0).id()); } for (uint iMail = 0; iMail < key.numUserIDs(); ++iMail) { // The following if /should/ always result in TRUE but we // won't trust implicitely the plugin that gave us these data. if (key.userID(iMail).email()) { QString email = QString::fromUtf8(key.userID(iMail).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); } } } if (signature.creationTime()) { mMetaData.creationTime.setTime_t(signature.creationTime()); } else { mMetaData.creationTime = QDateTime(); } if (mMetaData.signer.isEmpty()) { if (key.numUserIDs() > 0 && key.userID(0).name()) { mMetaData.signer = prettifyDN(key.userID(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, false); } mMetaData.isSigned = false; mMetaData.keyTrust = GpgME::Signature::Unknown; mMetaData.status = tr("Wrong Crypto Plug-In."); mMetaData.status_code = GPGME_SIG_STAT_NONE; auto ctx = gpgContext(mProtocol); if (!signature.isEmpty()) { auto result = ctx->verifyDetachedSignature(fromBA(signature), fromBA(text)); setVerificationResult(result, false, text); } else { QGpgME::QByteArrayDataProvider out; GpgME::Data outdata(&out); auto result = ctx->verifyOpaqueSignature(fromBA(text), outdata); setVerificationResult(result, false, out.data()); } if (!mMetaData.isSigned) { mMetaData.creationTime = QDateTime(); } } void SignedMessagePart::setVerificationResult(const GpgME::VerificationResult &result, bool parseText, const QByteArray &plainText) { auto signatures = result.signatures(); 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) { auto tempNode = new KMime::Content(); tempNode->setBody(plainText); tempNode->parse(); bindLifetime(tempNode); if (!tempNode->head().isEmpty()) { tempNode->contentDescription()->from7BitString("signed data"); } parseInternal(tempNode, false); } } } 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 GpgME::Protocol 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.keyTrust = GpgME::Signature::Unknown; mMetaData.status = tr("Wrong Crypto Plug-In."); mMetaData.status_code = GPGME_SIG_STAT_NONE; } 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; - auto passphraseError = false; - auto noSecKey = false; mMetaData.errorText.clear(); mMetaData.auditLogError = GpgME::Error(); mMetaData.auditLog.clear(); const QByteArray ciphertext = data.decodedContent(); auto ctx = gpgContext(mProtocol); QGpgME::QByteArrayDataProvider out; GpgME::Data outdata(&out); const std::pair res = ctx->decryptAndVerify(fromBA(ciphertext), outdata); const QByteArray &plainText = out.data(); const GpgME::DecryptionResult &decryptResult = res.first; const GpgME::VerificationResult &verifyResult = res.second; 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); } mDecryptRecipients = decryptResult.recipients(); - auto decryptionOk = !decryptResult.error(); - - if (!decryptionOk && mMetaData.isSigned) { + if (decryptResult.error() && mMetaData.isSigned) { //Only a signed part mMetaData.isEncrypted = false; mDecryptedData = plainText; return true; } - passphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY; - mMetaData.isEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA; - mMetaData.errorText = QString::fromLocal8Bit(decryptResult.error().asString()); if (mMetaData.isEncrypted && decryptResult.numRecipients() > 0) { mMetaData.keyId = decryptResult.recipient(0).keyID(); } - if (decryptionOk) { + if (!decryptResult.error()) { mDecryptedData = plainText; setText(QString::fromUtf8(mDecryptedData.constData())); } else { - noSecKey = true; + mMetaData.isEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA; + mMetaData.errorText = QString::fromLocal8Bit(decryptResult.error().asString()); + + std::stringstream ss; + ss << decryptResult << '\n' << verifyResult; + qWarning() << "Decryption failed: " << ss.str().c_str(); + + bool passphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY; + + auto noSecKey = true; foreach (const GpgME::DecryptionResult::Recipient &recipient, decryptResult.recipients()) { noSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY); } if (!passphraseError && !noSecKey) { // GpgME do not detect passphrase error correctly passphraseError = true; } - std::stringstream ss; - ss << decryptResult << '\n' << verifyResult; - qWarning() << "Decryption failed: " << ss.str().c_str(); if(noSecKey) { mError = NoKeyError; mMetaData.errorText = tr("Could not decrypt the data. ") + tr("No key found for recepients."); } else if (passphraseError) { mError = PassphraseError; } else { mError = UnknownError; mMetaData.errorText = tr("Could not decrypt the data. ") + tr("Error: %1").arg(mMetaData.errorText); } 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); auto tempNode = new KMime::Content(); tempNode->setContent(KMime::CRLFtoLF(mDecryptedData.constData())); tempNode->parse(); bindLifetime(tempNode); if (!tempNode->head().isEmpty()) { tempNode->contentDescription()->from7BitString("encrypted data"); } parseInternal(tempNode, false); } } 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(), false); } 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 {}; } diff --git a/framework/src/domain/mime/mimetreeparser/tests/gpgerrortest.cpp b/framework/src/domain/mime/mimetreeparser/tests/gpgerrortest.cpp index 32bffeb5..7e0e0d04 100644 --- a/framework/src/domain/mime/mimetreeparser/tests/gpgerrortest.cpp +++ b/framework/src/domain/mime/mimetreeparser/tests/gpgerrortest.cpp @@ -1,224 +1,216 @@ /* Copyright (c) 2016 Sandro Knauß 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 #include #include #include #include #include #include #include #include QByteArray readMailFromFile(const QString &mailFile) { QFile file(QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile); file.open(QIODevice::ReadOnly); Q_ASSERT(file.isOpen()); return file.readAll(); } void killAgent(const QString& dir) { QProcess proc; proc.setProgram(QStringLiteral("gpg-connect-agent")); QStringList arguments; arguments << "-S " << dir + "/S.gpg-agent"; proc.start(); proc.waitForStarted(); proc.write("KILLAGENT\n"); proc.write("BYE\n"); proc.closeWriteChannel(); proc.waitForFinished(); } class GpgErrorTest : public QObject { Q_OBJECT private slots: void testGpgConfiguredCorrectly() { setEnv("GNUPGHOME", GNUPGHOME); MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("openpgp-inline-charset-encrypted.mbox")); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0]; QVERIFY(bool(part)); - qWarning() << part->metaObject()->className() << part->text() << part->partMetaData()->status; QVERIFY(part->text().startsWith("asdasd")); QCOMPARE(part->encryptions().size(), 1); auto enc = part->encryptions()[0]; QCOMPARE(enc->error(), MimeTreeParser::MessagePart::NoError); - // QCOMPARE(enc->errorType(), Encryption::NoError); - // QCOMPARE(enc->errorString(), QString()); // QCOMPARE((int) enc->recipients().size(), 2); } void testNoGPGInstalled_data() { QTest::addColumn("mailFileName"); QTest::newRow("openpgp-inline-charset-encrypted") << "openpgp-inline-charset-encrypted.mbox"; QTest::newRow("openpgp-encrypted-attachment-and-non-encrypted-attachment") << "openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox"; QTest::newRow("smime-encrypted") << "smime-encrypted.mbox"; } void testNoGPGInstalled() { QFETCH(QString, mailFileName); setEnv("PATH", "/nonexististing"); setGpgMEfname("/nonexisting/gpg", ""); MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(mailFileName)); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QVERIFY(bool(part)); QCOMPARE(part->encryptions().size(), 1); QVERIFY(part->text().isEmpty()); - // auto enc = part->encryptions()[0]; - // qDebug() << "HUHU"<< enc->errorType(); - // QCOMPARE(enc->errorType(), Encryption::UnknownError); - // QCOMPARE(enc->errorString(), QString("Crypto plug-in \"OpenPGP\" could not decrypt the data.
Error: No data")); - // QCOMPARE((int) enc->recipients().size(), 0); + auto enc = part->encryptions()[0]; + QCOMPARE(enc->error(), MimeTreeParser::MessagePart::NoKeyError); } void testGpgIncorrectGPGHOME_data() { QTest::addColumn("mailFileName"); QTest::newRow("openpgp-inline-charset-encrypted") << "openpgp-inline-charset-encrypted.mbox"; QTest::newRow("openpgp-encrypted-attachment-and-non-encrypted-attachment") << "openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox"; QTest::newRow("smime-encrypted") << "smime-encrypted.mbox"; } void testGpgIncorrectGPGHOME() { QFETCH(QString, mailFileName); setEnv("GNUPGHOME", QByteArray(GNUPGHOME) + QByteArray("noexist")); MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(mailFileName)); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QVERIFY(bool(part)); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->signatures().size(), 0); QVERIFY(part->text().isEmpty()); - // auto enc = part->encryptions()[0]; - // qDebug() << enc->errorType(); - // QCOMPARE(enc->errorType(), Encryption::KeyMissing); - // QCOMPARE(enc->errorString(), QString("Crypto plug-in \"OpenPGP\" could not decrypt the data.
Error: Decryption failed")); + auto enc = part->encryptions()[0]; + QCOMPARE(enc->error(), MimeTreeParser::MessagePart::NoKeyError); // QCOMPARE((int) enc->recipients().size(), 2); } public Q_SLOTS: void init() { mResetGpgmeEngine = false; mModifiedEnv.clear(); { QGpgME::openpgp(); // We need to intialize it, otherwise ctx will be a nullpointer const GpgME::Context *ctx = GpgME::Context::createForProtocol(GpgME::Protocol::OpenPGP); const auto engineinfo = ctx->engineInfo(); mGpgmeEngine_fname = engineinfo.fileName(); } mEnv = QProcessEnvironment::systemEnvironment(); unsetEnv("GNUPGHOME"); } void cleanup() { QCoreApplication::sendPostedEvents(); const QString &gnupghome = qgetenv("GNUPGHOME"); if (!gnupghome.isEmpty()) { killAgent(gnupghome); } resetGpgMfname(); resetEnv(); } private: - void unsetEnv(const QByteArray &name) + void unsetEnv(const QByteArray &name) { mModifiedEnv << name; unsetenv(name); } void setEnv(const QByteArray &name, const QByteArray &value) { mModifiedEnv << name; setenv(name, value , 1); } void resetEnv() { foreach(const auto &i, mModifiedEnv) { if (mEnv.contains(i)) { setenv(i, mEnv.value(i).toUtf8(), 1); } else { unsetenv(i); } } } void resetGpgMfname() { if (mResetGpgmeEngine) { gpgme_set_engine_info (GPGME_PROTOCOL_OpenPGP, mGpgmeEngine_fname, NULL); } } void setGpgMEfname(const QByteArray &fname, const QByteArray &homedir) { mResetGpgmeEngine = true; gpgme_set_engine_info (GPGME_PROTOCOL_OpenPGP, fname, homedir); } QSet mModifiedEnv; QProcessEnvironment mEnv; bool mResetGpgmeEngine; QByteArray mGpgmeEngine_fname; }; QTEST_GUILESS_MAIN(GpgErrorTest) #include "gpgerrortest.moc"