00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "fetchjob.h"
00021
00022 #include <QtCore/QTimer>
00023 #include <KDE/KDebug>
00024 #include <KDE/KLocale>
00025
00026 #include "job_p.h"
00027 #include "message_p.h"
00028 #include "session_p.h"
00029
00030 namespace KIMAP
00031 {
00032 class FetchJobPrivate : public JobPrivate
00033 {
00034 public:
00035 FetchJobPrivate( FetchJob *job, Session *session, const QString& name ) : JobPrivate( session, name ), q(job), uidBased(false) { }
00036 ~FetchJobPrivate() { }
00037
00038 void parseBodyStructure( const QByteArray &structure, int &pos, KMime::Content *content );
00039 void parsePart( const QByteArray &structure, int &pos, KMime::Content *content );
00040 QByteArray parseString( const QByteArray &structure, int &pos );
00041 QByteArray parseSentence( const QByteArray &structure, int &pos );
00042 void skipLeadingSpaces( const QByteArray &structure, int &pos );
00043
00044 MessagePtr message(int id)
00045 {
00046 if ( !messages.contains(id) ) {
00047 messages[id] = MessagePtr(new KMime::Message);
00048 }
00049
00050 return messages[id];
00051 }
00052
00053 ContentPtr part(int id, QByteArray partName)
00054 {
00055 if ( !parts[id].contains(partName) ) {
00056 parts[id][partName] = ContentPtr(new KMime::Content);
00057 }
00058
00059 return parts[id][partName];
00060 }
00061
00062 void emitPendings()
00063 {
00064 if ( pendingUids.isEmpty() ) {
00065 return;
00066 }
00067
00068 if ( !pendingParts.isEmpty() ) {
00069 emit q->partsReceived( selectedMailBox,
00070 pendingUids, pendingParts );
00071
00072 } else if ( !pendingSizes.isEmpty() || !pendingFlags.isEmpty() ) {
00073 emit q->headersReceived( selectedMailBox,
00074 pendingUids, pendingSizes,
00075 pendingFlags, pendingMessages );
00076 } else {
00077 emit q->messagesReceived( selectedMailBox,
00078 pendingUids, pendingMessages );
00079 }
00080
00081 pendingUids.clear();
00082 pendingMessages.clear();
00083 pendingParts.clear();
00084 pendingSizes.clear();
00085 pendingFlags.clear();
00086 }
00087
00088 FetchJob * const q;
00089
00090 ImapSet set;
00091 bool uidBased;
00092 FetchJob::FetchScope scope;
00093 QString selectedMailBox;
00094
00095 QMap<qint64, MessagePtr> messages;
00096 QMap<qint64, MessageParts> parts;
00097 QMap<qint64, MessageFlags> flags;
00098 QMap<qint64, qint64> sizes;
00099 QMap<qint64, qint64> uids;
00100
00101 QTimer emitPendingsTimer;
00102 QMap<qint64, MessagePtr> pendingMessages;
00103 QMap<qint64, MessageParts> pendingParts;
00104 QMap<qint64, MessageFlags> pendingFlags;
00105 QMap<qint64, qint64> pendingSizes;
00106 QMap<qint64, qint64> pendingUids;
00107 };
00108 }
00109
00110 using namespace KIMAP;
00111
00112 FetchJob::FetchJob( Session *session )
00113 : Job( *new FetchJobPrivate(this, session, i18n("Fetch")) )
00114 {
00115 Q_D(FetchJob);
00116 d->scope.mode = FetchScope::Content;
00117 connect( &d->emitPendingsTimer, SIGNAL( timeout() ),
00118 this, SLOT( emitPendings() ) );
00119 }
00120
00121 FetchJob::~FetchJob()
00122 {
00123 }
00124
00125 void FetchJob::setSequenceSet( const ImapSet &set )
00126 {
00127 Q_D(FetchJob);
00128 Q_ASSERT( !set.toImapSequenceSet().trimmed().isEmpty() );
00129 d->set = set;
00130 }
00131
00132 ImapSet FetchJob::sequenceSet() const
00133 {
00134 Q_D(const FetchJob);
00135 return d->set;
00136 }
00137
00138 void FetchJob::setUidBased(bool uidBased)
00139 {
00140 Q_D(FetchJob);
00141 d->uidBased = uidBased;
00142 }
00143
00144 bool FetchJob::isUidBased() const
00145 {
00146 Q_D(const FetchJob);
00147 return d->uidBased;
00148 }
00149
00150 void FetchJob::setScope( const FetchScope &scope )
00151 {
00152 Q_D(FetchJob);
00153 d->scope = scope;
00154 }
00155
00156 FetchJob::FetchScope FetchJob::scope() const
00157 {
00158 Q_D(const FetchJob);
00159 return d->scope;
00160 }
00161
00162 QMap<qint64, MessagePtr> FetchJob::messages() const
00163 {
00164 Q_D(const FetchJob);
00165 return d->messages;
00166 }
00167
00168 QMap<qint64, MessageParts> FetchJob::parts() const
00169 {
00170 Q_D(const FetchJob);
00171 return d->parts;
00172 }
00173
00174 QMap<qint64, MessageFlags> FetchJob::flags() const
00175 {
00176 Q_D(const FetchJob);
00177 return d->flags;
00178 }
00179
00180 QMap<qint64, qint64> FetchJob::sizes() const
00181 {
00182 Q_D(const FetchJob);
00183 return d->sizes;
00184 }
00185
00186 QMap<qint64, qint64> FetchJob::uids() const
00187 {
00188 Q_D(const FetchJob);
00189 return d->uids;
00190 }
00191
00192 void FetchJob::doStart()
00193 {
00194 Q_D(FetchJob);
00195
00196 QByteArray parameters = d->set.toImapSequenceSet()+' ';
00197
00198 switch ( d->scope.mode ) {
00199 case FetchScope::Headers:
00200 if ( d->scope.parts.isEmpty() ) {
00201 parameters+="(RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)";
00202 } else {
00203 parameters+='(';
00204 foreach ( const QByteArray &part, d->scope.parts ) {
00205 parameters+="BODY.PEEK["+part+".MIME] ";
00206 }
00207 parameters+="UID)";
00208 }
00209 break;
00210 case FetchScope::Flags:
00211 parameters+="(FLAGS UID)";
00212 break;
00213 case FetchScope::Structure:
00214 parameters+="(BODYSTRUCTURE UID)";
00215 break;
00216 case FetchScope::Content:
00217 if ( d->scope.parts.isEmpty() ) {
00218 parameters+="(BODY.PEEK[] UID)";
00219 } else {
00220 parameters+='(';
00221 foreach ( const QByteArray &part, d->scope.parts ) {
00222 parameters+="BODY.PEEK["+part+"] ";
00223 }
00224 parameters+="UID)";
00225 }
00226 break;
00227 case FetchScope::Full:
00228 parameters+="(RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)";
00229 break;
00230 }
00231
00232 QByteArray command = "FETCH";
00233 if ( d->uidBased ) {
00234 command = "UID "+command;
00235 }
00236
00237 d->emitPendingsTimer.start( 100 );
00238 d->selectedMailBox = d->m_session->selectedMailBox();
00239 d->tags << d->sessionInternal()->sendCommand( command, parameters );
00240 }
00241
00242 void FetchJob::handleResponse( const Message &response )
00243 {
00244 Q_D(FetchJob);
00245
00246
00247
00248 if ( !response.content.isEmpty()
00249 && d->tags.size() == 1
00250 && d->tags.contains( response.content.first().toString() ) ) {
00251 d->emitPendingsTimer.stop();
00252 d->emitPendings();
00253 }
00254
00255 if (handleErrorReplies(response) == NotHandled ) {
00256 if ( response.content.size() == 4
00257 && response.content[2].toString()=="FETCH"
00258 && response.content[3].type()==Message::Part::List ) {
00259
00260 qint64 id = response.content[1].toString().toLongLong();
00261 QList<QByteArray> content = response.content[3].toList();
00262
00263 for ( QList<QByteArray>::ConstIterator it = content.constBegin();
00264 it!=content.constEnd(); ++it ) {
00265 QByteArray str = *it;
00266 ++it;
00267
00268 if ( it==content.constEnd() ) {
00269 kWarning() << "FETCH reply got truncated, skipping.";
00270 break;
00271 }
00272
00273 if ( str=="UID" ) {
00274 d->uids[id] = it->toLongLong();
00275 d->pendingUids[id] = d->uids[id];
00276 } else if ( str=="RFC822.SIZE" ) {
00277 d->sizes[id] = it->toLongLong();
00278 d->pendingSizes[id] = d->sizes[id];
00279 } else if ( str=="INTERNALDATE" ) {
00280 d->message(id)->date()->setDateTime( KDateTime::fromString( *it, KDateTime::RFCDate ) );
00281 } else if ( str=="FLAGS" ) {
00282 if ( (*it).startsWith('(') && (*it).endsWith(')') ) {
00283 QByteArray str = *it;
00284 str.chop(1);
00285 str.remove(0, 1);
00286 if ( !str.isEmpty() ) {
00287 d->flags[id] = str.split(' ');
00288 }
00289 } else {
00290 d->flags[id] << *it;
00291 }
00292 d->pendingFlags[id] = d->flags[id];
00293 } else if ( str=="BODYSTRUCTURE" ) {
00294 int pos = 0;
00295 d->parseBodyStructure(*it, pos, d->message(id).get());
00296 d->message(id)->assemble();
00297 } else if ( str.startsWith( "BODY[") ) {
00298 if ( !str.endsWith(']') ) {
00299 while ( !(*it).endsWith(']') ) ++it;
00300 ++it;
00301 }
00302
00303 int index;
00304 if ( (index=str.indexOf("HEADER"))>0 || (index=str.indexOf("MIME"))>0 ) {
00305 if ( str[index-1]=='.' ) {
00306 QByteArray partId = str.mid( 5, index-6 );
00307 d->part( id, partId )->setHead(*it);
00308 d->part( id, partId )->parse();
00309 } else {
00310 d->message(id)->setHead(*it);
00311 d->message(id)->parse();
00312 }
00313 } else {
00314 if ( str=="BODY[]" ) {
00315 d->message(id)->setContent( KMime::CRLFtoLF(*it) );
00316 d->message(id)->parse();
00317
00318 d->pendingMessages[id] = d->message(id);
00319 } else {
00320 QByteArray partId = str.mid( 5, str.size()-6 );
00321 d->part( id, partId )->setBody(*it);
00322 d->part( id, partId )->parse();
00323
00324 d->pendingParts[id] = d->parts[id];
00325 }
00326 }
00327 }
00328 }
00329
00330
00331
00332
00333 if ( d->scope.mode == FetchScope::Headers ) {
00334 d->pendingMessages[id] = d->message(id);
00335 }
00336 }
00337 }
00338 }
00339
00340 void FetchJobPrivate::parseBodyStructure(const QByteArray &structure, int &pos, KMime::Content *content)
00341 {
00342 skipLeadingSpaces(structure, pos);
00343
00344 if ( structure[pos]!='(' ) {
00345 return;
00346 }
00347
00348 pos++;
00349
00350
00351 if ( structure[pos]!='(' ) {
00352 pos--;
00353 parsePart( structure, pos, content );
00354 } else {
00355 content->contentType()->setMimeType("MULTIPART/MIXED");
00356 while ( pos<structure.size() && structure[pos]=='(' ) {
00357 KMime::Content *child = new KMime::Content;
00358 content->addContent( child );
00359 parseBodyStructure( structure, pos, child );
00360 child->assemble();
00361 }
00362
00363 QByteArray subType = parseString( structure, pos );
00364 content->contentType()->setMimeType( "MULTIPART/"+subType );
00365
00366 parseSentence( structure, pos );
00367
00368 QByteArray disposition = parseSentence( structure, pos );
00369 if ( disposition.contains("INLINE") ) {
00370 content->contentDisposition()->setDisposition( KMime::Headers::CDinline );
00371 } else if ( disposition.contains("ATTACHMENT") ) {
00372 content->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
00373 }
00374
00375 parseSentence( structure, pos );
00376 }
00377
00378
00379 while ( pos<structure.size() && structure[pos]!=')' ) {
00380 skipLeadingSpaces( structure, pos );
00381 parseSentence( structure, pos );
00382 skipLeadingSpaces( structure, pos );
00383 }
00384
00385 pos++;
00386 }
00387
00388 void FetchJobPrivate::parsePart( const QByteArray &structure, int &pos, KMime::Content *content )
00389 {
00390 if ( structure[pos]!='(' ) {
00391 return;
00392 }
00393
00394 pos++;
00395
00396 QByteArray mainType = parseString( structure, pos );
00397 QByteArray subType = parseString( structure, pos );
00398
00399 content->contentType()->setMimeType( mainType+'/'+subType );
00400
00401 parseSentence( structure, pos );
00402 parseString( structure, pos );
00403
00404 content->contentDescription()->from7BitString( parseString( structure, pos ) );
00405
00406 parseString( structure, pos );
00407 parseString( structure, pos );
00408 if ( mainType=="TEXT" ) {
00409 parseString( structure, pos );
00410 }
00411
00412 QByteArray disposition = parseSentence( structure, pos );
00413 if ( disposition.contains("INLINE") ) {
00414 content->contentDisposition()->setDisposition( KMime::Headers::CDinline );
00415 } else if ( disposition.contains("ATTACHMENT") ) {
00416 content->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
00417 }
00418
00419
00420 while ( pos<structure.size() && structure[pos]!=')' ) {
00421 skipLeadingSpaces( structure, pos );
00422 parseSentence( structure, pos );
00423 skipLeadingSpaces( structure, pos );
00424 }
00425 }
00426
00427 QByteArray FetchJobPrivate::parseSentence( const QByteArray &structure, int &pos )
00428 {
00429 QByteArray result;
00430 int stack = 0;
00431
00432 skipLeadingSpaces( structure, pos );
00433
00434 if ( structure[pos]!='(' ) {
00435 return parseString( structure, pos );
00436 }
00437
00438 int start = pos;
00439
00440 do {
00441 switch ( structure[pos] ) {
00442 case '(':
00443 pos++;
00444 stack++;
00445 break;
00446 case ')':
00447 pos++;
00448 stack--;
00449 break;
00450 case '[':
00451 pos++;
00452 stack++;
00453 break;
00454 case ']':
00455 pos++;
00456 stack--;
00457 break;
00458 default:
00459 skipLeadingSpaces(structure, pos);
00460 parseString(structure, pos);
00461 skipLeadingSpaces(structure, pos);
00462 break;
00463 }
00464 } while ( pos<structure.size() && stack!=0 );
00465
00466 result = structure.mid( start, pos - start );
00467
00468 return result;
00469 }
00470
00471 QByteArray FetchJobPrivate::parseString( const QByteArray &structure, int &pos )
00472 {
00473 QByteArray result;
00474
00475 skipLeadingSpaces( structure, pos );
00476
00477 int start = pos;
00478 bool foundSlash = false;
00479
00480
00481 if ( structure[pos] == '"' ) {
00482 pos++;
00483 Q_FOREVER {
00484 if ( structure[pos] == '\\' ) {
00485 pos+= 2;
00486 foundSlash = true;
00487 continue;
00488 }
00489 if ( structure[pos] == '"' ) {
00490 result = structure.mid( start+1, pos - start );
00491 pos++;
00492 break;
00493 }
00494 pos++;
00495 }
00496 } else {
00497 Q_FOREVER {
00498 if ( structure[pos] == ' ' || structure[pos] == '(' || structure[pos] == ')' || structure[pos] == '[' || structure[pos] == ']' || structure[pos] == '\n' || structure[pos] == '\r' || structure[pos] == '"') {
00499 break;
00500 }
00501 if (structure[pos] == '\\')
00502 foundSlash = true;
00503 pos++;
00504 }
00505
00506 result = structure.mid( start, pos - start );
00507
00508
00509 if ( result == "NIL" )
00510 result.clear();
00511 }
00512
00513
00514 if ( foundSlash ) {
00515 while ( result.contains( "\\\"" ) )
00516 result.replace( "\\\"", "\"" );
00517 while ( result.contains( "\\\\" ) )
00518 result.replace( "\\\\", "\\" );
00519 }
00520
00521 return result;
00522 }
00523
00524 void FetchJobPrivate::skipLeadingSpaces( const QByteArray &structure, int &pos )
00525 {
00526 while ( structure[pos]==' ' && pos<structure.size() ) pos++;
00527 }
00528
00529 #include "fetchjob.moc"