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