diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b16356..33eb119 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,90 +1,90 @@ cmake_minimum_required(VERSION 3.5) -set(PIM_VERSION "5.11.40") +set(PIM_VERSION "5.11.41") project(KIMAP VERSION ${PIM_VERSION}) # ECM setup set(KF5_MIN_VERSION "5.58.0") find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(GenerateExportHeader) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(ECMSetupVersion) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMQtDeclareLoggingCategory) set(QT_REQUIRED_VERSION "5.10.0") set(KIMAP_LIB_VERSION ${PIM_VERSION}) set(KMIME_LIBS_VERSION "5.11.40") ecm_setup_version(PROJECT VARIABLE_PREFIX KIMAP VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kimap_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5IMAPConfigVersion.cmake" SOVERSION 5 ) ########### Find packages ########### find_package(Sasl2) set_package_properties(Sasl2 PROPERTIES TYPE REQUIRED) find_package(KF5CoreAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5I18n ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5KIO ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_LIBS_VERSION} CONFIG REQUIRED) ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5IMAP") add_definitions(-DTRANSLATION_DOMAIN=\"libkimap5\") add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5IMAPConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5IMAPConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) ########### Targets ########### add_subdirectory(src) if(BUILD_TESTING) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Test) add_subdirectory(autotests) add_subdirectory(tests) endif() ########### Install Files ########### install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5IMAPConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5IMAPConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5IMAPTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5IMAPTargets.cmake NAMESPACE KF5::) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kimap_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) if (${ECM_VERSION} STRGREATER "5.58.0") install(FILES kimap.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) else() install(FILES kimap.categories DESTINATION ${KDE_INSTALL_CONFDIR}) endif() if (${ECM_VERSION} STRGREATER "5.58.0") install(FILES kimap.renamecategories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) else() install(FILES kimap.renamecategories DESTINATION ${KDE_INSTALL_CONFDIR}) endif() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/session.cpp b/src/session.cpp index 81f77c7..ead3c59 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -1,530 +1,535 @@ /* Copyright (c) 2009 Kevin Ottens Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Author: 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 "session.h" #include "session_p.h" #include "sessionuiproxy.h" #include #include #include "kimap_debug.h" #include "job.h" #include "job_p.h" #include "loginjob.h" #include "response_p.h" #include "sessionlogger_p.h" #include "sessionthread_p.h" #include "rfccodecs.h" Q_DECLARE_METATYPE(KTcpSocket::SslVersion) Q_DECLARE_METATYPE(QSslSocket::SslMode) static const int _kimap_sslVersionId = qRegisterMetaType(); using namespace KIMAP; Session::Session(const QString &hostName, quint16 port, QObject *parent) : QObject(parent), d(new SessionPrivate(this)) { if (!qEnvironmentVariableIsEmpty("KIMAP_LOGFILE")) { d->logger = new SessionLogger; } d->isSocketConnected = false; d->state = Disconnected; d->jobRunning = false; d->thread = new SessionThread(hostName, port); connect(d->thread, &SessionThread::encryptionNegotiationResult, d, &SessionPrivate::onEncryptionNegotiationResult); connect(d->thread, &SessionThread::sslError, d, &SessionPrivate::handleSslError); connect(d->thread, &SessionThread::socketDisconnected, d, &SessionPrivate::socketDisconnected); connect(d->thread, &SessionThread::responseReceived, d, &SessionPrivate::responseReceived); connect(d->thread, &SessionThread::socketConnected, d, &SessionPrivate::socketConnected); connect(d->thread, &SessionThread::socketActivity, d, &SessionPrivate::socketActivity); connect(d->thread, &SessionThread::socketError, d, &SessionPrivate::socketError); d->socketTimer.setSingleShot(true); connect(&d->socketTimer, &QTimer::timeout, d, &SessionPrivate::onSocketTimeout); d->startSocketTimer(); } Session::~Session() { //Make sure all jobs know we're done d->socketDisconnected(); delete d->thread; d->thread = nullptr; } void Session::setUiProxy(const SessionUiProxy::Ptr &proxy) { d->uiProxy = proxy; } void Session::setUiProxy(SessionUiProxy *proxy) { setUiProxy(SessionUiProxy::Ptr(proxy)); } QString Session::hostName() const { return d->thread->hostName(); } quint16 Session::port() const { return d->thread->port(); } +void Session::setUseNetworkProxy(bool useProxy) +{ + d->thread->setUseNetworkProxy(useProxy); +} + Session::State Session::state() const { return d->state; } QString Session::userName() const { return d->userName; } QByteArray Session::serverGreeting() const { return d->greeting; } int Session::jobQueueSize() const { return d->queue.size() + (d->jobRunning ? 1 : 0); } void KIMAP::Session::close() { d->thread->closeSocket(); } void SessionPrivate::handleSslError(const KSslErrorUiData &errorData) { //ignoreSslError is async, so the thread might already be gone when it returns QPointer _t = thread; const bool ignoreSslError = uiProxy && uiProxy->ignoreSslError(errorData); if (_t) { _t->sslErrorHandlerResponse(ignoreSslError); } } SessionPrivate::SessionPrivate(Session *session) : QObject(session), q(session), isSocketConnected(false), state(Session::Disconnected), logger(nullptr), thread(nullptr), jobRunning(false), currentJob(nullptr), tagCount(0), sslVersion(KTcpSocket::UnknownSslVersion), socketTimerInterval(30000) // By default timeouts on 30s { } SessionPrivate::~SessionPrivate() { delete logger; } void SessionPrivate::addJob(Job *job) { queue.append(job); emit q->jobQueueSizeChanged(q->jobQueueSize()); QObject::connect(job, &KJob::result, this, &SessionPrivate::jobDone); QObject::connect(job, &QObject::destroyed, this, &SessionPrivate::jobDestroyed); if (state != Session::Disconnected) { startNext(); } } void SessionPrivate::startNext() { QMetaObject::invokeMethod(this, &SessionPrivate::doStartNext); } void SessionPrivate::doStartNext() { if (queue.isEmpty() || jobRunning || !isSocketConnected) { return; } restartSocketTimer(); jobRunning = true; currentJob = queue.dequeue(); currentJob->doStart(); } void SessionPrivate::jobDone(KJob *job) { Q_UNUSED(job); Q_ASSERT(job == currentJob); stopSocketTimer(); jobRunning = false; currentJob = nullptr; emit q->jobQueueSizeChanged(q->jobQueueSize()); startNext(); } void SessionPrivate::jobDestroyed(QObject *job) { queue.removeAll(static_cast(job)); if (currentJob == job) { currentJob = nullptr; } } void SessionPrivate::responseReceived(const Response &response) { if (logger && isConnected()) { logger->dataReceived(response.toString()); } QByteArray tag; QByteArray code; if (response.content.size() >= 1) { tag = response.content[0].toString(); } if (response.content.size() >= 2) { code = response.content[1].toString(); } // BYE may arrive as part of a LOGOUT sequence or before the server closes the connection after an error. // In any case we should wait until the server closes the connection, so we don't have to do anything. if (code == "BYE") { Response simplified = response; if (simplified.content.size() >= 2) { simplified.content.removeFirst(); // Strip the tag simplified.content.removeFirst(); // Strip the code } qCDebug(KIMAP_LOG) << "Received BYE: " << simplified.toString(); return; } switch (state) { case Session::Disconnected: if (socketTimer.isActive()) { stopSocketTimer(); } if (code == "OK") { setState(Session::NotAuthenticated); Response simplified = response; simplified.content.removeFirst(); // Strip the tag simplified.content.removeFirst(); // Strip the code greeting = simplified.toString().trimmed(); // Save the server greeting startNext(); } else if (code == "PREAUTH") { setState(Session::Authenticated); Response simplified = response; simplified.content.removeFirst(); // Strip the tag simplified.content.removeFirst(); // Strip the code greeting = simplified.toString().trimmed(); // Save the server greeting startNext(); } else { thread->closeSocket(); } return; case Session::NotAuthenticated: if (code == "OK" && tag == authTag) { setState(Session::Authenticated); } break; case Session::Authenticated: if (code == "OK" && tag == selectTag) { setState(Session::Selected); currentMailBox = upcomingMailBox; } break; case Session::Selected: if ((code == "OK" && tag == closeTag) || (code != "OK" && tag == selectTag)) { setState(Session::Authenticated); currentMailBox = QByteArray(); } else if (code == "OK" && tag == selectTag) { currentMailBox = upcomingMailBox; } break; } if (tag == authTag) { authTag.clear(); } if (tag == selectTag) { selectTag.clear(); } if (tag == closeTag) { closeTag.clear(); } // If a job is running forward it the response if (currentJob != nullptr) { restartSocketTimer(); currentJob->handleResponse(response); } else { qCWarning(KIMAP_LOG) << "A message was received from the server with no job to handle it:" << response.toString() << '(' + response.toString().toHex() + ')'; } } void SessionPrivate::setState(Session::State s) { if (s != state) { Session::State oldState = state; state = s; emit q->stateChanged(state, oldState); } } QByteArray SessionPrivate::sendCommand(const QByteArray &command, const QByteArray &args) { QByteArray tag = 'A' + QByteArray::number(++tagCount).rightJustified(6, '0'); QByteArray payload = tag + ' ' + command; if (!args.isEmpty()) { payload += ' ' + args; } sendData(payload); if (command == "LOGIN" || command == "AUTHENTICATE") { authTag = tag; } else if (command == "SELECT" || command == "EXAMINE") { selectTag = tag; upcomingMailBox = args; upcomingMailBox.remove(0, 1); upcomingMailBox = upcomingMailBox.left(upcomingMailBox.indexOf('\"')); upcomingMailBox = KIMAP::decodeImapFolderName(upcomingMailBox); } else if (command == "CLOSE") { closeTag = tag; } return tag; } void SessionPrivate::sendData(const QByteArray &data) { restartSocketTimer(); if (logger && isConnected()) { logger->dataSent(data); } thread->sendData(data + "\r\n"); } void SessionPrivate::socketConnected() { stopSocketTimer(); isSocketConnected = true; bool willUseSsl = false; if (!queue.isEmpty()) { KIMAP::LoginJob *login = qobject_cast(queue.first()); if (login) { willUseSsl = (login->encryptionMode() == KIMAP::LoginJob::SSLorTLS); userName = login->userName(); } } if (state == Session::Disconnected && willUseSsl) { startNext(); } else { startSocketTimer(); } } bool SessionPrivate::isConnected() const { return state == Session::Authenticated || state == Session::Selected; } void SessionPrivate::socketDisconnected() { if (socketTimer.isActive()) { stopSocketTimer(); } if (logger && isConnected()) { logger->disconnectionOccured(); } if (isSocketConnected) { setState(Session::Disconnected); emit q->connectionLost(); } else { emit q->connectionFailed(); } isSocketConnected = false; clearJobQueue(); } void SessionPrivate::socketActivity() { restartSocketTimer(); } void SessionPrivate::socketError(KTcpSocket::Error error) { if (socketTimer.isActive()) { stopSocketTimer(); } if (currentJob) { currentJob->d_ptr->setSocketError(error); } else if (!queue.isEmpty()) { currentJob = queue.takeFirst(); currentJob->d_ptr->setSocketError(error); } if (isSocketConnected) { thread->closeSocket(); } else { emit q->connectionFailed(); clearJobQueue(); } } void SessionPrivate::clearJobQueue() { if (currentJob) { currentJob->connectionLost(); } else if (!queue.isEmpty()) { currentJob = queue.takeFirst(); currentJob->connectionLost(); } QQueue queueCopy = queue; // copy because jobDestroyed calls removeAll qDeleteAll(queueCopy); queue.clear(); emit q->jobQueueSizeChanged(0); } void SessionPrivate::startSsl(KTcpSocket::SslVersion version) { thread->startSsl(version); } QString Session::selectedMailBox() const { return QString::fromUtf8(d->currentMailBox); } void SessionPrivate::onEncryptionNegotiationResult(bool isEncrypted, KTcpSocket::SslVersion version) { if (isEncrypted) { sslVersion = version; } else { sslVersion = KTcpSocket::UnknownSslVersion; } emit encryptionNegotiationResult(isEncrypted); } KTcpSocket::SslVersion SessionPrivate::negotiatedEncryption() const { return sslVersion; } void SessionPrivate::setSocketTimeout(int ms) { bool timerActive = socketTimer.isActive(); if (timerActive) { stopSocketTimer(); } socketTimerInterval = ms; if (timerActive) { startSocketTimer(); } } int SessionPrivate::socketTimeout() const { return socketTimerInterval; } void SessionPrivate::startSocketTimer() { if (socketTimerInterval < 0) { return; } Q_ASSERT(!socketTimer.isActive()); socketTimer.start(socketTimerInterval); } void SessionPrivate::stopSocketTimer() { if (socketTimerInterval < 0) { return; } socketTimer.stop(); } void SessionPrivate::restartSocketTimer() { if (socketTimer.isActive()) { stopSocketTimer(); } startSocketTimer(); } void SessionPrivate::onSocketTimeout() { qCDebug(KIMAP_LOG) << "Socket timeout!"; thread->closeSocket(); } void Session::setTimeout(int timeout) { d->setSocketTimeout(timeout * 1000); } int Session::timeout() const { return d->socketTimeout() / 1000; } #include "moc_session.cpp" #include "moc_session_p.cpp" diff --git a/src/session.h b/src/session.h index 564e26e..d6e19fe 100644 --- a/src/session.h +++ b/src/session.h @@ -1,140 +1,152 @@ /* 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. */ #ifndef KIMAP_SESSION_H #define KIMAP_SESSION_H #include "kimap_export.h" #include #include "sessionuiproxy.h" namespace KIMAP { class SessionPrivate; class JobPrivate; struct Response; class KIMAP_EXPORT Session : public QObject { Q_OBJECT friend class JobPrivate; public: enum State { Disconnected = 0, NotAuthenticated, Authenticated, Selected }; Q_ENUM(State) Session(const QString &hostName, quint16 port, QObject *parent = nullptr); ~Session(); Q_REQUIRED_RESULT QString hostName() const; Q_REQUIRED_RESULT quint16 port() const; Q_REQUIRED_RESULT State state() const; /** * Returns the name that has been set with LoginJob::setUserName() * The user name is useful to uniquely identify an IMAP resource, in combination with the host name * @note If the Session was pre-authenticated, userName() will return an empty string * @since 4.7 */ Q_REQUIRED_RESULT QString userName() const; Q_REQUIRED_RESULT QByteArray serverGreeting() const; /** * Sets an ui proxy that displays the error messages and waits for user feedback. * @param proxy the ui proxy object */ void setUiProxy(const SessionUiProxy::Ptr &proxy); /** * Sets an ui proxy that displays the error messages and waits for user feedback. * @param proxy the ui proxy object * @deprecated Use the shared pointer version instead */ KIMAP_DEPRECATED void setUiProxy(SessionUiProxy *proxy); /** * Set the session timeout. The default is 30 seconds. * @param timeout The socket timeout in seconds, negative values disable the timeout. * @since 4.6 */ void setTimeout(int timeout); /** * Returns the session timeout. * @since 4.12 */ Q_REQUIRED_RESULT int timeout() const; /** * Returns the currently selected mailbox. * @since 4.5 */ Q_REQUIRED_RESULT QString selectedMailBox() const; + /** + * Sets whether the IMAP network connection should use the system proxy settings. + * + * @param useProxy @c true if the proxy is to be used + * The default is to not use the proxy. + * @since 5.11.41 + * + * @note If the session is currently connected to the IMAP server, calling this + * function will disconnect and reconnect to it with the changed proxy setting. + */ + void setUseNetworkProxy(bool useProxy); + Q_REQUIRED_RESULT int jobQueueSize() const; void close(); Q_SIGNALS: void jobQueueSizeChanged(int queueSize); /** Emitted when we lose a previously established connection Likely reasons: server closed the connection, loss of internet connectivity, etc... */ void connectionLost(); /** Emitted when the Session couldn't connect to the host. Likely reasons: invalid host address, no internet connectivity, firewall blocking rules, etc... Pending jobs in the queue will be deleted, and the first job in the queue will be failed. (ie: it will have its result signal emitted with a non-zero error code.) @since 4.7 */ void connectionFailed(); /** Emitted when the session's state changes. Not very useful after all... :-) If you want to receive the stateChanged arguments in your slot, you must register the State enum with @c Q_DECLARE_METATYPE(KIMAP::Session::State) and @c qRegisterMetaType(); @since 4.7 */ void stateChanged(KIMAP::Session::State newState, KIMAP::Session::State oldState); private: friend class SessionPrivate; SessionPrivate *const d; }; } #endif diff --git a/src/sessionthread.cpp b/src/sessionthread.cpp index 0783b5e..20f166b 100644 --- a/src/sessionthread.cpp +++ b/src/sessionthread.cpp @@ -1,302 +1,333 @@ /* 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 "kimap_debug.h" #include "imapstreamparser.h" #include "response_p.h" using namespace KIMAP; Q_DECLARE_METATYPE(KTcpSocket::Error) Q_DECLARE_METATYPE(KSslErrorUiData) static const int _kimap_socketErrorTypeId = qRegisterMetaType(); static const int _kimap_sslErrorUiData = qRegisterMetaType(); SessionThread::SessionThread(const QString &hostName, quint16 port) : QObject(), m_hostName(hostName), m_port(port), m_socket(nullptr), m_stream(nullptr), m_mutex(), - m_encryptedMode(false) + 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() != SessionSocket::ConnectedState && m_socket->state() != SessionSocket::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 = new SessionSocket; m_stream = new ImapStreamParser(m_socket); connect(m_socket, &QIODevice::readyRead, this, &SessionThread::readMessage, Qt::QueuedConnection); // Delay the call to slotSocketDisconnected so that it finishes disconnecting before we call reconnect() connect(m_socket, &KTcpSocket::disconnected, this, &SessionThread::slotSocketDisconnected, Qt::QueuedConnection); connect(m_socket, &KTcpSocket::connected, this, &SessionThread::socketConnected); connect(m_socket, SIGNAL(error(KTcpSocket::Error)), this, SLOT(slotSocketError(KTcpSocket::Error))); connect(m_socket, &QIODevice::bytesWritten, this, &SessionThread::socketActivity); if (m_socket->metaObject()->indexOfSignal("encryptedBytesWritten(qint64)") > -1) { connect(m_socket, &KTcpSocket::encryptedBytesWritten, // needs kdelibs > 4.8 this, &SessionThread::socketActivity); } connect(m_socket, &QIODevice::readyRead, this, &SessionThread::socketActivity); QMetaObject::invokeMethod(this, &SessionThread::reconnect, Qt::QueuedConnection); } // Called in secondary thread void SessionThread::threadQuit() { Q_ASSERT(QThread::currentThread() == thread()); delete m_stream; m_stream = nullptr; delete m_socket; m_socket = nullptr; thread()->quit(); } +// Called in secondary thread +void SessionThread::setUseProxyInternal(bool useProxy) +{ + m_useProxy = useProxy; + if (m_socket != nullptr) { + if (m_socket->state() != SessionSocket::UnconnectedState) { + m_socket->disconnectFromHost(); + QMetaObject::invokeMethod(this, &SessionThread::reconnect, Qt::QueuedConnection); + } + } +} + // Called in primary thread void SessionThread::startSsl(KTcpSocket::SslVersion version) { QMetaObject::invokeMethod(this, [this, version]() { doStartSsl(version); }); } // Called in secondary thread (via invokeMethod) void SessionThread::doStartSsl(KTcpSocket::SslVersion version) { Q_ASSERT(QThread::currentThread() == thread()); if (!m_socket) { return; } m_socket->setAdvertisedSslVersion(version); m_socket->ignoreSslErrors(); connect(m_socket, &KTcpSocket::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(KTcpSocket::Error error) { Q_ASSERT(QThread::currentThread() == thread()); if (!m_socket) { return; } Q_UNUSED(error); // can be used for debugging emit socketError(error); } // Called in secondary thread void SessionThread::sslConnected() { Q_ASSERT(QThread::currentThread() == thread()); if (!m_socket) { return; } KSslCipher cipher = m_socket->sessionCipher(); if (!m_socket->sslErrors().isEmpty() || m_socket->encryptionMode() != KTcpSocket::SslClientMode || 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."; KSslErrorUiData errorData(m_socket); emit sslError(errorData); } else { qCDebug(KIMAP_LOG) << "TLS negotiation done."; m_encryptedMode = true; emit encryptionNegotiationResult(true, m_socket->negotiatedSslVersion()); } } 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->negotiatedSslVersion()); } 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, KTcpSocket::UnknownSslVersion); } } #include "moc_sessionthread_p.cpp" diff --git a/src/sessionthread_p.h b/src/sessionthread_p.h index 6fb3a69..f523494 100644 --- a/src/sessionthread_p.h +++ b/src/sessionthread_p.h @@ -1,99 +1,103 @@ /* 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. */ #ifndef KIMAP_SESSIONTHREAD_P_H #define KIMAP_SESSIONTHREAD_P_H #include #include #include typedef KTcpSocket SessionSocket; namespace KIMAP { class ImapStreamParser; struct Response; class SessionThread : public QObject { Q_OBJECT public: explicit SessionThread(const QString &hostName, quint16 port); ~SessionThread(); inline QString hostName() { return m_hostName; } inline quint16 port() { return m_port; } + void setUseNetworkProxy(bool useProxy); + void sendData(const QByteArray &payload); public Q_SLOTS: void closeSocket(); void startSsl(KTcpSocket::SslVersion version); void sslErrorHandlerResponse(bool result); Q_SIGNALS: void socketConnected(); void socketDisconnected(); void socketActivity(); void socketError(KTcpSocket::Error); void responseReceived(const KIMAP::Response &response); void encryptionNegotiationResult(bool, KTcpSocket::SslVersion); void sslError(const KSslErrorUiData &); private Q_SLOTS: void reconnect(); void threadInit(); void threadQuit(); void readMessage(); void writeDataQueue(); void sslConnected(); void doCloseSocket(); void slotSocketError(KTcpSocket::Error); void slotSocketDisconnected(); void doStartSsl(KTcpSocket::SslVersion); void doSslErrorHandlerResponse(bool result); + void setUseProxyInternal(bool useProxy); private: QString m_hostName; quint16 m_port; SessionSocket *m_socket = nullptr; ImapStreamParser *m_stream = nullptr; QQueue m_dataQueue; // Protects m_dataQueue QMutex m_mutex; bool m_encryptedMode = false; + bool m_useProxy = false; }; } #endif