File indexing completed on 2024-06-16 05:01:53

0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
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 <QtTest>
0024 #include "test_Composer_Submission.h"
0025 #include "Utils/FakeCapabilitiesInjector.h"
0026 #include "Composer/MessageComposer.h"
0027 #include "Imap/data.h"
0028 #include "Imap/Model/DragAndDrop.h"
0029 #include "Imap/Model/ItemRoles.h"
0030 #include "Imap/Network/MsgPartNetAccessManager.h"
0031 #include "Streams/FakeSocket.h"
0032 
0033 ComposerSubmissionTest::ComposerSubmissionTest():
0034     m_submission(0), sendingSpy(0), sentSpy(0), requestedSendingSpy(0), requestedBurlSendingSpy(0),
0035     submissionSucceededSpy(0), submissionFailedSpy(0)
0036 {
0037 }
0038 
0039 void ComposerSubmissionTest::init()
0040 {
0041     LibMailboxSync::init();
0042 
0043     // There's a default delay of 50ms which made the cEmpty() and justKeepTask() ignore these pending actions...
0044     model->setProperty("trojita-imap-delayed-fetch-part", QVariant(0u));
0045 
0046     existsA = 5;
0047     uidValidityA = 333666;
0048     uidMapA << 10 << 11 << 12 << 13 << 14;
0049     uidNextA = 15;
0050     helperSyncAWithMessagesEmptyState();
0051 
0052     QCOMPARE(msgListA.model()->index(0, 0, msgListA).data(Imap::Mailbox::RoleMessageSubject), QVariant());
0053     cClient(t.mk("UID FETCH 10:14 (" FETCH_METADATA_ITEMS ")\r\n"));
0054     cServer("* 1 FETCH (BODYSTRUCTURE "
0055             "(\"text\" \"plain\" (\"charset\" \"UTF-8\" \"format\" \"flowed\") NIL NIL \"8bit\" 362 15 NIL NIL NIL)"
0056             " ENVELOPE (NIL \"subj\" NIL NIL NIL NIL NIL NIL NIL \"<msgid>\")"
0057             ")\r\n" +
0058             t.last("OK fetched\r\n"));
0059 
0060     m_msaFactory = new MSA::FakeFactory();
0061     QString accountId = QStringLiteral("fake_account");
0062     m_composer = std::make_shared<Composer::MessageComposer>(model);
0063     m_submission = new Composer::Submission(this, m_composer, model, m_msaFactory, accountId);
0064 
0065     sendingSpy = new QSignalSpy(m_msaFactory, SIGNAL(sending()));
0066     sentSpy = new QSignalSpy(m_msaFactory, SIGNAL(sent()));
0067     requestedSendingSpy = new QSignalSpy(m_msaFactory, SIGNAL(requestedSending(QByteArray,QList<QByteArray>,QByteArray)));
0068     requestedBurlSendingSpy = new QSignalSpy(m_msaFactory, SIGNAL(requestedBurlSending(QByteArray,QList<QByteArray>,QByteArray)));
0069 
0070     submissionSucceededSpy = new QSignalSpy(m_submission, SIGNAL(succeeded()));
0071     submissionFailedSpy = new QSignalSpy(m_submission, SIGNAL(failed(QString)));
0072 }
0073 
0074 void ComposerSubmissionTest::cleanup()
0075 {
0076     LibMailboxSync::cleanup();
0077 
0078     delete m_submission;
0079     m_submission = 0;
0080     m_composer.reset();
0081     delete m_msaFactory;
0082     m_msaFactory = 0;
0083     delete sendingSpy;
0084     sendingSpy = 0;
0085     delete sentSpy;
0086     sentSpy = 0;
0087     delete requestedSendingSpy;
0088     requestedSendingSpy = 0;
0089     delete requestedBurlSendingSpy;
0090     requestedBurlSendingSpy = 0;
0091     delete submissionSucceededSpy;
0092     submissionSucceededSpy = 0;
0093     delete submissionFailedSpy;
0094     submissionFailedSpy = 0;
0095 }
0096 
0097 /** @short Test that we can send a very simple mail */
0098 void ComposerSubmissionTest::testEmptySubmission()
0099 {
0100     // Try sending an empty mail
0101     m_submission->send();
0102 
0103     QVERIFY(sendingSpy->isEmpty());
0104     QVERIFY(sentSpy->isEmpty());
0105     QCOMPARE(requestedSendingSpy->size(), 1);
0106 
0107     // This is a fake MSA implementation, so we have to confirm that sending has begun
0108     m_msaFactory->doEmitSending();
0109     QCOMPARE(sendingSpy->size(), 1);
0110     QCOMPARE(sentSpy->size(), 0);
0111 
0112     m_msaFactory->doEmitSent();
0113 
0114     QCOMPARE(sendingSpy->size(), 1);
0115     QCOMPARE(sentSpy->size(), 1);
0116 
0117     QCOMPARE(submissionSucceededSpy->size(), 1);
0118     QCOMPARE(submissionFailedSpy->size(), 0);
0119     cEmpty();
0120 }
0121 
0122 void ComposerSubmissionTest::testSimpleSubmission()
0123 {
0124     m_composer->setFrom(
0125                 Imap::Message::MailAddress(QStringLiteral("Foo Bar"), QString(),
0126                                            QStringLiteral("foo.bar"), QStringLiteral("example.org")));
0127     m_composer->setSubject(QStringLiteral("testing"));
0128     m_composer->setText(QStringLiteral("Sample message"));
0129 
0130     m_submission->send();
0131     QCOMPARE(requestedSendingSpy->size(), 1);
0132     m_msaFactory->doEmitSending();
0133     QCOMPARE(sendingSpy->size(), 1);
0134     m_msaFactory->doEmitSent();
0135     QCOMPARE(sentSpy->size(), 1);
0136 
0137     QCOMPARE(submissionSucceededSpy->size(), 1);
0138     QCOMPARE(submissionFailedSpy->size(), 0);
0139 
0140     QVERIFY(requestedSendingSpy->size() == 1 &&
0141             requestedSendingSpy->at(0).size() == 3 &&
0142             requestedSendingSpy->at(0)[2].toByteArray().contains("Sample message"));
0143     cEmpty();
0144 
0145     //qDebug() << requestedSendingSpy->front();
0146 }
0147 
0148 void ComposerSubmissionTest::testSimpleSubmissionWithAppendFailed()
0149 {
0150     helperTestSimpleAppend(false, false, false, false);
0151 }
0152 
0153 
0154 void ComposerSubmissionTest::testSimpleSubmissionWithAppendNoAppenduid()
0155 {
0156     helperTestSimpleAppend(true, false, false, false);
0157 }
0158 
0159 void ComposerSubmissionTest::testSimpleSubmissionWithAppendAppenduid()
0160 {
0161     helperTestSimpleAppend(true, true, false, false);
0162 }
0163 
0164 void ComposerSubmissionTest::testSimpleSubmissionReplyingToOk()
0165 {
0166     helperTestSimpleAppend(true, true, true, true);
0167 }
0168 
0169 void ComposerSubmissionTest::testSimpleSubmissionReplyingToFailedFlags()
0170 {
0171     helperTestSimpleAppend(true, true, true, false);
0172 }
0173 
0174 void ComposerSubmissionTest::helperSetupProperHeaders()
0175 {
0176     m_composer->setFrom(
0177                 Imap::Message::MailAddress(QStringLiteral("Foo Bar"), QString(),
0178                                            QStringLiteral("foo.bar"), QStringLiteral("example.org")));
0179     m_composer->setSubject(QStringLiteral("testing"));
0180     m_composer->setText(QStringLiteral("Sample message"));
0181     m_submission->setImapOptions(true, QStringLiteral("outgoing"), QStringLiteral("somehost"), QStringLiteral("userfred"), false);
0182 }
0183 
0184 void ComposerSubmissionTest::helperTestSimpleAppend(bool appendOk, bool appendUid,
0185                                                     bool shallUpdateReplyingTo, bool replyingToUpdateOk)
0186 {
0187     // Don't bother with literal processing
0188     FakeCapabilitiesInjector injector(model);
0189     injector.injectCapability(QStringLiteral("LITERAL+"));
0190 
0191     helperSetupProperHeaders();
0192 
0193     if (shallUpdateReplyingTo) {
0194         QModelIndex msgA10 = model->index(0, 0, msgListA);
0195         QVERIFY(msgA10.isValid());
0196         QCOMPARE(msgA10.data(Imap::Mailbox::RoleMessageUid).toUInt(), uidMapA[0]);
0197         m_composer->setReplyingToMessage(msgA10);
0198     }
0199     m_submission->send();
0200 
0201     // We are waiting for APPEND to finish here
0202     QCOMPARE(requestedSendingSpy->size(), 0);
0203 
0204     for (int i=0; i<5; ++i)
0205         QCoreApplication::processEvents();
0206     QString sentSoFar = QString::fromUtf8(SOCK->writtenStuff());
0207     QString expected = t.mk("APPEND outgoing (\\Seen) ");
0208     QCOMPARE(sentSoFar.left(expected.size()), expected);
0209     cEmpty();
0210     QCOMPARE(requestedSendingSpy->size(), 0);
0211 
0212     if (!appendOk) {
0213         cServer(t.last("NO append failed\r\n"));
0214         cEmpty();
0215 
0216         QCOMPARE(submissionSucceededSpy->size(), 0);
0217         QCOMPARE(submissionFailedSpy->size(), 1);
0218         QCOMPARE(requestedSendingSpy->size(), 0);
0219         justKeepTask();
0220         return;
0221     }
0222 
0223     // Assume the APPEND has suceeded
0224     if (appendUid) {
0225         cServer(t.last("OK [APPENDUID 666333666 123] append done\r\n"));
0226     } else {
0227         cServer(t.last("OK append done\r\n"));
0228     }
0229     cEmpty();
0230 
0231     QCOMPARE(requestedSendingSpy->size(), 1);
0232     m_msaFactory->doEmitSending();
0233     QCOMPARE(sendingSpy->size(), 1);
0234     m_msaFactory->doEmitSent();
0235     QCOMPARE(sentSpy->size(), 1);
0236 
0237     QCOMPARE(submissionFailedSpy->size(), 0);
0238     if (shallUpdateReplyingTo) {
0239         QCOMPARE(submissionSucceededSpy->size(), 0);
0240         cClient(t.mk("UID STORE ") + QByteArray::number(uidMapA[0]) + " +FLAGS (\\Answered)\r\n");
0241         if (replyingToUpdateOk) {
0242             cServer(t.last("OK flags updated\r\n"));
0243         } else {
0244             cServer(t.last("NO you aren't going to update flags that easily\r\n"));
0245         }
0246         for (int i=0; i<5; ++i)
0247             QCoreApplication::processEvents();
0248     }
0249     QCOMPARE(submissionSucceededSpy->size(), 1);
0250     QCOMPARE(submissionFailedSpy->size(), 0);
0251 
0252     QVERIFY(requestedSendingSpy->size() == 1 &&
0253             requestedSendingSpy->at(0).size() == 3 &&
0254             requestedSendingSpy->at(0)[2].toByteArray().contains("Sample message"));
0255     cEmpty();
0256 
0257     //qDebug() << requestedSendingSpy->front();
0258 }
0259 
0260 /** @short Check that a missing file attachment prevents submission */
0261 void ComposerSubmissionTest::testMissingFileAttachmentSmtpSave()
0262 {
0263     helperMissingAttachment(true, false, false, true);
0264 }
0265 
0266 /** @short Check that a missing file attachment prevents submission */
0267 void ComposerSubmissionTest::testMissingFileAttachmentSmtpNoSave()
0268 {
0269     helperMissingAttachment(false, false, false, true);
0270 }
0271 
0272 /** @short Check that a missing file attachment prevents submission */
0273 void ComposerSubmissionTest::testMissingFileAttachmentBurlSave()
0274 {
0275     helperMissingAttachment(true, true, false, true);
0276 }
0277 
0278 /** @short Check that a missing file attachment prevents submission */
0279 void ComposerSubmissionTest::testMissingFileAttachmentBurlNoSave()
0280 {
0281     helperMissingAttachment(false, true, false, true);
0282 }
0283 
0284 /** @short Check that a missing file attachment prevents submission */
0285 void ComposerSubmissionTest::testMissingFileAttachmentImap()
0286 {
0287     helperMissingAttachment(true, false, true, true);
0288 }
0289 
0290 /** @short Check that a missing file attachment prevents submission */
0291 void ComposerSubmissionTest::testMissingImapAttachmentSmtpSave()
0292 {
0293     helperMissingAttachment(true, false, false, false);
0294 }
0295 
0296 /** @short Check that a missing file attachment prevents submission */
0297 void ComposerSubmissionTest::testMissingImapAttachmentSmtpNoSave()
0298 {
0299     helperMissingAttachment(false, false, false, false);
0300 }
0301 
0302 /** @short Check that a missing file attachment prevents submission */
0303 void ComposerSubmissionTest::testMissingImapAttachmentBurlSave()
0304 {
0305     helperMissingAttachment(true, true, false, false);
0306 }
0307 
0308 /** @short Check that a missing file attachment prevents submission */
0309 void ComposerSubmissionTest::testMissingImapAttachmentBurlNoSave()
0310 {
0311     helperMissingAttachment(false, true, false, false);
0312 }
0313 
0314 /** @short Check that a missing file attachment prevents submission */
0315 void ComposerSubmissionTest::testMissingImapAttachmentImap()
0316 {
0317     helperMissingAttachment(true, false, true, false);
0318 }
0319 
0320 void ComposerSubmissionTest::helperMissingAttachment(bool save, bool burl, bool imap, bool attachingFile)
0321 {
0322 #ifdef Q_OS_OS2
0323     QSKIP("Looks like QTemporaryFile is broken on OS/2");
0324 #endif
0325     helperSetupProperHeaders();
0326 
0327     if (imap) {
0328         Q_ASSERT(save);
0329     }
0330 
0331     QPersistentModelIndex msgA10 = model->index(0, 0, msgListA);
0332     QVERIFY(msgA10.isValid());
0333     QCOMPARE(msgA10.data(Imap::Mailbox::RoleMessageUid).toUInt(), uidMapA[0]);
0334 
0335     m_submission->setImapOptions(save, QStringLiteral("meh"), QStringLiteral("pwn"), QStringLiteral("bob"), imap);
0336     m_submission->setSmtpOptions(burl, QStringLiteral("pwn"));
0337     m_composer->setReplyingToMessage(msgA10);
0338     m_msaFactory->setBurlSupport(burl);
0339     m_msaFactory->setImapSupport(imap);
0340 
0341     if (attachingFile) {
0342         // needs a special block for proper RAII-based removal
0343         QTemporaryFile tempFile;
0344         tempFile.open();
0345         tempFile.write("Sample attachment for Trojita's ComposerSubmissionTest\r\n");
0346         QCOMPARE(m_composer->addFileAttachment(tempFile.fileName()), true);
0347         // The file gets deleted as soon as we leave this scope
0348     } else {
0349         // Attaching something which lives on the IMAP server
0350 
0351         // Make sure the IMAP bits are ready
0352         QCOMPARE(model->rowCount(msgA10), 1);
0353         QPersistentModelIndex partData = model->index(0, 0, msgA10);
0354         QVERIFY(partData.isValid());
0355 
0356         helperAttachImapPart(0, "/0");
0357         cClient(t.mk("UID FETCH ") + QByteArray::number(uidMapA[0]) + " (BODY.PEEK[1])\r\n");
0358     }
0359 
0360     m_submission->send();
0361 
0362     if (!attachingFile) {
0363         // Deliver the queued data to make the generic test prologue happy
0364         cServer("* 1 FETCH (BODY[1] \"contents\")\r\n");
0365         cServer(t.last("OK fetched\r\n"));
0366     }
0367     QCOMPARE(requestedSendingSpy->size(), 0);
0368     QCOMPARE(submissionSucceededSpy->size(), 0);
0369     QCOMPARE(submissionFailedSpy->size(), 1);
0370     cEmpty();
0371     justKeepTask();
0372 }
0373 
0374 void ComposerSubmissionTest::helperAttachImapPart(const int row, const QByteArray mimePart)
0375 {
0376     QScopedPointer<QMimeData> mimeData(new QMimeData());
0377     QByteArray encodedData;
0378     QDataStream stream(&encodedData, QIODevice::WriteOnly);
0379     stream.setVersion(QDataStream::Qt_4_6);
0380     stream << QStringLiteral("a") << uidValidityA << uidMapA[row] << mimePart;
0381     mimeData->setData(Imap::MimeTypes::xTrojitaImapPart, encodedData);
0382     auto partIndex = Imap::Network::MsgPartNetAccessManager::pathToPart(msgListA.model()->index(row, 0, msgListA), mimePart);
0383     QVERIFY(partIndex.isValid());
0384     QCOMPARE(m_composer->dropMimeData(mimeData.data(), Qt::CopyAction, partIndex.row(), partIndex.column(), partIndex),
0385              true);
0386 }
0387 
0388 void ComposerSubmissionTest::helperAttachImapMessage(const uint uid)
0389 {
0390     QScopedPointer<QMimeData> mimeData(new QMimeData());
0391     QByteArray encodedData;
0392     QDataStream stream(&encodedData, QIODevice::WriteOnly);
0393     stream.setVersion(QDataStream::Qt_4_6);
0394     stream << QStringLiteral("a") << uidValidityA << (QList<uint>() << uid);
0395     mimeData->setData(Imap::MimeTypes::xTrojitaMessageList, encodedData);
0396     QCOMPARE(m_composer->dropMimeData(mimeData.data(), Qt::CopyAction, 0, 0, QModelIndex()), true);
0397 }
0398 
0399 #define EXTRACT_TARILING_NUMBER(NUM) \
0400 { \
0401     QCOMPARE(sentSoFar.left(expected.size()), expected); \
0402     bool numberOk = false; \
0403     QString numericPart = sentSoFar.mid(expected.size()); \
0404     QCOMPARE(numericPart.right(3), QString::fromUtf8("}\r\n")); \
0405     int num = numericPart.leftRef(numericPart.size() - 3).toInt(&numberOk); \
0406     QVERIFY(numberOk); \
0407     NUM = num; \
0408 }
0409 
0410 #define EAT_OCTETS(NUM, OUT) \
0411 { \
0412     for (int i=0; i<5; ++i) \
0413         QCoreApplication::processEvents(); \
0414     QString buf = QString::fromUtf8(SOCK->writtenStuff()); \
0415     QVERIFY(buf.size() > NUM); \
0416     OUT = buf.mid(NUM); \
0417 }
0418 
0419 #define EXTRACT_OCTETS(NUM, PRIOR, REST) \
0420 { \
0421     for (int i=0; i<5; ++i) \
0422         QCoreApplication::processEvents(); \
0423     QString buf = QString::fromUtf8(SOCK->writtenStuff()); \
0424     QVERIFY(buf.size() > NUM); \
0425     PRIOR = buf.left(NUM); \
0426     REST = buf.mid(NUM); \
0427 }
0428 
0429 
0430 void ComposerSubmissionTest::testBurlSubmission()
0431 {
0432     FakeCapabilitiesInjector injector(model);
0433     injector.injectCapability(QStringLiteral("CATENATE"));
0434     injector.injectCapability(QStringLiteral("URLAUTH"));
0435     helperSetupProperHeaders();
0436     m_submission->setImapOptions(true, QStringLiteral("meh"), QStringLiteral("host"), QStringLiteral("usr"), false);
0437     m_submission->setSmtpOptions(true, QStringLiteral("smtpUser"));
0438     m_msaFactory->setBurlSupport(true);
0439 
0440     helperAttachImapPart(0, "/0");
0441     m_submission->send();
0442 
0443     for (int i=0; i<5; ++i)
0444         QCoreApplication::processEvents();
0445     QString sentSoFar = QString::fromUtf8(SOCK->writtenStuff());
0446     QString expected = t.mk("APPEND meh (\\Seen) CATENATE (TEXT {");
0447     int octets;
0448     EXTRACT_TARILING_NUMBER(octets);
0449     cServer("+ carry on\r\n");
0450     EAT_OCTETS(octets, sentSoFar);
0451     expected = QStringLiteral(" URL \"/a;UIDVALIDITY=333666/;UID=10/;SECTION=1\" TEXT {");
0452     EXTRACT_TARILING_NUMBER(octets);
0453     cServer("+ carry on\r\n");
0454     EAT_OCTETS(octets, sentSoFar);
0455     QCOMPARE(sentSoFar, QString::fromUtf8(")\r\n"));
0456     cServer(t.last("OK [APPENDUID 666333666 123] append done\r\n"));
0457     cClient(t.mk("GENURLAUTH \"imap://usr@host/meh;UIDVALIDITY=666333666/;UID=123;urlauth=submit+smtpUser\" INTERNAL\r\n"));
0458     // This is an actual example from RFC 4467
0459     QString genAuthUrl = QStringLiteral("imap://joe@example.com/INBOX/;uid=20/;section=1.2;urlauth=submit+fred:internal:91354a473744909de610943775f92038");
0460     cServer("* GENURLAUTH \"" + genAuthUrl.toUtf8() + "\"\r\n");
0461     cServer(t.last("OK genurlauth\r\n"));
0462     cEmpty();
0463     QCOMPARE(requestedSendingSpy->size(), 0);
0464     QCOMPARE(requestedBurlSendingSpy->size(), 1);
0465     m_msaFactory->doEmitSending();
0466     QCOMPARE(sendingSpy->size(), 1);
0467     m_msaFactory->doEmitSent();
0468     QCOMPARE(sentSpy->size(), 1);
0469 
0470     QCOMPARE(submissionSucceededSpy->size(), 1);
0471     QCOMPARE(submissionFailedSpy->size(), 0);
0472 
0473     QCOMPARE(requestedBurlSendingSpy->size(), 1);
0474     QCOMPARE(requestedBurlSendingSpy->at(0).size(), 3);
0475     QCOMPARE(requestedBurlSendingSpy->at(0)[2].toString(), genAuthUrl);
0476     cEmpty();
0477     justKeepTask();
0478 }
0479 
0480 void ComposerSubmissionTest::testBurlSubmissionAttachedWholeMessage()
0481 {
0482     FakeCapabilitiesInjector injector(model);
0483     injector.injectCapability(QStringLiteral("CATENATE"));
0484     injector.injectCapability(QStringLiteral("URLAUTH"));
0485     helperSetupProperHeaders();
0486     m_submission->setImapOptions(true, QStringLiteral("meh"), QStringLiteral("host"), QStringLiteral("usr"), false);
0487     m_submission->setSmtpOptions(true, QStringLiteral("smtpUser"));
0488     m_msaFactory->setBurlSupport(true);
0489 
0490     helperAttachImapMessage(uidMapA[1]);
0491     m_submission->send();
0492 
0493     for (int i=0; i<5; ++i)
0494         QCoreApplication::processEvents();
0495     QString sentSoFar = QString::fromUtf8(SOCK->writtenStuff());
0496     QString expected = t.mk("APPEND meh (\\Seen) CATENATE (TEXT {");
0497     int octets;
0498     EXTRACT_TARILING_NUMBER(octets);
0499     cServer("+ carry on\r\n");
0500     EAT_OCTETS(octets, sentSoFar);
0501     expected = QStringLiteral(" URL \"/a;UIDVALIDITY=333666/;UID=11\" TEXT {");
0502     EXTRACT_TARILING_NUMBER(octets);
0503     cServer("+ carry on\r\n");
0504     EAT_OCTETS(octets, sentSoFar);
0505     QCOMPARE(sentSoFar, QString::fromUtf8(")\r\n"));
0506     cServer(t.last("OK [APPENDUID 666333666 123] append done\r\n"));
0507     cClient(t.mk("GENURLAUTH \"imap://usr@host/meh;UIDVALIDITY=666333666/;UID=123;urlauth=submit+smtpUser\" INTERNAL\r\n"));
0508     // This is an actual example from RFC 4467
0509     QString genAuthUrl = QStringLiteral("imap://joe@example.com/INBOX/;uid=20;urlauth=submit+fred:internal:91354a473744909de610943775f92038");
0510     cServer("* GENURLAUTH \"" + genAuthUrl.toUtf8() + "\"\r\n");
0511     cServer(t.last("OK genurlauth\r\n"));
0512     cEmpty();
0513     QCOMPARE(requestedSendingSpy->size(), 0);
0514     QCOMPARE(requestedBurlSendingSpy->size(), 1);
0515     m_msaFactory->doEmitSending();
0516     QCOMPARE(sendingSpy->size(), 1);
0517     m_msaFactory->doEmitSent();
0518     QCOMPARE(sentSpy->size(), 1);
0519 
0520     QCOMPARE(submissionSucceededSpy->size(), 1);
0521     QCOMPARE(submissionFailedSpy->size(), 0);
0522 
0523     QCOMPARE(requestedBurlSendingSpy->size(), 1);
0524     QCOMPARE(requestedBurlSendingSpy->at(0).size(), 3);
0525     QCOMPARE(requestedBurlSendingSpy->at(0)[2].toString(), genAuthUrl);
0526     cEmpty();
0527     justKeepTask();
0528 
0529 }
0530 
0531 /** @short Chech that CATENATE is used and BURL disabled when the IMAP server cannot do URLAUTH */
0532 void ComposerSubmissionTest::testCatenateBurlWithoutUrlauth()
0533 {
0534     FakeCapabilitiesInjector injector(model);
0535     injector.injectCapability(QStringLiteral("CATENATE"));
0536     // The URLAUTH is, however, not advertised by the IMAP server
0537     helperSetupProperHeaders();
0538     m_submission->setImapOptions(true, QStringLiteral("meh"), QStringLiteral("host"), QStringLiteral("usr"), false);
0539     m_submission->setSmtpOptions(true, QStringLiteral("smtpUser"));
0540     m_msaFactory->setBurlSupport(true);
0541 
0542     QFETCH(QByteArray, bodystructure);
0543     QFETCH(QByteArray, mimePart);
0544     QFETCH(QByteArray, mimePartIndex);
0545     QFETCH(QByteArray, cte);
0546     QFETCH(QByteArray, rawDataFetch);
0547     QFETCH(QByteArray, rawDataSMTP);
0548 
0549     cServer("* 2 FETCH (UID " + QByteArray::number(uidMapA[1]) + " BODYSTRUCTURE (" + bodystructure + "))\r\n");
0550 
0551     helperAttachImapPart(1, mimePartIndex);
0552     cClient(t.mk("UID FETCH ") + QByteArray::number(uidMapA[1]) + " (BODY.PEEK[" + mimePart + "])\r\n");
0553     cServer("* 2 FETCH (BODY[" + mimePart + "] {" + QByteArray::number(rawDataFetch.size()) + "}\r\n" + rawDataFetch + ")\r\n");
0554     cServer(t.last("OK fetched\r\n"));
0555 
0556     m_submission->send();
0557 
0558     for (int i=0; i<5; ++i)
0559         QCoreApplication::processEvents();
0560     QString sentSoFar = QString::fromUtf8(SOCK->writtenStuff());
0561     QString expected = t.mk("APPEND meh (\\Seen) CATENATE (TEXT {");
0562     int octets;
0563     EXTRACT_TARILING_NUMBER(octets);
0564     cServer("+ carry on\r\n");
0565     QString preambleViaImap;
0566     EXTRACT_OCTETS(octets, preambleViaImap, sentSoFar);
0567     expected = QStringLiteral(" URL \"/a;UIDVALIDITY=333666/;UID=") + QByteArray::number(uidMapA[1]) + "/;SECTION=" + QString::fromUtf8(mimePart) + "\" TEXT {";
0568     EXTRACT_TARILING_NUMBER(octets);
0569     cServer("+ carry on\r\n");
0570     EAT_OCTETS(octets, sentSoFar);
0571     QCOMPARE(sentSoFar, QString::fromUtf8(")\r\n"));
0572     cServer(t.last("OK [APPENDUID 666333666 123] append done\r\n"));
0573     cEmpty();
0574     QCOMPARE(requestedSendingSpy->size(), 1);
0575     QCOMPARE(requestedBurlSendingSpy->size(), 0);
0576     m_msaFactory->doEmitSending();
0577     QCOMPARE(sendingSpy->size(), 1);
0578     m_msaFactory->doEmitSent();
0579     QCOMPARE(sentSpy->size(), 1);
0580 
0581     QCOMPARE(submissionSucceededSpy->size(), 1);
0582     QCOMPARE(submissionFailedSpy->size(), 0);
0583 
0584     QCOMPARE(requestedBurlSendingSpy->size(), 0);
0585     QCOMPARE(requestedSendingSpy->size(), 1);
0586     QCOMPARE(requestedSendingSpy->at(0).size(), 3);
0587     QByteArray outgoingMessage = requestedSendingSpy->at(0)[2].toByteArray();
0588     QVERIFY(outgoingMessage.contains("Subject: testing\r\n"));
0589     QVERIFY(outgoingMessage.contains("Sample message\r\n"));
0590     if (!outgoingMessage.contains(rawDataSMTP)) {
0591         qDebug() << outgoingMessage;
0592         qDebug() << rawDataSMTP;
0593     }
0594     QVERIFY(outgoingMessage.contains(rawDataSMTP));
0595     auto preambleViaSmtp = QString::fromUtf8(outgoingMessage).left(preambleViaImap.size());
0596     QCOMPARE(preambleViaSmtp, preambleViaImap);
0597     cEmpty();
0598     justKeepTask();
0599 }
0600 
0601 void ComposerSubmissionTest::testCatenateBurlWithoutUrlauth_data()
0602 {
0603     QTest::addColumn<QByteArray>("bodystructure");
0604     QTest::addColumn<QByteArray>("mimePart");
0605     QTest::addColumn<QByteArray>("mimePartIndex");
0606     QTest::addColumn<QByteArray>("cte");
0607     QTest::addColumn<QByteArray>("rawDataFetch");
0608     QTest::addColumn<QByteArray>("rawDataSMTP");
0609 
0610     QTest::newRow("plaintext-8bit")
0611             << bsPlaintext
0612             << QByteArray("1")
0613             << QByteArray("/0")
0614             << QByteArray("8bit")
0615             << QByteArray("contents fetched over IMAP")
0616             << QByteArray("\r\n"
0617                           "Content-Type: text/plain\r\n"
0618                           "Content-Disposition: attachment;\r\n"
0619                           "\tfilename=attachment\r\n"
0620                           "Content-Transfer-Encoding: 7bit\r\n"
0621                           "\r\n"
0622                           "contents fetched over IMAP");
0623 
0624     QTest::newRow("plaintext-quoted-printable")
0625             << bsMultipartSignedTextPlain
0626             << QByteArray("1")
0627             << QByteArray("/0/0")
0628             << QByteArray("quoted-printable")
0629             << QByteArray("pwn=20zor are usually found on very, very long lines indeed, which are looooong anyway.")
0630             << QByteArray("\r\n"
0631                           "Content-Type: text/plain\r\n"
0632                           "Content-Disposition: attachment;\r\n"
0633                           "\tfilename=attachment\r\n"
0634                           "Content-Transfer-Encoding: quoted-printable\r\n"
0635                           "\r\n"
0636                           "pwn zor are usually found on very, very long lines indeed, which are looooong=\r\n"
0637                           " anyway.\r\n"
0638                           "--trojita=");
0639 
0640     QTest::newRow("base64-part")
0641             << bsManyPlaintexts
0642             << QByteArray("2")
0643             << QByteArray("/0/1")
0644             << QByteArray("base64")
0645             << QByteArray("meh wtf").toBase64()
0646             << QByteArray("\r\n"
0647                           "Content-Type: plain/plain\r\n"
0648                           "Content-Disposition: attachment;\r\n"
0649                           "\tfilename=attachment\r\n"
0650                           "Content-Transfer-Encoding: base64\r\n"
0651                           "\r\n"
0652                           "bWVoIHd0Zg==\r\n\r\n"
0653                           "--trojita=");
0654 }
0655 
0656 /** @short Make sure that failed mail delivery prevents marking the original as answered, etc */
0657 void ComposerSubmissionTest::testFailedMsa()
0658 {
0659     FakeCapabilitiesInjector injector(model);
0660     injector.injectCapability(QStringLiteral("LITERAL+"));
0661 
0662     helperSetupProperHeaders();
0663 
0664     QModelIndex msgA10 = model->index(0, 0, msgListA);
0665     QVERIFY(msgA10.isValid());
0666     QCOMPARE(msgA10.data(Imap::Mailbox::RoleMessageUid).toUInt(), uidMapA[0]);
0667     m_composer->setReplyingToMessage(msgA10);
0668     m_submission->send();
0669 
0670     // We are waiting for APPEND to finish here
0671     QCOMPARE(requestedSendingSpy->size(), 0);
0672 
0673     for (int i=0; i<5; ++i)
0674         QCoreApplication::processEvents();
0675     QString sentSoFar = QString::fromUtf8(SOCK->writtenStuff());
0676     QString expected = t.mk("APPEND outgoing (\\Seen) ");
0677     QCOMPARE(sentSoFar.left(expected.size()), expected);
0678     cEmpty();
0679     QCOMPARE(requestedSendingSpy->size(), 0);
0680 
0681     // Assume the APPEND has suceeded
0682     cServer(t.last("OK [APPENDUID 666333666 123] append done\r\n"));
0683     cEmpty();
0684 
0685     QCOMPARE(requestedSendingSpy->size(), 1);
0686     m_msaFactory->doEmitSending();
0687     QCOMPARE(sendingSpy->size(), 1);
0688     m_msaFactory->doEmitError(QStringLiteral("error"));
0689     QCOMPARE(sentSpy->size(), 0);
0690 
0691     QCOMPARE(submissionFailedSpy->size(), 1);
0692     QCOMPARE(submissionSucceededSpy->size(), 0);
0693 
0694     QVERIFY(requestedSendingSpy->size() == 1 &&
0695             requestedSendingSpy->at(0).size() == 3 &&
0696             requestedSendingSpy->at(0)[2].toByteArray().contains("Sample message"));
0697     cEmpty();
0698 
0699 }
0700 
0701 /** @short Test the Parser that a missing continuation request effectively cancels out the processing of the current command
0702 
0703 We're testing it on this level because it is easy to trigger sending a literal this way.
0704 */
0705 void ComposerSubmissionTest::testNoImapContinuation()
0706 {
0707     helperSetupProperHeaders();
0708     m_submission->send();
0709     QCOMPARE(requestedSendingSpy->size(), 0);
0710 
0711     // Also queue a couple of another IMAP commands
0712     model->switchToMailbox(idxB);
0713     model->switchToMailbox(idxC);
0714 
0715     // A command sending the literal string will be rejected.
0716     // The number of octets is platform-dependent, so we cannot just use cClient() here.
0717     for (int i=0; i<5; ++i)
0718         QCoreApplication::processEvents();
0719     QString sentSoFar = QString::fromUtf8(SOCK->writtenStuff());
0720     QString expected = t.mk("APPEND outgoing (\\Seen) {");
0721     int octets;
0722     EXTRACT_TARILING_NUMBER(octets);
0723     Q_UNUSED(octets);
0724     cEmpty();
0725     cServer(t.last("NO rejected\r\n"));
0726     // The Parser shall detect this and proceed towards sending other IMAP commands
0727     cClient(t.mk("SELECT b\r\n"));
0728     cServer("* 0 exists\r\n" + t.last("OK selected\r\n"));
0729     cClient(t.mk("SELECT c\r\n"));
0730     cServer("* 0 exists\r\n" + t.last("OK selected\r\n"));
0731     cEmpty();
0732     QCOMPARE(submissionFailedSpy->size(), 1);
0733     QCOMPARE(submissionSucceededSpy->size(), 0);
0734     justKeepTask();
0735 }
0736 
0737 /** @short Make sure that the original message is marked as replied to when replying */
0738 void ComposerSubmissionTest::testReplyingNormal()
0739 {
0740     helperSetupProperHeaders();
0741     m_submission->setImapOptions(false, QString(), QString(), QString(), false);
0742     QModelIndex origMessage = msgListA.model()->index(0, 0, msgListA);
0743     QVERIFY(origMessage.isValid());
0744     QCOMPARE(origMessage.data(Imap::Mailbox::RoleMessageUid).toInt(), 10);
0745     m_composer->setReplyingToMessage(origMessage);
0746 
0747     m_submission->send();
0748     cEmpty();
0749 
0750     QCOMPARE(requestedSendingSpy->size(), 1);
0751     m_msaFactory->doEmitSending();
0752     QCOMPARE(sendingSpy->size(), 1);
0753     m_msaFactory->doEmitSent();
0754     QCOMPARE(sentSpy->size(), 1);
0755     cClient(t.mk("UID STORE 10 +FLAGS (\\Answered)\r\n"));
0756     cServer("* 1 FETCH (UID 10 FLAGS (\\Answered))\r\n"
0757             + t.last("OK stored\r\n"));
0758     cEmpty();
0759 
0760     QCOMPARE(submissionSucceededSpy->size(), 1);
0761     QCOMPARE(submissionFailedSpy->size(), 0);
0762 
0763     QVERIFY(requestedSendingSpy->size() == 1 &&
0764             requestedSendingSpy->at(0).size() == 3 &&
0765             requestedSendingSpy->at(0)[2].toByteArray().contains("Sample message"));
0766     cEmpty();
0767 }
0768 
0769 /** @short Make sure that the original message is marked as $Forwarded when forwarding */
0770 void ComposerSubmissionTest::testForwardingNormal()
0771 {
0772     helperSetupProperHeaders();
0773     m_submission->setImapOptions(false, QString(), QString(), QString(), false);
0774     QModelIndex origMessage = msgListA.model()->index(0, 0, msgListA);
0775     QVERIFY(origMessage.isValid());
0776     QCOMPARE(origMessage.data(Imap::Mailbox::RoleMessageUid).toInt(), 10);
0777     m_composer->prepareForwarding(origMessage, Composer::ForwardMode::FORWARD_AS_ATTACHMENT);
0778 
0779     cClientRegExp(t.mk("UID FETCH 10 \\(BODY\\.PEEK\\["
0780                        "("
0781                        "TEXT\\] BODY\\.PEEK\\[HEADER"
0782                        "|"
0783                        "HEADER\\] BODY\\.PEEK\\[TEXT"
0784                        ")"
0785                        "\\]\\)"));
0786     cServer("* 1 FETCH (BODY[TEXT] \"contents\" BODY[HEADER] \"headers\")\r\n");
0787     cServer(t.last("OK fetched\r\n"));
0788     cEmpty();
0789 
0790     m_submission->send();
0791     cEmpty();
0792 
0793     QCOMPARE(requestedSendingSpy->size(), 1);
0794     m_msaFactory->doEmitSending();
0795     QCOMPARE(sendingSpy->size(), 1);
0796     m_msaFactory->doEmitSent();
0797     QCOMPARE(sentSpy->size(), 1);
0798     cClient(t.mk("UID STORE 10 +FLAGS ($Forwarded)\r\n"));
0799     cServer("* 1 FETCH (UID 10 FLAGS ($Forwarded))\r\n"
0800             + t.last("OK stored\r\n"));
0801     cEmpty();
0802     QCOMPARE(submissionSucceededSpy->size(), 1);
0803     QCOMPARE(submissionFailedSpy->size(), 0);
0804 
0805     QVERIFY(requestedSendingSpy->size() == 1 &&
0806             requestedSendingSpy->at(0).size() == 3 &&
0807             requestedSendingSpy->at(0)[2].toByteArray().contains("Sample message"));
0808     cEmpty();
0809 }
0810 
0811 /** @short Check that we don't crash if a message being forwarded disappears while we're waiting for it */
0812 void ComposerSubmissionTest::testForwardingDeletedWhileFetching()
0813 {
0814     helperSetupProperHeaders();
0815     m_submission->setImapOptions(false, QString(), QString(), QString(), false);
0816     QModelIndex origMessage = msgListA.model()->index(0, 0, msgListA);
0817     QVERIFY(origMessage.isValid());
0818     QCOMPARE(origMessage.data(Imap::Mailbox::RoleMessageUid).toInt(), 10);
0819     m_composer->prepareForwarding(origMessage, Composer::ForwardMode::FORWARD_AS_ATTACHMENT);
0820 
0821     cClientRegExp(t.mk("UID FETCH 10 \\(BODY\\.PEEK\\["
0822                        "("
0823                        "TEXT\\] BODY\\.PEEK\\[HEADER"
0824                        "|"
0825                        "HEADER\\] BODY\\.PEEK\\[TEXT"
0826                        ")"
0827                        "\\]\\)"));
0828     cServer("* 1 EXPUNGE\r\n")
0829     cServer(t.last("NO not fetched, it's gone now\r\n"));
0830     cEmpty();
0831 
0832     m_submission->send();
0833     cEmpty();
0834 
0835     QCOMPARE(requestedSendingSpy->size(), 0);
0836     QCOMPARE(submissionSucceededSpy->size(), 0);
0837     QCOMPARE(submissionFailedSpy->size(), 1);
0838     cEmpty();
0839 }
0840 
0841 /** @short Make sure that replying to a removed message works reasonably well */
0842 void ComposerSubmissionTest::testReplyingToRemoved()
0843 {
0844     helperSetupProperHeaders();
0845     m_submission->setImapOptions(false, QString(), QString(), QString(), false);
0846     QModelIndex origMessage = msgListA.model()->index(0, 0, msgListA);
0847     QVERIFY(origMessage.isValid());
0848     QCOMPARE(origMessage.data(Imap::Mailbox::RoleMessageUid).toInt(), 10);
0849     m_composer->setReplyingToMessage(origMessage);
0850     cServer("* 1 EXPUNGE\r\n");
0851     QVERIFY(!m_composer->replyingToMessage().isValid());
0852 
0853     m_submission->send();
0854     cEmpty();
0855 
0856     QCOMPARE(requestedSendingSpy->size(), 1);
0857     m_msaFactory->doEmitSending();
0858     QCOMPARE(sendingSpy->size(), 1);
0859     m_msaFactory->doEmitSent();
0860     QCOMPARE(sentSpy->size(), 1);
0861     cEmpty();
0862 
0863     QCOMPARE(submissionSucceededSpy->size(), 1);
0864     QCOMPARE(submissionFailedSpy->size(), 0);
0865 
0866     QVERIFY(requestedSendingSpy->size() == 1 &&
0867             requestedSendingSpy->at(0).size() == 3 &&
0868             requestedSendingSpy->at(0)[2].toByteArray().contains("Sample message"));
0869     cEmpty();
0870 }
0871 
0872 QTEST_GUILESS_MAIN(ComposerSubmissionTest)