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 d->set = set;
00129 }
00130
00131 ImapSet FetchJob::sequenceSet() const
00132 {
00133 Q_D(const FetchJob);
00134 return d->set;
00135 }
00136
00137 void FetchJob::setUidBased(bool uidBased)
00138 {
00139 Q_D(FetchJob);
00140 d->uidBased = uidBased;
00141 }
00142
00143 bool FetchJob::isUidBased() const
00144 {
00145 Q_D(const FetchJob);
00146 return d->uidBased;
00147 }
00148
00149 void FetchJob::setScope( const FetchScope &scope )
00150 {
00151 Q_D(FetchJob);
00152 d->scope = scope;
00153 }
00154
00155 FetchJob::FetchScope FetchJob::scope() const
00156 {
00157 Q_D(const FetchJob);
00158 return d->scope;
00159 }
00160
00161 QMap<qint64, MessagePtr> FetchJob::messages() const
00162 {
00163 Q_D(const FetchJob);
00164 return d->messages;
00165 }
00166
00167 QMap<qint64, MessageParts> FetchJob::parts() const
00168 {
00169 Q_D(const FetchJob);
00170 return d->parts;
00171 }
00172
00173 QMap<qint64, MessageFlags> FetchJob::flags() const
00174 {
00175 Q_D(const FetchJob);
00176 return d->flags;
00177 }
00178
00179 QMap<qint64, qint64> FetchJob::sizes() const
00180 {
00181 Q_D(const FetchJob);
00182 return d->sizes;
00183 }
00184
00185 QMap<qint64, qint64> FetchJob::uids() const
00186 {
00187 Q_D(const FetchJob);
00188 return d->uids;
00189 }
00190
00191 void FetchJob::doStart()
00192 {
00193 Q_D(FetchJob);
00194
00195 QByteArray parameters = d->set.toImapSequenceSet()+' ';
00196
00197 switch ( d->scope.mode ) {
00198 case FetchScope::Headers:
00199 if ( d->scope.parts.isEmpty() ) {
00200 parameters+="(RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT)] FLAGS UID)";
00201 } else {
00202 parameters+='(';
00203 foreach ( const QByteArray &part, d->scope.parts ) {
00204 parameters+="BODY.PEEK["+part+".MIME] ";
00205 }
00206 parameters+="UID)";
00207 }
00208 break;
00209 case FetchScope::Flags:
00210 parameters+="(FLAGS UID)";
00211 break;
00212 case FetchScope::Structure:
00213 parameters+="(BODYSTRUCTURE UID)";
00214 break;
00215 case FetchScope::Content:
00216 if ( d->scope.parts.isEmpty() ) {
00217 parameters+="(BODY.PEEK[] UID)";
00218 } else {
00219 parameters+='(';
00220 foreach ( const QByteArray &part, d->scope.parts ) {
00221 parameters+="BODY.PEEK["+part+"] ";
00222 }
00223 parameters+="UID)";
00224 }
00225 break;
00226 case FetchScope::Full:
00227 parameters+="(RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)";
00228 break;
00229 }
00230
00231 QByteArray command = "FETCH";
00232 if ( d->uidBased ) {
00233 command = "UID "+command;
00234 }
00235
00236 d->emitPendingsTimer.start( 100 );
00237 d->selectedMailBox = d->sessionInternal()->selectedMailBox();
00238 d->tags << d->sessionInternal()->sendCommand( command, parameters );
00239 }
00240
00241 void FetchJob::handleResponse( const Message &response )
00242 {
00243 Q_D(FetchJob);
00244
00245
00246
00247 if ( !response.content.isEmpty()
00248 && d->tags.size() == 1
00249 && d->tags.contains( response.content.first().toString() ) ) {
00250 d->emitPendingsTimer.stop();
00251 d->emitPendings();
00252 }
00253
00254 if (handleErrorReplies(response) == NotHandled ) {
00255 if ( response.content.size() == 4
00256 && response.content[2].toString()=="FETCH"
00257 && response.content[3].type()==Message::Part::List ) {
00258
00259 qint64 id = response.content[1].toString().toLongLong();
00260 QList<QByteArray> content = response.content[3].toList();
00261
00262 for ( QList<QByteArray>::ConstIterator it = content.constBegin();
00263 it!=content.constEnd(); ++it ) {
00264 QByteArray str = *it;
00265 ++it;
00266
00267 if ( it==content.constEnd() ) {
00268 kWarning() << "FETCH reply got truncated, skipping.";
00269 break;
00270 }
00271
00272 if ( str=="UID" ) {
00273 d->uids[id] = it->toLongLong();
00274 d->pendingUids[id] = d->uids[id];
00275 } else if ( str=="RFC822.SIZE" ) {
00276 d->sizes[id] = it->toLongLong();
00277 d->pendingSizes[id] = d->sizes[id];
00278 } else if ( str=="INTERNALDATE" ) {
00279 d->message(id)->date()->setDateTime( KDateTime::fromString( *it, KDateTime::RFCDate ) );
00280 } else if ( str=="FLAGS" ) {
00281 if ( (*it).startsWith('(') && (*it).endsWith(')') ) {
00282 QByteArray str = *it;
00283 str.chop(1);
00284 str.remove(0, 1);
00285 d->flags[id] = str.split(' ');
00286 } else {
00287 d->flags[id] << *it;
00288 }
00289 d->pendingFlags[id] = d->flags[id];
00290 } else if ( str=="BODYSTRUCTURE" ) {
00291 int pos = 0;
00292 d->parseBodyStructure(*it, pos, d->message(id).get());
00293 d->message(id)->assemble();
00294 } else if ( str.startsWith( "BODY[") ) {
00295 if ( !str.endsWith(']') ) {
00296 while ( !(*it).endsWith(']') ) ++it;
00297 ++it;
00298 }
00299
00300 int index;
00301 if ( (index=str.indexOf("HEADER"))>0 || (index=str.indexOf("MIME"))>0 ) {
00302 if ( str[index-1]=='.' ) {
00303 QByteArray partId = str.mid( 5, index-6 );
00304 d->part( id, partId )->setHead(*it);
00305 d->part( id, partId )->parse();
00306 } else {
00307 d->message(id)->setHead(*it);
00308 d->message(id)->parse();
00309 }
00310 } else {
00311 if ( str=="BODY[]" ) {
00312 d->message(id)->setContent( KMime::CRLFtoLF(*it) );
00313 d->message(id)->parse();
00314
00315 d->pendingMessages[id] = d->message(id);
00316 } else {
00317 QByteArray partId = str.mid( 5, str.size()-6 );
00318 d->part( id, partId )->setBody(*it);
00319 d->part( id, partId )->parse();
00320
00321 d->pendingParts[id] = d->parts[id];
00322 }
00323 }
00324 }
00325 }
00326
00327
00328
00329
00330 if ( d->scope.mode == FetchScope::Headers ) {
00331 d->pendingMessages[id] = d->message(id);
00332 }
00333 }
00334 }
00335 }
00336
00337 void FetchJobPrivate::parseBodyStructure(const QByteArray &structure, int &pos, KMime::Content *content)
00338 {
00339 skipLeadingSpaces(structure, pos);
00340
00341 if ( structure[pos]!='(' ) {
00342 return;
00343 }
00344
00345 pos++;
00346
00347
00348 if ( structure[pos]!='(' ) {
00349 pos--;
00350 parsePart( structure, pos, content );
00351 } else {
00352 content->contentType()->setMimeType("MULTIPART/MIXED");
00353 while ( pos<structure.size() && structure[pos]=='(' ) {
00354 KMime::Content *child = new KMime::Content;
00355 content->addContent( child );
00356 parseBodyStructure( structure, pos, child );
00357 child->assemble();
00358 }
00359
00360 QByteArray subType = parseString( structure, pos );
00361 content->contentType()->setMimeType( "MULTIPART/"+subType );
00362
00363 parseSentence( structure, pos );
00364
00365 QByteArray disposition = parseSentence( structure, pos );
00366 if ( disposition.contains("INLINE") ) {
00367 content->contentDisposition()->setDisposition( KMime::Headers::CDinline );
00368 } else if ( disposition.contains("ATTACHMENT") ) {
00369 content->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
00370 }
00371
00372 parseSentence( structure, pos );
00373 }
00374
00375
00376 while ( pos<structure.size() && structure[pos]!=')' ) {
00377 skipLeadingSpaces( structure, pos );
00378 parseSentence( structure, pos );
00379 skipLeadingSpaces( structure, pos );
00380 }
00381
00382 pos++;
00383 }
00384
00385 void FetchJobPrivate::parsePart( const QByteArray &structure, int &pos, KMime::Content *content )
00386 {
00387 if ( structure[pos]!='(' ) {
00388 return;
00389 }
00390
00391 pos++;
00392
00393 QByteArray mainType = parseString( structure, pos );
00394 QByteArray subType = parseString( structure, pos );
00395
00396 content->contentType()->setMimeType( mainType+'/'+subType );
00397
00398 parseSentence( structure, pos );
00399 parseString( structure, pos );
00400
00401 content->contentDescription()->from7BitString( parseString( structure, pos ) );
00402
00403 parseString( structure, pos );
00404 parseString( structure, pos );
00405 if ( mainType=="TEXT" ) {
00406 parseString( structure, pos );
00407 }
00408
00409 QByteArray disposition = parseSentence( structure, pos );
00410 if ( disposition.contains("INLINE") ) {
00411 content->contentDisposition()->setDisposition( KMime::Headers::CDinline );
00412 } else if ( disposition.contains("ATTACHMENT") ) {
00413 content->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
00414 }
00415
00416
00417 while ( pos<structure.size() && structure[pos]!=')' ) {
00418 skipLeadingSpaces( structure, pos );
00419 parseSentence( structure, pos );
00420 skipLeadingSpaces( structure, pos );
00421 }
00422 }
00423
00424 QByteArray FetchJobPrivate::parseSentence( const QByteArray &structure, int &pos )
00425 {
00426 QByteArray result;
00427 int stack = 0;
00428
00429 skipLeadingSpaces( structure, pos );
00430
00431 if ( structure[pos]!='(' ) {
00432 return parseString( structure, pos );
00433 }
00434
00435 int start = pos;
00436
00437 do {
00438 switch ( structure[pos] ) {
00439 case '(':
00440 pos++;
00441 stack++;
00442 break;
00443 case ')':
00444 pos++;
00445 stack--;
00446 break;
00447 case '[':
00448 pos++;
00449 stack++;
00450 break;
00451 case ']':
00452 pos++;
00453 stack--;
00454 break;
00455 default:
00456 skipLeadingSpaces(structure, pos);
00457 parseString(structure, pos);
00458 skipLeadingSpaces(structure, pos);
00459 break;
00460 }
00461 } while ( pos<structure.size() && stack!=0 );
00462
00463 result = structure.mid( start, pos - start );
00464
00465 return result;
00466 }
00467
00468 QByteArray FetchJobPrivate::parseString( const QByteArray &structure, int &pos )
00469 {
00470 QByteArray result;
00471
00472 skipLeadingSpaces( structure, pos );
00473
00474 int start = pos;
00475 bool foundSlash = false;
00476
00477
00478 if ( structure[pos] == '"' ) {
00479 pos++;
00480 Q_FOREVER {
00481 if ( structure[pos] == '\\' ) {
00482 pos+= 2;
00483 foundSlash = true;
00484 continue;
00485 }
00486 if ( structure[pos] == '"' ) {
00487 result = structure.mid( start+1, pos - start );
00488 pos++;
00489 break;
00490 }
00491 pos++;
00492 }
00493 } else {
00494 Q_FOREVER {
00495 if ( structure[pos] == ' ' || structure[pos] == '(' || structure[pos] == ')' || structure[pos] == '[' || structure[pos] == ']' || structure[pos] == '\n' || structure[pos] == '\r' || structure[pos] == '"') {
00496 break;
00497 }
00498 if (structure[pos] == '\\')
00499 foundSlash = true;
00500 pos++;
00501 }
00502
00503 result = structure.mid( start, pos - start );
00504
00505
00506 if ( result == "NIL" )
00507 result.clear();
00508 }
00509
00510
00511 if ( foundSlash ) {
00512 while ( result.contains( "\\\"" ) )
00513 result.replace( "\\\"", "\"" );
00514 while ( result.contains( "\\\\" ) )
00515 result.replace( "\\\\", "\\" );
00516 }
00517
00518 return result;
00519 }
00520
00521 void FetchJobPrivate::skipLeadingSpaces( const QByteArray &structure, int &pos )
00522 {
00523 while ( structure[pos]==' ' && pos<structure.size() ) pos++;
00524 }
00525
00526 #include "fetchjob.moc"