diff --git a/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.html b/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.html new file mode 100644 --- /dev/null +++ b/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.html @@ -0,0 +1,10 @@ + + + + +
+ +
+
+ + diff --git a/mimetreeparser/autotests/data/hidden/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.html b/mimetreeparser/autotests/data/hidden/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.html new file mode 100644 --- /dev/null +++ b/mimetreeparser/autotests/data/hidden/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.html @@ -0,0 +1,10 @@ + + + + +
+ +
+
+ + diff --git a/mimetreeparser/autotests/data/inlined/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.html b/mimetreeparser/autotests/data/inlined/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.html new file mode 100644 --- /dev/null +++ b/mimetreeparser/autotests/data/inlined/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.html @@ -0,0 +1,17 @@ + + + + +
+ + +
+ + diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment-non-encrypted.mbox b/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment-non-encrypted.mbox new file mode 100644 --- /dev/null +++ b/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment-non-encrypted.mbox @@ -0,0 +1,26 @@ +From unknown@example.org Tue Oct 03 10:05:19 2017 +Return-Path: +To: konqi@example.org +Subject: only an encrypted attachment +X-PHP-Originating-Script: 1008:rcube.php +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="=_XXXXXXXXXXXXXX" +Date: Tue, 03 Oct 2017 12:13:14 +0000 +From: unknown@example.org +Message-ID: +X-Sender: unknown@example.org +User-Agent: Roundcube Webmail/1.1.9 + + +--=_XXXXXXXXXXXXXX +Content-Transfer-Encoding: 7bit +Content-Type: text/PGP; + name=encrypted.txt.pgp +Content-Disposition: attachment; + filename=encrypted.txt.pgp; + size=1402 + +nothing is encrypted here. +--=_XXXXXXXXXXXXXX-- + diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.html b/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.html new file mode 100644 --- /dev/null +++ b/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.html @@ -0,0 +1,19 @@ + + + + +
+ + + + diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.tree b/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.tree new file mode 100644 --- /dev/null +++ b/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment-non-encrypted.mbox.tree @@ -0,0 +1,4 @@ + * MimeTreeParser::MessagePartList + * MimeTreeParser::MimeMessagePart + * MimeTreeParser::AttachmentMessagePart + * MimeTreeParser::MessagePart diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment.mbox b/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment.mbox new file mode 100644 --- /dev/null +++ b/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment.mbox @@ -0,0 +1,32 @@ +From unknown@example.org Tue Oct 03 10:05:19 2017 +Return-Path: +To: konqi@example.org +Subject: only an encrypted attachment +X-PHP-Originating-Script: 1008:rcube.php +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="=_XXXXXXXXXXXXXX" +Date: Tue, 03 Oct 2017 12:13:14 +0000 +From: unknown@example.org +Message-ID: +X-Sender: unknown@example.org +User-Agent: Roundcube Webmail/1.1.9 + + +--=_XXXXXXXXXXXXXX +Content-Transfer-Encoding: base64 +Content-Type: text/PGP; + name=Brotzeiten.txt.pgp +Content-Disposition: attachment; + filename=encrypted.txt.pgp; + size=1402 + +hQEMAwzOQ1qnzNo7AQf/Z3qYebORMk5Z4R+Pmb6gBLA20G3yLRFyd0z+ygcEp+rc1jYE1ZqK1pFz +Z6WLgPdMcyrKBLrDeDfsQRbKvimR7NaXLSuNkRHFtgqCmaFDyIUMcf9DobAoKvGvmBJhgMW7WPCA +RUDaiJyqBp29nCQuOIqAiT5fm5LyyUmhRxUb9/CnulcxzW85EiITm0wMos30j1dU++3KDp5R6gcc +9uUVbdCWdiF4j3ilE8flCWg9tARTRGinu4ENc9eIjcn1hCotFS+4ccWYIwf6ZkVDS4wNfU7dDg+9 +ZwNb+RkXuYXT/tFdir+ewV0Njaj0ZWTUrqZSk2ArmdGo9ou7xl/2mrK6YNJIAUKgVsQlWZ1Kx4PI +0k0+TsJBgRzVNpIBHpmu0MI4CE03XHUslFhzdrjOG2Ts26U3EJw+jjssu0W4MVzYc20u3BgRnzHn +/SFA +--=_XXXXXXXXXXXXXX-- + diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment.mbox.html b/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment.mbox.html new file mode 100644 --- /dev/null +++ b/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment.mbox.html @@ -0,0 +1,37 @@ + + + + + + + diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment.mbox.tree b/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment.mbox.tree new file mode 100644 --- /dev/null +++ b/mimetreeparser/autotests/data/openpgp-encrypted-no-text-attachment.mbox.tree @@ -0,0 +1,5 @@ + * MimeTreeParser::MessagePartList + * MimeTreeParser::MimeMessagePart + * MimeTreeParser::EncryptedMessagePart + * MimeTreeParser::TextMessagePart + * MimeTreeParser::MessagePart diff --git a/mimetreeparser/src/CMakeLists.txt b/mimetreeparser/src/CMakeLists.txt --- a/mimetreeparser/src/CMakeLists.txt +++ b/mimetreeparser/src/CMakeLists.txt @@ -11,6 +11,7 @@ set(libmimetreeparser_main_SRCS bodyformatter/applicationpgpencrypted.cpp bodyformatter/applicationpkcs7mime.cpp + bodyformatter/encrypted.cpp bodyformatter/mailman.cpp bodyformatter/multipartalternative.cpp bodyformatter/multipartencrypted.cpp diff --git a/mimetreeparser/src/bodyformatter/encrypted.h b/mimetreeparser/src/bodyformatter/encrypted.h new file mode 100644 --- /dev/null +++ b/mimetreeparser/src/bodyformatter/encrypted.h @@ -0,0 +1,36 @@ +/* + Copyright (c) 2017 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. +*/ + +#ifndef __MIMETREEPARSER_BODYFORAMATTER_ENCRYPTED_H__ +#define __MIMETREEPARSER_BODYFORAMATTER_ENCRYPTED_H__ + +#include "interfaces/bodypartformatter.h" +#include "interfaces/bodypart.h" + +namespace MimeTreeParser { +class EncryptedBodyPartFormatter : public Interface::BodyPartFormatter +{ + static const EncryptedBodyPartFormatter *self; +public: + MessagePartPtr process(Interface::BodyPart &part) const override; + static const Interface::BodyPartFormatter *create(); +}; +} + +#endif diff --git a/mimetreeparser/src/bodyformatter/encrypted.cpp b/mimetreeparser/src/bodyformatter/encrypted.cpp new file mode 100644 --- /dev/null +++ b/mimetreeparser/src/bodyformatter/encrypted.cpp @@ -0,0 +1,105 @@ +/* + Copyright (c) 2017 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 "encrypted.h" + +#include "utils.h" + +#include "objecttreeparser.h" +#include "messagepart.h" + +#include + +#include + +#include + +#include "mimetreeparser_debug.h" + +using namespace MimeTreeParser; + +const EncryptedBodyPartFormatter *EncryptedBodyPartFormatter::self; + +const Interface::BodyPartFormatter *EncryptedBodyPartFormatter::create() +{ + if (!self) { + self = new EncryptedBodyPartFormatter(); + } + return self; +} + +MessagePart::Ptr EncryptedBodyPartFormatter::process(Interface::BodyPart &part) const +{ + KMime::Content *node = part.content(); + + if (!node->contents().isEmpty()) { + Q_ASSERT(false); + return MessagePart::Ptr(); + } + + const QGpgME::Protocol *useThisCryptProto = nullptr; + + useThisCryptProto = QGpgME::openpgp(); + + //TODO: Load correct crypto Proto + + part.nodeHelper()->setEncryptionState(node, KMMsgFullyEncrypted); + + EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(part.objectTreeParser(), + node->decodedText(), useThisCryptProto, + part.nodeHelper()->fromAsString(node), node)); + mp->setIsEncrypted(true); + mp->setDecryptMessage(part.source()->decryptMessage()); + PartMetaData *messagePart(mp->partMetaData()); + if (!part.source()->decryptMessage()) { + part.nodeHelper()->setNodeProcessed(node, false); // Set the data node to done to prevent it from being processed + } else if (KMime::Content *newNode = part.nodeHelper()->decryptedNodeForContent(node)) { + // if we already have a decrypted node for part.objectTreeParser() encrypted node, don't do the decryption again + return MessagePart::Ptr(new MimeMessagePart(part.objectTreeParser(), newNode, true)); + } else { + const auto codec = QTextCodec::codecForName("utf-8"); + mp->startDecryption(node->decodedContent(), codec); + + qCDebug(MIMETREEPARSER_LOG) << "decrypted, signed?:" << messagePart->isSigned; + + if (!messagePart->inProgress) { + if (!messagePart->isEncrypted) { + return nullptr; + } + auto tempNode = new KMime::Content(); + qDebug() << part.nodeHelper()->codec(node)->name(); + tempNode->contentType()->setCharset("utf-8"); + tempNode->setBody(KMime::CRLFtoLF(part.nodeHelper()->codec(node)->fromUnicode(mp->text()))); + tempNode->parse(); + + if (!tempNode->head().isEmpty()) { + tempNode->contentDescription()->from7BitString("encrypted data"); + } + part.nodeHelper()->cleanExtraContent(node); + mp->clearSubParts(); + + part.nodeHelper()->attachExtraContent(node, tempNode); + + mp->parseInternal(tempNode, false); + + part.nodeHelper()->setNodeProcessed(node, false); // Set the data node to done to prevent it from being processed + } + } + return mp; +} diff --git a/mimetreeparser/src/bodypartformatter.cpp b/mimetreeparser/src/bodypartformatter.cpp --- a/mimetreeparser/src/bodypartformatter.cpp +++ b/mimetreeparser/src/bodypartformatter.cpp @@ -33,6 +33,7 @@ #include "bodyformatter/applicationpgpencrypted.h" #include "bodyformatter/applicationpkcs7mime.h" +#include "bodyformatter/encrypted.h" #include "bodyformatter/mailman.h" #include "bodyformatter/multipartalternative.h" #include "bodyformatter/multipartmixed.h" @@ -166,6 +167,7 @@ insert(QStringLiteral("application/octet-stream"), ApplicationPkcs7MimeBodyPartFormatter::create()); insert(QStringLiteral("application/octet-stream"), AnyTypeBodyPartFormatter::create()); + insert(QStringLiteral("text/pgp"), EncryptedBodyPartFormatter::create()); insert(QStringLiteral("text/html"), TextHtmlBodyPartFormatter::create()); insert(QStringLiteral("text/rtf"), AnyTypeBodyPartFormatter::create()); insert(QStringLiteral("text/plain"), MailmanBodyPartFormatter::create()); diff --git a/mimetreeparser/src/messagepart.h b/mimetreeparser/src/messagepart.h --- a/mimetreeparser/src/messagepart.h +++ b/mimetreeparser/src/messagepart.h @@ -125,6 +125,7 @@ void appendSubPart(const MessagePart::Ptr &messagePart); const QVector &subParts() const; bool hasSubParts() const; + void clearSubParts(); Interface::ObjectTreeSource *source() const; NodeHelper* nodeHelper() const; @@ -375,6 +376,7 @@ std::vector> mDecryptRecipients; friend class DefaultRendererPrivate; + friend class EncryptedBodyPartFormatter; }; class MIMETREEPARSER_EXPORT SignedMessagePart : public MessagePart diff --git a/mimetreeparser/src/messagepart.cpp b/mimetreeparser/src/messagepart.cpp --- a/mimetreeparser/src/messagepart.cpp +++ b/mimetreeparser/src/messagepart.cpp @@ -242,6 +242,11 @@ return !d->mBlocks.isEmpty(); } +void MessagePart::clearSubParts() +{ + d->mBlocks.clear(); +} + //-----MessagePartList---------------------- MessagePartList::MessagePartList(ObjectTreeParser *otp) : MessagePart(otp, QString()) @@ -1221,7 +1226,16 @@ } mDecryptRecipients.clear(); + bDecryptionOk = !decryptResult.error(); + +// std::stringstream ss; +// ss << decryptResult << '\n' << verifyResult; +// qCDebug(MIMETREEPARSER_LOG) << ss.str().c_str(); + for (const auto &recipient : decryptResult.recipients()) { + if (!recipient.status()) { + bDecryptionOk = true; + } GpgME::Key key; QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false, false, false); // local, no sigs if (!job) { @@ -1246,19 +1260,15 @@ } mDecryptRecipients.push_back(std::make_pair(recipient, key)); } - bDecryptionOk = !decryptResult.error(); -// std::stringstream ss; -// ss << decryptResult << '\n' << verifyResult; -// qCDebug(MIMETREEPARSER_LOG) << ss.str().c_str(); if (!bDecryptionOk && partMetaData()->isSigned) { //Only a signed part partMetaData()->isEncrypted = false; bDecryptionOk = true; mDecryptedData = plainText; } else { mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY; - partMetaData()->isEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA; + partMetaData()->isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA; partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString()); if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) { partMetaData()->keyId = decryptResult.recipient(0).keyID(); diff --git a/mimetreeparser/src/nodehelper.h b/mimetreeparser/src/nodehelper.h --- a/mimetreeparser/src/nodehelper.h +++ b/mimetreeparser/src/nodehelper.h @@ -87,6 +87,8 @@ /** Attach an extra node to an existing node */ void attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content); + void cleanExtraContent(KMime::Content *topLevelNode); + /** Get the extra nodes attached to the @param topLevelNode and all sub-nodes of @param topLevelNode */ QList extraContents(KMime::Content *topLevelNode) const; diff --git a/mimetreeparser/src/nodehelper.cpp b/mimetreeparser/src/nodehelper.cpp --- a/mimetreeparser/src/nodehelper.cpp +++ b/mimetreeparser/src/nodehelper.cpp @@ -815,6 +815,12 @@ mExtraContents[topLevelNode].append(content); } +void NodeHelper::cleanExtraContent(KMime::Content *topLevelNode) +{ + qCDebug(MIMETREEPARSER_LOG) << "remove all extraContents for" << topLevelNode; + mExtraContents[topLevelNode].clear(); +} + QList< KMime::Content * > NodeHelper::extraContents(KMime::Content *topLevelnode) const { return mExtraContents.value(topLevelnode);