9#include "kimap_debug.h"
10#include <KLocalizedString>
14#include "response_p.h"
19class FetchJobPrivate :
public JobPrivate
22 FetchJobPrivate(FetchJob *job, Session *session,
const QString &name)
23 : JobPrivate(session,
name)
32 void parseBodyStructure(
const QByteArray &structure,
int &pos, KMime::Content *content);
33 void parsePart(
const QByteArray &structure,
int &pos, KMime::Content *content);
34 QByteArray parseString(
const QByteArray &structure,
int &pos);
35 QByteArray parseSentence(
const QByteArray &structure,
int &pos);
36 void skipLeadingSpaces(
const QByteArray &structure,
int &pos);
40 if (pendingMsgs.isEmpty()) {
44 Q_EMIT q->messagesAvailable(pendingMsgs);
46 if (!pendingParts.isEmpty()) {
47 Q_EMIT q->partsReceived(selectedMailBox, pendingUids, pendingParts);
48 Q_EMIT q->partsReceived(selectedMailBox, pendingUids, pendingAttributes, pendingParts);
50 if (!pendingSizes.isEmpty() || !pendingFlags.isEmpty() || !pendingMessages.isEmpty()) {
51 Q_EMIT q->headersReceived(selectedMailBox, pendingUids, pendingSizes, pendingFlags, pendingMessages);
52 Q_EMIT q->headersReceived(selectedMailBox, pendingUids, pendingSizes, pendingAttributes, pendingFlags, pendingMessages);
54 if (!pendingMessages.isEmpty()) {
55 Q_EMIT q->messagesReceived(selectedMailBox, pendingUids, pendingMessages);
56 Q_EMIT q->messagesReceived(selectedMailBox, pendingUids, pendingAttributes, pendingMessages);
60 pendingMessages.clear();
64 pendingAttributes.clear();
71 bool uidBased =
false;
72 FetchJob::FetchScope scope;
73 QString selectedMailBox;
74 bool gmailEnabled =
false;
76 QTimer emitPendingsTimer;
77 QMap<qint64, MessagePtr> pendingMessages;
78 QMap<qint64, MessageParts> pendingParts;
79 QMap<qint64, MessageFlags> pendingFlags;
80 QMap<qint64, MessageAttribute> pendingAttributes;
81 QMap<qint64, qint64> pendingSizes;
82 QMap<qint64, qint64> pendingUids;
83 QMap<qint64, Message> pendingMsgs;
89FetchJob::FetchScope::FetchScope()
96FetchJob::FetchJob(
Session *session)
97 : Job(*new FetchJobPrivate(this, session,
i18n(
"Fetch")))
121 d->uidBased = uidBased;
145 return d->gmailEnabled;
151 d->gmailEnabled = enabled;
157 return d->selectedMailBox;
160void FetchJob::doStart()
165 QByteArray parameters = d->set.toImapSequenceSet() +
' ';
168 switch (d->scope.mode) {
170 if (d->scope.parts.isEmpty()) {
171 parameters +=
"(RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID";
174 for (
const QByteArray &part : std::as_const(d->scope.parts)) {
175 parameters +=
"BODY.PEEK[" + part +
".MIME] ";
181 parameters +=
"(FLAGS UID";
184 parameters +=
"(BODYSTRUCTURE UID";
187 if (d->scope.parts.isEmpty()) {
188 parameters +=
"(BODY.PEEK[] UID";
191 for (
const QByteArray &part : std::as_const(d->scope.parts)) {
192 parameters +=
"BODY.PEEK[" + part +
"] ";
198 parameters +=
"(RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID";
201 if (d->scope.parts.isEmpty()) {
202 parameters +=
"(BODY.PEEK[] FLAGS UID";
204 parameters +=
"(BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)]";
205 for (
const QByteArray &part : std::as_const(d->scope.parts)) {
206 parameters +=
" BODY.PEEK[" + part +
".MIME] BODY.PEEK[" + part +
"]";
208 parameters +=
" FLAGS UID";
212 parameters +=
"(RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER] FLAGS UID";
216 if (d->gmailEnabled) {
217 parameters +=
" X-GM-LABELS X-GM-MSGID X-GM-THRID";
221 if (d->scope.changedSince > 0) {
223 if (d->scope.qresync) {
224 parameters +=
" VANISHED";
229 QByteArray command =
"FETCH";
231 command =
"UID " + command;
234 d->emitPendingsTimer.start(100);
235 d->selectedMailBox = d->m_session->selectedMailBox();
236 d->tags << d->sessionInternal()->sendCommand(command, parameters);
239void FetchJob::handleResponse(
const Response &response)
245 if (!response.content.isEmpty() && d->tags.size() == 1 && d->tags.contains(response.content.first().toString())) {
246 d->emitPendingsTimer.stop();
250 if (handleErrorReplies(response) == NotHandled) {
251 if (response.content.size() == 4 && response.content[1].toString() ==
"VANISHED") {
254 }
else if (response.content.size() == 4 && response.content[2].toString() ==
"FETCH" && response.content[3].type() == Response::Part::List) {
255 const qint64
id = response.content[1].toString().toLongLong();
256 const QList<QByteArray> content = response.content[3].toList();
259 MessagePtr message(
new KMime::Message);
260 bool shouldParseMessage =
false;
264 QByteArray str = *it;
268 qCWarning(KIMAP_LOG) <<
"FETCH reply got truncated, skipping.";
273 d->pendingUids[id] = msg.uid = it->toLongLong();
274 }
else if (str ==
"RFC822.SIZE") {
275 d->pendingSizes[id] = msg.size = it->toLongLong();
276 }
else if (str ==
"INTERNALDATE") {
278 }
else if (str ==
"FLAGS") {
279 if ((*it).startsWith(
'(') && (*it).endsWith(
')')) {
280 QByteArray str = *it;
283 const auto flags = str.
split(
' ');
284 d->pendingFlags[id] = flags;
287 d->pendingFlags[id] << *it;
290 }
else if (str ==
"X-GM-LABELS") {
291 d->pendingAttributes.insert(
id, {
"X-GM-LABELS", *it});
292 msg.attributes.
insert(
"X-GM-LABELS", *it);
293 }
else if (str ==
"X-GM-THRID") {
294 d->pendingAttributes.insert(
id, {
"X-GM-THRID", *it});
295 msg.attributes.
insert(
"X-GM-THRID", *it);
296 }
else if (str ==
"X-GM-MSGID") {
297 d->pendingAttributes.insert(
id, {
"X-GM-MSGID", *it});
298 msg.attributes.
insert(
"X-GM-MSGID", *it);
299 }
else if (str ==
"BODYSTRUCTURE") {
301 d->parseBodyStructure(*it, pos, message.data());
303 d->pendingMessages[id] = message;
304 msg.message = message;
307 while (!(*it).endsWith(
']')) {
314 if ((index = str.
indexOf(
"HEADER")) > 0 || (index = str.
indexOf(
"MIME")) > 0) {
315 if (str[index - 1] ==
'.') {
316 QByteArray partId = str.
mid(5, index - 6);
318 parts[partId] = ContentPtr(
new KMime::Content);
320 parts[partId]->setHead(*it);
321 parts[partId]->parse();
322 d->pendingParts[id] = parts;
325 message->setHead(*it);
326 shouldParseMessage =
true;
329 if (str ==
"BODY[]") {
330 message->setContent(KMime::CRLFtoLF(*it));
331 shouldParseMessage =
true;
333 d->pendingMessages[id] = message;
334 msg.message = message;
336 QByteArray partId = str.
mid(5, str.
size() - 6);
338 parts[partId] = ContentPtr(
new KMime::Content);
340 parts[partId]->setBody(*it);
341 parts[partId]->parse();
343 d->pendingParts[id] = parts;
350 if (shouldParseMessage) {
358 d->pendingMessages[id] = message;
359 msg.message = message;
362 d->pendingMsgs[id] = msg;
367void FetchJobPrivate::parseBodyStructure(
const QByteArray &structure,
int &pos, KMime::Content *content)
369 skipLeadingSpaces(structure, pos);
371 if (structure[pos] !=
'(') {
377 if (structure[pos] !=
'(') {
379 parsePart(structure, pos, content);
382 while (pos < structure.
size() && structure[pos] ==
'(') {
383 auto child =
new KMime::Content;
385 parseBodyStructure(structure, pos, child);
389 QByteArray subType = parseString(structure, pos);
392 QByteArray parameters = parseSentence(structure, pos);
393 if (parameters.
contains(
"BOUNDARY")) {
397 QByteArray disposition = parseSentence(structure, pos);
398 if (disposition.
contains(
"INLINE")) {
400 }
else if (disposition.
contains(
"ATTACHMENT")) {
404 parseSentence(structure, pos);
408 while (pos < structure.
size() && structure[pos] !=
')') {
409 skipLeadingSpaces(structure, pos);
410 parseSentence(structure, pos);
411 skipLeadingSpaces(structure, pos);
417void FetchJobPrivate::parsePart(
const QByteArray &structure,
int &pos, KMime::Content *content)
419 if (structure[pos] !=
'(') {
425 QByteArray mainType = parseString(structure, pos);
426 QByteArray subType = parseString(structure, pos);
430 parseSentence(structure, pos);
431 parseString(structure, pos);
435 parseString(structure, pos);
436 parseString(structure, pos);
437 parseString(structure, pos);
439 QByteArray disposition = parseSentence(structure, pos);
440 if (disposition.
contains(
"INLINE")) {
442 }
else if (disposition.
contains(
"ATTACHMENT")) {
447 && disposition.
contains(
"FILENAME")) {
448 QByteArray filename = disposition.
remove(0, disposition.
indexOf(
"FILENAME") + 11).
split(
'\"')[0];
453 while (pos < structure.
size() && structure[pos] !=
')') {
454 skipLeadingSpaces(structure, pos);
455 parseSentence(structure, pos);
456 skipLeadingSpaces(structure, pos);
460QByteArray FetchJobPrivate::parseSentence(
const QByteArray &structure,
int &pos)
465 skipLeadingSpaces(structure, pos);
467 if (structure[pos] !=
'(') {
468 return parseString(structure, pos);
474 switch (structure[pos]) {
492 skipLeadingSpaces(structure, pos);
493 parseString(structure, pos);
494 skipLeadingSpaces(structure, pos);
497 }
while (pos < structure.
size() && stack != 0);
504QByteArray FetchJobPrivate::parseString(
const QByteArray &structure,
int &pos)
508 skipLeadingSpaces(structure, pos);
511 bool foundSlash =
false;
514 if (structure[pos] ==
'"') {
517 if (structure[pos] ==
'\\') {
522 if (structure[pos] ==
'"') {
531 if (structure[pos] ==
' ' || structure[pos] ==
'(' || structure[pos] ==
')' || structure[pos] ==
'[' || structure[pos] ==
']'
532 || structure[pos] ==
'\n' || structure[pos] ==
'\r' || structure[pos] ==
'"') {
535 if (structure[pos] ==
'\\') {
544 if (result ==
"NIL") {
562void FetchJobPrivate::skipLeadingSpaces(
const QByteArray &structure,
int &pos)
564 while (pos < structure.
size() && structure[pos] ==
' ') {
569#include "moc_fetchjob.cpp"
Used to indicate what message data should be fetched.
@ FullHeaders
Fetch message size (in octets), internal date of the message, flags, UID and all RFC822 headers.
@ Full
Fetch the complete message.
@ Headers
Fetch RFC-2822 or MIME message headers.
@ Structure
Fetch the MIME message body structure (the UID is also fetched)
@ Content
Fetch the message content (the UID is also fetched)
@ HeaderAndContent
Fetch the message MIME headers and the content of parts specified in the parts field.
@ Flags
Fetch the message flags (the UID is also fetched)
Fetch message data from the server.
void setUidBased(bool uidBased)
Set how the sequence set should be interpreted.
ImapSet sequenceSet() const
The messages that will be fetched.
void messagesVanished(const KIMAP::ImapSet &uids)
Provides vanished messages.
FetchScope scope() const
Specifies what data will be fetched.
void setSequenceSet(const ImapSet &set)
Set which messages to fetch data for.
QString mailBox() const
Returns the name of the mailbox the fetch job is executed on.
void setScope(const FetchScope &scope)
Sets what data should be fetched.
bool isUidBased() const
How to interpret the sequence set.
bool setGmailExtensionsEnabled() const
Returns whether Gmail support is enabled.
Represents a set of natural numbers (1->∞) in a as compact as possible form.
bool isEmpty() const
Returns true if this set doesn't contains any values.
static ImapSet fromImapSequenceSet(const QByteArray &sequence)
Return the set corresponding to the given IMAP-compatible QByteArray representation.
const Headers::ContentType * contentType() const
const Headers::ContentDisposition * contentDisposition() const
void appendContent(Content *content)
const Headers::ContentDescription * contentDescription() const
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18n(const char *text, const TYPE &arg...)
QString name(const QVariant &location)
bool contains(QByteArrayView bv) const const
bool endsWith(QByteArrayView bv) const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray number(double n, char format, int precision)
QByteArray & remove(qsizetype pos, qsizetype len)
QByteArray & replace(QByteArrayView before, QByteArrayView after)
qsizetype size() const const
QList< QByteArray > split(char sep) const const
bool startsWith(QByteArrayView bv) const const
QByteArray trimmed() const const
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
const_iterator constBegin() const const
const_iterator constEnd() const const
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)