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"