22 #include <QtCore/QTimer>
24 #include <KDE/KLocalizedString>
27 #include "message_p.h"
28 #include "session_p.h"
32 class FetchJobPrivate :
public JobPrivate
35 FetchJobPrivate(
FetchJob *job, Session *session,
const QString& name ) : JobPrivate( session, name ), q( job ), uidBased( false ) { }
36 ~FetchJobPrivate() { }
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 );
46 if ( pendingUids.isEmpty() ) {
50 if ( !pendingParts.isEmpty() ) {
51 emit q->partsReceived( selectedMailBox,
52 pendingUids, pendingParts );
54 if ( !pendingSizes.isEmpty() || !pendingFlags.isEmpty() ) {
55 emit q->headersReceived( selectedMailBox,
56 pendingUids, pendingSizes,
57 pendingFlags, pendingMessages );
59 if ( !pendingMessages.isEmpty() ) {
60 emit q->messagesReceived( selectedMailBox,
61 pendingUids, pendingMessages );
65 pendingMessages.clear();
76 QString selectedMailBox;
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;
87 using namespace KIMAP;
89 FetchJob::FetchScope::FetchScope():
90 mode( FetchScope::Content ),
96 FetchJob::FetchJob( Session *session )
97 : Job( *new FetchJobPrivate( this, session, i18n(
"Fetch" ) ) )
100 connect( &d->emitPendingsTimer, SIGNAL(timeout()),
101 this, SLOT(emitPendings()) );
104 FetchJob::~FetchJob()
124 d->uidBased = uidBased;
147 return QMap<qint64, MessagePtr>();
152 return QMap<qint64, MessageParts>();
157 return QMap<qint64, MessageFlags>();
162 return QMap<qint64, qint64>();
167 return QMap<qint64, qint64>();
170 void FetchJob::doStart()
174 QByteArray parameters = d->set.toImapSequenceSet()+
' ';
175 Q_ASSERT( !parameters.trimmed().isEmpty() );
177 switch ( d->scope.mode ) {
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)";
183 foreach (
const QByteArray &part, d->scope.parts ) {
184 parameters +=
"BODY.PEEK[" + part +
".MIME] ";
186 parameters +=
"UID)";
190 parameters +=
"(FLAGS UID)";
193 parameters +=
"(BODYSTRUCTURE UID)";
196 if ( d->scope.parts.isEmpty() ) {
197 parameters +=
"(BODY.PEEK[] UID)";
200 foreach (
const QByteArray &part, d->scope.parts ) {
201 parameters +=
"BODY.PEEK[" + part +
"] ";
203 parameters +=
"UID)";
207 parameters +=
"(RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)";
210 if ( d->scope.parts.isEmpty() ) {
211 parameters +=
"(BODY.PEEK[] FLAGS UID)";
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 +
"]";
217 parameters +=
" FLAGS UID)";
221 parameters +=
"(RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER] FLAGS UID)";
225 if ( d->scope.changedSince > 0 ) {
226 parameters +=
" (CHANGEDSINCE " + QByteArray::number( d->scope.changedSince ) +
")";
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 );
239 void FetchJob::handleResponse(
const Message &response )
245 if ( !response.content.isEmpty() &&
246 d->tags.size() == 1 &&
247 d->tags.contains( response.content.first().toString() ) ) {
248 d->emitPendingsTimer.stop();
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 ) {
257 qint64
id = response.content[1].toString().toLongLong();
258 QList<QByteArray> content = response.content[3].toList();
260 MessagePtr message(
new KMime::Message );
261 bool shouldParseMessage =
false;
264 for ( QList<QByteArray>::ConstIterator it = content.constBegin();
265 it != content.constEnd(); ++it ) {
266 QByteArray str = *it;
269 if ( it == content.constEnd() ) {
270 kWarning() <<
"FETCH reply got truncated, skipping.";
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;
285 d->pendingFlags[id] = str.split(
' ' );
287 d->pendingFlags[id] << *it;
289 }
else if ( str ==
"BODYSTRUCTURE" ) {
291 d->parseBodyStructure( *it, pos, message.get() );
293 d->pendingMessages[id] = message;
294 }
else if ( str.startsWith(
"BODY[" ) ) {
295 if ( !str.endsWith(
']' ) ) {
296 while ( !( *it ).endsWith(
']' ) ) {
303 if ( ( index = str.indexOf(
"HEADER" ) ) > 0 || ( index = str.indexOf(
"MIME" ) ) > 0 ) {
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 );
309 parts[partId]->setHead( *it );
310 parts[partId]->parse();
311 d->pendingParts[id] =
parts;
313 message->setHead( *it );
314 shouldParseMessage =
true;
317 if ( str ==
"BODY[]" ) {
318 message->setContent( KMime::CRLFtoLF( *it ) );
319 shouldParseMessage =
true;
321 d->pendingMessages[id] = message;
323 QByteArray partId = str.mid( 5, str.size() - 6 );
324 if ( !parts.contains( partId ) ) {
325 parts[partId] = ContentPtr(
new KMime::Content );
327 parts[partId]->setBody( *it );
328 parts[partId]->parse();
330 d->pendingParts[id] =
parts;
336 if ( shouldParseMessage ) {
346 d->pendingMessages[id] = message;
352 void FetchJobPrivate::parseBodyStructure(
const QByteArray &structure,
int &pos, KMime::Content *content)
354 skipLeadingSpaces( structure, pos );
356 if ( structure[pos] !=
'(' ) {
362 if ( structure[pos] !=
'(' ) {
364 parsePart( structure, pos, content );
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 );
374 QByteArray subType = parseString( structure, pos );
375 content->contentType()->setMimeType(
"MULTIPART/" + subType );
377 QByteArray parameters = parseSentence( structure, pos );
378 if ( parameters.contains(
"BOUNDARY" ) ) {
379 content->contentType()->setBoundary( parameters.remove( 0, parameters.indexOf(
"BOUNDARY" ) + 11 ).split(
'\"' )[0] );
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 );
389 parseSentence( structure, pos );
393 while ( pos < structure.size() && structure[pos] !=
')' ) {
394 skipLeadingSpaces( structure, pos );
395 parseSentence( structure, pos );
396 skipLeadingSpaces( structure, pos );
402 void FetchJobPrivate::parsePart(
const QByteArray &structure,
int &pos, KMime::Content *content )
404 if ( structure[pos] !=
'(' ) {
410 QByteArray mainType = parseString( structure, pos );
411 QByteArray subType = parseString( structure, pos );
413 content->contentType()->setMimeType( mainType +
'/' + subType );
415 parseSentence( structure, pos );
416 parseString( structure, pos );
418 content->contentDescription()->from7BitString( parseString( structure, pos ) );
420 parseString( structure, pos );
421 parseString( structure, pos );
422 parseString( structure, pos );
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 );
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) );
438 while ( pos < structure.size() && structure[pos] !=
')' ) {
439 skipLeadingSpaces( structure, pos );
440 parseSentence( structure, pos );
441 skipLeadingSpaces( structure, pos );
445 QByteArray FetchJobPrivate::parseSentence(
const QByteArray &structure,
int &pos )
450 skipLeadingSpaces( structure, pos );
452 if ( structure[pos] !=
'(' ) {
453 return parseString( structure, pos );
459 switch ( structure[pos] ) {
477 skipLeadingSpaces( structure, pos );
478 parseString( structure, pos );
479 skipLeadingSpaces( structure, pos );
482 }
while ( pos < structure.size() && stack != 0 );
484 result = structure.mid( start, pos - start );
489 QByteArray FetchJobPrivate::parseString(
const QByteArray &structure,
int &pos )
493 skipLeadingSpaces( structure, pos );
496 bool foundSlash =
false;
499 if ( structure[pos] ==
'"' ) {
502 if ( structure[pos] ==
'\\' ) {
507 if ( structure[pos] ==
'"' ) {
508 result = structure.mid( start + 1, pos - start - 1 );
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] ==
'"' ) {
526 if ( structure[pos] ==
'\\' ) {
532 result = structure.mid( start, pos - start );
535 if ( result ==
"NIL" ) {
542 while ( result.contains(
"\\\"" ) ) {
543 result.replace(
"\\\"",
"\"" );
545 while ( result.contains(
"\\\\" ) ) {
546 result.replace(
"\\\\",
"\\" );
553 void FetchJobPrivate::skipLeadingSpaces(
const QByteArray &structure,
int &pos )
555 while ( pos < structure.size() && structure[pos] ==
' ' ) {
560 #include "moc_fetchjob.cpp"
bool isUidBased() const
How to interpret the sequence set.
void setSequenceSet(const ImapSet &set)
Set which messages to fetch data for.
Fetch the message content (the UID is also fetched)
Fetch the complete message.
KIMAP_DEPRECATED QMap< qint64, MessageFlags > flags() const
FetchScope scope() const
Specifies what data will be fetched.
Fetch message size (in octets), internal date of the message, flags, UID and all RFC822 headers...
Fetch the MIME message body structure (the UID is also fetched)
Fetch RFC-2822 or MIME message headers.
Fetch message data from the server.
void setUidBased(bool uidBased)
Set how the sequence set should be interpreted.
Fetch the message MIME headers and the content of parts specified in the parts field.
Fetch the message flags (the UID is also fetched)
KIMAP_DEPRECATED QMap< qint64, MessageParts > parts() const
KIMAP_DEPRECATED QMap< qint64, qint64 > sizes() const
Represents a set of natural numbers (1-> ) in a as compact as possible form.
KIMAP_DEPRECATED QMap< qint64, MessagePtr > messages() const
KIMAP_DEPRECATED QMap< qint64, qint64 > uids() const
QByteArray toImapSequenceSet() const
Returns a IMAP-compatible QByteArray representation of this set.
ImapSet sequenceSet() const
The messages that will be fetched.
void setScope(const FetchScope &scope)
Sets what data should be fetched.
Used to indicate what message data should be fetched.