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"