diff --git a/src/sessionthread.cpp b/src/sessionthread.cpp index 17aa5d4..e5a80e2 100644 --- a/src/sessionthread.cpp +++ b/src/sessionthread.cpp @@ -1,330 +1,339 @@ /* Copyright (c) 2009 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sessionthread_p.h" #include #include #include #include #include #include "kimap_debug.h" #include "imapstreamparser.h" #include "response_p.h" using namespace KIMAP; Q_DECLARE_METATYPE(KSslErrorUiData) namespace { static const int _kimap_abstractSocketError = qRegisterMetaType(); static const int _kimap_sslErrorUiData = qRegisterMetaType(); } SessionThread::SessionThread(const QString &hostName, quint16 port) : QObject(), m_hostName(hostName), m_port(port), m_encryptedMode(false), m_useProxy(false) { // Just like the Qt docs now recommend, for event-driven threads: // don't derive from QThread, create one directly and move the object to it. QThread *thread = new QThread(); moveToThread(thread); thread->start(); QMetaObject::invokeMethod(this, &SessionThread::threadInit); } SessionThread::~SessionThread() { QMetaObject::invokeMethod(this, &SessionThread::threadQuit); if (!thread()->wait(10 * 1000)) { qCWarning(KIMAP_LOG) << "Session thread refuses to die, killing harder..."; thread()->terminate(); // Make sure to wait until it's done, otherwise it can crash when the pthread callback is called thread()->wait(); } delete thread(); } // Called in primary thread, passes setting to secondary thread void SessionThread::setUseNetworkProxy(bool useProxy) { QMetaObject::invokeMethod(this, [this, useProxy]() { setUseProxyInternal(useProxy); }, Qt::QueuedConnection); } // Called in primary thread void SessionThread::sendData(const QByteArray &payload) { QMutexLocker locker(&m_mutex); m_dataQueue.enqueue(payload); QMetaObject::invokeMethod(this, &SessionThread::writeDataQueue); } // Called in secondary thread void SessionThread::writeDataQueue() { Q_ASSERT(QThread::currentThread() == thread()); if (!m_socket) { return; } QMutexLocker locker(&m_mutex); while (!m_dataQueue.isEmpty()) { m_socket->write(m_dataQueue.dequeue()); } } // Called in secondary thread void SessionThread::readMessage() { Q_ASSERT(QThread::currentThread() == thread()); if (!m_stream || m_stream->availableDataSize() == 0) { return; } Response message; QList *payload = &message.content; try { while (!m_stream->atCommandEnd()) { if (m_stream->hasString()) { QByteArray string = m_stream->readString(); if (string == "NIL") { *payload << Response::Part(QList()); } else { *payload << Response::Part(string); } } else if (m_stream->hasList()) { *payload << Response::Part(m_stream->readParenthesizedList()); } else if (m_stream->hasResponseCode()) { payload = &message.responseCode; } else if (m_stream->atResponseCodeEnd()) { payload = &message.content; } else if (m_stream->hasLiteral()) { QByteArray literal; while (!m_stream->atLiteralEnd()) { literal += m_stream->readLiteralPart(); } *payload << Response::Part(literal); } else { // Oops! Something really bad happened, we won't be able to recover // so close the socket immediately qWarning("Inconsistent state, probably due to some packet loss"); doCloseSocket(); return; } } emit responseReceived(message); } catch (const KIMAP::ImapParserException &e) { qCWarning(KIMAP_LOG) << "The stream parser raised an exception:" << e.what(); } if (m_stream->availableDataSize() > 1) { QMetaObject::invokeMethod(this, &SessionThread::readMessage, Qt::QueuedConnection); } } // Called in main thread void SessionThread::closeSocket() { QMetaObject::invokeMethod(this, &SessionThread::doCloseSocket, Qt::QueuedConnection); } // Called in secondary thread void SessionThread::doCloseSocket() { Q_ASSERT(QThread::currentThread() == thread()); if (!m_socket) { return; } m_encryptedMode = false; qCDebug(KIMAP_LOG) << "close"; m_socket->close(); } // Called in secondary thread void SessionThread::reconnect() { Q_ASSERT(QThread::currentThread() == thread()); if (m_socket == nullptr) { // threadQuit already called return; } if (m_socket->state() != QSslSocket::ConnectedState && m_socket->state() != QSslSocket::ConnectingState) { QNetworkProxy proxy; if (!m_useProxy) { qCDebug(KIMAP_LOG) << "Connecting to IMAP server with no proxy"; proxy.setType(QNetworkProxy::NoProxy); } else { qCDebug(KIMAP_LOG) << "Connecting to IMAP server using default system proxy"; proxy.setType(QNetworkProxy::DefaultProxy); } m_socket->setProxy(proxy); if (m_encryptedMode) { qCDebug(KIMAP_LOG) << "connectToHostEncrypted" << m_hostName << m_port; m_socket->connectToHostEncrypted(m_hostName, m_port); } else { qCDebug(KIMAP_LOG) << "connectToHost" << m_hostName << m_port; m_socket->connectToHost(m_hostName, m_port); } } } // Called in secondary thread void SessionThread::threadInit() { Q_ASSERT(QThread::currentThread() == thread()); m_socket = std::make_unique(); m_stream = std::make_unique(m_socket.get()); connect(m_socket.get(), &QIODevice::readyRead, this, &SessionThread::readMessage, Qt::QueuedConnection); // Delay the call to slotSocketDisconnected so that it finishes disconnecting before we call reconnect() connect(m_socket.get(), &QSslSocket::disconnected, this, &SessionThread::slotSocketDisconnected, Qt::QueuedConnection); connect(m_socket.get(), &QSslSocket::connected, this, &SessionThread::socketConnected); connect(m_socket.get(), QOverload::of(&QAbstractSocket::error), this, &SessionThread::slotSocketError); connect(m_socket.get(), &QIODevice::bytesWritten, this, &SessionThread::socketActivity); connect(m_socket.get(), &QSslSocket::encryptedBytesWritten, this, &SessionThread::socketActivity); connect(m_socket.get(), &QIODevice::readyRead, this, &SessionThread::socketActivity); QMetaObject::invokeMethod(this, &SessionThread::reconnect, Qt::QueuedConnection); } // Called in secondary thread void SessionThread::threadQuit() { Q_ASSERT(QThread::currentThread() == thread()); m_stream.reset(); m_socket.reset(); thread()->quit(); } // Called in secondary thread void SessionThread::setUseProxyInternal(bool useProxy) { m_useProxy = useProxy; if (m_socket != nullptr) { if (m_socket->state() != QSslSocket::UnconnectedState) { m_socket->disconnectFromHost(); QMetaObject::invokeMethod(this, &SessionThread::reconnect, Qt::QueuedConnection); } } } // Called in primary thread void SessionThread::startSsl(QSsl::SslProtocol protocol) { QMetaObject::invokeMethod(this, [this, protocol]() { doStartSsl(protocol); }); } // Called in secondary thread (via invokeMethod) void SessionThread::doStartSsl(QSsl::SslProtocol protocol) { Q_ASSERT(QThread::currentThread() == thread()); if (!m_socket) { return; } m_socket->setProtocol(protocol); m_socket->ignoreSslErrors(); // Don't worry, errors are handled manually below connect(m_socket.get(), &QSslSocket::encrypted, this, &SessionThread::sslConnected); m_socket->startClientEncryption(); } // Called in secondary thread void SessionThread::slotSocketDisconnected() { Q_ASSERT(QThread::currentThread() == thread()); emit socketDisconnected(); } // Called in secondary thread void SessionThread::slotSocketError(QAbstractSocket::SocketError error) { Q_ASSERT(QThread::currentThread() == thread()); if (!m_socket) { return; } emit socketError(error); } // Called in secondary thread void SessionThread::sslConnected() { Q_ASSERT(QThread::currentThread() == thread()); if (!m_socket) { return; } QSslCipher cipher = m_socket->sessionCipher(); - - if (!m_socket->sslErrors().isEmpty() || !m_socket->isEncrypted() +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) + if (!m_socket->sslErrors().isEmpty() +#else + if (!m_socket->sslHandshakeErrors().isEmpty() +#endif + || !m_socket->isEncrypted() || cipher.isNull() || cipher.usedBits() == 0) { qCDebug(KIMAP_LOG) << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull() << ", cipher.usedBits() is" << cipher.usedBits() << ", the socket says:" << m_socket->errorString() << "and the list of SSL errors contains" - << m_socket->sslErrors().count() << "items."; + #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) + << m_socket->sslErrors().count() + #else + << m_socket->sslHandshakeErrors().count() + #endif + << "items."; KSslErrorUiData errorData(m_socket.get()); emit sslError(errorData); } else { qCDebug(KIMAP_LOG) << "TLS negotiation done, the negotiated protocol is" << cipher.protocolString(); m_encryptedMode = true; emit encryptionNegotiationResult(true, m_socket->sessionProtocol()); } } void SessionThread::sslErrorHandlerResponse(bool response) { QMetaObject::invokeMethod(this, [this, response]() { doSslErrorHandlerResponse(response); }); } // Called in secondary thread (via invokeMethod) void SessionThread::doSslErrorHandlerResponse(bool response) { Q_ASSERT(QThread::currentThread() == thread()); if (!m_socket) { return; } if (response) { m_encryptedMode = true; emit encryptionNegotiationResult(true, m_socket->sessionProtocol()); } else { m_encryptedMode = false; //reconnect in unencrypted mode, so new commands can be issued m_socket->disconnectFromHost(); m_socket->waitForDisconnected(); m_socket->connectToHost(m_hostName, m_port); emit encryptionNegotiationResult(false, QSsl::UnknownProtocol); } } #include "moc_sessionthread_p.cpp"