• 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 
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"

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.3
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