File indexing completed on 2024-06-23 05:21:23
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)