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"