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

0001 /*
0002    Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
0003    Author: Kevin Ottens <kevin@kdab.com>
0004 
0005    This program is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 of the License, or (at your option) any later version.
0009 
0010    This program is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    General Public License for more details.
0014 
0015    You should have received a copy of the GNU General Public License
0016    along with this program; if not, write to the Free Software
0017    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include <qtest.h>
0021 
0022 #include "kimap2test/fakeserver.h"
0023 #include "kimap2/session.h"
0024 #include "kimap2/fetchjob.h"
0025 
0026 #include <QtTest>
0027 
0028 Q_DECLARE_METATYPE(KIMAP2::FetchJob::FetchScope)
0029 
0030 using namespace KIMAP2;
0031 
0032 class FetchJobTest: public QObject
0033 {
0034     Q_OBJECT
0035 
0036 private:
0037     QStringList m_signals;
0038 
0039     QMap<qint64, qint64> m_uids;
0040     QMap<qint64, qint64> m_sizes;
0041     QMap<qint64, KIMAP2::MessageFlags> m_flags;
0042     QMap<qint64, KIMAP2::MessagePtr> m_messages;
0043     QMap<qint64, KIMAP2::MessageParts> m_parts;
0044     QMap<qint64, KIMAP2::MessageAttributes> m_attrs;
0045 
0046 public Q_SLOTS:
0047     void onResultReceived(const FetchJob::Result &result)
0048     {
0049         qDebug() << "Received result " << result.sequenceNumber;
0050         m_signals << QStringLiteral("headersReceived");
0051         m_uids.insert(result.sequenceNumber, result.uid);
0052         m_sizes.insert(result.sequenceNumber, result.size);
0053         m_flags.insert(result.sequenceNumber, result.flags);
0054         m_messages.insert(result.sequenceNumber, result.message);
0055         if (!result.attributes.isEmpty()) {
0056             m_attrs.insert(result.sequenceNumber, result.attributes);
0057         }
0058         m_parts.insert(result.sequenceNumber, result.parts);
0059     }
0060 
0061 private Q_SLOTS:
0062 
0063     void testFetch_data()
0064     {
0065         qRegisterMetaType<KIMAP2::FetchJob::FetchScope>();
0066 
0067         QTest::addColumn<bool>("uidBased");
0068         QTest::addColumn< KIMAP2::ImapSet >("set");
0069         QTest::addColumn<int>("expectedMessageCount");
0070         QTest::addColumn< QList<QByteArray> >("scenario");
0071         QTest::addColumn<KIMAP2::FetchJob::FetchScope>("scope");
0072 
0073         KIMAP2::FetchJob::FetchScope scope;
0074         scope.mode = KIMAP2::FetchJob::FetchScope::Flags;
0075         scope.changedSince = 123456789;
0076 
0077         QList<QByteArray> scenario;
0078         scenario << FakeServer::preauth()
0079                  << "C: A000001 FETCH 1:4 (FLAGS UID) (CHANGEDSINCE 123456789)"
0080                  << "S: * 1 FETCH ( FLAGS () UID 1 )"
0081                  << "S: * 2 FETCH ( FLAGS () UID 2 )"
0082                  << "S: * 3 FETCH ( FLAGS () UID 3 )"
0083                  << "S: * 4 FETCH ( FLAGS () UID 4 )"
0084                  << "S: A000001 OK fetch done";
0085 
0086         QTest::newRow("messages have empty flags (with changedsince)") << false << KIMAP2::ImapSet(1, 4) << 4
0087                 << scenario << scope;
0088 
0089         scenario.clear();
0090         scope.changedSince = 0;
0091         scenario << FakeServer::preauth()
0092                  << "C: A000001 FETCH 1:4 (FLAGS UID)"
0093                  << "S: * 1 FETCH ( FLAGS () UID 1 )"
0094                  << "S: * 2 FETCH ( FLAGS () UID 2 )"
0095                  << "S: * 3 FETCH ( FLAGS () UID 3 )"
0096                  << "S: * 4 FETCH ( FLAGS () UID 4 )"
0097                  << "S: A000001 OK fetch done";
0098 
0099         QTest::newRow("messages have empty flags") << false << KIMAP2::ImapSet(1, 4) << 4
0100                 << scenario << scope;
0101 
0102         scenario.clear();
0103         // kill the connection part-way through a list, with carriage returns at end
0104         // BUG 253619
0105         // this should fail, but it shouldn't crash
0106         scenario << FakeServer::preauth()
0107                  << "C: A000001 FETCH 11 (RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)"
0108                  << "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 SUBJECT DATE)] {246}"
0109                  << "S: From: John Smith <jonathanr.smith@foobarbaz.com>\r\nTo: \"amagicemailaddress@foobarbazbarfoo.com\"\r\n\t<amagicemailaddress@foobarbazbarfoo.com>\r\nDate: Mon, 11 Oct 2010 03:34:48 +0100\r\nSubject: unsubscribe\r\nMessage-ID: <ASDFFDSASDFFDS@foobarbaz.com>\r\n\r\n"
0110                  << "X";
0111         scope.mode = KIMAP2::FetchJob::FetchScope::Headers;
0112         QTest::newRow("connection drop") << false << KIMAP2::ImapSet(11, 11) << 1 << scenario << scope;
0113 
0114         scenario.clear();
0115         // Important bit here if "([127.0.0.1])" which used to crash the stream parser
0116         scenario << FakeServer::preauth()
0117                  << "C: A000001 FETCH 11 (RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)"
0118                  << "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 SUBJECT DATE)] {246}"
0119                  << "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"
0120                  << "X";
0121         scope.mode = KIMAP2::FetchJob::FetchScope::Headers;
0122         QTest::newRow("buffer overwrite") << false << KIMAP2::ImapSet(11, 11) << 1 << scenario << scope;
0123 
0124         scenario.clear();
0125         // We're assuming a buffer overwrite here which made us miss the opening parenthesis
0126         // for the properties list
0127         scenario << FakeServer::preauth()
0128                  << "C: A000001 FETCH 11 (RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)"
0129                  << "S: * 11 FETCH {10}doh!\r\n\r\n\r\n)\r\n"
0130                  << "X";
0131         scope.mode = KIMAP2::FetchJob::FetchScope::Headers;
0132         QTest::newRow("buffer overwrite 2") << false << KIMAP2::ImapSet(11, 11) << 1 << scenario << scope;
0133 
0134         scenario.clear();
0135         scenario << FakeServer::preauth()
0136                  << "C: A000001 FETCH 11 (RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER] FLAGS UID) (CHANGEDSINCE 123456789)"
0137                  << "S: * 11 FETCH (UID 123 RFC822.SIZE 770 INTERNALDATE \"11-Oct-2010 03:33:50 +0100\" BODY[HEADER] {245}"
0138                  << "S: From: John Smith <jonathanr.smith@foobarbaz.com>\r\nTo: \"amagicemailaddress@foobarbazbarfoo.com\"\r\n\t<amagicemailaddress@foobarbazbarfoo.com>\r\nDate: Mon, 11 Oct 2010 03:34:48 +0100\r\nSubject: unsubscribe\r\nMessage-ID: <ASDFFDSASDFFDS@foobarbaz.com>\r\n\r\n  FLAGS ())"
0139                  << "S: A000001 OK fetch done";
0140         scope.mode = KIMAP2::FetchJob::FetchScope::FullHeaders;
0141         scope.changedSince = 123456789;
0142         QTest::newRow("fetch full headers") << false << KIMAP2::ImapSet(11, 11) << 1
0143                                             << scenario << scope;
0144 
0145         scenario.clear();
0146         const auto payloadSize = 32000;
0147         QByteArray payload;
0148         payload.fill('c', payloadSize);
0149         scenario << FakeServer::preauth()
0150                  << "C: A000001 FETCH 11 (RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER] FLAGS UID) (CHANGEDSINCE 123456789)"
0151                  << QString("S: * 11 FETCH (UID 123 RFC822.SIZE 770 INTERNALDATE \"11-Oct-2010 03:33:50 +0100\" BODY[HEADER] {%1}").arg(payloadSize).toLatin1()
0152                  << "S: " + payload + " FLAGS ())"
0153                  << "S: A000001 OK fetch done";
0154         scope.mode = KIMAP2::FetchJob::FetchScope::FullHeaders;
0155         scope.changedSince = 123456789;
0156         QTest::newRow("fetch large payload") << false << KIMAP2::ImapSet(11, 11) << 1
0157                                             << scenario << scope;
0158     }
0159 
0160     void testFetch()
0161     {
0162         QFETCH(bool, uidBased);
0163         QFETCH(KIMAP2::ImapSet, set);
0164         QFETCH(int, expectedMessageCount);
0165         QFETCH(QList<QByteArray>, scenario);
0166         QFETCH(KIMAP2::FetchJob::FetchScope, scope);
0167 
0168         FakeServer fakeServer;
0169         fakeServer.setScenario(scenario);
0170         fakeServer.startAndWait();
0171 
0172         KIMAP2::Session session(QStringLiteral("127.0.0.1"), 5989);
0173 
0174         KIMAP2::FetchJob *job = new KIMAP2::FetchJob(&session);
0175         job->setUidBased(uidBased);
0176         job->setSequenceSet(set);
0177         job->setScope(scope);
0178 
0179         connect(job, &FetchJob::resultReceived, this, &FetchJobTest::onResultReceived);
0180 
0181         bool result = job->exec();
0182         QEXPECT_FAIL("connection drop", "Expected failure on connection drop", Continue);
0183         QEXPECT_FAIL("buffer overwrite", "Expected failure on confused list", Continue);
0184         QEXPECT_FAIL("buffer overwrite 2", "Expected beginning of message missing", Continue);
0185         QVERIFY(result);
0186         if (result) {
0187             QVERIFY(m_signals.count() > 0);
0188             QCOMPARE(m_uids.count(), expectedMessageCount);
0189         }
0190 
0191         QVERIFY(fakeServer.isAllScenarioDone());
0192         fakeServer.quit();
0193 
0194         m_signals.clear();
0195         m_uids.clear();
0196         m_sizes.clear();
0197         m_flags.clear();
0198         m_messages.clear();
0199         m_parts.clear();
0200         m_attrs.clear();
0201     }
0202 
0203     void testFetchStructure()
0204     {
0205         QList<QByteArray> scenario;
0206         scenario << FakeServer::preauth()
0207                  << "C: A000001 FETCH 1:2 (BODYSTRUCTURE UID)"
0208                  << "S: * 1 FETCH (UID 10 BODYSTRUCTURE (\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"7BIT\" 5 1 NIL NIL NIL))"
0209                  << "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))"
0210                  << "S: A000001 OK fetch done";
0211 
0212         KIMAP2::FetchJob::FetchScope scope;
0213         scope.mode = KIMAP2::FetchJob::FetchScope::Structure;
0214 
0215         FakeServer fakeServer;
0216         fakeServer.setScenario(scenario);
0217         fakeServer.startAndWait();
0218 
0219         KIMAP2::Session session(QStringLiteral("127.0.0.1"), 5989);
0220 
0221         KIMAP2::FetchJob *job = new KIMAP2::FetchJob(&session);
0222         job->setUidBased(false);
0223         job->setSequenceSet(KIMAP2::ImapSet(1, 2));
0224         job->setScope(scope);
0225 
0226         connect(job, &FetchJob::resultReceived, this, &FetchJobTest::onResultReceived);
0227 
0228         bool result = job->exec();
0229         QVERIFY(result);
0230         QVERIFY(m_signals.count() > 0);
0231         QCOMPARE(m_uids.count(), 2);
0232         QCOMPARE(m_messages.count(), 2);
0233         QVERIFY(m_messages[1]);
0234         QCOMPARE(m_messages[1]->attachments().count(), 0);
0235         QVERIFY(m_messages[2]);
0236         QCOMPARE(m_messages[2]->attachments().count(), 1);
0237         QCOMPARE(m_messages[2]->contents().size(), 2);
0238         QCOMPARE(m_messages[2]->contents()[0]->contents().size(), 2);
0239         QCOMPARE(m_messages[2]->attachments().at(0)->contentDisposition()->filename(), QStringLiteral("photo.jpg"));
0240 
0241         fakeServer.quit();
0242 
0243         m_signals.clear();
0244         m_uids.clear();
0245         m_sizes.clear();
0246         m_flags.clear();
0247         m_messages.clear();
0248         m_parts.clear();
0249         m_attrs.clear();
0250     }
0251 
0252     void testFetchParts()
0253     {
0254         QList<QByteArray> scenario;
0255         scenario << FakeServer::preauth()
0256                  << "C: A000001 FETCH 2 (BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] BODY.PEEK[1.1.1.MIME] BODY.PEEK[1.1.1] FLAGS UID)"
0257                  << "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 <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 <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; charset=ISO-8859-1\r\n\r\n)\r\n"
0258                  << "S: A000001 OK fetch done";
0259 
0260         KIMAP2::FetchJob::FetchScope scope;
0261         scope.mode = KIMAP2::FetchJob::FetchScope::HeaderAndContent;
0262         scope.parts.clear();
0263         scope.parts.append("1.1.1");
0264 
0265         FakeServer fakeServer;
0266         fakeServer.setScenario(scenario);
0267         fakeServer.startAndWait();
0268 
0269         KIMAP2::Session session(QStringLiteral("127.0.0.1"), 5989);
0270 
0271         KIMAP2::FetchJob *job = new KIMAP2::FetchJob(&session);
0272         job->setUidBased(false);
0273         job->setSequenceSet(KIMAP2::ImapSet(2, 2));
0274         job->setScope(scope);
0275 
0276         connect(job, &FetchJob::resultReceived, this, &FetchJobTest::onResultReceived);
0277 
0278         bool result = job->exec();
0279 
0280         QVERIFY(result);
0281         QVERIFY(m_signals.count() > 0);
0282         QCOMPARE(m_uids.count(), 1);
0283         QCOMPARE(m_parts.count(), 1);
0284         QCOMPARE(m_attrs.count(), 0);
0285 
0286         // Check that we received the message header
0287         QCOMPARE(m_messages[2]->messageID()->identifier(), QByteArray("1234@example.com"));
0288 
0289         // Check that we recieved the flags
0290         QMap<qint64, KIMAP2::MessageFlags> expectedFlags;
0291         expectedFlags.insert(2, KIMAP2::MessageFlags() << "\\Seen");
0292         QCOMPARE(m_flags, expectedFlags);
0293 
0294         // Check that we didn't received the full message body, since we only requested a specific part
0295         QCOMPARE(m_messages[2]->decodedText().length(), 0);
0296         QCOMPARE(m_messages[2]->attachments().count(), 0);
0297 
0298         // Check that we received the part we requested
0299         QByteArray partId = m_parts[2].keys().first();
0300         QString text = m_parts[2].value(partId)->decodedText(true, true);
0301         QCOMPARE(partId, QByteArray("1.1.1"));
0302         QCOMPARE(text, QStringLiteral("Hi Jane, nice to meet you!")) ;
0303 
0304         fakeServer.quit();
0305 
0306         m_signals.clear();
0307         m_uids.clear();
0308         m_sizes.clear();
0309         m_flags.clear();
0310         m_messages.clear();
0311         m_parts.clear();
0312         m_attrs.clear();
0313     }
0314 
0315 };
0316 
0317 QTEST_GUILESS_MAIN(FetchJobTest)
0318 
0319 #include "fetchjobtest.moc"