File indexing completed on 2025-02-16 04:59:22

0001 /* Copyright (C) 2014 - 2015 Stephan Platz <trojita@paalsteek.de>
0002 
0003    This file is part of the Trojita Qt IMAP e-mail client,
0004    http://trojita.flaska.net/
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public License as
0008    published by the Free Software Foundation; either version 2 of
0009    the License or (at your option) version 3 or any later version
0010    accepted by the membership of KDE e.V. (or its successor approved
0011    by the membership of KDE e.V.), which shall act as a proxy
0012    defined in Section 14 of version 3 of the license.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017    GNU General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include <QTest>
0024 #include <QNetworkRequest>
0025 #include <QNetworkReply>
0026 #include <QStandardItemModel>
0027 
0028 #include "test_Cryptography_MessageModel.h"
0029 #include "configure.cmake.h"
0030 #include "Cryptography/MessageModel.h"
0031 #include "Cryptography/MessagePart.h"
0032 #ifdef TROJITA_HAVE_MIMETIC
0033 #include "Cryptography/LocalMimeParser.h"
0034 #endif
0035 #include "Imap/data.h"
0036 #include "Imap/Model/ItemRoles.h"
0037 #include "Imap/Model/MailboxTree.h"
0038 #include "Streams/FakeSocket.h"
0039 
0040 /** @short Verify passthrough of existing MIME parts without modifications */
0041 void CryptographyMessageModelTest::testImapMessageParts()
0042 {
0043     QFETCH(QByteArray, bodystructure);
0044     QFETCH(QByteArray, partId);
0045     QFETCH(pathList, path);
0046     QFETCH(QByteArray, pathToPart);
0047     QFETCH(QByteArray, data);
0048 
0049     // By default, there's a 50ms delay between the time we request a part download and the time it actually happens.
0050     // That's too long for a unit test.
0051     model->setProperty("trojita-imap-delayed-fetch-part", 0);
0052 
0053     helperSyncBNoMessages();
0054     cServer("* 1 EXISTS\r\n");
0055     cClient(t.mk("UID FETCH 1:* (FLAGS)\r\n"));
0056     cServer("* 1 FETCH (UID 333 FLAGS ())\r\n" + t.last("OK fetched\r\n"));
0057 
0058     QCOMPARE(model->rowCount(msgListB), 1);
0059     QModelIndex msg = msgListB.model()->index(0, 0, msgListB);
0060     QVERIFY(msg.isValid());
0061     QCOMPARE(model->rowCount(msg), 0);
0062     Cryptography::MessageModel msgModel(0, msg);
0063     cClient(t.mk("UID FETCH 333 (" FETCH_METADATA_ITEMS ")\r\n"));
0064     cServer("* 1 FETCH (UID 333 BODYSTRUCTURE (" + bodystructure + "))\r\n" + t.last("OK fetched\r\n"));
0065     cEmpty();
0066     QVERIFY(model->rowCount(msg) > 0);
0067     QModelIndex mappedMsg = msgModel.index(0,0);
0068     QVERIFY(mappedMsg.isValid());
0069     QVERIFY(msgModel.rowCount(mappedMsg) > 0);
0070 
0071     QModelIndex mappedPart = mappedMsg;
0072     for (QList<QPair<int,int> >::const_iterator it = path.constBegin(), end = path.constEnd(); it != end; ++it) {
0073         mappedPart = mappedPart.model()->index(it->first, it->second, mappedPart);
0074         QVERIFY(mappedPart.isValid());
0075     }
0076     QCOMPARE(mappedPart.data(Imap::Mailbox::RolePartData).toString(), QString());
0077     cClient(t.mk(QByteArray("UID FETCH 333 (BODY.PEEK[" + partId + "])\r\n")));
0078     cServer("* 1 FETCH (UID 333 BODY[" + partId + "] " + asLiteral(data) + ")\r\n" + t.last("OK fetched\r\n"));
0079 
0080     QCOMPARE(mappedPart.data(Imap::Mailbox::RolePartData).toByteArray(), data);
0081     QCOMPARE(mappedPart.data(Imap::Mailbox::RolePartPathToPart).toByteArray(), pathToPart);
0082 
0083     cEmpty();
0084     QVERIFY(errorSpy->isEmpty());
0085 }
0086 
0087 void CryptographyMessageModelTest::testImapMessageParts_data()
0088 {
0089     QTest::addColumn<QByteArray>("bodystructure");
0090     QTest::addColumn<QByteArray>("partId");
0091     QTest::addColumn<QList<QPair<int,int> > >("path");
0092     QTest::addColumn<QByteArray>("pathToPart");
0093     QTest::addColumn<QByteArray>("data");
0094 
0095     QTest::newRow("plaintext-root")
0096             << bsPlaintext
0097             << QByteArray("1")
0098             << (pathList() << qMakePair(0,0))
0099             << QByteArray("/0")
0100             << QByteArray("blesmrt");
0101 
0102     QTest::newRow("torture-plaintext")
0103             << bsTortureTest
0104             << QByteArray("1")
0105             << (pathList() << qMakePair(0,0) << qMakePair(0,0))
0106             << QByteArray("/0/0")
0107             << QByteArray("plaintext");
0108 
0109     QTest::newRow("torture-richtext")
0110             << bsTortureTest
0111             << QByteArray("2.2.1")
0112             << (pathList() << qMakePair(0,0) << qMakePair(1,0)
0113                 << qMakePair(0,0) << qMakePair(1,0) << qMakePair(0,0))
0114             << QByteArray("/0/1/0/1/0")
0115             << QByteArray("text richtext");
0116 }
0117 
0118 /** @short Verify building and retrieving of a custom MIME tree structure */
0119 void CryptographyMessageModelTest::testCustomMessageParts()
0120 {
0121 #if defined(__has_feature)
0122 #  if  __has_feature(address_sanitizer)
0123     // better would be checking for UBSAN, but I don't think that there's an appropriate feature for this
0124 #    define SKIP_QSTANDARDITEMMODEL
0125 #  endif
0126 #endif
0127 
0128 #ifdef SKIP_QSTANDARDITEMMODEL
0129     QSKIP("ASAN build, https://bugreports.qt.io/browse/QTBUG-56027");
0130 #else
0131     // Initialize model with a root item
0132     QStandardItemModel minimal;
0133     QStandardItem *dummy_root = new QStandardItem();
0134     QStandardItem *root_mime = new QStandardItem(QStringLiteral("multipart/mixed"));
0135     dummy_root->appendRow(root_mime);
0136     minimal.invisibleRootItem()->appendRow(dummy_root);
0137 
0138     // Make sure we didn't mess up until here
0139     QModelIndex index = minimal.index(0, 0);
0140     QVERIFY(index.model()->index(0, 0, index).isValid());
0141     QCOMPARE(index.model()->index(0, 0, index).data(), root_mime->data(Qt::DisplayRole));
0142 
0143     Cryptography::MessageModel msgModel(0, index);
0144 
0145     QModelIndex rootPartIndex = msgModel.index(0,0).model()->index(0, 0, msgModel.index(0, 0));
0146 
0147     QCOMPARE(rootPartIndex.data(), root_mime->data(Qt::DisplayRole));
0148 
0149     Cryptography::LocalMessagePart *localRoot = new Cryptography::LocalMessagePart(nullptr, 0, QByteArrayLiteral("multipart/mixed"));
0150     Cryptography::LocalMessagePart *localText = new Cryptography::LocalMessagePart(localRoot, 0, QByteArrayLiteral("text/plain"));
0151     localText->setData(QByteArrayLiteral("foobar"));
0152     localRoot->setChild(0, Cryptography::MessagePart::Ptr(localText));
0153     Cryptography::LocalMessagePart *localHtml = new Cryptography::LocalMessagePart(localRoot, 1, QByteArrayLiteral("text/html"));
0154     localHtml->setData(QByteArrayLiteral("<html>foobar</html>"));
0155     localRoot->setChild(1, Cryptography::MessagePart::Ptr(localHtml));
0156 
0157     msgModel.insertSubtree(rootPartIndex, Cryptography::MessagePart::Ptr(localRoot));
0158 
0159     QVERIFY(msgModel.rowCount(rootPartIndex) > 0);
0160 
0161     QModelIndex localRootIndex = rootPartIndex.model()->index(0, 0, rootPartIndex);
0162     QVERIFY(localRootIndex.isValid());
0163     QCOMPARE(localRootIndex.data(Imap::Mailbox::RolePartMimeType), localRoot->data(Imap::Mailbox::RolePartMimeType));
0164     QModelIndex localTextIndex = localRootIndex.model()->index(0, 0, localRootIndex);
0165     QVERIFY(localTextIndex.isValid());
0166     QCOMPARE(localTextIndex.data(Imap::Mailbox::RolePartMimeType), localText->data(Imap::Mailbox::RolePartMimeType));
0167     QModelIndex localHtmlIndex = localRootIndex.model()->index(1, 0, localRootIndex);
0168     QVERIFY(localHtmlIndex.isValid());
0169     QCOMPARE(localHtmlIndex.data(Imap::Mailbox::RolePartMimeType), localHtml->data(Imap::Mailbox::RolePartMimeType));
0170 #endif
0171 }
0172 
0173 /** @short Check adding a custom MIME tree structure to an existing message
0174  *
0175  *  This test fetches the structure of an IMAP message and adds some custom
0176  *  structure to that message. Adding random data as child of a text/plain
0177  *  MIME part does not make sense semantically but that's not what we want to
0178  *  test here.
0179  */
0180 void CryptographyMessageModelTest::testMixedMessageParts()
0181 {
0182 
0183     // By default, there's a 50ms delay between the time we request a part download and the time it actually happens.
0184     // That's too long for a unit test.
0185     model->setProperty("trojita-imap-delayed-fetch-part", 0);
0186 
0187     helperSyncBNoMessages();
0188     cServer("* 1 EXISTS\r\n");
0189     cClient(t.mk("UID FETCH 1:* (FLAGS)\r\n"));
0190     cServer("* 1 FETCH (UID 333 FLAGS ())\r\n" + t.last("OK fetched\r\n"));
0191 
0192     QCOMPARE(model->rowCount(msgListB), 1);
0193     QModelIndex msg = msgListB.model()->index(0, 0, msgListB);
0194     QVERIFY(msg.isValid());
0195     QCOMPARE(model->rowCount(msg), 0);
0196     cClient(t.mk("UID FETCH 333 (" FETCH_METADATA_ITEMS ")\r\n"));
0197     const QByteArray bsEncrypted("\"encrypted\" (\"protocol\" \"application/pgp-encrypted\" \"boundary\" \"trojita=_7cf0b2b6-64c6-41ad-b381-853caf492c54\") NIL NIL NIL");
0198     cServer("* 1 FETCH (UID 333 BODYSTRUCTURE (" + bsEncrypted + "))\r\n" + t.last("OK fetched\r\n"));
0199     cEmpty();
0200     QVERIFY(model->rowCount(msg) > 0);
0201     Cryptography::MessageModel msgModel(0, msg);
0202     QModelIndex mappedMsg = msgModel.index(0,0);
0203     QVERIFY(mappedMsg.isValid());
0204     QVERIFY(msgModel.rowCount(mappedMsg) > 0);
0205     QCOMPARE(msgModel.parent(mappedMsg), QModelIndex());
0206 
0207     QModelIndex mappedPart = mappedMsg.model()->index(0, 0, mappedMsg);
0208     QVERIFY(mappedPart.isValid());
0209     QCOMPARE(mappedPart.data(Imap::Mailbox::RolePartPathToPart).toByteArray(), QByteArrayLiteral("/0"));
0210 
0211     cEmpty();
0212     QVERIFY(errorSpy->isEmpty());
0213 
0214     // Add some custom structure to the given IMAP message
0215     Cryptography::LocalMessagePart *localRoot = new Cryptography::LocalMessagePart(nullptr, 0, QByteArrayLiteral("multipart/mixed"));
0216     Cryptography::LocalMessagePart *localText = new Cryptography::LocalMessagePart(localRoot, 0, QByteArrayLiteral("text/plain"));
0217     localText->setData(QByteArrayLiteral("foobar"));
0218     localRoot->setChild(0, Cryptography::MessagePart::Ptr(localText));
0219     Cryptography::LocalMessagePart *localHtml = new Cryptography::LocalMessagePart(localRoot, 1, QByteArrayLiteral("text/html"));
0220     localHtml->setData(QByteArrayLiteral("<html>foobar</html>"));
0221     localRoot->setChild(1, Cryptography::MessagePart::Ptr(localHtml));
0222 
0223     msgModel.insertSubtree(mappedPart, Cryptography::MessagePart::Ptr(localRoot));
0224 
0225     QVERIFY(msgModel.rowCount(mappedPart) > 0);
0226 
0227     QModelIndex localRootIndex = mappedPart.model()->index(0, 0, mappedPart);
0228     QVERIFY(localRootIndex.isValid());
0229     QCOMPARE(localRootIndex.data(Imap::Mailbox::RolePartMimeType), localRoot->data(Imap::Mailbox::RolePartMimeType));
0230     QModelIndex localTextIndex = localRootIndex.model()->index(0, 0, localRootIndex);
0231     QVERIFY(localTextIndex.isValid());
0232     QCOMPARE(localTextIndex.data(Imap::Mailbox::RolePartMimeType), localText->data(Imap::Mailbox::RolePartMimeType));
0233     QModelIndex localHtmlIndex = localRootIndex.model()->index(1, 0, localRootIndex);
0234     QVERIFY(localHtmlIndex.isValid());
0235     QCOMPARE(localHtmlIndex.data(Imap::Mailbox::RolePartMimeType), localHtml->data(Imap::Mailbox::RolePartMimeType));
0236 }
0237 
0238 /** @short Verify that we can handle data from Mimetic and use them locally */
0239 void CryptographyMessageModelTest::testLocalMimeParsing()
0240 {
0241 #ifdef TROJITA_HAVE_MIMETIC
0242     model->setProperty("trojita-imap-delayed-fetch-part", 0);
0243     helperSyncBNoMessages();
0244     cServer("* 1 EXISTS\r\n");
0245     cClient(t.mk("UID FETCH 1:* (FLAGS)\r\n"));
0246     cServer("* 1 FETCH (UID 333 FLAGS ())\r\n" + t.last("OK fetched\r\n"));
0247     QCOMPARE(model->rowCount(msgListB), 1);
0248     QModelIndex msg = msgListB.model()->index(0, 0, msgListB);
0249     QVERIFY(msg.isValid());
0250     QCOMPARE(model->rowCount(msg), 0);
0251     cClient(t.mk("UID FETCH 333 (" FETCH_METADATA_ITEMS ")\r\n"));
0252 
0253     Cryptography::MessageModel msgModel(0, msg);
0254     msgModel.registerPartHandler(std::make_shared<Cryptography::LocalMimeMessageParser>());
0255 
0256     const QByteArray bsTopLevelRfc822Message = QByteArrayLiteral(
0257                 "\"messaGe\" \"rFc822\" NIL NIL NIL \"7bit\" 1511 (\"Thu, 8 Aug 2013 09:02:50 +0200\" "
0258                 "\"Re: Your GSoC status\" ((\"Pali\" NIL \"pali.rohar\" \"gmail.com\")) "
0259                 "((\"Pali\" NIL \"pali.rohar\" \"gmail.com\")) "
0260                 "((\"Pali\" NIL \"pali.rohar\" \"gmail.com\")) ((\"Jan\" NIL \"jkt\" \"flaska.net\")) "
0261                 "NIL NIL NIL \"<201308080902.51071@pali>\") "
0262                 "((\"Text\" \"Plain\" (\"ChaRset\" \"uTf-8\") NIL NIL \"qUoted-printable\" 632 20 NIL NIL NIL NIL)"
0263                 "(\"applicatioN\" \"pGp-signature\" (\"Name\" \"signature.asc\") NIL "
0264                 "\"This is a digitally signed message part.\" \"7bit\" 205 NIL NIL NIL NIL) \"signed\" "
0265                 "(\"boundary\" \"nextPart2106994.VznBGuL01i\" \"protocol\" \"application/pgp-signature\" \"micalg\" \"pgp-sha1\") "
0266                 "NIL NIL NIL) 51 NIL NIL NIL NIL");
0267 
0268     cServer("* 1 FETCH (UID 333 BODYSTRUCTURE (" + bsTopLevelRfc822Message + "))\r\n" + t.last("OK fetched\r\n"));
0269     cEmpty();
0270     QVERIFY(model->rowCount(msg) > 0);
0271     auto mappedMsg = msgModel.index(0,0);
0272     QVERIFY(mappedMsg.isValid());
0273     QVERIFY(msgModel.rowCount(mappedMsg) > 0);
0274 
0275     QPersistentModelIndex msgRoot = mappedMsg.model()->index(0, 0, mappedMsg);
0276     QModelIndex formerMsgRoot = msgRoot;
0277     QVERIFY(msgRoot.isValid());
0278     QCOMPARE(msgRoot.data(Imap::Mailbox::RolePartPathToPart).toByteArray(),
0279              QByteArrayLiteral("/0"));
0280 
0281     QCOMPARE(msgRoot.data(Imap::Mailbox::RolePartMimeType).toByteArray(), QByteArrayLiteral("message/rfc822"));
0282     QCOMPARE(msgRoot.internalPointer(), formerMsgRoot.internalPointer());
0283     QCOMPARE(msgModel.rowCount(msgRoot), 0);
0284     cClientRegExp(t.mk("UID FETCH 333 \\(BODY\\.PEEK\\[1\\.(TEXT|HEADER)\\] BODY\\.PEEK\\[1\\.(TEXT|HEADER)\\]\\)"));
0285     QByteArray myHeader = QByteArrayLiteral("Content-Type: mULTIpart/miXed; boundary=sep\r\n"
0286                                             "MIME-Version: 1.0\r\n"
0287                                             "Subject: =?ISO-8859-2?B?7Lno+L794e3p?=\r\n"
0288                                             "From: =?utf-8?B?xJs=?= <1@example.org>\r\n"
0289                                             "Sender: =?utf-8?B?xJq=?= <0@example.org>\r\n"
0290                                             "To: =?utf-8?B?xJs=?= <2@example.org>, =?iso-8859-1?Q?=E1?= <3@example.org>\r\n"
0291                                             "Cc: =?utf-8?B?xJz=?= <4@example.org>\r\n"
0292                                             "reply-to: =?utf-8?B?xJm=?= <r@example.org>\r\n"
0293                                             "BCC: =?utf-8?B?xJr=?= <5@example.org>\r\n\r\n");
0294     QByteArray myBinaryBody = QByteArrayLiteral("This is the actual message body.\r\n");
0295     QString myUnicode = QStringLiteral("Λέσβος");
0296     QByteArray myBody = QByteArrayLiteral("preamble of a MIME message\r\n--sep\r\n"
0297                                           "Content-Type: text/plain; charset=\"utf-8\"\r\nContent-Transfer-Encoding: base64\r\n\r\n")
0298             + myUnicode.toUtf8().toBase64()
0299             + QByteArrayLiteral("\r\n--sep\r\nContent-Type: pWned/NOw\r\n\r\n")
0300             + myBinaryBody
0301             + QByteArrayLiteral("\r\n--sep--\r\n");
0302     cServer("* 1 FETCH (UID 333 BODY[1.TEXT] " + asLiteral(myBody) + " BODY[1.HEADER] " + asLiteral(myHeader) + ")\r\n"
0303             + t.last("OK fetched\r\n"));
0304 
0305     // the part got replaced, so our QModelIndex should be invalid now
0306     QVERIFY(msgRoot.internalPointer() != formerMsgRoot.internalPointer());
0307 
0308     QCOMPARE(msgModel.rowCount(msgRoot), 1);
0309     QCOMPARE(msgRoot.data(Imap::Mailbox::RolePartMimeType).toByteArray(), QByteArrayLiteral("message/rfc822"));
0310     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageSubject).toString(), QStringLiteral("ěščřžýáíé"));
0311     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageSender),
0312              QVariant(QVariantList() <<
0313                       (QStringList() << QStringLiteral("Ě") << QString() << QStringLiteral("0") << QStringLiteral("example.org"))));
0314     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageFrom),
0315              QVariant(QVariantList() <<
0316                       (QStringList() << QStringLiteral("ě") << QString() << QStringLiteral("1") << QStringLiteral("example.org"))));
0317     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageTo),
0318              QVariant(QVariantList() <<
0319                       (QStringList() << QStringLiteral("ě") << QString() << QStringLiteral("2") << QStringLiteral("example.org")) <<
0320                       (QStringList() << QStringLiteral("á") << QString() << QStringLiteral("3") << QStringLiteral("example.org"))));
0321     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageCc),
0322              QVariant(QVariantList() <<
0323                       (QStringList() << QStringLiteral("Ĝ") << QString() << QStringLiteral("4") << QStringLiteral("example.org"))));
0324     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageBcc),
0325              QVariant(QVariantList() <<
0326                       (QStringList() << QStringLiteral("Ě") << QString() << QStringLiteral("5") << QStringLiteral("example.org"))));
0327     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageReplyTo),
0328              QVariant(QVariantList() <<
0329                       (QStringList() << QStringLiteral("ę") << QString() << QStringLiteral("r") << QStringLiteral("example.org"))));
0330 
0331     // NOTE: the OFFSET_MIME parts are not implemented; that's more or less on purpose because they aren't being used through
0332     // the rest of the code so far.
0333 
0334     auto mHeader = msgRoot.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_HEADER, msgRoot);
0335     QVERIFY(mHeader.isValid());
0336     auto mText = msgRoot.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_TEXT, msgRoot);
0337     QVERIFY(mText.isValid());
0338     auto mMime = msgRoot.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_MIME, msgRoot);
0339     QVERIFY(!mMime.isValid());
0340     auto mRaw = msgRoot.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_RAW_CONTENTS, msgRoot);
0341     QVERIFY(mRaw.isValid());
0342     // We cannot compare them for an exact byte-equality because Mimetic apparently mangles the data a bit,
0343     // for example there's an extra space after the comma in the To field in this case :(
0344     QCOMPARE(mHeader.data(Imap::Mailbox::RolePartData).toByteArray().left(30), myHeader.left(30));
0345     QCOMPARE(mHeader.data(Imap::Mailbox::RolePartData).toByteArray().right(4), QByteArrayLiteral("\r\n\r\n"));
0346     QCOMPARE(mText.data(Imap::Mailbox::RolePartData).toByteArray(), myBody);
0347 
0348     // still that new C++11 toy, oh yeah :)
0349     using bodyFldParam_t = std::result_of<decltype(&Imap::Mailbox::TreeItemPart::bodyFldParam)(Imap::Mailbox::TreeItemPart)>::type;
0350     bodyFldParam_t expectedBodyFldParam;
0351     QCOMPARE(msgRoot.data(Imap::Mailbox::RolePartBodyFldParam).value<bodyFldParam_t>(), expectedBodyFldParam);
0352 
0353     auto multipartIdx = msgRoot.model()->index(0, 0, msgRoot);
0354     QVERIFY(multipartIdx.isValid());
0355     QCOMPARE(multipartIdx.data(Imap::Mailbox::RolePartMimeType).toByteArray(), QByteArrayLiteral("multipart/mixed"));
0356     QCOMPARE(msgModel.rowCount(multipartIdx), 2);
0357     expectedBodyFldParam.clear();
0358     expectedBodyFldParam["BOUNDARY"] = "sep";
0359     QCOMPARE(multipartIdx.data(Imap::Mailbox::RolePartBodyFldParam).value<bodyFldParam_t>(), expectedBodyFldParam);
0360 
0361     auto c1 = multipartIdx.model()->index(0, 0, multipartIdx);
0362     QVERIFY(c1.isValid());
0363     QCOMPARE(msgModel.rowCount(c1), 0);
0364     QCOMPARE(c1.data(Imap::Mailbox::RolePartMimeType).toByteArray(), QByteArrayLiteral("text/plain"));
0365     QCOMPARE(c1.data(Imap::Mailbox::RolePartData).toString(), myUnicode);
0366     expectedBodyFldParam.clear();
0367     expectedBodyFldParam["CHARSET"] = "utf-8";
0368     QCOMPARE(c1.data(Imap::Mailbox::RolePartBodyFldParam).value<bodyFldParam_t>(), expectedBodyFldParam);
0369 
0370     auto c1raw = c1.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_RAW_CONTENTS, c1);
0371     QVERIFY(c1raw.isValid());
0372     QCOMPARE(c1raw.data(Imap::Mailbox::RolePartData).toByteArray(), myUnicode.toUtf8().toBase64());
0373     QVERIFY(!c1.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_HEADER, c1).isValid());
0374     QVERIFY(!c1.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_TEXT, c1).isValid());
0375     QVERIFY(!c1.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_MIME, c1).isValid());
0376 
0377     auto c2 = multipartIdx.model()->index(1, 0, multipartIdx);
0378     QVERIFY(c2.isValid());
0379     QCOMPARE(msgModel.rowCount(c2), 0);
0380     QCOMPARE(c2.data(Imap::Mailbox::RolePartMimeType).toByteArray(), QByteArrayLiteral("pwned/now"));
0381     QCOMPARE(c2.data(Imap::Mailbox::RolePartData).toByteArray(), myBinaryBody);
0382 
0383     auto c2raw = c2.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_RAW_CONTENTS, c2);
0384     QVERIFY(c2raw.isValid());
0385     QCOMPARE(c2raw.data(Imap::Mailbox::RolePartData).toByteArray(), myBinaryBody);
0386     QVERIFY(!c2.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_HEADER, c2).isValid());
0387     QVERIFY(!c2.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_TEXT, c2).isValid());
0388     QVERIFY(!c2.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_MIME, c2).isValid());
0389 
0390     cEmpty();
0391     QVERIFY(errorSpy->isEmpty());
0392 #else
0393     QSKIP("Mimetic not available, cannot test LocalMimeMessageParser");
0394 #endif
0395 }
0396 
0397 void CryptographyMessageModelTest::testDelayedLoading()
0398 {
0399     model->setProperty("trojita-imap-delayed-fetch-part", 0);
0400     helperSyncBNoMessages();
0401     cServer("* 1 EXISTS\r\n");
0402     cClient(t.mk("UID FETCH 1:* (FLAGS)\r\n"));
0403     cServer("* 1 FETCH (UID 333 FLAGS ())\r\n" + t.last("OK fetched\r\n"));
0404     QCOMPARE(model->rowCount(msgListB), 1);
0405     QModelIndex msg = msgListB.model()->index(0, 0, msgListB);
0406     QVERIFY(msg.isValid());
0407 
0408     Cryptography::MessageModel msgModel(0, msg);
0409     msgModel.setObjectName("msgModel");
0410 
0411     cEmpty();
0412     QCOMPARE(msgModel.rowCount(QModelIndex()), 0);
0413     cClient(t.mk("UID FETCH 333 (" FETCH_METADATA_ITEMS ")\r\n"));
0414     cServer("* 1 FETCH (UID 333 BODYSTRUCTURE (" + bsPlaintext + "))\r\n" + t.last("OK fetched\r\n"));
0415     cEmpty();
0416     QCOMPARE(model->rowCount(msg), 1);
0417     QCOMPARE(msgModel.rowCount(QModelIndex()), 1);
0418     auto mappedMsg = msgModel.index(0,0);
0419     QVERIFY(mappedMsg.isValid());
0420 
0421     QPersistentModelIndex msgRoot = mappedMsg.model()->index(0, 0, mappedMsg);
0422     QVERIFY(msgRoot.isValid());
0423     QCOMPARE(msgRoot.data(Imap::Mailbox::RolePartPathToPart).toByteArray(),
0424              QByteArrayLiteral("/0"));
0425 }
0426 
0427 QTEST_GUILESS_MAIN(CryptographyMessageModelTest)