• Skip to content
  • Skip to link menu
KDE 4.5 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

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

KIMAP Library

Skip menu "KIMAP Library"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal