KIMAP

sessionthread.cpp
1/*
2 SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "sessionthread_p.h"
8
9#include <KSslErrorUiData>
10
11#include "kimap_debug.h"
12#include <QDebug>
13#include <QNetworkProxy>
14#include <QSslCipher>
15#include <QThread>
16
17#include "imapstreamparser.h"
18#include "response_p.h"
19
20using namespace KIMAP;
21
22Q_DECLARE_METATYPE(KSslErrorUiData)
23
24namespace
25{
26static const int _kimap_abstractSocketError = qRegisterMetaType<QAbstractSocket::SocketError>();
27static const int _kimap_sslErrorUiData = qRegisterMetaType<KSslErrorUiData>();
28}
29
30SessionThread::SessionThread(const QString &hostName, quint16 port)
31 : QObject()
32 , m_hostName(hostName)
33 , m_port(port)
34{
35 // Just like the Qt docs now recommend, for event-driven threads:
36 // don't derive from QThread, create one directly and move the object to it.
37 auto thread = new QThread();
38 moveToThread(thread);
39 thread->start();
40 QMetaObject::invokeMethod(this, &SessionThread::threadInit);
41}
42
43SessionThread::~SessionThread()
44{
45 QMetaObject::invokeMethod(this, &SessionThread::threadQuit);
46 if (!thread()->wait(10 * 1000)) {
47 qCWarning(KIMAP_LOG) << "Session thread refuses to die, killing harder...";
48 thread()->terminate();
49 // Make sure to wait until it's done, otherwise it can crash when the pthread callback is called
50 thread()->wait();
51 }
52 delete thread();
53}
54
55// Called in primary thread, passes setting to secondary thread
56void SessionThread::setUseNetworkProxy(bool useProxy)
57{
59 this,
60 [this, useProxy]() {
61 setUseProxyInternal(useProxy);
62 },
64}
65
66// Called in primary thread
67void SessionThread::sendData(const QByteArray &payload)
68{
69 QMutexLocker locker(&m_mutex);
70
71 m_dataQueue.enqueue(payload);
72 QMetaObject::invokeMethod(this, &SessionThread::writeDataQueue);
73}
74
75// Called in secondary thread
76void SessionThread::writeDataQueue()
77{
78 Q_ASSERT(QThread::currentThread() == thread());
79 if (!m_socket) {
80 return;
81 }
82 QMutexLocker locker(&m_mutex);
83
84 while (!m_dataQueue.isEmpty()) {
85 m_socket->write(m_dataQueue.dequeue());
86 }
87}
88
89// Called in secondary thread
90void SessionThread::readMessage()
91{
92 Q_ASSERT(QThread::currentThread() == thread());
93 if (!m_stream || m_stream->availableDataSize() == 0) {
94 return;
95 }
96
97 Response message;
98 QList<Response::Part> *payload = &message.content;
99
100 try {
101 while (!m_stream->atCommandEnd()) {
102 if (m_stream->hasString()) {
103 QByteArray string = m_stream->readString();
104 if (string == "NIL") {
105 *payload << Response::Part(QList<QByteArray>());
106 } else {
107 *payload << Response::Part(string);
108 }
109 } else if (m_stream->hasList()) {
110 *payload << Response::Part(m_stream->readParenthesizedList());
111 } else if (m_stream->hasResponseCode()) {
112 payload = &message.responseCode;
113 } else if (m_stream->atResponseCodeEnd()) {
114 payload = &message.content;
115 } else if (m_stream->hasLiteral()) {
116 QByteArray literal;
117 while (!m_stream->atLiteralEnd()) {
118 literal += m_stream->readLiteralPart();
119 }
120 *payload << Response::Part(literal);
121 } else {
122 // Oops! Something really bad happened, we won't be able to recover
123 // so close the socket immediately
124 qWarning("Inconsistent state, probably due to some packet loss");
125 doCloseSocket();
126 return;
127 }
128 }
129
130 Q_EMIT responseReceived(message);
131
132 } catch (const KIMAP::ImapParserException &e) {
133 qCWarning(KIMAP_LOG) << "The stream parser raised an exception:" << e.what();
134 }
135
136 if (m_stream->availableDataSize() > 1) {
137 QMetaObject::invokeMethod(this, &SessionThread::readMessage, Qt::QueuedConnection);
138 }
139}
140
141// Called in main thread
142void SessionThread::closeSocket()
143{
144 QMetaObject::invokeMethod(this, &SessionThread::doCloseSocket, Qt::QueuedConnection);
145}
146
147// Called in secondary thread
148void SessionThread::doCloseSocket()
149{
150 Q_ASSERT(QThread::currentThread() == thread());
151 if (!m_socket) {
152 return;
153 }
154 m_encryptedMode = false;
155 qCDebug(KIMAP_LOG) << "close";
156 m_socket->close();
157}
158
159// Called in secondary thread
160void SessionThread::reconnect()
161{
162 Q_ASSERT(QThread::currentThread() == thread());
163 if (m_socket == nullptr) { // threadQuit already called
164 return;
165 }
166 if (m_socket->state() != QSslSocket::ConnectedState && m_socket->state() != QSslSocket::ConnectingState) {
167 QNetworkProxy proxy;
168 if (!m_useProxy) {
169 qCDebug(KIMAP_LOG) << "Connecting to IMAP server with no proxy";
171 } else {
172 qCDebug(KIMAP_LOG) << "Connecting to IMAP server using default system proxy";
174 }
175 m_socket->setProxy(proxy);
176
177 if (m_encryptedMode) {
178 qCDebug(KIMAP_LOG) << "connectToHostEncrypted" << m_hostName << m_port;
179 m_socket->connectToHostEncrypted(m_hostName, m_port);
180 } else {
181 qCDebug(KIMAP_LOG) << "connectToHost" << m_hostName << m_port;
182 m_socket->connectToHost(m_hostName, m_port);
183 }
184 }
185}
186
187// Called in secondary thread
188void SessionThread::threadInit()
189{
190 Q_ASSERT(QThread::currentThread() == thread());
191 m_socket = std::make_unique<QSslSocket>();
192 m_stream = std::make_unique<ImapStreamParser>(m_socket.get());
193 connect(m_socket.get(), &QIODevice::readyRead, this, &SessionThread::readMessage, Qt::QueuedConnection);
194
195 // Delay the call to slotSocketDisconnected so that it finishes disconnecting before we call reconnect()
196 connect(m_socket.get(), &QSslSocket::disconnected, this, &SessionThread::slotSocketDisconnected, Qt::QueuedConnection);
197 connect(m_socket.get(), &QSslSocket::connected, this, &SessionThread::socketConnected);
198 connect(m_socket.get(), &QAbstractSocket::errorOccurred, this, &SessionThread::slotSocketError);
199
200 connect(m_socket.get(), &QIODevice::bytesWritten, this, &SessionThread::socketActivity);
201 connect(m_socket.get(), &QSslSocket::encryptedBytesWritten, this, &SessionThread::socketActivity);
202 connect(m_socket.get(), &QIODevice::readyRead, this, &SessionThread::socketActivity);
203 QMetaObject::invokeMethod(this, &SessionThread::reconnect, Qt::QueuedConnection);
204}
205
206// Called in secondary thread
207void SessionThread::threadQuit()
208{
209 Q_ASSERT(QThread::currentThread() == thread());
210 m_stream.reset();
211 m_socket.reset();
212 thread()->quit();
213}
214
215// Called in secondary thread
216void SessionThread::setUseProxyInternal(bool useProxy)
217{
218 m_useProxy = useProxy;
219 if (m_socket != nullptr) {
220 if (m_socket->state() != QSslSocket::UnconnectedState) {
221 m_socket->disconnectFromHost();
222 QMetaObject::invokeMethod(this, &SessionThread::reconnect, Qt::QueuedConnection);
223 }
224 }
225}
226
227// Called in primary thread
228void SessionThread::startSsl(QSsl::SslProtocol protocol)
229{
230 QMetaObject::invokeMethod(this, [this, protocol]() {
231 doStartSsl(protocol);
232 });
233}
234
235// Called in secondary thread (via invokeMethod)
236void SessionThread::doStartSsl(QSsl::SslProtocol protocol)
237{
238 Q_ASSERT(QThread::currentThread() == thread());
239 if (!m_socket) {
240 return;
241 }
242
243 m_socket->setProtocol(protocol);
244 m_socket->ignoreSslErrors(); // Don't worry, errors are handled manually below
245 connect(m_socket.get(), &QSslSocket::encrypted, this, &SessionThread::sslConnected);
246 m_socket->startClientEncryption();
247}
248
249// Called in secondary thread
250void SessionThread::slotSocketDisconnected()
251{
252 Q_ASSERT(QThread::currentThread() == thread());
253 Q_EMIT socketDisconnected();
254}
255
256// Called in secondary thread
257void SessionThread::slotSocketError(QAbstractSocket::SocketError error)
258{
259 Q_ASSERT(QThread::currentThread() == thread());
260 if (!m_socket) {
261 return;
262 }
263 Q_EMIT socketError(error);
264}
265
266// Called in secondary thread
267void SessionThread::sslConnected()
268{
269 Q_ASSERT(QThread::currentThread() == thread());
270 if (!m_socket) {
271 return;
272 }
273 QSslCipher cipher = m_socket->sessionCipher();
274 if (!m_socket->sslHandshakeErrors().isEmpty() || !m_socket->isEncrypted() || cipher.isNull() || cipher.usedBits() == 0) {
275 qCDebug(KIMAP_LOG) << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull() << ", cipher.usedBits() is" << cipher.usedBits()
276 << ", the socket says:" << m_socket->errorString() << "and the list of SSL errors contains" << m_socket->sslHandshakeErrors().count()
277 << "items.";
278 KSslErrorUiData errorData(m_socket.get());
279 Q_EMIT sslError(errorData);
280 } else {
281 qCDebug(KIMAP_LOG) << "TLS negotiation done, the negotiated protocol is" << cipher.protocolString();
282 m_encryptedMode = true;
283 Q_EMIT encryptionNegotiationResult(true, m_socket->sessionProtocol());
284 }
285}
286
287void SessionThread::sslErrorHandlerResponse(bool response)
288{
289 QMetaObject::invokeMethod(this, [this, response]() {
290 doSslErrorHandlerResponse(response);
291 });
292}
293
294// Called in secondary thread (via invokeMethod)
295void SessionThread::doSslErrorHandlerResponse(bool response)
296{
297 Q_ASSERT(QThread::currentThread() == thread());
298 if (!m_socket) {
299 return;
300 }
301 if (response) {
302 m_encryptedMode = true;
303 Q_EMIT encryptionNegotiationResult(true, m_socket->sessionProtocol());
304 } else {
305 m_encryptedMode = false;
307 m_socket->disconnectFromHost();
308 }
309}
310
311#include "moc_sessionthread_p.cpp"
void errorOccurred(QAbstractSocket::SocketError socketError)
void bytesWritten(qint64 bytes)
void readyRead()
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
void setType(QNetworkProxy::ProxyType type)
QueuedConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QThread * currentThread()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:37 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.