• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.8.3 API Reference
  • 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         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 7 2012 23:55:08 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIMAP Library

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

kdepimlibs-4.8.3 API Reference

Skip menu "kdepimlibs-4.8.3 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • 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
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal