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