KIMAP Library
fetchjob.cpp
00001 /* 00002 Copyright (c) 2009 Kevin Ottens <ervin@kde.org> 00003 00004 This library is free software; you can redistribute it and/or modify it 00005 under the terms of the GNU Library General Public License as published by 00006 the Free Software Foundation; either version 2 of the License, or (at your 00007 option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, but WITHOUT 00010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00012 License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to the 00016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00017 02110-1301, USA. 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 // We can predict it'll be handled by handleErrorReplies() so stop 00219 // the timer now so that result() will really be the last emitted signal. 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() ) { // Uh oh, message was truncated? 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[") ) { //krazy:exclude=strings 00269 if ( !str.endsWith(']') ) { // BODY[ ... ] might have been split, skip until we find the ] 00270 while ( !(*it).endsWith(']') ) ++it; 00271 ++it; 00272 } 00273 00274 int index; 00275 if ( (index=str.indexOf("HEADER"))>0 || (index=str.indexOf("MIME"))>0 ) { // headers 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 // XXX: [alexmerry, 2010-7-24]: (why) does this work without 00284 // d->pendingParts[id] = parts; when in Headers mode? 00285 } else { 00286 message->setHead(*it); 00287 shouldParseMessage = true; 00288 } 00289 } else { // full payload 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 // For the headers mode the message is built in several 00311 // steps, hence why we wait it to be done until putting it 00312 // in the pending queue. 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]!='(' ) { // simple part 00332 pos--; 00333 parsePart( structure, pos, content ); 00334 } else { // multi part 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 ); // Ditch the parameters... FIXME: Read it to get charset and name 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 ); // Ditch the body language 00356 } 00357 00358 // Consume what's left 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 ); // Ditch the parameters... FIXME: Read it to get charset and name 00382 parseString( structure, pos ); // ... and the id 00383 00384 content->contentDescription()->from7BitString( parseString( structure, pos ) ); 00385 00386 parseString( structure, pos ); // Ditch the encoding too 00387 parseString( structure, pos ); // ... and the size 00388 if ( mainType=="TEXT" ) { 00389 parseString( structure, pos ); // ... and the line count 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 // Consume what's left 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 // quoted string 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 { // unquoted string 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 // transform unquoted NIL 00489 if ( result == "NIL" ) 00490 result.clear(); 00491 } 00492 00493 // simplify slashes 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"