diff --git a/mimetreeparser/autotests/data/inlinepgpencrypted-error.mbox b/mimetreeparser/autotests/data/inlinepgpencrypted-error.mbox new file mode 100644 index 00000000..529b4d3b --- /dev/null +++ b/mimetreeparser/autotests/data/inlinepgpencrypted-error.mbox @@ -0,0 +1,55 @@ +From test@kolab.org Wed, 25 May 2011 23:49:40 +0100 +From: OpenPGP Test +To: test@kolab.org +Subject: inlinepgpencrypted - no seckey +Date: Wed, 25 May 2011 23:49:40 +0100 +Message-ID: <1786696.yKXrOjjflF@herrwackelpudding.localhost> +X-KMail-Transport: GMX +X-KMail-Fcc: 28 +X-KMail-Drafts: 7 +X-KMail-Templates: 9 +User-Agent: KMail/4.6 beta5 (Linux/2.6.34.7-0.7-desktop; KDE/4.6.41; x86_64; git-0269848; 2011-04-19) +MIME-Version: 1.0 +Content-Transfer-Encoding: 7Bit +Content-Type: text/plain; charset="us-ascii" + +-----BEGIN PGP MESSAGE----- +Version: GnuPG v2 + +hQIMA1U9QmLaS63yAQ/8C8o5D7wQ9LoPHrNCNelku19bwQogTIqxRJSTYzO0b0tr +Pb7Oyxkm1XabYxhg9bxFcNvvAbxcbzmnFJqkVPzCird43N5BDMtwGumiUjNNYVgy +4tD6hs+h8GsmmQ5/J5cmuUwA+Ee20ubrTMH2qkU75WcyuRAG+IFsA80eEKG5qR8y +i2WXjBiImcmjrEVtSA3L+mUHmhuWxz/46EnCelSAJMfhSG8zuTJnK6OFBSDQNkqE +NRJl0PO4DYDeJiSYeXWEB2GTvc9JXtcHm7wIwzHXHSrBlXvQWEj5B8z9GSOJwO0o +JuV29TVU4iDU8d3flfhMGZEJXUkIIwt66/0CtuJNDmIAnqc4wQO9LtXFXOI/YK7x +twidnLY04kmh1bZfQsUBhwdYqLUzr0AXqE2kRTozod4XgVBmphVt6Ytu11L1UFdb +1wKBaQG/qmhOmeMJb7eJX6I66p8LzKiqkfNlTnPQURELMbCmiRwoDCC5wnrDj8g/ +K0zvfNRFbGimbTHeQ01OncoVcRIlXK7njM6dTTqnglzwZagHn1Ph0krkdbnzLJkc +j8v1QLWuM1ttMIgW5xu4R4cjSSuLZKtZNmnlQe1e5rllZbwIFlUVH/SRNblAnRi5 +GUPDJWLZJppfFk2H1pjgze4s9oZljEKXzeOa/pfrfcZ8BVmg7UnKnMyygVH1+4aF +BA4D9uaj0SbGMOAQEACfyo8uFl+Yq9XwFbAJmeSP3/AMG2HhfCNgkGkcjE+EykTm +/jn/Emscw1QyjonX1RcOvtFHbI7VsUblCcJngytfikSgM/5U/NniPtrdqohOhjgO +WJ+TxWhO4K64WaBzq5E5Q+7S2ciZTkz3tlZ2jRI0pjTxuvxVUV5fHwlES7ZfHCUg +F4eGGFU7xz3gxC6Wt6OV2EGP7wa1qf430fa5bmLZ1QsJY7l+ApbqOoWqfDmjhN6o +qf6xEtt+xx41lakdWg05VPYzkhDv7FHb2pGWeLRZpG5Rblg3LVi94lGyXstNcFre +cudq5kM2rPB9/LL65qq54KB2BsXgBSuihvRpryHqv7PSSBw+Gx5wOWZ/DZOS2RvV +UsrpN1M8XqJYUX/AExzSajsABQkbLj3Gw1WRyed7Sokrrus9fXJy25FXQ3AjBEQZ +vl8nrsEFWFQIi8s3NWoHz6IU9jyDWzJp2Twi/PKVfe7r7aMeHGRJJWMvVQbIjPEW +C8GqjyVPZmmGw5Eo6V95kwF1ED6UZaEdEYLdgKIoXwL1epil2mEaX0AuugN1vkHr +35gyHosJC0dWtNRGoSh7nGR4uwEDs6Sf9J87b+QAGbbDgePprH6AAq0qsLxc0SNO +OWFzo8/CeA4KjsYXTCsIOov99TomqI93bP9BrhNBra4RMBxjsfZ5FL2X3cCwKBAA +jPFVgrctgkX3piwu58Zi5OpRbiXOLF6PdPaBjwyD3cFIU+TmdSLU0zGG/uCkwL3U +LSHhHEdf8D5laasulX7Bz72X2DXSKraoHu8tSa2f/gBRrEOSJV86yw6FAxLCn3Lm +NCn/cSKskO/m/J2WGhiHgFSe/4OrFpqx78tWKM+XheAgz6No9vPT9KooEyKqCwlS +lI7QHhLl9eWmT1NPRibfdL9aMzjPfxmE91vaN29NnxQJG2w7KnI7sxXvZljOvuSI +FE9NvGs2uHjRFjO0Vncjuv/fAbdvVvkTCSyLWZLUyOegJa/0KZOU48HtwwBzVxl1 +D9joee2bmQnmxuGomRwelUVbux1GKRhfCtnNuKQNXU7NP3AnNUDAQjrQSD5C1f3e +9tPOi3wRuXnlYfBcmemKUrdYNVpWBpHh+KnJ1rW/NqwNvUtq0ucYIT5//dKaPiIf +HqizKm0MntFbIv4f29TNfw5Wp1GcTXc6Dmt/KSCjLH+IxPtdAgI5ZlrdOfVxlY1B +abIFKjN0csPfkfX7l8g6ekOYgP/NRHQQs7Zyds59Zj7Roi7+uabV8svXRREm0V34 +595ro3cEzABOAnErxErC7Lm/VUI348kdOP/3IAckmwv1qts3P2eDA6CcLYE2V+sz +7mb9UGrUzu8hBxPjbuqIYfi2XOSxGRCvSH0Rmw7XzKfSRwHpusUQjpCbRXyntVqY +Db8+PufLBENx22ipLLEDltP1P9zRuy2KpANd0sggM/HtUC3Bjta7IR9Q3qbVcPDx +3Qu241eOBdb6 +=J3lb +-----END PGP MESSAGE----- diff --git a/mimetreeparser/autotests/data/inlinepgpencrypted-error.mbox.html b/mimetreeparser/autotests/data/inlinepgpencrypted-error.mbox.html new file mode 100644 index 00000000..a4427e01 --- /dev/null +++ b/mimetreeparser/autotests/data/inlinepgpencrypted-error.mbox.html @@ -0,0 +1,24 @@ + + + + +
+ +
+ + + + + + + + + + +
Encrypted message (decryption not possible)
Reason: Crypto plug-in "OpenPGP" could not decrypt the data.
Error: Decryption failed
+
No secret key found to encrypt the message. It is encrypted for following keys:
0x553D4262DA4BADF2
0xF6E6A3D126C630E0
+
End of encrypted message
+
+
+ + diff --git a/mimetreeparser/src/viewer/objecttreeparser.cpp b/mimetreeparser/src/viewer/objecttreeparser.cpp index 5a584ce2..586aff81 100644 --- a/mimetreeparser/src/viewer/objecttreeparser.cpp +++ b/mimetreeparser/src/viewer/objecttreeparser.cpp @@ -1,1754 +1,1775 @@ /* objecttreeparser.cpp This file is part of KMail, the KDE mail client. Copyright (c) 2003 Marc Mutz Copyright (C) 2002-2004 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia Copyright (c) 2015 Sandro Knauß KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KMail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ // MessageViewer includes #include "objecttreeparser.h" #include "memento/verifydetachedbodypartmemento.h" #include "memento/verifyopaquebodypartmemento.h" #include "memento/cryptobodypartmemento.h" #include "memento/decryptverifybodypartmemento.h" #include "messagepart.h" #include "objecttreesourceif.h" #include "partmetadata.h" #include "attachmentstrategy.h" #include "interfaces/htmlwriter.h" #include "csshelperbase.h" #include "bodypartformatterbasefactory.h" #include "viewer/partnodebodypart.h" #include "interfaces/bodypartformatter.h" #include "utils/mimetype.h" #include "job/kleojobexecutor.h" #include "nodehelper.h" #include "utils/iconnamecache.h" #include "mimetreeparser_debug.h" #include "converthtmltoplaintext.h" // KDEPIM includes #include #include #include #include #include #include #include #include #include // KDEPIMLIBS includes #include #include #include #include #include #include #include // KDE includes #include #include // Qt includes #include #include #include // other includes #include #include #include #include #include #include #include using namespace MimeTreeParser; using namespace MessageCore; ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser *topLevelParser, bool showOnlyOneMimePart, const AttachmentStrategy *strategy) : mSource(topLevelParser->mSource), mNodeHelper(topLevelParser->mNodeHelper), mHtmlWriter(topLevelParser->mHtmlWriter), mTopLevelContent(topLevelParser->mTopLevelContent), mCryptoProtocol(topLevelParser->mCryptoProtocol), mShowOnlyOneMimePart(showOnlyOneMimePart), mHasPendingAsyncJobs(false), mAllowAsync(topLevelParser->mAllowAsync), mAttachmentStrategy(strategy), mPrinting(false) { init(); } ObjectTreeParser::ObjectTreeParser(ObjectTreeSourceIf *source, MimeTreeParser::NodeHelper *nodeHelper, const Kleo::CryptoBackend::Protocol *protocol, bool showOnlyOneMimePart, const AttachmentStrategy *strategy) : mSource(source), mNodeHelper(nodeHelper), mHtmlWriter(0), mTopLevelContent(0), mCryptoProtocol(protocol), mShowOnlyOneMimePart(showOnlyOneMimePart), mHasPendingAsyncJobs(false), mAllowAsync(false), mAttachmentStrategy(strategy), mPrinting(false) { init(); } void ObjectTreeParser::init() { assert(mSource); if (!attachmentStrategy()) { mAttachmentStrategy = mSource->attachmentStrategy(); } if (!mNodeHelper) { mNodeHelper = new NodeHelper(); mDeleteNodeHelper = true; } else { mDeleteNodeHelper = false; } } ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser &other) : mSource(other.mSource), mNodeHelper(other.nodeHelper()), //TODO(Andras) hm, review what happens if mDeleteNodeHelper was true in the source mHtmlWriter(other.mHtmlWriter), mTopLevelContent(other.mTopLevelContent), mCryptoProtocol(other.cryptoProtocol()), mShowOnlyOneMimePart(other.showOnlyOneMimePart()), mHasPendingAsyncJobs(other.hasPendingAsyncJobs()), mAllowAsync(other.allowAsync()), mAttachmentStrategy(other.attachmentStrategy()), mDeleteNodeHelper(false), // TODO see above mPrinting(other.printing()) { } ObjectTreeParser::~ObjectTreeParser() { if (mDeleteNodeHelper) { delete mNodeHelper; mNodeHelper = 0; } } void ObjectTreeParser::setAllowAsync(bool allow) { assert(!mHasPendingAsyncJobs); mAllowAsync = allow; } bool ObjectTreeParser::allowAsync() const { return mAllowAsync; } bool ObjectTreeParser::hasPendingAsyncJobs() const { return mHasPendingAsyncJobs; } QString ObjectTreeParser::plainTextContent() const { return mPlainTextContent; } QString ObjectTreeParser::htmlContent() const { return mHtmlContent; } void ObjectTreeParser::copyContentFrom(const ObjectTreeParser *other) { mPlainTextContent += other->plainTextContent(); mHtmlContent += other->htmlContent(); if (!other->plainTextContentCharset().isEmpty()) { mPlainTextContentCharset = other->plainTextContentCharset(); } if (!other->htmlContentCharset().isEmpty()) { mHtmlContentCharset = other->htmlContentCharset(); } } bool ObjectTreeParser::printing() const { return mPrinting; } //----------------------------------------------------------------------------- void ObjectTreeParser::parseObjectTree(KMime::Content *node) { mTopLevelContent = node; const auto mp = parseObjectTreeInternal(node); if (mp) { mp->fix(); mp->copyContentFrom(); mp->html(false); } } void ObjectTreeParser::setPrinting(bool printing) { mPrinting = printing; } MessagePart::Ptr ObjectTreeParser::parseObjectTreeInternal(KMime::Content *node) { if (!node) { return MessagePart::Ptr(); } // reset pending async jobs state (we'll rediscover pending jobs as we go) mHasPendingAsyncJobs = false; // reset "processed" flags for... if (showOnlyOneMimePart()) { // ... this node and all descendants mNodeHelper->setNodeUnprocessed(node, false); if (MessageCore::NodeHelper::firstChild(node)) { mNodeHelper->setNodeUnprocessed(node, true); } } else if (!node->parent()) { // ...this node and all it's siblings and descendants mNodeHelper->setNodeUnprocessed(node, true); } const bool isRoot = node->isTopLevel(); MessagePartList::Ptr mpl(new MessagePartList(this)); mpl->setIsRoot(isRoot); for (; node; node = MessageCore::NodeHelper::nextSibling(node)) { if (mNodeHelper->nodeProcessed(node)) { continue; } ProcessResult processResult(mNodeHelper); QByteArray mediaType("text"); QByteArray subType("plain"); if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() && !node->contentType()->subType().isEmpty()) { mediaType = node->contentType()->mediaType(); subType = node->contentType()->subType(); } bool bRendered = false; const auto sub = mSource->bodyPartFormatterFactory()->subtypeRegistry(mediaType); while (true) { auto range = sub.equal_range(subType); for (auto it = range.first; it != range.second; ++it) { const auto formatter = (*it).second; if (!formatter) { continue; } PartNodeBodyPart part(this, &processResult, mTopLevelContent, node, mNodeHelper, codecFor(node)); // Set the default display strategy for this body part relying on the // identity of Interface::BodyPart::Display and AttachmentStrategy::Display part.setDefaultDisplay((Interface::BodyPart::Display) attachmentStrategy()->defaultDisplay(node)); mNodeHelper->setNodeDisplayedEmbedded(node, true); const auto result = formatter->process(part); if (!result) { continue; } if (const auto mp = dynamic_cast(result.data())) { mp->setAttachmentFlag(node); mpl->appendMessagePart(result); bRendered = true; break; } else if (dynamic_cast(result.data())) { QObject *asyncResultObserver = allowAsync() ? mSource->sourceObject() : 0; const auto r = formatter->format(&part, htmlWriter(), asyncResultObserver); if (r == Interface::BodyPartFormatter::AsIcon) { processResult.setNeverDisplayInline(true); formatter->adaptProcessResult(processResult); mNodeHelper->setNodeDisplayedEmbedded(node, false); const auto mp = defaultHandling(node, processResult); if (mp) { mp->setAttachmentFlag(node); mpl->appendMessagePart(mp); } bRendered = true; break; } continue; } else { continue; } } if (bRendered || subType == "*") { break; } subType = "*"; } if (!bRendered) { qCCritical(MIMETREEPARSER_LOG) << "THIS SHOULD NO LONGER HAPPEN:" << mediaType << '/' << subType; const auto mp = defaultHandling(node, processResult); if (mp) { mp->setAttachmentFlag(node); mpl->appendMessagePart(mp); } } mNodeHelper->setNodeProcessed(node, false); // adjust signed/encrypted flags if inline PGP was found processResult.adjustCryptoStatesOfNode(node); if (showOnlyOneMimePart()) { break; } } return mpl; } MessagePart::Ptr ObjectTreeParser::defaultHandling(KMime::Content *node, ProcessResult &result) { // ### (mmutz) default handling should go into the respective // ### bodypartformatters. if (!htmlWriter()) { qCWarning(MIMETREEPARSER_LOG) << "no htmlWriter()"; return MessagePart::Ptr(); } // always show images in multipart/related when showing in html, not with an additional icon if (result.isImage() && node->parent() && node->parent()->contentType()->subType() == "related" && mSource->htmlMail() && !showOnlyOneMimePart()) { QString fileName = mNodeHelper->writeNodeToTempFile(node); QString href = QUrl::fromLocalFile(fileName).url(); QByteArray cid = node->contentID()->identifier(); htmlWriter()->embedPart(cid, href); nodeHelper()->setNodeDisplayedEmbedded(node, true); return MessagePart::Ptr(); } MessagePart::Ptr mp; if (node->contentType()->mimeType() == QByteArray("application/octet-stream") && (node->contentType()->name().endsWith(QLatin1String("p7m")) || node->contentType()->name().endsWith(QLatin1String("p7s")) || node->contentType()->name().endsWith(QLatin1String("p7c")) ) && (mp = processApplicationPkcs7MimeSubtype(node, result))) { return mp; } const AttachmentStrategy *const as = attachmentStrategy(); if (as && as->defaultDisplay(node) == AttachmentStrategy::None && !showOnlyOneMimePart() && node->parent() /* message is not an attachment */) { mNodeHelper->setNodeDisplayedHidden(node, true); return MessagePart::Ptr(); } bool asIcon = true; if (!result.neverDisplayInline()) { if (as) { asIcon = as->defaultDisplay(node) == AttachmentStrategy::AsIcon; } } // Show it inline if showOnlyOneMimePart(), which means the user clicked the image // in the message structure viewer manually, and therefore wants to see the full image if (result.isImage() && showOnlyOneMimePart() && !result.neverDisplayInline()) { asIcon = false; } // neither image nor text -> show as icon if (!result.isImage() && !node->contentType()->isText()) { asIcon = true; } /*FIXME(Andras) port it // if the image is not complete do not try to show it inline if ( result.isImage() && !node->msgPart().isComplete() ) asIcon = true; */ if (asIcon) { if (!(as && as->defaultDisplay(node) == AttachmentStrategy::None) || showOnlyOneMimePart()) { // Write the node as icon only return TextMessagePart::Ptr(new TextMessagePart(this, node, false, false, mSource->decryptMessage(), MimeTreeParser::IconExternal)); } else { mNodeHelper->setNodeDisplayedHidden(node, true); } } else if (result.isImage()) { // Embed the image mNodeHelper->setNodeDisplayedEmbedded(node, true); return TextMessagePart::Ptr(new TextMessagePart(this, node, false, false, mSource->decryptMessage(), MimeTreeParser::IconInline)); } else { mNodeHelper->setNodeDisplayedEmbedded(node, true); const auto mp = TextMessagePart::Ptr(new TextMessagePart(this, node, false, false, mSource->decryptMessage(), MimeTreeParser::NoIcon)); result.setInlineSignatureState(mp->signatureState()); result.setInlineEncryptionState(mp->encryptionState()); return mp; } return MessagePart::Ptr(); } KMMsgSignatureState ProcessResult::inlineSignatureState() const { return mInlineSignatureState; } void ProcessResult::setInlineSignatureState(KMMsgSignatureState state) { mInlineSignatureState = state; } KMMsgEncryptionState ProcessResult::inlineEncryptionState() const { return mInlineEncryptionState; } void ProcessResult::setInlineEncryptionState(KMMsgEncryptionState state) { mInlineEncryptionState = state; } bool ProcessResult::neverDisplayInline() const { return mNeverDisplayInline; } void ProcessResult::setNeverDisplayInline(bool display) { mNeverDisplayInline = display; } bool ProcessResult::isImage() const { return mIsImage; } void ProcessResult::setIsImage(bool image) { mIsImage = image; } void ProcessResult::adjustCryptoStatesOfNode(KMime::Content *node) const { if ((inlineSignatureState() != KMMsgNotSigned) || (inlineEncryptionState() != KMMsgNotEncrypted)) { mNodeHelper->setSignatureState(node, inlineSignatureState()); mNodeHelper->setEncryptionState(node, inlineEncryptionState()); } } ////////////////// ////////////////// ////////////////// 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; } } void ObjectTreeParser::writeCertificateImportResult(const GpgME::ImportResult &res) { if (res.error()) { htmlWriter()->queue(i18n("Sorry, certificate could not be imported.
" "Reason: %1", QString::fromLocal8Bit(res.error().asString()))); return; } const int nImp = res.numImported(); const int nUnc = res.numUnchanged(); const int nSKImp = res.numSecretKeysImported(); const int nSKUnc = res.numSecretKeysUnchanged(); if (!nImp && !nSKImp && !nUnc && !nSKUnc) { htmlWriter()->queue(i18n("Sorry, no certificates were found in this message.")); return; } QString comment = QLatin1String("") + i18n("Certificate import status:") + QLatin1String("
 
