File indexing completed on 2024-11-24 04:44:31

0001 /*
0002    SPDX-FileCopyrightText: 2008 Omat Holding B.V. <info@omat.nl>
0003    SPDX-FileCopyrightText: 2009 Thomas McGuire <mcguire@kde.org>
0004 
0005    SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 // Own
0009 #include "fakeserver.h"
0010 
0011 // Qt
0012 #include <QDebug>
0013 #include <QTcpServer>
0014 #include <QTcpSocket>
0015 
0016 FakeServerThread::FakeServerThread(QObject *parent)
0017     : QThread(parent)
0018     , mServer(nullptr)
0019 {
0020 }
0021 
0022 void FakeServerThread::run()
0023 {
0024     mServer = new FakeServer();
0025 
0026     // Run forever, until someone from the outside calls quit() on us and quits the
0027     // event loop
0028     exec();
0029 
0030     delete mServer;
0031     mServer = nullptr;
0032 }
0033 
0034 FakeServer *FakeServerThread::server() const
0035 {
0036     Q_ASSERT(mServer != nullptr);
0037     return mServer;
0038 }
0039 
0040 FakeServer::FakeServer(QObject *parent)
0041     : QObject(parent)
0042 {
0043     mTcpServer = new QTcpServer();
0044     if (!mTcpServer->listen(QHostAddress(QHostAddress::LocalHost), 5989)) {
0045         qCritical() << "Unable to start the server";
0046     }
0047 
0048     connect(mTcpServer, &QTcpServer::newConnection, this, &FakeServer::newConnection);
0049 }
0050 
0051 FakeServer::~FakeServer()
0052 {
0053     if (mConnections > 0) {
0054         disconnect(mTcpServerConnection, &QTcpSocket::readyRead, this, &FakeServer::dataAvailable);
0055     }
0056 
0057     delete mTcpServer;
0058     mTcpServer = nullptr;
0059 }
0060 
0061 QByteArray FakeServer::parseDeleteMark(const QByteArray &expectedData, const QByteArray &dataReceived)
0062 {
0063     // Only called from parseResponse(), which is already thread-safe
0064 
0065     const QByteArray deleteMark = QStringLiteral("%DELE%").toUtf8();
0066     if (expectedData.contains(deleteMark)) {
0067         Q_ASSERT(!mAllowedDeletions.isEmpty());
0068         for (int i = 0; i < mAllowedDeletions.size(); i++) {
0069             QByteArray substituted = expectedData;
0070             substituted.replace(deleteMark, mAllowedDeletions[i]);
0071             if (substituted == dataReceived) {
0072                 mAllowedDeletions.removeAt(i);
0073                 return substituted;
0074             }
0075         }
0076         qWarning() << "Received:" << dataReceived.data() << "\nExpected:" << expectedData.data();
0077         Q_ASSERT_X(false, "FakeServer::parseDeleteMark", "Unable to substitute data!");
0078         return {};
0079     } else {
0080         return expectedData;
0081     }
0082 }
0083 
0084 QByteArray FakeServer::parseRetrMark(const QByteArray &expectedData, const QByteArray &dataReceived)
0085 {
0086     // Only called from parseResponse(), which is already thread-safe
0087 
0088     const QByteArray retrMark = QStringLiteral("%RETR%").toUtf8();
0089     if (expectedData.contains(retrMark)) {
0090         Q_ASSERT(!mAllowedRetrieves.isEmpty());
0091         for (int i = 0; i < mAllowedRetrieves.size(); i++) {
0092             QByteArray substituted = expectedData;
0093             substituted.replace(retrMark, mAllowedRetrieves[i]);
0094             if (substituted == dataReceived) {
0095                 mAllowedRetrieves.removeAt(i);
0096                 return substituted;
0097             }
0098         }
0099         qWarning() << "Received:" << dataReceived.data() << "\nExpected:" << expectedData.data();
0100         Q_ASSERT_X(false, "FakeServer::parseRetrMark", "Unable to substitute data!");
0101         return {};
0102     } else {
0103         return expectedData;
0104     }
0105 }
0106 
0107 QByteArray FakeServer::parseResponse(const QByteArray &expectedData, const QByteArray &dataReceived)
0108 {
0109     // Only called from dataAvailable, which is already thread-safe
0110 
0111     const QByteArray result = parseDeleteMark(expectedData, dataReceived);
0112     if (result != expectedData) {
0113         return result;
0114     } else {
0115         return parseRetrMark(expectedData, dataReceived);
0116     }
0117 }
0118 
0119 static QByteArray removeCRLF(const QByteArray &ba)
0120 {
0121     QByteArray returnArray = ba;
0122     return returnArray.replace(QByteArrayLiteral("\r\n"), QByteArray());
0123 }
0124 
0125 void FakeServer::dataAvailable()
0126 {
0127     QMutexLocker locker(&mMutex);
0128     mProgress++;
0129 
0130     // We got data, so we better expect it and have an answer!
0131     Q_ASSERT(!mReadData.isEmpty());
0132     Q_ASSERT(!mWriteData.isEmpty());
0133 
0134     const QByteArray data = mTcpServerConnection->readAll();
0135     const QByteArray expected(mReadData.takeFirst());
0136     const QByteArray reallyExpected = parseResponse(expected, data);
0137     if (data != reallyExpected) {
0138         qDebug() << "Got data:" << removeCRLF(data);
0139         qDebug() << "Expected data:" << removeCRLF(expected);
0140         qDebug() << "Really expected:" << removeCRLF(reallyExpected);
0141     }
0142 
0143     Q_ASSERT(data == reallyExpected);
0144 
0145     QByteArray toWrite = mWriteData.takeFirst();
0146     // qDebug() << "Going to write data:" << removeCRLF( toWrite );
0147     const bool allWritten = mTcpServerConnection->write(toWrite) == toWrite.size();
0148     Q_ASSERT(allWritten);
0149     Q_UNUSED(allWritten)
0150     const bool flushed = mTcpServerConnection->flush();
0151     Q_ASSERT(flushed);
0152     Q_UNUSED(flushed)
0153 }
0154 
0155 void FakeServer::newConnection()
0156 {
0157     QMutexLocker locker(&mMutex);
0158     Q_ASSERT(mConnections == 0);
0159     mConnections++;
0160     mGotDisconnected = false;
0161 
0162     mTcpServerConnection = mTcpServer->nextPendingConnection();
0163     mTcpServerConnection->write(QByteArray("+OK Initech POP3 server ready.\r\n"));
0164     connect(mTcpServerConnection, &QTcpSocket::readyRead, this, &FakeServer::dataAvailable);
0165     connect(mTcpServerConnection, &QTcpSocket::disconnected, this, &FakeServer::slotDisconnected);
0166 }
0167 
0168 void FakeServer::slotDisconnected()
0169 {
0170     QMutexLocker locker(&mMutex);
0171     mConnections--;
0172     mGotDisconnected = true;
0173     Q_ASSERT(mConnections == 0);
0174     Q_ASSERT(mAllowedDeletions.isEmpty());
0175     Q_ASSERT(mAllowedRetrieves.isEmpty());
0176     Q_ASSERT(mReadData.isEmpty());
0177     Q_ASSERT(mWriteData.isEmpty());
0178     Q_EMIT disconnected();
0179 }
0180 
0181 void FakeServer::setAllowedDeletions(const QString &deleteIds)
0182 {
0183     QMutexLocker locker(&mMutex);
0184     mAllowedDeletions.clear();
0185     const QStringList ids = deleteIds.split(QLatin1Char(','), Qt::SkipEmptyParts);
0186     for (const QString &id : ids) {
0187         mAllowedDeletions.append(id.toUtf8());
0188     }
0189 }
0190 
0191 void FakeServer::setAllowedRetrieves(const QString &retrieveIds)
0192 {
0193     QMutexLocker locker(&mMutex);
0194     mAllowedRetrieves.clear();
0195     const QStringList ids = retrieveIds.split(QLatin1Char(','), Qt::SkipEmptyParts);
0196     for (const QString &id : ids) {
0197         mAllowedRetrieves.append(id.toUtf8());
0198     }
0199 }
0200 
0201 void FakeServer::setMails(const QList<QByteArray> &mails)
0202 {
0203     QMutexLocker locker(&mMutex);
0204     mMails = mails;
0205 }
0206 
0207 void FakeServer::setNextConversation(const QString &conversation, const QList<int> &exceptions)
0208 {
0209     QMutexLocker locker(&mMutex);
0210 
0211     Q_ASSERT(mReadData.isEmpty());
0212     Q_ASSERT(mWriteData.isEmpty());
0213     Q_ASSERT(!conversation.isEmpty());
0214 
0215     mGotDisconnected = false;
0216     const QStringList lines = conversation.split(QStringLiteral("\r\n"), Qt::SkipEmptyParts);
0217     Q_ASSERT(lines.first().startsWith(QLatin1StringView("C:")));
0218 
0219     enum Mode {
0220         Client,
0221         Server,
0222     };
0223     Mode mode = Client;
0224 
0225     const QByteArray mailSizeMarker = QStringLiteral("%MAILSIZE%").toLatin1();
0226     const QByteArray mailMarker = QStringLiteral("%MAIL%").toLatin1();
0227     int sizeIndex = 0;
0228     int mailIndex = 0;
0229 
0230     for (const QString &line : lines) {
0231         QByteArray lineData(line.toUtf8());
0232 
0233         if (lineData.contains(mailSizeMarker)) {
0234             Q_ASSERT(mMails.size() > sizeIndex);
0235             lineData.replace(mailSizeMarker, QString::number(mMails[sizeIndex++].size()).toLatin1());
0236         }
0237         if (lineData.contains(mailMarker)) {
0238             while (exceptions.contains(mailIndex + 1)) {
0239                 mailIndex++;
0240             }
0241             Q_ASSERT(mMails.size() > mailIndex);
0242             lineData.replace(mailMarker, mMails[mailIndex++]);
0243         }
0244 
0245         if (lineData.startsWith("S: ")) {
0246             mWriteData.append(lineData.mid(3) + "\r\n");
0247             mode = Server;
0248         } else if (line.startsWith(QLatin1StringView("C: "))) {
0249             mReadData.append(lineData.mid(3) + "\r\n");
0250             mode = Client;
0251         } else {
0252             switch (mode) {
0253             case Server:
0254                 mWriteData.last() += (lineData + "\r\n");
0255                 break;
0256             case Client:
0257                 mReadData.last() += (lineData + "\r\n");
0258                 break;
0259             }
0260         }
0261     }
0262 }
0263 
0264 int FakeServer::progress() const
0265 {
0266     QMutexLocker locker(&mMutex);
0267     return mProgress;
0268 }
0269 
0270 bool FakeServer::gotDisconnected() const
0271 {
0272     QMutexLocker locker(&mMutex);
0273     return mGotDisconnected;
0274 }
0275 
0276 #include "moc_fakeserver.cpp"