KIMAP2

fetchjob.cpp
1 /*
2  Copyright (c) 2009 Kevin Ottens <[email protected]>
3  Copyright (c) 2017 Christian Mollekopf <[email protected]>
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 
29 namespace KIMAP2
30 {
31 class FetchJobPrivate : public JobPrivate
32 {
33 public:
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 
60 using namespace KIMAP2;
61 
62 FetchJob::FetchScope::FetchScope():
63  mode(FetchScope::Content),
64  changedSince(0),
65  gmailExtensionsEnabled(false)
66 {
67 
68 }
69 
70 FetchJob::FetchJob(Session *session)
71  : Job(*new FetchJobPrivate(this, session, "Fetch"))
72 {
73 }
74 
75 FetchJob::~FetchJob()
76 {
77 }
78 
79 void FetchJob::setAvoidParsing(bool avoid)
80 {
81  Q_D(FetchJob);
82  d->avoidParsing = avoid;
83 }
84 
86 {
87  Q_D(FetchJob);
88  Q_ASSERT(!set.isEmpty());
89  d->set = set;
90 }
91 
93 {
94  Q_D(const FetchJob);
95  return d->set;
96 }
97 
98 void 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 
110 void FetchJob::setScope(const FetchScope &scope)
111 {
112  Q_D(FetchJob);
113  d->scope = scope;
114 }
115 
117 {
118  Q_D(const FetchJob);
119  return d->scope;
120 }
121 
122 void 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) {
131  case FetchScope::Headers:
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;
142  case FetchScope::Flags:
143  parameters += "(FLAGS UID";
144  break;
145  case FetchScope::Structure:
146  parameters += "(BODYSTRUCTURE UID";
147  break;
148  case FetchScope::Content:
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;
162  case FetchScope::HeaderAndContent:
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;
173  case FetchScope::FullHeaders:
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 
196 void 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 
207  Result result;
208  result.sequenceNumber = response.content[1].toString().toLongLong();
209  bool shouldParseMessage = false;
210  for (QList<QByteArray>::ConstIterator it = content.constBegin();
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 
304 void 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 
354 void 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 
397 QByteArray FetchJobPrivate::parseSentence(const QByteArray &structure, int &pos)
398 {
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 
441 QByteArray FetchJobPrivate::parseString(const QByteArray &structure, int &pos)
442 {
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 
509 void 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"
void addContent(Content *content, bool prepend=false)
void clear()
void setSequenceSet(const ImapSet &set)
Set which messages to fetch data for.
Definition: fetchjob.cpp:85
QList< QByteArray > split(char sep) const const
QByteArray trimmed() const const
void setMimeType(const QByteArray &mimeType)
RFC2822Date
void chop(int n)
void setScope(const FetchScope &scope)
Sets what data should be fetched.
Definition: fetchjob.cpp:110
bool isEmpty() const const
virtual void from7BitString(const char *s, size_t len)
bool startsWith(const QByteArray &ba) const const
Used to indicate what message data should be fetched.
Definition: fetchjob.h:74
Headers::ContentDisposition * contentDisposition(bool create=true)
void setDisposition(contentDisposition disp)
bool isUidBased() const
How to interpret the sequence set.
Definition: fetchjob.cpp:104
int indexOf(char ch, int from) const const
void setAvoidParsing(bool)
Avoid calling parse() on returned KMime::Messages.
Definition: fetchjob.cpp:79
Represents a set of natural numbers (1->∞) in a as compact as possible form.
Definition: imapset.h:141
QByteArray number(int n, int base)
QByteArray & replace(int pos, int len, const char *after)
Definition: acl.cpp:25
QByteArray mid(int pos, int len) const const
void setBoundary(const QByteArray &s)
Headers::ContentType * contentType(bool create=true)
QDateTime fromString(const QString &string, Qt::DateFormat format)
qlonglong toLongLong(bool *ok, int base) const const
Fetch message data from the server.
Definition: fetchjob.h:59
ImapSet sequenceSet() const
The messages that will be fetched.
Definition: fetchjob.cpp:92
virtual Q_SCRIPTABLE void start()=0
bool contains(char ch) const const
contentDisposition disposition() const
void result(KJob *job)
FetchScope scope() const
Specifies what data will be fetched.
Definition: fetchjob.cpp:116
QList::const_iterator constEnd() const const
QList::const_iterator constBegin() const const
Headers::ContentDescription * contentDescription(bool create=true)
int size() const const
QByteArray & remove(int pos, int len)
bool endsWith(const QByteArray &ba) const const
void setFilename(const QString &filename)
void setUidBased(bool uidBased)
Set how the sequence set should be interpreted.
Definition: fetchjob.cpp:98
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Dec 6 2021 23:00:24 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.