22 #include <QtCore/QTimer>
24 #include <KDE/KLocale>
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::FetchJob( Session *session )
90 : Job( *new FetchJobPrivate( this, session, i18n(
"Fetch" ) ) )
93 d->scope.mode = FetchScope::Content;
94 connect( &d->emitPendingsTimer, SIGNAL(timeout()),
95 this, SLOT(emitPendings()) );
118 d->uidBased = uidBased;
141 return QMap<qint64, MessagePtr>();
146 return QMap<qint64, MessageParts>();
151 return QMap<qint64, MessageFlags>();
156 return QMap<qint64, qint64>();
161 return QMap<qint64, qint64>();
164 void FetchJob::doStart()
168 QByteArray parameters = d->set.toImapSequenceSet()+
' ';
169 Q_ASSERT( !parameters.trimmed().isEmpty() );
171 switch ( d->scope.mode ) {
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)";
177 foreach (
const QByteArray &part, d->scope.parts ) {
178 parameters +=
"BODY.PEEK[" + part +
".MIME] ";
180 parameters +=
"UID)";
184 parameters +=
"(FLAGS UID)";
187 parameters +=
"(BODYSTRUCTURE UID)";
190 if ( d->scope.parts.isEmpty() ) {
191 parameters +=
"(BODY.PEEK[] UID)";
194 foreach (
const QByteArray &part, d->scope.parts ) {
195 parameters +=
"BODY.PEEK[" + part +
"] ";
197 parameters +=
"UID)";
201 parameters +=
"(RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)";
204 if ( d->scope.parts.isEmpty() ) {
205 parameters +=
"(BODY.PEEK[] FLAGS UID)";
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 +
"]";
211 parameters +=
" FLAGS UID)";
216 QByteArray command =
"FETCH";
218 command =
"UID " + command;
221 d->emitPendingsTimer.start( 100 );
222 d->selectedMailBox = d->m_session->selectedMailBox();
223 d->tags << d->sessionInternal()->sendCommand( command, parameters );
226 void FetchJob::handleResponse(
const Message &response )
232 if ( !response.content.isEmpty() &&
233 d->tags.size() == 1 &&
234 d->tags.contains( response.content.first().toString() ) ) {
235 d->emitPendingsTimer.stop();
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 ) {
244 qint64
id = response.content[1].toString().toLongLong();
245 QList<QByteArray> content = response.content[3].toList();
247 MessagePtr message(
new KMime::Message );
248 bool shouldParseMessage =
false;
251 for ( QList<QByteArray>::ConstIterator it = content.constBegin();
252 it != content.constEnd(); ++it ) {
253 QByteArray str = *it;
256 if ( it == content.constEnd() ) {
257 kWarning() <<
"FETCH reply got truncated, skipping.";
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;
272 d->pendingFlags[id] = str.split(
' ' );
274 d->pendingFlags[id] << *it;
276 }
else if ( str ==
"BODYSTRUCTURE" ) {
278 d->parseBodyStructure( *it, pos, message.get() );
280 d->pendingMessages[id] = message;
281 }
else if ( str.startsWith(
"BODY[" ) ) {
282 if ( !str.endsWith(
']' ) ) {
283 while ( !( *it ).endsWith(
']' ) ) {
290 if ( ( index = str.indexOf(
"HEADER" ) ) > 0 || ( index = str.indexOf(
"MIME" ) ) > 0 ) {
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 );
296 parts[partId]->setHead( *it );
297 parts[partId]->parse();
298 d->pendingParts[id] =
parts;
300 message->setHead( *it );
301 shouldParseMessage =
true;
304 if ( str ==
"BODY[]" ) {
305 message->setContent( KMime::CRLFtoLF( *it ) );
306 shouldParseMessage =
true;
308 d->pendingMessages[id] = message;
310 QByteArray partId = str.mid( 5, str.size() - 6 );
311 if ( !parts.contains( partId ) ) {
312 parts[partId] = ContentPtr(
new KMime::Content );
314 parts[partId]->setBody( *it );
315 parts[partId]->parse();
317 d->pendingParts[id] =
parts;
323 if ( shouldParseMessage ) {
331 d->pendingMessages[id] = message;
337 void FetchJobPrivate::parseBodyStructure(
const QByteArray &structure,
int &pos, KMime::Content *content)
339 skipLeadingSpaces( structure, pos );
341 if ( structure[pos] !=
'(' ) {
348 if ( structure[pos] !=
'(' ) {
350 parsePart( structure, pos, content );
352 content->contentType()->setMimeType(
"MULTIPART/MIXED" );
353 while ( pos < structure.size() && structure[pos] ==
'(' ) {
354 KMime::Content *child =
new KMime::Content;
355 content->addContent( child );
356 parseBodyStructure( structure, pos, child );
360 QByteArray subType = parseString( structure, pos );
361 content->contentType()->setMimeType(
"MULTIPART/" + subType );
363 QByteArray parameters = parseSentence( structure, pos );
364 if ( parameters.contains(
"BOUNDARY" ) ) {
365 content->contentType()->setBoundary( parameters.remove( 0, parameters.indexOf(
"BOUNDARY" ) + 11 ).split(
'\"' )[0] );
368 QByteArray disposition = parseSentence( structure, pos );
369 if ( disposition.contains(
"INLINE" ) ) {
370 content->contentDisposition()->setDisposition( KMime::Headers::CDinline );
371 }
else if ( disposition.contains(
"ATTACHMENT" ) ) {
372 content->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
375 parseSentence( structure, pos );
379 while ( pos < structure.size() && structure[pos] !=
')' ) {
380 skipLeadingSpaces( structure, pos );
381 parseSentence( structure, pos );
382 skipLeadingSpaces( structure, pos );
388 void FetchJobPrivate::parsePart(
const QByteArray &structure,
int &pos, KMime::Content *content )
390 if ( structure[pos] !=
'(' ) {
396 QByteArray mainType = parseString( structure, pos );
397 QByteArray subType = parseString( structure, pos );
399 content->contentType()->setMimeType( mainType +
'/' + subType );
401 parseSentence( structure, pos );
402 parseString( structure, pos );
404 content->contentDescription()->from7BitString( parseString( structure, pos ) );
406 parseString( structure, pos );
407 parseString( structure, pos );
408 parseString( structure, pos );
410 QByteArray disposition = parseSentence( structure, pos );
411 if ( disposition.contains(
"INLINE" ) ) {
412 content->contentDisposition()->setDisposition( KMime::Headers::CDinline );
413 }
else if ( disposition.contains(
"ATTACHMENT" ) ) {
414 content->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
416 if ( ( content->contentDisposition()->disposition() == KMime::Headers::CDattachment ||
417 content->contentDisposition()->disposition() == KMime::Headers::CDinline ) &&
418 disposition.contains(
"FILENAME" ) ) {
419 QByteArray filename = disposition.remove( 0, disposition.indexOf(
"FILENAME" ) + 11 ).split(
'\"' )[0];
420 content->contentDisposition()->setFilename( filename );
424 while ( pos < structure.size() && structure[pos] !=
')' ) {
425 skipLeadingSpaces( structure, pos );
426 parseSentence( structure, pos );
427 skipLeadingSpaces( structure, pos );
431 QByteArray FetchJobPrivate::parseSentence(
const QByteArray &structure,
int &pos )
436 skipLeadingSpaces( structure, pos );
438 if ( structure[pos] !=
'(' ) {
439 return parseString( structure, pos );
445 switch ( structure[pos] ) {
463 skipLeadingSpaces( structure, pos );
464 parseString( structure, pos );
465 skipLeadingSpaces( structure, pos );
468 }
while ( pos < structure.size() && stack != 0 );
470 result = structure.mid( start, pos - start );
475 QByteArray FetchJobPrivate::parseString(
const QByteArray &structure,
int &pos )
479 skipLeadingSpaces( structure, pos );
482 bool foundSlash =
false;
485 if ( structure[pos] ==
'"' ) {
488 if ( structure[pos] ==
'\\' ) {
493 if ( structure[pos] ==
'"' ) {
494 result = structure.mid( start + 1, pos - start - 1 );
502 if ( structure[pos] ==
' ' ||
503 structure[pos] ==
'(' ||
504 structure[pos] ==
')' ||
505 structure[pos] ==
'[' ||
506 structure[pos] ==
']' ||
507 structure[pos] ==
'\n' ||
508 structure[pos] ==
'\r' ||
509 structure[pos] ==
'"' ) {
512 if ( structure[pos] ==
'\\' ) {
518 result = structure.mid( start, pos - start );
521 if ( result ==
"NIL" ) {
528 while ( result.contains(
"\\\"" ) ) {
529 result.replace(
"\\\"",
"\"" );
531 while ( result.contains(
"\\\\" ) ) {
532 result.replace(
"\\\\",
"\\" );
539 void FetchJobPrivate::skipLeadingSpaces(
const QByteArray &structure,
int &pos )
541 while ( pos < structure.size() && structure[pos] ==
' ' ) {
546 #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.
Used to indicate what message data should be fetched.
Fetch the message content (the UID is also fetched)
QMap< qint64, MessageFlags > flags() const
FetchScope scope() const
Specifies what data will be 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 complete message.
QMap< qint64, MessageParts > parts() const
QMap< qint64, qint64 > sizes() const
Represents a set of natural numbers (1-> ) in a as compact as possible form.
Fetch the MIME message body structure (the UID is also fetched)
QMap< qint64, MessagePtr > messages() const
QMap< qint64, qint64 > uids() const
QByteArray toImapSequenceSet() const
Returns a IMAP-compatible QByteArray representation of this set.
Fetch the message flags (the UID is also fetched)
ImapSet sequenceSet() const
The messages that will be fetched.
void setScope(const FetchScope &scope)
Sets what data should be fetched.