diff --git a/framework/src/domain/mime/mimetreeparser/bodypartformatter_impl.cpp b/framework/src/domain/mime/mimetreeparser/bodypartformatter_impl.cpp index 532a906e..a6713fef 100644 --- a/framework/src/domain/mime/mimetreeparser/bodypartformatter_impl.cpp +++ b/framework/src/domain/mime/mimetreeparser/bodypartformatter_impl.cpp @@ -1,145 +1,160 @@ /* -*- c++ -*- bodypartformatter.cpp This file is part of KMail, the KDE mail client. Copyright (c) 2003 Marc Mutz 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. */ #include "mimetreeparser_debug.h" #include "applicationpgpencrypted.h" #include "applicationpkcs7mime.h" #include "mailman.h" #include "multipartalternative.h" #include "multipartmixed.h" #include "multipartencrypted.h" #include "multipartsigned.h" #include "texthtml.h" #include "textplain.h" #include "bodypartformatter.h" #include "bodypart.h" #include "bodypartformatterbasefactory.h" #include "bodypartformatterbasefactory_p.h" #include "objecttreeparser.h" #include "messagepart.h" #include using namespace MimeTreeParser; namespace { class AnyTypeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { static const AnyTypeBodyPartFormatter *self; public: static const MimeTreeParser::Interface::BodyPartFormatter *create() { if (!self) { self = new AnyTypeBodyPartFormatter(); } return self; } }; const AnyTypeBodyPartFormatter *AnyTypeBodyPartFormatter::self = nullptr; class ImageTypeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { static const ImageTypeBodyPartFormatter *self; public: static const MimeTreeParser::Interface::BodyPartFormatter *create() { if (!self) { self = new ImageTypeBodyPartFormatter(); } return self; } }; const ImageTypeBodyPartFormatter *ImageTypeBodyPartFormatter::self = nullptr; class MessageRfc822BodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { static const MessageRfc822BodyPartFormatter *self; public: - MessagePart::Ptr process(Interface::BodyPart &) const Q_DECL_OVERRIDE; - static const MimeTreeParser::Interface::BodyPartFormatter *create(); + MessagePart::Ptr process(Interface::BodyPart &part) const Q_DECL_OVERRIDE + { + return MessagePart::Ptr(new EncapsulatedRfc822MessagePart(part.objectTreeParser(), part.content(), part.content()->bodyAsMessage())); + } + + static const MimeTreeParser::Interface::BodyPartFormatter *create() + { + if (!self) { + self = new MessageRfc822BodyPartFormatter(); + } + return self; + } }; const MessageRfc822BodyPartFormatter *MessageRfc822BodyPartFormatter::self; -const MimeTreeParser::Interface::BodyPartFormatter *MessageRfc822BodyPartFormatter::create() +class HeadersBodyPartFormatter + : public MimeTreeParser::Interface::BodyPartFormatter { - if (!self) { - self = new MessageRfc822BodyPartFormatter(); + static const HeadersBodyPartFormatter *self; +public: + MessagePart::Ptr process(Interface::BodyPart &part) const Q_DECL_OVERRIDE + { + return MessagePart::Ptr(new HeadersPart(part.objectTreeParser(), part.content())); } - return self; -} -MessagePart::Ptr MessageRfc822BodyPartFormatter::process(Interface::BodyPart &part) const -{ - const KMime::Message::Ptr message = part.content()->bodyAsMessage(); - return MessagePart::Ptr(new EncapsulatedRfc822MessagePart(part.objectTreeParser(), part.content(), message)); -} + static const MimeTreeParser::Interface::BodyPartFormatter *create() { + if (!self) { + self = new HeadersBodyPartFormatter(); + } + return self; + } +}; -typedef TextPlainBodyPartFormatter ApplicationPgpBodyPartFormatter; +const HeadersBodyPartFormatter *HeadersBodyPartFormatter::self = nullptr; } // anon namespace void BodyPartFormatterBaseFactoryPrivate::messageviewer_create_builtin_bodypart_formatters() { insert("application", "octet-stream", AnyTypeBodyPartFormatter::create()); - insert("application", "pgp", ApplicationPgpBodyPartFormatter::create()); + insert("application", "pgp", TextPlainBodyPartFormatter::create()); insert("application", "pkcs7-mime", ApplicationPkcs7MimeBodyPartFormatter::create()); insert("application", "x-pkcs7-mime", ApplicationPkcs7MimeBodyPartFormatter::create()); insert("application", "pgp-encrypted", ApplicationPGPEncryptedBodyPartFormatter::create()); insert("application", "*", AnyTypeBodyPartFormatter::create()); insert("text", "html", TextHtmlBodyPartFormatter::create()); insert("text", "rtf", AnyTypeBodyPartFormatter::create()); insert("text", "plain", MailmanBodyPartFormatter::create()); insert("text", "plain", TextPlainBodyPartFormatter::create()); + insert("text", "rfc822-headers", HeadersBodyPartFormatter::create()); insert("text", "*", MailmanBodyPartFormatter::create()); insert("text", "*", TextPlainBodyPartFormatter::create()); insert("image", "*", ImageTypeBodyPartFormatter::create()); insert("message", "rfc822", MessageRfc822BodyPartFormatter::create()); insert("message", "*", AnyTypeBodyPartFormatter::create()); insert("multipart", "alternative", MultiPartAlternativeBodyPartFormatter::create()); insert("multipart", "encrypted", MultiPartEncryptedBodyPartFormatter::create()); insert("multipart", "signed", MultiPartSignedBodyPartFormatter::create()); insert("multipart", "*", MultiPartMixedBodyPartFormatter::create()); insert("*", "*", AnyTypeBodyPartFormatter::create()); } diff --git a/framework/src/domain/mime/mimetreeparser/messagepart.cpp b/framework/src/domain/mime/mimetreeparser/messagepart.cpp index 541efd7f..17719ff8 100644 --- a/framework/src/domain/mime/mimetreeparser/messagepart.cpp +++ b/framework/src/domain/mime/mimetreeparser/messagepart.cpp @@ -1,1162 +1,1172 @@ /* 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; 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(); if (decryptResult.error() && mMetaData.isSigned) { //Only a signed part mMetaData.isEncrypted = false; mDecryptedData = plainText; return true; } if (mMetaData.isEncrypted && decryptResult.numRecipients() > 0) { mMetaData.keyId = decryptResult.recipient(0).keyID(); } if (!decryptResult.error()) { mDecryptedData = plainText; setText(QString::fromUtf8(mDecryptedData.constData())); } else { 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; } 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 {}; } + +HeadersPart::HeadersPart(ObjectTreeParser *otp, KMime::Content *node) + : MessagePart(otp, QString(), node) +{ +} + +HeadersPart::~HeadersPart() +{ + +} diff --git a/framework/src/domain/mime/mimetreeparser/messagepart.h b/framework/src/domain/mime/mimetreeparser/messagepart.h index 1d416e8f..c039637a 100644 --- a/framework/src/domain/mime/mimetreeparser/messagepart.h +++ b/framework/src/domain/mime/mimetreeparser/messagepart.h @@ -1,380 +1,389 @@ /* Copyright (c) 2015 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_MESSAGEPART_H__ #define __MIMETREEPARSER_MESSAGEPART_H__ #include "util.h" #include "enums.h" #include "partmetadata.h" #include #include #include #include #include #include class QTextCodec; class PartPrivate; namespace GpgME { class ImportResult; } namespace KMime { class Content; } namespace MimeTreeParser { class ObjectTreeParser; class HTMLBlock; typedef QSharedPointer HTMLBlockPtr; class CryptoBodyPartMemento; class MultiPartAlternativeBodyPartFormatter; class SignedMessagePart; class EncryptedMessagePart; class MessagePart : public QObject { Q_OBJECT Q_PROPERTY(bool attachment READ isAttachment) Q_PROPERTY(bool root READ isRoot) Q_PROPERTY(bool isHtml READ isHtml) Q_PROPERTY(QString plaintextContent READ plaintextContent) Q_PROPERTY(QString htmlContent READ htmlContent) public: enum Disposition { Inline, Attachment, Invalid }; typedef QSharedPointer Ptr; MessagePart(ObjectTreeParser *otp, const QString &text, KMime::Content *node = nullptr); virtual ~MessagePart(); virtual QString text() const; void setText(const QString &text); virtual bool isAttachment() const; void setIsRoot(bool root); bool isRoot() const; void setParentPart(MessagePart *parentPart); MessagePart *parentPart() const; virtual QString plaintextContent() const; virtual QString htmlContent() const; virtual bool isHtml() const; QByteArray mimeType() const; QByteArray charset() const; QString filename() const; Disposition disposition() const; bool isText() const; enum Error { NoError = 0, PassphraseError, NoKeyError, UnknownError }; Error error() const; QString errorString() const; PartMetaData *partMetaData(); void appendSubPart(const MessagePart::Ptr &messagePart); const QVector &subParts() const; bool hasSubParts() const; KMime::Content *node() const; virtual KMMsgSignatureState signatureState() const; virtual KMMsgEncryptionState encryptionState() const; QVector signatures() const; QVector encryptions() const; void bindLifetime(KMime::Content *); protected: void parseInternal(KMime::Content *node, bool onlyOneMimePart); QString renderInternalText() const; QString mText; ObjectTreeParser *mOtp; PartMetaData mMetaData; MessagePart *mParentPart; KMime::Content *mNode; QVector mNodesToDelete; Error mError; private: QVector mBlocks; bool mRoot; }; class MimeMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; MimeMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart); virtual ~MimeMessagePart(); QString text() const Q_DECL_OVERRIDE; QString plaintextContent() const Q_DECL_OVERRIDE; QString htmlContent() const Q_DECL_OVERRIDE; private: friend class AlternativeMessagePart; friend class ::PartPrivate; }; class MessagePartList : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; MessagePartList(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node = nullptr); virtual ~MessagePartList(); QString text() const Q_DECL_OVERRIDE; QString plaintextContent() const Q_DECL_OVERRIDE; QString htmlContent() const Q_DECL_OVERRIDE; }; enum IconType { NoIcon = 0, IconExternal, IconInline }; class TextMessagePart : public MessagePartList { Q_OBJECT public: typedef QSharedPointer Ptr; TextMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node); virtual ~TextMessagePart(); KMMsgSignatureState signatureState() const Q_DECL_OVERRIDE; KMMsgEncryptionState encryptionState() const Q_DECL_OVERRIDE; private: void parseContent(); KMMsgSignatureState mSignatureState; KMMsgEncryptionState mEncryptionState; friend class DefaultRendererPrivate; friend class ObjectTreeParser; friend class ::PartPrivate; }; class AttachmentMessagePart : public TextMessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; AttachmentMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node); virtual ~AttachmentMessagePart(); virtual bool isAttachment() const Q_DECL_OVERRIDE { return true; } }; class HtmlMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; HtmlMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node); virtual ~HtmlMessagePart(); QString text() const Q_DECL_OVERRIDE; bool isHtml() const Q_DECL_OVERRIDE; private: QString mBodyHTML; QByteArray mCharset; friend class DefaultRendererPrivate; friend class ::PartPrivate; }; class AlternativeMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; AlternativeMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node); virtual ~AlternativeMessagePart(); QString text() const Q_DECL_OVERRIDE; bool isHtml() const Q_DECL_OVERRIDE; QString plaintextContent() const Q_DECL_OVERRIDE; QString htmlContent() const Q_DECL_OVERRIDE; QList availableModes(); private: QMap mChildNodes; QMap mChildParts; friend class DefaultRendererPrivate; friend class ObjectTreeParser; friend class MultiPartAlternativeBodyPartFormatter; friend class ::PartPrivate; }; class CertMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; CertMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const GpgME::Protocol cryptoProto); virtual ~CertMessagePart(); QString text() const Q_DECL_OVERRIDE; void import(); private: const GpgME::Protocol mCryptoProto; friend class DefaultRendererPrivate; }; class EncapsulatedRfc822MessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; EncapsulatedRfc822MessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message); virtual ~EncapsulatedRfc822MessagePart(); QString text() const Q_DECL_OVERRIDE; QString from() const; QDateTime date() const; private: const KMime::Message::Ptr mMessage; friend class DefaultRendererPrivate; }; class EncryptedMessagePart : public MessagePart { Q_OBJECT Q_PROPERTY(bool isEncrypted READ isEncrypted) public: typedef QSharedPointer Ptr; EncryptedMessagePart(ObjectTreeParser *otp, const QString &text, const GpgME::Protocol protocol, const QString &fromAddress, KMime::Content *node, KMime::Content *encryptedNode = nullptr); virtual ~EncryptedMessagePart(); QString text() const Q_DECL_OVERRIDE; void setIsEncrypted(bool encrypted); bool isEncrypted() const; bool isDecryptable() const; void startDecryption(const QByteArray &text, const QTextCodec *aCodec); void startDecryption(KMime::Content *data = nullptr); QByteArray mDecryptedData; QString plaintextContent() const Q_DECL_OVERRIDE; QString htmlContent() const Q_DECL_OVERRIDE; private: /** Handles the dectyptioon of a given content * returns true if the decryption was successfull * if used in async mode, check if mMetaData.inProgress is true, it inicates a running decryption process. */ bool okDecryptMIME(KMime::Content &data); protected: const GpgME::Protocol mProtocol; QString mFromAddress; QByteArray mVerifiedText; std::vector mDecryptRecipients; KMime::Content *mEncryptedNode; friend class DefaultRendererPrivate; friend class ::PartPrivate; }; class SignedMessagePart : public MessagePart { Q_OBJECT Q_PROPERTY(bool isSigned READ isSigned) public: typedef QSharedPointer Ptr; SignedMessagePart(ObjectTreeParser *otp, const QString &text, const GpgME::Protocol protocol, const QString &fromAddress, KMime::Content *node, KMime::Content *signedData); virtual ~SignedMessagePart(); void setIsSigned(bool isSigned); bool isSigned() const; void startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature); void startVerification(); QByteArray mDecryptedData; QString plaintextContent() const Q_DECL_OVERRIDE; QString htmlContent() const Q_DECL_OVERRIDE; private: void sigStatusToMetaData(const GpgME::Signature &signature); void setVerificationResult(const GpgME::VerificationResult &result, bool parseText, const QByteArray &plainText); protected: GpgME::Protocol mProtocol; QString mFromAddress; KMime::Content *mSignedData; friend EncryptedMessagePart; friend class DefaultRendererPrivate; friend class ::PartPrivate; }; +class HeadersPart : public MessagePart +{ + Q_OBJECT +public: + typedef QSharedPointer Ptr; + HeadersPart(ObjectTreeParser *otp, KMime::Content *node); + virtual ~HeadersPart(); +}; + } #endif //__MIMETREEPARSER_MESSAGEPART_H__ diff --git a/framework/src/domain/mime/mimetreeparser/tests/mimetreeparsertest.cpp b/framework/src/domain/mime/mimetreeparser/tests/mimetreeparsertest.cpp index 961dbf9a..90a07a7c 100644 --- a/framework/src/domain/mime/mimetreeparser/tests/mimetreeparsertest.cpp +++ b/framework/src/domain/mime/mimetreeparser/tests/mimetreeparsertest.cpp @@ -1,377 +1,392 @@ /* 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 QByteArray readMailFromFile(const QString &mailFile) { QFile file(QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile); file.open(QIODevice::ReadOnly); Q_ASSERT(file.isOpen()); return file.readAll(); } class InterfaceTest : public QObject { Q_OBJECT private slots: void testTextMail() { const auto expectedText = QStringLiteral("If you can see this text it means that your email client couldn't display our newsletter properly.\nPlease visit this link to view the newsletter on our website: http://www.gog.com/newsletter/"); MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("plaintext.mbox")); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QCOMPARE(part->text(), expectedText); QCOMPARE(part->charset(), QStringLiteral("utf-8").toLocal8Bit()); QCOMPARE(part->encryptions().size(), 0); QCOMPARE(part->signatures().size(), 0); QCOMPARE(otp.collectAttachmentParts().size(), 0); QCOMPARE(otp.plainTextContent(), expectedText); QVERIFY(otp.htmlContent().isEmpty()); } void testAlternative() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("alternative.mbox")); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QVERIFY(bool(part)); QCOMPARE(part->plaintextContent(), QStringLiteral("If you can see this text it means that your email client couldn't display our newsletter properly.\nPlease visit this link to view the newsletter on our website: http://www.gog.com/newsletter/\n")); //FIXME html charset is different from plain, and both are not ISO-8859-1 QCOMPARE(part->charset(), QStringLiteral("ISO-8859-1").toLocal8Bit()); QCOMPARE(part->htmlContent(), QStringLiteral("

