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

KIMAP Library

  • 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::FetchJob( Session *session )
90  : Job( *new FetchJobPrivate( this, session, i18n( "Fetch" ) ) )
91 {
92  Q_D( FetchJob );
93  d->scope.mode = FetchScope::Content;
94  connect( &d->emitPendingsTimer, SIGNAL(timeout()),
95  this, SLOT(emitPendings()) );
96 }
97 
98 FetchJob::~FetchJob()
99 {
100 }
101 
102 void FetchJob::setSequenceSet( const ImapSet &set )
103 {
104  Q_D( FetchJob );
105  Q_ASSERT( !set.toImapSequenceSet().trimmed().isEmpty() );
106  d->set = set;
107 }
108 
109 ImapSet FetchJob::sequenceSet() const
110 {
111  Q_D( const FetchJob );
112  return d->set;
113 }
114 
115 void FetchJob::setUidBased(bool uidBased)
116 {
117  Q_D( FetchJob );
118  d->uidBased = uidBased;
119 }
120 
121 bool FetchJob::isUidBased() const
122 {
123  Q_D( const FetchJob );
124  return d->uidBased;
125 }
126 
127 void FetchJob::setScope( const FetchScope &scope )
128 {
129  Q_D( FetchJob );
130  d->scope = scope;
131 }
132 
133 FetchJob::FetchScope FetchJob::scope() const
134 {
135  Q_D( const FetchJob );
136  return d->scope;
137 }
138 
139 QMap<qint64, MessagePtr> FetchJob::messages() const
140 {
141  return QMap<qint64, MessagePtr>();
142 }
143 
144 QMap<qint64, MessageParts> FetchJob::parts() const
145 {
146  return QMap<qint64, MessageParts>();
147 }
148 
149 QMap<qint64, MessageFlags> FetchJob::flags() const
150 {
151  return QMap<qint64, MessageFlags>();
152 }
153 
154 QMap<qint64, qint64> FetchJob::sizes() const
155 {
156  return QMap<qint64, qint64>();
157 }
158 
159 QMap<qint64, qint64> FetchJob::uids() const
160 {
161  return QMap<qint64, qint64>();
162 }
163 
164 void FetchJob::doStart()
165 {
166  Q_D( FetchJob );
167 
168  QByteArray parameters = d->set.toImapSequenceSet()+' ';
169  Q_ASSERT( !parameters.trimmed().isEmpty() );
170 
171  switch ( d->scope.mode ) {
172  case FetchScope::Headers:
173  if ( d->scope.parts.isEmpty() ) {
174  parameters += "(RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)";
175  } else {
176  parameters += '(';
177  foreach ( const QByteArray &part, d->scope.parts ) {
178  parameters += "BODY.PEEK[" + part + ".MIME] ";
179  }
180  parameters += "UID)";
181  }
182  break;
183  case FetchScope::Flags:
184  parameters += "(FLAGS UID)";
185  break;
186  case FetchScope::Structure:
187  parameters += "(BODYSTRUCTURE UID)";
188  break;
189  case FetchScope::Content:
190  if ( d->scope.parts.isEmpty() ) {
191  parameters += "(BODY.PEEK[] UID)";
192  } else {
193  parameters += '(';
194  foreach ( const QByteArray &part, d->scope.parts ) {
195  parameters += "BODY.PEEK[" + part + "] ";
196  }
197  parameters += "UID)";
198  }
199  break;
200  case FetchScope::Full:
201  parameters += "(RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)";
202  break;
203  case FetchScope::HeaderAndContent:
204  if ( d->scope.parts.isEmpty() ) {
205  parameters += "(BODY.PEEK[] FLAGS UID)";
206  } else {
207  parameters += "(BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)]";
208  foreach ( const QByteArray &part, d->scope.parts ) {
209  parameters += " BODY.PEEK[" + part + ".MIME] BODY.PEEK[" + part + "]"; //krazy:exclude=doublequote_chars
210  }
211  parameters += " FLAGS UID)";
212  }
213  break;
214  }
215 
216  QByteArray command = "FETCH";
217  if ( d->uidBased ) {
218  command = "UID " + command;
219  }
220 
221  d->emitPendingsTimer.start( 100 );
222  d->selectedMailBox = d->m_session->selectedMailBox();
223  d->tags << d->sessionInternal()->sendCommand( command, parameters );
224 }
225 
226 void FetchJob::handleResponse( const Message &response )
227 {
228  Q_D( FetchJob );
229 
230  // We can predict it'll be handled by handleErrorReplies() so stop
231  // the timer now so that result() will really be the last emitted signal.
232  if ( !response.content.isEmpty() &&
233  d->tags.size() == 1 &&
234  d->tags.contains( response.content.first().toString() ) ) {
235  d->emitPendingsTimer.stop();
236  d->emitPendings();
237  }
238 
239  if ( handleErrorReplies( response ) == NotHandled ) {
240  if ( response.content.size() == 4 &&
241  response.content[2].toString() == "FETCH" &&
242  response.content[3].type() == Message::Part::List ) {
243 
244  qint64 id = response.content[1].toString().toLongLong();
245  QList<QByteArray> content = response.content[3].toList();
246 
247  MessagePtr message( new KMime::Message );
248  bool shouldParseMessage = false;
249  MessageParts parts;
250 
251  for ( QList<QByteArray>::ConstIterator it = content.constBegin();
252  it != content.constEnd(); ++it ) {
253  QByteArray str = *it;
254  ++it;
255 
256  if ( it == content.constEnd() ) { // Uh oh, message was truncated?
257  kWarning() << "FETCH reply got truncated, skipping.";
258  break;
259  }
260 
261  if ( str == "UID" ) {
262  d->pendingUids[id] = it->toLongLong();
263  } else if ( str == "RFC822.SIZE" ) {
264  d->pendingSizes[id] = it->toLongLong();
265  } else if ( str == "INTERNALDATE" ) {
266  message->date()->setDateTime( KDateTime::fromString( *it, KDateTime::RFCDate ) );
267  } else if ( str == "FLAGS" ) {
268  if ( ( *it ).startsWith( '(' ) && ( *it ).endsWith( ')' ) ) {
269  QByteArray str = *it;
270  str.chop( 1 );
271  str.remove( 0, 1 );
272  d->pendingFlags[id] = str.split( ' ' );
273  } else {
274  d->pendingFlags[id] << *it;
275  }
276  } else if ( str == "BODYSTRUCTURE" ) {
277  int pos = 0;
278  d->parseBodyStructure( *it, pos, message.get() );
279  message->assemble();
280  d->pendingMessages[id] = message;
281  } else if ( str.startsWith( "BODY[" ) ) { //krazy:exclude=strings
282  if ( !str.endsWith( ']' ) ) { // BODY[ ... ] might have been split, skip until we find the ]
283  while ( !( *it ).endsWith( ']' ) ) {
284  ++it;
285  }
286  ++it;
287  }
288 
289  int index;
290  if ( ( index = str.indexOf( "HEADER" ) ) > 0 || ( index = str.indexOf( "MIME" ) ) > 0 ) { // headers
291  if ( str[index-1] == '.' ) {
292  QByteArray partId = str.mid( 5, index - 6 );
293  if ( !parts.contains( partId ) ) {
294  parts[partId] = ContentPtr( new KMime::Content );
295  }
296  parts[partId]->setHead( *it );
297  parts[partId]->parse();
298  d->pendingParts[id] = parts;
299  } else {
300  message->setHead( *it );
301  shouldParseMessage = true;
302  }
303  } else { // full payload
304  if ( str == "BODY[]" ) {
305  message->setContent( KMime::CRLFtoLF( *it ) );
306  shouldParseMessage = true;
307 
308  d->pendingMessages[id] = message;
309  } else {
310  QByteArray partId = str.mid( 5, str.size() - 6 );
311  if ( !parts.contains( partId ) ) {
312  parts[partId] = ContentPtr( new KMime::Content );
313  }
314  parts[partId]->setBody( *it );
315  parts[partId]->parse();
316 
317  d->pendingParts[id] = parts;
318  }
319  }
320  }
321  }
322 
323  if ( shouldParseMessage ) {
324  message->parse();
325  }
326 
327  // For the headers mode the message is built in several
328  // steps, hence why we wait it to be done until putting it
329  // in the pending queue.
330  if ( d->scope.mode == FetchScope::Headers || d->scope.mode == FetchScope::HeaderAndContent ) {
331  d->pendingMessages[id] = message;
332  }
333  }
334  }
335 }
336 
337 void FetchJobPrivate::parseBodyStructure(const QByteArray &structure, int &pos, KMime::Content *content)
338 {
339  skipLeadingSpaces( structure, pos );
340 
341  if ( structure[pos] != '(' ) {
342  return;
343  }
344 
345  pos++;
346 
347  if ( structure[pos] != '(' ) { // simple part
348  pos--;
349  parsePart( structure, pos, content );
350  } else { // multi part
351  content->contentType()->setMimeType( "MULTIPART/MIXED" );
352  while ( pos < structure.size() && structure[pos] == '(' ) {
353  KMime::Content *child = new KMime::Content;
354  content->addContent( child );
355  parseBodyStructure( structure, pos, child );
356  child->assemble();
357  }
358 
359  QByteArray subType = parseString( structure, pos );
360  content->contentType()->setMimeType( "MULTIPART/" + subType );
361 
362  QByteArray parameters = parseSentence( structure, pos ); // FIXME: Read the charset
363  if ( parameters.contains( "BOUNDARY" ) ) {
364  content->contentType()->setBoundary( parameters.remove( 0, parameters.indexOf( "BOUNDARY" ) + 11 ).split( '\"' )[0] );
365  }
366 
367  QByteArray disposition = parseSentence( structure, pos );
368  if ( disposition.contains( "INLINE" ) ) {
369  content->contentDisposition()->setDisposition( KMime::Headers::CDinline );
370  } else if ( disposition.contains( "ATTACHMENT" ) ) {
371  content->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
372  }
373 
374  parseSentence( structure, pos ); // Ditch the body language
375  }
376 
377  // Consume what's left
378  while ( pos < structure.size() && structure[pos] != ')' ) {
379  skipLeadingSpaces( structure, pos );
380  parseSentence( structure, pos );
381  skipLeadingSpaces( structure, pos );
382  }
383 
384  pos++;
385 }
386 
387 void FetchJobPrivate::parsePart( const QByteArray &structure, int &pos, KMime::Content *content )
388 {
389  if ( structure[pos] != '(' ) {
390  return;
391  }
392 
393  pos++;
394 
395  QByteArray mainType = parseString( structure, pos );
396  QByteArray subType = parseString( structure, pos );
397 
398  content->contentType()->setMimeType( mainType + '/' + subType );
399 
400  parseSentence( structure, pos ); // Ditch the parameters... FIXME: Read it to get charset and name
401  parseString( structure, pos ); // ... and the id
402 
403  content->contentDescription()->from7BitString( parseString( structure, pos ) );
404 
405  parseString( structure, pos ); // Ditch the encoding too
406  parseString( structure, pos ); // ... and the size
407  parseString( structure, pos ); // ... and the line count
408 
409  QByteArray disposition = parseSentence( structure, pos );
410  if ( disposition.contains( "INLINE" ) ) {
411  content->contentDisposition()->setDisposition( KMime::Headers::CDinline );
412  } else if ( disposition.contains( "ATTACHMENT" ) ) {
413  content->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
414  }
415  if ( ( content->contentDisposition()->disposition() == KMime::Headers::CDattachment ||
416  content->contentDisposition()->disposition() == KMime::Headers::CDinline ) &&
417  disposition.contains( "FILENAME" ) ) {
418  QByteArray filename = disposition.remove( 0, disposition.indexOf( "FILENAME" ) + 11 ).split( '\"' )[0];
419  content->contentDisposition()->setFilename( filename );
420  }
421 
422  // Consume what's left
423  while ( pos < structure.size() && structure[pos] != ')' ) {
424  skipLeadingSpaces( structure, pos );
425  parseSentence( structure, pos );
426  skipLeadingSpaces( structure, pos );
427  }
428 }
429 
430 QByteArray FetchJobPrivate::parseSentence( const QByteArray &structure, int &pos )
431 {
432  QByteArray result;
433  int stack = 0;
434 
435  skipLeadingSpaces( structure, pos );
436 
437  if ( structure[pos] != '(' ) {
438  return parseString( structure, pos );
439  }
440 
441  int start = pos;
442 
443  do {
444  switch ( structure[pos] ) {
445  case '(':
446  pos++;
447  stack++;
448  break;
449  case ')':
450  pos++;
451  stack--;
452  break;
453  case '[':
454  pos++;
455  stack++;
456  break;
457  case ']':
458  pos++;
459  stack--;
460  break;
461  default:
462  skipLeadingSpaces( structure, pos );
463  parseString( structure, pos );
464  skipLeadingSpaces( structure, pos );
465  break;
466  }
467  } while ( pos < structure.size() && stack != 0 );
468 
469  result = structure.mid( start, pos - start );
470 
471  return result;
472 }
473 
474 QByteArray FetchJobPrivate::parseString( const QByteArray &structure, int &pos )
475 {
476  QByteArray result;
477 
478  skipLeadingSpaces( structure, pos );
479 
480  int start = pos;
481  bool foundSlash = false;
482 
483  // quoted string
484  if ( structure[pos] == '"' ) {
485  pos++;
486  Q_FOREVER {
487  if ( structure[pos] == '\\' ) {
488  pos += 2;
489  foundSlash = true;
490  continue;
491  }
492  if ( structure[pos] == '"' ) {
493  result = structure.mid( start + 1, pos - start - 1 );
494  pos++;
495  break;
496  }
497  pos++;
498  }
499  } else { // unquoted string
500  Q_FOREVER {
501  if ( structure[pos] == ' ' ||
502  structure[pos] == '(' ||
503  structure[pos] == ')' ||
504  structure[pos] == '[' ||
505  structure[pos] == ']' ||
506  structure[pos] == '\n' ||
507  structure[pos] == '\r' ||
508  structure[pos] == '"' ) {
509  break;
510  }
511  if ( structure[pos] == '\\' ) {
512  foundSlash = true;
513  }
514  pos++;
515  }
516 
517  result = structure.mid( start, pos - start );
518 
519  // transform unquoted NIL
520  if ( result == "NIL" ) {
521  result.clear();
522  }
523  }
524 
525  // simplify slashes
526  if ( foundSlash ) {
527  while ( result.contains( "\\\"" ) ) {
528  result.replace( "\\\"", "\"" );
529  }
530  while ( result.contains( "\\\\" ) ) {
531  result.replace( "\\\\", "\\" );
532  }
533  }
534 
535  return result;
536 }
537 
538 void FetchJobPrivate::skipLeadingSpaces( const QByteArray &structure, int &pos )
539 {
540  while ( pos < structure.size() && structure[pos] == ' ' ) {
541  pos++;
542  }
543 }
544 
545 #include "moc_fetchjob.cpp"
KIMAP::FetchJob::isUidBased
bool isUidBased() const
How to interpret the sequence set.
Definition: fetchjob.cpp:121
KIMAP::FetchJob::setSequenceSet
void setSequenceSet(const ImapSet &set)
Set which messages to fetch data for.
Definition: fetchjob.cpp:102
KIMAP::FetchJob::FetchScope
Used to indicate what message data should be fetched.
Definition: fetchjob.h:71
KIMAP::FetchJob::FetchScope::Content
Fetch the message content (the UID is also fetched)
Definition: fetchjob.h:105
KIMAP::FetchJob::flags
QMap< qint64, MessageFlags > flags() const
Definition: fetchjob.cpp:149
KIMAP::FetchJob::scope
FetchScope scope() const
Specifies what data will be fetched.
Definition: fetchjob.cpp:133
KIMAP::FetchJob::FetchScope::Headers
Fetch RFC-2822 or MIME message headers.
Definition: fetchjob.h:90
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:115
KIMAP::FetchJob::FetchScope::HeaderAndContent
Fetch the message MIME headers and the content of parts specified in the parts field.
Definition: fetchjob.h:127
KIMAP::FetchJob::FetchScope::Full
Fetch the complete message.
Definition: fetchjob.h:109
KIMAP::FetchJob::parts
QMap< qint64, MessageParts > parts() const
Definition: fetchjob.cpp:144
KIMAP::FetchJob::sizes
QMap< qint64, qint64 > sizes() const
Definition: fetchjob.cpp:154
KIMAP::ImapSet
Represents a set of natural numbers (1-&gt; ) in a as compact as possible form.
Definition: imapset.h:140
KIMAP::FetchJob::FetchScope::Structure
Fetch the MIME message body structure (the UID is also fetched)
Definition: fetchjob.h:98
KIMAP::FetchJob::messages
QMap< qint64, MessagePtr > messages() const
Definition: fetchjob.cpp:139
KIMAP::FetchJob::uids
QMap< qint64, qint64 > uids() const
Definition: fetchjob.cpp:159
KIMAP::ImapSet::toImapSequenceSet
QByteArray toImapSequenceSet() const
Returns a IMAP-compatible QByteArray representation of this set.
Definition: imapset.cpp:272
KIMAP::FetchJob::FetchScope::Flags
Fetch the message flags (the UID is also fetched)
Definition: fetchjob.h:94
KIMAP::FetchJob::sequenceSet
ImapSet sequenceSet() const
The messages that will be fetched.
Definition: fetchjob.cpp:109
KIMAP::FetchJob::setScope
void setScope(const FetchScope &scope)
Sets what data should be fetched.
Definition: fetchjob.cpp:127
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Tue Nov 26 2013 09:02:14 by doxygen 1.8.5 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-4.11.3 API Reference

Skip menu "kdepimlibs-4.11.3 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
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