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 00169 switch ( d->scope.mode ) { 00170 case FetchScope::Headers: 00171 if ( d->scope.parts.isEmpty() ) { 00172 parameters+="(RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)"; 00173 } else { 00174 parameters+='('; 00175 foreach ( const QByteArray &part, d->scope.parts ) { 00176 parameters+="BODY.PEEK["+part+".MIME] "; 00177 } 00178 parameters+="UID)"; 00179 } 00180 break; 00181 case FetchScope::Flags: 00182 parameters+="(FLAGS UID)"; 00183 break; 00184 case FetchScope::Structure: 00185 parameters+="(BODYSTRUCTURE UID)"; 00186 break; 00187 case FetchScope::Content: 00188 if ( d->scope.parts.isEmpty() ) { 00189 parameters+="(BODY.PEEK[] UID)"; 00190 } else { 00191 parameters+='('; 00192 foreach ( const QByteArray &part, d->scope.parts ) { 00193 parameters+="BODY.PEEK["+part+"] "; 00194 } 00195 parameters+="UID)"; 00196 } 00197 break; 00198 case FetchScope::Full: 00199 parameters+="(RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"; 00200 break; 00201 } 00202 00203 QByteArray command = "FETCH"; 00204 if ( d->uidBased ) { 00205 command = "UID "+command; 00206 } 00207 00208 d->emitPendingsTimer.start( 100 ); 00209 d->selectedMailBox = d->m_session->selectedMailBox(); 00210 d->tags << d->sessionInternal()->sendCommand( command, parameters ); 00211 } 00212 00213 void FetchJob::handleResponse( const Message &response ) 00214 { 00215 Q_D(FetchJob); 00216 00217 // We can predict it'll be handled by handleErrorReplies() so stop 00218 // the timer now so that result() will really be the last emitted signal. 00219 if ( !response.content.isEmpty() 00220 && d->tags.size() == 1 00221 && d->tags.contains( response.content.first().toString() ) ) { 00222 d->emitPendingsTimer.stop(); 00223 d->emitPendings(); 00224 } 00225 00226 if (handleErrorReplies(response) == NotHandled ) { 00227 if ( response.content.size() == 4 00228 && response.content[2].toString()=="FETCH" 00229 && response.content[3].type()==Message::Part::List ) { 00230 00231 qint64 id = response.content[1].toString().toLongLong(); 00232 QList<QByteArray> content = response.content[3].toList(); 00233 00234 MessagePtr message( new KMime::Message ); 00235 bool shouldParseMessage = false; 00236 MessageParts parts; 00237 00238 for ( QList<QByteArray>::ConstIterator it = content.constBegin(); 00239 it!=content.constEnd(); ++it ) { 00240 QByteArray str = *it; 00241 ++it; 00242 00243 if ( it==content.constEnd() ) { // Uh oh, message was truncated? 00244 kWarning() << "FETCH reply got truncated, skipping."; 00245 break; 00246 } 00247 00248 if ( str=="UID" ) { 00249 d->pendingUids[id] = it->toLongLong(); 00250 } else if ( str=="RFC822.SIZE" ) { 00251 d->pendingSizes[id] = it->toLongLong(); 00252 } else if ( str=="INTERNALDATE" ) { 00253 message->date()->setDateTime( KDateTime::fromString( *it, KDateTime::RFCDate ) ); 00254 } else if ( str=="FLAGS" ) { 00255 if ( (*it).startsWith('(') && (*it).endsWith(')') ) { 00256 QByteArray str = *it; 00257 str.chop(1); 00258 str.remove(0, 1); 00259 d->pendingFlags[id] = str.split(' '); 00260 } else { 00261 d->pendingFlags[id] << *it; 00262 } 00263 } else if ( str=="BODYSTRUCTURE" ) { 00264 int pos = 0; 00265 d->parseBodyStructure(*it, pos, message.get()); 00266 message->assemble(); 00267 } else if ( str.startsWith( "BODY[") ) { //krazy:exclude=strings 00268 if ( !str.endsWith(']') ) { // BODY[ ... ] might have been split, skip until we find the ] 00269 while ( !(*it).endsWith(']') ) ++it; 00270 ++it; 00271 } 00272 00273 int index; 00274 if ( (index=str.indexOf("HEADER"))>0 || (index=str.indexOf("MIME"))>0 ) { // headers 00275 if ( str[index-1]=='.' ) { 00276 QByteArray partId = str.mid( 5, index-6 ); 00277 if ( !parts.contains( partId ) ) { 00278 parts[partId] = ContentPtr( new KMime::Content ); 00279 } 00280 parts[partId]->setHead(*it); 00281 parts[partId]->parse(); 00282 // XXX: [alexmerry, 2010-7-24]: (why) does this work without 00283 // d->pendingParts[id] = parts; when in Headers mode? 00284 } else { 00285 message->setHead(*it); 00286 shouldParseMessage = true; 00287 } 00288 } else { // full payload 00289 if ( str=="BODY[]" ) { 00290 message->setContent( KMime::CRLFtoLF(*it) ); 00291 shouldParseMessage = true; 00292 00293 d->pendingMessages[id] = message; 00294 } else { 00295 QByteArray partId = str.mid( 5, str.size()-6 ); 00296 parts[partId]->setBody(*it); 00297 parts[partId]->parse(); 00298 00299 d->pendingParts[id] = parts; 00300 } 00301 } 00302 } 00303 } 00304 00305 if ( shouldParseMessage ) { 00306 message->parse(); 00307 } 00308 00309 // For the headers mode the message is built in several 00310 // steps, hence why we wait it to be done until putting it 00311 // in the pending queue. 00312 if ( d->scope.mode == FetchScope::Headers ) { 00313 d->pendingMessages[id] = message; 00314 } 00315 } 00316 } 00317 } 00318 00319 void FetchJobPrivate::parseBodyStructure(const QByteArray &structure, int &pos, KMime::Content *content) 00320 { 00321 skipLeadingSpaces(structure, pos); 00322 00323 if ( structure[pos]!='(' ) { 00324 return; 00325 } 00326 00327 pos++; 00328 00329 00330 if ( structure[pos]!='(' ) { // simple part 00331 pos--; 00332 parsePart( structure, pos, content ); 00333 } else { // multi part 00334 content->contentType()->setMimeType("MULTIPART/MIXED"); 00335 while ( pos<structure.size() && structure[pos]=='(' ) { 00336 KMime::Content *child = new KMime::Content; 00337 content->addContent( child ); 00338 parseBodyStructure( structure, pos, child ); 00339 child->assemble(); 00340 } 00341 00342 QByteArray subType = parseString( structure, pos ); 00343 content->contentType()->setMimeType( "MULTIPART/"+subType ); 00344 00345 parseSentence( structure, pos ); // Ditch the parameters... FIXME: Read it to get charset and name 00346 00347 QByteArray disposition = parseSentence( structure, pos ); 00348 if ( disposition.contains("INLINE") ) { 00349 content->contentDisposition()->setDisposition( KMime::Headers::CDinline ); 00350 } else if ( disposition.contains("ATTACHMENT") ) { 00351 content->contentDisposition()->setDisposition( KMime::Headers::CDattachment ); 00352 } 00353 00354 parseSentence( structure, pos ); // Ditch the body language 00355 } 00356 00357 // Consume what's left 00358 while ( pos<structure.size() && structure[pos]!=')' ) { 00359 skipLeadingSpaces( structure, pos ); 00360 parseSentence( structure, pos ); 00361 skipLeadingSpaces( structure, pos ); 00362 } 00363 00364 pos++; 00365 } 00366 00367 void FetchJobPrivate::parsePart( const QByteArray &structure, int &pos, KMime::Content *content ) 00368 { 00369 if ( structure[pos]!='(' ) { 00370 return; 00371 } 00372 00373 pos++; 00374 00375 QByteArray mainType = parseString( structure, pos ); 00376 QByteArray subType = parseString( structure, pos ); 00377 00378 content->contentType()->setMimeType( mainType+'/'+subType ); 00379 00380 parseSentence( structure, pos ); // Ditch the parameters... FIXME: Read it to get charset and name 00381 parseString( structure, pos ); // ... and the id 00382 00383 content->contentDescription()->from7BitString( parseString( structure, pos ) ); 00384 00385 parseString( structure, pos ); // Ditch the encoding too 00386 parseString( structure, pos ); // ... and the size 00387 if ( mainType=="TEXT" ) { 00388 parseString( structure, pos ); // ... and the line count 00389 } 00390 00391 QByteArray disposition = parseSentence( structure, pos ); 00392 if ( disposition.contains("INLINE") ) { 00393 content->contentDisposition()->setDisposition( KMime::Headers::CDinline ); 00394 } else if ( disposition.contains("ATTACHMENT") ) { 00395 content->contentDisposition()->setDisposition( KMime::Headers::CDattachment ); 00396 } 00397 00398 // Consume what's left 00399 while ( pos<structure.size() && structure[pos]!=')' ) { 00400 skipLeadingSpaces( structure, pos ); 00401 parseSentence( structure, pos ); 00402 skipLeadingSpaces( structure, pos ); 00403 } 00404 } 00405 00406 QByteArray FetchJobPrivate::parseSentence( const QByteArray &structure, int &pos ) 00407 { 00408 QByteArray result; 00409 int stack = 0; 00410 00411 skipLeadingSpaces( structure, pos ); 00412 00413 if ( structure[pos]!='(' ) { 00414 return parseString( structure, pos ); 00415 } 00416 00417 int start = pos; 00418 00419 do { 00420 switch ( structure[pos] ) { 00421 case '(': 00422 pos++; 00423 stack++; 00424 break; 00425 case ')': 00426 pos++; 00427 stack--; 00428 break; 00429 case '[': 00430 pos++; 00431 stack++; 00432 break; 00433 case ']': 00434 pos++; 00435 stack--; 00436 break; 00437 default: 00438 skipLeadingSpaces(structure, pos); 00439 parseString(structure, pos); 00440 skipLeadingSpaces(structure, pos); 00441 break; 00442 } 00443 } while ( pos<structure.size() && stack!=0 ); 00444 00445 result = structure.mid( start, pos - start ); 00446 00447 return result; 00448 } 00449 00450 QByteArray FetchJobPrivate::parseString( const QByteArray &structure, int &pos ) 00451 { 00452 QByteArray result; 00453 00454 skipLeadingSpaces( structure, pos ); 00455 00456 int start = pos; 00457 bool foundSlash = false; 00458 00459 // quoted string 00460 if ( structure[pos] == '"' ) { 00461 pos++; 00462 Q_FOREVER { 00463 if ( structure[pos] == '\\' ) { 00464 pos+= 2; 00465 foundSlash = true; 00466 continue; 00467 } 00468 if ( structure[pos] == '"' ) { 00469 result = structure.mid( start+1, pos - start ); 00470 pos++; 00471 break; 00472 } 00473 pos++; 00474 } 00475 } else { // unquoted string 00476 Q_FOREVER { 00477 if ( structure[pos] == ' ' || structure[pos] == '(' || structure[pos] == ')' || structure[pos] == '[' || structure[pos] == ']' || structure[pos] == '\n' || structure[pos] == '\r' || structure[pos] == '"') { 00478 break; 00479 } 00480 if (structure[pos] == '\\') 00481 foundSlash = true; 00482 pos++; 00483 } 00484 00485 result = structure.mid( start, pos - start ); 00486 00487 // transform unquoted NIL 00488 if ( result == "NIL" ) 00489 result.clear(); 00490 } 00491 00492 // simplify slashes 00493 if ( foundSlash ) { 00494 while ( result.contains( "\\\"" ) ) 00495 result.replace( "\\\"", "\"" ); 00496 while ( result.contains( "\\\\" ) ) 00497 result.replace( "\\\\", "\\" ); 00498 } 00499 00500 return result; 00501 } 00502 00503 void FetchJobPrivate::skipLeadingSpaces( const QByteArray &structure, int &pos ) 00504 { 00505 while ( structure[pos]==' ' && pos<structure.size() ) pos++; 00506 } 00507 00508 #include "fetchjob.moc"