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"