summaryrefslogtreecommitdiff
path: root/kimap/tests/kimaptest/fakeserver.h
blob: add362694c67b63ff3c40d73b1ccb6c9a1d15f49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/*
   Copyright (C) 2008 Omat Holding B.V. <info@omat.nl>

   Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
   Author: Kevin Ottens <kevin@kdab.com>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#ifndef FAKESERVER_H
#define FAKESERVER_H

#include <QStringList>
#include <QTcpSocket>
#include <QTcpServer>
#include <QThread>
#include <QMutex>
#include <QSsl>

namespace KIMAP
{
  class ImapStreamParser;
}

Q_DECLARE_METATYPE( QList<QByteArray> )

/**
 * Pretends to be an IMAP server for the purposes of unit tests.
 *
 * FakeServer does not really understand the IMAP protocol.  Instead,
 * you give it a script, or scenario, that lists how an IMAP session
 * exchange should go.  When it receives the client parts of the
 * scenario, it will respond with the following server parts.
 *
 * The server can be furnished with several scenarios.  The first
 * scenario will be played out to the first client that connects, the
 * second scenario to the second client connection and so on.
 *
 * The fake server runs as a separate thread in the same process it
 * is started from, and listens for connections on port 5989 on the
 * local machine.
 *
 * Scenarios are in the form of protocol messages, with a tag at the
 * start to indicate whether it is message that will be sent by the
 * client ("C:") or a response that should be sent by the server
 * ("S:").  For example:
 * @code
 * C: A000001 LIST "" *
 * S: * LIST ( \HasChildren ) / INBOX
 * S: * LIST ( \HasNoChildren ) / INBOX/&AOQ- &APY- &APw- @ &IKw-
 * S: * LIST ( \HasChildren ) / INBOX/lost+found
 * S: * LIST ( \HasNoChildren ) / "INBOX/lost+found/Calendar Public-20080128"
 * S: A000001 OK LIST completed
 * @endcode
 *
 * A line starting with X indicates that the connection should be
 * closed by the server.  This should be the last line in the
 * scenario.  For example, the following simulates the server closing
 * the connection after receiving too many bad commands:
 * @code
 * C: A000001 madhatter
 * S: A000001 BAD Command madhatter
 * X
 * @endcode
 *
 * FakeServer::preauth() and FakeServer::greeting() provide standard
 * PREAUTH and OK responses, respectively, that can be used (unmodified)
 * as the first line of a scenario.
 *
 * A typical usage is something like
 * @code
 * QList<QByteArray> scenario;
 * scenario << FakeServer::preauth()
 *          << "C: A000001 CAPABILITY"
 *          << "S: * CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI"
 *          << "S: A000001 OK CAPABILITY completed";
 *
 * FakeServer fakeServer;
 * fakeServer.setScenario( scenario );
 * fakeServer.startAndWait();
 *
 * KIMAP::Session session( "127.0.0.1", 5989 );
 * KIMAP::CapabilitiesJob *job = new KIMAP::CapabilitiesJob(&session);
 * QVERIFY( job->exec() );
 * // check the returned capabilities
 *
 * fakeServer.quit();
 * @endcode
 */
class FakeServer : public QThread
{
    Q_OBJECT

public:
    /**
     * Get the default PREAUTH response
     *
     * This is the initial PREAUTH message that the server
     * sends at the start of a session to indicate that the
     * user is already authenticated by some other mechanism.
     *
     * Can be used as the first line in a scenario where
     * you want to skip the LOGIN stage of the protocol.
     */
    static QByteArray preauth();
    /**
     * Get the default greeting
     *
     * This is the initial OK message that the server sends at the
     * start of a session to indicate that a LOGIN is required.
     *
     * Can be used as the first line in a scenario where
     * you want to use the LOGIN command.
     */
    static QByteArray greeting();

    FakeServer( QObject* parent = 0 );
    ~FakeServer();

    /**
     * Sets the encryption mode used by the server socket.
     */
    void setEncrypted( QSsl::SslProtocol protocol );

    /**
     * Starts the server and waits for it to be ready
     *
     * You should use this instead of start() to avoid race conditions.
     */
    void startAndWait();

    /**
     * Starts the fake IMAP server
     *
     * You should not call this directly.  Use start() instead.
     *
     * @reimp
     */
    virtual void run();

    /**
     * Removes any previously-added scenarios, and adds a new one
     *
     * After this, there will only be one scenario, and so the fake
     * server will only be able to service a single request.  More
     * scenarios can be added with addScenario, though.
     *
     * @see addScenario()\n
     * addScenarioFromFile()
     */
    void setScenario( const QList<QByteArray> &scenario );

    /**
     * Adds a new scenario
     *
     * Note that scenarios will be used in the order that clients
     * connect.  If this is the 5th scenario that has been added
     * (bearing in mind that setScenario() resets the scenario
     * count), it will be used to service the 5th client that
     * connects.
     *
     * @see addScenarioFromFile()
     *
     * @param scenario  the scenario as a list of messages
     */
    void addScenario( const QList<QByteArray> &scenario );
    /**
     * Adds a new scenario from a local file
     *
     * Note that scenarios will be used in the order that clients
     * connect.  If this is the 5th scenario that has been added
     * (bearing in mind that setScenario() resets the scenario
     * count), it will be used to service the 5th client that
     * connects.
     *
     * @see addScenario()
     *
     * @param fileName  the name of the file that contains the
     *                  scenario; it will be split at line
     *                  boundaries, and excess whitespace will
     *                  be trimmed from the start and end of lines
     */
    void addScenarioFromFile( const QString &fileName );

    /**
     * Checks whether a particular scenario has completed
     *
     * @param scenarioNumber  the number of the scenario to check,
     *                        in order of addition/client connection
     */
    bool isScenarioDone( int scenarioNumber ) const;
    /**
     * Whether all the scenarios that were added to the fake
     * server have been completed.
     */
    bool isAllScenarioDone() const;

protected:
    /**
     * Whether the received content is the same as the expected.
     * Use QCOMPARE, if creating subclasses.
     */
    virtual void compareReceived(const QByteArray& received, const QByteArray& expected) const;

private Q_SLOTS:
    void newConnection();
    void dataAvailable();
    void started();

private:
    void writeServerPart( int scenarioNumber );
    void readClientPart( int scenarioNumber );

    QList< QList<QByteArray> > m_scenarios;
    QTcpServer *m_tcpServer;
    mutable QMutex m_mutex;
    QList<QTcpSocket*> m_clientSockets;
    QList<KIMAP::ImapStreamParser*> m_clientParsers;
    bool m_encrypted;
    bool m_starttls;
    QSsl::SslProtocol m_sslProtocol;
};

#endif