File indexing completed on 2024-05-12 05:17:10
0001 /* 0002 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 0003 SPDX-FileContributor: Kevin Ottens <kevin@kdab.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include <QTest> 0009 0010 #include "kimap/fetchjob.h" 0011 #include "kimap/session.h" 0012 #include "kimaptest/fakeserver.h" 0013 0014 #include <QSignalSpy> 0015 #include <QTest> 0016 0017 Q_DECLARE_METATYPE(KIMAP::FetchJob::FetchScope) 0018 0019 class FetchJobTest : public QObject 0020 { 0021 Q_OBJECT 0022 0023 public: 0024 FetchJobTest() 0025 { 0026 qRegisterMetaType<KIMAP::ImapSet>(); 0027 } 0028 0029 private: 0030 QStringList m_signals; 0031 0032 QMap<qint64, qint64> m_uids; 0033 QMap<qint64, qint64> m_sizes; 0034 QMap<qint64, KIMAP::MessageFlags> m_flags; 0035 QMap<qint64, KIMAP::MessagePtr> m_messages; 0036 QMap<qint64, KIMAP::MessageParts> m_parts; 0037 QMap<qint64, KIMAP::MessageAttribute> m_attrs; 0038 QMap<qint64, KIMAP::Message> m_msgs; 0039 0040 public Q_SLOTS: 0041 void onHeadersReceived(const QString & /*mailBox*/, 0042 const QMap<qint64, qint64> &uids, 0043 const QMap<qint64, qint64> &sizes, 0044 const QMap<qint64, KIMAP::MessageAttribute> &attrs, 0045 const QMap<qint64, KIMAP::MessageFlags> &flags, 0046 const QMap<qint64, KIMAP::MessagePtr> &messages) 0047 { 0048 m_signals << QStringLiteral("headersReceived"); 0049 m_uids.insert(uids); 0050 m_sizes.insert(sizes); 0051 m_flags.insert(flags); 0052 m_messages.insert(messages); 0053 m_attrs.insert(attrs); 0054 } 0055 0056 void onMessagesReceived(const QString & /*mailbox*/, 0057 const QMap<qint64, qint64> &uids, 0058 const QMap<qint64, KIMAP::MessageAttribute> &attrs, 0059 const QMap<qint64, KIMAP::MessagePtr> &messages) 0060 { 0061 m_signals << QStringLiteral("messagesReceived"); 0062 m_uids.insert(uids); 0063 m_messages.insert(messages); 0064 m_attrs.insert(attrs); 0065 } 0066 0067 void onPartsReceived(const QString & /*mailbox*/, 0068 const QMap<qint64, qint64> & /*uids*/, 0069 const QMap<qint64, KIMAP::MessageAttribute> &attrs, 0070 const QMap<qint64, KIMAP::MessageParts> &parts) 0071 { 0072 m_signals << QStringLiteral("partsReceived"); 0073 m_attrs.insert(attrs); 0074 m_parts.insert(parts); 0075 } 0076 0077 void onMessagesAvailable(const QMap<qint64, KIMAP::Message> &messages) 0078 { 0079 m_signals << QStringLiteral("messagesAvailable"); 0080 m_msgs.insert(messages); 0081 } 0082 0083 private Q_SLOTS: 0084 0085 void testFetch_data() 0086 { 0087 qRegisterMetaType<KIMAP::FetchJob::FetchScope>(); 0088 0089 QTest::addColumn<bool>("uidBased"); 0090 QTest::addColumn<KIMAP::ImapSet>("set"); 0091 QTest::addColumn<int>("expectedMessageCount"); 0092 QTest::addColumn<QList<QByteArray>>("scenario"); 0093 QTest::addColumn<KIMAP::FetchJob::FetchScope>("scope"); 0094 QTest::addColumn<KIMAP::ImapSet>("expectedVanished"); 0095 0096 KIMAP::FetchJob::FetchScope scope; 0097 scope.mode = KIMAP::FetchJob::FetchScope::Flags; 0098 scope.changedSince = 123456789; 0099 0100 QList<QByteArray> scenario; 0101 scenario << FakeServer::preauth() << "C: A000001 FETCH 1:4 (FLAGS UID) (CHANGEDSINCE 123456789)" 0102 << "S: * 1 FETCH ( FLAGS () UID 1 )" 0103 << "S: * 2 FETCH ( FLAGS () UID 2 )" 0104 << "S: * 3 FETCH ( FLAGS () UID 3 )" 0105 << "S: * 4 FETCH ( FLAGS () UID 4 )" 0106 << "S: A000001 OK fetch done"; 0107 0108 QTest::newRow("messages have empty flags (with changedsince)") << false << KIMAP::ImapSet(1, 4) << 4 << scenario << scope << KIMAP::ImapSet{}; 0109 0110 scenario.clear(); 0111 scope.changedSince = 0; 0112 scenario << FakeServer::preauth() << "C: A000001 FETCH 1:4 (FLAGS UID)" 0113 << "S: * 1 FETCH ( FLAGS () UID 1 )" 0114 << "S: * 2 FETCH ( FLAGS () UID 2 )" 0115 << "S: * 3 FETCH ( FLAGS () UID 3 )" 0116 << "S: * 4 FETCH ( FLAGS () UID 4 )" 0117 << "S: A000001 OK fetch done"; 0118 0119 QTest::newRow("messages have empty flags") << false << KIMAP::ImapSet(1, 4) << 4 << scenario << scope << KIMAP::ImapSet{}; 0120 0121 scenario.clear(); 0122 // kill the connection part-way through a list, with carriage returns at end 0123 // BUG 253619 0124 // this should fail, but it shouldn't crash 0125 scenario << FakeServer::preauth() 0126 << "C: A000001 FETCH 11 (RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)" 0127 << "S: * 11 FETCH (RFC822.SIZE 770 INTERNALDATE \"11-Oct-2010 03:33:50 +0100\" BODY[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO " 0128 "SUBJECT DATE)] {246}" 0129 << "S: From: John Smith <jonathanr.smith@foobarbaz.com>\r\nTo: " 0130 "\"amagicemailaddress@foobarbazbarfoo.com\"\r\n\t<amagicemailaddress@foobarbazbarfoo.com>\r\nDate: Mon, 11 Oct 2010 03:34:48 " 0131 "+0100\r\nSubject: unsubscribe\r\nMessage-ID: <ASDFFDSASDFFDS@foobarbaz.com>\r\n\r\n" 0132 << "X"; 0133 scope.mode = KIMAP::FetchJob::FetchScope::Headers; 0134 QTest::newRow("connection drop") << false << KIMAP::ImapSet(11, 11) << 1 << scenario << scope << KIMAP::ImapSet{}; 0135 0136 scenario.clear(); 0137 // Important bit here if "([127.0.0.1])" which used to crash the stream parser 0138 scenario << FakeServer::preauth() 0139 << "C: A000001 FETCH 11 (RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)" 0140 << "S: * 11 FETCH (RFC822.SIZE 770 INTERNALDATE \"11-Oct-2010 03:33:50 +0100\" BODY[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO " 0141 "SUBJECT DATE)] {246}" 0142 << "S: ([127.0.0.1])\r\nDate: Mon, 11 Oct 2010 03:34:48 +0100\r\nSubject: unsubscribe\r\nMessage-ID: <ASDFFDSASDFFDS@foobarbaz.com>\r\n\r\n" 0143 << "X"; 0144 scope.mode = KIMAP::FetchJob::FetchScope::Headers; 0145 QTest::newRow("buffer overwrite") << false << KIMAP::ImapSet(11, 11) << 1 << scenario << scope << KIMAP::ImapSet{}; 0146 0147 scenario.clear(); 0148 // We're assuming a buffer overwrite here which made us miss the opening parenthesis 0149 // for the properties list 0150 scenario << FakeServer::preauth() 0151 << "C: A000001 FETCH 11 (RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)" 0152 << "S: * 11 FETCH {10}doh!\r\n\r\n\r\n)\r\n" 0153 << "X"; 0154 scope.mode = KIMAP::FetchJob::FetchScope::Headers; 0155 QTest::newRow("buffer overwrite 2") << false << KIMAP::ImapSet(11, 11) << 1 << scenario << scope << KIMAP::ImapSet{}; 0156 0157 scenario.clear(); 0158 scenario << FakeServer::preauth() << "C: A000001 FETCH 11 (RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER] FLAGS UID) (CHANGEDSINCE 123456789)" 0159 << "S: * 11 FETCH (UID 123 RFC822.SIZE 770 INTERNALDATE \"11-Oct-2010 03:33:50 +0100\" BODY[HEADER] {245}" 0160 << "S: From: John Smith <jonathanr.smith@foobarbaz.com>\r\nTo: " 0161 "\"amagicemailaddress@foobarbazbarfoo.com\"\r\n\t<amagicemailaddress@foobarbazbarfoo.com>\r\nDate: Mon, 11 Oct 2010 03:34:48 " 0162 "+0100\r\nSubject: unsubscribe\r\nMessage-ID: <ASDFFDSASDFFDS@foobarbaz.com>\r\n\r\n FLAGS ())" 0163 << "S: A000001 OK fetch done"; 0164 scope.mode = KIMAP::FetchJob::FetchScope::FullHeaders; 0165 scope.changedSince = 123456789; 0166 QTest::newRow("fetch full headers") << false << KIMAP::ImapSet(11, 11) << 1 << scenario << scope << KIMAP::ImapSet{}; 0167 0168 scenario.clear(); 0169 scenario << FakeServer::preauth() << "C: A000001 UID FETCH 300:500 (FLAGS UID) (CHANGEDSINCE 12345 VANISHED)" 0170 << "S: * VANISHED (EARLIER) 300:310,405,411" 0171 << "S: * 1 FETCH (UID 404 MODSEQ (65402) FLAGS (\\Seen))" 0172 << "S: * 2 FETCH (UID 406 MODSEQ (75403) FLAGS (\\Deleted))" 0173 << "S: * 4 FETCH (UID 408 MODSEQ (29738) FLAGS ($Nojunk $AutoJunk $MDNSent))" 0174 << "S: A000001 OK Fetch completed"; 0175 scope.mode = KIMAP::FetchJob::FetchScope::Flags; 0176 scope.changedSince = 12345; 0177 scope.qresync = true; 0178 KIMAP::ImapSet vanished; 0179 vanished.add(KIMAP::ImapInterval{300, 310}); 0180 vanished.add(QList<qint64>{405, 411}); 0181 QTest::newRow("qresync") << true << KIMAP::ImapSet(300, 500) << 3 << scenario << scope << vanished; 0182 } 0183 0184 void testFetch() 0185 { 0186 QFETCH(bool, uidBased); 0187 QFETCH(KIMAP::ImapSet, set); 0188 QFETCH(int, expectedMessageCount); 0189 QFETCH(QList<QByteArray>, scenario); 0190 QFETCH(KIMAP::FetchJob::FetchScope, scope); 0191 QFETCH(KIMAP::ImapSet, expectedVanished); 0192 0193 FakeServer fakeServer; 0194 fakeServer.setScenario(scenario); 0195 fakeServer.startAndWait(); 0196 0197 KIMAP::Session session(QStringLiteral("127.0.0.1"), 5989); 0198 0199 auto job = new KIMAP::FetchJob(&session); 0200 job->setUidBased(uidBased); 0201 job->setSequenceSet(set); 0202 job->setScope(scope); 0203 0204 connect(job, 0205 SIGNAL(headersReceived(QString, 0206 QMap<qint64, qint64>, 0207 QMap<qint64, qint64>, 0208 QMap<qint64, KIMAP::MessageAttribute>, 0209 QMap<qint64, KIMAP::MessageFlags>, 0210 QMap<qint64, KIMAP::MessagePtr>)), 0211 this, 0212 SLOT(onHeadersReceived(QString, 0213 QMap<qint64, qint64>, 0214 QMap<qint64, qint64>, 0215 QMap<qint64, KIMAP::MessageAttribute>, 0216 QMap<qint64, KIMAP::MessageFlags>, 0217 QMap<qint64, KIMAP::MessagePtr>))); 0218 connect(job, &KIMAP::FetchJob::messagesAvailable, this, &FetchJobTest::onMessagesAvailable); 0219 0220 QSignalSpy vanishedSpy(job, &KIMAP::FetchJob::messagesVanished); 0221 QVERIFY(vanishedSpy.isValid()); 0222 0223 bool result = job->exec(); 0224 QEXPECT_FAIL("connection drop", "Expected failure on connection drop", Continue); 0225 QEXPECT_FAIL("buffer overwrite", "Expected failure on confused list", Continue); 0226 QEXPECT_FAIL("buffer overwrite 2", "Expected beginning of message missing", Continue); 0227 QVERIFY(result); 0228 if (result) { 0229 QVERIFY(m_signals.count() > 0); 0230 QCOMPARE(m_uids.count(), expectedMessageCount); 0231 QCOMPARE(m_msgs.count(), expectedMessageCount); 0232 if (scope.qresync) { 0233 QCOMPARE(vanishedSpy.size(), 1); 0234 QCOMPARE(vanishedSpy.at(0).at(0).value<KIMAP::ImapSet>(), expectedVanished); 0235 } 0236 } 0237 0238 QVERIFY(fakeServer.isAllScenarioDone()); 0239 fakeServer.quit(); 0240 0241 m_signals.clear(); 0242 m_uids.clear(); 0243 m_sizes.clear(); 0244 m_flags.clear(); 0245 m_messages.clear(); 0246 m_parts.clear(); 0247 m_attrs.clear(); 0248 m_msgs.clear(); 0249 } 0250 0251 void testFetchStructure() 0252 { 0253 QList<QByteArray> scenario; 0254 scenario 0255 << FakeServer::preauth() << "C: A000001 FETCH 1:2 (BODYSTRUCTURE UID)" 0256 << R"(S: * 1 FETCH (UID 10 BODYSTRUCTURE ("TEXT" "PLAIN" ("CHARSET" "ISO-8859-1") NIL NIL "7BIT" 5 1 NIL NIL NIL)))" 0257 << R"(S: * 2 FETCH (UID 20 BODYSTRUCTURE (((("TEXT" "PLAIN" ("CHARSET" "ISO-8859-1") NIL NIL "7BIT" 72 4 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "ISO-8859-1") NIL NIL "QUOTED-PRINTABLE" 281 5 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "0001") NIL NIL)("IMAGE" "GIF" ("NAME" "B56.gif") "<B56@goomoji.gmail>" NIL "BASE64" 528 NIL NIL NIL) "RELATED" ("BOUNDARY" "0002") NIL NIL)("IMAGE" "JPEG" ("NAME" "photo.jpg") NIL NIL "BASE64" 53338 NIL ("ATTACHMENT" ("FILENAME" "photo.jpg")) NIL) "MIXED" ("BOUNDARY" "0003") NIL NIL)))" 0258 << "S: A000001 OK fetch done"; 0259 0260 KIMAP::FetchJob::FetchScope scope; 0261 scope.mode = KIMAP::FetchJob::FetchScope::Structure; 0262 0263 FakeServer fakeServer; 0264 fakeServer.setScenario(scenario); 0265 fakeServer.startAndWait(); 0266 0267 KIMAP::Session session(QStringLiteral("127.0.0.1"), 5989); 0268 0269 auto job = new KIMAP::FetchJob(&session); 0270 job->setUidBased(false); 0271 job->setSequenceSet(KIMAP::ImapSet(1, 2)); 0272 job->setScope(scope); 0273 0274 connect(job, 0275 SIGNAL(messagesReceived(QString, QMap<qint64, qint64>, QMap<qint64, KIMAP::MessageAttribute>, QMap<qint64, KIMAP::MessagePtr>)), 0276 this, 0277 SLOT(onMessagesReceived(QString, QMap<qint64, qint64>, QMap<qint64, KIMAP::MessageAttribute>, QMap<qint64, KIMAP::MessagePtr>))); 0278 connect(job, &KIMAP::FetchJob::messagesAvailable, this, &FetchJobTest::onMessagesAvailable); 0279 0280 bool result = job->exec(); 0281 QVERIFY(result); 0282 QVERIFY(m_signals.count() > 0); 0283 QCOMPARE(m_uids.count(), 2); 0284 QCOMPARE(m_messages[1]->attachments().count(), 0); 0285 QCOMPARE(m_messages[2]->attachments().count(), 1); 0286 QCOMPARE(m_messages[2]->contents().size(), 2); 0287 QCOMPARE(m_messages[2]->contents()[0]->contents().size(), 2); 0288 QCOMPARE(m_messages[2]->attachments().at(0)->contentDisposition()->filename(), QStringLiteral("photo.jpg")); 0289 QCOMPARE(m_msgs.count(), 2); 0290 QCOMPARE(m_msgs[1].message->attachments().count(), 0); 0291 QCOMPARE(m_msgs[2].message->attachments().count(), 1); 0292 QCOMPARE(m_msgs[2].message->contents().size(), 2); 0293 QCOMPARE(m_msgs[2].message->contents()[0]->contents().size(), 2); 0294 QCOMPARE(m_msgs[2].message->attachments().at(0)->contentDisposition()->filename(), QStringLiteral("photo.jpg")); 0295 0296 fakeServer.quit(); 0297 0298 m_signals.clear(); 0299 m_uids.clear(); 0300 m_sizes.clear(); 0301 m_flags.clear(); 0302 m_messages.clear(); 0303 m_parts.clear(); 0304 m_attrs.clear(); 0305 m_msgs.clear(); 0306 } 0307 0308 void testFetchParts() 0309 { 0310 QList<QByteArray> scenario; 0311 scenario << FakeServer::preauth() 0312 << "C: A000001 FETCH 2 (BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] BODY.PEEK[1.1.1.MIME] " 0313 "BODY.PEEK[1.1.1] FLAGS UID)" 0314 << "S: * 2 FETCH (UID 20 FLAGS (\\Seen) BODY[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] {154}\r\nFrom: Joe Smith " 0315 "<smith@example.com>\r\nDate: Wed, 2 Mar 2011 11:33:24 +0700\r\nMessage-ID: <1234@example.com>\r\nSubject: hello\r\nTo: Jane " 0316 "<jane@example.com>\r\n\r\n BODY[1.1.1] {28}\r\nHi Jane, nice to meet you!\r\n BODY[1.1.1.MIME] {48}\r\nContent-Type: text/plain; " 0317 "charset=ISO-8859-1\r\n\r\n)\r\n" 0318 << "S: A000001 OK fetch done"; 0319 0320 KIMAP::FetchJob::FetchScope scope; 0321 scope.mode = KIMAP::FetchJob::FetchScope::HeaderAndContent; 0322 scope.parts.clear(); 0323 scope.parts.append("1.1.1"); 0324 0325 FakeServer fakeServer; 0326 fakeServer.setScenario(scenario); 0327 fakeServer.startAndWait(); 0328 0329 KIMAP::Session session(QStringLiteral("127.0.0.1"), 5989); 0330 0331 auto job = new KIMAP::FetchJob(&session); 0332 job->setUidBased(false); 0333 job->setSequenceSet(KIMAP::ImapSet(2, 2)); 0334 job->setScope(scope); 0335 0336 connect(job, 0337 SIGNAL(headersReceived(QString, 0338 QMap<qint64, qint64>, 0339 QMap<qint64, qint64>, 0340 QMap<qint64, KIMAP::MessageAttribute>, 0341 QMap<qint64, KIMAP::MessageFlags>, 0342 QMap<qint64, KIMAP::MessagePtr>)), 0343 this, 0344 SLOT(onHeadersReceived(QString, 0345 QMap<qint64, qint64>, 0346 QMap<qint64, qint64>, 0347 QMap<qint64, KIMAP::MessageAttribute>, 0348 QMap<qint64, KIMAP::MessageFlags>, 0349 QMap<qint64, KIMAP::MessagePtr>))); 0350 connect(job, 0351 SIGNAL(partsReceived(QString, QMap<qint64, qint64>, QMap<qint64, KIMAP::MessageAttribute>, QMap<qint64, KIMAP::MessageParts>)), 0352 this, 0353 SLOT(onPartsReceived(QString, QMap<qint64, qint64>, QMap<qint64, KIMAP::MessageAttribute>, QMap<qint64, KIMAP::MessageParts>))); 0354 connect(job, &KIMAP::FetchJob::messagesAvailable, this, &FetchJobTest::onMessagesAvailable); 0355 bool result = job->exec(); 0356 0357 QVERIFY(result); 0358 QVERIFY(m_signals.count() > 0); 0359 QCOMPARE(m_uids.count(), 1); 0360 QCOMPARE(m_parts.count(), 1); 0361 QCOMPARE(m_attrs.count(), 0); 0362 QCOMPARE(m_msgs.count(), 1); 0363 0364 // Check that we received the message header 0365 QCOMPARE(m_messages[2]->messageID()->identifier(), QByteArray("1234@example.com")); 0366 QCOMPARE(m_msgs[2].message->messageID()->identifier(), QByteArray("1234@example.com")); 0367 0368 // Check that we received the flags 0369 QMap<qint64, KIMAP::MessageFlags> expectedFlags; 0370 expectedFlags.insert(2, KIMAP::MessageFlags() << "\\Seen"); 0371 QCOMPARE(m_flags, expectedFlags); 0372 QCOMPARE(m_msgs[2].flags, expectedFlags[2]); 0373 0374 // Check that we didn't received the full message body, since we only requested a specific part 0375 QCOMPARE(m_messages[2]->decodedText().length(), 0); 0376 QCOMPARE(m_messages[2]->attachments().count(), 0); 0377 QCOMPARE(m_msgs[2].message->decodedText().length(), 0); 0378 QCOMPARE(m_msgs[2].message->attachments().count(), 0); 0379 0380 // Check that we received the part we requested 0381 QByteArray partId = m_parts[2].keys().first(); 0382 QString text = m_parts[2].value(partId)->decodedText(true, true); 0383 QCOMPARE(partId, QByteArray("1.1.1")); 0384 QCOMPARE(text, QStringLiteral("Hi Jane, nice to meet you!")); 0385 0386 QCOMPARE(m_msgs[2].parts.keys().first(), QByteArray("1.1.1")); 0387 QCOMPARE(m_msgs[2].parts.value(partId)->decodedText(true, true), QStringLiteral("Hi Jane, nice to meet you!")); 0388 0389 fakeServer.quit(); 0390 0391 m_signals.clear(); 0392 m_uids.clear(); 0393 m_sizes.clear(); 0394 m_flags.clear(); 0395 m_messages.clear(); 0396 m_parts.clear(); 0397 m_attrs.clear(); 0398 m_msgs.clear(); 0399 } 0400 }; 0401 0402 QTEST_GUILESS_MAIN(FetchJobTest) 0403 0404 #include "fetchjobtest.moc"