• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdepimlibs API Reference
  • KDE Home
  • Contact Us
 

KIMAP Library

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

KDE's Doxygen guidelines are available online.

KIMAP Library

Skip menu "KIMAP Library"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Related Pages

kdepimlibs API Reference

Skip menu "kdepimlibs API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kldap
  • kmbox
  • kmime
  • kpimidentities
  • kpimtextedit
  • kresources
  • ktnef
  • kxmlrpcclient
  • microblog

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal