kioslaves

imapparser.cc

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