diff --git a/CMakeLists.txt b/CMakeLists.txt index efb89fe..c739ba5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,86 +1,88 @@ cmake_minimum_required(VERSION 3.5) set(PIM_VERSION "5.14.40") project(KIMAP VERSION ${PIM_VERSION}) set(CMAKE_CXX_STANDARD 14) # ECM setup set(KF5_MIN_VERSION "5.69.0") find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(GenerateExportHeader) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(ECMSetupVersion) include(FeatureSummary) include(ECMQtDeclareLoggingCategory) set(QT_REQUIRED_VERSION "5.12.0") set(KIMAP_LIB_VERSION ${PIM_VERSION}) set(KMIME_LIBS_VERSION "5.14.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\") if (EXISTS "${CMAKE_SOURCE_DIR}/.git") add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050e00) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054500) endif() +add_definitions(-DQT_NO_SIGNALS_SLOTS_KEYWORDS) +add_definitions(-DQT_NO_EMIT) 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 ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/capabilitiesjob.cpp b/src/capabilitiesjob.cpp index 054d837..4caaa87 100644 --- a/src/capabilitiesjob.cpp +++ b/src/capabilitiesjob.cpp @@ -1,76 +1,76 @@ /* 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 "capabilitiesjob.h" #include #include "job_p.h" #include "response_p.h" #include "session_p.h" namespace KIMAP { class CapabilitiesJobPrivate : public JobPrivate { public: CapabilitiesJobPrivate(Session *session, const QString &name) : JobPrivate(session, name) { } ~CapabilitiesJobPrivate() { } QStringList capabilities; }; } using namespace KIMAP; CapabilitiesJob::CapabilitiesJob(Session *session) : Job(*new CapabilitiesJobPrivate(session, i18n("Capabilities"))) { } CapabilitiesJob::~CapabilitiesJob() { } QStringList CapabilitiesJob::capabilities() const { Q_D(const CapabilitiesJob); return d->capabilities; } void CapabilitiesJob::doStart() { Q_D(CapabilitiesJob); d->tags << d->sessionInternal()->sendCommand("CAPABILITY"); } void CapabilitiesJob::handleResponse(const Response &response) { Q_D(CapabilitiesJob); if (handleErrorReplies(response) == NotHandled) { const int responseSize(response.content.size()); if (responseSize >= 2 && response.content[1].toString() == "CAPABILITY") { for (int i = 2; i < responseSize; ++i) { d->capabilities << QLatin1String(response.content[i].toString().toUpper()); } - emit capabilitiesReceived(d->capabilities); + Q_EMIT capabilitiesReceived(d->capabilities); } } } diff --git a/src/fetchjob.cpp b/src/fetchjob.cpp index a628274..5f7ebc9 100644 --- a/src/fetchjob.cpp +++ b/src/fetchjob.cpp @@ -1,608 +1,608 @@ /* 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 "fetchjob.h" #include #include "kimap_debug.h" #include #include "job_p.h" #include "response_p.h" #include "session_p.h" namespace KIMAP { class FetchJobPrivate : public JobPrivate { public: FetchJobPrivate(FetchJob *job, Session *session, const QString &name) : JobPrivate(session, name) , q(job) , uidBased(false) , gmailEnabled(false) { } ~FetchJobPrivate() { } void parseBodyStructure(const QByteArray &structure, int &pos, KMime::Content *content); void parsePart(const QByteArray &structure, int &pos, KMime::Content *content); QByteArray parseString(const QByteArray &structure, int &pos); QByteArray parseSentence(const QByteArray &structure, int &pos); void skipLeadingSpaces(const QByteArray &structure, int &pos); void emitPendings() { if (pendingMsgs.isEmpty()) { return; } Q_EMIT q->messagesAvailable(pendingMsgs); if (!pendingParts.isEmpty()) { - emit q->partsReceived(selectedMailBox, + Q_EMIT q->partsReceived(selectedMailBox, pendingUids, pendingParts); - emit q->partsReceived(selectedMailBox, + Q_EMIT q->partsReceived(selectedMailBox, pendingUids, pendingAttributes, pendingParts); } if (!pendingSizes.isEmpty() || !pendingFlags.isEmpty() || !pendingMessages.isEmpty()) { - emit q->headersReceived(selectedMailBox, + Q_EMIT q->headersReceived(selectedMailBox, pendingUids, pendingSizes, pendingFlags, pendingMessages); - emit q->headersReceived(selectedMailBox, + Q_EMIT q->headersReceived(selectedMailBox, pendingUids, pendingSizes, pendingAttributes, pendingFlags, pendingMessages); } if (!pendingMessages.isEmpty()) { - emit q->messagesReceived(selectedMailBox, + Q_EMIT q->messagesReceived(selectedMailBox, pendingUids, pendingMessages); - emit q->messagesReceived(selectedMailBox, + Q_EMIT q->messagesReceived(selectedMailBox, pendingUids, pendingAttributes, pendingMessages); } pendingUids.clear(); pendingMessages.clear(); pendingParts.clear(); pendingSizes.clear(); pendingFlags.clear(); pendingAttributes.clear(); pendingMsgs.clear(); } FetchJob *const q; ImapSet set; bool uidBased; FetchJob::FetchScope scope; QString selectedMailBox; bool gmailEnabled; QTimer emitPendingsTimer; QMap pendingMessages; QMap pendingParts; QMap pendingFlags; QMap pendingAttributes; QMap pendingSizes; QMap pendingUids; QMap pendingMsgs; }; } using namespace KIMAP; FetchJob::FetchScope::FetchScope(): mode(FetchScope::Content), changedSince(0) { } FetchJob::FetchJob(Session *session) : Job(*new FetchJobPrivate(this, session, i18n("Fetch"))) { Q_D(FetchJob); connect(&d->emitPendingsTimer, SIGNAL(timeout()), this, SLOT(emitPendings())); } FetchJob::~FetchJob() { } void FetchJob::setSequenceSet(const ImapSet &set) { Q_D(FetchJob); Q_ASSERT(!set.toImapSequenceSet().trimmed().isEmpty()); d->set = set; } ImapSet FetchJob::sequenceSet() const { Q_D(const FetchJob); return d->set; } void FetchJob::setUidBased(bool uidBased) { Q_D(FetchJob); d->uidBased = uidBased; } bool FetchJob::isUidBased() const { Q_D(const FetchJob); return d->uidBased; } void FetchJob::setScope(const FetchScope &scope) { Q_D(FetchJob); d->scope = scope; } FetchJob::FetchScope FetchJob::scope() const { Q_D(const FetchJob); return d->scope; } bool FetchJob::setGmailExtensionsEnabled() const { Q_D(const FetchJob); return d->gmailEnabled; } void FetchJob::setGmailExtensionsEnabled(bool enabled) { Q_D(FetchJob); d->gmailEnabled = enabled; } QString FetchJob::mailBox() const { Q_D(const FetchJob); return d->selectedMailBox; } void FetchJob::doStart() { Q_D(FetchJob); QByteArray parameters = d->set.toImapSequenceSet() + ' '; Q_ASSERT(!parameters.trimmed().isEmpty()); switch (d->scope.mode) { case FetchScope::Headers: if (d->scope.parts.isEmpty()) { parameters += "(RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID"; } else { parameters += '('; for (const QByteArray &part : qAsConst(d->scope.parts)) { parameters += "BODY.PEEK[" + part + ".MIME] "; } parameters += "UID"; } break; case FetchScope::Flags: parameters += "(FLAGS UID"; break; case FetchScope::Structure: parameters += "(BODYSTRUCTURE UID"; break; case FetchScope::Content: if (d->scope.parts.isEmpty()) { parameters += "(BODY.PEEK[] UID"; } else { parameters += '('; for (const QByteArray &part : qAsConst(d->scope.parts)) { parameters += "BODY.PEEK[" + part + "] "; } parameters += "UID"; } break; case FetchScope::Full: parameters += "(RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID"; break; case FetchScope::HeaderAndContent: if (d->scope.parts.isEmpty()) { parameters += "(BODY.PEEK[] FLAGS UID"; } else { parameters += "(BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)]"; for (const QByteArray &part : qAsConst(d->scope.parts)) { parameters += " BODY.PEEK[" + part + ".MIME] BODY.PEEK[" + part + "]"; //krazy:exclude=doublequote_chars } parameters += " FLAGS UID"; } break; case FetchScope::FullHeaders: parameters += "(RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER] FLAGS UID"; break; } if (d->gmailEnabled) { parameters += " X-GM-LABELS X-GM-MSGID X-GM-THRID"; } parameters += ")"; if (d->scope.changedSince > 0) { parameters += " (CHANGEDSINCE " + QByteArray::number(d->scope.changedSince) + ")"; } QByteArray command = "FETCH"; if (d->uidBased) { command = "UID " + command; } d->emitPendingsTimer.start(100); d->selectedMailBox = d->m_session->selectedMailBox(); d->tags << d->sessionInternal()->sendCommand(command, parameters); } void FetchJob::handleResponse(const Response &response) { Q_D(FetchJob); // We can predict it'll be handled by handleErrorReplies() so stop // the timer now so that result() will really be the last emitted signal. if (!response.content.isEmpty() && d->tags.size() == 1 && d->tags.contains(response.content.first().toString())) { d->emitPendingsTimer.stop(); d->emitPendings(); } if (handleErrorReplies(response) == NotHandled) { if (response.content.size() == 4 && response.content[2].toString() == "FETCH" && response.content[3].type() == Response::Part::List) { qint64 id = response.content[1].toString().toLongLong(); QList content = response.content[3].toList(); Message msg; MessagePtr message(new KMime::Message); bool shouldParseMessage = false; MessageParts parts; for (QList::ConstIterator it = content.constBegin(); it != content.constEnd(); ++it) { QByteArray str = *it; ++it; if (it == content.constEnd()) { // Uh oh, message was truncated? qCWarning(KIMAP_LOG) << "FETCH reply got truncated, skipping."; break; } if (str == "UID") { d->pendingUids[id] = msg.uid = it->toLongLong(); } else if (str == "RFC822.SIZE") { d->pendingSizes[id] = msg.size = it->toLongLong(); } else if (str == "INTERNALDATE") { message->date()->setDateTime(QDateTime::fromString(QLatin1String(*it), Qt::RFC2822Date)); } else if (str == "FLAGS") { if ((*it).startsWith('(') && (*it).endsWith(')')) { QByteArray str = *it; str.chop(1); str.remove(0, 1); const auto flags = str.split(' '); d->pendingFlags[id] = flags; msg.flags = flags; } else { d->pendingFlags[id] << *it; msg.flags << *it; } } else if (str == "X-GM-LABELS") { d->pendingAttributes.insert(id, { "X-GM-LABELS", *it }); msg.attributes.insert("X-GM-LABELS", *it); } else if (str == "X-GM-THRID") { d->pendingAttributes.insert(id, { "X-GM-THRID", *it }); msg.attributes.insert("X-GM-THRID", *it); } else if (str == "X-GM-MSGID") { d->pendingAttributes.insert(id, { "X-GM-MSGID", *it }); msg.attributes.insert("X-GM-MSGID", *it); } else if (str == "BODYSTRUCTURE") { int pos = 0; d->parseBodyStructure(*it, pos, message.data()); message->assemble(); d->pendingMessages[id] = message; msg.message = message; } else if (str.startsWith("BODY[")) { //krazy:exclude=strings if (!str.endsWith(']')) { // BODY[ ... ] might have been split, skip until we find the ] while (!(*it).endsWith(']')) { ++it; } ++it; } int index; if ((index = str.indexOf("HEADER")) > 0 || (index = str.indexOf("MIME")) > 0) { // headers if (str[index - 1] == '.') { QByteArray partId = str.mid(5, index - 6); if (!parts.contains(partId)) { parts[partId] = ContentPtr(new KMime::Content); } parts[partId]->setHead(*it); parts[partId]->parse(); d->pendingParts[id] = parts; msg.parts = parts; } else { message->setHead(*it); shouldParseMessage = true; } } else { // full payload if (str == "BODY[]") { message->setContent(KMime::CRLFtoLF(*it)); shouldParseMessage = true; d->pendingMessages[id] = message; msg.message = message; } else { QByteArray partId = str.mid(5, str.size() - 6); if (!parts.contains(partId)) { parts[partId] = ContentPtr(new KMime::Content); } parts[partId]->setBody(*it); parts[partId]->parse(); d->pendingParts[id] = parts; msg.parts = parts; } } } } if (shouldParseMessage) { message->parse(); } // For the headers mode the message is built in several // steps, hence why we wait it to be done until putting it // in the pending queue. if (d->scope.mode == FetchScope::Headers || d->scope.mode == FetchScope::HeaderAndContent || d->scope.mode == FetchScope::FullHeaders) { d->pendingMessages[id] = message; msg.message = message; } d->pendingMsgs[id] = msg; } } } void FetchJobPrivate::parseBodyStructure(const QByteArray &structure, int &pos, KMime::Content *content) { skipLeadingSpaces(structure, pos); if (structure[pos] != '(') { return; } pos++; if (structure[pos] != '(') { // simple part pos--; parsePart(structure, pos, content); } else { // multi part content->contentType()->setMimeType("MULTIPART/MIXED"); while (pos < structure.size() && structure[pos] == '(') { KMime::Content *child = new KMime::Content; content->addContent(child); parseBodyStructure(structure, pos, child); child->assemble(); } QByteArray subType = parseString(structure, pos); content->contentType()->setMimeType("MULTIPART/" + subType); QByteArray parameters = parseSentence(structure, pos); // FIXME: Read the charset if (parameters.contains("BOUNDARY")) { content->contentType()->setBoundary(parameters.remove(0, parameters.indexOf("BOUNDARY") + 11).split('\"')[0]); } QByteArray disposition = parseSentence(structure, pos); if (disposition.contains("INLINE")) { content->contentDisposition()->setDisposition(KMime::Headers::CDinline); } else if (disposition.contains("ATTACHMENT")) { content->contentDisposition()->setDisposition(KMime::Headers::CDattachment); } parseSentence(structure, pos); // Ditch the body language } // Consume what's left while (pos < structure.size() && structure[pos] != ')') { skipLeadingSpaces(structure, pos); parseSentence(structure, pos); skipLeadingSpaces(structure, pos); } pos++; } void FetchJobPrivate::parsePart(const QByteArray &structure, int &pos, KMime::Content *content) { if (structure[pos] != '(') { return; } pos++; QByteArray mainType = parseString(structure, pos); QByteArray subType = parseString(structure, pos); content->contentType()->setMimeType(mainType + '/' + subType); parseSentence(structure, pos); // Ditch the parameters... FIXME: Read it to get charset and name parseString(structure, pos); // ... and the id content->contentDescription()->from7BitString(parseString(structure, pos)); parseString(structure, pos); // Ditch the encoding too parseString(structure, pos); // ... and the size parseString(structure, pos); // ... and the line count QByteArray disposition = parseSentence(structure, pos); if (disposition.contains("INLINE")) { content->contentDisposition()->setDisposition(KMime::Headers::CDinline); } else if (disposition.contains("ATTACHMENT")) { content->contentDisposition()->setDisposition(KMime::Headers::CDattachment); } if ((content->contentDisposition()->disposition() == KMime::Headers::CDattachment || content->contentDisposition()->disposition() == KMime::Headers::CDinline) && disposition.contains("FILENAME")) { QByteArray filename = disposition.remove(0, disposition.indexOf("FILENAME") + 11).split('\"')[0]; content->contentDisposition()->setFilename(QLatin1String(filename)); } // Consume what's left while (pos < structure.size() && structure[pos] != ')') { skipLeadingSpaces(structure, pos); parseSentence(structure, pos); skipLeadingSpaces(structure, pos); } } QByteArray FetchJobPrivate::parseSentence(const QByteArray &structure, int &pos) { QByteArray result; int stack = 0; skipLeadingSpaces(structure, pos); if (structure[pos] != '(') { return parseString(structure, pos); } int start = pos; do { switch (structure[pos]) { case '(': pos++; stack++; break; case ')': pos++; stack--; break; case '[': pos++; stack++; break; case ']': pos++; stack--; break; default: skipLeadingSpaces(structure, pos); parseString(structure, pos); skipLeadingSpaces(structure, pos); break; } } while (pos < structure.size() && stack != 0); result = structure.mid(start, pos - start); return result; } QByteArray FetchJobPrivate::parseString(const QByteArray &structure, int &pos) { QByteArray result; skipLeadingSpaces(structure, pos); int start = pos; bool foundSlash = false; // quoted string if (structure[pos] == '"') { pos++; Q_FOREVER { if (structure[pos] == '\\') { pos += 2; foundSlash = true; continue; } if (structure[pos] == '"') { result = structure.mid(start + 1, pos - start - 1); pos++; break; } pos++; } } else { // unquoted string Q_FOREVER { if (structure[pos] == ' ' || structure[pos] == '(' || structure[pos] == ')' || structure[pos] == '[' || structure[pos] == ']' || structure[pos] == '\n' || structure[pos] == '\r' || structure[pos] == '"') { break; } if (structure[pos] == '\\') { foundSlash = true; } pos++; } result = structure.mid(start, pos - start); // transform unquoted NIL if (result == "NIL") { result.clear(); } } // simplify slashes if (foundSlash) { while (result.contains("\\\"")) { result.replace("\\\"", "\""); } while (result.contains("\\\\")) { result.replace("\\\\", "\\"); } } return result; } void FetchJobPrivate::skipLeadingSpaces(const QByteArray &structure, int &pos) { while (pos < structure.size() && structure[pos] == ' ') { pos++; } } #include "moc_fetchjob.cpp" diff --git a/src/idlejob.cpp b/src/idlejob.cpp index 99bed73..0097321 100644 --- a/src/idlejob.cpp +++ b/src/idlejob.cpp @@ -1,171 +1,171 @@ /* 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 "idlejob.h" #include #include #include "job_p.h" #include "response_p.h" #include "session_p.h" namespace KIMAP { class IdleJobPrivate : public JobPrivate { public: IdleJobPrivate(IdleJob *job, Session *session, const QString &name) : JobPrivate(session, name), q(job), messageCount(-1), recentCount(-1), lastMessageCount(-1), lastRecentCount(-1), originalSocketTimeout(-1) { } ~IdleJobPrivate() { } void emitStats() { emitStatsTimer.stop(); - emit q->mailBoxStats(q, m_session->selectedMailBox(), + Q_EMIT q->mailBoxStats(q, m_session->selectedMailBox(), messageCount, recentCount); lastMessageCount = messageCount; lastRecentCount = recentCount; messageCount = -1; recentCount = -1; } void resetTimeout() { sessionInternal()->setSocketTimeout(originalSocketTimeout); } IdleJob *const q; QTimer emitStatsTimer; int messageCount; int recentCount; int lastMessageCount; int lastRecentCount; int originalSocketTimeout; }; } using namespace KIMAP; IdleJob::IdleJob(Session *session) : Job(*new IdleJobPrivate(this, session, i18nc("name of the idle job", "Idle"))) { Q_D(IdleJob); connect(&d->emitStatsTimer, SIGNAL(timeout()), this, SLOT(emitStats())); connect(this, SIGNAL(result(KJob*)), this, SLOT(resetTimeout())); } IdleJob::~IdleJob() { } void KIMAP::IdleJob::stop() { Q_D(IdleJob); d->sessionInternal()->setSocketTimeout(d->originalSocketTimeout); d->sessionInternal()->sendData("DONE"); } void IdleJob::doStart() { Q_D(IdleJob); d->originalSocketTimeout = d->sessionInternal()->socketTimeout(); d->sessionInternal()->setSocketTimeout(-1); d->tags << d->sessionInternal()->sendCommand("IDLE"); } void IdleJob::handleResponse(const Response &response) { Q_D(IdleJob); - // We can predict it'll be handled by handleErrorReplies() so emit + // We can predict it'll be handled by handleErrorReplies() so Q_EMIT // pending signals now (if needed) so that result() will really be // the last emitted signal. if (!response.content.isEmpty() && d->tags.size() == 1 && d->tags.contains(response.content.first().toString()) && (d->messageCount >= 0 || d->recentCount >= 0)) { d->emitStats(); } if (handleErrorReplies(response) == NotHandled) { if (!response.content.isEmpty() && response.content[0].toString() == "+") { // Got the continuation all is fine return; } else if (response.content.size() > 2) { const QByteArray ba = response.content[2].toString(); if (ba == "EXISTS") { if (d->messageCount >= 0) { d->emitStats(); } d->messageCount = response.content[1].toString().toInt(); } else if (ba == "RECENT") { if (d->recentCount >= 0) { d->emitStats(); } d->recentCount = response.content[1].toString().toInt(); } else if (ba == "FETCH") { const qint64 uid = response.content[1].toString().toLongLong(); Q_EMIT mailBoxMessageFlagsChanged(this, uid); } } if (d->messageCount >= 0 && d->recentCount >= 0) { d->emitStats(); } else if (d->messageCount >= 0 || d->recentCount >= 0) { d->emitStatsTimer.start(200); } } } QString KIMAP::IdleJob::lastMailBox() const { Q_D(const IdleJob); return d->m_session->selectedMailBox(); } int KIMAP::IdleJob::lastMessageCount() const { Q_D(const IdleJob); return d->lastMessageCount; } int KIMAP::IdleJob::lastRecentCount() const { Q_D(const IdleJob); return d->lastRecentCount; } #include "moc_idlejob.cpp" diff --git a/src/listjob.cpp b/src/listjob.cpp index 9a0a02d..634c95c 100644 --- a/src/listjob.cpp +++ b/src/listjob.cpp @@ -1,222 +1,222 @@ /* 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 "listjob.h" #include #include #include "job_p.h" #include "response_p.h" #include "rfccodecs.h" #include "session_p.h" namespace KIMAP { class ListJobPrivate : public JobPrivate { public: ListJobPrivate(ListJob *job, Session *session, const QString &name) : JobPrivate(session, name), q(job), option(ListJob::NoOption) { } ~ListJobPrivate() { } void emitPendings() { if (pendingDescriptors.isEmpty()) { return; } - emit q->mailBoxesReceived(pendingDescriptors, pendingFlags); + Q_EMIT q->mailBoxesReceived(pendingDescriptors, pendingFlags); pendingDescriptors.clear(); pendingFlags.clear(); } ListJob *const q; ListJob::Option option; QList namespaces; QByteArray command; QTimer emitPendingsTimer; QList pendingDescriptors; QList< QList > pendingFlags; }; } using namespace KIMAP; ListJob::ListJob(Session *session) : Job(*new ListJobPrivate(this, session, i18n("List"))) { Q_D(ListJob); connect(&d->emitPendingsTimer, SIGNAL(timeout()), this, SLOT(emitPendings())); } ListJob::~ListJob() { } void ListJob::setIncludeUnsubscribed(bool include) { Q_D(ListJob); if (include) { d->option = ListJob::IncludeUnsubscribed; } else { d->option = ListJob::NoOption; } } bool ListJob::isIncludeUnsubscribed() const { Q_D(const ListJob); return (d->option == ListJob::IncludeUnsubscribed); } void ListJob::setOption(Option option) { Q_D(ListJob); d->option = option; } ListJob::Option ListJob::option() const { Q_D(const ListJob); return d->option; } void ListJob::setQueriedNamespaces(const QList &namespaces) { Q_D(ListJob); d->namespaces = namespaces; } QList ListJob::queriedNamespaces() const { Q_D(const ListJob); return d->namespaces; } QList ListJob::mailBoxes() const { return QList(); } QMap< MailBoxDescriptor, QList > ListJob::flags() const { return QMap< MailBoxDescriptor, QList >(); } void ListJob::doStart() { Q_D(ListJob); switch (d->option) { case IncludeUnsubscribed: d->command = "LIST"; break; case IncludeFolderRoleFlags: d->command = "XLIST"; break; case NoOption: d->command = "LSUB"; } d->emitPendingsTimer.start(100); if (d->namespaces.isEmpty()) { d->tags << d->sessionInternal()->sendCommand(d->command, "\"\" *"); } else { for (const MailBoxDescriptor &descriptor : qAsConst(d->namespaces)) { QString parameters = QStringLiteral("\"\" \"%1\""); if (descriptor.name.endsWith(descriptor.separator)) { QString name = encodeImapFolderName(descriptor.name); name.chop(1); d->tags << d->sessionInternal()->sendCommand(d->command, parameters.arg(name).toUtf8()); } d->tags << d->sessionInternal()->sendCommand(d->command, parameters.arg(descriptor.name + QLatin1Char('*')).toUtf8()); } } } void ListJob::handleResponse(const Response &response) { Q_D(ListJob); // We can predict it'll be handled by handleErrorReplies() so stop // the timer now so that result() will really be the last emitted signal. if (!response.content.isEmpty() && d->tags.size() == 1 && d->tags.contains(response.content.first().toString())) { d->emitPendingsTimer.stop(); d->emitPendings(); } if (handleErrorReplies(response) == NotHandled) { if (response.content.size() >= 5 && response.content[1].toString() == d->command) { QList flags = response.content[2].toList(); for (QList::iterator it = flags.begin(), itEnd = flags.end(); it != itEnd; ++it) { *it = it->toLower(); } QByteArray separator = response.content[3].toString(); if (separator.isEmpty()) { // Defaults to / for servers reporting an empty list // it's supposedly not a problem as servers doing that // only do it for mailboxes with no child. separator = "/"; //krazy:exclude=doublequote_chars since a QByteArray } Q_ASSERT(separator.size() == 1); QByteArray fullName; for (int i = 4; i < response.content.size(); i++) { fullName += response.content[i].toString() + ' '; } fullName.chop(1); fullName = decodeImapFolderName(fullName); MailBoxDescriptor mailBoxDescriptor; mailBoxDescriptor.separator = QLatin1Char(separator[0]); mailBoxDescriptor.name = QString::fromUtf8(fullName); convertInboxName(mailBoxDescriptor); d->pendingDescriptors << mailBoxDescriptor; d->pendingFlags << flags; } } } void ListJob::convertInboxName(KIMAP::MailBoxDescriptor &descriptor) { //Inbox must be case sensitive, according to the RFC, so make it always uppercase QStringList pathParts = descriptor.name.split(descriptor.separator); if (!pathParts.isEmpty() && pathParts[0].compare(QLatin1String("INBOX"), Qt::CaseInsensitive) == 0) { pathParts.removeAt(0); descriptor.name = QStringLiteral("INBOX"); if (!pathParts.isEmpty()) { descriptor.name += descriptor.separator + pathParts.join(descriptor.separator); } } } #include "moc_listjob.cpp" diff --git a/src/session.cpp b/src/session.cpp index ee90b70..3e70f75 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -1,535 +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(QSsl::SslProtocol) 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(QSsl::UnknownProtocol), socketTimerInterval(30000) // By default timeouts on 30s { } SessionPrivate::~SessionPrivate() { delete logger; } void SessionPrivate::addJob(Job *job) { queue.append(job); - emit q->jobQueueSizeChanged(q->jobQueueSize()); + Q_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()); + Q_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); + Q_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(); + Q_EMIT q->connectionLost(); } else { - emit q->connectionFailed(); + Q_EMIT q->connectionFailed(); } isSocketConnected = false; clearJobQueue(); } void SessionPrivate::socketActivity() { restartSocketTimer(); } void SessionPrivate::socketError(QAbstractSocket::SocketError 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(); + Q_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); + Q_EMIT q->jobQueueSizeChanged(0); } void SessionPrivate::startSsl(QSsl::SslProtocol protocol) { thread->startSsl(protocol); } QString Session::selectedMailBox() const { return QString::fromUtf8(d->currentMailBox); } void SessionPrivate::onEncryptionNegotiationResult(bool isEncrypted, QSsl::SslProtocol protocol) { if (isEncrypted) { sslVersion = protocol; } else { sslVersion = QSsl::UnknownProtocol; } - emit encryptionNegotiationResult(isEncrypted); + Q_EMIT encryptionNegotiationResult(isEncrypted); } QSsl::SslProtocol 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/sessionthread.cpp b/src/sessionthread.cpp index 71f2523..6eda850 100644 --- a/src/sessionthread.cpp +++ b/src/sessionthread.cpp @@ -1,344 +1,344 @@ /* 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); + Q_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); #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(m_socket.get(), QOverload::of(&QAbstractSocket::error), #else connect(m_socket.get(), QOverload::of(&QAbstractSocket::errorOccurred), #endif 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(); + Q_EMIT socketDisconnected(); } // Called in secondary thread void SessionThread::slotSocketError(QAbstractSocket::SocketError error) { Q_ASSERT(QThread::currentThread() == thread()); if (!m_socket) { return; } - emit socketError(error); + Q_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 (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" #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); + Q_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()); + Q_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()); + Q_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); + Q_EMIT encryptionNegotiationResult(false, QSsl::UnknownProtocol); } } #include "moc_sessionthread_p.cpp"