File indexing completed on 2024-05-12 05:17:18

0001 /**
0002  * This file is part of the KDE project
0003  * SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org>
0004  * SPDX-FileCopyrightText: 2009 Andras Mantia <amantia@kde.org>
0005  *
0006  * SPDX-License-Identifier: LGPL-2.0-or-later
0007  */
0008 
0009 #include <QCoreApplication>
0010 #include <QDebug>
0011 
0012 #include "acl.h"
0013 #include "appendjob.h"
0014 #include "capabilitiesjob.h"
0015 #include "closejob.h"
0016 #include "createjob.h"
0017 #include "deleteacljob.h"
0018 #include "deletejob.h"
0019 #include "expungejob.h"
0020 #include "fetchjob.h"
0021 #include "getacljob.h"
0022 #include "getmetadatajob.h"
0023 #include "listjob.h"
0024 #include "listrightsjob.h"
0025 #include "loginjob.h"
0026 #include "logoutjob.h"
0027 #include "myrightsjob.h"
0028 #include "namespacejob.h"
0029 #include "renamejob.h"
0030 #include "selectjob.h"
0031 #include "session.h"
0032 #include "sessionuiproxy.h"
0033 #include "setacljob.h"
0034 #include "setmetadatajob.h"
0035 #include "storejob.h"
0036 #include "subscribejob.h"
0037 #include "unsubscribejob.h"
0038 
0039 using namespace KIMAP;
0040 
0041 using PartsReceivedSignal = void (FetchJob::*)(const QString &, const QMap<qint64, qint64> &, const QMap<qint64, MessageParts> &);
0042 
0043 using HeadersReceivedSignal = void (FetchJob::*)(const QString &,
0044                                                  const QMap<qint64, qint64> &,
0045                                                  const QMap<qint64, qint64> &,
0046                                                  const QMap<qint64, MessageFlags> &,
0047                                                  const QMap<qint64, MessagePtr> &);
0048 
0049 class UiProxy : public SessionUiProxy
0050 {
0051 public:
0052     bool ignoreSslError(const KSslErrorUiData &errorData) override
0053     {
0054         Q_UNUSED(errorData)
0055         return true;
0056     }
0057 };
0058 
0059 void dumpContentHelper(KMime::Content *part, const QString &partId = QString())
0060 {
0061     if (partId.isEmpty()) {
0062         qDebug() << "** Message root **";
0063     } else {
0064         qDebug() << "** Part" << partId << "**";
0065     }
0066 
0067     qDebug() << part->head();
0068 
0069     KMime::Content::List children = part->contents();
0070     for (int i = 0; i < children.size(); i++) {
0071         QString newId = partId;
0072         if (!newId.isEmpty()) {
0073             newId += QLatin1StringView(".");
0074         }
0075         newId += QString::number(i + 1);
0076         dumpContentHelper(children[i], newId);
0077     }
0078 }
0079 
0080 void listFolders(Session *session, bool includeUnsubscribed = false, const QString &nameFilter = QLatin1StringView(""))
0081 {
0082     auto list = new ListJob(session);
0083     list->setOption(includeUnsubscribed ? KIMAP::ListJob::IncludeUnsubscribed : KIMAP::ListJob::NoOption);
0084     list->exec();
0085     Q_ASSERT_X(list->error() == 0, "ListJob", list->errorString().toLocal8Bit().constData());
0086     int count = list->mailBoxes().size();
0087     for (int i = 0; i < count; ++i) {
0088         MailBoxDescriptor descriptor = list->mailBoxes()[i];
0089         if (descriptor.name.endsWith(nameFilter)) {
0090             qDebug() << descriptor.separator << descriptor.name;
0091         }
0092     }
0093 }
0094 
0095 void testMetaData(Session *session)
0096 {
0097     qDebug() << "TESTING: METADATA commands";
0098     auto create = new CreateJob(session);
0099     create->setMailBox(QStringLiteral("INBOX/TestFolder"));
0100     create->exec();
0101 
0102     auto setmetadata = new SetMetaDataJob(session);
0103     setmetadata->setMailBox(QStringLiteral("INBOX/TestFolder"));
0104     setmetadata->setServerCapability(SetMetaDataJob::Annotatemore);
0105     setmetadata->setEntry("/comment");
0106     setmetadata->addMetaData("value.priv", "My new comment");
0107     setmetadata->exec();
0108 
0109     setmetadata = new SetMetaDataJob(session);
0110     setmetadata->setMailBox(QStringLiteral("INBOX/TestFolder"));
0111     setmetadata->setServerCapability(SetMetaDataJob::Annotatemore);
0112     setmetadata->setEntry("/check");
0113     setmetadata->addMetaData("value.priv", "true");
0114     setmetadata->exec();
0115 
0116     auto getmetadata = new GetMetaDataJob(session);
0117     getmetadata->setMailBox(QStringLiteral("INBOX/TestFolder"));
0118     getmetadata->setServerCapability(SetMetaDataJob::Annotatemore);
0119     getmetadata->addEntry("/*", "value.priv");
0120     getmetadata->exec();
0121     Q_ASSERT_X(getmetadata->metaData(QLatin1StringView("INBOX/TestFolder"), "/check", "value.priv") == "true", "", "/check metadata should be true");
0122     Q_ASSERT_X(getmetadata->metaData(QLatin1StringView("INBOX/TestFolder"), "/comment", "value.priv") == "My new comment",
0123                "",
0124                "/check metadata should be My new comment");
0125 
0126     // cleanup
0127     auto deletejob = new DeleteJob(session);
0128     deletejob->setMailBox(QLatin1StringView("INBOX/TestFolder"));
0129     deletejob->exec();
0130 }
0131 
0132 void testAcl(Session *session, const QString &user)
0133 {
0134     qDebug() << "TESTING: ACL commands";
0135     auto create = new CreateJob(session);
0136     create->setMailBox(QStringLiteral("INBOX/TestFolder"));
0137     create->exec();
0138 
0139     auto listRights = new ListRightsJob(session);
0140     listRights->setMailBox(QStringLiteral("INBOX/TestFolder"));
0141     listRights->setIdentifier(user.toLatin1());
0142     listRights->exec();
0143     qDebug() << "Default rights on INBOX/TestFolder: " << Acl::rightsToString(listRights->defaultRights());
0144     const QList<Acl::Rights> possible = listRights->possibleRights();
0145     QStringList strList;
0146     for (Acl::Rights r : std::as_const(possible)) {
0147         strList << QString::fromLatin1(Acl::rightsToString(r));
0148     }
0149     qDebug() << "Possible rights on INBOX/TestFolder: " << strList;
0150 
0151     auto myRights = new MyRightsJob(session);
0152     myRights->setMailBox(QStringLiteral("INBOX/TestFolder"));
0153     myRights->exec();
0154 
0155     Acl::Rights mine = myRights->rights();
0156     qDebug() << "My rights on INBOX/TestFolder: " << Acl::rightsToString(mine);
0157     qDebug() << "Reading INBOX/TestFolder is possible: " << myRights->hasRightEnabled(Acl::Read);
0158     Q_ASSERT_X(myRights->hasRightEnabled(Acl::Read), "Reading INBOX is NOT possible", "");
0159 
0160     auto getAcl = new GetAclJob(session);
0161     getAcl->setMailBox(QStringLiteral("INBOX/TestFolder"));
0162     getAcl->exec();
0163     qDebug() << "Anyone rights on INBOX/TestFolder: " << getAcl->rights("anyone");
0164     Acl::Rights users = getAcl->rights(user.toLatin1());
0165     qDebug() << user << " rights on INBOX/TestFolder: " << Acl::rightsToString(users);
0166     Q_ASSERT_X(mine == users, "GETACL returns different rights for the same user", "");
0167 
0168     qDebug() << "Removing Delete right ";
0169     mine = Acl::Delete;
0170     auto setAcl = new SetAclJob(session);
0171     setAcl->setMailBox(QStringLiteral("INBOX/TestFolder"));
0172     setAcl->setIdentifier(user.toLatin1());
0173     setAcl->setRights(AclJobBase::Remove, mine);
0174     setAcl->exec();
0175 
0176     getAcl = new GetAclJob(session);
0177     getAcl->setMailBox(QStringLiteral("INBOX/TestFolder"));
0178     getAcl->exec();
0179     users = getAcl->rights(user.toLatin1());
0180     qDebug() << user << " rights on INBOX/TestFolder: " << Acl::rightsToString(users);
0181 
0182     qDebug() << "Adding back Delete right ";
0183     mine = Acl::Delete;
0184     setAcl = new SetAclJob(session);
0185     setAcl->setMailBox(QStringLiteral("INBOX/TestFolder"));
0186     setAcl->setIdentifier(user.toLatin1());
0187     setAcl->setRights(AclJobBase::Add, mine);
0188     setAcl->exec();
0189 
0190     getAcl = new GetAclJob(session);
0191     getAcl->setMailBox(QStringLiteral("INBOX/TestFolder"));
0192     getAcl->exec();
0193     users = getAcl->rights(user.toLatin1());
0194     qDebug() << user << " rights on INBOX/TestFolder: " << Acl::rightsToString(users);
0195 
0196     // cleanup
0197     auto deletejob = new DeleteJob(session);
0198     deletejob->setMailBox(QStringLiteral("INBOX/TestFolder"));
0199     deletejob->exec();
0200 }
0201 
0202 void testAppendAndStore(Session *session)
0203 {
0204     qDebug() << "TESTING: APPEND and STORE";
0205     // setup
0206     auto create = new CreateJob(session);
0207     create->setMailBox(QStringLiteral("INBOX/TestFolder"));
0208     create->exec();
0209 
0210     QByteArray testMailContent =
0211         "Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)\r\n"
0212         "From: Fred Foobar <foobar@Blurdybloop.COM>\r\n"
0213         "Subject: afternoon meeting\r\n"
0214         "To: mooch@owatagu.siam.edu\r\n"
0215         "Message-Id: <B27397-0100000@Blurdybloop.COM>\r\n"
0216         "MIME-Version: 1.0\r\n"
0217         "Content-Type: TEXT/PLAIN; CHARSET=US-ASCII\r\n"
0218         "\r\n"
0219         "Hello Joe, do you think we can meet at 3:30 tomorrow?\r\n";
0220 
0221     qDebug() << "Append a message in INBOX/TestFolder...";
0222     auto append = new AppendJob(session);
0223     append->setMailBox(QStringLiteral("INBOX/TestFolder"));
0224     append->setContent(testMailContent);
0225     append->exec();
0226     Q_ASSERT_X(append->error() == 0, "AppendJob", append->errorString().toLocal8Bit().constData());
0227 
0228     qDebug() << "Read the message back and compare...";
0229     auto select = new SelectJob(session);
0230     select->setMailBox(QStringLiteral("INBOX/TestFolder"));
0231     select->exec();
0232 
0233     auto fetch = new FetchJob(session);
0234     FetchJob::FetchScope scope;
0235     fetch->setSequenceSet(ImapSet(1));
0236     scope.parts.clear();
0237     scope.mode = FetchJob::FetchScope::Content;
0238     fetch->setScope(scope);
0239     MessagePtr message;
0240     QObject::connect(fetch,
0241                      static_cast<HeadersReceivedSignal>(&FetchJob::headersReceived),
0242                      fetch,
0243                      [&](const QString &,
0244                          const QMap<qint64, qint64> &,
0245                          const QMap<qint64, qint64> &,
0246                          const QMap<qint64, MessageFlags> &,
0247                          const QMap<qint64, MessagePtr> &msgs) {
0248                          message = msgs[1];
0249                      });
0250     fetch->exec();
0251     Q_ASSERT_X(fetch->error() == 0, "FetchJob", fetch->errorString().toLocal8Bit().constData());
0252     testMailContent.replace("\r\n", "\n");
0253     Q_ASSERT_X(testMailContent == message->head() + "\n" + message->body(),
0254                "Message differs from reference",
0255                QByteArray(message->head() + "\n" + message->body()).constData());
0256 
0257     fetch = new FetchJob(session);
0258     fetch->setSequenceSet(ImapSet(1));
0259     scope.parts.clear();
0260     scope.mode = FetchJob::FetchScope::Flags;
0261     fetch->setScope(scope);
0262     MessageFlags expectedFlags;
0263     QObject::connect(fetch,
0264                      static_cast<HeadersReceivedSignal>(&FetchJob::headersReceived),
0265                      fetch,
0266                      [&](const QString &,
0267                          const QMap<qint64, qint64> &,
0268                          const QMap<qint64, qint64> &,
0269                          const QMap<qint64, MessageFlags> &flags,
0270                          const QMap<qint64, MessagePtr> &) {
0271                          expectedFlags = flags[1];
0272                      });
0273     fetch->exec();
0274     qDebug() << "Read the message flags:" << expectedFlags;
0275 
0276     qDebug() << "Add the \\Deleted flag...";
0277     expectedFlags << "\\Deleted";
0278     std::sort(expectedFlags.begin(), expectedFlags.end());
0279     auto store = new StoreJob(session);
0280     store->setSequenceSet(ImapSet(1));
0281     store->setMode(StoreJob::AppendFlags);
0282     store->setFlags(QList<QByteArray>() << "\\Deleted");
0283     store->exec();
0284     Q_ASSERT_X(store->error() == 0, "StoreJob", store->errorString().toLocal8Bit().constData());
0285 
0286     QList<QByteArray> resultingFlags = store->resultingFlags()[1];
0287     std::sort(resultingFlags.begin(), resultingFlags.end());
0288     if (expectedFlags != resultingFlags) {
0289         qDebug() << resultingFlags;
0290     }
0291     Q_ASSERT(expectedFlags == resultingFlags);
0292 
0293     select = new SelectJob(session);
0294     select->setMailBox(QStringLiteral("INBOX"));
0295     select->exec();
0296 
0297     // cleanup
0298     auto deletejob = new DeleteJob(session);
0299     deletejob->setMailBox(QStringLiteral("INBOX/TestFolder"));
0300     deletejob->exec();
0301     deletejob = new DeleteJob(session);
0302     deletejob->setMailBox(QStringLiteral("INBOX/RenamedTestFolder"));
0303     deletejob->exec();
0304 }
0305 
0306 void testRename(Session *session)
0307 {
0308     qDebug() << "TESTING: RENAME";
0309     // setup
0310     auto create = new CreateJob(session);
0311     create->setMailBox(QStringLiteral("INBOX/TestFolder"));
0312     create->exec();
0313 
0314     qDebug() << "Listing mailboxes with name TestFolder:";
0315     listFolders(session, true, QStringLiteral("TestFolder"));
0316 
0317     // actual tests
0318     qDebug() << "Renaming to RenamedTestFolder";
0319     auto rename = new RenameJob(session);
0320     rename->setSourceMailBox(QStringLiteral("INBOX/TestFolder"));
0321     rename->setDestinationMailBox(QStringLiteral("INBOX/RenamedTestFolder"));
0322     rename->exec();
0323 
0324     qDebug() << "Listing mailboxes with name TestFolder:";
0325     listFolders(session, true, QStringLiteral("TestFolder"));
0326     qDebug() << "Listing mailboxes with name RenamedTestFolder:";
0327     listFolders(session, true, QStringLiteral("RenamedTestFolder"));
0328 
0329     // cleanup
0330     auto deletejob = new DeleteJob(session);
0331     deletejob->setMailBox(QStringLiteral("INBOX/TestFolder"));
0332     deletejob->exec();
0333     deletejob = new DeleteJob(session);
0334     deletejob->setMailBox(QStringLiteral("INBOX/RenamedTestFolder"));
0335     deletejob->exec();
0336 }
0337 
0338 void testSubscribe(Session *session)
0339 {
0340     qDebug() << "TESTING: SUBSCRIBE/UNSUBSCRIBE";
0341     // setup
0342     auto create = new CreateJob(session);
0343     create->setMailBox(QStringLiteral("INBOX/TestFolder"));
0344     create->exec();
0345 
0346     qDebug() << "Listing  subscribed mailboxes with name TestFolder:";
0347     listFolders(session, false, QStringLiteral("TestFolder"));
0348 
0349     // actual tests
0350     qDebug() << "Subscribing to INBOX/TestFolder";
0351     auto subscribe = new SubscribeJob(session);
0352     subscribe->setMailBox(QStringLiteral("INBOX/TestFolder"));
0353     subscribe->exec();
0354 
0355     qDebug() << "Listing  subscribed mailboxes with name TestFolder:";
0356     listFolders(session, false, QStringLiteral("TestFolder"));
0357 
0358     qDebug() << "Unsubscribing from INBOX/TestFolder";
0359     auto unsubscribe = new UnsubscribeJob(session);
0360     unsubscribe->setMailBox(QStringLiteral("INBOX/TestFolder"));
0361     unsubscribe->exec();
0362 
0363     qDebug() << "Listing  subscribed mailboxes with name TestFolder:";
0364     listFolders(session, false, QStringLiteral("TestFolder"));
0365 
0366     // cleanup
0367     auto deletejob = new DeleteJob(session);
0368     deletejob->setMailBox(QStringLiteral("INBOX/TestFolder"));
0369     deletejob->exec();
0370 }
0371 
0372 void testDelete(Session *session)
0373 {
0374     qDebug() << "TESTING: DELETE";
0375     qDebug() << "Creating INBOX/TestFolder:";
0376     auto create = new CreateJob(session);
0377     create->setMailBox(QStringLiteral("INBOX/TestFolder"));
0378     create->exec();
0379 
0380     qDebug() << "Listing  with name TestFolder  before DELETE:";
0381     listFolders(session, true, QStringLiteral("TestFolder"));
0382 
0383     qDebug() << "Deleting INBOX/TestFolder";
0384     auto deletejob = new DeleteJob(session);
0385     deletejob->setMailBox(QStringLiteral("INBOX/TestFolder"));
0386     deletejob->exec();
0387 
0388     qDebug() << "Listing with name TestFolder after DELETE:";
0389     listFolders(session, true, QStringLiteral("TestFolder"));
0390 }
0391 
0392 int main(int argc, char **argv)
0393 {
0394     QCoreApplication::setApplicationName(QStringLiteral("TestImapServer"));
0395 
0396     if (argc < 4) {
0397         qCritical() << "Not enough parameters, expecting: <server> <user> <password>";
0398     }
0399 
0400     QString server = QString::fromLocal8Bit(argv[1]);
0401     int port = 143;
0402     if (server.count(QLatin1Char(':')) == 1) {
0403         const QStringList lstSplit = server.split(QLatin1Char(':'));
0404         port = lstSplit.last().toInt();
0405         server = lstSplit.first();
0406     }
0407     QString user = QString::fromLocal8Bit(argv[2]);
0408     QString password = QString::fromLocal8Bit(argv[3]);
0409 
0410     qDebug() << "Querying:" << server << port << user << password;
0411     qDebug();
0412 
0413     QCoreApplication app(argc, argv);
0414     Session session(server, port);
0415     UiProxy::Ptr proxy(new UiProxy());
0416     session.setUiProxy(proxy);
0417 
0418     qDebug() << "Logging in...";
0419     auto login = new LoginJob(&session);
0420     // login->setEncryptionMode( LoginJob::TlsV1 );
0421     // login->setAuthenticationMode( LoginJob::Plain );
0422     login->setUserName(user);
0423     login->setPassword(password);
0424     login->exec();
0425     qDebug();
0426 
0427     /*if (login->encryptionMode() == LoginJob::Unencrypted)
0428     {
0429       qDebug() << "Encrypted login not possible, try to log in without encryption";
0430       login = new LoginJob( &session );
0431       login->setUserName( user );
0432       login->setPassword( password );
0433       login->exec();
0434       Q_ASSERT_X( login->error() == 0, "LoginJob", login->errorString().toLocal8Bit().constData() );
0435       Q_ASSERT( session.state() == Session::Authenticated );
0436       qDebug();
0437 
0438     }*/
0439 
0440     qDebug() << "Server greeting:" << session.serverGreeting();
0441 
0442     qDebug() << "Asking for capabilities:";
0443     auto capabilities = new CapabilitiesJob(&session);
0444     capabilities->exec();
0445     Q_ASSERT_X(capabilities->error() == 0, "CapabilitiesJob", capabilities->errorString().toLocal8Bit().constData());
0446     Q_ASSERT(session.state() == Session::Authenticated);
0447     qDebug() << capabilities->capabilities();
0448     qDebug();
0449 
0450     qDebug() << "Asking for namespaces:";
0451     auto namespaces = new NamespaceJob(&session);
0452     namespaces->exec();
0453     Q_ASSERT_X(namespaces->error() == 0, "CapabilitiesJob", namespaces->errorString().toLocal8Bit().constData());
0454     Q_ASSERT(session.state() == Session::Authenticated);
0455 
0456     qDebug() << "Contains empty namespace:" << namespaces->containsEmptyNamespace();
0457 
0458     qDebug() << "Personal:";
0459     const auto personalNamespaces = namespaces->personalNamespaces();
0460     for (MailBoxDescriptor ns : personalNamespaces) {
0461         qDebug() << ns.separator << ns.name;
0462     }
0463 
0464     qDebug() << "User:    ";
0465     const auto userNamespaces = namespaces->userNamespaces();
0466     for (MailBoxDescriptor ns : userNamespaces) {
0467         qDebug() << ns.separator << ns.name;
0468     }
0469 
0470     qDebug() << "Shared:  ";
0471     const auto sharedNamespaces = namespaces->sharedNamespaces();
0472     for (MailBoxDescriptor ns : sharedNamespaces) {
0473         qDebug() << ns.separator << ns.name;
0474     }
0475     qDebug();
0476 
0477     qDebug() << "Listing mailboxes:";
0478     listFolders(&session);
0479     Q_ASSERT(session.state() == Session::Authenticated);
0480 
0481     qDebug() << "Selecting INBOX:";
0482     auto select = new SelectJob(&session);
0483     select->setMailBox(QStringLiteral("INBOX"));
0484     select->exec();
0485     Q_ASSERT_X(select->error() == 0, "SelectJob", select->errorString().toLocal8Bit().constData());
0486     Q_ASSERT(session.state() == Session::Selected);
0487     qDebug() << "Flags:" << select->flags();
0488     qDebug() << "Permanent flags:" << select->permanentFlags();
0489     qDebug() << "Total Number of Messages:" << select->messageCount();
0490     qDebug() << "Number of recent Messages:" << select->recentCount();
0491     qDebug() << "First Unseen Message Index:" << select->firstUnseenIndex();
0492     qDebug() << "UID validity:" << select->uidValidity();
0493     qDebug() << "Next UID:" << select->nextUid();
0494     qDebug();
0495 
0496     qDebug() << "Fetching first 3 messages headers:";
0497     auto fetch = new FetchJob(&session);
0498     FetchJob::FetchScope scope;
0499     fetch->setSequenceSet(ImapSet(1, 3));
0500     scope.parts.clear();
0501     scope.mode = FetchJob::FetchScope::Headers;
0502     fetch->setScope(scope);
0503     QMap<qint64, qint64> sizes;
0504     QMap<qint64, MessagePtr> messages;
0505 
0506     QObject::connect(fetch,
0507                      static_cast<HeadersReceivedSignal>(&FetchJob::headersReceived),
0508                      fetch,
0509                      [&](const QString &,
0510                          const QMap<qint64, qint64> &,
0511                          const QMap<qint64, qint64> &sizes_,
0512                          const QMap<qint64, MessageFlags> &,
0513                          const QMap<qint64, MessagePtr> &msgs_) {
0514                          sizes = sizes_;
0515                          messages = msgs_;
0516                      });
0517     fetch->exec();
0518     Q_ASSERT_X(fetch->error() == 0, "FetchJob", fetch->errorString().toLocal8Bit().constData());
0519     Q_ASSERT(session.state() == Session::Selected);
0520     const auto messagesKey = messages.keys();
0521     for (qint64 id : messagesKey) {
0522         qDebug() << "* Message" << id << "(" << sizes[id] << "bytes )";
0523         qDebug() << "  From      :" << messages[id]->from()->asUnicodeString();
0524         qDebug() << "  To        :" << messages[id]->to()->asUnicodeString();
0525         qDebug() << "  Date      :" << messages[id]->date()->asUnicodeString();
0526         qDebug() << "  Subject   :" << messages[id]->subject()->asUnicodeString();
0527         qDebug() << "  Message-ID:" << messages[id]->messageID()->asUnicodeString();
0528     }
0529     qDebug();
0530 
0531     qDebug() << "Fetching first 3 messages flags:";
0532     fetch = new FetchJob(&session);
0533     fetch->setSequenceSet(ImapSet(1, 3));
0534     scope.parts.clear();
0535     scope.mode = FetchJob::FetchScope::Flags;
0536     fetch->setScope(scope);
0537     QMap<qint64, MessageFlags> flags;
0538     QObject::connect(fetch,
0539                      static_cast<HeadersReceivedSignal>(&FetchJob::headersReceived),
0540                      fetch,
0541                      [&](const QString &,
0542                          const QMap<qint64, qint64> &,
0543                          const QMap<qint64, qint64> &,
0544                          const QMap<qint64, MessageFlags> &flags_,
0545                          const QMap<qint64, MessagePtr> &) {
0546                          flags = flags_;
0547                      });
0548     fetch->exec();
0549     Q_ASSERT_X(fetch->error() == 0, "FetchJob", fetch->errorString().toLocal8Bit().constData());
0550     Q_ASSERT(session.state() == Session::Selected);
0551     const auto flagsKey = flags.keys();
0552     for (qint64 id : flagsKey) {
0553         qDebug() << "* Message" << id << "flags:" << flags[id];
0554     }
0555     qDebug();
0556 
0557     qDebug() << "Fetching first message structure:";
0558     fetch = new FetchJob(&session);
0559     fetch->setSequenceSet(ImapSet(1));
0560     scope.parts.clear();
0561     scope.mode = FetchJob::FetchScope::Structure;
0562     fetch->setScope(scope);
0563     QObject::connect(fetch,
0564                      static_cast<HeadersReceivedSignal>(&FetchJob::headersReceived),
0565                      fetch,
0566                      [&](const QString &,
0567                          const QMap<qint64, qint64> &,
0568                          const QMap<qint64, qint64> &,
0569                          const QMap<qint64, MessageFlags> &,
0570                          const QMap<qint64, MessagePtr> &msgs_) {
0571                          messages = msgs_;
0572                      });
0573     fetch->exec();
0574     Q_ASSERT_X(fetch->error() == 0, "FetchJob", fetch->errorString().toLocal8Bit().constData());
0575     Q_ASSERT(session.state() == Session::Selected);
0576     MessagePtr message = messages[1];
0577     dumpContentHelper(message.data());
0578     qDebug();
0579 
0580     qDebug() << "Fetching first message second part headers:";
0581     fetch = new FetchJob(&session);
0582     fetch->setSequenceSet(ImapSet(1));
0583     scope.parts.clear();
0584     scope.parts << "2";
0585     scope.mode = FetchJob::FetchScope::Headers;
0586     fetch->setScope(scope);
0587     QMap<qint64, MessageParts> allParts;
0588     QObject::connect(fetch,
0589                      static_cast<PartsReceivedSignal>(&FetchJob::partsReceived),
0590                      fetch,
0591                      [&](const QString &, const QMap<qint64, qint64> &, const QMap<qint64, MessageParts> &parts_) {
0592                          allParts = parts_;
0593                      });
0594     fetch->exec();
0595     Q_ASSERT_X(fetch->error() == 0, "FetchJob", fetch->errorString().toLocal8Bit().constData());
0596     Q_ASSERT(session.state() == Session::Selected);
0597     const auto allkeys = allParts.keys();
0598     for (qint64 id : allkeys) {
0599         qDebug() << "* Message" << id << "parts headers";
0600         MessageParts parts = allParts[id];
0601         const auto parsKeys = parts.keys();
0602         for (const QByteArray &partId : parsKeys) {
0603             qDebug() << "  ** Part" << partId;
0604             qDebug() << "     Name       :" << parts[partId]->contentType()->name();
0605             qDebug() << "     Mimetype   :" << parts[partId]->contentType()->mimeType();
0606             qDebug() << "     Description:" << parts[partId]->contentDescription()->asUnicodeString().simplified();
0607         }
0608     }
0609     qDebug();
0610 
0611     qDebug() << "Fetching first message second part content:";
0612     fetch = new FetchJob(&session);
0613     fetch->setSequenceSet(ImapSet(1));
0614     scope.parts.clear();
0615     scope.parts << "2";
0616     scope.mode = FetchJob::FetchScope::Content;
0617     fetch->setScope(scope);
0618     QObject::connect(fetch,
0619                      static_cast<PartsReceivedSignal>(&FetchJob::partsReceived),
0620                      fetch,
0621                      [&](const QString &, const QMap<qint64, qint64> &, const QMap<qint64, MessageParts> &parts_) {
0622                          allParts = parts_;
0623                      });
0624     fetch->exec();
0625     Q_ASSERT_X(fetch->error() == 0, "FetchJob", fetch->errorString().toLocal8Bit().constData());
0626     Q_ASSERT(session.state() == Session::Selected);
0627     const auto allpartskeys = allParts.keys();
0628     for (int id : allpartskeys) {
0629         MessageParts parts = allParts[id];
0630         const auto partsKeys = parts.keys();
0631         for (const QByteArray &partId : partsKeys) {
0632             qDebug() << "* Message" << id << "part" << partId << "content:";
0633             qDebug() << parts[partId]->body();
0634         }
0635     }
0636     qDebug();
0637 
0638     testDelete(&session);
0639 
0640     testSubscribe(&session);
0641 
0642     testRename(&session);
0643 
0644     testAppendAndStore(&session);
0645 
0646     testAcl(&session, user);
0647 
0648     testMetaData(&session);
0649 
0650     qDebug() << "Expunge INBOX:";
0651     auto expunge = new ExpungeJob(&session);
0652     expunge->exec();
0653 
0654     qDebug() << "Closing INBOX:";
0655     auto close = new CloseJob(&session);
0656     close->exec();
0657     Q_ASSERT(session.state() == Session::Authenticated);
0658     qDebug();
0659 
0660     qDebug() << "Logging out...";
0661     auto logout = new LogoutJob(&session);
0662     logout->exec();
0663     Q_ASSERT_X(logout->error() == 0, "LogoutJob", logout->errorString().toLocal8Bit().constData());
0664     Q_ASSERT(session.state() == Session::Disconnected);
0665 
0666     return 0;
0667 }