From 4f006ff552a5ecf4550554d53ece8f4e9c1b9dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Nicole?= Date: Thu, 8 Mar 2018 12:09:25 +0100 Subject: Support encrypted mails forwarding Summary: Some notes: - What we do is: if the mail is encrypted, decrypt it and copy its content into a new message (with plaintext, html and attachments, if any), and use this message as attachment for forwarding - The `isEncrypted` function from KMime doesn't seem to detect every kind of encrypted mails. AFAIK this structure is not detected: - `multipart/mixed` - `text/plain` - `application/pgp-encrypted` (attachement, named "ATT00001") - `application/octet-stream` (attachment named "encrypted.asc") Reviewers: cmollekopf Tags: PHID-PROJ-6npnfcmppynqynn7slmv Maniphest Tasks: T8112, T7024 Differential Revision: https://phabricator.kde.org/D10966 --- framework/src/domain/composercontroller.cpp | 21 +- framework/src/domain/mime/mailtemplates.cpp | 82 ++++- .../mime/mimetreeparser/tests/CMakeLists.txt | 2 +- .../mime/mimetreeparser/tests/interfacetest.cpp | 377 --------------------- .../mimetreeparser/tests/mimetreeparsertest.cpp | 377 +++++++++++++++++++++ .../src/domain/mime/tests/mailtemplatetest.cpp | 49 +++ 6 files changed, 513 insertions(+), 395 deletions(-) delete mode 100644 framework/src/domain/mime/mimetreeparser/tests/interfacetest.cpp create mode 100644 framework/src/domain/mime/mimetreeparser/tests/mimetreeparsertest.cpp diff --git a/framework/src/domain/composercontroller.cpp b/framework/src/domain/composercontroller.cpp index 09d4c154..2286a71b 100644 --- a/framework/src/domain/composercontroller.cpp +++ b/framework/src/domain/composercontroller.cpp @@ -99,19 +99,19 @@ class AddresseeController : public Kube::ListPropertyController public: bool mFoundAllKeys = true; + QSet mMissingKeys; - AddresseeController() - : Kube::ListPropertyController{{"name", "keyFound", "key"}} + AddresseeController() : Kube::ListPropertyController{{"name", "keyFound", "key"}} { - QObject::connect(this, &Kube::ListPropertyController::added, this, [this] (const QByteArray &id, const QVariantMap &map) { - findKey(id, map.value("name").toString()); - }); + QObject::connect( + this, &Kube::ListPropertyController::added, this, [this](const QByteArray &id, const QVariantMap &map) { + findKey(id, map.value("name").toString()); + }); + QObject::connect(this, &Kube::ListPropertyController::removed, this, [this] (const QByteArray &id) { mMissingKeys.remove(id); setFoundAllKeys(mMissingKeys.isEmpty()); }); - - } bool foundAllKeys() @@ -133,12 +133,13 @@ public: mb.fromUnicodeString(addressee); SinkLog() << "Searching key for: " << mb.address(); - asyncRun>(this, [mb] { + asyncRun>(this, + [mb] { return MailCrypto::findKeys(QStringList{} << mb.address(), false, false, MailCrypto::OPENPGP); }, [this, addressee, id](const std::vector &keys) { if (!keys.empty()) { - if (keys.size() > 1 ) { + if (keys.size() > 1) { SinkWarning() << "Found more than one key, encrypting to all of them."; } SinkLog() << "Found key: " << keys.front().primaryFingerprint(); @@ -154,7 +155,7 @@ public: void set(const QStringList &list) { - for (const auto &email: list) { + for (const auto &email : list) { add({{"name", email}}); } } diff --git a/framework/src/domain/mime/mailtemplates.cpp b/framework/src/domain/mime/mailtemplates.cpp index 8e644b34..30f9a48d 100644 --- a/framework/src/domain/mime/mailtemplates.cpp +++ b/framework/src/domain/mime/mailtemplates.cpp @@ -53,6 +53,18 @@ static bool operator==(const KMime::Types::Mailbox &left, const KMime::Types::Ma return (left.addrSpec().asString() == right.addrSpec().asString()); } } + + Message* contentToMessage(Content* content) { + content->assemble(); + const auto encoded = content->encodedContent(); + + auto message = new Message(); + message->setContent(encoded); + message->parse(); + + return message; + } + } static KMime::Types::Mailbox::List stripMyAddressesFromAddressList(const KMime::Types::Mailbox::List &list, const KMime::Types::AddrSpecList me) @@ -215,7 +227,7 @@ KMime::Content *createMultipartAlternativeContent(const QString &plainBody, cons //FIXME This is supposed to select a charset out of the available charsets that contains all necessary characters to render the text // QTextCodec *charset = selectCharset(m_charsets, htmlBody); // htmlPart->contentType()->setCharset(charset->name()); - textPart->contentType()->setCharset("utf-8"); + htmlPart->contentType()->setCharset("utf-8"); htmlPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit); htmlPart->fromUnicodeString(htmlBody); multipartAlternative->addContent(htmlPart); @@ -223,6 +235,19 @@ KMime::Content *createMultipartAlternativeContent(const QString &plainBody, cons return multipartAlternative; } +KMime::Content *createMultipartMixedContent(QVector contents) +{ + KMime::Content *multiPartMixed = new KMime::Content(); + multiPartMixed->contentType()->setMimeType("multipart/mixed"); + multiPartMixed->contentType()->setBoundary(KMime::multiPartBoundary()); + + for (const auto &content : contents) { + multiPartMixed->addContent(content); + } + + return multiPartMixed; +} + void addProcessedBodyToMessage(const KMime::Message::Ptr &msg, const QString &plainBody, const QString &htmlBody, bool forward) { //FIXME @@ -864,27 +889,70 @@ void MailTemplates::reply(const KMime::Message::Ptr &origMsg, const std::functio }); } -void MailTemplates::forward(const KMime::Message::Ptr &origMsg, const std::function &callback) +void MailTemplates::forward(const KMime::Message::Ptr &origMsg, + const std::function &callback) { KMime::Message::Ptr wrapperMsg(new KMime::Message); wrapperMsg->to()->clear(); wrapperMsg->cc()->clear(); - wrapperMsg->subject()->fromUnicodeString(forwardSubject(origMsg->subject()->asUnicodeString()), "utf-8"); + // Decrypt the original message, it will be encrypted again in the composer + // for the right recipient + KMime::Message::Ptr forwardedMessage(new KMime::Message()); + if (isEncrypted(origMsg.data())) { + qDebug() << "Original message was encrypted, decrypting it"; + MimeTreeParser::ObjectTreeParser otp; + otp.parseObjectTree(origMsg.data()); + otp.decryptParts(); - const QByteArray refStr = getRefStr(origMsg); + auto htmlContent = otp.htmlContent(); + + KMime::Content *recreatedMsg = + htmlContent.isEmpty() ? createPlainPartContent(otp.plainTextContent()) : + createMultipartAlternativeContent(otp.plainTextContent(), htmlContent); + + KMime::Message::Ptr tmpForwardedMessage; + auto attachments = otp.collectAttachmentParts(); + if (!attachments.isEmpty()) { + QVector contents = {recreatedMsg}; + for (const auto &attachment : attachments) { + contents.append(attachment->node()); + } + + auto msg = createMultipartMixedContent(contents); + + tmpForwardedMessage.reset(KMime::contentToMessage(msg)); + } else { + tmpForwardedMessage.reset(KMime::contentToMessage(recreatedMsg)); + } + + origMsg->contentType()->fromUnicodeString(tmpForwardedMessage->contentType()->asUnicodeString(), "utf-8"); + origMsg->assemble(); + forwardedMessage->setHead(origMsg->head()); + forwardedMessage->setBody(tmpForwardedMessage->encodedBody()); + forwardedMessage->parse(); + + } else { + qDebug() << "Original message was not encrypted, using it as-is"; + forwardedMessage = origMsg; + } + + wrapperMsg->subject()->fromUnicodeString( + forwardSubject(forwardedMessage->subject()->asUnicodeString()), "utf-8"); + + const QByteArray refStr = getRefStr(forwardedMessage); if (!refStr.isEmpty()) { wrapperMsg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8"); } - KMime::Content* fwdAttachment = new KMime::Content; + KMime::Content *fwdAttachment = new KMime::Content; fwdAttachment->contentDisposition()->setDisposition(KMime::Headers::CDinline); fwdAttachment->contentType()->setMimeType("message/rfc822"); - fwdAttachment->contentDisposition()->setFilename(origMsg->subject()->asUnicodeString() + ".eml"); + fwdAttachment->contentDisposition()->setFilename(forwardedMessage->subject()->asUnicodeString() + ".eml"); // The mail was parsed in loadMessage before, so no need to assemble it - fwdAttachment->setBody(origMsg->encodedContent()); + fwdAttachment->setBody(forwardedMessage->encodedContent()); wrapperMsg->addContent(fwdAttachment); wrapperMsg->assemble(); diff --git a/framework/src/domain/mime/mimetreeparser/tests/CMakeLists.txt b/framework/src/domain/mime/mimetreeparser/tests/CMakeLists.txt index edc037bc..88f7c47a 100644 --- a/framework/src/domain/mime/mimetreeparser/tests/CMakeLists.txt +++ b/framework/src/domain/mime/mimetreeparser/tests/CMakeLists.txt @@ -11,7 +11,7 @@ include(ECMAddTests) find_package(Gpgmepp 1.7.1 CONFIG) find_package(QGpgme 1.7.1 CONFIG) -add_executable(mimetreeparsertest interfacetest.cpp) +add_executable(mimetreeparsertest mimetreeparsertest.cpp) add_gpg_crypto_test(mimetreeparsertest mimetreeparsertest) target_link_libraries(mimetreeparsertest kube_otp diff --git a/framework/src/domain/mime/mimetreeparser/tests/interfacetest.cpp b/framework/src/domain/mime/mimetreeparser/tests/interfacetest.cpp deleted file mode 100644 index 69509715..00000000 --- a/framework/src/domain/mime/mimetreeparser/tests/interfacetest.cpp +++ /dev/null @@ -1,377 +0,0 @@ -/* - 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"))); - } -}; - -QTEST_GUILESS_MAIN(InterfaceTest) -#include "interfacetest.moc" diff --git a/framework/src/domain/mime/mimetreeparser/tests/mimetreeparsertest.cpp b/framework/src/domain/mime/mimetreeparser/tests/mimetreeparsertest.cpp new file mode 100644 index 00000000..961dbf9a --- /dev/null +++ b/framework/src/domain/mime/mimetreeparser/tests/mimetreeparsertest.cpp @@ -0,0 +1,377 @@ +/* + 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"))); + } +}; + +QTEST_GUILESS_MAIN(InterfaceTest) +#include "mimetreeparsertest.moc" diff --git a/framework/src/domain/mime/tests/mailtemplatetest.cpp b/framework/src/domain/mime/tests/mailtemplatetest.cpp index 8d044608..6338cd58 100644 --- a/framework/src/domain/mime/tests/mailtemplatetest.cpp +++ b/framework/src/domain/mime/tests/mailtemplatetest.cpp @@ -248,6 +248,55 @@ private slots: QCOMPARE(origMsg->subject(false)->asUnicodeString(), {"A random subject with alternative contenttype"}); } + void testEncryptedForwardAsAttachment() + { + auto msg = readMail("openpgp-encrypted.mbox"); + KMime::Message::Ptr result; + MailTemplates::forward(msg, [&](const KMime::Message::Ptr &r) { result = r; }); + QTRY_VERIFY(result); + QCOMPARE(result->subject(false)->asUnicodeString(), {"FW: OpenPGP encrypted"}); + QCOMPARE(result->to()->addresses(), {}); + QCOMPARE(result->cc()->addresses(), {}); + + auto attachments = result->attachments(); + QCOMPARE(attachments.size(), 1); + auto attachment = attachments[0]; + QCOMPARE(attachment->contentDisposition(false)->disposition(), KMime::Headers::CDinline); + QCOMPARE(attachment->contentDisposition(false)->filename(), {"OpenPGP encrypted.eml"}); + QVERIFY(attachment->bodyIsMessage()); + + attachment->parse(); + auto origMsg = attachment->bodyAsMessage(); + QCOMPARE(origMsg->subject(false)->asUnicodeString(), {"OpenPGP encrypted"}); + } + + void testEncryptedWithAttachmentsForwardAsAttachment() + { + auto msg = readMail("openpgp-encrypted-two-attachments.mbox"); + KMime::Message::Ptr result; + MailTemplates::forward(msg, [&](const KMime::Message::Ptr &r) { result = r; }); + QTRY_VERIFY(result); + QCOMPARE(result->subject(false)->asUnicodeString(), {"FW: OpenPGP encrypted with 2 text attachments"}); + QCOMPARE(result->to()->addresses(), {}); + QCOMPARE(result->cc()->addresses(), {}); + + auto attachments = result->attachments(); + QCOMPARE(attachments.size(), 1); + auto attachment = attachments[0]; + QCOMPARE(attachment->contentDisposition(false)->disposition(), KMime::Headers::CDinline); + QCOMPARE(attachment->contentDisposition(false)->filename(), {"OpenPGP encrypted with 2 text attachments.eml"}); + QVERIFY(attachment->bodyIsMessage()); + + attachment->parse(); + auto origMsg = attachment->bodyAsMessage(); + QCOMPARE(origMsg->subject(false)->asUnicodeString(), {"OpenPGP encrypted with 2 text attachments"}); + + auto attattachments = origMsg->attachments(); + QCOMPARE(attattachments.size(), 2); + QCOMPARE(attattachments[0]->contentDisposition(false)->filename(), {"attachment1.txt"}); + QCOMPARE(attattachments[1]->contentDisposition(false)->filename(), {"attachment2.txt"}); + } + void testCreatePlainMail() { QStringList to = {{"to@example.org"}}; -- cgit v1.2.3