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