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

kioslave/imap4

imapparser.cpp

00001 /**********************************************************************
00002  *
00003  *   imapparser.cc  - IMAP4rev1 Parser
00004  *   Copyright (C) 2001-2002 Michael Haeckel <haeckel@kde.org>
00005  *   Copyright (C) 2000 Sven Carstens <s.carstens@gmx.de>
00006  *
00007  *   This program is free software; you can redistribute it and/or modify
00008  *   it under the terms of the GNU General Public License as published by
00009  *   the Free Software Foundation; either version 2 of the License, or
00010  *   (at your option) any later version.
00011  *
00012  *   This program is distributed in the hope that it will be useful,
00013  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *   GNU General Public License for more details.
00016  *
00017  *   You should have received a copy of the GNU General Public License
00018  *   along with this program; if not, write to the Free Software
00019  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  *   Send comments and bug fixes to s.carstens@gmx.de
00022  *
00023  *********************************************************************/
00024 
00025 #include "imapparser.h"
00026 #include "imapinfo.h"
00027 #include "mailheader.h"
00028 #include "mimeheader.h"
00029 #include "mailaddress.h"
00030 
00031 #include <sys/types.h>
00032 
00033 #include <stdlib.h>
00034 #include <unistd.h>
00035 #include <QList>
00036 
00037 extern "C" {
00038 #include <sasl/sasl.h>
00039 }
00040 
00041 #include <QRegExp>
00042 #include <QBuffer>
00043 #include <QString>
00044 #include <QStringList>
00045 
00046 #include <kascii.h>
00047 #include <kdebug.h>
00048 #include <kcodecs.h>
00049 #include <kglobal.h>
00050 #include <kurl.h>
00051 
00052 #include <kimap/rfccodecs.h>
00053 using namespace KIMAP;
00054 
00055 static sasl_callback_t callbacks[] = {
00056     { SASL_CB_ECHOPROMPT, NULL, NULL },
00057     { SASL_CB_NOECHOPROMPT, NULL, NULL },
00058     { SASL_CB_GETREALM, NULL, NULL },
00059     { SASL_CB_USER, NULL, NULL },
00060     { SASL_CB_AUTHNAME, NULL, NULL },
00061     { SASL_CB_PASS, NULL, NULL },
00062     { SASL_CB_CANON_USER, NULL, NULL },
00063     { SASL_CB_LIST_END, NULL, NULL }
00064 };
00065 
00066 imapParser::imapParser ()
00067 {
00068   currentState = ISTATE_NO;
00069   commandCounter = 0;
00070   lastHandled = 0;
00071 }
00072 
00073 imapParser::~imapParser ()
00074 {
00075   delete lastHandled;
00076   lastHandled = 0;
00077 }
00078 
00079 CommandPtr
00080 imapParser::doCommand (CommandPtr aCmd)
00081 {
00082   int pl = 0;
00083   sendCommand (aCmd);
00084   while (pl != -1 && !aCmd->isComplete ()) {
00085     while ((pl = parseLoop ()) == 0)
00086      ;
00087   }
00088 
00089   return aCmd;
00090 }
00091 
00092 CommandPtr
00093 imapParser::sendCommand (CommandPtr aCmd)
00094 {
00095   aCmd->setId (QString::number(commandCounter++));
00096   sentQueue.append (aCmd);
00097 
00098   continuation.resize(0);
00099   const QString& command = aCmd->command();
00100 
00101   if (command == "SELECT" || command == "EXAMINE")
00102   {
00103      // we need to know which box we are selecting
00104     parseString p;
00105     p.fromString(aCmd->parameter());
00106     currentBox = parseOneWord(p);
00107     kDebug(7116) <<"imapParser::sendCommand - setting current box to" << currentBox;
00108   }
00109   else if (command == "CLOSE")
00110   {
00111      // we no longer have a box open
00112     currentBox.clear();
00113   }
00114   else if (command.contains("SEARCH")
00115            || command == "GETACL"
00116            || command == "LISTRIGHTS"
00117            || command == "MYRIGHTS"
00118            || command == "GETANNOTATION"
00119            || command == "NAMESPACE"
00120            || command == "GETQUOTAROOT"
00121            || command == "GETQUOTA"
00122            || command == "X-GET-OTHER-USERS"
00123            || command == "X-GET-DELEGATES"
00124            || command == "X-GET-OUT-OF-OFFICE")
00125   {
00126     lastResults.clear ();
00127   }
00128   else if (command == "LIST"
00129            || command == "LSUB")
00130   {
00131     listResponses.clear ();
00132   }
00133   parseWriteLine (aCmd->getStr ());
00134   return aCmd;
00135 }
00136 
00137 bool
00138 imapParser::clientLogin (const QString & aUser, const QString & aPass,
00139   QString & resultInfo)
00140 {
00141   CommandPtr cmd;
00142   bool retVal = false;
00143 
00144   cmd =
00145     doCommand ( CommandPtr( new
00146                             imapCommand ("LOGIN", "\"" + KIMAP::quoteIMAP(aUser)
00147                                          + "\" \"" + KIMAP::quoteIMAP(aPass) + "\"")) );
00148 
00149   if (cmd->result () == "OK")
00150   {
00151     currentState = ISTATE_LOGIN;
00152     retVal = true;
00153   }
00154   resultInfo = cmd->resultInfo();
00155   completeQueue.removeAll (cmd);
00156   return retVal;
00157 }
00158 
00159 static bool sasl_interact( KIO::SlaveBase *slave, KIO::AuthInfo &ai, void *in )
00160 {
00161   kDebug(7116) <<"sasl_interact";
00162   sasl_interact_t *interact = ( sasl_interact_t * ) in;
00163 
00164   //some mechanisms do not require username && pass, so it doesn't need a popup
00165   //window for getting this info
00166   for ( ; interact->id != SASL_CB_LIST_END; interact++ ) {
00167     if ( interact->id == SASL_CB_AUTHNAME ||
00168          interact->id == SASL_CB_PASS ) {
00169 
00170       if ( ai.username.isEmpty() || ai.password.isEmpty() ) {
00171         if (!slave->openPasswordDialog(ai))
00172           return false;
00173       }
00174       break;
00175     }
00176   }
00177 
00178   interact = ( sasl_interact_t * ) in;
00179   while( interact->id != SASL_CB_LIST_END ) {
00180     kDebug(7116) <<"SASL_INTERACT id:" << interact->id;
00181     switch( interact->id ) {
00182       case SASL_CB_USER:
00183       case SASL_CB_AUTHNAME:
00184         kDebug(7116) <<"SASL_CB_[USER|AUTHNAME]: '" << ai.username <<"'";
00185         interact->result = strdup( ai.username.toUtf8() );
00186         interact->len = strlen( (const char *) interact->result );
00187         break;
00188       case SASL_CB_PASS:
00189         kDebug(7116) <<"SASL_CB_PASS: [hidden]";
00190         interact->result = strdup( ai.password.toUtf8() );
00191         interact->len = strlen( (const char *) interact->result );
00192         break;
00193       default:
00194         interact->result = 0;
00195         interact->len = 0;
00196         break;
00197     }
00198     interact++;
00199   }
00200   return true;
00201 }
00202 
00203 bool
00204 imapParser::clientAuthenticate ( KIO::SlaveBase *slave, KIO::AuthInfo &ai,
00205   const QString & aFQDN, const QString & aAuth, bool isSSL, QString & resultInfo)
00206 {
00207   bool retVal = false;
00208   int result;
00209   sasl_conn_t *conn = 0;
00210   sasl_interact_t *client_interact = 0;
00211   const char *out = 0;
00212   uint outlen = 0;
00213   const char *mechusing = 0;
00214   QByteArray tmp, challenge;
00215 
00216   kDebug(7116) <<"aAuth:" << aAuth <<" FQDN:" << aFQDN <<" isSSL:" << isSSL;
00217 
00218   // see if server supports this authenticator
00219   if (!hasCapability ("AUTH=" + aAuth))
00220     return false;
00221 
00222 //  result = sasl_client_new( isSSL ? "imaps" : "imap",
00223   result = sasl_client_new( "imap", /* FIXME: with cyrus-imapd, even imaps' digest-uri
00224                                        must be 'imap'. I don't know if it's good or bad. */
00225                        aFQDN.toLatin1(),
00226                        0, 0, callbacks, 0, &conn );
00227 
00228   if ( result != SASL_OK ) {
00229     kDebug(7116) <<"sasl_client_new failed with:" << result;
00230     resultInfo = QString::fromUtf8( sasl_errdetail( conn ) );
00231     return false;
00232   }
00233 
00234   do {
00235     result = sasl_client_start(conn, aAuth.toLatin1(), &client_interact,
00236                        hasCapability("SASL-IR") ? &out : 0, &outlen, &mechusing);
00237 
00238     if ( result == SASL_INTERACT ) {
00239       if ( !sasl_interact( slave, ai, client_interact ) ) {
00240         sasl_dispose( &conn );
00241         return false;
00242       }
00243     }
00244   } while ( result == SASL_INTERACT );
00245 
00246   if ( result != SASL_CONTINUE && result != SASL_OK ) {
00247     kDebug(7116) <<"sasl_client_start failed with:" << result;
00248     resultInfo = QString::fromUtf8( sasl_errdetail( conn ) );
00249     sasl_dispose( &conn );
00250     return false;
00251   }
00252   CommandPtr cmd;
00253 
00254   tmp = QByteArray::fromRawData( out, outlen );
00255   challenge = tmp.toBase64();
00256   tmp.clear();
00257   // then lets try it
00258   QString firstCommand = aAuth;
00259   if ( !challenge.isEmpty() ) {
00260     firstCommand += ' ';
00261     firstCommand += QString::fromLatin1( challenge.data(), challenge.size() );
00262   }
00263   cmd = sendCommand (CommandPtr(new imapCommand ("AUTHENTICATE", firstCommand.toLatin1())));
00264 
00265   while ( true )
00266   {
00267     //read the next line
00268     while (parseLoop() == 0) {
00269       ;
00270     }
00271     if ( cmd->isComplete() ) break;
00272 
00273     if (!continuation.isEmpty())
00274     {
00275 //      kDebug(7116) <<"S:" << QCString(continuation.data(),continuation.size()+1);
00276       if ( continuation.size() > 4 ) {
00277         tmp = QByteArray::fromRawData( continuation.data() + 2, continuation.size() - 4 );
00278         challenge = QByteArray::fromBase64( tmp );
00279 //        kDebug(7116) <<"S-1:" << QCString(challenge.data(),challenge.size()+1);
00280         tmp.clear();
00281       }
00282 
00283       do {
00284         result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(),
00285                                   challenge.size(),
00286                                   &client_interact,
00287                                   &out, &outlen);
00288 
00289         if (result == SASL_INTERACT) {
00290           if ( !sasl_interact( slave, ai, client_interact ) ) {
00291             sasl_dispose( &conn );
00292             return false;
00293           }
00294         }
00295       } while ( result == SASL_INTERACT );
00296 
00297       if ( result != SASL_CONTINUE && result != SASL_OK ) {
00298         kDebug(7116) <<"sasl_client_step failed with:" << result;
00299         resultInfo = QString::fromUtf8( sasl_errdetail( conn ) );
00300         sasl_dispose( &conn );
00301         return false;
00302       }
00303 
00304       tmp = QByteArray::fromRawData( out, outlen );
00305 //      kDebug(7116) <<"C-1:" << QCString(tmp.data(),tmp.size()+1);
00306       challenge = tmp.toBase64();
00307       tmp.clear();
00308 //      kDebug(7116) <<"C:" << QCString(challenge.data(),challenge.size()+1);
00309       parseWriteLine (challenge);
00310       continuation.resize(0);
00311     }
00312   }
00313 
00314   if (cmd->result () == "OK")
00315   {
00316     currentState = ISTATE_LOGIN;
00317     retVal = true;
00318   }
00319   resultInfo = cmd->resultInfo();
00320   completeQueue.removeAll (cmd);
00321 
00322   sasl_dispose( &conn ); //we don't use sasl_en/decode(), so it's safe to dispose the connection.
00323   return retVal;
00324 }
00325 
00326 void
00327 imapParser::parseUntagged (parseString & result)
00328 {
00329   //kDebug(7116) <<"imapParser::parseUntagged - '" << result.cstr() <<"'";
00330 
00331   parseOneWord(result);        // *
00332   QByteArray what = parseLiteral (result); // see whats coming next
00333 
00334   switch (what[0])
00335   {
00336     //the status responses
00337   case 'B':                    // BAD or BYE
00338     if (qstrncmp(what, "BAD", what.size()) == 0)
00339     {
00340       parseResult (what, result);
00341     }
00342     else if (qstrncmp(what, "BYE", what.size()) == 0)
00343     {
00344       parseResult (what, result);
00345       if ( sentQueue.count() ) {
00346         // BYE that interrupts a command -> copy the reason for it
00347         CommandPtr current = sentQueue.at (0);
00348         current->setResultInfo(result.cstr());
00349       }
00350       currentState = ISTATE_NO;
00351     }
00352     break;
00353 
00354   case 'N':                    // NO
00355     if (what[1] == 'O' && what.size() == 2)
00356     {
00357       parseResult (what, result);
00358     }
00359     else if (qstrncmp(what, "NAMESPACE", what.size()) == 0)
00360     {
00361       parseNamespace (result);
00362     }
00363     break;
00364 
00365   case 'O':                    // OK
00366     if (what[1] == 'K' && what.size() == 2)
00367     {
00368       parseResult (what, result);
00369     } else if (qstrncmp(what, "OTHER-USER", 10) == 0) { // X-GET-OTHER-USER
00370       parseOtherUser (result);
00371     } else if (qstrncmp(what, "OUT-OF-OFFICE", 13) == 0) { // X-GET-OUT-OF-OFFICE
00372       parseOutOfOffice (result);
00373     }
00374     break;
00375   case 'D':
00376     if (qstrncmp(what, "DELEGATE", 8) == 0) { // X-GET-DELEGATES
00377       parseDelegate (result);
00378     }
00379     break;
00380 
00381   case 'P':                    // PREAUTH
00382     if (qstrncmp(what, "PREAUTH", what.size()) == 0)
00383     {
00384       parseResult (what, result);
00385       currentState = ISTATE_LOGIN;
00386     }
00387     break;
00388 
00389     // parse the other responses
00390   case 'C':                    // CAPABILITY
00391     if (qstrncmp(what, "CAPABILITY", what.size()) == 0)
00392     {
00393       parseCapability (result);
00394     }
00395     break;
00396 
00397   case 'F':                    // FLAGS
00398     if (qstrncmp(what, "FLAGS", what.size()) == 0)
00399     {
00400       parseFlags (result);
00401     }
00402     break;
00403 
00404   case 'L':                    // LIST or LSUB or LISTRIGHTS
00405     if (qstrncmp(what, "LIST", what.size()) == 0)
00406     {
00407       parseList (result);
00408     }
00409     else if (qstrncmp(what, "LSUB", what.size()) == 0)
00410     {
00411       parseLsub (result);
00412     }
00413     else if (qstrncmp(what, "LISTRIGHTS", what.size()) == 0)
00414     {
00415       parseListRights (result);
00416     }
00417     break;
00418 
00419   case 'M': // MYRIGHTS
00420     if (qstrncmp(what, "MYRIGHTS", what.size()) == 0)
00421     {
00422       parseMyRights (result);
00423     }
00424     break;
00425   case 'S':                    // SEARCH or STATUS
00426     if (qstrncmp(what, "SEARCH", what.size()) == 0)
00427     {
00428       parseSearch (result);
00429     }
00430     else if (qstrncmp(what, "STATUS", what.size()) == 0)
00431     {
00432       parseStatus (result);
00433     }
00434     break;
00435 
00436   case 'A': // ACL or ANNOTATION
00437     if (qstrncmp(what, "ACL", what.size()) == 0)
00438     {
00439       parseAcl (result);
00440     }
00441     else if (qstrncmp(what, "ANNOTATION", what.size()) == 0)
00442     {
00443       parseAnnotation (result);
00444     }
00445     break;
00446   case 'Q': // QUOTA or QUOTAROOT
00447     if ( what.size() > 5 && qstrncmp(what, "QUOTAROOT", what.size()) == 0)
00448     {
00449       parseQuotaRoot( result );
00450     }
00451     else if (qstrncmp(what, "QUOTA", what.size()) == 0)
00452     {
00453       parseQuota( result );
00454     }
00455     break;
00456   case 'X': // Custom command
00457     {
00458       parseCustom( result );
00459     }
00460     break;
00461   default:
00462     //better be a number
00463     {
00464       ulong number;
00465       bool valid;
00466 
00467       number = what.toUInt(&valid);
00468       if (valid)
00469       {
00470         what = parseLiteral (result);
00471         switch (what[0])
00472         {
00473         case 'E':
00474           if (qstrncmp(what, "EXISTS", what.size()) == 0)
00475           {
00476             parseExists (number, result);
00477           }
00478           else if (qstrncmp(what, "EXPUNGE", what.size()) == 0)
00479           {
00480             parseExpunge (number, result);
00481           }
00482           break;
00483 
00484         case 'F':
00485           if (qstrncmp(what, "FETCH", what.size()) == 0)
00486           {
00487             seenUid.clear();
00488             parseFetch (number, result);
00489           }
00490           break;
00491 
00492         case 'S':
00493           if (qstrncmp(what, "STORE", what.size()) == 0)  // deprecated store
00494           {
00495             seenUid.clear();
00496             parseFetch (number, result);
00497           }
00498           break;
00499 
00500         case 'R':
00501           if (qstrncmp(what, "RECENT", what.size()) == 0)
00502           {
00503             parseRecent (number, result);
00504           }
00505           break;
00506         default:
00507           break;
00508         }
00509       }
00510     }
00511     break;
00512   }                             //switch
00513 }                               //func
00514 
00515 
00516 void
00517 imapParser::parseResult (QByteArray & result, parseString & rest,
00518   const QString & command)
00519 {
00520   if (command == "SELECT")
00521     selectInfo.setReadWrite(true);
00522 
00523   if (rest[0] == '[')
00524   {
00525     rest.pos++;
00526     QByteArray option = parseOneWord(rest, true);
00527 
00528     switch (option[0])
00529     {
00530     case 'A':                  // ALERT
00531       if (option == "ALERT")
00532       {
00533         rest.pos = rest.data.indexOf(']', rest.pos) + 1;
00534         // The alert text is after [ALERT].
00535         // Is this correct or do we need to care about litterals?
00536         selectInfo.setAlert( rest.cstr() );
00537       }
00538       break;
00539 
00540     case 'N':                  // NEWNAME
00541       if (option == "NEWNAME")
00542       {
00543       }
00544       break;
00545 
00546     case 'P':                  //PARSE or PERMANENTFLAGS
00547       if (option == "PARSE")
00548       {
00549       }
00550       else if (option == "PERMANENTFLAGS")
00551       {
00552         uint end = rest.data.indexOf(']', rest.pos);
00553         QByteArray flags(rest.data.data() + rest.pos, end - rest.pos);
00554         selectInfo.setPermanentFlags (flags);
00555         rest.pos = end;
00556       }
00557       break;
00558 
00559     case 'R':                  //READ-ONLY or READ-WRITE
00560       if (option == "READ-ONLY")
00561       {
00562         selectInfo.setReadWrite (false);
00563       }
00564       else if (option == "READ-WRITE")
00565       {
00566         selectInfo.setReadWrite (true);
00567       }
00568       break;
00569 
00570     case 'T':                  //TRYCREATE
00571       if (option == "TRYCREATE")
00572       {
00573       }
00574       break;
00575 
00576     case 'U':                  //UIDVALIDITY or UNSEEN
00577       if (option == "UIDVALIDITY")
00578       {
00579         ulong value;
00580         if (parseOneNumber (rest, value))
00581           selectInfo.setUidValidity (value);
00582       }
00583       else if (option == "UNSEEN")
00584       {
00585         ulong value;
00586         if (parseOneNumber (rest, value))
00587           selectInfo.setUnseen (value);
00588       }
00589       else if (option == "UIDNEXT")
00590       {
00591         ulong value;
00592         if (parseOneNumber (rest, value))
00593           selectInfo.setUidNext (value);
00594       }
00595       else
00596       break;
00597 
00598     }
00599     if (rest[0] == ']')
00600       rest.pos++; //tie off ]
00601     skipWS (rest);
00602   }
00603 
00604   if (command.isEmpty())
00605   {
00606     // This happens when parsing an intermediate result line (those that start with '*').
00607     // No state change involved, so we can stop here.
00608     return;
00609   }
00610 
00611   switch (command[0].toLatin1 ())
00612   {
00613   case 'A':
00614     if (command == "AUTHENTICATE")
00615       if (qstrncmp(result, "OK", result.size()) == 0)
00616         currentState = ISTATE_LOGIN;
00617     break;
00618 
00619   case 'L':
00620     if (command == "LOGIN")
00621       if (qstrncmp(result, "OK", result.size()) == 0)
00622         currentState = ISTATE_LOGIN;
00623     break;
00624 
00625   case 'E':
00626     if (command == "EXAMINE")
00627     {
00628       if (qstrncmp(result, "OK", result.size()) == 0)
00629         currentState = ISTATE_SELECT;
00630       else
00631       {
00632         if (currentState == ISTATE_SELECT)
00633           currentState = ISTATE_LOGIN;
00634         currentBox.clear();
00635       }
00636       kDebug(7116) <<"imapParser::parseResult - current box is now" << currentBox;
00637     }
00638     break;
00639 
00640   case 'S':
00641     if (command == "SELECT")
00642     {
00643       if (qstrncmp(result, "OK", result.size()) == 0)
00644         currentState = ISTATE_SELECT;
00645       else
00646       {
00647         if (currentState == ISTATE_SELECT)
00648           currentState = ISTATE_LOGIN;
00649         currentBox.clear();
00650       }
00651       kDebug(7116) <<"imapParser::parseResult - current box is now" << currentBox;
00652     }
00653     break;
00654 
00655   default:
00656     break;
00657   }
00658 
00659 }
00660 
00661 void imapParser::parseCapability (parseString & result)
00662 {
00663   QByteArray data = result.cstr();
00664   kAsciiToLower( data.data() );
00665   imapCapabilities = QString::fromLatin1(data).split ( ' ', QString::SkipEmptyParts );
00666 }
00667 
00668 void imapParser::parseFlags (parseString & result)
00669 {
00670   selectInfo.setFlags(result.cstr());
00671 }
00672 
00673 void imapParser::parseList (parseString & result)
00674 {
00675   imapList this_one;
00676 
00677   if (result[0] != '(')
00678     return;                     //not proper format for us
00679 
00680   result.pos++; // tie off (
00681 
00682   this_one.parseAttributes( result );
00683 
00684   result.pos++; // tie off )
00685   skipWS (result);
00686 
00687   this_one.setHierarchyDelimiter(parseLiteral(result));
00688   this_one.setName(QString::fromUtf8(KIMAP::decodeImapFolderName( parseLiteral(result))));  // decode modified UTF7
00689 
00690   listResponses.append (this_one);
00691 }
00692 
00693 void imapParser::parseLsub (parseString & result)
00694 {
00695   imapList this_one (result.cstr(), *this);
00696   listResponses.append (this_one);
00697 }
00698 
00699 void imapParser::parseListRights (parseString & result)
00700 {
00701   parseOneWord (result); // skip mailbox name
00702   parseOneWord (result); // skip user id
00703   while ( true ) {
00704     const QByteArray word = parseOneWord (result);
00705     if ( word.isEmpty() )
00706       break;
00707     lastResults.append (word);
00708   }
00709 }
00710 
00711 void imapParser::parseAcl (parseString & result)
00712 {
00713   parseOneWord (result); // skip mailbox name
00714   // The result is user1 perm1 user2 perm2 etc. The caller will sort it out.
00715   while ( !result.isEmpty() ) {
00716     const QByteArray word = parseLiteral(result);
00717     if ( word.isEmpty() )
00718       break;
00719     lastResults.append (word);
00720   }
00721 }
00722 
00723 void imapParser::parseAnnotation (parseString & result)
00724 {
00725   parseOneWord (result); // skip mailbox name
00726   skipWS (result);
00727   parseOneWord (result); // skip entry name (we know it since we don't allow wildcards in it)
00728   skipWS (result);
00729   if (result.isEmpty() || result[0] != '(')
00730     return;
00731   result.pos++;
00732   skipWS (result);
00733   // The result is name1 value1 name2 value2 etc. The caller will sort it out.
00734   while ( !result.isEmpty() && result[0] != ')' ) {
00735     const QByteArray word = parseLiteral (result);
00736     if ( word.isEmpty() )
00737       break;
00738     lastResults.append (word);
00739   }
00740 }
00741 
00742 
00743 void imapParser::parseQuota (parseString & result)
00744 {
00745   // quota_response  ::= "QUOTA" SP astring SP quota_list
00746   // quota_list      ::= "(" #quota_resource ")"
00747   // quota_resource  ::= atom SP number SP number
00748   QByteArray root = parseOneWord( result );
00749   if ( root.isEmpty() ) {
00750     lastResults.append( "" );
00751   } else {
00752     lastResults.append( root );
00753   }
00754   if (result.isEmpty() || result[0] != '(')
00755     return;
00756   result.pos++;
00757   skipWS (result);
00758   QStringList triplet;
00759   while ( !result.isEmpty() && result[0] != ')' ) {
00760     const QByteArray word = parseLiteral(result);
00761     if ( word.isEmpty() )
00762       break;
00763     triplet.append(word);
00764   }
00765   lastResults.append( triplet.join(" ") );
00766 }
00767 
00768 void imapParser::parseQuotaRoot (parseString & result)
00769 {
00770   //    quotaroot_response
00771   //         ::= "QUOTAROOT" SP astring *(SP astring)
00772   parseOneWord (result); // skip mailbox name
00773   skipWS (result);
00774   if ( result.isEmpty() )
00775     return;
00776   QStringList roots;
00777   while ( !result.isEmpty() ) {
00778     const QByteArray word = parseLiteral (result);
00779     if ( word.isEmpty() )
00780       break;
00781     roots.append (word);
00782   }
00783   lastResults.append( roots.isEmpty() ? "" : roots.join( " " ) );
00784 }
00785 
00786 void imapParser::parseCustom (parseString & result)
00787 {
00788   QByteArray word = parseLiteral (result, false, false);
00789   lastResults.append( word );
00790 }
00791 
00792 void imapParser::parseOtherUser (parseString & result)
00793 {
00794   lastResults.append( parseOneWord ( result ) );
00795 }
00796 
00797 void imapParser::parseDelegate (parseString & result)
00798 {
00799   const QString email = parseOneWord ( result );
00800 
00801   QStringList rights;
00802   while ( !result.isEmpty() ) {
00803     QByteArray word = parseLiteral ( result, false, false );
00804     rights.append( word );
00805   }
00806 
00807   lastResults.append( email + ':' + rights.join( "," ) );
00808 }
00809 
00810 void imapParser::parseOutOfOffice (parseString & result)
00811 {
00812   const QString state = parseOneWord (result);
00813   parseOneWord (result); // skip encoding
00814 
00815   QByteArray msg = parseLiteral (result, false, false);
00816 
00817   lastResults.append( state + '^' + QString::fromUtf8( msg ) );
00818 }
00819 
00820 void imapParser::parseMyRights (parseString & result)
00821 {
00822   parseOneWord (result); // skip mailbox name
00823   Q_ASSERT( lastResults.isEmpty() ); // we can only be called once
00824   lastResults.append (parseOneWord (result) );
00825 }
00826 
00827 void imapParser::parseSearch (parseString & result)
00828 {
00829   ulong value;
00830 
00831   while (parseOneNumber (result, value))
00832   {
00833     lastResults.append (QString::number(value));
00834   }
00835 }
00836 
00837 void imapParser::parseStatus (parseString & inWords)
00838 {
00839   lastStatus = imapInfo ();
00840 
00841   parseLiteral(inWords);       // swallow the box
00842   if (inWords[0] != '(')
00843     return;
00844 
00845   inWords.pos++;
00846   skipWS (inWords);
00847 
00848   while (!inWords.isEmpty() && inWords[0] != ')')
00849   {
00850     ulong value;
00851 
00852     QByteArray label = parseOneWord(inWords);
00853     if (parseOneNumber (inWords, value))
00854     {
00855       if (label == "MESSAGES")
00856         lastStatus.setCount (value);
00857       else if (label == "RECENT")
00858         lastStatus.setRecent (value);
00859       else if (label == "UIDVALIDITY")
00860         lastStatus.setUidValidity (value);
00861       else if (label == "UNSEEN")
00862         lastStatus.setUnseen (value);
00863       else if (label == "UIDNEXT")
00864         lastStatus.setUidNext (value);
00865     }
00866   }
00867 
00868   if (inWords[0] == ')')
00869     inWords.pos++;
00870   skipWS (inWords);
00871 }
00872 
00873 void imapParser::parseExists (ulong value, parseString & result)
00874 {
00875   selectInfo.setCount (value);
00876   result.pos = result.data.size();
00877 }
00878 
00879 void imapParser::parseExpunge (ulong value, parseString & result)
00880 {
00881   Q_UNUSED(value);
00882   Q_UNUSED(result);
00883 }
00884 
00885 void imapParser::parseAddressList (parseString & inWords, QList<mailAddress *>& list)
00886 {
00887   if ( inWords.isEmpty() )
00888     return;
00889   if (inWords[0] != '(')
00890   {
00891     parseOneWord (inWords);     // parse NIL
00892   }
00893   else
00894   {
00895     inWords.pos++;
00896     skipWS (inWords);
00897 
00898     while (!inWords.isEmpty () && inWords[0] != ')')
00899     {
00900       if (inWords[0] == '(') {
00901         mailAddress *addr = new mailAddress;
00902         parseAddress(inWords, *addr);
00903         list.append(addr);
00904       } else {
00905         break;
00906       }
00907     }
00908 
00909     if (!inWords.isEmpty() && inWords[0] == ')')
00910       inWords.pos++;
00911     skipWS (inWords);
00912   }
00913 }
00914 
00915 const mailAddress& imapParser::parseAddress (parseString & inWords, mailAddress& retVal)
00916 {
00917   inWords.pos++;
00918   skipWS (inWords);
00919 
00920   retVal.setFullName(parseLiteral(inWords));
00921   retVal.setCommentRaw(parseLiteral(inWords));
00922   retVal.setUser(parseLiteral(inWords));
00923   retVal.setHost(parseLiteral(inWords));
00924 
00925   if (!inWords.isEmpty() && inWords[0] == ')')
00926     inWords.pos++;
00927   skipWS (inWords);
00928 
00929   return retVal;
00930 }
00931 
00932 mailHeader * imapParser::parseEnvelope (parseString & inWords)
00933 {
00934   mailHeader *envelope = 0;
00935 
00936   if (inWords[0] != '(')
00937     return envelope;
00938   inWords.pos++;
00939   skipWS (inWords);
00940 
00941   envelope = new mailHeader;
00942 
00943   //date
00944   envelope->setDate(parseLiteral(inWords));
00945 
00946   //subject
00947   envelope->setSubject(parseLiteral(inWords));
00948 
00949   QList<mailAddress *> list;
00950 
00951   //from
00952   parseAddressList(inWords, list);
00953   if (!list.isEmpty()) {
00954       envelope->setFrom(*list.last());
00955       list.clear();
00956   }
00957 
00958   //sender
00959   parseAddressList(inWords, list);
00960   if (!list.isEmpty()) {
00961       envelope->setSender(*list.last());
00962       list.clear();
00963   }
00964 
00965   //reply-to
00966   parseAddressList(inWords, list);
00967   if (!list.isEmpty()) {
00968       envelope->setReplyTo(*list.last());
00969       list.clear();
00970   }
00971 
00972   //to
00973   parseAddressList (inWords, envelope->to());
00974 
00975   //cc
00976   parseAddressList (inWords, envelope->cc());
00977 
00978   //bcc
00979   parseAddressList (inWords, envelope->bcc());
00980 
00981   //in-reply-to
00982   envelope->setInReplyTo(parseLiteral(inWords));
00983 
00984   //message-id
00985   envelope->setMessageId(parseLiteral(inWords));
00986 
00987   // see if we have more to come
00988   while (!inWords.isEmpty () && inWords[0] != ')')
00989   {
00990     //eat the extensions to this part
00991     if (inWords[0] == '(')
00992       parseSentence (inWords);
00993     else
00994       parseLiteral (inWords);
00995   }
00996 
00997   if (!inWords.isEmpty() && inWords[0] == ')')
00998     inWords.pos++;
00999   skipWS (inWords);
01000 
01001   return envelope;
01002 }
01003 
01004 // parse parameter pairs into a dictionary
01005 // caller must clean up the dictionary items
01006 QHash < QByteArray, QString > imapParser::parseDisposition (parseString & inWords)
01007 {
01008   QByteArray disposition;
01009   QHash < QByteArray, QString > retVal;
01010 
01011   if (inWords[0] != '(')
01012   {
01013     //disposition only
01014     disposition = parseOneWord (inWords);
01015   }
01016   else
01017   {
01018     inWords.pos++;
01019     skipWS (inWords);
01020 
01021     //disposition
01022     disposition = parseOneWord (inWords);
01023 
01024     retVal = parseParameters (inWords);
01025     if (inWords[0] != ')')
01026       return retVal;
01027     inWords.pos++;
01028     skipWS (inWords);
01029   }
01030 
01031   if (!disposition.isEmpty ())
01032   {
01033     retVal.insert ("content-disposition", QString(disposition));
01034   }
01035 
01036   return retVal;
01037 }
01038 
01039 // parse parameter pairs into a dictionary
01040 // caller must clean up the dictionary items
01041 QHash < QByteArray, QString > imapParser::parseParameters (parseString & inWords)
01042 {
01043   QHash < QByteArray, QString > retVal;
01044 
01045   if (inWords[0] != '(')
01046   {
01047     //better be NIL
01048     parseOneWord (inWords);
01049   }
01050   else
01051   {
01052     inWords.pos++;
01053     skipWS (inWords);
01054 
01055     while (!inWords.isEmpty () && inWords[0] != ')')
01056     {
01057       const QByteArray l1 = parseLiteral(inWords);
01058       const QByteArray l2 = parseLiteral(inWords);
01059       retVal.insert (l1.toLower(), QString(l2));
01060     }
01061 
01062     if (inWords[0] != ')')
01063       return retVal;
01064     inWords.pos++;
01065     skipWS (inWords);
01066   }
01067 
01068   return retVal;
01069 }
01070 
01071 mimeHeader * imapParser::parseSimplePart (parseString & inWords,
01072   QString & inSection, mimeHeader * localPart)
01073 {
01074   QByteArray subtype;
01075   QByteArray typeStr;
01076   QHash < QByteArray, QString > parameters;
01077   ulong size;
01078 
01079   if (inWords[0] != '(')
01080     return 0;
01081 
01082   if (!localPart)
01083     localPart = new mimeHeader;
01084 
01085   localPart->setPartSpecifier (inSection);
01086 
01087   inWords.pos++;
01088   skipWS (inWords);
01089 
01090   //body type
01091   typeStr = parseLiteral(inWords);
01092 
01093   //body subtype
01094   subtype = parseLiteral(inWords);
01095 
01096   localPart->setType (typeStr + '/' + subtype);
01097 
01098   //body parameter parenthesized list
01099   parameters = parseParameters (inWords);
01100   {
01101     QHashIterator < QByteArray, QString > it (parameters);
01102 
01103     while (it.hasNext ())
01104     {
01105       it.next();
01106       localPart->setTypeParm (it.key (), it.value ());
01107     }
01108     parameters.clear ();
01109   }
01110 
01111   //body id
01112   localPart->setID (parseLiteral(inWords));
01113 
01114   //body description
01115   localPart->setDescription (parseLiteral(inWords));
01116 
01117   //body encoding
01118   localPart->setEncoding (parseLiteral(inWords));
01119 
01120   //body size
01121   if (parseOneNumber (inWords, size))
01122     localPart->setLength (size);
01123 
01124   // type specific extensions
01125   if (localPart->getType().toUpper() == "MESSAGE/RFC822")
01126   {
01127     //envelope structure
01128     mailHeader *envelope = parseEnvelope (inWords);
01129 
01130     //body structure
01131     parseBodyStructure (inWords, inSection, envelope);
01132 
01133     localPart->setNestedMessage (envelope);
01134 
01135     //text lines
01136     ulong lines;
01137     parseOneNumber (inWords, lines);
01138   }
01139   else
01140   {
01141     if (typeStr ==  "TEXT")
01142     {
01143       //text lines
01144       ulong lines;
01145       parseOneNumber (inWords, lines);
01146     }
01147 
01148     // md5
01149     parseLiteral(inWords);
01150 
01151     // body disposition
01152     parameters = parseDisposition (inWords);
01153     {
01154       QString disposition = parameters["content-disposition"];
01155 
01156       localPart->setDisposition (disposition.toAscii ());
01157       QHashIterator < QByteArray, QString > it (parameters);
01158       while (it.hasNext ())
01159       {
01160         it.next();
01161         localPart->setDispositionParm (it.key (), it.value ());
01162       }
01163       parameters.clear ();
01164     }
01165 
01166     // body language
01167     parseSentence (inWords);
01168   }
01169 
01170   // see if we have more to come
01171   while (!inWords.isEmpty () && inWords[0] != ')')
01172   {
01173     //eat the extensions to this part
01174     if (inWords[0] == '(')
01175       parseSentence (inWords);
01176     else
01177       parseLiteral(inWords);
01178   }
01179 
01180   if (inWords[0] == ')')
01181     inWords.pos++;
01182   skipWS (inWords);
01183 
01184   return localPart;
01185 }
01186 
01187 mimeHeader * imapParser::parseBodyStructure (parseString & inWords,
01188   QString & inSection, mimeHeader * localPart)
01189 {
01190   bool init = false;
01191   if (inSection.isEmpty())
01192   {
01193     // first run
01194     init = true;
01195     // assume one part
01196     inSection = "1";
01197   }
01198   int section = 0;
01199 
01200   if (inWords[0] != '(')
01201   {
01202     // skip ""
01203     parseOneWord (inWords);
01204     return 0;
01205   }
01206   inWords.pos++;
01207   skipWS (inWords);
01208 
01209   if (inWords[0] == '(')
01210   {
01211     QByteArray subtype;
01212     QHash< QByteArray, QString > parameters;
01213     QString outSection;
01214 
01215     if (!localPart)
01216       localPart = new mimeHeader;
01217     else
01218     {
01219       // might be filled from an earlier run
01220       localPart->clearNestedParts ();
01221       localPart->clearTypeParameters ();
01222       localPart->clearDispositionParameters ();
01223       // an envelope was passed in so this is the multipart header
01224       outSection = inSection + ".HEADER";
01225     }
01226     if (inWords[0] == '(' && init)
01227       inSection = "0";
01228 
01229     // set the section
01230     if ( !outSection.isEmpty() ) {
01231       localPart->setPartSpecifier(outSection);
01232     } else {
01233       localPart->setPartSpecifier(inSection);
01234     }
01235 
01236     // is multipart (otherwise it is a simplepart and handled later)
01237     while (inWords[0] == '(')
01238     {
01239       outSection = QString::number(++section);
01240       if (!init)
01241         outSection = inSection + '.' + outSection;
01242       mimeHeader *subpart = parseBodyStructure (inWords, outSection, 0);
01243       localPart->addNestedPart (subpart);
01244     }
01245 
01246     // fetch subtype
01247     subtype = parseOneWord (inWords);
01248 
01249     localPart->setType ("MULTIPART/" + subtype);
01250 
01251     // fetch parameters
01252     parameters = parseParameters (inWords);
01253     {
01254       QHashIterator < QByteArray, QString > it (parameters);
01255 
01256       while (it.hasNext ())
01257       {
01258         it.next();
01259         localPart->setTypeParm (it.key (), it.value ());
01260       }
01261       parameters.clear ();
01262     }
01263 
01264     // body disposition
01265     parameters = parseDisposition (inWords);
01266     {
01267       QString disposition = parameters["content-disposition"];
01268 
01269       localPart->setDisposition (disposition.toAscii ());
01270       QHashIterator < QByteArray, QString > it (parameters);
01271       while (it.hasNext ())
01272       {
01273         it.next();
01274         localPart->setDispositionParm (it.key (), it.value ());
01275       }
01276       parameters.clear ();
01277     }
01278 
01279     // body language
01280     parseSentence (inWords);
01281 
01282   }
01283   else
01284   {
01285     // is simple part
01286     inWords.pos--;
01287     inWords.data[inWords.pos] = '('; //fake a sentence
01288     if ( localPart )
01289       inSection = inSection + ".1";
01290     localPart = parseSimplePart (inWords, inSection, localPart);
01291     inWords.pos--;
01292     inWords.data[inWords.pos] = ')'; //remove fake
01293   }
01294 
01295   // see if we have more to come
01296   while (!inWords.isEmpty () && inWords[0] != ')')
01297   {
01298     //eat the extensions to this part
01299     if (inWords[0] == '(')
01300       parseSentence (inWords);
01301     else
01302       parseLiteral(inWords);
01303   }
01304 
01305   if (inWords[0] == ')')
01306     inWords.pos++;
01307   skipWS (inWords);
01308 
01309   return localPart;
01310 }
01311 
01312 void imapParser::parseBody (parseString & inWords)
01313 {
01314   // see if we got a part specifier
01315   if (inWords[0] == '[')
01316   {
01317     QByteArray specifier;
01318     QByteArray label;
01319     inWords.pos++;
01320 
01321     specifier = parseOneWord (inWords, true);
01322 
01323     if (inWords[0] == '(')
01324     {
01325       inWords.pos++;
01326 
01327       while (!inWords.isEmpty () && inWords[0] != ')')
01328       {
01329         label = parseOneWord (inWords);
01330       }
01331 
01332       if (inWords[0] == ')')
01333         inWords.pos++;
01334     }
01335     if (inWords[0] == ']')
01336       inWords.pos++;
01337     skipWS (inWords);
01338 
01339     // parse the header
01340     if (qstrncmp(specifier, "0", specifier.size()) == 0)
01341     {
01342       mailHeader *envelope = 0;
01343       if (lastHandled)
01344         envelope = lastHandled->getHeader ();
01345 
01346       if (!envelope || seenUid.isEmpty ())
01347       {
01348         kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii ();
01349         // don't know where to put it, throw it away
01350         parseLiteral(inWords, true);
01351       }
01352       else
01353       {
01354         kDebug(7116) <<"imapParser::parseBody - reading" << envelope << seenUid.toAscii ();
01355         // fill it up with data
01356         QString theHeader = parseLiteral(inWords, true);
01357         mimeIOQString myIO;
01358 
01359         myIO.setString (theHeader);
01360         envelope->parseHeader (myIO);
01361 
01362       }
01363     }
01364     else if (qstrncmp(specifier, "HEADER.FIELDS", specifier.size()) == 0)
01365     {
01366       // BODY[HEADER.FIELDS (References)] {n}
01367       //kDebug(7116) <<"imapParser::parseBody - HEADER.FIELDS:"
01368       // << QCString(label.data(), label.size()+1);
01369       if (qstrncmp(label, "REFERENCES", label.size()) == 0)
01370       {
01371        mailHeader *envelope = 0;
01372        if (lastHandled)
01373          envelope = lastHandled->getHeader ();
01374 
01375        if (!envelope || seenUid.isEmpty ())
01376        {
01377          kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii ();
01378          // don't know where to put it, throw it away
01379          parseLiteral (inWords, true);
01380        }
01381        else
01382        {
01383          QByteArray references = parseLiteral(inWords, true);
01384          int start = references.indexOf ('<');
01385          int end = references.lastIndexOf ('>');
01386          if (start < end)
01387            references = references.mid (start, end - start + 1);
01388          envelope->setReferences(references.simplified());
01389        }
01390       }
01391       else
01392       { // not a header we care about throw it away
01393         parseLiteral(inWords, true);
01394       }
01395     }
01396     else
01397     {
01398       if (specifier.contains(".MIME") )
01399       {
01400         mailHeader *envelope = new mailHeader;
01401         QString theHeader = parseLiteral(inWords, false);
01402         mimeIOQString myIO;
01403         myIO.setString (theHeader);
01404         envelope->parseHeader (myIO);
01405         if (lastHandled)
01406           lastHandled->setHeader (envelope);
01407         return;
01408       }
01409       // throw it away
01410       kDebug(7116) <<"imapParser::parseBody - discarding" << seenUid.toAscii ();
01411       parseLiteral(inWords, true);
01412     }
01413 
01414   }
01415   else // no part specifier
01416   {
01417     mailHeader *envelope = 0;
01418     if (lastHandled)
01419       envelope = lastHandled->getHeader ();
01420 
01421     if (!envelope || seenUid.isEmpty ())
01422     {
01423       kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii ();
01424       // don't know where to put it, throw it away
01425       parseSentence (inWords);
01426     }
01427     else
01428     {
01429       kDebug(7116) <<"imapParser::parseBody - reading" << envelope << seenUid.toAscii ();
01430       // fill it up with data
01431       QString section;
01432       mimeHeader *body = parseBodyStructure (inWords, section, envelope);
01433       if (body != envelope)
01434         delete body;
01435     }
01436   }
01437 }
01438 
01439 void imapParser::parseFetch (ulong /* value */, parseString & inWords)
01440 {
01441   if (inWords[0] != '(')
01442     return;
01443   inWords.pos++;
01444   skipWS (inWords);
01445 
01446   delete lastHandled;
01447   lastHandled = 0;
01448 
01449   while (!inWords.isEmpty () && inWords[0] != ')')
01450   {
01451     if (inWords[0] == '(')
01452       parseSentence (inWords);
01453     else
01454     {
01455       const QByteArray word = parseLiteral(inWords, false, true);
01456 
01457       switch (word[0])
01458       {
01459       case 'E':
01460         if (word == "ENVELOPE")
01461         {
01462           mailHeader *envelope = 0;
01463 
01464           if (lastHandled)
01465             envelope = lastHandled->getHeader ();
01466           else
01467             lastHandled = new imapCache();
01468 
01469           if (envelope && !envelope->getMessageId ().isEmpty ())
01470           {
01471             // we have seen this one already
01472             // or don't know where to put it
01473             parseSentence (inWords);
01474           }
01475           else
01476           {
01477             envelope = parseEnvelope (inWords);
01478             if (envelope)
01479             {
01480               envelope->setPartSpecifier (seenUid + ".0");
01481               lastHandled->setHeader (envelope);
01482               lastHandled->setUid (seenUid.toULong ());
01483             }
01484           }
01485         }
01486         break;
01487 
01488       case 'B':
01489         if (word == "BODY")
01490         {
01491           parseBody (inWords);
01492         }
01493         else if (word == "BODY[]" )
01494         {
01495           // Do the same as with "RFC822"
01496           parseLiteral(inWords, true);
01497         }
01498         else if (word == "BODYSTRUCTURE")
01499         {
01500           mailHeader *envelope = 0;
01501 
01502           if (lastHandled)
01503             envelope = lastHandled->getHeader ();
01504 
01505           // fill it up with data
01506           QString section;
01507           mimeHeader *body =
01508             parseBodyStructure (inWords, section, envelope);
01509           QByteArray data;
01510           QDataStream stream( &data, QIODevice::WriteOnly );
01511           if ( body )
01512             body->serialize(stream);
01513           parseRelay(data);
01514 
01515           delete body;
01516         }
01517         break;
01518 
01519       case 'U':
01520         if (word == "UID")
01521         {
01522           seenUid = parseOneWord(inWords);
01523           mailHeader *envelope = 0;
01524           if (lastHandled)
01525             envelope = lastHandled->getHeader ();
01526           else
01527             lastHandled = new imapCache();
01528 
01529           if (seenUid.isEmpty ())
01530           {
01531             // unknown what to do
01532             kDebug(7116) <<"imapParser::parseFetch - UID empty";
01533           }
01534           else
01535           {
01536             lastHandled->setUid (seenUid.toULong ());
01537           }
01538           if (envelope)
01539             envelope->setPartSpecifier (seenUid);
01540         }
01541         break;
01542 
01543       case 'R':
01544         if (word == "RFC822.SIZE")
01545         {
01546           ulong size;
01547           parseOneNumber (inWords, size);
01548 
01549           if (!lastHandled) lastHandled = new imapCache();
01550           lastHandled->setSize (size);
01551         }
01552         else if (word.startsWith("RFC822"))
01553         {
01554           // might be RFC822 RFC822.TEXT RFC822.HEADER
01555           parseLiteral(inWords, true);
01556         }
01557         break;
01558 
01559       case 'I':
01560         if (word == "INTERNALDATE")
01561         {
01562           const QByteArray date = parseOneWord(inWords);
01563           if (!lastHandled) lastHandled = new imapCache();
01564           lastHandled->setDate(date);
01565         }
01566         break;
01567 
01568       case 'F':
01569         if (word == "FLAGS")
01570         {
01571       //kDebug(7116) <<"GOT FLAGS" << inWords.cstr();
01572           if (!lastHandled) lastHandled = new imapCache();
01573           lastHandled->setFlags (imapInfo::_flags (inWords.cstr()));
01574         }
01575         break;
01576 
01577       default:
01578         parseLiteral(inWords);
01579         break;
01580       }
01581     }
01582   }
01583 
01584   // see if we have more to come
01585   while (!inWords.isEmpty () && inWords[0] != ')')
01586   {
01587     //eat the extensions to this part
01588     if (inWords[0] == '(')
01589       parseSentence (inWords);
01590     else
01591       parseLiteral(inWords);
01592   }
01593 
01594   if (inWords.isEmpty() || inWords[0] != ')')
01595     return;
01596   inWords.pos++;
01597   skipWS (inWords);
01598 }
01599 
01600 
01601 // default parser
01602 void imapParser::parseSentence (parseString & inWords)
01603 {
01604   bool first = true;
01605   int stack = 0;
01606 
01607   //find the first nesting parentheses
01608 
01609   while (!inWords.isEmpty () && (stack != 0 || first))
01610   {
01611     first = false;
01612     skipWS (inWords);
01613 
01614     unsigned char ch = inWords[0];
01615     switch (ch)
01616     {
01617     case '(':
01618       inWords.pos++;
01619       ++stack;
01620       break;
01621     case ')':
01622       inWords.pos++;
01623       --stack;
01624       break;
01625     case '[':
01626       inWords.pos++;
01627       ++stack;
01628       break;
01629     case ']':
01630       inWords.pos++;
01631       --stack;
01632       break;
01633     default:
01634       parseLiteral(inWords);
01635       skipWS (inWords);
01636       break;
01637     }
01638   }
01639   skipWS (inWords);
01640 }
01641 
01642 void imapParser::parseRecent (ulong value, parseString & result)
01643 {
01644   selectInfo.setRecent (value);
01645   result.pos = result.data.size();
01646 }
01647 
01648 void imapParser::parseNamespace (parseString & result)
01649 {
01650   if ( result[0] != '(' )
01651     return;
01652 
01653   QString delimEmpty;
01654   if ( namespaceToDelimiter.contains( QString() ) )
01655     delimEmpty = namespaceToDelimiter[QString()];
01656 
01657   namespaceToDelimiter.clear();
01658   imapNamespaces.clear();
01659 
01660   // remember what section we're in (user, other users, shared)
01661   int ns = -1;
01662   bool personalAvailable = false;
01663   while ( !result.isEmpty() )
01664   {
01665     if ( result[0] == '(' )
01666     {
01667       result.pos++; // tie off (
01668       if ( result[0] == '(' )
01669       {
01670         // new namespace section
01671         result.pos++; // tie off (
01672         ++ns;
01673       }
01674       // namespace prefix
01675       QString prefix = QString::fromLatin1( parseOneWord( result ) );
01676       // delimiter
01677       QString delim = QString::fromLatin1( parseOneWord( result ) );
01678       kDebug(7116) <<"imapParser::parseNamespace ns='" << prefix <<"',delim='" << delim <<"'";
01679       if ( ns == 0 )
01680       {
01681         // at least one personal ns
01682         personalAvailable = true;
01683       }
01684       QString nsentry = QString::number( ns ) + '=' + prefix + '=' + delim;
01685       imapNamespaces.append( nsentry );
01686       if ( prefix.right( 1 ) == delim ) {
01687         // strip delimiter to get a correct entry for comparisons
01688         prefix.resize( prefix.length() );
01689       }
01690       namespaceToDelimiter[prefix] = delim;
01691 
01692       result.pos++; // tie off )
01693       skipWS( result );
01694     } else if ( result[0] == ')' )
01695     {
01696       result.pos++; // tie off )
01697       skipWS( result );
01698     } else if ( result[0] == 'N' )
01699     {
01700       // drop NIL
01701       ++ns;
01702       parseOneWord( result );
01703     } else {
01704       // drop whatever it is
01705       parseOneWord( result );
01706     }
01707   }
01708   if ( !delimEmpty.isEmpty() ) {
01709     // remember default delimiter
01710     namespaceToDelimiter[QString()] = delimEmpty;
01711     if ( !personalAvailable )
01712     {
01713       // at least one personal ns would be nice
01714       kDebug(7116) <<"imapParser::parseNamespace - registering own personal ns";
01715       QString nsentry = "0==" + delimEmpty;
01716       imapNamespaces.append( nsentry );
01717     }
01718   }
01719 }
01720 
01721 int imapParser::parseLoop ()
01722 {
01723   parseString result;
01724 
01725   if (!parseReadLine(result.data)) return -1;
01726 
01727   //kDebug(7116) << result.cstr(); // includes \n
01728 
01729   if (result.data.isEmpty())
01730     return 0;
01731   if (!sentQueue.count ())
01732   {
01733     // maybe greeting or BYE everything else SHOULD not happen, use NOOP or IDLE
01734     kDebug(7116) <<"imapParser::parseLoop - unhandledResponse:" << result.cstr();
01735     unhandled << result.cstr();
01736   }
01737   else
01738   {
01739     CommandPtr current = sentQueue.at (0);
01740     switch (result[0])
01741     {
01742     case '*':
01743       result.data.resize(result.data.size() - 2);  // tie off CRLF
01744       parseUntagged (result);
01745       break;
01746     case '+':
01747       continuation = result.data;
01748       break;
01749     default:
01750       {
01751         QByteArray tag = parseLiteral(result);
01752         if (current->id() == tag.data())
01753         {
01754           result.data.resize(result.data.size() - 2);  // tie off CRLF
01755           QByteArray resultCode = parseLiteral (result); //the result
01756           current->setResult (resultCode);
01757           current->setResultInfo(result.cstr());
01758           current->setComplete ();
01759 
01760           sentQueue.removeAll (current);
01761           completeQueue.append (current);
01762           if (result.length())
01763             parseResult (resultCode, result, current->command());
01764         }
01765         else
01766         {
01767           kDebug(7116) <<"imapParser::parseLoop - unknown tag '" << tag <<"'";
01768           QByteArray cstr = tag + ' ' + result.cstr();
01769           result.data = cstr;
01770           result.pos = 0;
01771           result.data.resize(cstr.length());
01772         }
01773       }
01774       break;
01775     }
01776   }
01777 
01778   return 1;
01779 }
01780 
01781 void
01782 imapParser::parseRelay (const QByteArray & buffer)
01783 {
01784   Q_UNUSED(buffer);
01785   qWarning
01786     ("imapParser::parseRelay - virtual function not reimplemented - data lost");
01787 }
01788 
01789 void
01790 imapParser::parseRelay (ulong len)
01791 {
01792   Q_UNUSED(len);
01793   qWarning
01794     ("imapParser::parseRelay - virtual function not reimplemented - announcement lost");
01795 }
01796 
01797 bool imapParser::parseRead (QByteArray & buffer, long len, long relay)
01798 {
01799   Q_UNUSED(buffer);
01800   Q_UNUSED(len);
01801   Q_UNUSED(relay);
01802   qWarning
01803     ("imapParser::parseRead - virtual function not reimplemented - no data read");
01804   return false;
01805 }
01806 
01807 bool imapParser::parseReadLine (QByteArray & buffer, long relay)
01808 {
01809   Q_UNUSED(buffer);
01810   Q_UNUSED(relay);
01811   qWarning
01812     ("imapParser::parseReadLine - virtual function not reimplemented - no data read");
01813   return false;
01814 }
01815 
01816 void
01817 imapParser::parseWriteLine (const QString & str)
01818 {
01819   Q_UNUSED(str);
01820   qWarning
01821     ("imapParser::parseWriteLine - virtual function not reimplemented - no data written");
01822 }
01823 
01824 void
01825 imapParser::parseURL (const KUrl & _url, QString & _box, QString & _section,
01826                       QString & _type, QString & _uid, QString & _validity, QString & _info)
01827 {
01828   QStringList parameters;
01829 
01830   _box = _url.path ();
01831   kDebug(7116) <<"imapParser::parseURL" << _box;
01832   int paramStart = _box.indexOf("/;");
01833   if ( paramStart > -1 )
01834   {
01835     QString paramString = _box.right( _box.length() - paramStart-2 );
01836     parameters = paramString.split (';', QString::SkipEmptyParts);  //split parameters
01837     _box.truncate( paramStart ); // strip parameters
01838   }
01839   // extract parameters
01840   for (QStringList::ConstIterator it (parameters.constBegin ());
01841        it != parameters.constEnd (); ++it)
01842   {
01843     QString temp = (*it);
01844 
01845     // if we have a '/' separator we'll just nuke it
01846     int pt = temp.indexOf ('/');
01847     if (pt > 0)
01848       temp.truncate(pt);
01849     if (temp.startsWith("section=", Qt::CaseInsensitive))
01850       _section = temp.right (temp.length () - 8);
01851     else if (temp.startsWith("type=", Qt::CaseInsensitive))
01852       _type = temp.right (temp.length () - 5);
01853     else if (temp.startsWith("uid=", Qt::CaseInsensitive))
01854       _uid = temp.right (temp.length () - 4);
01855     else if (temp.startsWith("uidvalidity=", Qt::CaseInsensitive))
01856       _validity = temp.right (temp.length () - 12);
01857     else if (temp.startsWith("info=", Qt::CaseInsensitive))
01858       _info = temp.right (temp.length () - 5);
01859   }
01860 //  kDebug(7116) <<"URL: section=" << _section <<", type=" << _type <<", uid=" << _uid;
01861 //  kDebug(7116) <<"URL: user()" << _url.user();
01862 //  kDebug(7116) <<"URL: path()" << _url.path();
01863 //  kDebug(7116) <<"URL: encodedPathAndQuery()" << _url.encodedPathAndQuery();
01864 
01865   if (!_box.isEmpty ())
01866   {
01867     // strip /
01868     if (_box[0] == '/')
01869       _box = _box.right (_box.length () - 1);
01870     if (!_box.isEmpty () && _box[_box.length () - 1] == '/')
01871       _box.truncate(_box.length() - 1);
01872   }
01873   kDebug(7116) <<"URL: box=" << _box <<", section=" << _section <<", type="
01874     << _type << ", uid=" << _uid << ", validity=" << _validity << ", info=" << _info;
01875 }
01876 
01877 
01878 QByteArray imapParser::parseLiteral(parseString & inWords, bool relay, bool stopAtBracket) {
01879 
01880   if (!inWords.isEmpty() && inWords[0] == '{')
01881   {
01882     QByteArray retVal;
01883     int runLen = inWords.find ('}', 1);
01884     if (runLen > 0)
01885     {
01886       bool proper;
01887       long runLenSave = runLen + 1;
01888       QByteArray tmpstr(runLen, '\0');
01889       inWords.takeMidNoResize(tmpstr, 1, runLen - 1);
01890       runLen = tmpstr.toULong (&proper);
01891       inWords.pos += runLenSave;
01892       if (proper)
01893       {
01894         //now get the literal from the server
01895         if (relay)
01896           parseRelay (runLen);
01897         QByteArray rv;
01898         parseRead (rv, runLen, relay ? runLen : 0);
01899         rv.resize(qMax(runLen, rv.size())); // what's the point?
01900         retVal = rv;
01901         inWords.clear();
01902         parseReadLine (inWords.data); // must get more
01903 
01904         // no duplicate data transfers
01905         relay = false;
01906       }
01907       else
01908       {
01909         kDebug(7116) <<"imapParser::parseLiteral - error parsing {} -" /*<< strLen*/;
01910       }
01911     }
01912     else
01913     {
01914       inWords.clear();
01915       kDebug(7116) <<"imapParser::parseLiteral - error parsing unmatched {";
01916     }
01917     skipWS (inWords);
01918     return retVal;
01919   }
01920 
01921   return parseOneWord(inWords, stopAtBracket);
01922 }
01923 
01924 // does not know about literals ( {7} literal )
01925 QByteArray imapParser::parseOneWord (parseString & inWords, bool stopAtBracket)
01926 {
01927   uint len = inWords.length();
01928   if (len == 0) {
01929     return QByteArray();
01930   }
01931 
01932   if (len > 0 && inWords[0] == '"')
01933   {
01934     unsigned int i = 1;
01935     bool quote = false;
01936     while (i < len && (inWords[i] != '"' || quote))
01937     {
01938       if (inWords[i] == '\\') quote = !quote;
01939       else quote = false;
01940       i++;
01941     }
01942     if (i < len)
01943     {
01944       QByteArray retVal;
01945       retVal.resize(i);
01946       inWords.pos++;
01947       inWords.takeLeftNoResize(retVal, i - 1);
01948       len = i - 1;
01949       int offset = 0;
01950       for (unsigned int j = 0; j < len; j++) {
01951         if (retVal[j] == '\\') {
01952           offset++;
01953           j++;
01954         }
01955         retVal[j - offset] = retVal[j];
01956       }
01957       retVal.resize( len - offset );
01958       inWords.pos += i;
01959       skipWS (inWords);
01960       return retVal;
01961     }
01962     else
01963     {
01964       kDebug(7116) <<"imapParser::parseOneWord - error parsing unmatched \"";
01965       QByteArray retVal = inWords.cstr();
01966       inWords.clear();
01967       return retVal;
01968     }
01969   }
01970   else
01971   {
01972     // not quoted
01973     unsigned int i;
01974     // search for end
01975     for (i = 0; i < len; ++i) {
01976         char ch = inWords[i];
01977         if (ch <= ' ' || ch == '(' || ch == ')' ||
01978             (stopAtBracket && (ch == '[' || ch == ']')))
01979             break;
01980     }
01981 
01982     QByteArray retVal;
01983     retVal.resize(i);
01984     inWords.takeLeftNoResize(retVal, i);
01985     inWords.pos += i;
01986 
01987     if (retVal == "NIL") {
01988       retVal.truncate(0);
01989     }
01990     skipWS (inWords);
01991     return retVal;
01992   }
01993 }
01994 
01995 bool imapParser::parseOneNumber (parseString & inWords, ulong & num)
01996 {
01997   bool valid;
01998   num = parseOneWord(inWords, true).toULong(&valid);
01999   return valid;
02000 }
02001 
02002 bool imapParser::hasCapability (const QString & cap)
02003 {
02004   QString c = cap.toLower();
02005 //  kDebug(7116) <<"imapParser::hasCapability - Looking for '" << cap <<"'";
02006   for (QStringList::ConstIterator it = imapCapabilities.constBegin ();
02007        it != imapCapabilities.constEnd (); ++it)
02008   {
02009 //    kDebug(7116) <<"imapParser::hasCapability - Examining '" << (*it) <<"'";
02010     if ( !(kasciistricmp(c.toAscii(), (*it).toAscii())) )
02011     {
02012       return true;
02013     }
02014   }
02015   return false;
02016 }
02017 
02018 void imapParser::removeCapability (const QString & cap)
02019 {
02020   imapCapabilities.removeAll(cap.toLower());
02021 }
02022 
02023 QString imapParser::namespaceForBox( const QString & box )
02024 {
02025   kDebug(7116) <<"imapParse::namespaceForBox" << box;
02026   QString myNamespace;
02027   if ( !box.isEmpty() )
02028   {
02029     const QList<QString> list = namespaceToDelimiter.keys();
02030     QString cleanPrefix;
02031     for ( QList<QString>::ConstIterator it = list.begin(); it != list.end(); ++it )
02032     {
02033       if ( !(*it).isEmpty() && box.contains( *it ) )
02034         return (*it);
02035     }
02036   }
02037   return myNamespace;
02038 }
02039 

kioslave/imap4

Skip menu "kioslave/imap4"
  • Main Page
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  • kabc
  • kblog
  • kcal
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.9
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