File indexing completed on 2024-04-28 05:18:34
0001 /* 0002 SPDX-FileCopyrightText: 2009 Bertjan Broeksema <broeksema@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #include "mboxtest.h" 0008 0009 #include <QDir> 0010 #include <QFile> 0011 0012 #include <QStandardPaths> 0013 #include <QTemporaryDir> 0014 #include <QTest> 0015 0016 QTEST_MAIN(MboxTest) 0017 0018 #include "test-entries.h" 0019 0020 using namespace KMBox; 0021 0022 static const char *testDir = "libmbox-unit-test"; 0023 static const char *testFile = "test-mbox-file"; 0024 static const char *testLockFile = "test-mbox-lock-file"; 0025 0026 QString MboxTest::fileName() 0027 { 0028 return mTempDir->path() + QLatin1Char('/') + QLatin1StringView(testFile); 0029 } 0030 0031 QString MboxTest::lockFileName() 0032 { 0033 return mTempDir->path() + QLatin1Char('/') + QLatin1StringView(testLockFile); 0034 } 0035 0036 void MboxTest::removeTestFile() 0037 { 0038 QFile file(fileName()); 0039 file.remove(); 0040 QVERIFY(!file.exists()); 0041 } 0042 0043 void MboxTest::initTestCase() 0044 { 0045 mTempDir = new QTemporaryDir(QDir::tempPath() + QLatin1Char('/') + QLatin1StringView(testDir)); 0046 0047 QDir temp(mTempDir->path()); 0048 QVERIFY(temp.exists()); 0049 0050 QFile mboxfile(fileName()); 0051 mboxfile.open(QIODevice::WriteOnly); 0052 mboxfile.close(); 0053 QVERIFY(mboxfile.exists()); 0054 0055 mMail1 = KMime::Message::Ptr(new KMime::Message); 0056 mMail1->setContent(KMime::CRLFtoLF(sEntry1)); 0057 mMail1->parse(); 0058 0059 mMail2 = KMime::Message::Ptr(new KMime::Message); 0060 mMail2->setContent(KMime::CRLFtoLF(sEntry2)); 0061 mMail2->parse(); 0062 } 0063 0064 void MboxTest::testSetLockMethod() 0065 { 0066 MBox mbox1; 0067 0068 if (!QStandardPaths::findExecutable(QStringLiteral("lockfile")).isEmpty()) { 0069 QVERIFY(mbox1.setLockType(MBox::ProcmailLockfile)); 0070 } else { 0071 QVERIFY(!mbox1.setLockType(MBox::ProcmailLockfile)); 0072 } 0073 0074 if (!QStandardPaths::findExecutable(QStringLiteral("mutt_dotlock")).isEmpty()) { 0075 QVERIFY(mbox1.setLockType(MBox::MuttDotlock)); 0076 QVERIFY(mbox1.setLockType(MBox::MuttDotlockPrivileged)); 0077 } else { 0078 QVERIFY(!mbox1.setLockType(MBox::MuttDotlock)); 0079 QVERIFY(!mbox1.setLockType(MBox::MuttDotlockPrivileged)); 0080 } 0081 0082 QVERIFY(mbox1.setLockType(MBox::None)); 0083 } 0084 0085 void MboxTest::testLockBeforeLoad() 0086 { 0087 // Should fail because it's not known which file to lock. 0088 MBox mbox; 0089 0090 if (!QStandardPaths::findExecutable(QStringLiteral("lockfile")).isEmpty()) { 0091 QVERIFY(mbox.setLockType(MBox::ProcmailLockfile)); 0092 QVERIFY(!mbox.lock()); 0093 } 0094 0095 if (!QStandardPaths::findExecutable(QStringLiteral("mutt_dotlock")).isEmpty()) { 0096 QVERIFY(mbox.setLockType(MBox::MuttDotlock)); 0097 QVERIFY(!mbox.lock()); 0098 QVERIFY(mbox.setLockType(MBox::MuttDotlockPrivileged)); 0099 QVERIFY(!mbox.lock()); 0100 } 0101 0102 QVERIFY(mbox.setLockType(MBox::None)); 0103 QVERIFY(!mbox.lock()); 0104 } 0105 0106 void MboxTest::testProcMailLock() 0107 { 0108 // It really only makes sense to test this if the lockfile executable can be 0109 // found. 0110 0111 MBox mbox; 0112 if (!mbox.setLockType(MBox::ProcmailLockfile)) { 0113 QEXPECT_FAIL("", "This test only works when procmail is installed.", Abort); 0114 QVERIFY(false); 0115 } 0116 0117 QVERIFY(mbox.load(fileName())); 0118 0119 // By default the filename is used as part of the lockfile filename. 0120 QVERIFY(!QFile(fileName() + QLatin1StringView(".lock")).exists()); 0121 QVERIFY(mbox.lock()); 0122 QVERIFY(QFile(fileName() + QLatin1StringView(".lock")).exists()); 0123 QVERIFY(mbox.unlock()); 0124 QVERIFY(!QFile(fileName() + QLatin1StringView(".lock")).exists()); 0125 0126 mbox.setLockFile(lockFileName()); 0127 QVERIFY(!QFile(lockFileName()).exists()); 0128 QVERIFY(mbox.lock()); 0129 QVERIFY(QFile(lockFileName()).exists()); 0130 QVERIFY(mbox.unlock()); 0131 QVERIFY(!QFile(lockFileName()).exists()); 0132 } 0133 0134 void MboxTest::testConcurrentAccess() 0135 { 0136 // tests if mbox works correctly when another program locks the file 0137 // and appends new messages 0138 0139 MBox mbox; 0140 0141 if (!mbox.setLockType(MBox::ProcmailLockfile)) { 0142 QEXPECT_FAIL("", "This test only works when procmail is installed.", Abort); 0143 QVERIFY(false); 0144 } 0145 0146 ThreadFillsMBox thread(fileName()); // locks the mbox file and adds a new message 0147 thread.start(); 0148 0149 QVERIFY(mbox.load(fileName())); 0150 0151 MBoxEntry entry = mbox.appendMessage(mMail1); 0152 0153 // as the thread appended sEntry1, the offset for the now appended message 0154 // must be greater 0155 QVERIFY(static_cast<int>(entry.messageOffset()) > sEntry1.length()); 0156 0157 thread.wait(); 0158 } 0159 0160 void MboxTest::testAppend() 0161 { 0162 removeTestFile(); 0163 0164 QFileInfo info(fileName()); 0165 QCOMPARE(info.size(), static_cast<qint64>(0)); 0166 0167 MBox mbox; 0168 mbox.setLockType(MBox::None); 0169 0170 QVERIFY(mbox.load(fileName())); 0171 0172 // First message added to an empty file should be at offset 0 0173 QCOMPARE(mbox.entries().size(), 0); 0174 QCOMPARE(mbox.appendMessage(mMail1).messageOffset(), static_cast<quint64>(0)); 0175 QCOMPARE(mbox.entries().size(), 1); 0176 QVERIFY(mbox.entries().constFirst().separatorSize() > 0); 0177 QCOMPARE(mbox.entries().constFirst().messageSize(), static_cast<quint64>(sEntry1.size())); 0178 0179 const MBoxEntry offsetMail2 = mbox.appendMessage(mMail2); 0180 QVERIFY(offsetMail2.messageOffset() > static_cast<quint64>(sEntry1.size())); 0181 QCOMPARE(mbox.entries().size(), 2); 0182 QVERIFY(mbox.entries().constLast().separatorSize() > 0); 0183 QCOMPARE(mbox.entries().constLast().messageSize(), static_cast<quint64>(sEntry2.size())); 0184 0185 // check if appended entries can be read 0186 const MBoxEntry::List list = mbox.entries(); 0187 for (const MBoxEntry &msgInfo : list) { 0188 const QByteArray header = mbox.readMessageHeaders(msgInfo); 0189 QVERIFY(!header.isEmpty()); 0190 0191 KMime::Message *message = mbox.readMessage(msgInfo); 0192 QVERIFY(message != nullptr); 0193 0194 auto headers = new KMime::Message(); 0195 headers->setHead(KMime::CRLFtoLF(header)); 0196 headers->parse(); 0197 0198 QCOMPARE(message->messageID()->identifier(), headers->messageID()->identifier()); 0199 QCOMPARE(message->subject()->as7BitString(), headers->subject()->as7BitString()); 0200 QCOMPARE(message->to()->as7BitString(), headers->to()->as7BitString()); 0201 QCOMPARE(message->from()->as7BitString(), headers->from()->as7BitString()); 0202 0203 if (msgInfo.messageOffset() == 0) { 0204 QCOMPARE(message->messageID()->identifier(), mMail1->messageID()->identifier()); 0205 QCOMPARE(message->subject()->as7BitString(), mMail1->subject()->as7BitString()); 0206 QCOMPARE(message->to()->as7BitString(), mMail1->to()->as7BitString()); 0207 QCOMPARE(message->from()->as7BitString(), mMail1->from()->as7BitString()); 0208 } else if (msgInfo.messageOffset() == offsetMail2.messageOffset()) { 0209 QCOMPARE(message->messageID()->identifier(), mMail2->messageID()->identifier()); 0210 QCOMPARE(message->subject()->as7BitString(), mMail2->subject()->as7BitString()); 0211 QCOMPARE(message->to()->as7BitString(), mMail2->to()->as7BitString()); 0212 QCOMPARE(message->from()->as7BitString(), mMail2->from()->as7BitString()); 0213 } 0214 0215 delete message; 0216 delete headers; 0217 } 0218 } 0219 0220 void MboxTest::testSaveAndLoad() 0221 { 0222 removeTestFile(); 0223 0224 MBox mbox; 0225 QVERIFY(mbox.setLockType(MBox::None)); 0226 QVERIFY(mbox.load(fileName())); 0227 QVERIFY(mbox.entries().isEmpty()); 0228 mbox.appendMessage(mMail1); 0229 mbox.appendMessage(mMail2); 0230 0231 MBoxEntry::List infos1 = mbox.entries(); 0232 QCOMPARE(infos1.size(), 2); 0233 0234 QVERIFY(mbox.save()); 0235 QVERIFY(QFileInfo::exists(fileName())); 0236 0237 MBoxEntry::List infos2 = mbox.entries(); 0238 QCOMPARE(infos2.size(), 2); 0239 0240 for (int i = 0; i < 2; ++i) { 0241 QCOMPARE(infos1.at(i).messageOffset(), infos2.at(i).messageOffset()); 0242 QCOMPARE(infos1.at(i).separatorSize(), infos2.at(i).separatorSize()); 0243 QCOMPARE(infos1.at(i).messageSize(), infos2.at(i).messageSize()); 0244 } 0245 0246 MBox mbox2; 0247 QVERIFY(mbox2.setLockType(MBox::None)); 0248 QVERIFY(mbox2.load(fileName())); 0249 0250 MBoxEntry::List infos3 = mbox2.entries(); 0251 QCOMPARE(infos3.size(), 2); 0252 0253 for (int i = 0; i < 2; ++i) { 0254 QCOMPARE(infos3.at(i), infos2.at(i)); 0255 0256 QCOMPARE(infos3.at(i).messageOffset(), infos1.at(i).messageOffset()); 0257 QCOMPARE(infos3.at(i).separatorSize(), infos1.at(i).separatorSize()); 0258 QCOMPARE(infos3.at(i).messageSize(), infos1.at(i).messageSize()); 0259 0260 quint64 minSize = infos2.at(i).messageSize(); 0261 quint64 maxSize = infos2.at(i).messageSize() + 1; 0262 QVERIFY(infos3.at(i).messageSize() >= minSize); 0263 QVERIFY(infos3.at(i).messageSize() <= maxSize); 0264 } 0265 } 0266 0267 void MboxTest::testBlankLines() 0268 { 0269 for (int i = 0; i < 5; ++i) { 0270 removeTestFile(); 0271 0272 KMime::Message::Ptr mail = KMime::Message::Ptr(new KMime::Message); 0273 mail->setContent(KMime::CRLFtoLF(sEntry1 + QByteArray(i, '\n'))); 0274 mail->parse(); 0275 0276 MBox mbox1; 0277 QVERIFY(mbox1.setLockType(MBox::None)); 0278 QVERIFY(mbox1.load(fileName())); 0279 mbox1.appendMessage(mail); 0280 mbox1.appendMessage(mail); 0281 mbox1.appendMessage(mail); 0282 mbox1.save(); 0283 0284 QVERIFY(mbox1.setLockType(MBox::None)); 0285 QVERIFY(mbox1.load(fileName())); 0286 QCOMPARE(mbox1.entries().size(), 3); 0287 0288 quint64 minSize = sEntry1.size() + i - 1; // Possibly on '\n' falls off. 0289 quint64 maxSize = sEntry1.size() + i; 0290 for (int j = 0; j < 3; ++j) { 0291 QVERIFY(mbox1.entries().at(j).messageSize() >= minSize); 0292 QVERIFY(mbox1.entries().at(j).messageSize() <= maxSize); 0293 } 0294 } 0295 } 0296 0297 void MboxTest::testEntries() 0298 { 0299 removeTestFile(); 0300 0301 MBox mbox1; 0302 QVERIFY(mbox1.setLockType(MBox::None)); 0303 QVERIFY(mbox1.load(fileName())); 0304 mbox1.appendMessage(mMail1); 0305 mbox1.appendMessage(mMail2); 0306 mbox1.appendMessage(mMail1); 0307 0308 MBoxEntry::List infos = mbox1.entries(); 0309 QCOMPARE(infos.size(), 3); 0310 0311 MBoxEntry::List deletedEntries; 0312 deletedEntries << infos.at(0); 0313 0314 MBoxEntry::List infos2 = mbox1.entries(deletedEntries); 0315 QCOMPARE(infos2.size(), 2); 0316 QVERIFY(infos2.first() != infos.first()); 0317 QVERIFY(infos2.last() != infos.first()); 0318 0319 deletedEntries << infos.at(1); 0320 infos2 = mbox1.entries(deletedEntries); 0321 0322 QCOMPARE(infos2.size(), 1); 0323 QVERIFY(infos2.first() != infos.at(0)); 0324 QVERIFY(infos2.first() != infos.at(1)); 0325 0326 deletedEntries << infos.at(2); 0327 infos2 = mbox1.entries(deletedEntries); 0328 QCOMPARE(infos2.size(), 0); 0329 0330 QVERIFY(!deletedEntries.contains(MBoxEntry(10))); // some random offset 0331 infos2 = mbox1.entries(MBoxEntry::List() << MBoxEntry(10)); 0332 QCOMPARE(infos2.size(), 3); 0333 QCOMPARE(infos2.at(0), infos.at(0)); 0334 QCOMPARE(infos2.at(1), infos.at(1)); 0335 QCOMPARE(infos2.at(2), infos.at(2)); 0336 } 0337 0338 void MboxTest::testPurge() 0339 { 0340 MBox mbox1; 0341 QVERIFY(mbox1.setLockType(MBox::None)); 0342 QVERIFY(mbox1.load(fileName())); 0343 mbox1.appendMessage(mMail1); 0344 mbox1.appendMessage(mMail1); 0345 mbox1.appendMessage(mMail1); 0346 QVERIFY(mbox1.save()); 0347 0348 MBoxEntry::List list = mbox1.entries(); 0349 0350 // First test: Delete only the first (all messages afterwards have to be moved). 0351 mbox1.purge(MBoxEntry::List() << list.first()); 0352 0353 MBox mbox2; 0354 QVERIFY(mbox2.load(fileName())); 0355 MBoxEntry::List list2 = mbox2.entries(); 0356 QCOMPARE(list2.size(), 2); // Is a message actually gone? 0357 0358 quint64 newOffsetSecondMessage = list.last().messageOffset() - list.at(1).messageOffset(); 0359 0360 QCOMPARE(list2.first().messageOffset(), static_cast<quint64>(0)); 0361 QCOMPARE(list2.last().messageOffset(), newOffsetSecondMessage); 0362 0363 // Second test: Delete the first two (the last message have to be moved). 0364 removeTestFile(); 0365 0366 QVERIFY(mbox1.load(fileName())); 0367 mbox1.appendMessage(mMail1); 0368 mbox1.appendMessage(mMail1); 0369 mbox1.appendMessage(mMail1); 0370 QVERIFY(mbox1.save()); 0371 0372 list = mbox1.entries(); 0373 0374 mbox1.purge(MBoxEntry::List() << list.at(0) << list.at(1)); 0375 QVERIFY(mbox2.load(fileName())); 0376 list2 = mbox2.entries(); 0377 QCOMPARE(list2.size(), 1); // Are the messages actually gone? 0378 QCOMPARE(list2.first().messageOffset(), static_cast<quint64>(0)); 0379 0380 // Third test: Delete all messages. 0381 removeTestFile(); 0382 0383 QVERIFY(mbox1.load(fileName())); 0384 mbox1.appendMessage(mMail1); 0385 mbox1.appendMessage(mMail1); 0386 mbox1.appendMessage(mMail1); 0387 QVERIFY(mbox1.save()); 0388 0389 list = mbox1.entries(); 0390 0391 mbox1.purge(MBoxEntry::List() << list.at(0) << list.at(1) << list.at(2)); 0392 QVERIFY(mbox2.load(fileName())); 0393 list2 = mbox2.entries(); 0394 QCOMPARE(list2.size(), 0); // Are the messages actually gone? 0395 } 0396 0397 void MboxTest::testLockTimeout() 0398 { 0399 MBox mbox; 0400 mbox.load(fileName()); 0401 mbox.setLockType(MBox::None); 0402 mbox.setUnlockTimeout(1000); 0403 0404 QVERIFY(!mbox.locked()); 0405 mbox.lock(); 0406 QVERIFY(mbox.locked()); 0407 0408 QTest::qWait(1500); 0409 QVERIFY(!mbox.locked()); 0410 } 0411 0412 void MboxTest::testHeaders() 0413 { 0414 MBox mbox; 0415 QVERIFY(mbox.setLockType(MBox::None)); 0416 QVERIFY(mbox.load(fileName())); 0417 mbox.appendMessage(mMail1); 0418 mbox.appendMessage(mMail2); 0419 QVERIFY(mbox.save()); 0420 0421 const MBoxEntry::List list = mbox.entries(); 0422 0423 for (const MBoxEntry &msgInfo : list) { 0424 const QByteArray header = mbox.readMessageHeaders(msgInfo); 0425 QVERIFY(!header.isEmpty()); 0426 0427 KMime::Message *message = mbox.readMessage(msgInfo); 0428 QVERIFY(message != nullptr); 0429 0430 auto headers = new KMime::Message(); 0431 headers->setHead(KMime::CRLFtoLF(header)); 0432 headers->parse(); 0433 0434 QCOMPARE(message->messageID()->identifier(), headers->messageID()->identifier()); 0435 QCOMPARE(message->subject()->as7BitString(), headers->subject()->as7BitString()); 0436 QCOMPARE(message->to()->as7BitString(), headers->to()->as7BitString()); 0437 QCOMPARE(message->from()->as7BitString(), headers->from()->as7BitString()); 0438 0439 delete message; 0440 delete headers; 0441 } 0442 } 0443 0444 void MboxTest::testReadOnlyMbox() 0445 { 0446 { // create a non-empty mbox file 0447 MBox mbox; 0448 QVERIFY(mbox.load(fileName())); 0449 mbox.appendMessage(mMail1); 0450 mbox.appendMessage(mMail2); 0451 QVERIFY(mbox.save()); 0452 } 0453 0454 QFile::Permissions perm = QFile::permissions(fileName()); 0455 QFile::setPermissions(fileName(), QFile::ReadOwner); 0456 0457 MBox mbox; 0458 QVERIFY(mbox.load(fileName())); 0459 QVERIFY(mbox.isReadOnly()); 0460 0461 // this still works since we could save it to a different file 0462 MBoxEntry entry = mbox.appendMessage(mMail1); 0463 0464 QVERIFY(!mbox.save()); // original mbox is read-only 0465 0466 // reading back the appended message (from memory) 0467 QByteArray msg = mbox.readRawMessage(entry); 0468 QVERIFY(!msg.isEmpty()); 0469 0470 // read first message from disk 0471 MBoxEntry::List list = mbox.entries(); 0472 msg = mbox.readRawMessage(list.at(0)); 0473 QVERIFY(!msg.isEmpty()); 0474 0475 QVERIFY(!mbox.purge(list)); // original mbox is read-only 0476 0477 QString tmpSaved = mTempDir->path() + QLatin1StringView("tempSaved.mbox"); 0478 QVERIFY(mbox.save(tmpSaved)); // other mbox file can be written 0479 0480 MBox savedMbox; 0481 savedMbox.setReadOnly(); 0482 QVERIFY(savedMbox.isReadOnly()); 0483 QVERIFY(savedMbox.load(tmpSaved)); 0484 QVERIFY(!savedMbox.save()); 0485 0486 // set back to initial permissions 0487 QFile::setPermissions(fileName(), perm); 0488 } 0489 0490 void MboxTest::cleanupTestCase() 0491 { 0492 mTempDir->remove(); 0493 delete mTempDir; 0494 } 0495 0496 //--------------------------------------------------------------------- 0497 0498 ThreadFillsMBox::ThreadFillsMBox(const QString &fileName) 0499 { 0500 mbox = new MBox; 0501 QVERIFY(mbox->load(fileName)); 0502 mbox->setLockType(MBox::ProcmailLockfile); 0503 mbox->lock(); 0504 } 0505 0506 void ThreadFillsMBox::run() 0507 { 0508 QTest::qSleep(2000); 0509 0510 QFile file(mbox->fileName()); 0511 file.open(QIODevice::WriteOnly | QIODevice::Append); 0512 0513 QByteArray message = KMime::CRLFtoLF(sEntry1); 0514 file.write(QByteArray("From test@local.local ") + QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8() + "\n"); 0515 file.write(message); 0516 file.write("\n\n"); 0517 file.close(); 0518 0519 mbox->unlock(); 0520 delete mbox; 0521 } 0522 0523 #include "moc_mboxtest.cpp"