KIMAP2

fetchjob.cpp
1/*
2 Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
3 Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsys.com>
4
5 This library is free software; you can redistribute it and/or modify it
6 under the terms of the GNU Library General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or (at your
8 option) any later version.
9
10 This library is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
13 License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 02110-1301, USA.
19*/
20
21#include "fetchjob.h"
22
23#include "kimap_debug.h"
24
25#include "job_p.h"
26#include "message_p.h"
27#include "session_p.h"
28
29namespace KIMAP2
30{
31class FetchJobPrivate : public JobPrivate
32{
33public:
34 FetchJobPrivate(FetchJob *job, Session *session, const QString &name)
35 : JobPrivate(session, name)
36 , q(job)
37 , uidBased(false)
38 , avoidParsing(false)
39 { }
40
41 ~FetchJobPrivate()
42 { }
43
44 void parseBodyStructure(const QByteArray &structure, int &pos, KMime::Content *content);
45 void parsePart(const QByteArray &structure, int &pos, KMime::Content *content);
46 QByteArray parseString(const QByteArray &structure, int &pos);
47 QByteArray parseSentence(const QByteArray &structure, int &pos);
48 void skipLeadingSpaces(const QByteArray &structure, int &pos);
49
50 FetchJob *const q;
51
52 ImapSet set;
53 bool uidBased;
55 QString selectedMailBox;
56 bool avoidParsing;
57};
58}
59
60using namespace KIMAP2;
61
62FetchJob::FetchScope::FetchScope():
63 mode(FetchScope::Content),
64 changedSince(0),
65 gmailExtensionsEnabled(false)
66{
67
68}
69
70FetchJob::FetchJob(Session *session)
71 : Job(*new FetchJobPrivate(this, session, "Fetch"))
72{
73}
74
75FetchJob::~FetchJob()
76{
77}
78
80{
82 d->avoidParsing = avoid;
83}
84
86{
88 Q_ASSERT(!set.isEmpty());
89 d->set = set;
90}
91
93{
94 Q_D(const FetchJob);
95 return d->set;
96}
97
98void FetchJob::setUidBased(bool uidBased)
99{
100 Q_D(FetchJob);
101 d->uidBased = uidBased;
102}
103
105{
106 Q_D(const FetchJob);
107 return d->uidBased;
108}
109
111{
112 Q_D(FetchJob);
113 d->scope = scope;
114}
115
117{
118 Q_D(const FetchJob);
119 return d->scope;
120}
121
122void FetchJob::doStart()
123{
124 Q_D(FetchJob);
125
126 d->set.optimize();
127 QByteArray parameters = d->set.toImapSequenceSet() + ' ';
128 Q_ASSERT(!parameters.trimmed().isEmpty());
129
130 switch (d->scope.mode) {
132 if (d->scope.parts.isEmpty()) {
133 parameters += "(RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID";
134 } else {
135 parameters += '(';
136 foreach (const QByteArray &part, d->scope.parts) {
137 parameters += "BODY.PEEK[" + part + ".MIME] ";
138 }
139 parameters += "UID";
140 }
141 break;
143 parameters += "(FLAGS UID";
144 break;
146 parameters += "(BODYSTRUCTURE UID";
147 break;
149 if (d->scope.parts.isEmpty()) {
150 parameters += "(BODY.PEEK[] UID";
151 } else {
152 parameters += '(';
153 foreach (const QByteArray &part, d->scope.parts) {
154 parameters += "BODY.PEEK[" + part + "] ";
155 }
156 parameters += "UID";
157 }
158 break;
159 case FetchScope::Full:
160 parameters += "(RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID";
161 break;
163 if (d->scope.parts.isEmpty()) {
164 parameters += "(BODY.PEEK[] FLAGS UID";
165 } else {
166 parameters += "(BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)]";
167 foreach (const QByteArray &part, d->scope.parts) {
168 parameters += " BODY.PEEK[" + part + ".MIME] BODY.PEEK[" + part + "]"; //krazy:exclude=doublequote_chars
169 }
170 parameters += " FLAGS UID";
171 }
172 break;
174 parameters += "(RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER] FLAGS UID";
175 break;
176 }
177
178 if (d->scope.gmailExtensionsEnabled) {
179 parameters += " X-GM-LABELS X-GM-MSGID X-GM-THRID";
180 }
181 parameters += ")";
182
183 if (d->scope.changedSince > 0) {
184 parameters += " (CHANGEDSINCE " + QByteArray::number(d->scope.changedSince) + ")";
185 }
186
187 QByteArray command = "FETCH";
188 if (d->uidBased) {
189 command = "UID " + command;
190 }
191
192 d->selectedMailBox = d->m_session->selectedMailBox();
193 d->sendCommand(command, parameters);
194}
195
196void FetchJob::handleResponse(const Message &response)
197{
198 Q_D(FetchJob);
199
200 if (handleErrorReplies(response) == NotHandled) {
201 if (response.content.size() == 4 &&
202 response.content[2].toString() == "FETCH" &&
203 response.content[3].type() == Message::Part::List) {
204
205 const QList<QByteArray> content = response.content[3].toList();
206
208 result.sequenceNumber = response.content[1].toString().toLongLong();
209 bool shouldParseMessage = false;
211 it != content.constEnd(); ++it) {
212 QByteArray str = *it;
213 ++it;
214 if (it == content.constEnd()) { // Uh oh, message was truncated?
215 qCWarning(KIMAP2_LOG) << "FETCH reply got truncated, skipping.";
216 qCWarning(KIMAP2_LOG) << response.toString();
217 qCWarning(KIMAP2_LOG) << result.sequenceNumber;
218 qCWarning(KIMAP2_LOG) << content;
219 qCWarning(KIMAP2_LOG) << str;
220 break;
221 }
222
223 if (str == "UID") {
224 result.uid = it->toLongLong();
225 } else if (str == "RFC822.SIZE") {
226 result.size = it->toLongLong();
227 } else if (str == "INTERNALDATE") {
228 if (!result.message) {
229 result.message = MessagePtr(new KMime::Message);
230 }
231 result.message->date()->setDateTime(QDateTime::fromString(QLatin1String(*it), Qt::RFC2822Date));
232 } else if (str == "FLAGS") {
233 if ((*it).startsWith('(') && (*it).endsWith(')')) {
234 QByteArray str = *it;
235 str.chop(1);
236 str.remove(0, 1);
237 result.flags = str.split(' ');
238 } else {
239 result.flags << *it;
240 }
241 } else if (str == "X-GM-LABELS") {
242 result.attributes << qMakePair<QByteArray, QVariant>("X-GM-LABELS", *it);
243 } else if (str == "X-GM-THRID") {
244 result.attributes << qMakePair<QByteArray, QVariant>("X-GM-THRID", *it);
245 } else if (str == "X-GM-MSGID") {
246 result.attributes << qMakePair<QByteArray, QVariant>("X-GM-MSGID", *it);
247 } else if (str == "BODYSTRUCTURE") {
248 if (!result.message) {
249 result.message = MessagePtr(new KMime::Message);
250 }
251 int pos = 0;
252 d->parseBodyStructure(*it, pos, result.message.data());
253 result.message->assemble();
254 } else if (str.startsWith("BODY[")) { //krazy:exclude=strings
255 if (!str.endsWith(']')) { // BODY[ ... ] might have been split, skip until we find the ]
256 while (it != content.constEnd() && !(*it).endsWith(']')) {
257 ++it;
258 }
259 }
260
261 int index;
262 if ((index = str.indexOf("HEADER")) > 0 || (index = str.indexOf("MIME")) > 0) { // headers
263 if (str[index - 1] == '.') {
264 QByteArray partId = str.mid(5, index - 6);
265 if (!result.parts.contains(partId)) {
266 result.parts[partId] = ContentPtr(new KMime::Content);
267 }
268 result.parts[partId]->setHead(*it);
269 result.parts[partId]->parse();
270 } else {
271 if (!result.message) {
272 result.message = MessagePtr(new KMime::Message);
273 }
274 shouldParseMessage = true;
275 result.message->setHead(*it);
276 }
277 } else { // full payload
278 if (str == "BODY[]") {
279 if (!result.message) {
280 result.message = MessagePtr(new KMime::Message);
281 }
282 shouldParseMessage = true;
283 result.message->setContent(KMime::CRLFtoLF(*it));
284 } else {
285 QByteArray partId = str.mid(5, str.size() - 6);
286 if (!result.parts.contains(partId)) {
287 result.parts[partId] = ContentPtr(new KMime::Content);
288 }
289 result.parts[partId]->setBody(*it);
290 result.parts[partId]->parse();
291 }
292 }
293 }
294 }
295
296 if (result.message && shouldParseMessage && !d->avoidParsing) {
297 result.message->parse();
298 }
299 emit resultReceived(result);
300 }
301 }
302}
303
304void FetchJobPrivate::parseBodyStructure(const QByteArray &structure, int &pos, KMime::Content *content)
305{
306 skipLeadingSpaces(structure, pos);
307
308 if (structure[pos] != '(') {
309 return;
310 }
311
312 pos++;
313
314 if (structure[pos] != '(') { // simple part
315 pos--;
316 parsePart(structure, pos, content);
317 } else { // multi part
318 content->contentType()->setMimeType("MULTIPART/MIXED");
319 while (pos < structure.size() && structure[pos] == '(') {
320 KMime::Content *child = new KMime::Content;
321 content->addContent(child);
322 parseBodyStructure(structure, pos, child);
323 child->assemble();
324 }
325
326 QByteArray subType = parseString(structure, pos);
327 content->contentType()->setMimeType("MULTIPART/" + subType);
328
329 QByteArray parameters = parseSentence(structure, pos); // FIXME: Read the charset
330 if (parameters.contains("BOUNDARY")) {
331 content->contentType()->setBoundary(parameters.remove(0, parameters.indexOf("BOUNDARY") + 11).split('\"')[0]);
332 }
333
334 QByteArray disposition = parseSentence(structure, pos);
335 if (disposition.contains("INLINE")) {
336 content->contentDisposition()->setDisposition(KMime::Headers::CDinline);
337 } else if (disposition.contains("ATTACHMENT")) {
338 content->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
339 }
340
341 parseSentence(structure, pos); // Ditch the body language
342 }
343
344 // Consume what's left
345 while (pos < structure.size() && structure[pos] != ')') {
346 skipLeadingSpaces(structure, pos);
347 parseSentence(structure, pos);
348 skipLeadingSpaces(structure, pos);
349 }
350
351 pos++;
352}
353
354void FetchJobPrivate::parsePart(const QByteArray &structure, int &pos, KMime::Content *content)
355{
356 if (structure[pos] != '(') {
357 return;
358 }
359
360 pos++;
361
362 QByteArray mainType = parseString(structure, pos);
363 QByteArray subType = parseString(structure, pos);
364
365 content->contentType()->setMimeType(mainType + '/' + subType);
366
367 parseSentence(structure, pos); // Ditch the parameters... FIXME: Read it to get charset and name
368 parseString(structure, pos); // ... and the id
369
370 content->contentDescription()->from7BitString(parseString(structure, pos));
371
372 parseString(structure, pos); // Ditch the encoding too
373 parseString(structure, pos); // ... and the size
374 parseString(structure, pos); // ... and the line count
375
376 QByteArray disposition = parseSentence(structure, pos);
377 if (disposition.contains("INLINE")) {
378 content->contentDisposition()->setDisposition(KMime::Headers::CDinline);
379 } else if (disposition.contains("ATTACHMENT")) {
380 content->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
381 }
382 if ((content->contentDisposition()->disposition() == KMime::Headers::CDattachment ||
383 content->contentDisposition()->disposition() == KMime::Headers::CDinline) &&
384 disposition.contains("FILENAME")) {
385 QByteArray filename = disposition.remove(0, disposition.indexOf("FILENAME") + 11).split('\"')[0];
386 content->contentDisposition()->setFilename(QLatin1String(filename));
387 }
388
389 // Consume what's left
390 while (pos < structure.size() && structure[pos] != ')') {
391 skipLeadingSpaces(structure, pos);
392 parseSentence(structure, pos);
393 skipLeadingSpaces(structure, pos);
394 }
395}
396
397QByteArray FetchJobPrivate::parseSentence(const QByteArray &structure, int &pos)
398{
399 QByteArray result;
400 int stack = 0;
401
402 skipLeadingSpaces(structure, pos);
403
404 if (structure[pos] != '(') {
405 return parseString(structure, pos);
406 }
407
408 int start = pos;
409
410 do {
411 switch (structure[pos]) {
412 case '(':
413 pos++;
414 stack++;
415 break;
416 case ')':
417 pos++;
418 stack--;
419 break;
420 case '[':
421 pos++;
422 stack++;
423 break;
424 case ']':
425 pos++;
426 stack--;
427 break;
428 default:
429 skipLeadingSpaces(structure, pos);
430 parseString(structure, pos);
431 skipLeadingSpaces(structure, pos);
432 break;
433 }
434 } while (pos < structure.size() && stack != 0);
435
436 result = structure.mid(start, pos - start);
437
438 return result;
439}
440
441QByteArray FetchJobPrivate::parseString(const QByteArray &structure, int &pos)
442{
443 QByteArray result;
444
445 skipLeadingSpaces(structure, pos);
446
447 int start = pos;
448 bool foundSlash = false;
449
450 // quoted string
451 if (structure[pos] == '"') {
452 pos++;
453 Q_FOREVER {
454 if (structure[pos] == '\\')
455 {
456 pos += 2;
457 foundSlash = true;
458 continue;
459 }
460 if (structure[pos] == '"')
461 {
462 result = structure.mid(start + 1, pos - start - 1);
463 pos++;
464 break;
465 }
466 pos++;
467 }
468 } else { // unquoted string
469 Q_FOREVER {
470 if (structure[pos] == ' ' ||
471 structure[pos] == '(' ||
472 structure[pos] == ')' ||
473 structure[pos] == '[' ||
474 structure[pos] == ']' ||
475 structure[pos] == '\n' ||
476 structure[pos] == '\r' ||
477 structure[pos] == '"')
478 {
479 break;
480 }
481 if (structure[pos] == '\\')
482 {
483 foundSlash = true;
484 }
485 pos++;
486 }
487
488 result = structure.mid(start, pos - start);
489
490 // transform unquoted NIL
491 if (result == "NIL") {
492 result.clear();
493 }
494 }
495
496 // simplify slashes
497 if (foundSlash) {
498 while (result.contains("\\\"")) {
499 result.replace("\\\"", "\"");
500 }
501 while (result.contains("\\\\")) {
502 result.replace("\\\\", "\\");
503 }
504 }
505
506 return result;
507}
508
509void FetchJobPrivate::skipLeadingSpaces(const QByteArray &structure, int &pos)
510{
511 while (pos < structure.size() && structure[pos] == ' ') {
512 pos++;
513 }
514}
515
516#include "moc_fetchjob.cpp"
Used to indicate what message data should be fetched.
Definition fetchjob.h:75
@ Full
Fetch the complete message.
Definition fetchjob.h:115
@ Headers
Fetch RFC-2822 or MIME message headers.
Definition fetchjob.h:96
@ Flags
Fetch the message flags (the UID is also fetched)
Definition fetchjob.h:100
@ Content
Fetch the message content (the UID is also fetched)
Definition fetchjob.h:111
@ FullHeaders
Fetch message size (in octets), internal date of the message, flags, UID and all RFC822 headers.
Definition fetchjob.h:139
@ HeaderAndContent
Fetch the message MIME headers and the content of parts specified in the parts field.
Definition fetchjob.h:131
@ Structure
Fetch the MIME message body structure (the UID is also fetched)
Definition fetchjob.h:104
Fetch message data from the server.
Definition fetchjob.h:60
void setUidBased(bool uidBased)
Set how the sequence set should be interpreted.
Definition fetchjob.cpp:98
ImapSet sequenceSet() const
The messages that will be fetched.
Definition fetchjob.cpp:92
FetchScope scope() const
Specifies what data will be fetched.
Definition fetchjob.cpp:116
void setAvoidParsing(bool)
Avoid calling parse() on returned KMime::Messages.
Definition fetchjob.cpp:79
void setSequenceSet(const ImapSet &set)
Set which messages to fetch data for.
Definition fetchjob.cpp:85
void setScope(const FetchScope &scope)
Sets what data should be fetched.
Definition fetchjob.cpp:110
bool isUidBased() const
How to interpret the sequence set.
Definition fetchjob.cpp:104
Represents a set of natural numbers (1->∞) in a as compact as possible form.
Definition imapset.h:142
bool isEmpty() const
Returns true if this set doesn't contains any values.
Definition imapset.cpp:314
void result(KJob *job)
Headers::ContentDescription * contentDescription(bool create=true)
Headers::ContentType * contentType(bool create=true)
Headers::ContentDisposition * contentDisposition(bool create=true)
void addContent(Content *content, bool prepend=false)
void setDisposition(contentDisposition disp)
contentDisposition disposition() const
void setFilename(const QString &filename)
void setMimeType(const QByteArray &mimeType)
void setBoundary(const QByteArray &s)
virtual void from7BitString(const char *s, size_t len)
Q_SCRIPTABLE Q_NOREPLY void start()
QString name(StandardShortcut id)
void chop(qsizetype n)
void clear()
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
qlonglong toLongLong(bool *ok, int base) const const
QByteArray trimmed() const const
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
QList< T > toList() const const
const_iterator constBegin() const const
const_iterator constEnd() const const
RFC2822Date
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:21:18 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.