File indexing completed on 2024-11-24 04:44:32

0001 /*
0002    SPDX-FileCopyrightText: 2009 Thomas McGuire <mcguire@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "jobs.h"
0008 #include "settings.h"
0009 
0010 #include <MailTransport/Transport>
0011 
0012 #include "pop3resource_debug.h"
0013 #include <KIO/SslUi>
0014 #include <KLocalizedString>
0015 
0016 #include "pop3protocol.h"
0017 
0018 #include <QThread>
0019 
0020 POPSession::POPSession(Settings &settings, const QString &password)
0021     : mProtocol(std::make_unique<POP3Protocol>(settings, password))
0022     , mThread(new QThread)
0023 {
0024     qRegisterMetaType<Result>();
0025     connect(mProtocol.get(), &POP3Protocol::sslError, this, &POPSession::handleSslError, Qt::BlockingQueuedConnection);
0026     mProtocol->moveToThread(mThread.get());
0027     mThread->start();
0028 }
0029 
0030 POPSession::~POPSession()
0031 {
0032     closeSession();
0033     mThread->quit();
0034     mThread->wait();
0035 }
0036 
0037 void POPSession::setCurrentJob(BaseJob *job)
0038 {
0039     mCurrentJob = job;
0040 }
0041 
0042 void POPSession::handleSslError(const KSslErrorUiData &errorData)
0043 {
0044     const bool cont = KIO::SslUi::askIgnoreSslErrors(errorData, KIO::SslUi::RecallAndStoreRules);
0045     mProtocol->setContinueAfterSslError(cont);
0046 }
0047 
0048 POP3Protocol *POPSession::getProtocol() const
0049 {
0050     return mProtocol.get();
0051 }
0052 
0053 void POPSession::abortCurrentJob()
0054 {
0055     // This is never the case anymore, since all jobs are sync
0056     if (mCurrentJob) {
0057         mCurrentJob->kill(KJob::Quietly);
0058         mCurrentJob = nullptr;
0059     }
0060 }
0061 
0062 void POPSession::closeSession()
0063 {
0064     QMetaObject::invokeMethod(mProtocol.get(), [this]() {
0065         Q_ASSERT(QThread::currentThread() != qApp->thread());
0066         mProtocol->closeConnection();
0067     });
0068 }
0069 
0070 static QByteArray cleanupListResponse(const QByteArray &response)
0071 {
0072     QByteArray ret = response.simplified(); // Workaround for Maillennium POP3/UNIBOX
0073 
0074     // Get rid of the null terminating character, if it exists
0075     const int retSize = ret.size();
0076     if (retSize > 0 && ret.at(retSize - 1) == 0) {
0077         ret.chop(1);
0078     }
0079     return ret;
0080 }
0081 
0082 static QString intListToString(const QList<int> &intList)
0083 {
0084     QString idList;
0085     for (const int id : intList) {
0086         idList += QString::number(id) + QLatin1Char(',');
0087     }
0088     idList.chop(1);
0089     return idList;
0090 }
0091 
0092 BaseJob::BaseJob(POPSession *POPSession)
0093     : mPOPSession(POPSession)
0094 {
0095     mPOPSession->setCurrentJob(this);
0096     connect(this, &BaseJob::jobDone, this, &BaseJob::handleJobDone);
0097 }
0098 
0099 BaseJob::~BaseJob()
0100 {
0101     // Don't do that here, the job might be destroyed after another one was started
0102     // and therefore overwrite the current job
0103     // mPOPSession->setCurrentJob(nullptr);
0104 }
0105 
0106 void BaseJob::startJob(const QString &path)
0107 {
0108     Q_ASSERT(QThread::currentThread() == qApp->thread());
0109     POP3Protocol *protocol = mPOPSession->getProtocol();
0110     connect(protocol, &POP3Protocol::data, this, &BaseJob::slotData);
0111     // Important: copy the arguments into the lambda, it'll crash if you capture by reference
0112     QMetaObject::invokeMethod(protocol, [this, path, protocol]() {
0113         Q_ASSERT(QThread::currentThread() != qApp->thread());
0114         const Result result = protocol->get(path);
0115         disconnect(protocol, &POP3Protocol::data, this, &BaseJob::slotData);
0116         Q_EMIT jobDone(result);
0117     });
0118 }
0119 
0120 void BaseJob::handleJobDone(const Result &result)
0121 {
0122     Q_ASSERT(QThread::currentThread() == qApp->thread());
0123     mPOPSession->setCurrentJob(nullptr);
0124     if (!result.success) {
0125         setError(result.error);
0126         setErrorText(result.errorString);
0127     }
0128     emitResult();
0129 }
0130 
0131 void BaseJob::slotData(const QByteArray &data)
0132 {
0133     qCWarning(POP3RESOURCE_LOG) << "Got unexpected job data:" << data.data();
0134 }
0135 
0136 LoginJob::LoginJob(POPSession *popSession)
0137     : BaseJob(popSession)
0138 {
0139 }
0140 
0141 void LoginJob::start()
0142 {
0143     if (!POP3Protocol::initSASL()) {
0144         setError(KJob::UserDefinedError);
0145         setErrorText(i18n("Unable to initialize SASL, aborting mail check."));
0146         emitResult();
0147     }
0148 
0149     Q_ASSERT(QThread::currentThread() == qApp->thread());
0150     POP3Protocol *protocol = mPOPSession->getProtocol();
0151     QMetaObject::invokeMethod(protocol, [this, protocol]() {
0152         Q_ASSERT(QThread::currentThread() != qApp->thread());
0153         const Result result = protocol->openConnection();
0154         Q_EMIT jobDone(result);
0155     });
0156 }
0157 
0158 ListJob::ListJob(POPSession *popSession)
0159     : BaseJob(popSession)
0160 {
0161 }
0162 
0163 void ListJob::start()
0164 {
0165     startJob(QStringLiteral("/index"));
0166 }
0167 
0168 void ListJob::slotData(const QByteArray &data)
0169 {
0170     Q_ASSERT(!data.isEmpty());
0171 
0172     const QByteArray cleanData = cleanupListResponse(data);
0173     const int space = cleanData.indexOf(' ');
0174 
0175     if (space > 0) {
0176         QByteArray lengthString = cleanData.mid(space + 1);
0177         const int spaceInLengthPos = lengthString.indexOf(' ');
0178         if (spaceInLengthPos != -1) {
0179             lengthString.truncate(spaceInLengthPos);
0180         }
0181         const int length = lengthString.toInt();
0182 
0183         QByteArray idString = cleanData.left(space);
0184 
0185         bool idIsNumber;
0186         const int id = QString::fromLatin1(idString).toInt(&idIsNumber);
0187         if (idIsNumber) {
0188             mIdList.insert(id, length);
0189         } else {
0190             qCWarning(POP3RESOURCE_LOG) << "Got non-integer ID as part of the LIST response, ignoring" << idString.data();
0191         }
0192     } else {
0193         qCWarning(POP3RESOURCE_LOG) << "Got invalid LIST response:" << data.data();
0194     }
0195 }
0196 
0197 QMap<int, int> ListJob::idList() const
0198 {
0199     return mIdList;
0200 }
0201 
0202 UIDListJob::UIDListJob(POPSession *popSession)
0203     : BaseJob(popSession)
0204 {
0205 }
0206 
0207 void UIDListJob::start()
0208 {
0209     startJob(QStringLiteral("/uidl"));
0210 }
0211 
0212 void UIDListJob::slotData(const QByteArray &data)
0213 {
0214     Q_ASSERT(!data.isEmpty());
0215 
0216     QByteArray cleanData = cleanupListResponse(data);
0217     const int space = cleanData.indexOf(' ');
0218 
0219     if (space <= 0) {
0220         qCWarning(POP3RESOURCE_LOG) << "Invalid response to the UIDL command:" << data.data();
0221         qCWarning(POP3RESOURCE_LOG) << "Ignoring this entry.";
0222     } else {
0223         const QByteArray idString = cleanData.left(space);
0224         const QByteArray uidString = cleanData.mid(space + 1);
0225         bool idIsNumber;
0226         const int id = QString::fromLatin1(idString).toInt(&idIsNumber);
0227         if (idIsNumber) {
0228             const QString uidQString = QString::fromLatin1(uidString);
0229             if (!uidQString.isEmpty()) {
0230                 mUidList.insert(id, uidQString);
0231                 mIdList.insert(uidQString, id);
0232             } else {
0233                 qCWarning(POP3RESOURCE_LOG) << "Got invalid/empty UID from the UIDL command:" << uidString.data();
0234                 qCWarning(POP3RESOURCE_LOG) << "The whole response was:" << data.data();
0235             }
0236         } else {
0237             qCWarning(POP3RESOURCE_LOG) << "Got invalid ID from the UIDL command:" << idString.data();
0238             qCWarning(POP3RESOURCE_LOG) << "The whole response was:" << data.data();
0239         }
0240     }
0241 }
0242 
0243 QMap<int, QString> UIDListJob::uidList() const
0244 {
0245     return mUidList;
0246 }
0247 
0248 QMap<QString, int> UIDListJob::idList() const
0249 {
0250     return mIdList;
0251 }
0252 
0253 DeleteJob::DeleteJob(POPSession *popSession)
0254     : BaseJob(popSession)
0255 {
0256 }
0257 
0258 void DeleteJob::setDeleteIds(const QList<int> &ids)
0259 {
0260     mIdsToDelete = ids;
0261 }
0262 
0263 void DeleteJob::start()
0264 {
0265     qCDebug(POP3RESOURCE_LOG) << "================= DeleteJob::start. =============================";
0266     startJob(QLatin1StringView("/remove/") + intListToString(mIdsToDelete));
0267 }
0268 
0269 QList<int> DeleteJob::deletedIDs() const
0270 {
0271     // FIXME : The protocol class doesn't tell us which of the IDs were actually deleted, we
0272     //         just assume all of them here
0273     return mIdsToDelete;
0274 }
0275 
0276 QuitJob::QuitJob(POPSession *popSession)
0277     : BaseJob(popSession)
0278 {
0279 }
0280 
0281 void QuitJob::start()
0282 {
0283     startJob(QStringLiteral("/quit"));
0284 }
0285 
0286 FetchJob::FetchJob(POPSession *session)
0287     : BaseJob(session)
0288 {
0289 }
0290 
0291 void FetchJob::setFetchIds(const QList<int> &ids, const QList<int> &sizes)
0292 {
0293     mIdsPendingDownload = ids;
0294     for (int size : std::as_const(sizes)) {
0295         mTotalBytesToDownload += size;
0296     }
0297 }
0298 
0299 void FetchJob::start()
0300 {
0301     setTotalAmount(KJob::Bytes, mTotalBytesToDownload);
0302     connect(mPOPSession->getProtocol(), &POP3Protocol::messageComplete, this, &FetchJob::slotMessageComplete);
0303     startJob(QLatin1StringView("/download/") + intListToString(mIdsPendingDownload));
0304 }
0305 
0306 void FetchJob::slotData(const QByteArray &data)
0307 {
0308     mCurrentMessage += data;
0309     mBytesDownloaded += data.size();
0310     mDataCounter++;
0311     if (mDataCounter % 5 == 0) {
0312         setProcessedAmount(KJob::Bytes, mBytesDownloaded);
0313     }
0314 }
0315 
0316 void FetchJob::handleJobDone(const Result &result)
0317 {
0318     disconnect(mPOPSession->getProtocol(), &POP3Protocol::messageComplete, this, &FetchJob::slotMessageComplete);
0319     BaseJob::handleJobDone(result);
0320 }
0321 
0322 void FetchJob::slotMessageComplete()
0323 {
0324     KMime::Message::Ptr msg(new KMime::Message);
0325     msg->setContent(KMime::CRLFtoLF(mCurrentMessage));
0326     msg->parse();
0327 
0328     mCurrentMessage.clear();
0329     const int idOfCurrentMessage = mIdsPendingDownload.takeFirst();
0330     Q_EMIT messageFinished(idOfCurrentMessage, msg);
0331 }
0332 
0333 #include "moc_jobs.cpp"