kioslaves/imap4

imapparser.cc

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.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[0] != '(')
00899   {
00900     parseOneWordC (inWords);     // parse NIL
00901   }
00902   else
00903   {
00904     inWords.pos++;
00905     skipWS (inWords);
00906 
00907     while (!inWords.isEmpty () && inWords[0] != ')')
00908     {
00909       if (inWords[0] == '(') {
00910         mailAddress *addr = new mailAddress;
00911         parseAddress(inWords, *addr);
00912         list.append(addr);
00913       } else {
00914         break;
00915       }
00916     }
00917 
00918     if (inWords[0] == ')')
00919       inWords.pos++;
00920     skipWS (inWords);
00921   }
00922 }
00923 
00924 const mailAddress& imapParser::parseAddress (parseString & inWords, mailAddress& retVal)
00925 {
00926   inWords.pos++;
00927   skipWS (inWords);
00928 
00929   retVal.setFullName(rfcDecoder::quoteIMAP(parseLiteralC(inWords)));
00930   retVal.setCommentRaw(parseLiteralC(inWords));
00931   retVal.setUser(parseLiteralC(inWords));
00932   retVal.setHost(parseLiteralC(inWords));
00933 
00934   if (inWords[0] == ')')
00935     inWords.pos++;
00936   skipWS (inWords);
00937 
00938   return retVal;
00939 }
00940 
00941 mailHeader * imapParser::parseEnvelope (parseString & inWords)
00942 {
00943   mailHeader *envelope = 0;
00944 
00945   if (inWords[0] != '(')
00946     return envelope;
00947   inWords.pos++;
00948   skipWS (inWords);
00949 
00950   envelope = new mailHeader;
00951 
00952   //date
00953   envelope->setDate(parseLiteralC(inWords));
00954 
00955   //subject
00956   envelope->setSubject(parseLiteralC(inWords));
00957 
00958   QPtrList<mailAddress> list;
00959   list.setAutoDelete(true);
00960 
00961   //from
00962   parseAddressList(inWords, list);
00963   if (!list.isEmpty()) {
00964       envelope->setFrom(*list.last());
00965       list.clear();
00966   }
00967 
00968   //sender
00969   parseAddressList(inWords, list);
00970   if (!list.isEmpty()) {
00971       envelope->setSender(*list.last());
00972       list.clear();
00973   }
00974 
00975   //reply-to
00976   parseAddressList(inWords, list);
00977   if (!list.isEmpty()) {
00978       envelope->setReplyTo(*list.last());
00979       list.clear();
00980   }
00981 
00982   //to
00983   parseAddressList (inWords, envelope->to());
00984 
00985   //cc
00986   parseAddressList (inWords, envelope->cc());
00987 
00988   //bcc
00989   parseAddressList (inWords, envelope->bcc());
00990 
00991   //in-reply-to
00992   envelope->setInReplyTo(parseLiteralC(inWords));
00993 
00994   //message-id
00995   envelope->setMessageId(parseLiteralC(inWords));
00996 
00997   // see if we have more to come
00998   while (!inWords.isEmpty () && inWords[0] != ')')
00999   {
01000     //eat the extensions to this part
01001     if (inWords[0] == '(')
01002       parseSentence (inWords);
01003     else
01004       parseLiteralC (inWords);
01005   }
01006 
01007   if (inWords[0] == ')')
01008     inWords.pos++;
01009   skipWS (inWords);
01010 
01011   return envelope;
01012 }
01013 
01014 // parse parameter pairs into a dictionary
01015 // caller must clean up the dictionary items
01016 QAsciiDict < QString > imapParser::parseDisposition (parseString & inWords)
01017 {
01018   QCString disposition;
01019   QAsciiDict < QString > retVal (17, false);
01020 
01021   // return value is a shallow copy
01022   retVal.setAutoDelete (false);
01023 
01024   if (inWords[0] != '(')
01025   {
01026     //disposition only
01027     disposition = parseOneWordC (inWords);
01028   }
01029   else
01030   {
01031     inWords.pos++;
01032     skipWS (inWords);
01033 
01034     //disposition
01035     disposition = parseOneWordC (inWords);
01036     retVal = parseParameters (inWords);
01037     if (inWords[0] != ')')
01038       return retVal;
01039     inWords.pos++;
01040     skipWS (inWords);
01041   }
01042 
01043   if (!disposition.isEmpty ())
01044   {
01045     retVal.insert ("content-disposition", new QString(disposition));
01046   }
01047 
01048   return retVal;
01049 }
01050 
01051 // parse parameter pairs into a dictionary
01052 // caller must clean up the dictionary items
01053 QAsciiDict < QString > imapParser::parseParameters (parseString & inWords)
01054 {
01055   QAsciiDict < QString > retVal (17, false);
01056 
01057   // return value is a shallow copy
01058   retVal.setAutoDelete (false);
01059 
01060   if (inWords[0] != '(')
01061   {
01062     //better be NIL
01063     parseOneWordC (inWords);
01064   }
01065   else
01066   {
01067     inWords.pos++;
01068     skipWS (inWords);
01069 
01070     while (!inWords.isEmpty () && inWords[0] != ')')
01071     {
01072       QCString l1 = parseLiteralC(inWords);
01073       QCString l2 = parseLiteralC(inWords);
01074       retVal.insert (l1, new QString(l2));
01075     }
01076 
01077     if (inWords[0] != ')')
01078       return retVal;
01079     inWords.pos++;
01080     skipWS (inWords);
01081   }
01082 
01083   return retVal;
01084 }
01085 
01086 mimeHeader * imapParser::parseSimplePart (parseString & inWords,
01087   QString & inSection, mimeHeader * localPart)
01088 {
01089   QCString subtype;
01090   QCString typeStr;
01091   QAsciiDict < QString > parameters (17, false);
01092   ulong size;
01093 
01094   parameters.setAutoDelete (true);
01095 
01096   if (inWords[0] != '(')
01097     return 0;
01098 
01099   if (!localPart)
01100     localPart = new mimeHeader;
01101 
01102   localPart->setPartSpecifier (inSection);
01103 
01104   inWords.pos++;
01105   skipWS (inWords);
01106 
01107   //body type
01108   typeStr = parseLiteralC(inWords);
01109 
01110   //body subtype
01111   subtype = parseLiteralC(inWords);
01112 
01113   localPart->setType (typeStr + "/" + subtype);
01114 
01115   //body parameter parenthesized list
01116   parameters = parseParameters (inWords);
01117   {
01118     QAsciiDictIterator < QString > it (parameters);
01119 
01120     while (it.current ())
01121     {
01122       localPart->setTypeParm (it.currentKey (), *(it.current ()));
01123       ++it;
01124     }
01125     parameters.clear ();
01126   }
01127 
01128   //body id
01129   localPart->setID (parseLiteralC(inWords));
01130 
01131   //body description
01132   localPart->setDescription (parseLiteralC(inWords));
01133 
01134   //body encoding
01135   localPart->setEncoding (parseLiteralC(inWords));
01136 
01137   //body size
01138   if (parseOneNumber (inWords, size))
01139     localPart->setLength (size);
01140 
01141   // type specific extensions
01142   if (localPart->getType().upper() == "MESSAGE/RFC822")
01143   {
01144     //envelope structure
01145     mailHeader *envelope = parseEnvelope (inWords);
01146 
01147     //body structure
01148     parseBodyStructure (inWords, inSection, envelope);
01149 
01150     localPart->setNestedMessage (envelope);
01151 
01152     //text lines
01153     ulong lines;
01154     parseOneNumber (inWords, lines);
01155   }
01156   else
01157   {
01158     if (typeStr ==  "TEXT")
01159     {
01160       //text lines
01161       ulong lines;
01162       parseOneNumber (inWords, lines);
01163     }
01164 
01165     // md5
01166     parseLiteralC(inWords);
01167 
01168     // body disposition
01169     parameters = parseDisposition (inWords);
01170     {
01171       QString *disposition = parameters["content-disposition"];
01172 
01173       if (disposition)
01174         localPart->setDisposition (disposition->ascii ());
01175       parameters.remove ("content-disposition");
01176       QAsciiDictIterator < QString > it (parameters);
01177       while (it.current ())
01178       {
01179         localPart->setDispositionParm (it.currentKey (),
01180                                        *(it.current ()));
01181         ++it;
01182       }
01183 
01184       parameters.clear ();
01185     }
01186 
01187     // body language
01188     parseSentence (inWords);
01189   }
01190 
01191   // see if we have more to come
01192   while (!inWords.isEmpty () && inWords[0] != ')')
01193   {
01194     //eat the extensions to this part
01195     if (inWords[0] == '(')
01196       parseSentence (inWords);
01197     else
01198       parseLiteralC(inWords);
01199   }
01200   if (inWords[0] == ')')
01201     inWords.pos++;
01202   skipWS (inWords);
01203 
01204   return localPart;
01205 }
01206 
01207 mimeHeader * imapParser::parseBodyStructure (parseString & inWords,
01208   QString & inSection, mimeHeader * localPart)
01209 {
01210   bool init = false;
01211   if (inSection.isEmpty())
01212   {
01213     // first run
01214     init = true;
01215     // assume one part
01216     inSection = "1";
01217   }
01218   int section = 0;
01219 
01220   if (inWords[0] != '(')
01221   {
01222     // skip ""
01223     parseOneWordC (inWords);
01224     return 0;
01225   }
01226   inWords.pos++;
01227   skipWS (inWords);
01228 
01229   if (inWords[0] == '(')
01230   {
01231     QByteArray subtype;
01232     QAsciiDict < QString > parameters (17, false);
01233     QString outSection;
01234     parameters.setAutoDelete (true);
01235     if (!localPart)
01236       localPart = new mimeHeader;
01237     else
01238     {
01239       // might be filled from an earlier run
01240       localPart->clearNestedParts ();
01241       localPart->clearTypeParameters ();
01242       localPart->clearDispositionParameters ();
01243       // an envelope was passed in so this is the multipart header
01244       outSection = inSection + ".HEADER";
01245     }
01246     if (inWords[0] == '(' && init)
01247       inSection = "0";
01248 
01249     // set the section
01250     if ( !outSection.isEmpty() ) {
01251       localPart->setPartSpecifier(outSection);
01252     } else {
01253       localPart->setPartSpecifier(inSection);
01254     }
01255 
01256     // is multipart (otherwise its a simplepart and handled later)
01257     while (inWords[0] == '(')
01258     {
01259       outSection = QString::number(++section);
01260       if (!init)
01261         outSection = inSection + "." + outSection;
01262       mimeHeader *subpart = parseBodyStructure (inWords, outSection, 0);
01263       localPart->addNestedPart (subpart);
01264     }
01265 
01266     // fetch subtype
01267     subtype = parseOneWordC (inWords);
01268 
01269     localPart->setType ("MULTIPART/" + b2c(subtype));
01270 
01271     // fetch parameters
01272     parameters = parseParameters (inWords);
01273     {
01274       QAsciiDictIterator < QString > it (parameters);
01275 
01276       while (it.current ())
01277       {
01278         localPart->setTypeParm (it.currentKey (), *(it.current ()));
01279         ++it;
01280       }
01281       parameters.clear ();
01282     }
01283 
01284     // body disposition
01285     parameters = parseDisposition (inWords);
01286     {
01287       QString *disposition = parameters["content-disposition"];
01288 
01289       if (disposition)
01290         localPart->setDisposition (disposition->ascii ());
01291       parameters.remove ("content-disposition");
01292       QAsciiDictIterator < QString > it (parameters);
01293       while (it.current ())
01294       {
01295         localPart->setDispositionParm (it.currentKey (),
01296                                        *(it.current ()));
01297         ++it;
01298       }
01299       parameters.clear ();
01300     }
01301 
01302     // body language
01303     parseSentence (inWords);
01304 
01305   }
01306   else
01307   {
01308     // is simple part
01309     inWords.pos--;
01310     inWords.data[inWords.pos] = '('; //fake a sentence
01311     if ( localPart )
01312       inSection = inSection + ".1";
01313     localPart = parseSimplePart (inWords, inSection, localPart);
01314     inWords.pos--;
01315     inWords.data[inWords.pos] = ')'; //remove fake
01316   }
01317 
01318   // see if we have more to come
01319   while (!inWords.isEmpty () && inWords[0] != ')')
01320   {
01321     //eat the extensions to this part
01322     if (inWords[0] == '(')
01323       parseSentence (inWords);
01324     else
01325       parseLiteralC(inWords);
01326   }
01327 
01328   if (inWords[0] == ')')
01329     inWords.pos++;
01330   skipWS (inWords);
01331 
01332   return localPart;
01333 }
01334 
01335 void imapParser::parseBody (parseString & inWords)
01336 {
01337   // see if we got a part specifier
01338   if (inWords[0] == '[')
01339   {
01340     QCString specifier;
01341     QCString label;
01342     inWords.pos++;
01343 
01344     specifier = parseOneWordC (inWords, TRUE);
01345 
01346     if (inWords[0] == '(')
01347     {
01348       inWords.pos++;
01349 
01350       while (!inWords.isEmpty () && inWords[0] != ')')
01351       {
01352         label = parseOneWordC (inWords);
01353       }
01354 
01355       if (inWords[0] == ')')
01356         inWords.pos++;
01357     }
01358     if (inWords[0] == ']')
01359       inWords.pos++;
01360     skipWS (inWords);
01361 
01362     // parse the header
01363     if (specifier == "0")
01364     {
01365       mailHeader *envelope = 0;
01366       if (lastHandled)
01367         envelope = lastHandled->getHeader ();
01368 
01369       if (!envelope || seenUid.isEmpty ())
01370       {
01371         kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl;
01372         // don't know where to put it, throw it away
01373         parseLiteralC(inWords, true);
01374       }
01375       else
01376       {
01377         kdDebug(7116) << "imapParser::parseBody - reading " << envelope << " " << seenUid.ascii () << endl;
01378         // fill it up with data
01379         QString theHeader = parseLiteralC(inWords, true);
01380         mimeIOQString myIO;
01381 
01382         myIO.setString (theHeader);
01383         envelope->parseHeader (myIO);
01384 
01385       }
01386     }
01387     else if (specifier == "HEADER.FIELDS")
01388     {
01389       // BODY[HEADER.FIELDS (References)] {n}
01390       //kdDebug(7116) << "imapParser::parseBody - HEADER.FIELDS: "
01391       // << QCString(label.data(), label.size()+1) << endl;
01392       if (label == "REFERENCES")
01393       {
01394        mailHeader *envelope = 0;
01395        if (lastHandled)
01396          envelope = lastHandled->getHeader ();
01397 
01398        if (!envelope || seenUid.isEmpty ())
01399        {
01400          kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl;
01401          // don't know where to put it, throw it away
01402          parseLiteralC (inWords, true);
01403        }
01404        else
01405        {
01406          QCString references = parseLiteralC(inWords, true);
01407          int start = references.find ('<');
01408          int end = references.findRev ('>');
01409          if (start < end)
01410                  references = references.mid (start, end - start + 1);
01411          envelope->setReferences(references.simplifyWhiteSpace());
01412        }
01413       }
01414       else
01415       { // not a header we care about throw it away
01416         parseLiteralC(inWords, true);
01417       }
01418     }
01419     else
01420     {
01421       if (specifier.find(".MIME") != -1)
01422       {
01423         mailHeader *envelope = new mailHeader;
01424         QString theHeader = parseLiteralC(inWords, false);
01425         mimeIOQString myIO;
01426         myIO.setString (theHeader);
01427         envelope->parseHeader (myIO);
01428         if (lastHandled)
01429           lastHandled->setHeader (envelope);
01430         return;
01431       }
01432       // throw it away
01433       kdDebug(7116) << "imapParser::parseBody - discarding " << seenUid.ascii () << endl;
01434       parseLiteralC(inWords, true);
01435     }
01436 
01437   }
01438   else // no part specifier
01439   {
01440     mailHeader *envelope = 0;
01441     if (lastHandled)
01442       envelope = lastHandled->getHeader ();
01443 
01444     if (!envelope || seenUid.isEmpty ())
01445     {
01446       kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl;
01447       // don't know where to put it, throw it away
01448       parseSentence (inWords);
01449     }
01450     else
01451     {
01452       kdDebug(7116) << "imapParser::parseBody - reading " << envelope << " " << seenUid.ascii () << endl;
01453       // fill it up with data
01454       QString section;
01455       mimeHeader *body = parseBodyStructure (inWords, section, envelope);
01456       if (body != envelope)
01457         delete body;
01458     }
01459   }
01460 }
01461 
01462 void imapParser::parseFetch (ulong /* value */, parseString & inWords)
01463 {
01464   if (inWords[0] != '(')
01465     return;
01466   inWords.pos++;
01467   skipWS (inWords);
01468 
01469   delete lastHandled;
01470   lastHandled = 0;
01471 
01472   while (!inWords.isEmpty () && inWords[0] != ')')
01473   {
01474     if (inWords[0] == '(')
01475       parseSentence (inWords);
01476     else
01477     {
01478       QCString word = parseLiteralC(inWords, false, true);
01479 
01480       switch (word[0])
01481       {
01482       case 'E':
01483         if (word == "ENVELOPE")
01484         {
01485           mailHeader *envelope = 0;
01486 
01487           if (lastHandled)
01488             envelope = lastHandled->getHeader ();
01489           else
01490             lastHandled = new imapCache();
01491 
01492           if (envelope && !envelope->getMessageId ().isEmpty ())
01493           {
01494             // we have seen this one already
01495             // or don't know where to put it
01496             parseSentence (inWords);
01497           }
01498           else
01499           {
01500             envelope = parseEnvelope (inWords);
01501             if (envelope)
01502             {
01503               envelope->setPartSpecifier (seenUid + ".0");
01504               lastHandled->setHeader (envelope);
01505               lastHandled->setUid (seenUid.toULong ());
01506             }
01507           }
01508         }
01509         break;
01510 
01511       case 'B':
01512         if (word == "BODY")
01513         {
01514           parseBody (inWords);
01515         }
01516         else if (word == "BODY[]" )
01517         {
01518           // Do the same as with "RFC822"
01519           parseLiteralC(inWords, true);
01520         }
01521         else if (word == "BODYSTRUCTURE")
01522         {
01523           mailHeader *envelope = 0;
01524 
01525           if (lastHandled)
01526             envelope = lastHandled->getHeader ();
01527 
01528           // fill it up with data
01529           QString section;
01530           mimeHeader *body =
01531             parseBodyStructure (inWords, section, envelope);
01532           QByteArray data;
01533           QDataStream stream( data, IO_WriteOnly );
01534           body->serialize(stream);
01535           parseRelay(data);
01536 
01537           delete body;
01538         }
01539         break;
01540 
01541       case 'U':
01542         if (word == "UID")
01543         {
01544           seenUid = parseOneWordC(inWords);
01545           mailHeader *envelope = 0;
01546           if (lastHandled)
01547             envelope = lastHandled->getHeader ();
01548           else
01549             lastHandled = new imapCache();
01550 
01551           if (seenUid.isEmpty ())
01552           {
01553             // unknown what to do
01554             kdDebug(7116) << "imapParser::parseFetch - UID empty" << endl;
01555           }
01556           else
01557           {
01558             lastHandled->setUid (seenUid.toULong ());
01559           }
01560           if (envelope)
01561             envelope->setPartSpecifier (seenUid);
01562         }
01563         break;
01564 
01565       case 'R':
01566         if (word == "RFC822.SIZE")
01567         {
01568           ulong size;
01569           parseOneNumber (inWords, size);
01570 
01571           if (!lastHandled) lastHandled = new imapCache();
01572           lastHandled->setSize (size);
01573         }
01574         else if (word.find ("RFC822") == 0)
01575         {
01576           // might be RFC822 RFC822.TEXT RFC822.HEADER
01577           parseLiteralC(inWords, true);
01578         }
01579         break;
01580 
01581       case 'I':
01582         if (word == "INTERNALDATE")
01583         {
01584           QCString date = parseOneWordC(inWords);
01585           if (!lastHandled) lastHandled = new imapCache();
01586           lastHandled->setDate(date);
01587         }
01588         break;
01589 
01590       case 'F':
01591         if (word == "FLAGS")
01592         {
01593       //kdDebug(7116) << "GOT FLAGS " << inWords.cstr() << endl;
01594           if (!lastHandled) lastHandled = new imapCache();
01595           lastHandled->setFlags (imapInfo::_flags (inWords.cstr()));
01596         }
01597         break;
01598 
01599       default:
01600         parseLiteralC(inWords);
01601         break;
01602       }
01603     }
01604   }
01605 
01606   // see if we have more to come
01607   while (!inWords.isEmpty () && inWords[0] != ')')
01608   {
01609     //eat the extensions to this part
01610     if (inWords[0] == '(')
01611       parseSentence (inWords);
01612     else
01613       parseLiteralC(inWords);
01614   }
01615 
01616   if (inWords[0] != ')')
01617     return;
01618   inWords.pos++;
01619   skipWS (inWords);
01620 }
01621 
01622 
01623 // default parser
01624 void imapParser::parseSentence (parseString & inWords)
01625 {
01626   bool first = true;
01627   int stack = 0;
01628 
01629   //find the first nesting parentheses
01630 
01631   while (!inWords.isEmpty () && (stack != 0 || first))
01632   {
01633     first = false;
01634     skipWS (inWords);
01635 
01636     unsigned char ch = inWords[0];
01637     switch (ch)
01638     {
01639     case '(':
01640       inWords.pos++;
01641       ++stack;
01642       break;
01643     case ')':
01644       inWords.pos++;
01645       --stack;
01646       break;
01647     case '[':
01648       inWords.pos++;
01649       ++stack;
01650       break;
01651     case ']':
01652       inWords.pos++;
01653       --stack;
01654       break;
01655     default:
01656       parseLiteralC(inWords);
01657       skipWS (inWords);
01658       break;
01659     }
01660   }
01661   skipWS (inWords);
01662 }
01663 
01664 void imapParser::parseRecent (ulong value, parseString & result)
01665 {
01666   selectInfo.setRecent (value);
01667   result.pos = result.data.size();
01668 }
01669 
01670 void imapParser::parseNamespace (parseString & result)
01671 {
01672   if ( result[0] != '(' )
01673     return;
01674 
01675   QString delimEmpty;
01676   if ( namespaceToDelimiter.contains( QString::null ) )
01677     delimEmpty = namespaceToDelimiter[QString::null];
01678 
01679   namespaceToDelimiter.clear();
01680   imapNamespaces.clear();
01681 
01682   // remember what section we're in (user, other users, shared)
01683   int ns = -1;
01684   bool personalAvailable = false;
01685   while ( !result.isEmpty() )
01686   {
01687     if ( result[0] == '(' )
01688     {
01689       result.pos++; // tie off (
01690       if ( result[0] == '(' )
01691       {
01692         // new namespace section
01693         result.pos++; // tie off (
01694         ++ns;
01695       }
01696       // namespace prefix
01697       QCString prefix = parseOneWordC( result );
01698       // delimiter
01699       QCString delim = parseOneWordC( result );
01700       kdDebug(7116) << "imapParser::parseNamespace ns='" << prefix <<
01701        "',delim='" << delim << "'" << endl;
01702       if ( ns == 0 )
01703       {
01704         // at least one personal ns
01705         personalAvailable = true;
01706       }
01707       QString nsentry = QString::number( ns ) + "=" + QString(prefix) +
01708         "=" + QString(delim);
01709       imapNamespaces.append( nsentry );
01710       if ( prefix.right( 1 ) == delim ) {
01711         // strip delimiter to get a correct entry for comparisons
01712         prefix.resize( prefix.length() );
01713       }
01714       namespaceToDelimiter[prefix] = delim;
01715 
01716       result.pos++; // tie off )
01717       skipWS( result );
01718     } else if ( result[0] == ')' )
01719     {
01720       result.pos++; // tie off )
01721       skipWS( result );
01722     } else if ( result[0] == 'N' )
01723     {
01724       // drop NIL
01725       ++ns;
01726       parseOneWordC( result );
01727     } else {
01728       // drop whatever it is
01729       parseOneWordC( result );
01730     }
01731   }
01732   if ( !delimEmpty.isEmpty() ) {
01733     // remember default delimiter
01734     namespaceToDelimiter[QString::null] = delimEmpty;
01735     if ( !personalAvailable )
01736     {
01737       // at least one personal ns would be nice
01738       kdDebug(7116) << "imapParser::parseNamespace - registering own personal ns" << endl;
01739       QString nsentry = "0==" + delimEmpty;
01740       imapNamespaces.append( nsentry );
01741     }
01742   }
01743 }
01744 
01745 int imapParser::parseLoop ()
01746 {
01747   parseString result;
01748 
01749   if (!parseReadLine(result.data)) return -1;
01750 
01751   //kdDebug(7116) << result.cstr(); // includes \n
01752 
01753   if (result.data.isEmpty())
01754     return 0;
01755   if (!sentQueue.count ())
01756   {
01757     // maybe greeting or BYE everything else SHOULD not happen, use NOOP or IDLE
01758     kdDebug(7116) << "imapParser::parseLoop - unhandledResponse: \n" << result.cstr() << endl;
01759     unhandled << result.cstr();
01760   }
01761   else
01762   {
01763     imapCommand *current = sentQueue.at (0);
01764     switch (result[0])
01765     {
01766     case '*':
01767       result.data.resize(result.data.size() - 2);  // tie off CRLF
01768       parseUntagged (result);
01769       break;
01770     case '+':
01771       continuation.duplicate(result.data);
01772       break;
01773     default:
01774       {
01775         QCString tag = parseLiteralC(result);
01776         if (current->id() == tag.data())
01777         {
01778           result.data.resize(result.data.size() - 2);  // tie off CRLF
01779           QByteArray resultCode = parseLiteral (result); //the result
01780           current->setResult (resultCode);
01781           current->setResultInfo(result.cstr());
01782           current->setComplete ();
01783 
01784           sentQueue.removeRef (current);
01785           completeQueue.append (current);
01786           if (result.length())
01787             parseResult (resultCode, result, current->command());
01788         }
01789         else
01790         {
01791           kdDebug(7116) << "imapParser::parseLoop - unknown tag '" << tag << "'" << endl;
01792           QCString cstr = tag + " " + result.cstr();
01793           result.data = cstr;
01794           result.pos = 0;
01795           result.data.resize(cstr.length());
01796         }
01797       }
01798       break;
01799     }
01800   }
01801 
01802   return 1;
01803 }
01804 
01805 void
01806 imapParser::parseRelay (const QByteArray & buffer)
01807 {
01808   Q_UNUSED(buffer);
01809   qWarning
01810     ("imapParser::parseRelay - virtual function not reimplemented - data lost");
01811 }
01812 
01813 void
01814 imapParser::parseRelay (ulong len)
01815 {
01816   Q_UNUSED(len);
01817   qWarning
01818     ("imapParser::parseRelay - virtual function not reimplemented - announcement lost");
01819 }
01820 
01821 bool imapParser::parseRead (QByteArray & buffer, ulong len, ulong relay)
01822 {
01823   Q_UNUSED(buffer);
01824   Q_UNUSED(len);
01825   Q_UNUSED(relay);
01826   qWarning
01827     ("imapParser::parseRead - virtual function not reimplemented - no data read");
01828   return FALSE;
01829 }
01830 
01831 bool imapParser::parseReadLine (QByteArray & buffer, ulong relay)
01832 {
01833   Q_UNUSED(buffer);
01834   Q_UNUSED(relay);
01835   qWarning
01836     ("imapParser::parseReadLine - virtual function not reimplemented - no data read");
01837   return FALSE;
01838 }
01839 
01840 void
01841 imapParser::parseWriteLine (const QString & str)
01842 {
01843   Q_UNUSED(str);
01844   qWarning
01845     ("imapParser::parseWriteLine - virtual function not reimplemented - no data written");
01846 }
01847 
01848 void
01849 imapParser::parseURL (const KURL & _url, QString & _box, QString & _section,
01850                       QString & _type, QString & _uid, QString & _validity, QString & _info)
01851 {
01852   QStringList parameters;
01853 
01854   _box = _url.path ();
01855   kdDebug(7116) << "imapParser::parseURL " << _box << endl;
01856   int paramStart = _box.find("/;");
01857   if ( paramStart > -1 )
01858   {
01859     QString paramString = _box.right( _box.length() - paramStart-2 );
01860     parameters = QStringList::split (';', paramString);  //split parameters
01861     _box.truncate( paramStart ); // strip parameters
01862   }
01863   // extract parameters
01864   for (QStringList::ConstIterator it (parameters.begin ());
01865        it != parameters.end (); ++it)
01866   {
01867     QString temp = (*it);
01868 
01869     int pt = temp.find ('/');
01870     if (pt > 0)
01871     {
01872       if (temp.findRev ('"', pt) == -1 || temp.find('"', pt) == -1)
01873       {
01874         // if we have non-quoted '/' separator we'll just nuke it
01875         temp.truncate(pt);
01876       }
01877     }
01878     if (temp.find ("section=", 0, false) == 0)
01879       _section = temp.right (temp.length () - 8);
01880     else if (temp.find ("type=", 0, false) == 0)
01881       _type = temp.right (temp.length () - 5);
01882     else if (temp.find ("uid=", 0, false) == 0)
01883       _uid = temp.right (temp.length () - 4);
01884     else if (temp.find ("uidvalidity=", 0, false) == 0)
01885       _validity = temp.right (temp.length () - 12);
01886     else if (temp.find ("info=", 0, false) == 0)
01887       _info = temp.right (temp.length () - 5);
01888   }
01889 //  kdDebug(7116) << "URL: section= " << _section << ", type= " << _type << ", uid= " << _uid << endl;
01890 //  kdDebug(7116) << "URL: user() " << _url.user() << endl;
01891 //  kdDebug(7116) << "URL: path() " << _url.path() << endl;
01892 //  kdDebug(7116) << "URL: encodedPathAndQuery() " << _url.encodedPathAndQuery() << endl;
01893 
01894   if (!_box.isEmpty ())
01895   {
01896     // strip /
01897     if (_box[0] == '/')
01898       _box = _box.right (_box.length () - 1);
01899     if (!_box.isEmpty () && _box[_box.length () - 1] == '/')
01900       _box.truncate(_box.length() - 1);
01901   }
01902   kdDebug(7116) << "URL: box= " << _box << ", section= " << _section << ", type= "
01903     << _type << ", uid= " << _uid << ", validity= " << _validity << ", info= " << _info << endl;
01904 }
01905 
01906 
01907 QCString imapParser::parseLiteralC(parseString & inWords, bool relay, bool stopAtBracket, int *outlen) {
01908 
01909   if (inWords[0] == '{')
01910   {
01911     QCString retVal;
01912     ulong runLen = inWords.find ('}', 1);
01913     if (runLen > 0)
01914     {
01915       bool proper;
01916       ulong runLenSave = runLen + 1;
01917       QCString tmpstr(runLen);
01918       inWords.takeMidNoResize(tmpstr, 1, runLen - 1);
01919       runLen = tmpstr.toULong (&proper);
01920       inWords.pos += runLenSave;
01921       if (proper)
01922       {
01923         //now get the literal from the server
01924         if (relay)
01925           parseRelay (runLen);
01926         QByteArray rv;
01927         parseRead (rv, runLen, relay ? runLen : 0);
01928         rv.resize(QMAX(runLen, rv.size())); // what's the point?
01929         retVal = b2c(rv);
01930         inWords.clear();
01931         parseReadLine (inWords.data); // must get more
01932 
01933         // no duplicate data transfers
01934         relay = false;
01935       }
01936       else
01937       {
01938         kdDebug(7116) << "imapParser::parseLiteral - error parsing {} - " /*<< strLen*/ << endl;
01939       }
01940     }
01941     else
01942     {
01943       inWords.clear();
01944       kdDebug(7116) << "imapParser::parseLiteral - error parsing unmatched {" << endl;
01945     }
01946     if (outlen) {
01947       *outlen = retVal.length(); // optimize me
01948     }
01949     skipWS (inWords);
01950     return retVal;
01951   }
01952 
01953   return parseOneWordC(inWords, stopAtBracket, outlen);
01954 }
01955 
01956 // does not know about literals ( {7} literal )
01957 QCString imapParser::parseOneWordC (parseString & inWords, bool stopAtBracket, int *outLen)
01958 {
01959   uint retValSize = 0;
01960   uint len = inWords.length();
01961   if (len == 0) {
01962     return QCString();
01963   }
01964 
01965   if (len > 0 && inWords[0] == '"')
01966   {
01967     unsigned int i = 1;
01968     bool quote = FALSE;
01969     while (i < len && (inWords[i] != '"' || quote))
01970     {
01971       if (inWords[i] == '\\') quote = !quote;
01972       else quote = FALSE;
01973       i++;
01974     }
01975     if (i < len)
01976     {
01977       QCString retVal(i);
01978       inWords.pos++;
01979       inWords.takeLeftNoResize(retVal, i - 1);
01980       len = i - 1;
01981       int offset = 0;
01982       for (unsigned int j = 0; j <= len; j++) {
01983         if (retVal[j] == '\\') {
01984           offset++;
01985           j++;
01986         }
01987         retVal[j - offset] = retVal[j];
01988       }
01989       retVal[len - offset] = 0;
01990       retValSize = len - offset;
01991       inWords.pos += i;
01992       skipWS (inWords);
01993       if (outLen) {
01994         *outLen = retValSize;
01995       }
01996       return retVal;
01997     }
01998     else
01999     {
02000       kdDebug(7116) << "imapParser::parseOneWord - error parsing unmatched \"" << endl;
02001       QCString retVal = inWords.cstr();
02002       retValSize = len;
02003       inWords.clear();
02004       if (outLen) {
02005         *outLen = retValSize;
02006       }
02007       return retVal;
02008     }
02009   }
02010   else
02011   {
02012     // not quoted
02013     unsigned int i;
02014     // search for end
02015     for (i = 0; i < len; ++i) {
02016         char ch = inWords[i];
02017         if (ch <= ' ' || ch == '(' || ch == ')' ||
02018             (stopAtBracket && (ch == '[' || ch == ']')))
02019             break;
02020     }
02021 
02022     QCString retVal(i+1);
02023     inWords.takeLeftNoResize(retVal, i);
02024     retValSize = i;
02025     inWords.pos += i;
02026 
02027     if (retVal == "NIL") {
02028       retVal.truncate(0);
02029       retValSize = 0;
02030     }
02031     skipWS (inWords);
02032     if (outLen) {
02033       *outLen = retValSize;
02034     }
02035     return retVal;
02036   }
02037 }
02038 
02039 bool imapParser::parseOneNumber (parseString & inWords, ulong & num)
02040 {
02041   bool valid;
02042   num = parseOneWordC(inWords, TRUE).toULong(&valid);
02043   return valid;
02044 }
02045 
02046 bool imapParser::hasCapability (const QString & cap)
02047 {
02048   QString c = cap.lower();
02049 //  kdDebug(7116) << "imapParser::hasCapability - Looking for '" << cap << "'" << endl;
02050   for (QStringList::ConstIterator it = imapCapabilities.begin ();
02051        it != imapCapabilities.end (); ++it)
02052   {
02053 //    kdDebug(7116) << "imapParser::hasCapability - Examining '" << (*it) << "'" << endl;
02054     if ( !(kasciistricmp(c.ascii(), (*it).ascii())) )
02055     {
02056       return true;
02057     }
02058   }
02059   return false;
02060 }
02061 
02062 void imapParser::removeCapability (const QString & cap)
02063 {
02064   imapCapabilities.remove(cap.lower());
02065 }
02066 
02067 QString imapParser::namespaceForBox( const QString & box )
02068 {
02069   kdDebug(7116) << "imapParse::namespaceForBox " << box << endl;
02070   QString myNamespace;
02071   if ( !box.isEmpty() )
02072   {
02073     QValueList<QString> list = namespaceToDelimiter.keys();
02074     QString cleanPrefix;
02075     for ( QValueList<QString>::Iterator it = list.begin(); it != list.end(); ++it )
02076     {
02077       if ( !(*it).isEmpty() && box.find( *it ) != -1 )
02078         return (*it);
02079     }
02080   }
02081   return myNamespace;
02082 }
02083 
KDE Home | KDE Accessibility Home | Description of Access Keys