HTML text

\n\n")); QCOMPARE(otp.collectAttachmentParts().size(), 0); QCOMPARE(part->encryptions().size(), 0); QCOMPARE(part->signatures().size(), 0); } void testTextHtml() { auto expectedText = QStringLiteral("

HTML text

"); MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("html.mbox")); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QVERIFY(bool(part)); QCOMPARE(part->htmlContent(), expectedText); QCOMPARE(part->charset(), QStringLiteral("windows-1252").toLocal8Bit()); QCOMPARE(part->encryptions().size(), 0); QCOMPARE(part->signatures().size(), 0); auto contentAttachmentList = otp.collectAttachmentParts(); QCOMPARE(contentAttachmentList.size(), 0); QCOMPARE(otp.htmlContent(), expectedText); QVERIFY(otp.plainTextContent().isEmpty()); } void testSMimeEncrypted() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("smime-encrypted.mbox")); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("The quick brown fox jumped over the lazy dog.")); QCOMPARE(part->charset(), QStringLiteral("us-ascii").toLocal8Bit()); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->signatures().size(), 0); auto contentAttachmentList = otp.collectAttachmentParts(); QCOMPARE(contentAttachmentList.size(), 0); } void testOpenPGPEncryptedAttachment() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("test text")); QCOMPARE(part->charset(), QStringLiteral("us-ascii").toLocal8Bit()); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->signatures().size(), 1); QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted); QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned); auto contentAttachmentList = otp.collectAttachmentParts(); QCOMPARE(contentAttachmentList.size(), 2); // QCOMPARE(contentAttachmentList[0]->availableContents(), QVector() << "text/plain"); // QCOMPARE(contentAttachmentList[0]->content().size(), 1); QCOMPARE(contentAttachmentList[0]->encryptions().size(), 1); QCOMPARE(contentAttachmentList[0]->signatures().size(), 1); QCOMPARE(contentAttachmentList[0]->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted); QCOMPARE(contentAttachmentList[0]->signatureState(), MimeTreeParser::KMMsgFullySigned); // QCOMPARE(contentAttachmentList[1]->availableContents(), QVector() << "image/png"); // QCOMPARE(contentAttachmentList[1]->content().size(), 1); QCOMPARE(contentAttachmentList[1]->encryptions().size(), 0); QCOMPARE(contentAttachmentList[1]->signatures().size(), 0); QCOMPARE(contentAttachmentList[1]->encryptionState(), MimeTreeParser::KMMsgNotEncrypted); QCOMPARE(contentAttachmentList[1]->signatureState(), MimeTreeParser::KMMsgNotSigned); } void testOpenPGPInline() { 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].dynamicCast(); QVERIFY(bool(part)); QCOMPARE(part->charset(), QStringLiteral("ISO-8859-1").toLocal8Bit()); QCOMPARE(part->text(), QString::fromUtf8("asdasd asd asd asdf sadf sdaf sadf öäü")); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->signatures().size(), 1); QCOMPARE(otp.collectAttachmentParts().size(), 0); } void testOpenPPGInlineWithNonEncText() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("openpgp-inline-encrypted+nonenc.mbox")); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part1 = partList[0].dynamicCast(); QVERIFY(bool(part1)); QCOMPARE(part1->text(), QStringLiteral("Not encrypted not signed :(\n\nsome random text")); //TODO test if we get the proper subparts with the appropriate encryptions QCOMPARE(part1->charset(), QStringLiteral("us-ascii").toLocal8Bit()); QCOMPARE(part1->encryptionState(), MimeTreeParser::KMMsgPartiallyEncrypted); QCOMPARE(part1->signatureState(), MimeTreeParser::KMMsgNotSigned); // QCOMPARE(part1->text(), QStringLiteral("Not encrypted not signed :(\n\n")); // QCOMPARE(part1->charset(), QStringLiteral("us-ascii").toLocal8Bit()); // QCOMPARE(contentList[1]->content(), QStringLiteral("some random text").toLocal8Bit()); // QCOMPARE(contentList[1]->charset(), QStringLiteral("us-ascii").toLocal8Bit()); // QCOMPARE(contentList[1]->encryptions().size(), 1); // QCOMPARE(contentList[1]->signatures().size(), 0); QCOMPARE(otp.collectAttachmentParts().size(), 0); } void testEncryptionBlock() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part1 = partList[0].dynamicCast(); QVERIFY(bool(part1)); QCOMPARE(part1->encryptions().size(), 1); // auto enc = contentList[0]->encryptions()[0]; // QCOMPARE((int) enc->recipients().size(), 2); // auto r = enc->recipients()[0]; // QCOMPARE(r->keyid(),QStringLiteral("14B79E26050467AA")); // QCOMPARE(r->name(),QStringLiteral("kdetest")); // QCOMPARE(r->email(),QStringLiteral("you@you.com")); // QCOMPARE(r->comment(),QStringLiteral("")); // r = enc->recipients()[1]; // QCOMPARE(r->keyid(),QStringLiteral("8D9860C58F246DE6")); // QCOMPARE(r->name(),QStringLiteral("unittest key")); // QCOMPARE(r->email(),QStringLiteral("test@kolab.org")); // QCOMPARE(r->comment(),QStringLiteral("no password")); auto attachmentList = otp.collectAttachmentParts(); QCOMPARE(attachmentList.size(), 2); auto attachment1 = attachmentList[0]; QVERIFY(attachment1->node()); QCOMPARE(attachment1->filename(), QStringLiteral("file.txt")); auto attachment2 = attachmentList[1]; QVERIFY(attachment2->node()); QCOMPARE(attachment2->filename(), QStringLiteral("image.png")); } void testSignatureBlock() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QVERIFY(bool(part)); // QCOMPARE(contentList[0]->signatures().size(), 1); // auto sig = contentList[0]->signatures()[0]; // QCOMPARE(sig->creationDateTime(), QDateTime(QDate(2015,05,01),QTime(15,12,47))); // QCOMPARE(sig->expirationDateTime(), QDateTime()); // QCOMPARE(sig->neverExpires(), true); // auto key = sig->key(); // QCOMPARE(key->keyid(),QStringLiteral("8D9860C58F246DE6")); // QCOMPARE(key->name(),QStringLiteral("unittest key")); // QCOMPARE(key->email(),QStringLiteral("test@kolab.org")); // QCOMPARE(key->comment(),QStringLiteral("no password")); } void testRelatedAlternative() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("cid-links.mbox")); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QVERIFY(bool(part)); QCOMPARE(part->encryptions().size(), 0); QCOMPARE(part->signatures().size(), 0); QCOMPARE(otp.collectAttachmentParts().size(), 0); } void testAttachmentPart() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("attachment.mbox")); otp.print(); auto partList = otp.collectAttachmentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QVERIFY(bool(part)); auto att = part->node(); qWarning() << "Attachment type: " << att->contentType(true)->mimeType(); QCOMPARE(part->mimeType(), QByteArray("image/jpeg")); } void testCidLink() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("cid-links.mbox")); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QVERIFY(bool(part)); auto resolvedContent = otp.resolveCidLinks(part->htmlContent()); QVERIFY(!resolvedContent.contains("cid:")); } void testCidLinkInForwardedInline() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("cid-links-forwarded-inline.mbox")); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QVERIFY(bool(part)); auto resolvedContent = otp.resolveCidLinks(part->htmlContent()); QVERIFY(!resolvedContent.contains("cid:")); } void testOpenPGPInlineError() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("inlinepgpgencrypted-error.mbox")); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QVERIFY(bool(part)); QVERIFY(part->error()); } void testEncapsulated() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("encapsulated-with-attachment.mbox")); otp.decryptParts(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 2); auto part = partList[1].dynamicCast(); QVERIFY(bool(part)); QCOMPARE(part->from(), QLatin1String("Thomas McGuire ")); QCOMPARE(part->date().toString(), QLatin1String("Wed Aug 5 10:57:58 2009 GMT+0200")); auto subPartList = otp.collectContentParts(part); QCOMPARE(subPartList.size(), 1); qWarning() << subPartList[0]->metaObject()->className(); auto subPart = subPartList[0].dynamicCast(); QVERIFY(bool(subPart)); } void test8bitEncodedInPlaintext() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("8bitencoded.mbox")); QVERIFY(otp.plainTextContent().contains(QString::fromUtf8("Why Pisa’s Tower"))); QVERIFY(otp.htmlContent().contains(QString::fromUtf8("Why Pisa’s Tower"))); } void testInlineSigned() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("openpgp-inline-signed.mbox")); otp.decryptParts(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QCOMPARE(part->signatures().size(), 1); QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgNotEncrypted); QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned); } void testEncryptedAndSigned() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile("openpgp-encrypted+signed.mbox")); otp.decryptParts(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast(); QCOMPARE(part->signatures().size(), 1); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted); QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned); QVERIFY(otp.plainTextContent().contains(QString::fromUtf8("encrypted message text"))); } + + void testOpenpgpMultipartEmbedded() + { + MimeTreeParser::ObjectTreeParser otp; + otp.parseObjectTree(readMailFromFile("openpgp-multipart-embedded.mbox")); + otp.print(); + otp.decryptParts(); + otp.print(); + auto partList = otp.collectContentParts(); + QCOMPARE(partList.size(), 1); + auto part = partList[0].dynamicCast(); + QCOMPARE(part->encryptions().size(), 1); + QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted); + QCOMPARE(otp.plainTextContent(), QString::fromUtf8("sdflskjsdf\n\n-- \nThis is a HTML signature.\n")); + } }; QTEST_GUILESS_MAIN(InterfaceTest) #include "mimetreeparsertest.moc" diff --git a/framework/src/domain/mime/testdata/openpgp-multipart-embedded.mbox b/framework/src/domain/mime/testdata/openpgp-multipart-embedded.mbox new file mode 100644 index 00000000..d5fc4ca2 --- /dev/null +++ b/framework/src/domain/mime/testdata/openpgp-multipart-embedded.mbox @@ -0,0 +1,76 @@ +To: test@kolab.org +From: test1 +Openpgp: preference=signencrypt +Autocrypt: addr=test1@kolab.org; prefer-encrypt=mutual; keydata= + xsBNBFrd3FcBCADAGsU4BTBJ7nXGZ2IV6ZpSDcuZeCteDdrp9YNGYdax49Z9rn2RIfMur07i + A3zkLuQSI63QpWr/sp8Kkcs+OMRfeNAHSyQcdOwE/SU/PF45LHYcgvdgL7bvNRiyrn++eXar + woCH/QNZFgsy4PQCBW4DXor4jeDgeKisdk+ArpSY+Dd5l+Gdna8GQyUHWOL15gKUEDBq9dta + fswI18LxTSIbofsSxnQ0BgHNgn4bFXRLWiFJMyjvXXwZAXmChpxchESeIyu3Bvu38kA+jNDh + QmJL359mbXtu6JtvUjL0T9xhydHhGQ8iHCUpUkAh50KDJUUFQAUD+wSeMbsr7dk/osBdABEB + AAHNHVRlc3QxIEtvbGFiIDx0ZXN0MUBrb2xhYi5vcmc+wsCOBBMBCAA4FiEEy9EWSF25Vgyj + zZHgLjt3h7G3WSAFAlrd3FcCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQLjt3h7G3 + WSBwjAf6A9D/BOo6ixWfrTAfunk06TIt309P33/aIrhmICUoJ3KxatnMfcFr9GDnVtGv7RZl + VG43dWLMz8ghjGVjWOQUzlYl19HS/7KfHFE3jQHawcidYHcsVBbD/Ml9lCRnE4CcO/7MqObh + FHVIO+M0XJK82Qqu1kZ2ySNOMG+DJ3JGMEWMwrf8HjdwfANCn9uW4G9+q3dyN7TWUaYVLf74 + Ekz2KpCxi0Y/b/01+8UR6iSRbn+5R2CIPhgdrrfYlHaB1HHeeJlue8JK3zxYcRTfD4B2c8Jj + UAMO0Y8A7PMTj9SpnBt+t4XUyrTwwqLwulRBAQBHSPI7jQ6JVM2yTOe7Yy1dbc7ATQRa3dxX + AQgAy/RH8KEQHk2SJ60G0YJkOSioGNU+sAMP6rKIH8AXljZEwriiWqxcunoHlrOHOfuyBBL2 + TTiien0247g7q2i7aPBZhll7/cp4DZKcsLZ9QIkB4sHY1SaVT865dIfhoxcKwi8NzbemDsUQ + QB6f9P3CSZqFW2EohzKf3vfYnLVLTRvzlndAzThYXso1iWeX9v1JtzOi4w7JP5RvTMx+FXCy + ibfkLGJL2ZG6rapK7jtn8SfJGiQeH441OmEDjtfqrADKDY/pKJdPeEzakuZuxhrh8FtFjtZX + WU9dbzE46uXUA/ChLW0H/zuyxseVCHyAbF5pFyibGfk1hmYFVwjQotHojwARAQABwsB2BBgB + CAAgFiEEy9EWSF25VgyjzZHgLjt3h7G3WSAFAlrd3FcCGwwACgkQLjt3h7G3WSDJrAgApMbl + U4PAoe04jVSIMApU8Q22NNysZGS3l6KLpOqS/1DXd2U0S6EbFcCmUUwu3tk6nz6qCBKu7SEe + E1bjJUSB5880IA3C8HC1VDHCdVMJGJKvpqnj/4Jf8STQROL7UeQXZvby5CRZGMV7lBDAquG6 + vk3Eo3CeAfKel+XtHA/qruayTVHn1PV3/22ThcnXkwB3Mpdflz/tmPwHZqwOPavpJMckE03K + HNzSulXDNXdGqd4kKu43UIYnfm/3jnfK/LOrOK8zl40mk26Vm19MMRgc2TP6a70J5r6H1RLU + BTEkoVskD4m6I1G4FEIPq1g8jv7e0zcukh3vdpdKKxR+69M/2w== +Message-ID: <9af7e819-eb6c-d243-cd2d-b04d23ba2c0d@kolab.org> +Date: Mon, 23 Apr 2018 17:11:58 +0200 +User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 + Thunderbird/52.7.0 +MIME-Version: 1.0 +Subject: Encrypted Message +Content-Type: multipart/encrypted; + protocol="application/pgp-encrypted"; + boundary="EjqoZxTNU00i9s539J9SoOOl3OuK0xUCc" + +This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156) +--EjqoZxTNU00i9s539J9SoOOl3OuK0xUCc +Content-Type: application/pgp-encrypted +Content-Description: PGP/MIME version identification + +Version: 1 + +--EjqoZxTNU00i9s539J9SoOOl3OuK0xUCc +Content-Type: application/octet-stream; name="encrypted.asc" +Content-Description: OpenPGP encrypted message +Content-Disposition: inline; filename="encrypted.asc" + +-----BEGIN PGP MESSAGE----- + +hQEMAwzOQ1qnzNo7AQf/QznYpLuJ+BB9BBoYFjISqGek8blRD8UVO96E8yYSeAXL +uILJUakYjykzwg+Yd+yjR1arye1BYEnrpftXVR//WdkitPsq2nzxkHynCJCiMsl8 +LIx2u8OoAXJa5L0ohERKt/MAJn6H+x2+WxVzIGtaOw7K7rBL+J800wB6KkxKdvlF +rFkfmlfJOKueOPUVzvQHJBLJXcdKH5z8GikESKrRGWpTn5DdFIG2nmxuMhNndcrd +6oXZ11PMKr3QTrjG7huntvASQQ9Vd3Kxvi8kqaJIdspRckoZd65Xo+UdC+VWFYH8 +ynsWX3F3zqRxHhK7IZgaTLHRc55NzdXLmtQ5zVt6pIUBDAPKpRg2CPD7UAEIALbc +/NnS49ghW+fN7RkIwHyXX/kcZk/+87ydT6Tvh3xdcgZY9Aczwcx64kvP4M5PXaaT +xxsXK0vLtk19vGJEeMzHGHacp1QQzZwCRa4Pmi2cyiK+J5TpF8RapZflLwIQ+zWk +WRSa6jpd5SzQvEQ8t8OQ9PYOEhNYZLHkZCRK3bwY7jW1RzqnWgHbHk8M70YzBZw4 +RMLwNuNfYw25TJFxnlhPUiN56z0ZsNOj16TTyINLI1pCLG8boKfEzCCMyIHZpU+U +4DGM9ziLA/gH1MYcONPogwi24pUXW2ssoT5bIfNFGhT8DAZYAwyHeJnrCbEdcfp5 +CepYtnkH1Gfz1KQ66oHSwNUBqvtjoE5wA+EYxoTopJ5/g5qtcfsYh9o6ksepFjZb ++rzhzI8XtGs43+YE1QrOx9N7E/XiG1iXTqeqyKfq6vGgfE4tfgZlRcnI1sllwX2X +/2Va7aWr7VRt2DJ444XKRtmwP2U47lqM0SEGb01SOI2WBfxB0lAsetNS7t83KoCb +W/JHj9zEpLEiGr8lrI+8ydXlfFA4nTlugwJP2o42fU9EMDzieUFfaX7wzKKXQq8R +ek04j5PcJSeRBr5/HyrAR3QUoB8qL/9KqOxmcW/aKLteshYWuDWRhDYaTIsLJV6p +Or4+8qV5bonBZHuRXDOt3qxWd0++msUqj8Kkw0cZ5ID7EsQFdYBapxDz7ZKj5PM6 +AEmzYarsM9FKsVMx7KMxIpkOOEWXN6/jrhIO1rkk4WQvl79BW9gj+6DnBwpM4R/k +bnuWedSvbL/4ee21XWf4p01aF+RHn1UbvOqkfwX69M00crE7PnNaeMbKy46bcELF +OTNGkv7SYoUmctTINyV3zPWxJnsIkrSFurKXF/+6tSnaItU1ziM= +=L0oI +-----END PGP MESSAGE----- + +--EjqoZxTNU00i9s539J9SoOOl3OuK0xUCc-- +