"); if (nImp) comment += i18np("1 new certificate was imported.", "%1 new certificates were imported.", nImp) + QLatin1String("
"); if (nUnc) comment += i18np("1 certificate was unchanged.", "%1 certificates were unchanged.", nUnc) + QLatin1String("
"); if (nSKImp) comment += i18np("1 new secret key was imported.", "%1 new secret keys were imported.", nSKImp) + QLatin1String("
"); if (nSKUnc) comment += i18np("1 secret key was unchanged.", "%1 secret keys were unchanged.", nSKUnc) + QLatin1String("
"); comment += QLatin1String(" 
"); htmlWriter()->queue(comment); if (!nImp && !nSKImp) { htmlWriter()->queue(QStringLiteral("
")); return; } const std::vector imports = res.imports(); if (imports.empty()) { htmlWriter()->queue(i18n("Sorry, no details on certificate import available.") + QLatin1String("
")); return; } htmlWriter()->queue(QLatin1String("") + i18n("Certificate import details:") + QLatin1String("
")); std::vector::const_iterator end(imports.end()); for (std::vector::const_iterator it = imports.begin(); it != end; ++it) { if ((*it).error()) { htmlWriter()->queue(i18nc("Certificate import failed.", "Failed: %1 (%2)", QLatin1String((*it).fingerprint()), QString::fromLocal8Bit((*it).error().asString()))); } else if ((*it).status() & ~GpgME::Import::ContainedSecretKey) { if ((*it).status() & GpgME::Import::ContainedSecretKey) { htmlWriter()->queue(i18n("New or changed: %1 (secret key available)", QLatin1String((*it).fingerprint()))); } else { htmlWriter()->queue(i18n("New or changed: %1", QLatin1String((*it).fingerprint()))); } } htmlWriter()->queue(QStringLiteral("
")); } htmlWriter()->queue(QStringLiteral("
")); } bool ObjectTreeParser::okDecryptMIME(KMime::Content &data, QByteArray &decryptedData, bool &signatureFound, std::vector &signatures, bool showWarning, bool &passphraseError, bool &actuallyEncrypted, bool &decryptionStarted, PartMetaData &partMetaData) { passphraseError = false; decryptionStarted = false; partMetaData.errorText.clear(); partMetaData.auditLogError = GpgME::Error(); partMetaData.auditLog.clear(); bool bDecryptionOk = false; enum { NO_PLUGIN, NOT_INITIALIZED, CANT_DECRYPT } cryptPlugError = NO_PLUGIN; const Kleo::CryptoBackend::Protocol *cryptProto = cryptoProtocol(); QString cryptPlugLibName; if (cryptProto) { cryptPlugLibName = cryptProto->name(); } assert(mSource->decryptMessage()); const QString errorMsg = i18n("Could not decrypt the data."); if (cryptProto) { QByteArray ciphertext = data.decodedContent(); #ifdef MARCS_DEBUG QString cipherStr = QString::fromLatin1(ciphertext); bool cipherIsBinary = (!cipherStr.contains(QStringLiteral("BEGIN ENCRYPTED MESSAGE"), Qt::CaseInsensitive)) && (!cipherStr.contains(QStringLiteral("BEGIN PGP ENCRYPTED MESSAGE"), Qt::CaseInsensitive)) && (!cipherStr.contains(QStringLiteral("BEGIN PGP MESSAGE"), Qt::CaseInsensitive)); dumpToFile("dat_04_reader.encrypted", ciphertext.data(), ciphertext.size()); QString deb; deb = QLatin1String("\n\nE N C R Y P T E D D A T A = "); if (cipherIsBinary) { deb += QLatin1String("[binary data]"); } else { deb += QLatin1String("\""); deb += cipherStr; deb += QLatin1String("\""); } deb += "\n\n"; qCDebug(MIMETREEPARSER_LOG) << deb; #endif qCDebug(MIMETREEPARSER_LOG) << "going to call CRYPTPLUG" << cryptPlugLibName; // Check whether the memento contains a result from last time: const DecryptVerifyBodyPartMemento *m = dynamic_cast(mNodeHelper->bodyPartMemento(&data, "decryptverify")); if (!m) { Kleo::DecryptVerifyJob *job = cryptProto->decryptVerifyJob(); if (!job) { cryptPlugError = CANT_DECRYPT; cryptProto = 0; } else { DecryptVerifyBodyPartMemento *newM = new DecryptVerifyBodyPartMemento(job, ciphertext); if (allowAsync()) { QObject::connect(newM, &CryptoBodyPartMemento::update, nodeHelper(), &NodeHelper::update); QObject::connect(newM, SIGNAL(update(MimeTreeParser::UpdateMode)), mSource->sourceObject(), SLOT(update(MimeTreeParser::UpdateMode))); if (newM->start()) { decryptionStarted = true; mHasPendingAsyncJobs = true; } else { m = newM; } } else { newM->exec(); m = newM; } mNodeHelper->setBodyPartMemento(&data, "decryptverify", newM); } } else if (m->isRunning()) { decryptionStarted = true; mHasPendingAsyncJobs = true; m = 0; } if (m) { const QByteArray &plainText = m->plainText(); const GpgME::DecryptionResult &decryptResult = m->decryptResult(); const GpgME::VerificationResult &verifyResult = m->verifyResult(); std::stringstream ss; ss << decryptResult << '\n' << verifyResult; qCDebug(MIMETREEPARSER_LOG) << ss.str().c_str(); signatureFound = verifyResult.signatures().size() > 0; signatures = verifyResult.signatures(); bDecryptionOk = !decryptResult.error(); partMetaData.auditLogError = m->auditLogError(); partMetaData.auditLog = m->auditLogAsHtml(); if (!bDecryptionOk && signatureFound) { //Only a signed part actuallyEncrypted = false; bDecryptionOk = true; decryptedData = plainText; } else { - passphraseError = decryptResult.error().isCanceled() - || decryptResult.error().code() == GPG_ERR_NO_SECKEY; + passphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY; actuallyEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA; partMetaData.errorText = QString::fromLocal8Bit(decryptResult.error().asString()); partMetaData.isEncrypted = actuallyEncrypted; if (actuallyEncrypted && decryptResult.numRecipients() > 0) { partMetaData.keyId = decryptResult.recipient(0).keyID(); } qCDebug(MIMETREEPARSER_LOG) << "ObjectTreeParser::decryptMIME: returned from CRYPTPLUG"; if (bDecryptionOk) { decryptedData = plainText; } else if (htmlWriter() && showWarning) { - decryptedData = "
" - + errorMsg.toUtf8() - + "
"; + bool noSecKey = true; + const QString sNoSecKeyHeader = i18n("No secret key found to encrypt the message. It is encrypted for following keys:"); + QString secKeyList; + foreach (const GpgME::DecryptionResult::Recipient &recipient, decryptResult.recipients()) { + noSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY); + + if (!secKeyList.isEmpty()) { + secKeyList += QStringLiteral("
"); + } + + secKeyList += QStringLiteral("
%4") + .arg(cryptProto->displayName(), + cryptProto->name(), + QString::fromLatin1(recipient.keyID()), + QString::fromLatin1(QByteArray("0x") + recipient.keyID()) + ); + } + + decryptedData = "
"; + if (noSecKey) { + decryptedData += QString(sNoSecKeyHeader + QStringLiteral("
") + secKeyList).toUtf8(); + } else { + decryptedData += errorMsg.toUtf8(); + } + decryptedData += "
"; if (!passphraseError) { partMetaData.errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1String("
") + i18n("Error: %1", partMetaData.errorText); } } } } } if (!cryptProto) { decryptedData = "
" + errorMsg.toUtf8() + "
"; switch (cryptPlugError) { case NOT_INITIALIZED: partMetaData.errorText = i18n("Crypto plug-in \"%1\" is not initialized.", cryptPlugLibName); break; case CANT_DECRYPT: partMetaData.errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName); break; case NO_PLUGIN: partMetaData.errorText = i18n("No appropriate crypto plug-in was found."); break; } } dumpToFile("dat_05_reader.decrypted", decryptedData.data(), decryptedData.size()); return bDecryptionOk; } MessagePart::Ptr ObjectTreeParser::processTextHtmlSubtype(KMime::Content *curNode, ProcessResult &) { HtmlMessagePart::Ptr mp(new HtmlMessagePart(this, curNode, mSource)); if (curNode->topLevel()->textContent() == curNode || attachmentStrategy()->defaultDisplay(curNode) == AttachmentStrategy::Inline || showOnlyOneMimePart()) { } else { // we need the copy of htmlcontent and charset in anycase for the current design of otp. //Should be not neeed if otp don't hold any status anymore mp->fix(); } return mp; } void ObjectTreeParser::extractNodeInfos(KMime::Content *curNode, bool isFirstTextPart) { if (isFirstTextPart) { mPlainTextContent += curNode->decodedText(); mPlainTextContentCharset += NodeHelper::charset(curNode); } } MessagePart::Ptr ObjectTreeParser::processTextPlainSubtype(KMime::Content *curNode, ProcessResult &result) { const bool isFirstTextPart = (curNode->topLevel()->textContent() == curNode); if (!isFirstTextPart && attachmentStrategy()->defaultDisplay(curNode) != AttachmentStrategy::Inline && !showOnlyOneMimePart()) { return MessagePart::Ptr(); } extractNodeInfos(curNode, isFirstTextPart); QString label = NodeHelper::fileName(curNode); const bool bDrawFrame = !isFirstTextPart && !showOnlyOneMimePart() && !label.isEmpty(); const QString fileName = mNodeHelper->writeNodeToTempFile(curNode); TextMessagePart::Ptr mp(new TextMessagePart(this, curNode, bDrawFrame, !fileName.isEmpty(), mSource->decryptMessage(), MimeTreeParser::NoIcon)); result.setInlineSignatureState(mp->signatureState()); result.setInlineEncryptionState(mp->encryptionState()); if (isFirstTextPart) { mPlainTextContent = mp->text(); } mNodeHelper->setNodeDisplayedEmbedded(curNode, true); return mp; } MessagePart::Ptr ObjectTreeParser::processMultiPartMixedSubtype(KMime::Content *node, ProcessResult &) { KMime::Content *child = MessageCore::NodeHelper::firstChild(node); if (!child) { return MessagePart::Ptr(); } // normal treatment of the parts in the mp/mixed container MimeMessagePart::Ptr mp(new MimeMessagePart(this, child, false)); return mp; } MessagePart::Ptr ObjectTreeParser::processMultiPartAlternativeSubtype(KMime::Content *node, ProcessResult &) { KMime::Content *child = MessageCore::NodeHelper::firstChild(node); if (!child) { return MessagePart::Ptr(); } KMime::Content *dataHtml = findType(child, "text/html", false, true); KMime::Content *dataPlain = findType(child, "text/plain", false, true); 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 = findType(child, "multipart/related", false, true); // 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 && mSource->htmlMail()) { dataHtml = findType(child, "multipart/mixed", false, true); } } if (dataPlain || dataHtml) { AlternativeMessagePart::Ptr mp(new AlternativeMessagePart(this, dataPlain, dataHtml)); if ((mSource->htmlMail() && dataHtml) || (dataHtml && dataPlain && dataPlain->body().isEmpty())) { if (dataPlain) { mNodeHelper->setNodeProcessed(dataPlain, false); } mSource->setHtmlMode(Util::MultipartHtml); mp->setViewHtml(true); } if (!mSource->htmlMail() && dataPlain) { mNodeHelper->setNodeProcessed(dataHtml, false); mSource->setHtmlMode(Util::MultipartPlain); mp->setViewHtml(false); } return mp; } MimeMessagePart::Ptr mp(new MimeMessagePart(this, child, false)); return mp; } MessagePart::Ptr ObjectTreeParser::processMultiPartSignedSubtype(KMime::Content *node, ProcessResult &) { KMime::Content *signedData = MessageCore::NodeHelper::firstChild(node); assert(signedData); if (node->contents().size() != 2) { qCDebug(MIMETREEPARSER_LOG) << "mulitpart/signed must have exactly two child parts!" << endl << "processing as multipart/mixed"; return MessagePart::Ptr(new MimeMessagePart(this, signedData, false)); } KMime::Content *signature = node->contents().at(1); assert(signature); QString protocolContentType = node->contentType()->parameter(QStringLiteral("protocol")).toLower(); const QString signatureContentType = QLatin1String(signature->contentType()->mimeType().toLower()); if (protocolContentType.isEmpty()) { qCWarning(MIMETREEPARSER_LOG) << "Message doesn't set the protocol for the multipart/signed content-type, " "using content-type of the signature:" << signatureContentType; protocolContentType = signatureContentType; } const Kleo::CryptoBackend::Protocol *protocol = Q_NULLPTR; if (protocolContentType == QLatin1String("application/pkcs7-signature") || protocolContentType == QLatin1String("application/x-pkcs7-signature")) { protocol = Kleo::CryptoBackendFactory::instance()->smime(); } else if (protocolContentType == QLatin1String("application/pgp-signature") || protocolContentType == QLatin1String("application/x-pgp-signature")) { protocol = Kleo::CryptoBackendFactory::instance()->openpgp(); } if (!protocol) { return MessagePart::Ptr(new MimeMessagePart(this, signedData, false)); } mNodeHelper->setNodeProcessed(signature, true); mNodeHelper->setSignatureState(node, KMMsgFullySigned); const QByteArray cleartext = KMime::LFtoCRLF(signedData->encodedContent()); const QTextCodec *aCodec(codecFor(signedData)); CryptoMessagePart::Ptr mp(new CryptoMessagePart(this, aCodec->toUnicode(cleartext), protocol, NodeHelper::fromAsString(node), signature)); PartMetaData *messagePart(mp->partMetaData()); messagePart->isSigned = true; if (protocol) { mp->startVerificationDetached(cleartext, signedData, signature->decodedContent()); } else { messagePart->auditLogError = GpgME::Error(GPG_ERR_NOT_IMPLEMENTED); } return mp; } MessagePart::Ptr ObjectTreeParser::processMultiPartEncryptedSubtype(KMime::Content *node, ProcessResult &result) { Q_UNUSED(result); KMime::Content *child = MessageCore::NodeHelper::firstChild(node); if (!child) { Q_ASSERT(false); return MessagePart::Ptr(); } const Kleo::CryptoBackend::Protocol *useThisCryptProto = Q_NULLPTR; /* ATTENTION: This code is to be replaced by the new 'auto-detect' feature. -------------------------------------- */ KMime::Content *data = findType(child, "application/octet-stream", false, true); if (data) { useThisCryptProto = Kleo::CryptoBackendFactory::instance()->openpgp(); } if (!data) { data = findType(child, "application/pkcs7-mime", false, true); if (data) { useThisCryptProto = Kleo::CryptoBackendFactory::instance()->smime(); } } /* --------------------------------------------------------------------------------------------------------------- */ if (!data) { return MessagePart::Ptr(new MimeMessagePart(this, child, false)); } KMime::Content *dataChild = MessageCore::NodeHelper::firstChild(data); if (dataChild) { Q_ASSERT(false); return MessagePart::Ptr(new MimeMessagePart(this, dataChild, false)); } mNodeHelper->setEncryptionState(node, KMMsgFullyEncrypted); CryptoMessagePart::Ptr mp(new CryptoMessagePart(this, data->decodedText(), useThisCryptProto, NodeHelper::fromAsString(data), node)); mp->setIsEncrypted(true); mp->setDecryptMessage(mSource->decryptMessage()); PartMetaData *messagePart(mp->partMetaData()); if (!mSource->decryptMessage()) { mNodeHelper->setNodeProcessed(data, false); // Set the data node to done to prevent it from being processed } else if (KMime::Content *newNode = mNodeHelper->decryptedNodeForContent(data)) { // if we already have a decrypted node for this encrypted node, don't do the decryption again return MessagePart::Ptr(new MimeMessagePart(this, newNode, mShowOnlyOneMimePart)); } else { mp->startDecryption(data); qCDebug(MIMETREEPARSER_LOG) << "decrypted, signed?:" << messagePart->isSigned; if (!messagePart->inProgress) { mNodeHelper->setNodeProcessed(data, false); // Set the data node to done to prevent it from being processed if (messagePart->isDecryptable && messagePart->isSigned) { // Note: Multipart/Encrypted might also be signed // without encapsulating a nicely formatted // ~~~~~~~ Multipart/Signed part. // (see RFC 3156 --> 6.2) // In this case we paint a _2nd_ frame inside the // encryption frame, but we do _not_ show a respective // encapsulated MIME part in the Mime Tree Viewer // since we do want to show the _true_ structure of the // message there - not the structure that the sender's // MUA 'should' have sent. :-D (khz, 12.09.2002) mNodeHelper->setSignatureState(node, KMMsgFullySigned); qCDebug(MIMETREEPARSER_LOG) << "setting FULLY SIGNED to:" << node; } } } return mp; } MessagePart::Ptr ObjectTreeParser::processApplicationPkcs7MimeSubtype(KMime::Content *node, ProcessResult &result) { if (node->head().isEmpty()) { return MessagePart::Ptr(); } const Kleo::CryptoBackend::Protocol *smimeCrypto = Kleo::CryptoBackendFactory::instance()->smime(); if (!smimeCrypto) { return MessagePart::Ptr(); } const QString smimeType = node->contentType()->parameter(QStringLiteral("smime-type")).toLower(); if (smimeType == QLatin1String("certs-only")) { result.setNeverDisplayInline(true); CertMessagePart::Ptr mp(new CertMessagePart(this, node, smimeCrypto, mSource->autoImportKeys())); return mp; } bool isSigned = (smimeType == QLatin1String("signed-data")); bool isEncrypted = (smimeType == QLatin1String("enveloped-data")); // Analyze "signTestNode" node to find/verify a signature. // If zero this verification was successfully done after // decrypting via recursion by insertAndParseNewChildNode(). KMime::Content *signTestNode = isEncrypted ? 0 : node; // We try decrypting the content // if we either *know* that it is an encrypted message part // or there is neither signed nor encrypted parameter. CryptoMessagePart::Ptr mp; if (!isSigned) { if (isEncrypted) { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime == S/MIME TYPE: enveloped (encrypted) data"; } else { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - type unknown - enveloped (encrypted) data ?"; } mp = CryptoMessagePart::Ptr(new CryptoMessagePart(this, node->decodedText(), smimeCrypto, NodeHelper::fromAsString(node), node)); mp->setIsEncrypted(true); mp->setDecryptMessage(mSource->decryptMessage()); PartMetaData *messagePart(mp->partMetaData()); if (!mSource->decryptMessage()) { isEncrypted = true; signTestNode = 0; // PENDING(marc) to be abs. sure, we'd need to have to look at the content } else { mp->startDecryption(); if (messagePart->isDecryptable) { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - encryption found - enveloped (encrypted) data !"; isEncrypted = true; mNodeHelper->setEncryptionState(node, KMMsgFullyEncrypted); if (messagePart->isSigned) { mNodeHelper->setSignatureState(node, KMMsgFullySigned); } signTestNode = 0; } else { // decryption failed, which could be because the part was encrypted but // decryption failed, or because we didn't know if it was encrypted, tried, // and failed. If the message was not actually encrypted, we continue // assuming it's signed if (mp->mPassphraseError || (smimeType.isEmpty() && messagePart->isEncrypted)) { isEncrypted = true; signTestNode = 0; } if (isEncrypted) { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - ERROR: COULD NOT DECRYPT enveloped data !"; } else { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - NO encryption found"; } } } if (isEncrypted) { mNodeHelper->setEncryptionState(node, KMMsgFullyEncrypted); } } // We now try signature verification if necessarry. if (signTestNode) { if (isSigned) { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime == S/MIME TYPE: opaque signed data"; } else { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - type unknown - opaque signed data ?"; } const QTextCodec *aCodec(codecFor(signTestNode)); const QByteArray signaturetext = signTestNode->decodedContent(); mp = CryptoMessagePart::Ptr(new CryptoMessagePart(this, aCodec->toUnicode(signaturetext), smimeCrypto, NodeHelper::fromAsString(node), signTestNode)); mp->setDecryptMessage(mSource->decryptMessage()); PartMetaData *messagePart(mp->partMetaData()); if (smimeCrypto) { mp->startVerificationDetached(signaturetext, 0, QByteArray()); } else { messagePart->auditLogError = GpgME::Error(GPG_ERR_NOT_IMPLEMENTED); } if (messagePart->isSigned) { if (!isSigned) { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - signature found - opaque signed data !"; isSigned = true; } mNodeHelper->setSignatureState(signTestNode, KMMsgFullySigned); if (signTestNode != node) { mNodeHelper->setSignatureState(node, KMMsgFullySigned); } } else { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - NO signature found :-("; } } return mp; } void ObjectTreeParser::writePartIcon(KMime::Content *msgPart, bool inlineImage) { if (!htmlWriter() || !msgPart) { return; } const QString name = msgPart->contentType()->name(); QString label = name.isEmpty() ? NodeHelper::fileName(msgPart) : name; if (label.isEmpty()) { label = i18nc("display name for an unnamed attachment", "Unnamed"); } label = StringUtil::quoteHtmlChars(label, true); QString comment = msgPart->contentDescription()->asUnicodeString(); comment = StringUtil::quoteHtmlChars(comment, true); if (label == comment) { comment.clear(); } QString href = mNodeHelper->asHREF(msgPart, QStringLiteral("body")); if (inlineImage) { const QString fileName = mNodeHelper->writeNodeToTempFile(msgPart); // show the filename of the image below the embedded image htmlWriter()->queue(QLatin1String("
" "" "
" "
") + label + QLatin1String("" "
" "
") + comment + QLatin1String("
")); } else { // show the filename next to the image const QString iconName = QUrl::fromLocalFile(mNodeHelper->iconName(msgPart)).url(); if (iconName.right(14) == QLatin1String("mime_empty.png")) { mNodeHelper->magicSetType(msgPart); //iconName = mNodeHelper->iconName( msgPart ); } const int iconSize = KIconLoader::global()->currentSize(KIconLoader::Desktop); htmlWriter()->queue(QStringLiteral("
").arg(href) + QStringLiteral("\"\"/").arg(QString::number(iconSize), iconName) + label + QStringLiteral("
") + QStringLiteral("
%1
").arg(comment)); } } //----------------------------------------------------------------------------- bool ObjectTreeParser::okVerify(const QByteArray &data, const Kleo::CryptoBackend::Protocol *cryptProto, PartMetaData &messagePart, QByteArray &verifiedText, std::vector &signatures, const QByteArray &signature, KMime::Content *sign) { enum { NO_PLUGIN, NOT_INITIALIZED, CANT_VERIFY_SIGNATURES } cryptPlugError = NO_PLUGIN; QString cryptPlugLibName; QString cryptPlugDisplayName; if (cryptProto) { cryptPlugLibName = cryptProto->name(); cryptPlugDisplayName = cryptProto->displayName(); } messagePart.isSigned = false; messagePart.technicalProblem = (cryptProto == 0); messagePart.keyTrust = GpgME::Signature::Unknown; messagePart.status = i18n("Wrong Crypto Plug-In."); messagePart.status_code = GPGME_SIG_STAT_NONE; const QByteArray mementoName = "verification"; CryptoBodyPartMemento *m = dynamic_cast(mNodeHelper->bodyPartMemento(sign, mementoName)); if (!m) { if (!signature.isEmpty()) { Kleo::VerifyDetachedJob *job = cryptProto->verifyDetachedJob(); if (job) { m = new VerifyDetachedBodyPartMemento(job, cryptProto->keyListJob(), signature, data); } else { cryptPlugError = CANT_VERIFY_SIGNATURES; cryptProto = 0; } } else { Kleo::VerifyOpaqueJob *job = cryptProto->verifyOpaqueJob(); if (job) { m = new VerifyOpaqueBodyPartMemento(job, cryptProto->keyListJob(), data); } else { cryptPlugError = CANT_VERIFY_SIGNATURES; cryptProto = 0; } } if (m) { if (allowAsync()) { QObject::connect(m, &CryptoBodyPartMemento::update, mNodeHelper, &NodeHelper::update); QObject::connect(m, SIGNAL(update(MimeTreeParser::UpdateMode)), mSource->sourceObject(), SLOT(update(MimeTreeParser::UpdateMode))); if (m->start()) { messagePart.inProgress = true; mHasPendingAsyncJobs = true; } } else { m->exec(); } mNodeHelper->setBodyPartMemento(sign, mementoName, m); } } else if (m->isRunning()) { messagePart.inProgress = true; mHasPendingAsyncJobs = true; m = 0; } else { messagePart.inProgress = false; mHasPendingAsyncJobs = false; } if (m && !messagePart.inProgress) { if (!signature.isEmpty()) { VerifyDetachedBodyPartMemento *vm = dynamic_cast(m); verifiedText = data; signatures = vm->verifyResult().signatures(); } else { VerifyOpaqueBodyPartMemento *vm = dynamic_cast(m); verifiedText = vm->plainText(); signatures = vm->verifyResult().signatures(); } messagePart.auditLogError = m->auditLogError(); messagePart.auditLog = m->auditLogAsHtml(); messagePart.isSigned = !signatures.empty(); } if (!cryptProto) { QString errorMsg; switch (cryptPlugError) { case NOT_INITIALIZED: errorMsg = i18n("Crypto plug-in \"%1\" is not initialized.", cryptPlugLibName); break; case CANT_VERIFY_SIGNATURES: errorMsg = i18n("Crypto plug-in \"%1\" cannot verify signatures.", cryptPlugLibName); break; case NO_PLUGIN: if (cryptPlugDisplayName.isEmpty()) { errorMsg = i18n("No appropriate crypto plug-in was found."); } else { errorMsg = i18nc("%1 is either 'OpenPGP' or 'S/MIME'", "No %1 plug-in was found.", cryptPlugDisplayName); } break; } messagePart.errorText = i18n("The message is signed, but the " "validity of the signature cannot be " "verified.
" "Reason: %1", errorMsg); } return messagePart.isSigned; } void ObjectTreeParser::sigStatusToMetaData(const std::vector &signatures, const Kleo::CryptoBackend::Protocol *cryptProto, PartMetaData &messagePart, GpgME::Key key) { if (messagePart.isSigned) { GpgME::Signature signature = signatures.front(); messagePart.status_code = signatureToStatus(signature); messagePart.isGoodSignature = messagePart.status_code & GPGME_SIG_STAT_GOOD; // save extended signature status flags messagePart.sigSummary = signature.summary(); if (messagePart.isGoodSignature && !key.keyID()) { // Search for the key by it's fingerprint so that we can check for // trust etc. Kleo::KeyListJob *job = cryptProto->keyListJob(false); // local, no sigs if (!job) { qCDebug(MIMETREEPARSER_LOG) << "The Crypto backend does not support listing keys. "; } else { std::vector found_keys; // As we are local it is ok to make this synchronous GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(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.size() != 1) { // Should not Happen at this point qCDebug(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint(); } else { key = found_keys[0]; } } } if (key.keyID()) { messagePart.keyId = key.keyID(); } if (messagePart.keyId.isEmpty()) { messagePart.keyId = signature.fingerprint(); } messagePart.keyTrust = signature.validity(); if (key.numUserIDs() > 0 && key.userID(0).id()) { messagePart.signer = Kleo::DN(key.userID(0).id()).prettyDN(); } 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.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()) { messagePart.signerMailAddresses.append(email); } } } if (signature.creationTime()) { messagePart.creationTime.setTime_t(signature.creationTime()); } else { messagePart.creationTime = QDateTime(); } if (messagePart.signer.isEmpty()) { if (key.numUserIDs() > 0 && key.userID(0).name()) { messagePart.signer = Kleo::DN(key.userID(0).name()).prettyDN(); } if (!messagePart.signerMailAddresses.empty()) { if (messagePart.signer.isEmpty()) { messagePart.signer = messagePart.signerMailAddresses.front(); } else { messagePart.signer += QLatin1String(" <") + messagePart.signerMailAddresses.front() + QLatin1Char('>'); } } } } } static QString iconToDataUrl(const QString &iconPath) { QFile f(iconPath); if (!f.open(QIODevice::ReadOnly)) { return QString(); } const QByteArray ba = f.readAll(); return QStringLiteral("data:image/png;base64,%1").arg(QLatin1String(ba.toBase64().constData())); } QString ObjectTreeParser::quotedHTML(const QString &s, bool decorate) { assert(cssHelper()); KTextToHTML::Options convertFlags = KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText; if (decorate && mSource->showEmoticons()) { convertFlags |= KTextToHTML::ReplaceSmileys; } QString htmlStr; const QString normalStartTag = cssHelper()->nonQuotedFontTag(); QString quoteFontTag[3]; QString deepQuoteFontTag[3]; for (int i = 0; i < 3; ++i) { quoteFontTag[i] = cssHelper()->quoteFontTag(i); deepQuoteFontTag[i] = cssHelper()->quoteFontTag(i + 3); } const QString normalEndTag = QStringLiteral(""); const QString quoteEnd = QStringLiteral(""); const unsigned int length = s.length(); bool paraIsRTL = false; bool startNewPara = true; unsigned int pos, beg; // skip leading empty lines for (pos = 0; pos < length && s[pos] <= QLatin1Char(' '); ++pos) ; while (pos > 0 && (s[pos - 1] == QLatin1Char(' ') || s[pos - 1] == QLatin1Char('\t'))) { pos--; } beg = pos; int currQuoteLevel = -2; // -2 == no previous lines bool curHidden = false; // no hide any block if (mSource->showExpandQuotesMark()) { // Cache Icons if (mCollapseIcon.isEmpty()) { mCollapseIcon = iconToDataUrl(IconNameCache::instance()->iconPath(QStringLiteral("quotecollapse"), 0)); } if (mExpandIcon.isEmpty()) { mExpandIcon = iconToDataUrl(IconNameCache::instance()->iconPath(QStringLiteral("quoteexpand"), 0)); } } int previousQuoteDepth = -1; while (beg < length) { /* search next occurrence of '\n' */ pos = s.indexOf(QLatin1Char('\n'), beg, Qt::CaseInsensitive); if (pos == (unsigned int)(-1)) { pos = length; } QString line(s.mid(beg, pos - beg)); beg = pos + 1; bool foundQuote = false; /* calculate line's current quoting depth */ int actQuoteLevel = -1; const int numberOfCaracters(line.length()); int quoteLength = 0; for (int p = 0; p < numberOfCaracters; ++p) { switch (line[p].toLatin1()) { case '>': case '|': actQuoteLevel++; quoteLength = p; foundQuote = true; break; case ' ': // spaces and tabs are allowed between the quote markers case '\t': case '\r': quoteLength = p; break; default: // stop quoting depth calculation p = numberOfCaracters; break; } } /* for() */ if (!foundQuote) { quoteLength = 0; } bool actHidden = false; // This quoted line needs be hidden if (mSource->showExpandQuotesMark() && mSource->levelQuote() >= 0 && mSource->levelQuote() <= (actQuoteLevel)) { actHidden = true; } if (actQuoteLevel != currQuoteLevel) { /* finish last quotelevel */ if (currQuoteLevel == -1) { htmlStr.append(normalEndTag); } else if (currQuoteLevel >= 0 && !curHidden) { htmlStr.append(quoteEnd); } //Close blockquote if (previousQuoteDepth > actQuoteLevel) { htmlStr += cssHelper()->addEndBlockQuote((previousQuoteDepth - actQuoteLevel)); } /* start new quotelevel */ if (actQuoteLevel == -1) { htmlStr += normalStartTag; } else { if (mSource->showExpandQuotesMark()) { if (actHidden) { //only show the QuoteMark when is the first line of the level hidden if (!curHidden) { //Expand all quotes htmlStr += QLatin1String("
"); htmlStr += QStringLiteral("" "") .arg(-1) .arg(mExpandIcon); htmlStr += QLatin1String("

"); htmlStr += quoteEnd; } } else { htmlStr += QLatin1String("
"); htmlStr += QStringLiteral("" "") .arg(actQuoteLevel) .arg(mCollapseIcon); htmlStr += QLatin1String("
"); if (actQuoteLevel < 3) { htmlStr += quoteFontTag[actQuoteLevel]; } else { htmlStr += deepQuoteFontTag[actQuoteLevel % 3]; } } } else { // Add blockquote if (previousQuoteDepth < actQuoteLevel) { htmlStr += cssHelper()->addStartBlockQuote(actQuoteLevel - previousQuoteDepth); } if (actQuoteLevel < 3) { htmlStr += quoteFontTag[actQuoteLevel]; } else { htmlStr += deepQuoteFontTag[actQuoteLevel % 3]; } } } currQuoteLevel = actQuoteLevel; } curHidden = actHidden; if (!actHidden) { // don't write empty
blocks (they have zero height) // ignore ^M DOS linebreaks if (!line.remove(QLatin1Char('\015')).isEmpty()) { if (startNewPara) { paraIsRTL = line.isRightToLeft(); } htmlStr += QStringLiteral("
").arg(paraIsRTL ? QStringLiteral("rtl") : QStringLiteral("ltr")); // if quoteLengh == 0 && foundQuote => a simple quote if (foundQuote) { quoteLength++; htmlStr += QStringLiteral("%1").arg(line.left(quoteLength)); const int rightString = (line.length()) - quoteLength; if (rightString > 0) { htmlStr += QStringLiteral("").arg(cssHelper()->quoteColorName(actQuoteLevel)) + KTextToHTML::convertToHtml(line.right(rightString), convertFlags) + QStringLiteral(""); } } else { htmlStr += KTextToHTML::convertToHtml(line, convertFlags); } htmlStr += QLatin1String("
"); startNewPara = looksLikeParaBreak(s, pos); } else { htmlStr += QLatin1String("
"); // after an empty line, always start a new paragraph startNewPara = true; } } previousQuoteDepth = actQuoteLevel; } /* while() */ /* really finish the last quotelevel */ if (currQuoteLevel == -1) { htmlStr.append(normalEndTag); } else { htmlStr += quoteEnd + cssHelper()->addEndBlockQuote(currQuoteLevel + 1); } // qCDebug(MIMETREEPARSER_LOG) << "========================================\n" // << htmlStr // << "\n======================================\n"; return htmlStr; } const QTextCodec *ObjectTreeParser::codecFor(KMime::Content *node) const { assert(node); if (mSource->overrideCodec()) { return mSource->overrideCodec(); } return mNodeHelper->codec(node); } // Guesstimate if the newline at newLinePos actually separates paragraphs in the text s // We use several heuristics: // 1. If newLinePos points after or before (=at the very beginning of) text, it is not between paragraphs // 2. If the previous line was longer than the wrap size, we want to consider it a paragraph on its own // (some clients, notably Outlook, send each para as a line in the plain-text version). // 3. Otherwise, we check if the newline could have been inserted for wrapping around; if this // was the case, then the previous line will be shorter than the wrap size (which we already // know because of item 2 above), but adding the first word from the next line will make it // longer than the wrap size. bool ObjectTreeParser::looksLikeParaBreak(const QString &s, unsigned int newLinePos) const { const unsigned int WRAP_COL = 78; unsigned int length = s.length(); // 1. Is newLinePos at an end of the text? if (newLinePos >= length - 1 || newLinePos == 0) { return false; } // 2. Is the previous line really a paragraph -- longer than the wrap size? // First char of prev line -- works also for first line unsigned prevStart = s.lastIndexOf(QLatin1Char('\n'), newLinePos - 1) + 1; unsigned prevLineLength = newLinePos - prevStart; if (prevLineLength > WRAP_COL) { return true; } // find next line to delimit search for first word unsigned int nextStart = newLinePos + 1; int nextEnd = s.indexOf(QLatin1Char('\n'), nextStart); if (nextEnd == -1) { nextEnd = length; } QString nextLine = s.mid(nextStart, nextEnd - nextStart); length = nextLine.length(); // search for first word in next line unsigned int wordStart; bool found = false; for (wordStart = 0; !found && wordStart < length; wordStart++) { switch (nextLine[wordStart].toLatin1()) { case '>': case '|': case ' ': // spaces, tabs and quote markers don't count case '\t': case '\r': break; default: found = true; break; } } /* for() */ if (!found) { // next line is essentially empty, it seems -- empty lines are // para separators return true; } //Find end of first word. //Note: flowText (in kmmessage.cpp) separates words for wrap by //spaces only. This should be consistent, which calls for some //refactoring. int wordEnd = nextLine.indexOf(QLatin1Char(' '), wordStart); if (wordEnd == (-1)) { wordEnd = length; } int wordLength = wordEnd - wordStart; // 3. If adding a space and the first word to the prev line don't // make it reach the wrap column, then the break was probably // meaningful return prevLineLength + wordLength + 1 < WRAP_COL; } #ifdef MARCS_DEBUG void ObjectTreeParser::dumpToFile(const char *filename, const char *start, size_t len) { assert(filename); QFile f(QString::fromAscii(filename)); if (f.open(QIODevice::WriteOnly)) { if (start) { QDataStream ds(&f); ds.writeRawData(start, len); } f.close(); // If data is 0 we just create a zero length file. } } #endif // !NDEBUG KMime::Content *ObjectTreeParser::findType(KMime::Content *content, const QByteArray &mimeType, bool deep, bool wide) { if ((!content->contentType()->isEmpty()) && (mimeType.isEmpty() || (mimeType == content->contentType()->mimeType()))) { return content; } KMime::Content *child = MessageCore::NodeHelper::firstChild(content); if (child && deep) { //first child return findType(child, mimeType, deep, wide); } KMime::Content *next = MessageCore::NodeHelper::nextSibling(content); if (next && wide) { //next on the same level return findType(next, mimeType, deep, wide); } return 0; } KMime::Content *ObjectTreeParser::findType(KMime::Content *content, const QByteArray &mediaType, const QByteArray &subType, bool deep, bool wide) { if (!content->contentType()->isEmpty()) { if ((mediaType.isEmpty() || mediaType == content->contentType()->mediaType()) && (subType.isEmpty() || subType == content->contentType()->subType())) { return content; } } KMime::Content *child = MessageCore::NodeHelper::firstChild(content); if (child && deep) { //first child return findType(child, mediaType, subType, deep, wide); } KMime::Content *next = MessageCore::NodeHelper::nextSibling(content); if (next && wide) { //next on the same level return findType(next, mediaType, subType, deep, wide); } return 0; } KMime::Content *ObjectTreeParser::findTypeNot(KMime::Content *content, const QByteArray &mediaType, const QByteArray &subType, bool deep, bool wide) { if ((!content->contentType()->isEmpty()) && (mediaType.isEmpty() || content->contentType()->mediaType() != mediaType) && (subType.isEmpty() || content->contentType()->subType() != subType) ) { return content; } KMime::Content *child = MessageCore::NodeHelper::firstChild(content); if (child && deep) { return findTypeNot(child, mediaType, subType, deep, wide); } KMime::Content *next = MessageCore::NodeHelper::nextSibling(content); if (next && wide) { return findTypeNot(next, mediaType, subType, deep, wide); } return 0; } QByteArray ObjectTreeParser::plainTextContentCharset() const { return mPlainTextContentCharset; } QByteArray ObjectTreeParser::htmlContentCharset() const { return mHtmlContentCharset; } void ObjectTreeParser::setCryptoProtocol(const Kleo::CryptoBackend::Protocol *protocol) { mCryptoProtocol = protocol; } const Kleo::CryptoBackend::Protocol *ObjectTreeParser::cryptoProtocol() const { return mCryptoProtocol; } bool ObjectTreeParser::showOnlyOneMimePart() const { return mShowOnlyOneMimePart; } void ObjectTreeParser::setShowOnlyOneMimePart(bool show) { mShowOnlyOneMimePart = show; } const AttachmentStrategy *ObjectTreeParser::attachmentStrategy() const { return mAttachmentStrategy; } HtmlWriter *ObjectTreeParser::htmlWriter() const { if (mHtmlWriter) { return mHtmlWriter; } return mSource->htmlWriter(); } CSSHelperBase *ObjectTreeParser::cssHelper() const { return mSource->cssHelper(); } MimeTreeParser::NodeHelper *ObjectTreeParser::nodeHelper() const { return mNodeHelper; }