File indexing completed on 2024-05-12 05:17:17
0001 /* 0002 SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "sessionthread_p.h" 0008 0009 #include <KSslErrorUiData> 0010 0011 #include "kimap_debug.h" 0012 #include <QDebug> 0013 #include <QNetworkProxy> 0014 #include <QSslCipher> 0015 #include <QThread> 0016 0017 #include "imapstreamparser.h" 0018 #include "response_p.h" 0019 0020 using namespace KIMAP; 0021 0022 Q_DECLARE_METATYPE(KSslErrorUiData) 0023 0024 namespace 0025 { 0026 static const int _kimap_abstractSocketError = qRegisterMetaType<QAbstractSocket::SocketError>(); 0027 static const int _kimap_sslErrorUiData = qRegisterMetaType<KSslErrorUiData>(); 0028 } 0029 0030 SessionThread::SessionThread(const QString &hostName, quint16 port) 0031 : QObject() 0032 , m_hostName(hostName) 0033 , m_port(port) 0034 { 0035 // Just like the Qt docs now recommend, for event-driven threads: 0036 // don't derive from QThread, create one directly and move the object to it. 0037 auto thread = new QThread(); 0038 moveToThread(thread); 0039 thread->start(); 0040 QMetaObject::invokeMethod(this, &SessionThread::threadInit); 0041 } 0042 0043 SessionThread::~SessionThread() 0044 { 0045 QMetaObject::invokeMethod(this, &SessionThread::threadQuit); 0046 if (!thread()->wait(10 * 1000)) { 0047 qCWarning(KIMAP_LOG) << "Session thread refuses to die, killing harder..."; 0048 thread()->terminate(); 0049 // Make sure to wait until it's done, otherwise it can crash when the pthread callback is called 0050 thread()->wait(); 0051 } 0052 delete thread(); 0053 } 0054 0055 // Called in primary thread, passes setting to secondary thread 0056 void SessionThread::setUseNetworkProxy(bool useProxy) 0057 { 0058 QMetaObject::invokeMethod( 0059 this, 0060 [this, useProxy]() { 0061 setUseProxyInternal(useProxy); 0062 }, 0063 Qt::QueuedConnection); 0064 } 0065 0066 // Called in primary thread 0067 void SessionThread::sendData(const QByteArray &payload) 0068 { 0069 QMutexLocker locker(&m_mutex); 0070 0071 m_dataQueue.enqueue(payload); 0072 QMetaObject::invokeMethod(this, &SessionThread::writeDataQueue); 0073 } 0074 0075 // Called in secondary thread 0076 void SessionThread::writeDataQueue() 0077 { 0078 Q_ASSERT(QThread::currentThread() == thread()); 0079 if (!m_socket) { 0080 return; 0081 } 0082 QMutexLocker locker(&m_mutex); 0083 0084 while (!m_dataQueue.isEmpty()) { 0085 m_socket->write(m_dataQueue.dequeue()); 0086 } 0087 } 0088 0089 // Called in secondary thread 0090 void SessionThread::readMessage() 0091 { 0092 Q_ASSERT(QThread::currentThread() == thread()); 0093 if (!m_stream || m_stream->availableDataSize() == 0) { 0094 return; 0095 } 0096 0097 Response message; 0098 QList<Response::Part> *payload = &message.content; 0099 0100 try { 0101 while (!m_stream->atCommandEnd()) { 0102 if (m_stream->hasString()) { 0103 QByteArray string = m_stream->readString(); 0104 if (string == "NIL") { 0105 *payload << Response::Part(QList<QByteArray>()); 0106 } else { 0107 *payload << Response::Part(string); 0108 } 0109 } else if (m_stream->hasList()) { 0110 *payload << Response::Part(m_stream->readParenthesizedList()); 0111 } else if (m_stream->hasResponseCode()) { 0112 payload = &message.responseCode; 0113 } else if (m_stream->atResponseCodeEnd()) { 0114 payload = &message.content; 0115 } else if (m_stream->hasLiteral()) { 0116 QByteArray literal; 0117 while (!m_stream->atLiteralEnd()) { 0118 literal += m_stream->readLiteralPart(); 0119 } 0120 *payload << Response::Part(literal); 0121 } else { 0122 // Oops! Something really bad happened, we won't be able to recover 0123 // so close the socket immediately 0124 qWarning("Inconsistent state, probably due to some packet loss"); 0125 doCloseSocket(); 0126 return; 0127 } 0128 } 0129 0130 Q_EMIT responseReceived(message); 0131 0132 } catch (const KIMAP::ImapParserException &e) { 0133 qCWarning(KIMAP_LOG) << "The stream parser raised an exception:" << e.what(); 0134 } 0135 0136 if (m_stream->availableDataSize() > 1) { 0137 QMetaObject::invokeMethod(this, &SessionThread::readMessage, Qt::QueuedConnection); 0138 } 0139 } 0140 0141 // Called in main thread 0142 void SessionThread::closeSocket() 0143 { 0144 QMetaObject::invokeMethod(this, &SessionThread::doCloseSocket, Qt::QueuedConnection); 0145 } 0146 0147 // Called in secondary thread 0148 void SessionThread::doCloseSocket() 0149 { 0150 Q_ASSERT(QThread::currentThread() == thread()); 0151 if (!m_socket) { 0152 return; 0153 } 0154 m_encryptedMode = false; 0155 qCDebug(KIMAP_LOG) << "close"; 0156 m_socket->close(); 0157 } 0158 0159 // Called in secondary thread 0160 void SessionThread::reconnect() 0161 { 0162 Q_ASSERT(QThread::currentThread() == thread()); 0163 if (m_socket == nullptr) { // threadQuit already called 0164 return; 0165 } 0166 if (m_socket->state() != QSslSocket::ConnectedState && m_socket->state() != QSslSocket::ConnectingState) { 0167 QNetworkProxy proxy; 0168 if (!m_useProxy) { 0169 qCDebug(KIMAP_LOG) << "Connecting to IMAP server with no proxy"; 0170 proxy.setType(QNetworkProxy::NoProxy); 0171 } else { 0172 qCDebug(KIMAP_LOG) << "Connecting to IMAP server using default system proxy"; 0173 proxy.setType(QNetworkProxy::DefaultProxy); 0174 } 0175 m_socket->setProxy(proxy); 0176 0177 if (m_encryptedMode) { 0178 qCDebug(KIMAP_LOG) << "connectToHostEncrypted" << m_hostName << m_port; 0179 m_socket->connectToHostEncrypted(m_hostName, m_port); 0180 } else { 0181 qCDebug(KIMAP_LOG) << "connectToHost" << m_hostName << m_port; 0182 m_socket->connectToHost(m_hostName, m_port); 0183 } 0184 } 0185 } 0186 0187 // Called in secondary thread 0188 void SessionThread::threadInit() 0189 { 0190 Q_ASSERT(QThread::currentThread() == thread()); 0191 m_socket = std::make_unique<QSslSocket>(); 0192 m_stream = std::make_unique<ImapStreamParser>(m_socket.get()); 0193 connect(m_socket.get(), &QIODevice::readyRead, this, &SessionThread::readMessage, Qt::QueuedConnection); 0194 0195 // Delay the call to slotSocketDisconnected so that it finishes disconnecting before we call reconnect() 0196 connect(m_socket.get(), &QSslSocket::disconnected, this, &SessionThread::slotSocketDisconnected, Qt::QueuedConnection); 0197 connect(m_socket.get(), &QSslSocket::connected, this, &SessionThread::socketConnected); 0198 connect(m_socket.get(), &QAbstractSocket::errorOccurred, this, &SessionThread::slotSocketError); 0199 0200 connect(m_socket.get(), &QIODevice::bytesWritten, this, &SessionThread::socketActivity); 0201 connect(m_socket.get(), &QSslSocket::encryptedBytesWritten, this, &SessionThread::socketActivity); 0202 connect(m_socket.get(), &QIODevice::readyRead, this, &SessionThread::socketActivity); 0203 QMetaObject::invokeMethod(this, &SessionThread::reconnect, Qt::QueuedConnection); 0204 } 0205 0206 // Called in secondary thread 0207 void SessionThread::threadQuit() 0208 { 0209 Q_ASSERT(QThread::currentThread() == thread()); 0210 m_stream.reset(); 0211 m_socket.reset(); 0212 thread()->quit(); 0213 } 0214 0215 // Called in secondary thread 0216 void SessionThread::setUseProxyInternal(bool useProxy) 0217 { 0218 m_useProxy = useProxy; 0219 if (m_socket != nullptr) { 0220 if (m_socket->state() != QSslSocket::UnconnectedState) { 0221 m_socket->disconnectFromHost(); 0222 QMetaObject::invokeMethod(this, &SessionThread::reconnect, Qt::QueuedConnection); 0223 } 0224 } 0225 } 0226 0227 // Called in primary thread 0228 void SessionThread::startSsl(QSsl::SslProtocol protocol) 0229 { 0230 QMetaObject::invokeMethod(this, [this, protocol]() { 0231 doStartSsl(protocol); 0232 }); 0233 } 0234 0235 // Called in secondary thread (via invokeMethod) 0236 void SessionThread::doStartSsl(QSsl::SslProtocol protocol) 0237 { 0238 Q_ASSERT(QThread::currentThread() == thread()); 0239 if (!m_socket) { 0240 return; 0241 } 0242 0243 m_socket->setProtocol(protocol); 0244 m_socket->ignoreSslErrors(); // Don't worry, errors are handled manually below 0245 connect(m_socket.get(), &QSslSocket::encrypted, this, &SessionThread::sslConnected); 0246 m_socket->startClientEncryption(); 0247 } 0248 0249 // Called in secondary thread 0250 void SessionThread::slotSocketDisconnected() 0251 { 0252 Q_ASSERT(QThread::currentThread() == thread()); 0253 Q_EMIT socketDisconnected(); 0254 } 0255 0256 // Called in secondary thread 0257 void SessionThread::slotSocketError(QAbstractSocket::SocketError error) 0258 { 0259 Q_ASSERT(QThread::currentThread() == thread()); 0260 if (!m_socket) { 0261 return; 0262 } 0263 Q_EMIT socketError(error); 0264 } 0265 0266 // Called in secondary thread 0267 void SessionThread::sslConnected() 0268 { 0269 Q_ASSERT(QThread::currentThread() == thread()); 0270 if (!m_socket) { 0271 return; 0272 } 0273 QSslCipher cipher = m_socket->sessionCipher(); 0274 if (!m_socket->sslHandshakeErrors().isEmpty() || !m_socket->isEncrypted() || cipher.isNull() || cipher.usedBits() == 0) { 0275 qCDebug(KIMAP_LOG) << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull() << ", cipher.usedBits() is" << cipher.usedBits() 0276 << ", the socket says:" << m_socket->errorString() << "and the list of SSL errors contains" << m_socket->sslHandshakeErrors().count() 0277 << "items."; 0278 KSslErrorUiData errorData(m_socket.get()); 0279 Q_EMIT sslError(errorData); 0280 } else { 0281 qCDebug(KIMAP_LOG) << "TLS negotiation done, the negotiated protocol is" << cipher.protocolString(); 0282 m_encryptedMode = true; 0283 Q_EMIT encryptionNegotiationResult(true, m_socket->sessionProtocol()); 0284 } 0285 } 0286 0287 void SessionThread::sslErrorHandlerResponse(bool response) 0288 { 0289 QMetaObject::invokeMethod(this, [this, response]() { 0290 doSslErrorHandlerResponse(response); 0291 }); 0292 } 0293 0294 // Called in secondary thread (via invokeMethod) 0295 void SessionThread::doSslErrorHandlerResponse(bool response) 0296 { 0297 Q_ASSERT(QThread::currentThread() == thread()); 0298 if (!m_socket) { 0299 return; 0300 } 0301 if (response) { 0302 m_encryptedMode = true; 0303 Q_EMIT encryptionNegotiationResult(true, m_socket->sessionProtocol()); 0304 } else { 0305 m_encryptedMode = false; 0306 Q_EMIT socketError(QAbstractSocket::SslInvalidUserDataError); 0307 m_socket->disconnectFromHost(); 0308 } 0309 } 0310 0311 #include "moc_sessionthread_p.cpp"