/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Messaging Framework. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include "popclient.h" #include "popauthenticator.h" #include "popconfiguration.h" #include #include #include #include #include #include #include class MessageFlushedWrapper : public QMailMessageBufferFlushCallback { PopClient *context; bool isComplete; public: MessageFlushedWrapper(PopClient *_context, bool _isComplete) : context(_context) , isComplete(_isComplete) { } void messageFlushed(QMailMessage *message) override { context->messageFlushed(*message, isComplete); context->removeAllFromBuffer(message); } }; PopClient::PopClient(const QMailAccountId &id, QObject* parent) : QObject(parent), config(QMailAccountConfiguration(id)), selected(false), deleting(false), headerLimit(0), additional(0), partialContent(false), dataStream(new LongStream), transport(0), testing(false), pendingDeletes(false), credentials(QMailCredentialsFactory::getCredentialsHandlerForAccount(config)), loginFailed(false) { inactiveTimer.setSingleShot(true); connect(&inactiveTimer, SIGNAL(timeout()), this, SLOT(connectionInactive())); connect(QMailMessageBuffer::instance(), SIGNAL(flushed()), this, SLOT(messageBufferFlushed())); setupAccount(); setupFolders(); } PopClient::~PopClient() { foreach (QMailMessageBufferFlushCallback * c, callbacks) { QMailMessageBuffer::instance()->removeCallback(c); } delete dataStream; delete transport; delete credentials; } void PopClient::messageBufferFlushed() { callbacks.clear(); } void PopClient::createTransport() { if (!transport) { // Set up the transport transport = new QMailTransport("POP"); connect(transport, SIGNAL(updateStatus(QString)), this, SIGNAL(updateStatus(QString))); connect(transport, SIGNAL(connected(QMailTransport::EncryptType)), this, SLOT(connected(QMailTransport::EncryptType))); connect(transport, SIGNAL(errorOccurred(int,QString)), this, SLOT(transportError(int,QString))); connect(transport, SIGNAL(readyRead()), this, SLOT(incomingData())); #ifndef QT_NO_SSL connect(transport, SIGNAL(sslErrorOccured(QMailServiceAction::Status::ErrorCode,QString)), this, SIGNAL(connectionError(QMailServiceAction::Status::ErrorCode,QString))); #endif } } void PopClient::deleteTransport() { if (transport) { // Need to immediately disconnect these signals or slots may try to use null transport object disconnect(transport, SIGNAL(updateStatus(QString)), this, SIGNAL(updateStatus(QString))); disconnect(transport, SIGNAL(connected(QMailTransport::EncryptType)), this, SLOT(connected(QMailTransport::EncryptType))); disconnect(transport, SIGNAL(errorOccurred(int,QString)), this, SLOT(transportError(int,QString))); disconnect(transport, SIGNAL(readyRead()), this, SLOT(incomingData())); #ifndef QT_NO_SSL disconnect(transport, SIGNAL(sslErrorOccured(QMailServiceAction::Status::ErrorCode,QString)), this, SIGNAL(connectionError(QMailServiceAction::Status::ErrorCode,QString))); #endif // A Qt socket remains in an unusuable state for a short time after closing, // thus it can't be immediately reused transport->deleteLater(); transport = 0; } } void PopClient::testConnection() { testing = true; pendingDeletes = false; closeConnection(); PopConfiguration popCfg(config); if ( popCfg.mailServer().isEmpty() ) { status = Exit; operationFailed(QMailServiceAction::Status::ErrConfiguration, tr("Cannot open connection without POP server configuration")); return; } createTransport(); status = Init; capabilities.clear(); transport->setAcceptUntrustedCertificates(popCfg.acceptUntrustedCertificates()); transport->open(popCfg.mailServer(), popCfg.mailPort(), static_cast(popCfg.mailEncryption())); } void PopClient::newConnection() { testing = false; pendingDeletes = false; lastStatusTimer.start(); if (transport && transport->connected()) { if (selected) { // Re-use the existing connection inactiveTimer.stop(); } else { // We won't get an updated listing until we re-connect closeConnection(); } } // Re-load the configuration for this account config = QMailAccountConfiguration(config.id()); PopConfiguration popCfg(config); if ( popCfg.mailServer().isEmpty() ) { status = Exit; operationFailed(QMailServiceAction::Status::ErrConfiguration, tr("Cannot open connection without POP server configuration")); return; } if (credentials) { credentials->init(popCfg); } if (!selected) { serverUidNumber.clear(); serverUid.clear(); serverSize.clear(); obsoleteUids.clear(); newUids.clear(); messageCount = 0; } if (transport && transport->connected() && selected) { if (deleting) { uidlIntegrityCheck(); } // Retrieve the specified messages status = RequestMessage; nextAction(); } else { createTransport(); status = Init; capabilities.clear(); transport->setAcceptUntrustedCertificates(popCfg.acceptUntrustedCertificates()); transport->open(popCfg.mailServer(), popCfg.mailPort(), static_cast(popCfg.mailEncryption())); } } void PopClient::setupAccount() const { QMailAccount account(config.id()); if (account.status() & QMailAccount::CanCreateFolders) { account.setStatus(QMailAccount::CanCreateFolders, false); if (!QMailStore::instance()->updateAccount(&account)) { qWarning() << "Unable to update account" << account.id() << "to CanCreateFolders" << false; } else { qMailLog(POP) << "CanCreateFolders for " << account.id() << "changed to" << false; } } } void PopClient::setupFolders() const { // Update non-local folders which have 'RenamePermitted=true'/'DeletionPermitted=true'/'ChildCreationPermitted=true'/'MessagesPermitted=false' QMailFolderKey popKey = QMailFolderKey::parentAccountId(config.id()); popKey &= QMailFolderKey::id(QMailFolder::LocalStorageFolderId, QMailDataComparator::NotEqual); popKey &= QMailFolderKey::ancestorFolderIds(QMailFolderId(QMailFolder::LocalStorageFolderId), QMailDataComparator::Excludes); popKey &= QMailFolderKey::status(QMailFolder::DeletionPermitted, QMailDataComparator::Includes) | QMailFolderKey::status(QMailFolder::RenamePermitted, QMailDataComparator::Includes) | QMailFolderKey::status(QMailFolder::ChildCreationPermitted, QMailDataComparator::Includes) | QMailFolderKey::status(QMailFolder::MessagesPermitted, QMailDataComparator::Excludes); QMailFolderIdList folderIds = QMailStore::instance()->queryFolders(popKey); foreach (const QMailFolderId &folderId, folderIds) { QMailFolder folder = QMailFolder(folderId); folder.setStatus(QMailFolder::DeletionPermitted, false); folder.setStatus(QMailFolder::RenamePermitted, false); folder.setStatus(QMailFolder::ChildCreationPermitted, false); folder.setStatus(QMailFolder::MessagesPermitted, true); if (!QMailStore::instance()->updateFolder(&folder)) { qWarning() << "Unable to update flags for POP folder" << folder.id() << folder.path(); } else { qMailLog(POP) << "Flags for POP folder" << folder.id() << folder.path() << "updated"; } } } QMailAccountId PopClient::accountId() const { return config.id(); } bool PopClient::synchronizationEnabled(const QMailFolderId &id) const { return id.isValid() // not accountChecking || (QMailFolder(folderId).status() & QMailFolder::SynchronizationEnabled); } void PopClient::setOperation(QMailRetrievalAction::RetrievalSpecification spec) { selected = false; deleting = false; additional = 0; switch (spec) { case QMailRetrievalAction::Auto: { // Re-load the configuration for this account QMailAccountConfiguration accountCfg(config.id()); PopConfiguration popCfg(accountCfg); if (popCfg.isAutoDownload()) { // Just download everything headerLimit = UINT_MAX; } else { headerLimit = popCfg.maxMailSize() * 1024; } } break; case QMailRetrievalAction::Content: headerLimit = UINT_MAX; break; case QMailRetrievalAction::MetaData: case QMailRetrievalAction::Flags: default: headerLimit = 0; break; } findInbox(); } // Returns true if inbox already exists, otherwise returns false bool PopClient::findInbox() { bool result = false; QMailAccount account(config.id()); // get/create child folder QMailFolderIdList folderList = QMailStore::instance()->queryFolders(QMailFolderKey::parentAccountId(account.id())); if (folderList.count() > 1) { qWarning() << "Pop account has more than one child folder, account" << account.id(); folderId = folderList.first(); result = true; } else if (folderList.count() == 1) { folderId = folderList.first(); result = true; } else { QMailFolder childFolder("Inbox", QMailFolderId(), account.id()); childFolder.setDisplayName(tr("Inbox")); childFolder.setStatus(QMailFolder::SynchronizationEnabled, true); childFolder.setStatus(QMailFolder::Incoming, true); childFolder.setStatus(QMailFolder::MessagesPermitted, true); if (!QMailStore::instance()->addFolder(&childFolder)) qWarning() << "Unable to add child folder to pop account"; folderId = childFolder.id(); account.setStandardFolder(QMailFolder::InboxFolder, folderId); if (!QMailStore::instance()->updateAccount(&account)) { qWarning() << "Unable to update account" << account.id(); } } partialContent = QMailFolder(folderId).status() & QMailFolder::PartialContent; return result; } void PopClient::setAdditional(uint _additional) { additional = _additional; } void PopClient::setDeleteOperation() { deleting = true; } void PopClient::setSelectedMails(const SelectionMap& data) { // We shouldn't have anything left in our retrieval list... if (!retrievalSize.isEmpty()) { foreach (const QString& uid, retrievalSize.keys()) qMailLog(POP) << "Message" << uid << "still in retrieve map..."; retrievalSize.clear(); } selected = true; selectionMap = data; selectionItr = selectionMap.begin(); completionList.clear(); messageCount = 0; if (deleting == false) { totalRetrievalSize = 0; foreach (const QMailMessageId& id, selectionMap.values()) { QMailMessageMetaData message(id); uint size = message.indicativeSize(); uint bytes = message.size(); retrievalSize.insert(message.serverUid(), qMakePair(qMakePair(size, bytes), 0u)); totalRetrievalSize += size; } // Report the total size we will retrieve progressRetrievalSize = 0; emit progressChanged(progressRetrievalSize, totalRetrievalSize); } } void PopClient::connected(QMailTransport::EncryptType encryptType) { PopConfiguration popCfg(config); if (popCfg.mailEncryption() == encryptType) { qMailLog(POP) << "Connected"; emit updateStatus(tr("Connected")); } #ifndef QT_NO_SSL if ((popCfg.mailEncryption() != QMailTransport::Encrypt_SSL) && (status == TLS)) { // We have entered TLS mode - restart the connection capabilities.clear(); status = Init; nextAction(); } #endif } void PopClient::transportError(int status, QString msg) { operationFailed(status, msg); } void PopClient::closeConnection() { inactiveTimer.stop(); if (transport) { if (transport->connected()) { if ( status == Exit ) { // We have already sent our quit command transport->close(); } else { // Send a quit command sendCommand("QUIT"); status = Exit; transport->close(); } } else if (transport->inUse()) { transport->close(); } } deleteTransport(); } void PopClient::sendCommand(const char *data, int len) { if (len == -1) len = ::strlen(data); QDataStream &out(transport->stream()); out.writeRawData(data, len); out.writeRawData("\r\n", 2); if (len){ QString logData(data); QRegExp passExp("^PASS\\s"); if (passExp.indexIn(logData) != -1) { logData = logData.left(passExp.matchedLength()) + "