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::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] !=
'(' ) {
347 if ( structure[pos] !=
'(' ) {
349 parsePart( structure, pos, content );
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 );
359 QByteArray subType = parseString( structure, pos );
360 content->contentType()->setMimeType(
"MULTIPART/" + subType );
362 QByteArray parameters = parseSentence( structure, pos );
363 if ( parameters.contains(
"BOUNDARY" ) ) {
364 content->contentType()->setBoundary( parameters.remove( 0, parameters.indexOf(
"BOUNDARY" ) + 11 ).split(
'\"' )[0] );
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 );
374 parseSentence( structure, pos );
378 while ( pos < structure.size() && structure[pos] !=
')' ) {
379 skipLeadingSpaces( structure, pos );
380 parseSentence( structure, pos );
381 skipLeadingSpaces( structure, pos );
387 void FetchJobPrivate::parsePart(
const QByteArray &structure,
int &pos, KMime::Content *content )
389 if ( structure[pos] !=
'(' ) {
395 QByteArray mainType = parseString( structure, pos );
396 QByteArray subType = parseString( structure, pos );
398 content->contentType()->setMimeType( mainType +
'/' + subType );
400 parseSentence( structure, pos );
401 parseString( structure, pos );
403 content->contentDescription()->from7BitString( parseString( structure, pos ) );
405 parseString( structure, pos );
406 parseString( structure, pos );
407 parseString( structure, pos );
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 );
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 );
423 while ( pos < structure.size() && structure[pos] !=
')' ) {
424 skipLeadingSpaces( structure, pos );
425 parseSentence( structure, pos );
426 skipLeadingSpaces( structure, pos );
430 QByteArray FetchJobPrivate::parseSentence(
const QByteArray &structure,
int &pos )
435 skipLeadingSpaces( structure, pos );
437 if ( structure[pos] !=
'(' ) {
438 return parseString( structure, pos );
444 switch ( structure[pos] ) {
462 skipLeadingSpaces( structure, pos );
463 parseString( structure, pos );
464 skipLeadingSpaces( structure, pos );
467 }
while ( pos < structure.size() && stack != 0 );
469 result = structure.mid( start, pos - start );
474 QByteArray FetchJobPrivate::parseString(
const QByteArray &structure,
int &pos )
478 skipLeadingSpaces( structure, pos );
481 bool foundSlash =
false;
484 if ( structure[pos] ==
'"' ) {
487 if ( structure[pos] ==
'\\' ) {
492 if ( structure[pos] ==
'"' ) {
493 result = structure.mid( start + 1, pos - start - 1 );
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] ==
'"' ) {
511 if ( structure[pos] ==
'\\' ) {
517 result = structure.mid( start, pos - start );
520 if ( result ==
"NIL" ) {
527 while ( result.contains(
"\\\"" ) ) {
528 result.replace(
"\\\"",
"\"" );
530 while ( result.contains(
"\\\\" ) ) {
531 result.replace(
"\\\\",
"\\" );
538 void FetchJobPrivate::skipLeadingSpaces(
const QByteArray &structure,
int &pos )
540 while ( pos < structure.size() && structure[pos] ==
' ' ) {
545 #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.