fakeserver.cpp 6.01 KB
Newer Older
1
/*
2
   SPDX-FileCopyrightText: 2008 Omat Holding B.V. <info@omat.nl>
3

4
5
   SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
   SPDX-FileContributor: Kevin Ottens <kevin@kdab.com>
6

7
   SPDX-License-Identifier: GPL-2.0-or-later
8
9
10
11
*/

// Own
#include "fakeserver.h"
12
#include "sslserver.h"
13
14
15
16

// Qt
#include <QDebug>

17
#include <QFile>
Laurent Montel's avatar
Laurent Montel committed
18
19
#include <QTcpServer>
#include <QTcpSocket>
Laurent Montel's avatar
Laurent Montel committed
20
#include <QTest>
21

Christophe Giboudeaux's avatar
Christophe Giboudeaux committed
22
#include "imapstreamparser.h"
23

24
25
26
27
28
29
30
31
32
QByteArray FakeServer::preauth()
{
    return "S: * PREAUTH localhost Test Library server ready";
}

QByteArray FakeServer::greeting()
{
    return "S: * OK localhost Test Library server ready";
}
33

Laurent Montel's avatar
Laurent Montel committed
34
35
36
37
FakeServer::FakeServer(QObject *parent)
    : QThread(parent)
    , m_encrypted(false)
    , m_starttls(false)
38
{
Laurent Montel's avatar
Laurent Montel committed
39
    moveToThread(this);
40
41
42
43
}

FakeServer::~FakeServer()
{
Laurent Montel's avatar
Laurent Montel committed
44
45
    quit();
    wait();
46
47
}

48
49
void FakeServer::startAndWait()
{
Laurent Montel's avatar
Laurent Montel committed
50
51
    start();
    // this will block until the event queue starts
Laurent Montel's avatar
Laurent Montel committed
52
    QMetaObject::invokeMethod(this, &FakeServer::started, Qt::BlockingQueuedConnection);
53
54
}

55
56
void FakeServer::dataAvailable()
{
Laurent Montel's avatar
Laurent Montel committed
57
    QMutexLocker locker(&m_mutex);
58

Laurent Montel's avatar
Laurent Montel committed
59
    auto socket = qobject_cast<QTcpSocket *>(sender());
Laurent Montel's avatar
Laurent Montel committed
60
    QVERIFY(socket);
61

Laurent Montel's avatar
Laurent Montel committed
62
    int scenarioNumber = m_clientSockets.indexOf(socket);
63

64
65
66
67
68
69
    if (m_scenarios[scenarioNumber].isEmpty()) {
        KIMAP::ImapStreamParser *clientParser = m_clientParsers[scenarioNumber];
        QByteArray received = "C: " + clientParser->readUntilCommandEnd().trimmed();
        qWarning() << "Scenario" << scenarioNumber << "finished, but we got command" << received;
        QVERIFY(false);
    }
70

Laurent Montel's avatar
Laurent Montel committed
71
72
    readClientPart(scenarioNumber);
    writeServerPart(scenarioNumber);
73
    if (m_starttls) {
Laurent Montel's avatar
Laurent Montel committed
74
75
76
        m_starttls = false;
        qDebug() << "start tls";
        static_cast<QSslSocket *>(socket)->startServerEncryption();
77
    }
78
79
80
81
}

void FakeServer::newConnection()
{
Laurent Montel's avatar
Laurent Montel committed
82
    QMutexLocker locker(&m_mutex);
83

84
    m_clientSockets << m_tcpServer->nextPendingConnection();
Laurent Montel's avatar
Laurent Montel committed
85
86
    connect(m_clientSockets.last(), SIGNAL(readyRead()), this, SLOT(dataAvailable()));
    m_clientParsers << new KIMAP::ImapStreamParser(m_clientSockets.last(), true);
87

Laurent Montel's avatar
Laurent Montel committed
88
    QVERIFY(m_clientSockets.size() <= m_scenarios.size());
89

Laurent Montel's avatar
Laurent Montel committed
90
    writeServerPart(m_clientSockets.size() - 1);
91
92
}

Laurent Montel's avatar
Laurent Montel committed
93
void FakeServer::setEncrypted(QSsl::SslProtocol protocol)
94
{
Laurent Montel's avatar
Laurent Montel committed
95
96
    m_encrypted = true;
    m_sslProtocol = protocol;
97
98
}

99
100
101
102
103
void FakeServer::setWaitForStartTls(bool wait)
{
    m_waitForStartTls = wait;
}

104
105
void FakeServer::run()
{
106
    if (m_encrypted) {
107
        m_tcpServer = new SslServer(m_sslProtocol, m_waitForStartTls);
108
109
110
    } else {
        m_tcpServer = new QTcpServer();
    }
Laurent Montel's avatar
Laurent Montel committed
111
    if (!m_tcpServer->listen(QHostAddress(QHostAddress::LocalHost), 5989)) {
Christophe Giboudeaux's avatar
Christophe Giboudeaux committed
112
        qFatal("Unable to start the server");
113
114
    }

Laurent Montel's avatar
Laurent Montel committed
115
    connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection()));
116
117

    exec();
118

Laurent Montel's avatar
Laurent Montel committed
119
120
    qDeleteAll(m_clientParsers);
    qDeleteAll(m_clientSockets);
121

122
123
124
    delete m_tcpServer;
}

125
126
void FakeServer::started()
{
Laurent Montel's avatar
Laurent Montel committed
127
    // do nothing: this is a dummy slot used by startAndWait()
128
129
}

