File indexing completed on 2024-05-12 05:17:16

0001 /*
0002     SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "selectjob.h"
0008 
0009 #include "kimap_debug.h"
0010 
0011 #include "imapset.h"
0012 #include "job_p.h"
0013 #include "response_p.h"
0014 #include "rfccodecs.h"
0015 #include "session_p.h"
0016 
0017 #include <KLocalizedString>
0018 
0019 #include <QMap>
0020 #include <QTimer>
0021 
0022 namespace KIMAP
0023 {
0024 class SelectJobPrivate : public JobPrivate
0025 {
0026 public:
0027     SelectJobPrivate(SelectJob *q, Session *session, const QString &name)
0028         : JobPrivate(session, name)
0029         , q(q)
0030     {
0031         QObject::connect(&emitPendingsTimer, &QTimer::timeout, [this]() {
0032             emitPendings();
0033         });
0034     }
0035 
0036     void emitPendings()
0037     {
0038         if (pendingMessages.empty()) {
0039             return;
0040         }
0041 
0042         Q_EMIT q->modified(pendingMessages);
0043         pendingMessages.clear();
0044     }
0045 
0046     QString mailBox;
0047     bool readOnly = false;
0048 
0049     QMap<qint64, Message> pendingMessages;
0050     QTimer emitPendingsTimer;
0051 
0052     QList<QByteArray> flags;
0053     QList<QByteArray> permanentFlags;
0054     int messageCount = -1;
0055     int recentCount = -1;
0056     int firstUnseenIndex = -1;
0057     qint64 uidValidity = -1;
0058     qint64 nextUid = -1;
0059     quint64 highestmodseq = 0;
0060     qint64 lastUidvalidity = -1;
0061     quint64 lastModseq = 0;
0062     ImapSet knownUids;
0063 
0064     bool condstoreEnabled = false;
0065 
0066     SelectJob *const q;
0067 };
0068 }
0069 
0070 using namespace KIMAP;
0071 
0072 SelectJob::SelectJob(Session *session)
0073     : Job(*new SelectJobPrivate(this, session, i18nc("name of the select job", "Select")))
0074 {
0075 }
0076 
0077 SelectJob::~SelectJob()
0078 {
0079 }
0080 
0081 void SelectJob::setMailBox(const QString &mailBox)
0082 {
0083     Q_D(SelectJob);
0084     d->mailBox = mailBox;
0085 }
0086 
0087 QString SelectJob::mailBox() const
0088 {
0089     Q_D(const SelectJob);
0090     return d->mailBox;
0091 }
0092 
0093 void SelectJob::setOpenReadOnly(bool readOnly)
0094 {
0095     Q_D(SelectJob);
0096     d->readOnly = readOnly;
0097 }
0098 
0099 bool SelectJob::isOpenReadOnly() const
0100 {
0101     Q_D(const SelectJob);
0102     return d->readOnly;
0103 }
0104 
0105 QList<QByteArray> SelectJob::flags() const
0106 {
0107     Q_D(const SelectJob);
0108     return d->flags;
0109 }
0110 
0111 QList<QByteArray> SelectJob::permanentFlags() const
0112 {
0113     Q_D(const SelectJob);
0114     return d->permanentFlags;
0115 }
0116 
0117 int SelectJob::messageCount() const
0118 {
0119     Q_D(const SelectJob);
0120     return d->messageCount;
0121 }
0122 
0123 int SelectJob::recentCount() const
0124 {
0125     Q_D(const SelectJob);
0126     return d->recentCount;
0127 }
0128 
0129 int SelectJob::firstUnseenIndex() const
0130 {
0131     Q_D(const SelectJob);
0132     return d->firstUnseenIndex;
0133 }
0134 
0135 qint64 SelectJob::uidValidity() const
0136 {
0137     Q_D(const SelectJob);
0138     return d->uidValidity;
0139 }
0140 
0141 qint64 SelectJob::nextUid() const
0142 {
0143     Q_D(const SelectJob);
0144     return d->nextUid;
0145 }
0146 
0147 quint64 SelectJob::highestModSequence() const
0148 {
0149     Q_D(const SelectJob);
0150     return d->highestmodseq;
0151 }
0152 
0153 void SelectJob::setCondstoreEnabled(bool enable)
0154 {
0155     Q_D(SelectJob);
0156     d->condstoreEnabled = enable;
0157 }
0158 
0159 bool SelectJob::condstoreEnabled() const
0160 {
0161     Q_D(const SelectJob);
0162     return d->condstoreEnabled;
0163 }
0164 
0165 void SelectJob::setQResync(qint64 lastUidvalidity, quint64 lastModseq, const ImapSet &knownUids)
0166 {
0167     Q_D(SelectJob);
0168     d->lastUidvalidity = lastUidvalidity;
0169     d->lastModseq = lastModseq;
0170     d->knownUids = knownUids;
0171     setCondstoreEnabled(true);
0172 }
0173 
0174 void SelectJob::doStart()
0175 {
0176     Q_D(SelectJob);
0177 
0178     QByteArray command = "SELECT";
0179     if (d->readOnly) {
0180         command = "EXAMINE";
0181     }
0182 
0183     QByteArray params = '\"' + KIMAP::encodeImapFolderName(d->mailBox.toUtf8()) + '\"';
0184 
0185     if (d->condstoreEnabled) {
0186         // Check whether we only do CONDSTORE, or QRESYNC
0187         if (d->lastUidvalidity == -1 && d->lastModseq == 0) {
0188             params += " (CONDSTORE)";
0189         } else {
0190             params += " (QRESYNC (" + QByteArray::number(d->lastUidvalidity) + " " + QByteArray::number(d->lastModseq);
0191             if (!d->knownUids.isEmpty()) {
0192                 params += " " + d->knownUids.toImapSequenceSet();
0193             }
0194             params += "))";
0195         }
0196     }
0197 
0198     d->emitPendingsTimer.start(100);
0199     d->tags << d->sessionInternal()->sendCommand(command, params);
0200 }
0201 
0202 void SelectJob::handleResponse(const Response &response)
0203 {
0204     Q_D(SelectJob);
0205 
0206     // Check for [READ-ONLY] response in final tagged OK
0207     // This must be checked before handleErrorReplies(), because that calls emitResult()
0208     // right away
0209     if (!response.content.isEmpty() && d->tags.contains(response.content.first().toString())) {
0210         if (response.responseCode.size() >= 1 && response.responseCode[0].toString() == "READ-ONLY") {
0211             d->readOnly = true;
0212         }
0213     }
0214 
0215     // We can predict it'll be handled by handleErrorReplies() so stop
0216     // the timer now so that result() will really be the last emitted signal.
0217     if (!response.content.isEmpty() && d->tags.size() == 1 && d->tags.contains(response.content.first().toString())) {
0218         d->emitPendingsTimer.stop();
0219         d->emitPendings();
0220     }
0221 
0222     if (handleErrorReplies(response) == NotHandled) {
0223         if (response.content.size() >= 2) {
0224             QByteArray code = response.content[1].toString();
0225 
0226             if (code == "OK") {
0227                 if (response.responseCode.size() < 2) {
0228                     return;
0229                 }
0230 
0231                 code = response.responseCode[0].toString();
0232                 if (code == "PERMANENTFLAGS") {
0233                     d->permanentFlags = response.responseCode[1].toList();
0234                 } else if (code == "HIGHESTMODSEQ") {
0235                     bool isInt;
0236                     quint64 value = response.responseCode[1].toString().toULongLong(&isInt);
0237                     if (!isInt) {
0238                         return;
0239                     }
0240                     d->highestmodseq = value;
0241                 } else {
0242                     bool isInt;
0243                     qint64 value = response.responseCode[1].toString().toLongLong(&isInt);
0244                     if (!isInt) {
0245                         return;
0246                     }
0247                     if (code == "UIDVALIDITY") {
0248                         d->uidValidity = value;
0249                     } else if (code == "UNSEEN") {
0250                         d->firstUnseenIndex = value;
0251                     } else if (code == "UIDNEXT") {
0252                         d->nextUid = value;
0253                     }
0254                 }
0255             } else if (code == "FLAGS") {
0256                 d->flags = response.content[2].toList();
0257             } else if (code == "VANISHED" && response.content.size() == 4) { // VANISHED response in SELECT is RFC5162 (QRESYNC) extension
0258                 const auto vanishedSet = ImapSet::fromImapSequenceSet(response.content[3].toString());
0259                 Q_EMIT vanished(vanishedSet);
0260             } else {
0261                 bool isInt = false;
0262                 int value = response.content[1].toString().toInt(&isInt);
0263                 if (!isInt || response.content.size() < 3) {
0264                     return;
0265                 }
0266 
0267                 code = response.content[2].toString();
0268                 if (code == "FETCH") { // FETCH response in SELECT is RFC5162 (QRESYNC) extension
0269                     Message msg{};
0270                     const auto content = response.content[3].toList();
0271                     for (auto it = content.cbegin(), end = content.cend(); it != end; ++it) {
0272                         const auto name = *it;
0273                         ++it;
0274 
0275                         if (it == content.constEnd()) { // Uh oh, message was truncated?
0276                             qCWarning(KIMAP_LOG) << "SELECT reply got truncated, skipping.";
0277                             break;
0278                         }
0279 
0280                         if (name == "UID") {
0281                             msg.uid = it->toLongLong();
0282                         } else if (name == "FLAGS") {
0283                             if ((*it).startsWith('(') && (*it).endsWith(')')) {
0284                                 QByteArray str = *it;
0285                                 str.chop(1);
0286                                 str.remove(0, 1);
0287                                 const auto flags = str.split(' ');
0288                                 msg.flags = flags;
0289                             } else {
0290                                 msg.flags << *it;
0291                             }
0292                         } else if (name == "MODSEQ") {
0293                             QByteArray modseq = *it;
0294                             if (modseq.startsWith('(') && modseq.endsWith(')')) {
0295                                 modseq.chop(1);
0296                                 modseq.remove(0, 1);
0297                             }
0298 
0299                             msg.attributes.insert(name, modseq.toULongLong());
0300                         }
0301                     }
0302 
0303                     d->pendingMessages.insert(value, msg);
0304                 } else if (code == "EXISTS") {
0305                     d->messageCount = value;
0306                 } else if (code == "RECENT") {
0307                     d->recentCount = value;
0308                 }
0309             }
0310         } else {
0311             qCDebug(KIMAP_LOG) << response.toString();
0312         }
0313     } else {
0314         Q_ASSERT(error() || d->m_session->selectedMailBox() == d->mailBox);
0315     }
0316 }
0317 
0318 #include "moc_selectjob.cpp"