• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • 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       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"

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
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • 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.4
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