Laurent Montel's avatar
Laurent Montel committed
130
void FakeServer::setScenario(const QList<QByteArray> &scenario)
131
{
Laurent Montel's avatar
Laurent Montel committed
132
    QMutexLocker locker(&m_mutex);
133

134
135
    m_scenarios.clear();
    m_scenarios << scenario;
136
137
}

Laurent Montel's avatar
Laurent Montel committed
138
void FakeServer::addScenario(const QList<QByteArray> &scenario)
139
{
Laurent Montel's avatar
Laurent Montel committed
140
    QMutexLocker locker(&m_mutex);
141
142
143
144

    m_scenarios << scenario;
}

Laurent Montel's avatar
Laurent Montel committed
145
void FakeServer::addScenarioFromFile(const QString &fileName)
146
{
Laurent Montel's avatar
Laurent Montel committed
147
148
    QFile file(fileName);
    file.open(QFile::ReadOnly);
149

Laurent Montel's avatar
Laurent Montel committed
150
    QList<QByteArray> scenario;
151

Laurent Montel's avatar
Laurent Montel committed
152
153
154
    // When loading from files we never have the authentication phase
    // force jumping directly to authenticated state.
    scenario << preauth();
155

Laurent Montel's avatar
Laurent Montel committed
156
157
158
    while (!file.atEnd()) {
        scenario << file.readLine().trimmed();
    }
159

Laurent Montel's avatar
Laurent Montel committed
160
    file.close();
161

Laurent Montel's avatar
Laurent Montel committed
162
    addScenario(scenario);
163
164
}

Laurent Montel's avatar
Laurent Montel committed
165
bool FakeServer::isScenarioDone(int scenarioNumber) const
166
{
Laurent Montel's avatar
Laurent Montel committed
167
    QMutexLocker locker(&m_mutex);
168

Laurent Montel's avatar
Laurent Montel committed
169
170
171
172
173
    if (scenarioNumber < m_scenarios.size()) {
        return m_scenarios[scenarioNumber].isEmpty();
    } else {
        return true; // Non existent hence empty, right?
    }
174
175
}

176
bool FakeServer::isAllScenarioDone() const
177
{
Laurent Montel's avatar
Laurent Montel committed
178
    QMutexLocker locker(&m_mutex);
179

Laurent Montel's avatar
Laurent Montel committed
180
    for (const QList<QByteArray> &scenario : qAsConst(m_scenarios)) {
Laurent Montel's avatar
Laurent Montel committed
181
182
183
        if (!scenario.isEmpty()) {
            return false;
        }
184
    }
185

Laurent Montel's avatar
Laurent Montel committed
186
    return true;
187
188
}

Laurent Montel's avatar
Laurent Montel committed
189
void FakeServer::writeServerPart(int scenarioNumber)
190
191
192
{
    QList<QByteArray> scenario = m_scenarios[scenarioNumber];
    QTcpSocket *clientSocket = m_clientSockets[scenarioNumber];
193

Laurent Montel's avatar
Laurent Montel committed
194
    while (!scenario.isEmpty() && (scenario.first().startsWith("S: ") || scenario.first().startsWith("W: "))) {
Laurent Montel's avatar
Laurent Montel committed
195
196
197
198
199
200
201
202
203
        QByteArray rule = scenario.takeFirst();

        if (rule.startsWith("S: ")) {
            QByteArray payload = rule.mid(3);
            clientSocket->write(payload + "\r\n");
        } else {
            int timeout = rule.mid(3).toInt();
            QTest::qWait(timeout);
        }
204
    }
205

Laurent Montel's avatar
Laurent Montel committed
206
    if (!scenario.isEmpty() && scenario.first().startsWith("X")) {
Laurent Montel's avatar
Laurent Montel committed
207
208
        scenario.takeFirst();
        clientSocket->close();
209
210
    }

Laurent Montel's avatar
Laurent Montel committed
211
212
    if (!scenario.isEmpty()) {
        QVERIFY(scenario.first().startsWith("C: "));
213
214
215
    }

    m_scenarios[scenarioNumber] = scenario;
216
217
}

Laurent Montel's avatar
Laurent Montel committed
218
void FakeServer::compareReceived(const QByteArray &received, const QByteArray &expected) const
219
{
Laurent Montel's avatar
Laurent Montel committed
220
221
    QCOMPARE(QString::fromUtf8(received), QString::fromUtf8(expected));
    QCOMPARE(received, expected);
222
223
}

Laurent Montel's avatar
Laurent Montel committed
224
void FakeServer::readClientPart(int scenarioNumber)
225
{
226
227
228
    QList<QByteArray> scenario = m_scenarios[scenarioNumber];
    KIMAP::ImapStreamParser *clientParser = m_clientParsers[scenarioNumber];

Laurent Montel's avatar
Laurent Montel committed
229
    while (!scenario.isEmpty() && scenario.first().startsWith("C: ")) {
Laurent Montel's avatar
Laurent Montel committed
230
231
        QByteArray received = "C: " + clientParser->readUntilCommandEnd().trimmed();
        QByteArray expected = scenario.takeFirst();
232
233
234
        if (expected.contains("C: SKIP")) {
            continue;
        }
Laurent Montel's avatar
Laurent Montel committed
235
236
237
238
        compareReceived(received, expected);
        if (received.contains("STARTTLS")) {
            m_starttls = true;
        }
239
240
    }

Laurent Montel's avatar
Laurent Montel committed
241
    if (!scenario.isEmpty()) {
242
        QVERIFY(scenario.first().startsWith("S: ") || scenario.first().startsWith("X"));
243
    }
244
245

    m_scenarios[scenarioNumber] = scenario;
246
}