File indexing completed on 2024-05-12 05:21:34

0001 /*
0002   SPDX-FileCopyrightText: 2010 BetterInbox <contact@betterinbox.com>
0003   SPDX-FileContributor: Christophe Laveault <christophe@betterinbox.com>
0004   SPDX-FileContributor: Gregory Schlomoff <gregory.schlomoff@gmail.com>
0005 
0006   SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "fakeserver.h"
0010 
0011 #include <QDebug>
0012 #include <QFile>
0013 #include <QTcpServer>
0014 #include <QTcpSocket>
0015 #include <QTest>
0016 
0017 FakeServer::FakeServer(QObject *parent)
0018     : QThread(parent)
0019 {
0020     moveToThread(this);
0021 }
0022 
0023 QByteArray FakeServer::greeting()
0024 {
0025     return "S: 220 localhost ESMTP xx777xx";
0026 }
0027 
0028 QList<QByteArray> FakeServer::greetingAndEhlo(bool multiline)
0029 {
0030     return QList<QByteArray>() << greeting() << "C: EHLO 127.0.0.1" << QByteArray("S: 250") + (multiline ? '-' : ' ') + "Localhost ready to roll";
0031 }
0032 
0033 QList<QByteArray> FakeServer::bye()
0034 {
0035     return {"C: QUIT", "S: 221 So long, and thanks for all the fish", "X: "};
0036 }
0037 
0038 FakeServer::~FakeServer()
0039 {
0040     quit();
0041     wait();
0042 }
0043 
0044 void FakeServer::startAndWait()
0045 {
0046     start();
0047     // this will block until the event queue starts
0048     QMetaObject::invokeMethod(this, &FakeServer::started, Qt::BlockingQueuedConnection);
0049 }
0050 
0051 void FakeServer::dataAvailable()
0052 {
0053     QMutexLocker locker(&m_mutex);
0054 
0055     auto socket = qobject_cast<QTcpSocket *>(sender());
0056     Q_ASSERT(socket != nullptr);
0057 
0058     int scenarioNumber = m_clientSockets.indexOf(socket);
0059 
0060     QVERIFY(!m_scenarios[scenarioNumber].isEmpty());
0061 
0062     readClientPart(scenarioNumber);
0063     writeServerPart(scenarioNumber);
0064 }
0065 
0066 void FakeServer::newConnection()
0067 {
0068     QMutexLocker locker(&m_mutex);
0069 
0070     m_clientSockets << m_tcpServer->nextPendingConnection();
0071     connect(m_clientSockets.last(), &QIODevice::readyRead, this, &FakeServer::dataAvailable);
0072     // m_clientParsers << new KIMAP::ImapStreamParser( m_clientSockets.last(), true );
0073 
0074     QVERIFY(m_clientSockets.size() <= m_scenarios.size());
0075 
0076     writeServerPart(m_clientSockets.size() - 1);
0077 }
0078 
0079 void FakeServer::run()
0080 {
0081     m_tcpServer = new QTcpServer();
0082     if (!m_tcpServer->listen(QHostAddress(QHostAddress::LocalHost), 5989)) {
0083         qFatal("Unable to start the server");
0084         return;
0085     }
0086 
0087     connect(m_tcpServer, &QTcpServer::newConnection, this, &FakeServer::newConnection);
0088 
0089     exec();
0090 
0091     qDeleteAll(m_clientSockets);
0092 
0093     delete m_tcpServer;
0094 }
0095 
0096 void FakeServer::started()
0097 {
0098     // do nothing: this is a dummy slot used by startAndWait()
0099 }
0100 
0101 void FakeServer::setScenario(const QList<QByteArray> &scenario)
0102 {
0103     QMutexLocker locker(&m_mutex);
0104 
0105     m_scenarios.clear();
0106     m_scenarios << scenario;
0107 }
0108 
0109 void FakeServer::addScenario(const QList<QByteArray> &scenario)
0110 {
0111     QMutexLocker locker(&m_mutex);
0112 
0113     m_scenarios << scenario;
0114 }
0115 
0116 void FakeServer::addScenarioFromFile(const QString &fileName)
0117 {
0118     QFile file(fileName);
0119     file.open(QFile::ReadOnly);
0120 
0121     QList<QByteArray> scenario;
0122 
0123     // When loading from files we never have the authentication phase
0124     // force jumping directly to authenticated state.
0125     // scenario << preauth();
0126 
0127     while (!file.atEnd()) {
0128         scenario << file.readLine().trimmed();
0129     }
0130 
0131     file.close();
0132 
0133     addScenario(scenario);
0134 }
0135 
0136 bool FakeServer::isScenarioDone(int scenarioNumber) const
0137 {
0138     QMutexLocker locker(&m_mutex);
0139 
0140     if (scenarioNumber < m_scenarios.size()) {
0141         return m_scenarios[scenarioNumber].isEmpty();
0142     } else {
0143         return true; // Non existent hence empty, right?
0144     }
0145 }
0146 
0147 bool FakeServer::isAllScenarioDone() const
0148 {
0149     QMutexLocker locker(&m_mutex);
0150 
0151     for (const QList<QByteArray> &scenario : std::as_const(m_scenarios)) {
0152         if (!scenario.isEmpty()) {
0153             qDebug() << scenario;
0154             return false;
0155         }
0156     }
0157 
0158     return true;
0159 }
0160 
0161 void FakeServer::writeServerPart(int scenarioNumber)
0162 {
0163     QList<QByteArray> scenario = m_scenarios[scenarioNumber];
0164     QTcpSocket *clientSocket = m_clientSockets[scenarioNumber];
0165 
0166     while (!scenario.isEmpty() && (scenario.first().startsWith("S: ") || scenario.first().startsWith("W: "))) {
0167         QByteArray rule = scenario.takeFirst();
0168 
0169         if (rule.startsWith("S: ")) {
0170             QByteArray payload = rule.mid(3);
0171             clientSocket->write(payload + "\r\n");
0172         } else {
0173             int timeout = rule.mid(3).toInt();
0174             QTest::qWait(timeout);
0175         }
0176     }
0177 
0178     if (!scenario.isEmpty() && scenario.first().startsWith('X')) {
0179         scenario.takeFirst();
0180         clientSocket->close();
0181     }
0182 
0183     if (!scenario.isEmpty()) {
0184         QVERIFY(scenario.first().startsWith("C: "));
0185     }
0186 
0187     m_scenarios[scenarioNumber] = scenario;
0188 }
0189 
0190 void FakeServer::readClientPart(int scenarioNumber)
0191 {
0192     QList<QByteArray> scenario = m_scenarios[scenarioNumber];
0193     QTcpSocket *clientSocket = m_clientSockets[scenarioNumber];
0194 
0195     while (!scenario.isEmpty() && scenario.first().startsWith("C: ")) {
0196         QByteArray line = clientSocket->readLine();
0197         QByteArray received = "C: " + line.trimmed();
0198         QByteArray expected = scenario.takeFirst();
0199 
0200         if (expected == "C: SKIP" && !scenario.isEmpty()) {
0201             expected = scenario.takeFirst();
0202             while (received != expected) {
0203                 received = "C: " + clientSocket->readLine().trimmed();
0204             }
0205         }
0206 
0207         QCOMPARE(QString::fromUtf8(received), QString::fromUtf8(expected));
0208         QCOMPARE(received, expected);
0209     }
0210 
0211     if (!scenario.isEmpty()) {
0212         QVERIFY(scenario.first().startsWith("S: "));
0213     }
0214 
0215     m_scenarios[scenarioNumber] = scenario;
0216 }
0217 
0218 #include "moc_fakeserver.cpp"