KIMAP

sessionthread.cpp
1 /*
2  SPDX-FileCopyrightText: 2009 Kevin Ottens <[email protected]>
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 
20 using namespace KIMAP;
21 
22 Q_DECLARE_METATYPE(KSslErrorUiData)
23 
24 namespace
25 {
26 static const int _kimap_abstractSocketError = qRegisterMetaType<QAbstractSocket::SocketError>();
27 static const int _kimap_sslErrorUiData = qRegisterMetaType<KSslErrorUiData>();
28 }
29 
30 SessionThread::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 
43 SessionThread::~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
56 void SessionThread::setUseNetworkProxy(bool useProxy)
57 {
59  this,
60  [this, useProxy]() {
61  setUseProxyInternal(useProxy);
62  },
64 }
65 
66 // Called in primary thread
67 void 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
76 void 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
90 void 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
142 void SessionThread::closeSocket()
143 {
144  QMetaObject::invokeMethod(this, &SessionThread::doCloseSocket, Qt::QueuedConnection);
145 }
146 
147 // Called in secondary thread
148 void 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
160 void 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
188 void 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
207 void 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
216 void 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
228 void 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)
236 void 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
250 void SessionThread::slotSocketDisconnected()
251 {
252  Q_ASSERT(QThread::currentThread() == thread());
253  Q_EMIT socketDisconnected();
254 }
255 
256 // Called in secondary thread
257 void 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
267 void 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 
287 void SessionThread::sslErrorHandlerResponse(bool response)
288 {
289  QMetaObject::invokeMethod(this, [this, response]() {
290  doSslErrorHandlerResponse(response);
291  });
292 }
293 
294 // Called in secondary thread (via invokeMethod)
295 void 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;
306  Q_EMIT socketError(QAbstractSocket::SslInvalidUserDataError);
307  m_socket->disconnectFromHost();
308  }
309 }
310 
311 #include "moc_sessionthread_p.cpp"
void encryptedBytesWritten(qint64 written)
bool isNull() const const
int usedBits() const const
void bytesWritten(qint64 bytes)
QString protocolString() const const
void readyRead()
QThread * currentThread()
QueuedConnection
void errorOccurred(QAbstractSocket::SocketError socketError)
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
void encrypted()
QString message
void setType(QNetworkProxy::ProxyType type)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Dec 3 2023 03:51:44 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.