kioslaves/imap4

imap4.cc

00001 /**********************************************************************
00002  *
00003  *   imap4.cc  - IMAP4rev1 KIOSlave
00004  *   Copyright (C) 2001-2002  Michael Haeckel <haeckel@kde.org>
00005  *   Copyright (C) 1999  John Corey
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 jcorey@fruity.ath.cx
00022  *
00023  *********************************************************************/
00024 
00059 #ifdef HAVE_CONFIG_H
00060 #include <config.h>
00061 #endif
00062 
00063 #include "imap4.h"
00064 
00065 #include "rfcdecoder.h"
00066 
00067 #include <sys/stat.h>
00068 
00069 #include <stdio.h>
00070 #include <stdlib.h>
00071 #include <signal.h>
00072 #include <sys/types.h>
00073 #include <sys/wait.h>
00074 
00075 #ifdef HAVE_LIBSASL2
00076 extern "C" {
00077 #include <sasl/sasl.h>
00078 }
00079 #endif
00080 
00081 #include <qbuffer.h>
00082 #include <qdatetime.h>
00083 #include <qregexp.h>
00084 #include <kprotocolmanager.h>
00085 #include <kmessagebox.h>
00086 #include <kdebug.h>
00087 #include <kio/connection.h>
00088 #include <kio/slaveinterface.h>
00089 #include <kio/passdlg.h>
00090 #include <klocale.h>
00091 #include <kmimetype.h>
00092 #include <kmdcodec.h>
00093 
00094 #include "kdepimmacros.h"
00095 
00096 #define IMAP_PROTOCOL "imap"
00097 #define IMAP_SSL_PROTOCOL "imaps"
00098 
00099 using namespace KIO;
00100 
00101 extern "C"
00102 {
00103   void sigalrm_handler (int);
00104   KDE_EXPORT int kdemain (int argc, char **argv);
00105 }
00106 
00107 #ifdef HAVE_LIBSASL2
00108 static sasl_callback_t callbacks[] = {
00109     { SASL_CB_ECHOPROMPT, NULL, NULL },
00110     { SASL_CB_NOECHOPROMPT, NULL, NULL },
00111     { SASL_CB_GETREALM, NULL, NULL },
00112     { SASL_CB_USER, NULL, NULL },
00113     { SASL_CB_AUTHNAME, NULL, NULL },
00114     { SASL_CB_PASS, NULL, NULL },
00115     { SASL_CB_GETOPT, NULL, NULL },
00116     { SASL_CB_CANON_USER, NULL, NULL },
00117     { SASL_CB_LIST_END, NULL, NULL }
00118 };
00119 #endif
00120 
00121 int
00122 kdemain (int argc, char **argv)
00123 {
00124   kdDebug(7116) << "IMAP4::kdemain" << endl;
00125 
00126   KInstance instance ("kio_imap4");
00127   if (argc != 4)
00128   {
00129     fprintf(stderr, "Usage: kio_imap4 protocol domain-socket1 domain-socket2\n");
00130     ::exit (-1);
00131   }
00132 
00133 #ifdef HAVE_LIBSASL2
00134   if ( sasl_client_init( callbacks ) != SASL_OK ) {
00135     fprintf(stderr, "SASL library initialization failed!\n");
00136     ::exit (-1);
00137   }
00138 #endif
00139 
00140   //set debug handler
00141 
00142   IMAP4Protocol *slave;
00143   if (strcasecmp (argv[1], IMAP_SSL_PROTOCOL) == 0)
00144     slave = new IMAP4Protocol (argv[2], argv[3], true);
00145   else if (strcasecmp (argv[1], IMAP_PROTOCOL) == 0)
00146     slave = new IMAP4Protocol (argv[2], argv[3], false);
00147   else
00148     abort ();
00149   slave->dispatchLoop ();
00150   delete slave;
00151 
00152 #ifdef HAVE_LIBSASL2
00153   sasl_done();
00154 #endif
00155 
00156   return 0;
00157 }
00158 
00159 void
00160 sigchld_handler (int signo)
00161 {
00162   int pid, status;
00163 
00164   while (true && signo == SIGCHLD)
00165   {
00166     pid = waitpid (-1, &status, WNOHANG);
00167     if (pid <= 0)
00168     {
00169       // Reinstall signal handler, since Linux resets to default after
00170       // the signal occurred ( BSD handles it different, but it should do
00171       // no harm ).
00172       signal (SIGCHLD, sigchld_handler);
00173       return;
00174     }
00175   }
00176 }
00177 
00178 IMAP4Protocol::IMAP4Protocol (const QCString & pool, const QCString & app, bool isSSL):TCPSlaveBase ((isSSL ? 993 : 143),
00179         (isSSL ? IMAP_SSL_PROTOCOL : IMAP_PROTOCOL), pool,
00180               app, isSSL), imapParser (), mimeIO (), outputBuffer(outputCache)
00181 {
00182   outputBufferIndex = 0;
00183   mySSL = isSSL;
00184   readBuffer[0] = 0x00;
00185   relayEnabled = false;
00186   readBufferLen = 0;
00187   cacheOutput = false;
00188   decodeContent = false;
00189   mTimeOfLastNoop = QDateTime();
00190 }
00191 
00192 IMAP4Protocol::~IMAP4Protocol ()
00193 {
00194   closeDescriptor();
00195   kdDebug(7116) << "IMAP4: Finishing" << endl;
00196 }
00197 
00198 void
00199 IMAP4Protocol::get (const KURL & _url)
00200 {
00201   if (!makeLogin()) return;
00202   kdDebug(7116) << "IMAP4::get -  " << _url.prettyURL() << endl;
00203   QString aBox, aSequence, aType, aSection, aValidity, aDelimiter, aInfo;
00204   enum IMAP_TYPE aEnum =
00205     parseURL (_url, aBox, aSection, aType, aSequence, aValidity, aDelimiter, aInfo);
00206   if (aEnum != ITYPE_ATTACH)
00207     mimeType (getMimeType(aEnum));
00208   if (aInfo == "DECODE")
00209     decodeContent = true;
00210 
00211   if (aSequence == "0:0" && getState() == ISTATE_SELECT)
00212   {
00213     imapCommand *cmd = doCommand (imapCommand::clientNoop());
00214     completeQueue.removeRef(cmd);
00215   }
00216 
00217   if (aSequence.isEmpty ())
00218   {
00219     aSequence = "1:*";
00220   }
00221 
00222   mProcessedSize = 0;
00223   imapCommand *cmd = NULL;
00224   if (!assureBox (aBox, true)) return;
00225 
00226 #ifdef USE_VALIDITY
00227   if (selectInfo.uidValidityAvailable () && !aValidity.isEmpty ()
00228       && selectInfo.uidValidity () != aValidity.toULong ())
00229   {
00230     // this url is stale
00231     error (ERR_COULD_NOT_READ, _url.prettyURL());
00232     return;
00233   }
00234   else
00235 #endif
00236   {
00237     // The "section" specified by the application can be:
00238     // * empty (which means body, size and flags)
00239     // * a known keyword, like STRUCTURE, ENVELOPE, HEADER, BODY.PEEK[...]
00240     //        (in which case the slave has some logic to add the necessary items)
00241     // * Otherwise, it specifies the exact data items to request. In this case, all
00242     //        the logic is in the app.
00243 
00244     QString aUpper = aSection.upper();
00245     if (aUpper.find ("STRUCTURE") != -1)
00246     {
00247       aSection = "BODYSTRUCTURE";
00248     }
00249     else if (aUpper.find ("ENVELOPE") != -1)
00250     {
00251       aSection = "UID RFC822.SIZE FLAGS ENVELOPE";
00252       if (hasCapability("IMAP4rev1")) {
00253         aSection += " BODY.PEEK[HEADER.FIELDS (REFERENCES)]";
00254       } else {
00255         // imap4 does not know HEADER.FIELDS
00256         aSection += " RFC822.HEADER.LINES (REFERENCES)";
00257       }
00258     }
00259     else if (aUpper == "HEADER")
00260     {
00261       aSection = "UID RFC822.HEADER RFC822.SIZE FLAGS";
00262     }
00263     else if (aUpper.find ("BODY.PEEK[") != -1)
00264     {
00265       if (aUpper.find ("BODY.PEEK[]") != -1)
00266       {
00267         if (!hasCapability("IMAP4rev1")) // imap4 does not know BODY.PEEK[]
00268           aSection.replace("BODY.PEEK[]", "RFC822.PEEK");
00269       }
00270       aSection.prepend("UID RFC822.SIZE FLAGS ");
00271     }
00272     else if (aSection.isEmpty())
00273     {
00274       aSection = "UID BODY[] RFC822.SIZE FLAGS";
00275     }
00276     if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX)
00277     {
00278       // write the digest header
00279       cacheOutput = true;
00280       outputLine
00281         ("Content-Type: multipart/digest; boundary=\"IMAPDIGEST\"\r\n", 55);
00282       if (selectInfo.recentAvailable ())
00283         outputLineStr ("X-Recent: " +
00284                        QString::number(selectInfo.recent ()) + "\r\n");
00285       if (selectInfo.countAvailable ())
00286         outputLineStr ("X-Count: " + QString::number(selectInfo.count ()) +
00287                        "\r\n");
00288       if (selectInfo.unseenAvailable ())
00289         outputLineStr ("X-Unseen: " +
00290                        QString::number(selectInfo.unseen ()) + "\r\n");
00291       if (selectInfo.uidValidityAvailable ())
00292         outputLineStr ("X-uidValidity: " +
00293                        QString::number(selectInfo.uidValidity ()) +
00294                        "\r\n");
00295       if (selectInfo.uidNextAvailable ())
00296         outputLineStr ("X-UidNext: " +
00297                        QString::number(selectInfo.uidNext ()) + "\r\n");
00298       if (selectInfo.flagsAvailable ())
00299         outputLineStr ("X-Flags: " + QString::number(selectInfo.flags ()) +
00300                        "\r\n");
00301       if (selectInfo.permanentFlagsAvailable ())
00302         outputLineStr ("X-PermanentFlags: " +
00303                        QString::number(selectInfo.permanentFlags ()) + "\r\n");
00304       if (selectInfo.readWriteAvailable ()) {
00305         if (selectInfo.readWrite()) {
00306           outputLine ("X-Access: Read/Write\r\n", 22);
00307         } else {
00308           outputLine ("X-Access: Read only\r\n", 21);
00309         }
00310       }
00311       outputLine ("\r\n", 2);
00312       flushOutput(QString::null);
00313       cacheOutput = false;
00314     }
00315 
00316     if (aEnum == ITYPE_MSG || (aEnum == ITYPE_ATTACH && !decodeContent))
00317       relayEnabled = true; // normal mode, relay data
00318 
00319     if (aSequence != "0:0")
00320     {
00321       QString contentEncoding;
00322       if (aEnum == ITYPE_ATTACH && decodeContent)
00323       {
00324         // get the MIME header and fill getLastHandled()
00325         QString mySection = aSection;
00326         mySection.replace("]", ".MIME]");
00327         cmd = sendCommand (imapCommand::clientFetch (aSequence, mySection));
00328         do
00329         {
00330           while (!parseLoop ());
00331         }
00332         while (!cmd->isComplete ());
00333         completeQueue.removeRef (cmd);
00334         // get the content encoding now because getLastHandled will be cleared
00335         if (getLastHandled() && getLastHandled()->getHeader())
00336           contentEncoding = getLastHandled()->getHeader()->getEncoding();
00337 
00338         // from here on collect the data
00339         // it is send to the client in flushOutput in one go
00340         // needed to decode the content
00341         cacheOutput = true;
00342       }
00343 
00344       cmd = sendCommand (imapCommand::clientFetch (aSequence, aSection));
00345       int res;
00346       aUpper = aSection.upper();
00347       do
00348       {
00349         while (!(res = parseLoop()));
00350         if (res == -1) break;
00351 
00352         mailHeader *lastone = 0;
00353         imapCache *cache = getLastHandled ();
00354         if (cache)
00355           lastone = cache->getHeader ();
00356 
00357         if (cmd && !cmd->isComplete ())
00358         {
00359           if ((aUpper.find ("BODYSTRUCTURE") != -1)
00360                     || (aUpper.find ("FLAGS") != -1)
00361                     || (aUpper.find ("UID") != -1)
00362                     || (aUpper.find ("ENVELOPE") != -1)
00363                     || (aUpper.find ("BODY.PEEK[0]") != -1
00364                         && (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX)))
00365           {
00366             if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX)
00367             {
00368               // write the mime header (default is here message/rfc822)
00369               outputLine ("--IMAPDIGEST\r\n", 14);
00370               cacheOutput = true;
00371               if (cache && cache->getUid () != 0)
00372                 outputLineStr ("X-UID: " +
00373                                QString::number(cache->getUid ()) + "\r\n");
00374               if (cache && cache->getSize () != 0)
00375                 outputLineStr ("X-Length: " +
00376                                QString::number(cache->getSize ()) + "\r\n");
00377               if (cache && !cache->getDate ().isEmpty())
00378                 outputLineStr ("X-Date: " + cache->getDate () + "\r\n");
00379               if (cache && cache->getFlags () != 0)
00380                 outputLineStr ("X-Flags: " +
00381                                QString::number(cache->getFlags ()) + "\r\n");
00382             } else cacheOutput = true;
00383             if ( lastone && !decodeContent )
00384               lastone->outputPart (*this);
00385             cacheOutput = false;
00386             flushOutput(contentEncoding);
00387           }
00388         } // if not complete
00389       }
00390       while (cmd && !cmd->isComplete ());
00391       if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX)
00392       {
00393         // write the end boundary
00394         outputLine ("--IMAPDIGEST--\r\n", 16);
00395       }
00396 
00397       completeQueue.removeRef (cmd);
00398     }
00399   }
00400 
00401   // just to keep everybody happy when no data arrived
00402   data (QByteArray ());
00403 
00404   finished ();
00405   relayEnabled = false;
00406   cacheOutput = false;
00407   kdDebug(7116) << "IMAP4::get -  finished" << endl;
00408 }
00409 
00410 void
00411 IMAP4Protocol::listDir (const KURL & _url)
00412 {
00413   kdDebug(7116) << " IMAP4::listDir - " << _url.prettyURL() << endl;
00414 
00415   if (_url.path().isEmpty())
00416   {
00417     KURL url = _url;
00418     url.setPath("/");
00419     redirection( url );
00420     finished();
00421     return;
00422   }
00423 
00424   QString myBox, mySequence, myLType, mySection, myValidity, myDelimiter, myInfo;
00425   // parseURL with caching
00426   enum IMAP_TYPE myType =
00427     parseURL (_url, myBox, mySection, myLType, mySequence, myValidity,
00428       myDelimiter, myInfo, true);
00429 
00430   if (!makeLogin()) return;
00431 
00432   if (myType == ITYPE_DIR || myType == ITYPE_DIR_AND_BOX)
00433   {
00434     QString listStr = myBox;
00435     imapCommand *cmd;
00436 
00437     if (!listStr.isEmpty () && !listStr.endsWith(myDelimiter) &&
00438         mySection != "FOLDERONLY")
00439       listStr += myDelimiter;
00440 
00441     if (mySection.isEmpty())
00442     {
00443       listStr += "%";
00444     } else if (mySection == "COMPLETE") {
00445       listStr += "*";
00446     }
00447     kdDebug(7116) << "IMAP4Protocol::listDir - listStr=" << listStr << endl;
00448     cmd =
00449       doCommand (imapCommand::clientList ("", listStr,
00450             (myLType == "LSUB" || myLType == "LSUBNOCHECK")));
00451     if (cmd->result () == "OK")
00452     {
00453       QString mailboxName;
00454       UDSEntry entry;
00455       UDSAtom atom;
00456       KURL aURL = _url;
00457       if (aURL.path().find(';') != -1)
00458         aURL.setPath(aURL.path().left(aURL.path().find(';')));
00459 
00460       kdDebug(7116) << "IMAP4Protocol::listDir - got " << listResponses.count () << endl;
00461 
00462       if (myLType == "LSUB")
00463       {
00464         // fire the same command as LIST to check if the box really exists
00465         QValueList<imapList> listResponsesSave = listResponses;
00466         doCommand (imapCommand::clientList ("", listStr, false));
00467         for (QValueListIterator < imapList > it = listResponsesSave.begin ();
00468             it != listResponsesSave.end (); ++it)
00469         {
00470           bool boxOk = false;
00471           for (QValueListIterator < imapList > it2 = listResponses.begin ();
00472               it2 != listResponses.end (); ++it2)
00473           {
00474             if ((*it2).name() == (*it).name())
00475             {
00476               boxOk = true;
00477               // copy the flags from the LIST-command
00478               (*it) = (*it2);
00479               break;
00480             }
00481           }
00482           if (boxOk)
00483             doListEntry (aURL, myBox, (*it), (mySection != "FOLDERONLY"));
00484           else // this folder is dead
00485             kdDebug(7116) << "IMAP4Protocol::listDir - suppress " << (*it).name() << endl;
00486         }
00487         listResponses = listResponsesSave;
00488       }
00489       else // LIST or LSUBNOCHECK
00490       {
00491         for (QValueListIterator < imapList > it = listResponses.begin ();
00492             it != listResponses.end (); ++it)
00493         {
00494           doListEntry (aURL, myBox, (*it), (mySection != "FOLDERONLY"));
00495         }
00496       }
00497       entry.clear ();
00498       listEntry (entry, true);
00499     }
00500     else
00501     {
00502       error (ERR_CANNOT_ENTER_DIRECTORY, _url.prettyURL());
00503       completeQueue.removeRef (cmd);
00504       return;
00505     }
00506     completeQueue.removeRef (cmd);
00507   }
00508   if ((myType == ITYPE_BOX || myType == ITYPE_DIR_AND_BOX)
00509       && myLType != "LIST" && myLType != "LSUB" && myLType != "LSUBNOCHECK")
00510   {
00511     KURL aURL = _url;
00512     aURL.setQuery (QString::null);
00513     const QString encodedUrl = aURL.url(0, 106); // utf-8
00514 
00515     if (!_url.query ().isEmpty ())
00516     {
00517       QString query = KURL::decode_string (_url.query ());
00518       query = query.right (query.length () - 1);
00519       if (!query.isEmpty())
00520       {
00521         imapCommand *cmd = NULL;
00522 
00523         if (!assureBox (myBox, true)) return;
00524 
00525         if (!selectInfo.countAvailable() || selectInfo.count())
00526         {
00527           cmd = doCommand (imapCommand::clientSearch (query));
00528           if (cmd->result() != "OK")
00529           {
00530             error(ERR_UNSUPPORTED_ACTION, _url.prettyURL());
00531             completeQueue.removeRef (cmd);
00532             return;
00533           }
00534           completeQueue.removeRef (cmd);
00535 
00536           QStringList list = getResults ();
00537           int stretch = 0;
00538 
00539           if (selectInfo.uidNextAvailable ())
00540             stretch = QString::number(selectInfo.uidNext ()).length ();
00541           UDSEntry entry;
00542           imapCache fake;
00543 
00544           for (QStringList::ConstIterator it = list.begin(); it != list.end();
00545                ++it)
00546           {
00547             fake.setUid((*it).toULong());
00548             doListEntry (encodedUrl, stretch, &fake);
00549           }
00550           entry.clear ();
00551           listEntry (entry, true);
00552         }
00553       }
00554     }
00555     else
00556     {
00557       if (!assureBox (myBox, true)) return;
00558 
00559       kdDebug(7116) << "IMAP4: select returned:" << endl;
00560       if (selectInfo.recentAvailable ())
00561         kdDebug(7116) << "Recent: " << selectInfo.recent () << "d" << endl;
00562       if (selectInfo.countAvailable ())
00563         kdDebug(7116) << "Count: " << selectInfo.count () << "d" << endl;
00564       if (selectInfo.unseenAvailable ())
00565         kdDebug(7116) << "Unseen: " << selectInfo.unseen () << "d" << endl;
00566       if (selectInfo.uidValidityAvailable ())
00567         kdDebug(7116) << "uidValidity: " << selectInfo.uidValidity () << "d" << endl;
00568       if (selectInfo.flagsAvailable ())
00569         kdDebug(7116) << "Flags: " << selectInfo.flags () << "d" << endl;
00570       if (selectInfo.permanentFlagsAvailable ())
00571         kdDebug(7116) << "PermanentFlags: " << selectInfo.permanentFlags () << "d" << endl;
00572       if (selectInfo.readWriteAvailable ())
00573         kdDebug(7116) << "Access: " << (selectInfo.readWrite ()? "Read/Write" : "Read only") << endl;
00574 
00575 #ifdef USE_VALIDITY
00576       if (selectInfo.uidValidityAvailable ()
00577           && selectInfo.uidValidity () != myValidity.toULong ())
00578       {
00579         //redirect
00580         KURL newUrl = _url;
00581 
00582         newUrl.setPath ("/" + myBox + ";UIDVALIDITY=" +
00583                         QString::number(selectInfo.uidValidity ()));
00584         kdDebug(7116) << "IMAP4::listDir - redirecting to " << newUrl.prettyURL() << endl;
00585         redirection (newUrl);
00586 
00587 
00588       }
00589       else
00590 #endif
00591       if (selectInfo.count () > 0)
00592       {
00593         int stretch = 0;
00594 
00595         if (selectInfo.uidNextAvailable ())
00596           stretch = QString::number(selectInfo.uidNext ()).length ();
00597         //        kdDebug(7116) << selectInfo.uidNext() << "d used to stretch " << stretch << endl;
00598         UDSEntry entry;
00599 
00600         if (mySequence.isEmpty()) mySequence = "1:*";
00601 
00602         bool withSubject = mySection.isEmpty();
00603         if (mySection.isEmpty()) mySection = "UID RFC822.SIZE ENVELOPE";
00604 
00605         bool withFlags = mySection.upper().find("FLAGS") != -1;
00606         imapCommand *fetch =
00607           sendCommand (imapCommand::
00608                        clientFetch (mySequence, mySection));
00609         imapCache *cache;
00610         do
00611         {
00612           while (!parseLoop ());
00613 
00614           cache = getLastHandled ();
00615 
00616           if (cache && !fetch->isComplete())
00617             doListEntry (encodedUrl, stretch, cache, withFlags, withSubject);
00618         }
00619         while (!fetch->isComplete ());
00620         entry.clear ();
00621         listEntry (entry, true);
00622       }
00623     }
00624   }
00625   if ( !selectInfo.alert().isNull() ) {
00626     if ( !myBox.isEmpty() ) {
00627       warning( i18n( "Message from %1 while processing '%2': %3" ).arg( myHost, myBox, selectInfo.alert() ) );
00628     } else {
00629       warning( i18n( "Message from %1: %2" ).arg( myHost, selectInfo.alert() ) );
00630     }
00631     selectInfo.setAlert( 0 );
00632   }
00633 
00634   kdDebug(7116) << "IMAP4Protocol::listDir - Finishing listDir" << endl;
00635   finished ();
00636 }
00637 
00638 void
00639 IMAP4Protocol::setHost (const QString & _host, int _port,
00640                         const QString & _user, const QString & _pass)
00641 {
00642   if (myHost != _host || myPort != _port || myUser != _user || myPass != _pass)
00643   { // what's the point of doing 4 string compares to avoid 4 string copies?
00644     // DF: I guess to avoid calling closeConnection() unnecessarily.
00645     if (!myHost.isEmpty ())
00646       closeConnection ();
00647     myHost = _host;
00648     myPort = _port;
00649     myUser = _user;
00650     myPass = _pass;
00651   }
00652 }
00653 
00654 void
00655 IMAP4Protocol::parseRelay (const QByteArray & buffer)
00656 {
00657   if (relayEnabled) {
00658     // relay data immediately
00659     data( buffer );
00660     mProcessedSize += buffer.size();
00661     processedSize( mProcessedSize );
00662   } else if (cacheOutput)
00663   {
00664     // collect data
00665     if ( !outputBuffer.isOpen() ) {
00666       outputBuffer.open(IO_WriteOnly);
00667     }
00668     outputBuffer.at(outputBufferIndex);
00669     outputBuffer.writeBlock(buffer, buffer.size());
00670     outputBufferIndex += buffer.size();
00671   }
00672 }
00673 
00674 void
00675 IMAP4Protocol::parseRelay (ulong len)
00676 {
00677   if (relayEnabled)
00678     totalSize (len);
00679 }
00680 
00681 
00682 bool IMAP4Protocol::parseRead(QByteArray & buffer, ulong len, ulong relay)
00683 {
00684   char buf[8192];
00685   while (buffer.size() < len)
00686   {
00687     ssize_t readLen = myRead(buf, QMIN(len - buffer.size(), sizeof(buf) - 1));
00688     if (readLen == 0)
00689     {
00690       kdDebug(7116) << "parseRead: readLen == 0 - connection broken" << endl;
00691       error (ERR_CONNECTION_BROKEN, myHost);
00692       setState(ISTATE_CONNECT);
00693       closeConnection();
00694       return FALSE;
00695     }
00696     if (relay > buffer.size())
00697     {
00698       QByteArray relayData;
00699       ssize_t relbuf = relay - buffer.size();
00700       int currentRelay = QMIN(relbuf, readLen);
00701       relayData.setRawData(buf, currentRelay);
00702       parseRelay(relayData);
00703       relayData.resetRawData(buf, currentRelay);
00704     }
00705     {
00706       QBuffer stream (buffer);
00707       stream.open (IO_WriteOnly);
00708       stream.at (buffer.size ());
00709       stream.writeBlock (buf, readLen);
00710       stream.close ();
00711     }
00712   }
00713   return (buffer.size() == len);
00714 }
00715 
00716 
00717 bool IMAP4Protocol::parseReadLine (QByteArray & buffer, ulong relay)
00718 {
00719   if (myHost.isEmpty()) return FALSE;
00720 
00721   while (true) {
00722     ssize_t copyLen = 0;
00723     if (readBufferLen > 0)
00724     {
00725       while (copyLen < readBufferLen && readBuffer[copyLen] != '\n') copyLen++;
00726       if (copyLen < readBufferLen) copyLen++;
00727       if (relay > 0)
00728       {
00729         QByteArray relayData;
00730 
00731         if (copyLen < (ssize_t) relay)
00732           relay = copyLen;
00733         relayData.setRawData (readBuffer, relay);
00734         parseRelay (relayData);
00735         relayData.resetRawData (readBuffer, relay);
00736 //        kdDebug(7116) << "relayed : " << relay << "d" << endl;
00737       }
00738       // append to buffer
00739       {
00740         QBuffer stream (buffer);
00741 
00742         stream.open (IO_WriteOnly);
00743         stream.at (buffer.size ());
00744         stream.writeBlock (readBuffer, copyLen);
00745         stream.close ();
00746 //        kdDebug(7116) << "appended " << copyLen << "d got now " << buffer.size() << endl;
00747       }
00748 
00749       readBufferLen -= copyLen;
00750       if (readBufferLen)
00751         memmove(readBuffer, &readBuffer[copyLen], readBufferLen);
00752       if (buffer[buffer.size() - 1] == '\n') return TRUE;
00753     }
00754     if (!isConnectionValid())
00755     {
00756       kdDebug(7116) << "parseReadLine - connection broken" << endl;
00757       error (ERR_CONNECTION_BROKEN, myHost);
00758       setState(ISTATE_CONNECT);
00759       closeConnection();
00760       return FALSE;
00761     }
00762     if (!waitForResponse( responseTimeout() ))
00763     {
00764       error(ERR_SERVER_TIMEOUT, myHost);
00765       setState(ISTATE_CONNECT);
00766       closeConnection();
00767       return FALSE;
00768     }
00769     readBufferLen = read(readBuffer, IMAP_BUFFER - 1);
00770     if (readBufferLen == 0)
00771     {
00772       kdDebug(7116) << "parseReadLine: readBufferLen == 0 - connection broken" << endl;
00773       error (ERR_CONNECTION_BROKEN, myHost);
00774       setState(ISTATE_CONNECT);
00775       closeConnection();
00776       return FALSE;
00777     }
00778   }
00779 }
00780 
00781 void
00782 IMAP4Protocol::setSubURL (const KURL & _url)
00783 {
00784   kdDebug(7116) << "IMAP4::setSubURL - " << _url.prettyURL() << endl;
00785   KIO::TCPSlaveBase::setSubURL (_url);
00786 }
00787 
00788 void
00789 IMAP4Protocol::put (const KURL & _url, int, bool, bool)
00790 {
00791   kdDebug(7116) << "IMAP4::put - " << _url.prettyURL() << endl;
00792 //  KIO::TCPSlaveBase::put(_url,permissions,overwrite,resume);
00793   QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
00794   enum IMAP_TYPE aType =
00795     parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
00796 
00797   // see if it is a box
00798   if (aType != ITYPE_BOX && aType != ITYPE_DIR_AND_BOX)
00799   {
00800     if (aBox[aBox.length () - 1] == '/')
00801       aBox = aBox.right (aBox.length () - 1);
00802     imapCommand *cmd = doCommand (imapCommand::clientCreate (aBox));
00803 
00804     if (cmd->result () != "OK") {
00805       error (ERR_COULD_NOT_WRITE, _url.prettyURL());
00806       completeQueue.removeRef (cmd);
00807       return;
00808     }
00809     completeQueue.removeRef (cmd);
00810   }
00811   else
00812   {
00813     QPtrList < QByteArray > bufferList;
00814     int length = 0;
00815 
00816     int result;
00817     // Loop until we got 'dataEnd'
00818     do
00819     {
00820       QByteArray *buffer = new QByteArray ();
00821       dataReq ();               // Request for data
00822       result = readData (*buffer);
00823       if (result > 0)
00824       {
00825         bufferList.append (buffer);
00826         length += result;
00827       } else {
00828         delete buffer;
00829       }
00830     }
00831     while (result > 0);
00832 
00833     if (result != 0)
00834     {
00835       error (ERR_ABORTED, _url.prettyURL());
00836       return;
00837     }
00838 
00839     imapCommand *cmd =
00840       sendCommand (imapCommand::clientAppend (aBox, aSection, length));
00841     while (!parseLoop ());
00842 
00843     // see if server is waiting
00844     if (!cmd->isComplete () && !getContinuation ().isEmpty ())
00845     {
00846       bool sendOk = true;
00847       ulong wrote = 0;
00848 
00849       QByteArray *buffer;
00850       // send data to server
00851       while (!bufferList.isEmpty () && sendOk)
00852       {
00853         buffer = bufferList.take (0);
00854 
00855         sendOk =
00856           (write (buffer->data (), buffer->size ()) ==
00857            (ssize_t) buffer->size ());
00858         wrote += buffer->size ();
00859         processedSize(wrote);
00860         delete buffer;
00861         if (!sendOk)
00862         {
00863           error (ERR_CONNECTION_BROKEN, myHost);
00864           completeQueue.removeRef (cmd);
00865           setState(ISTATE_CONNECT);
00866           closeConnection();
00867           return;
00868         }
00869       }
00870       parseWriteLine ("");
00871       // Wait until cmd is complete, or connection breaks.
00872       while (!cmd->isComplete () && getState() != ISTATE_NO)
00873         parseLoop ();
00874       if ( getState() == ISTATE_NO ) {
00875         // TODO KDE4: pass cmd->resultInfo() as third argument.
00876         // ERR_CONNECTION_BROKEN expects a host, no way to pass details about the problem.
00877         error( ERR_CONNECTION_BROKEN, myHost );
00878         completeQueue.removeRef (cmd);
00879         closeConnection();
00880         return;
00881       }
00882       else if (cmd->result () != "OK") {
00883         error( ERR_SLAVE_DEFINED, cmd->resultInfo() );
00884         completeQueue.removeRef (cmd);
00885         return;
00886       }
00887       else
00888       {
00889         if (hasCapability("UIDPLUS"))
00890         {
00891           QString uid = cmd->resultInfo();
00892           if (uid.find("APPENDUID") != -1)
00893           {
00894             uid = uid.section(" ", 2, 2);
00895             uid.truncate(uid.length()-1);
00896             infoMessage("UID "+uid);
00897           }
00898         }
00899         // MUST reselect to get the new message
00900         else if (aBox == getCurrentBox ())
00901         {
00902           cmd =
00903             doCommand (imapCommand::
00904                        clientSelect (aBox, !selectInfo.readWrite ()));
00905           completeQueue.removeRef (cmd);
00906         }
00907       }
00908     }
00909     else
00910     {
00911       //error (ERR_COULD_NOT_WRITE, myHost);
00912       // Better ship the error message, e.g. "Over Quota"
00913       error (ERR_SLAVE_DEFINED, cmd->resultInfo());
00914       completeQueue.removeRef (cmd);
00915       return;
00916     }
00917 
00918     completeQueue.removeRef (cmd);
00919   }
00920 
00921   finished ();
00922 }
00923 
00924 void
00925 IMAP4Protocol::mkdir (const KURL & _url, int)
00926 {
00927   kdDebug(7116) << "IMAP4::mkdir - " << _url.prettyURL() << endl;
00928   QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
00929   parseURL(_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
00930   kdDebug(7116) << "IMAP4::mkdir - create " << aBox << endl;
00931   imapCommand *cmd = doCommand (imapCommand::clientCreate(aBox));
00932 
00933   if (cmd->result () != "OK")
00934   {
00935     kdDebug(7116) << "IMAP4::mkdir - " << cmd->resultInfo() << endl;
00936     error (ERR_COULD_NOT_MKDIR, _url.prettyURL());
00937     completeQueue.removeRef (cmd);
00938     return;
00939   }
00940   completeQueue.removeRef (cmd);
00941 
00942   // start a new listing to find the type of the folder
00943   enum IMAP_TYPE type =
00944     parseURL(_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
00945   if (type == ITYPE_BOX)
00946   {
00947     bool ask = ( aInfo.find( "ASKUSER" ) != -1 );
00948     if ( ask &&
00949         messageBox(QuestionYesNo,
00950           i18n("The following folder will be created on the server: %1 "
00951                "What do you want to store in this folder?").arg( aBox ),
00952           i18n("Create Folder"),
00953           i18n("&Messages"), i18n("&Subfolders")) == KMessageBox::No )
00954     {
00955       cmd = doCommand(imapCommand::clientDelete(aBox));
00956       completeQueue.removeRef (cmd);
00957       cmd = doCommand(imapCommand::clientCreate(aBox + aDelimiter));
00958       if (cmd->result () != "OK")
00959       {
00960         error (ERR_COULD_NOT_MKDIR, _url.prettyURL());
00961         completeQueue.removeRef (cmd);
00962         return;
00963       }
00964       completeQueue.removeRef (cmd);
00965     }
00966   }
00967 
00968   cmd = doCommand(imapCommand::clientSubscribe(aBox));
00969   completeQueue.removeRef(cmd);
00970 
00971   finished ();
00972 }
00973 
00974 void
00975 IMAP4Protocol::copy (const KURL & src, const KURL & dest, int, bool overwrite)
00976 {
00977   kdDebug(7116) << "IMAP4::copy - [" << (overwrite ? "Overwrite" : "NoOverwrite") << "] " << src.prettyURL() << " -> " << dest.prettyURL() << endl;
00978   QString sBox, sSequence, sLType, sSection, sValidity, sDelimiter, sInfo;
00979   QString dBox, dSequence, dLType, dSection, dValidity, dDelimiter, dInfo;
00980   enum IMAP_TYPE sType =
00981     parseURL (src, sBox, sSection, sLType, sSequence, sValidity, sDelimiter, sInfo);
00982   enum IMAP_TYPE dType =
00983     parseURL (dest, dBox, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo);
00984 
00985   // see if we have to create anything
00986   if (dType != ITYPE_BOX && dType != ITYPE_DIR_AND_BOX)
00987   {
00988     // this might be konqueror
00989     int sub = dBox.find (sBox);
00990 
00991     // might be moving to upper folder
00992     if (sub > 0)
00993     {
00994       KURL testDir = dest;
00995 
00996       QString subDir = dBox.right (dBox.length () - dBox.findRev ('/'));
00997       QString topDir = dBox.left (sub);
00998       testDir.setPath ("/" + topDir);
00999       dType =
01000         parseURL (testDir, topDir, dSection, dLType, dSequence, dValidity,
01001           dDelimiter, dInfo);
01002 
01003       kdDebug(7116) << "IMAP4::copy - checking this destination " << topDir << endl;
01004       // see if this is what the user wants
01005       if (dType == ITYPE_BOX || dType == ITYPE_DIR_AND_BOX)
01006       {
01007         kdDebug(7116) << "IMAP4::copy - assuming this destination " << topDir << endl;
01008         dBox = topDir;
01009       }
01010       else
01011       {
01012 
01013         // maybe if we create a new mailbox
01014         topDir = "/" + topDir + subDir;
01015         testDir.setPath (topDir);
01016         kdDebug(7116) << "IMAP4::copy - checking this destination " << topDir << endl;
01017         dType =
01018           parseURL (testDir, topDir, dSection, dLType, dSequence, dValidity,
01019             dDelimiter, dInfo);
01020         if (dType != ITYPE_BOX && dType != ITYPE_DIR_AND_BOX)
01021         {
01022           // ok then we'll create a mailbox
01023           imapCommand *cmd = doCommand (imapCommand::clientCreate (topDir));
01024 
01025           // on success we'll use it, else we'll just try to create the given dir
01026           if (cmd->result () == "OK")
01027           {
01028             kdDebug(7116) << "IMAP4::copy - assuming this destination " << topDir << endl;
01029             dType = ITYPE_BOX;
01030             dBox = topDir;
01031           }
01032           else
01033           {
01034             completeQueue.removeRef (cmd);
01035             cmd = doCommand (imapCommand::clientCreate (dBox));
01036             if (cmd->result () == "OK")
01037               dType = ITYPE_BOX;
01038             else
01039               error (ERR_COULD_NOT_WRITE, dest.prettyURL());
01040           }
01041           completeQueue.removeRef (cmd);
01042         }
01043       }
01044 
01045     }
01046   }
01047   if (sType == ITYPE_MSG || sType == ITYPE_BOX || sType == ITYPE_DIR_AND_BOX)
01048   {
01049     //select the source box
01050     if (!assureBox(sBox, true)) return;
01051     kdDebug(7116) << "IMAP4::copy - " << sBox << " -> " << dBox << endl;
01052 
01053     //issue copy command
01054     imapCommand *cmd =
01055       doCommand (imapCommand::clientCopy (dBox, sSequence));
01056     if (cmd->result () != "OK")
01057     {
01058       kdError(5006) << "IMAP4::copy - " << cmd->resultInfo() << endl;
01059       error (ERR_COULD_NOT_WRITE, dest.prettyURL());
01060       completeQueue.removeRef (cmd);
01061       return;
01062     } else {
01063       if (hasCapability("UIDPLUS"))
01064       {
01065         QString uid = cmd->resultInfo();
01066         if (uid.find("COPYUID") != -1)
01067         {
01068           uid = uid.section(" ", 2, 3);
01069           uid.truncate(uid.length()-1);
01070           infoMessage("UID "+uid);
01071         }
01072       }
01073     }
01074     completeQueue.removeRef (cmd);
01075   }
01076   else
01077   {
01078     error (ERR_ACCESS_DENIED, src.prettyURL());
01079     return;
01080   }
01081   finished ();
01082 }
01083 
01084 void
01085 IMAP4Protocol::del (const KURL & _url, bool isFile)
01086 {
01087   kdDebug(7116) << "IMAP4::del - [" << (isFile ? "File" : "NoFile") << "] " << _url.prettyURL() << endl;
01088   QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
01089   enum IMAP_TYPE aType =
01090     parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
01091 
01092   switch (aType)
01093   {
01094   case ITYPE_BOX:
01095   case ITYPE_DIR_AND_BOX:
01096     if (!aSequence.isEmpty ())
01097     {
01098       if (aSequence == "*")
01099       {
01100         if (!assureBox (aBox, false)) return;
01101         imapCommand *cmd = doCommand (imapCommand::clientExpunge ());
01102         if (cmd->result () != "OK") {
01103           error (ERR_CANNOT_DELETE, _url.prettyURL());
01104           completeQueue.removeRef (cmd);
01105           return;
01106         }
01107         completeQueue.removeRef (cmd);
01108       }
01109       else
01110       {
01111         // if open for read/write
01112         if (!assureBox (aBox, false)) return;
01113         imapCommand *cmd =
01114           doCommand (imapCommand::
01115                      clientStore (aSequence, "+FLAGS.SILENT", "\\DELETED"));
01116         if (cmd->result () != "OK") {
01117           error (ERR_CANNOT_DELETE, _url.prettyURL());
01118           completeQueue.removeRef (cmd);
01119           return;
01120         }
01121         completeQueue.removeRef (cmd);
01122       }
01123     }
01124     else
01125     {
01126       if (getCurrentBox() == aBox)
01127       {
01128         imapCommand *cmd = doCommand(imapCommand::clientClose());
01129         completeQueue.removeRef(cmd);
01130         setState(ISTATE_LOGIN);
01131       }
01132       // We unsubscribe, otherwise we get ghost folders on UW-IMAP
01133       imapCommand *cmd = doCommand(imapCommand::clientUnsubscribe(aBox));
01134       completeQueue.removeRef(cmd);
01135       cmd = doCommand(imapCommand::clientDelete (aBox));
01136       // If this doesn't work, we try to empty the mailbox first
01137       if (cmd->result () != "OK")
01138       {
01139         completeQueue.removeRef(cmd);
01140         if (!assureBox(aBox, false)) return;
01141         bool stillOk = true;
01142         if (stillOk)
01143         {
01144           imapCommand *cmd = doCommand(
01145             imapCommand::clientStore("1:*", "+FLAGS.SILENT", "\\DELETED"));
01146           if (cmd->result () != "OK") stillOk = false;
01147           completeQueue.removeRef(cmd);
01148         }
01149         if (stillOk)
01150         {
01151           imapCommand *cmd = doCommand(imapCommand::clientClose());
01152           if (cmd->result () != "OK") stillOk = false;
01153           completeQueue.removeRef(cmd);
01154           setState(ISTATE_LOGIN);
01155         }
01156         if (stillOk)
01157         {
01158           imapCommand *cmd = doCommand (imapCommand::clientDelete(aBox));
01159           if (cmd->result () != "OK") stillOk = false;
01160           completeQueue.removeRef(cmd);
01161         }
01162         if (!stillOk)
01163         {
01164           error (ERR_COULD_NOT_RMDIR, _url.prettyURL());
01165           return;
01166         }
01167       } else {
01168         completeQueue.removeRef (cmd);
01169       }
01170     }
01171     break;
01172 
01173   case ITYPE_DIR:
01174     {
01175       imapCommand *cmd = doCommand (imapCommand::clientDelete (aBox));
01176       if (cmd->result () != "OK") {
01177         error (ERR_COULD_NOT_RMDIR, _url.prettyURL());
01178         completeQueue.removeRef (cmd);
01179         return;
01180       }
01181       completeQueue.removeRef (cmd);
01182     }
01183     break;
01184 
01185   case ITYPE_MSG:
01186     {
01187       // if open for read/write
01188       if (!assureBox (aBox, false)) return;
01189       imapCommand *cmd =
01190         doCommand (imapCommand::
01191                    clientStore (aSequence, "+FLAGS.SILENT", "\\DELETED"));
01192       if (cmd->result () != "OK") {
01193         error (ERR_CANNOT_DELETE, _url.prettyURL());
01194         completeQueue.removeRef (cmd);
01195         return;
01196       }
01197       completeQueue.removeRef (cmd);
01198     }
01199     break;
01200 
01201   case ITYPE_UNKNOWN:
01202   case ITYPE_ATTACH:
01203     error (ERR_CANNOT_DELETE, _url.prettyURL());
01204     break;
01205   }
01206   finished ();
01207 }
01208 
01209 /*
01210  * Copy a mail: data = 'C' + srcURL (KURL) + destURL (KURL)
01211  * Capabilities: data = 'c'. Result shipped in infoMessage() signal
01212  * No-op: data = 'N'
01213  * Namespace: data = 'n'. Result shipped in infoMessage() signal
01214  *                        The format is: section=namespace=delimiter
01215  *                        Note that the namespace can be empty
01216  * Unsubscribe: data = 'U' + URL (KURL)
01217  * Subscribe: data = 'u' + URL (KURL)
01218  * Change the status: data = 'S' + URL (KURL) + Flags (QCString)
01219  * ACL commands: data = 'A' + command + URL (KURL) + command-dependent args
01220  * AnnotateMore commands: data = 'M' + 'G'et/'S'et + URL + entry + command-dependent args
01221  * Search: data = 'E' + URL (KURL)
01222  */
01223 void
01224 IMAP4Protocol::special (const QByteArray & aData)
01225 {
01226   kdDebug(7116) << "IMAP4Protocol::special" << endl;
01227   if (!makeLogin()) return;
01228 
01229   QDataStream stream(aData, IO_ReadOnly);
01230 
01231   int tmp;
01232   stream >> tmp;
01233 
01234   switch (tmp) {
01235   case 'C':
01236   {
01237     // copy
01238     KURL src;
01239     KURL dest;
01240     stream >> src >> dest;
01241     copy(src, dest, 0, FALSE);
01242     break;
01243   }
01244   case 'c':
01245   {
01246     // capabilities
01247     infoMessage(imapCapabilities.join(" "));
01248     finished();
01249     break;
01250   }
01251   case 'N':
01252   {
01253     // NOOP
01254     imapCommand *cmd = doCommand(imapCommand::clientNoop());
01255     if (cmd->result () != "OK")
01256     {
01257       kdDebug(7116) << "NOOP did not succeed - connection broken" << endl;
01258       completeQueue.removeRef (cmd);
01259       error (ERR_CONNECTION_BROKEN, myHost);
01260       return;
01261     }
01262     completeQueue.removeRef (cmd);
01263     finished();
01264     break;
01265   }
01266   case 'n':
01267   {
01268     // namespace in the form "section=namespace=delimiter"
01269     // entries are separated by ,
01270     infoMessage( imapNamespaces.join(",") );
01271     finished();
01272     break;
01273   }
01274   case 'U':
01275   {
01276     // unsubscribe
01277     KURL _url;
01278     stream >> _url;
01279     QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
01280     parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
01281     imapCommand *cmd = doCommand(imapCommand::clientUnsubscribe(aBox));
01282     if (cmd->result () != "OK")
01283     {
01284       completeQueue.removeRef (cmd);
01285       error(ERR_SLAVE_DEFINED, i18n("Unsubscribe of folder %1 "
01286                                     "failed. The server returned: %2")
01287             .arg(_url.prettyURL())
01288             .arg(cmd->resultInfo()));
01289       return;
01290     }
01291     completeQueue.removeRef (cmd);
01292     finished();
01293     break;
01294   }
01295   case 'u':
01296   {
01297     // subscribe
01298     KURL _url;
01299     stream >> _url;
01300     QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
01301     parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
01302     imapCommand *cmd = doCommand(imapCommand::clientSubscribe(aBox));
01303     if (cmd->result () != "OK")
01304     {
01305       completeQueue.removeRef (cmd);
01306       error(ERR_SLAVE_DEFINED, i18n("Subscribe of folder %1 "
01307                                     "failed. The server returned: %2")
01308             .arg(_url.prettyURL())
01309             .arg(cmd->resultInfo()));
01310       return;
01311     }
01312     completeQueue.removeRef (cmd);
01313     finished();
01314     break;
01315   }
01316   case 'A':
01317   {
01318     // acl
01319     int cmd;
01320     stream >> cmd;
01321     if ( hasCapability( "ACL" ) ) {
01322       specialACLCommand( cmd, stream );
01323     } else {
01324       error( ERR_UNSUPPORTED_ACTION, "ACL" );
01325     }
01326     break;
01327   }
01328   case 'M':
01329   {
01330     // annotatemore
01331     int cmd;
01332     stream >> cmd;
01333     if ( hasCapability( "ANNOTATEMORE" ) ) {
01334       specialAnnotateMoreCommand( cmd, stream );
01335     } else {
01336       error( ERR_UNSUPPORTED_ACTION, "ANNOTATEMORE" );
01337     }
01338     break;
01339   }
01340   case 'S':
01341   {
01342     // status
01343     KURL _url;
01344     QCString newFlags;
01345     stream >> _url >> newFlags;
01346 
01347     QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
01348     parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
01349     if (!assureBox(aBox, false)) return;
01350     imapCommand *cmd = doCommand (imapCommand::
01351                                   clientStore (aSequence, "-FLAGS.SILENT",
01352                                                "\\SEEN \\ANSWERED \\FLAGGED \\DRAFT"));
01353     if (cmd->result () != "OK")
01354     {
01355       completeQueue.removeRef (cmd);
01356       error(ERR_COULD_NOT_WRITE, i18n("Changing the flags of message %1 "
01357                                       "failed.").arg(_url.prettyURL()));
01358       return;
01359     }
01360     completeQueue.removeRef (cmd);
01361     if (!newFlags.isEmpty())
01362     {
01363       cmd = doCommand (imapCommand::
01364                        clientStore (aSequence, "+FLAGS.SILENT", newFlags));
01365       if (cmd->result () != "OK")
01366       {
01367         completeQueue.removeRef (cmd);
01368         error(ERR_COULD_NOT_WRITE, i18n("Changing the flags of message %1 "
01369                                         "failed.").arg(_url.prettyURL()));
01370         return;
01371       }
01372       completeQueue.removeRef (cmd);
01373     }
01374     finished();
01375     break;
01376   }
01377   case 'E':
01378   {
01379     // search
01380     specialSearchCommand( stream );
01381     break;
01382   }
01383   default:
01384     kdWarning(7116) << "Unknown command in special(): " << tmp << endl;
01385     error( ERR_UNSUPPORTED_ACTION, QString(QChar(tmp)) );
01386     break;
01387   }
01388 }
01389 
01390 void
01391 IMAP4Protocol::specialACLCommand( int command, QDataStream& stream )
01392 {
01393   // All commands start with the URL to the box
01394   KURL _url;
01395   stream >> _url;
01396   QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
01397   parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
01398 
01399   switch( command ) {
01400   case 'S': // SETACL
01401   {
01402     QString user, acl;
01403     stream >> user >> acl;
01404     kdDebug(7116) << "SETACL " << aBox << " " << user << " " << acl << endl;
01405     imapCommand *cmd = doCommand(imapCommand::clientSetACL(aBox, user, acl));
01406     if (cmd->result () != "OK")
01407     {
01408       error(ERR_SLAVE_DEFINED, i18n("Setting the Access Control List on folder %1 "
01409                                       "for user %2 failed. The server returned: %3")
01410             .arg(_url.prettyURL())
01411             .arg(user)
01412             .arg(cmd->resultInfo()));
01413       return;
01414     }
01415     completeQueue.removeRef (cmd);
01416     finished();
01417     break;
01418   }
01419   case 'D': // DELETEACL
01420   {
01421     QString user;
01422     stream >> user;
01423     kdDebug(7116) << "DELETEACL " << aBox << " " << user << endl;
01424     imapCommand *cmd = doCommand(imapCommand::clientDeleteACL(aBox, user));
01425     if (cmd->result () != "OK")
01426     {
01427       error(ERR_SLAVE_DEFINED, i18n("Deleting the Access Control List on folder %1 "
01428                                     "for user %2 failed. The server returned: %3")
01429             .arg(_url.prettyURL())
01430             .arg(user)
01431             .arg(cmd->resultInfo()));
01432       return;
01433     }
01434     completeQueue.removeRef (cmd);
01435     finished();
01436     break;
01437   }
01438   case 'G': // GETACL
01439   {
01440     kdDebug(7116) << "GETACL " << aBox << endl;
01441     imapCommand *cmd = doCommand(imapCommand::clientGetACL(aBox));
01442     if (cmd->result () != "OK")
01443     {
01444       error(ERR_SLAVE_DEFINED, i18n("Retrieving the Access Control List on folder %1 "
01445                                      "failed. The server returned: %2")
01446             .arg(_url.prettyURL())
01447             .arg(cmd->resultInfo()));
01448       return;
01449     }
01450     // Returning information to the application from a special() command isn't easy.
01451     // I'm reusing the infoMessage trick seen above (for capabilities), but this
01452     // limits me to a string instead of a stringlist. I'm using space as separator,
01453     // since I don't think it can be used in login names.
01454     kdDebug(7116) << getResults() << endl;
01455     infoMessage(getResults().join( " " ));
01456     finished();
01457     break;
01458   }
01459   case 'L': // LISTRIGHTS
01460   {
01461     // Do we need this one? It basically shows which rights are tied together, but that's all?
01462     error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) );
01463     break;
01464   }
01465   case 'M': // MYRIGHTS
01466   {
01467     kdDebug(7116) << "MYRIGHTS " << aBox << endl;
01468     imapCommand *cmd = doCommand(imapCommand::clientMyRights(aBox));
01469     if (cmd->result () != "OK")
01470     {
01471       error(ERR_SLAVE_DEFINED, i18n("Retrieving the Access Control List on folder %1 "
01472                                     "failed. The server returned: %2")
01473             .arg(_url.prettyURL())
01474             .arg(cmd->resultInfo()));
01475       return;
01476     }
01477     QStringList lst = getResults();
01478     kdDebug(7116) << "myrights results: " << lst << endl;
01479     if ( !lst.isEmpty() ) {
01480       Q_ASSERT( lst.count() == 1 );
01481       infoMessage( lst.first() );
01482     }
01483     finished();
01484     break;
01485   }
01486   default:
01487     kdWarning(7116) << "Unknown special ACL command:" << command << endl;
01488     error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) );
01489   }
01490 }
01491 
01492 void
01493 IMAP4Protocol::specialSearchCommand( QDataStream& stream )
01494 {
01495   kdDebug(7116) << "IMAP4Protocol::specialSearchCommand" << endl;
01496   KURL _url;
01497   stream >> _url;
01498   QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
01499   parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
01500   if (!assureBox(aBox, false)) return;
01501 
01502   imapCommand *cmd = doCommand (imapCommand::clientSearch( aSection ));
01503   if (cmd->result () != "OK")
01504   {
01505     error(ERR_SLAVE_DEFINED, i18n("Searching of folder %1 "
01506           "failed. The server returned: %2")
01507         .arg(aBox)
01508         .arg(cmd->resultInfo()));
01509     return;
01510   }
01511   completeQueue.removeRef(cmd);
01512   QStringList lst = getResults();
01513   kdDebug(7116) << "IMAP4Protocol::specialSearchCommand '" << aSection <<
01514     "' returns " << lst << endl;
01515   infoMessage( lst.join( " " ) );
01516 
01517   finished();
01518 }
01519 
01520 void
01521 IMAP4Protocol::specialAnnotateMoreCommand( int command, QDataStream& stream )
01522 {
01523   // All commands start with the URL to the box
01524   KURL _url;
01525   stream >> _url;
01526   QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
01527   parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
01528 
01529   switch( command ) {
01530   case 'S': // SETANNOTATION
01531   {
01532     // Params:
01533     //  KURL URL of the mailbox
01534     //  QString entry (should be an actual entry name, no % or *; empty for server entries)
01535     //  QMap<QString,QString> attributes (name and value)
01536     QString entry;
01537     QMap<QString, QString> attributes;
01538     stream >> entry >> attributes;
01539     kdDebug(7116) << "SETANNOTATION " << aBox << " " << entry << " " << attributes.count() << " attributes" << endl;
01540     imapCommand *cmd = doCommand(imapCommand::clientSetAnnotation(aBox, entry, attributes));
01541     if (cmd->result () != "OK")
01542     {
01543       error(ERR_SLAVE_DEFINED, i18n("Setting the annotation %1 on folder %2 "
01544                                     " failed. The server returned: %3")
01545             .arg(entry)
01546             .arg(_url.prettyURL())
01547             .arg(cmd->resultInfo()));
01548       return;
01549     }
01550     completeQueue.removeRef (cmd);
01551     finished();
01552     break;
01553   }
01554   case 'G': // GETANNOTATION.
01555   {
01556     // Params:
01557     //  KURL URL of the mailbox
01558     //  QString entry (should be an actual entry name, no % or *; empty for server entries)
01559     //  QStringList attributes (list of attributes to be retrieved, possibly with % or *)
01560     QString entry;
01561     QStringList attributeNames;
01562     stream >> entry >> attributeNames;
01563     kdDebug(7116) << "GETANNOTATION " << aBox << " " << entry << " " << attributeNames << endl;
01564     imapCommand *cmd = doCommand(imapCommand::clientGetAnnotation(aBox, entry, attributeNames));
01565     if (cmd->result () != "OK")
01566     {
01567       error(ERR_SLAVE_DEFINED, i18n("Retrieving the annotation %1 on folder %2 "
01568                                      "failed. The server returned: %3")
01569             .arg(entry)
01570             .arg(_url.prettyURL())
01571             .arg(cmd->resultInfo()));
01572       return;
01573     }
01574     // Returning information to the application from a special() command isn't easy.
01575     // I'm reusing the infoMessage trick seen above (for capabilities and acls), but this
01576     // limits me to a string instead of a stringlist. Let's use \r as separator.
01577     kdDebug(7116) << getResults() << endl;
01578     infoMessage(getResults().join( "\r" ));
01579     finished();
01580     break;
01581   }
01582   default:
01583     kdWarning(7116) << "Unknown special annotate command:" << command << endl;
01584     error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) );
01585   }
01586 }
01587 
01588 void
01589 IMAP4Protocol::rename (const KURL & src, const KURL & dest, bool overwrite)
01590 {
01591   kdDebug(7116) << "IMAP4::rename - [" << (overwrite ? "Overwrite" : "NoOverwrite") << "] " << src.prettyURL() << " -> " << dest.prettyURL() << endl;
01592   QString sBox, sSequence, sLType, sSection, sValidity, sDelimiter, sInfo;
01593   QString dBox, dSequence, dLType, dSection, dValidity, dDelimiter, dInfo;
01594   enum IMAP_TYPE sType =
01595     parseURL (src, sBox, sSection, sLType, sSequence, sValidity, sDelimiter, sInfo, false);
01596   enum IMAP_TYPE dType =
01597     parseURL (dest, dBox, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo, false);
01598 
01599   if (dType == ITYPE_UNKNOWN)
01600   {
01601     switch (sType)
01602     {
01603     case ITYPE_BOX:
01604     case ITYPE_DIR:
01605     case ITYPE_DIR_AND_BOX:
01606       {
01607         if (getState() == ISTATE_SELECT && sBox == getCurrentBox())
01608         {
01609           kdDebug(7116) << "IMAP4::rename - close " << getCurrentBox() << endl;
01610           // mailbox can only be renamed if it is closed
01611           imapCommand *cmd = doCommand (imapCommand::clientClose());
01612           bool ok = cmd->result() == "OK";
01613           completeQueue.removeRef(cmd);
01614           if (!ok)
01615           {
01616             error(ERR_CANNOT_RENAME, i18n("Unable to close mailbox."));
01617             return;
01618           }
01619           setState(ISTATE_LOGIN);
01620         }
01621         imapCommand *cmd = doCommand (imapCommand::clientRename (sBox, dBox));
01622         if (cmd->result () != "OK") {
01623           error (ERR_CANNOT_RENAME, cmd->result ());
01624           completeQueue.removeRef (cmd);
01625           return;
01626         }
01627         completeQueue.removeRef (cmd);
01628       }
01629       break;
01630 
01631     case ITYPE_MSG:
01632     case ITYPE_ATTACH:
01633     case ITYPE_UNKNOWN:
01634       error (ERR_CANNOT_RENAME, src.prettyURL());
01635       break;
01636     }
01637   }
01638   else
01639   {
01640     error (ERR_CANNOT_RENAME, src.prettyURL());
01641     return;
01642   }
01643   finished ();
01644 }
01645 
01646 void
01647 IMAP4Protocol::slave_status ()
01648 {
01649   bool connected = (getState() != ISTATE_NO) && isConnectionValid();
01650   kdDebug(7116) << "IMAP4::slave_status " << connected << endl;
01651   slaveStatus ( connected ? myHost : QString::null, connected );
01652 }
01653 
01654 void
01655 IMAP4Protocol::dispatch (int command, const QByteArray & data)
01656 {
01657   kdDebug(7116) << "IMAP4::dispatch - command=" << command << endl;
01658   KIO::TCPSlaveBase::dispatch (command, data);
01659 }
01660 
01661 void
01662 IMAP4Protocol::stat (const KURL & _url)
01663 {
01664   kdDebug(7116) << "IMAP4::stat - " << _url.prettyURL() << endl;
01665   QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
01666   // parseURL with caching
01667   enum IMAP_TYPE aType =
01668     parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter,
01669         aInfo, true);
01670 
01671   UDSEntry entry;
01672   UDSAtom atom;
01673 
01674   atom.m_uds = UDS_NAME;
01675   atom.m_str = aBox;
01676   entry.append (atom);
01677 
01678   if (!aSection.isEmpty())
01679   {
01680     if (getState() == ISTATE_SELECT && aBox == getCurrentBox())
01681     {
01682       imapCommand *cmd = doCommand (imapCommand::clientClose());
01683       bool ok = cmd->result() == "OK";
01684       completeQueue.removeRef(cmd);
01685       if (!ok)
01686       {
01687         error(ERR_COULD_NOT_STAT, aBox);
01688         return;
01689       }
01690       setState(ISTATE_LOGIN);
01691     }
01692     bool ok = false;
01693     QString cmdInfo;
01694     if (aType == ITYPE_MSG || aType == ITYPE_ATTACH)
01695       ok = true;
01696     else
01697     {
01698       imapCommand *cmd = doCommand(imapCommand::clientStatus(aBox, aSection));
01699       ok = cmd->result() == "OK";
01700       cmdInfo = cmd->resultInfo();
01701       completeQueue.removeRef(cmd);
01702     }
01703     if (!ok)
01704     {
01705       bool found = false;
01706       imapCommand *cmd = doCommand (imapCommand::clientList ("", aBox));
01707       if (cmd->result () == "OK")
01708       {
01709         for (QValueListIterator < imapList > it = listResponses.begin ();
01710              it != listResponses.end (); ++it)
01711         {
01712           if (aBox == (*it).name ()) found = true;
01713         }
01714       }
01715       completeQueue.removeRef (cmd);
01716       if (found)
01717         error(ERR_COULD_NOT_STAT, aBox);
01718       else
01719         error(KIO::ERR_DOES_NOT_EXIST, aBox);
01720       return;
01721     }
01722     if ((aSection == "UIDNEXT" && getStatus().uidNextAvailable())
01723       || (aSection == "UNSEEN" && getStatus().unseenAvailable()))
01724     {
01725       atom.m_uds = UDS_SIZE;
01726       atom.m_str = QString::null;
01727       atom.m_long = (aSection == "UIDNEXT") ? getStatus().uidNext()
01728         : getStatus().unseen();
01729       entry.append(atom);
01730     }
01731   } else
01732   if (aType == ITYPE_BOX || aType == ITYPE_DIR_AND_BOX || aType == ITYPE_MSG ||
01733       aType == ITYPE_ATTACH)
01734   {
01735     ulong validity = 0;
01736     // see if the box is already in select/examine state
01737     if (aBox == getCurrentBox ())
01738       validity = selectInfo.uidValidity ();
01739     else
01740     {
01741       // do a status lookup on the box
01742       // only do this if the box is not selected
01743       // the server might change the validity for new select/examine
01744       imapCommand *cmd =
01745         doCommand (imapCommand::clientStatus (aBox, "UIDVALIDITY"));
01746       completeQueue.removeRef (cmd);
01747       validity = getStatus ().uidValidity ();
01748     }
01749     validity = 0;               // temporary
01750 
01751     if (aType == ITYPE_BOX || aType == ITYPE_DIR_AND_BOX)
01752     {
01753       // has no or an invalid uidvalidity
01754       if (validity > 0 && validity != aValidity.toULong ())
01755       {
01756         //redirect
01757         KURL newUrl = _url;
01758 
01759         newUrl.setPath ("/" + aBox + ";UIDVALIDITY=" +
01760                         QString::number(validity));
01761         kdDebug(7116) << "IMAP4::stat - redirecting to " << newUrl.prettyURL() << endl;
01762         redirection (newUrl);
01763       }
01764     }
01765     else if (aType == ITYPE_MSG || aType == ITYPE_ATTACH)
01766     {
01767       //must determine if this message exists
01768       //cause konqueror will check this on paste operations
01769 
01770       // has an invalid uidvalidity
01771       // or no messages in box
01772       if (validity > 0 && validity != aValidity.toULong ())
01773       {
01774         aType = ITYPE_UNKNOWN;
01775         kdDebug(7116) << "IMAP4::stat - url has invalid validity [" << validity << "d] " << _url.prettyURL() << endl;
01776       }
01777     }
01778   }
01779 
01780   atom.m_uds = UDS_MIME_TYPE;
01781   atom.m_str = getMimeType (aType);
01782   entry.append (atom);
01783 
01784   kdDebug(7116) << "IMAP4: stat: " << atom.m_str << endl;
01785   switch (aType)
01786   {
01787   case ITYPE_DIR:
01788     atom.m_uds = UDS_FILE_TYPE;
01789     atom.m_str = QString::null;
01790     atom.m_long = S_IFDIR;
01791     entry.append (atom);
01792     break;
01793 
01794   case ITYPE_BOX:
01795   case ITYPE_DIR_AND_BOX:
01796     atom.m_uds = UDS_FILE_TYPE;
01797     atom.m_str = QString::null;
01798     atom.m_long = S_IFDIR;
01799     entry.append (atom);
01800     break;
01801 
01802   case ITYPE_MSG:
01803   case ITYPE_ATTACH:
01804     atom.m_uds = UDS_FILE_TYPE;
01805     atom.m_str = QString::null;
01806     atom.m_long = S_IFREG;
01807     entry.append (atom);
01808     break;
01809 
01810   case ITYPE_UNKNOWN:
01811     error (ERR_DOES_NOT_EXIST, _url.prettyURL());
01812     break;
01813   }
01814 
01815   statEntry (entry);
01816   kdDebug(7116) << "IMAP4::stat - Finishing stat" << endl;
01817   finished ();
01818 }
01819 
01820 void IMAP4Protocol::openConnection()
01821 {
01822   if (makeLogin()) connected();
01823 }
01824 
01825 void IMAP4Protocol::closeConnection()
01826 {
01827   if (getState() == ISTATE_NO) return;
01828   if (getState() == ISTATE_SELECT && metaData("expunge") == "auto")
01829   {
01830     imapCommand *cmd = doCommand (imapCommand::clientExpunge());
01831     completeQueue.removeRef (cmd);
01832   }
01833   if (getState() != ISTATE_CONNECT)
01834   {
01835     imapCommand *cmd = doCommand (imapCommand::clientLogout());
01836     completeQueue.removeRef (cmd);
01837   }
01838   closeDescriptor();
01839   setState(ISTATE_NO);
01840   completeQueue.clear();
01841   sentQueue.clear();
01842   lastHandled = 0;
01843   currentBox = QString::null;
01844   readBufferLen = 0;
01845 }
01846 
01847 bool IMAP4Protocol::makeLogin ()
01848 {
01849   if (getState () == ISTATE_LOGIN || getState () == ISTATE_SELECT)
01850     return true;
01851 
01852   kdDebug(7116) << "IMAP4::makeLogin - checking login" << endl;
01853   bool alreadyConnected = getState() == ISTATE_CONNECT;
01854   kdDebug(7116) << "IMAP4::makeLogin - alreadyConnected " << alreadyConnected << endl;
01855   if (alreadyConnected || connectToHost (myHost.latin1(), myPort))
01856   {
01857 //      fcntl (m_iSock, F_SETFL, (fcntl (m_iSock, F_GETFL) | O_NDELAY));
01858 
01859     setState(ISTATE_CONNECT);
01860 
01861     myAuth = metaData("auth");
01862     myTLS  = metaData("tls");
01863     kdDebug(7116) << "myAuth: " << myAuth << endl;
01864 
01865     imapCommand *cmd;
01866 
01867     unhandled.clear ();
01868     if (!alreadyConnected) while (!parseLoop ());    //get greeting
01869     QString greeting;
01870     if (!unhandled.isEmpty()) greeting = unhandled.first().stripWhiteSpace();
01871     unhandled.clear ();       //get rid of it
01872     cmd = doCommand (new imapCommand ("CAPABILITY", ""));
01873 
01874     kdDebug(7116) << "IMAP4: setHost: capability" << endl;
01875     for (QStringList::Iterator it = imapCapabilities.begin ();
01876          it != imapCapabilities.end (); ++it)
01877     {
01878       kdDebug(7116) << "'" << (*it) << "'" << endl;
01879     }
01880     completeQueue.removeRef (cmd);
01881 
01882     if (!hasCapability("IMAP4") && !hasCapability("IMAP4rev1"))
01883     {
01884       error(ERR_COULD_NOT_LOGIN, i18n("The server %1 supports neither "
01885         "IMAP4 nor IMAP4rev1.\nIt identified itself with: %2")
01886         .arg(myHost).arg(greeting));
01887       closeConnection();
01888       return false;
01889     }
01890 
01891     if (metaData("nologin") == "on") return TRUE;
01892 
01893     if (myTLS == "on" && !hasCapability(QString("STARTTLS")))
01894     {
01895       error(ERR_COULD_NOT_LOGIN, i18n("The server does not support TLS.\n"
01896         "Disable this security feature to connect unencrypted."));
01897       closeConnection();
01898       return false;
01899     }
01900     if ((myTLS == "on" || (canUseTLS() && myTLS != "off")) &&
01901       hasCapability(QString("STARTTLS")))
01902     {
01903       imapCommand *cmd = doCommand (imapCommand::clientStartTLS());
01904       if (cmd->result () == "OK")
01905       {
01906         completeQueue.removeRef(cmd);
01907         int tlsrc = startTLS();
01908         if (tlsrc == 1)
01909         {
01910           kdDebug(7116) << "TLS mode has been enabled." << endl;
01911           imapCommand *cmd2 = doCommand (new imapCommand ("CAPABILITY", ""));
01912           for (QStringList::Iterator it = imapCapabilities.begin ();
01913                                      it != imapCapabilities.end (); ++it)
01914           {
01915             kdDebug(7116) << "'" << (*it) << "'" << endl;
01916           }
01917           completeQueue.removeRef (cmd2);
01918         } else {
01919           kdWarning(7116) << "TLS mode setup has failed.  Aborting." << endl;
01920           error (ERR_COULD_NOT_LOGIN, i18n("Starting TLS failed."));
01921           closeConnection();
01922           return false;
01923         }
01924       } else completeQueue.removeRef(cmd);
01925     }
01926 
01927     if (myAuth.isEmpty () || myAuth == "*") {
01928       if (hasCapability (QString ("LOGINDISABLED"))) {
01929         error (ERR_COULD_NOT_LOGIN, i18n("LOGIN is disabled by the server."));
01930         closeConnection();
01931         return false;
01932       }
01933     }
01934     else {
01935       if (!hasCapability (QString ("AUTH=") + myAuth)) {
01936         error (ERR_COULD_NOT_LOGIN, i18n("The authentication method %1 is not "
01937           "supported by the server.").arg(myAuth));
01938         closeConnection();
01939         return false;
01940       }
01941     }
01942 
01943     if ( greeting.contains(  QRegExp(  "Cyrus IMAP4 v2.1" ) ) ) {
01944       removeCapability( "ANNOTATEMORE" );
01945     }
01946 
01947     kdDebug(7116) << "IMAP4::makeLogin - attempting login" << endl;
01948 
01949     KIO::AuthInfo authInfo;
01950     authInfo.username = myUser;
01951     authInfo.password = myPass;
01952     authInfo.prompt = i18n ("Username and password for your IMAP account:");
01953 
01954     kdDebug(7116) << "IMAP4::makeLogin - open_PassDlg said user=" << myUser << " pass=xx" << endl;
01955 
01956     QString resultInfo;
01957     if (myAuth.isEmpty () || myAuth == "*")
01958     {
01959       if (myUser.isEmpty () || myPass.isEmpty ()) {
01960         if(openPassDlg (authInfo)) {
01961           myUser = authInfo.username;
01962           myPass = authInfo.password;
01963         }
01964       }
01965       if (!clientLogin (myUser, myPass, resultInfo))
01966         error(KIO::ERR_COULD_NOT_LOGIN, i18n("Unable to login. Probably the "
01967         "password is wrong.\nThe server %1 replied:\n%2").arg(myHost).arg(resultInfo));
01968     }
01969     else
01970     {
01971 #ifdef HAVE_LIBSASL2
01972       if (!clientAuthenticate (this, authInfo, myHost, myAuth, mySSL, resultInfo))
01973         error(KIO::ERR_COULD_NOT_LOGIN, i18n("Unable to authenticate via %1.\n"
01974     "The server %2 replied:\n%3").arg(myAuth).arg(myHost).arg(resultInfo));
01975       else {
01976         myUser = authInfo.username;
01977         myPass = authInfo.password;
01978       }
01979 #else
01980       error(KIO::ERR_COULD_NOT_LOGIN, i18n("SASL authentication is not compiled into kio_imap4."));
01981 #endif
01982     }
01983     if ( hasCapability("NAMESPACE") )
01984     {
01985       // get all namespaces and save the namespace - delimiter association
01986       cmd = doCommand( imapCommand::clientNamespace() );
01987       if (cmd->result () == "OK")
01988       {
01989         kdDebug(7116) << "makeLogin - registered namespaces" << endl;
01990       }
01991       completeQueue.removeRef (cmd);
01992     }
01993     // get the default delimiter (empty listing)
01994     cmd = doCommand( imapCommand::clientList("", "") );
01995     if (cmd->result () == "OK")
01996     {
01997       QValueListIterator < imapList > it = listResponses.begin();
01998       if ( it == listResponses.end() )
01999       {
02000           // empty answer - this is a buggy imap server
02001           // as a fallback we fire a normal listing and take the first answer
02002           completeQueue.removeRef (cmd);
02003           cmd = doCommand( imapCommand::clientList("", "%") );
02004           if (cmd->result () == "OK")
02005           {
02006               it = listResponses.begin();
02007           }
02008       }
02009       if ( it != listResponses.end() )
02010       {
02011         namespaceToDelimiter[QString::null] = (*it).hierarchyDelimiter();
02012         kdDebug(7116) << "makeLogin - delimiter for empty ns='" <<
02013           (*it).hierarchyDelimiter() << "'" << endl;
02014         if ( !hasCapability("NAMESPACE") )
02015         {
02016           // server does not support namespaces
02017           QString nsentry = QString::number( 0 ) + "=="
02018             + (*it).hierarchyDelimiter();
02019           imapNamespaces.append( nsentry );
02020         }
02021       }
02022     }
02023     completeQueue.removeRef (cmd);
02024   } else {
02025     kdDebug(7116) << "makeLogin - NO login" << endl;
02026   }
02027 
02028   return getState() == ISTATE_LOGIN;
02029 }
02030 
02031 void
02032 IMAP4Protocol::parseWriteLine (const QString & aStr)
02033 {
02034   //kdDebug(7116) << "Writing: " << aStr << endl;
02035   QCString writer = aStr.utf8();
02036   int len = writer.length();
02037 
02038   // append CRLF if necessary
02039   if (len == 0 || (writer[len - 1] != '\n')) {
02040     len += 2;
02041     writer += "\r\n";
02042   }
02043 
02044   // write it
02045   write(writer.data(), len);
02046 }
02047 
02048 QString
02049 IMAP4Protocol::getMimeType (enum IMAP_TYPE aType)
02050 {
02051   switch (aType)
02052   {
02053   case ITYPE_DIR:
02054     return "inode/directory";
02055     break;
02056 
02057   case ITYPE_BOX:
02058     return "message/digest";
02059     break;
02060 
02061   case ITYPE_DIR_AND_BOX:
02062     return "message/directory";
02063     break;
02064 
02065   case ITYPE_MSG:
02066     return "message/rfc822";
02067     break;
02068 
02069   // this should be handled by flushOutput
02070   case ITYPE_ATTACH:
02071     return "application/octet-stream";
02072     break;
02073 
02074   case ITYPE_UNKNOWN:
02075   default:
02076     return "unknown/unknown";
02077   }
02078 }
02079 
02080 
02081 
02082 void
02083 IMAP4Protocol::doListEntry (const KURL & _url, int stretch, imapCache * cache,
02084   bool withFlags, bool withSubject)
02085 {
02086   KURL aURL = _url;
02087   aURL.setQuery (QString::null);
02088   const QString encodedUrl = aURL.url(0, 106); // utf-8
02089   doListEntry(encodedUrl, stretch, cache, withFlags, withSubject);
02090 }
02091 
02092 
02093 
02094 void
02095 IMAP4Protocol::doListEntry (const QString & encodedUrl, int stretch, imapCache * cache,
02096   bool withFlags, bool withSubject)
02097 {
02098   if (cache)
02099   {
02100     UDSEntry entry;
02101     UDSAtom atom;
02102 
02103     entry.clear ();
02104 
02105     const QString uid = QString::number(cache->getUid());
02106 
02107     atom.m_uds = UDS_NAME;
02108     atom.m_str = uid;
02109     atom.m_long = 0;
02110     if (stretch > 0)
02111     {
02112       atom.m_str = "0000000000000000" + atom.m_str;
02113       atom.m_str = atom.m_str.right (stretch);
02114     }
02115     if (withSubject)
02116     {
02117       mailHeader *header = cache->getHeader();
02118       if (header)
02119         atom.m_str += " " + header->getSubject();
02120     }
02121     entry.append (atom);
02122 
02123     atom.m_uds = UDS_URL;
02124     atom.m_str = encodedUrl; // utf-8
02125     if (atom.m_str[atom.m_str.length () - 1] != '/')
02126       atom.m_str += '/';
02127     atom.m_str += ";UID=" + uid;
02128     atom.m_long = 0;
02129     entry.append (atom);
02130 
02131     atom.m_uds = UDS_FILE_TYPE;
02132     atom.m_str = QString::null;
02133     atom.m_long = S_IFREG;
02134     entry.append (atom);
02135 
02136     atom.m_uds = UDS_SIZE;
02137     atom.m_long = cache->getSize();
02138     entry.append (atom);
02139 
02140     atom.m_uds = UDS_MIME_TYPE;
02141     atom.m_str = "message/rfc822";
02142     atom.m_long = 0;
02143     entry.append (atom);
02144 
02145     atom.m_uds = UDS_USER;
02146     atom.m_str = myUser;
02147     entry.append (atom);
02148 
02149     atom.m_uds = KIO::UDS_ACCESS;
02150     atom.m_long = (withFlags) ? cache->getFlags() : S_IRUSR | S_IXUSR | S_IWUSR;
02151     entry.append (atom);
02152 
02153     listEntry (entry, false);
02154   }
02155 }
02156 
02157 void
02158 IMAP4Protocol::doListEntry (const KURL & _url, const QString & myBox,
02159                             const imapList & item, bool appendPath)
02160 {
02161   KURL aURL = _url;
02162   aURL.setQuery (QString::null);
02163   UDSEntry entry;
02164   UDSAtom atom;
02165   int hdLen = item.hierarchyDelimiter().length();
02166 
02167   {
02168     // mailboxName will be appended to the path if appendPath is true
02169     QString mailboxName = item.name ();
02170 
02171     // some beautification
02172     if (mailboxName.find (myBox) == 0 && mailboxName.length() > myBox.length())
02173     {
02174       mailboxName =
02175         mailboxName.right (mailboxName.length () - myBox.length ());
02176     }
02177     if (mailboxName[0] == '/')
02178         mailboxName = mailboxName.right (mailboxName.length () - 1);
02179     if (mailboxName.left(hdLen) == item.hierarchyDelimiter())
02180       mailboxName = mailboxName.right(mailboxName.length () - hdLen);
02181     if (mailboxName.right(hdLen) == item.hierarchyDelimiter())
02182       mailboxName.truncate(mailboxName.length () - hdLen);
02183 
02184     atom.m_uds = UDS_NAME;
02185     if (!item.hierarchyDelimiter().isEmpty() &&
02186         mailboxName.find(item.hierarchyDelimiter()) != -1)
02187       atom.m_str = mailboxName.section(item.hierarchyDelimiter(), -1);
02188     else
02189       atom.m_str = mailboxName;
02190 
02191     // konqueror will die with an assertion failure otherwise
02192     if (atom.m_str.isEmpty ())
02193       atom.m_str = "..";
02194 
02195     if (!atom.m_str.isEmpty ())
02196     {
02197       atom.m_long = 0;
02198       entry.append (atom);
02199 
02200       if (!item.noSelect ())
02201       {
02202         atom.m_uds = UDS_MIME_TYPE;
02203         if (!item.noInferiors ())
02204         {
02205           atom.m_str = "message/directory";
02206         } else {
02207           atom.m_str = "message/digest";
02208         }
02209         atom.m_long = 0;
02210         entry.append (atom);
02211         mailboxName += '/';
02212 
02213         // explicitly set this as a directory for KFileDialog
02214         atom.m_uds = UDS_FILE_TYPE;
02215         atom.m_str = QString::null;
02216         atom.m_long = S_IFDIR;
02217         entry.append (atom);
02218       }
02219       else if (!item.noInferiors ())
02220       {
02221         atom.m_uds = UDS_MIME_TYPE;
02222         atom.m_str = "inode/directory";
02223         atom.m_long = 0;
02224         entry.append (atom);
02225         mailboxName += '/';
02226 
02227         // explicitly set this as a directory for KFileDialog
02228         atom.m_uds = UDS_FILE_TYPE;
02229         atom.m_str = QString::null;
02230         atom.m_long = S_IFDIR;
02231         entry.append (atom);
02232       }
02233       else
02234       {
02235         atom.m_uds = UDS_MIME_TYPE;
02236         atom.m_str = "unknown/unknown";
02237         atom.m_long = 0;
02238         entry.append (atom);
02239       }
02240 
02241       atom.m_uds = UDS_URL;
02242       QString path = aURL.path();
02243       atom.m_str = aURL.url (0, 106); // utf-8
02244       if (appendPath)
02245       {
02246         if (path[path.length() - 1] == '/' && !path.isEmpty() && path != "/")
02247           path.truncate(path.length() - 1);
02248         if (!path.isEmpty() && path != "/"
02249             && path.right(hdLen) != item.hierarchyDelimiter()) {
02250           path += item.hierarchyDelimiter();
02251         }
02252         path += mailboxName;
02253         if (path.upper() == "/INBOX/") {
02254             // make sure the client can rely on INBOX
02255             path = path.upper();
02256         }
02257       }
02258       aURL.setPath(path);
02259       atom.m_str = aURL.url(0, 106); // utf-8
02260       atom.m_long = 0;
02261       entry.append (atom);
02262 
02263       atom.m_uds = UDS_USER;
02264       atom.m_str = myUser;
02265       entry.append (atom);
02266 
02267       atom.m_uds = UDS_ACCESS;
02268       atom.m_long = S_IRUSR | S_IXUSR | S_IWUSR;
02269       entry.append (atom);
02270 
02271       atom.m_uds = UDS_EXTRA;
02272       atom.m_str = item.attributesAsString();
02273       atom.m_long = 0;
02274       entry.append (atom);
02275 
02276       listEntry (entry, false);
02277     }
02278   }
02279 }
02280 
02281 enum IMAP_TYPE
02282 IMAP4Protocol::parseURL (const KURL & _url, QString & _box,
02283                          QString & _section, QString & _type, QString & _uid,
02284                          QString & _validity, QString & _hierarchyDelimiter,
02285                          QString & _info, bool cache)
02286 {
02287   enum IMAP_TYPE retVal;
02288   retVal = ITYPE_UNKNOWN;
02289 
02290   imapParser::parseURL (_url, _box, _section, _type, _uid, _validity, _info);
02291 //  kdDebug(7116) << "URL: query - '" << KURL::decode_string(_url.query()) << "'" << endl;
02292 
02293   // get the delimiter
02294   QString myNamespace = namespaceForBox( _box );
02295   kdDebug(7116) << "IMAP4::parseURL - namespace=" << myNamespace << endl;
02296   if ( namespaceToDelimiter.contains(myNamespace) )
02297   {
02298     _hierarchyDelimiter = namespaceToDelimiter[myNamespace];
02299     kdDebug(7116) << "IMAP4::parseURL - delimiter=" << _hierarchyDelimiter << endl;
02300   }
02301 
02302   if (!_box.isEmpty ())
02303   {
02304     kdDebug(7116) << "IMAP4::parseURL - box=" << _box << endl;
02305 
02306     if (makeLogin ())
02307     {
02308       if (getCurrentBox () != _box ||
02309           _type == "LIST" || _type == "LSUB" || _type == "LSUBNOCHECK")
02310       {
02311         if ( cache )
02312         {
02313           // assume a normal box
02314           retVal = ITYPE_DIR_AND_BOX;
02315         } else
02316         {
02317           // start a listing for the box to get the type
02318           imapCommand *cmd;
02319 
02320           cmd = doCommand (imapCommand::clientList ("", _box));
02321           if (cmd->result () == "OK")
02322           {
02323             for (QValueListIterator < imapList > it = listResponses.begin ();
02324                 it != listResponses.end (); ++it)
02325             {
02326               //kdDebug(7116) << "IMAP4::parseURL - checking " << _box << " to " << (*it).name() << endl;
02327               if (_box == (*it).name ())
02328               {
02329                 if ( !(*it).hierarchyDelimiter().isEmpty() )
02330                   _hierarchyDelimiter = (*it).hierarchyDelimiter();
02331                 if ((*it).noSelect ())
02332                 {
02333                   retVal = ITYPE_DIR;
02334                 }
02335                 else if ((*it).noInferiors ())
02336                 {
02337                   retVal = ITYPE_BOX;
02338                 }
02339                 else
02340                 {
02341                   retVal = ITYPE_DIR_AND_BOX;
02342                 }
02343               }
02344             }
02345             // if we got no list response for the box see if it's a prefix
02346             if ( retVal == ITYPE_UNKNOWN &&
02347                  namespaceToDelimiter.contains(_box) ) {
02348               retVal = ITYPE_DIR;
02349             }
02350           } else {
02351             kdDebug(7116) << "IMAP4::parseURL - got error for " << _box << endl;
02352           }
02353           completeQueue.removeRef (cmd);
02354         } // cache
02355       }
02356       else // current == box
02357       {
02358         retVal = ITYPE_BOX;
02359       }
02360     }
02361     else
02362       kdDebug(7116) << "IMAP4::parseURL: no login!" << endl;
02363 
02364   }
02365   else // empty box
02366   {
02367     // the root is just a dir
02368     kdDebug(7116) << "IMAP4: parseURL: box [root]" << endl;
02369     retVal = ITYPE_DIR;
02370   }
02371 
02372   // see if it is a real sequence or a simple uid
02373   if (retVal == ITYPE_BOX || retVal == ITYPE_DIR_AND_BOX)
02374   {
02375     if (!_uid.isEmpty ())
02376     {
02377       if (_uid.find (':') == -1 && _uid.find (',') == -1
02378           && _uid.find ('*') == -1)
02379         retVal = ITYPE_MSG;
02380     }
02381   }
02382   if (retVal == ITYPE_MSG)
02383   {
02384     if ( (_section.find ("BODY.PEEK[", 0, false) != -1 ||
02385           _section.find ("BODY[", 0, false) != -1) &&
02386          _section.find(".MIME") == -1 &&
02387          _section.find(".HEADER") == -1 )
02388       retVal = ITYPE_ATTACH;
02389   }
02390   if ( _hierarchyDelimiter.isEmpty() &&
02391        (_type == "LIST" || _type == "LSUB" || _type == "LSUBNOCHECK") )
02392   {
02393     // this shouldn't happen but when the delimiter is really empty
02394     // we try to reconstruct it from the URL
02395     if (!_box.isEmpty())
02396     {
02397       int start = _url.path().findRev(_box);
02398       if (start != -1)
02399         _hierarchyDelimiter = _url.path().mid(start-1, start);
02400       kdDebug(7116) << "IMAP4::parseURL - reconstructed delimiter:" << _hierarchyDelimiter
02401         << " from URL " << _url.path() << endl;
02402     }
02403     if (_hierarchyDelimiter.isEmpty())
02404       _hierarchyDelimiter = "/";
02405   }
02406   kdDebug(7116) << "IMAP4::parseURL - return " << retVal << endl;
02407 
02408   return retVal;
02409 }
02410 
02411 int
02412 IMAP4Protocol::outputLine (const QCString & _str, int len)
02413 {
02414   if (len == -1) {
02415     len = _str.length();
02416   }
02417 
02418   if (cacheOutput)
02419   {
02420     if ( !outputBuffer.isOpen() ) {
02421       outputBuffer.open(IO_WriteOnly);
02422     }
02423     outputBuffer.at(outputBufferIndex);
02424     outputBuffer.writeBlock(_str.data(), len);
02425     outputBufferIndex += len;
02426     return 0;
02427   }
02428 
02429   QByteArray temp;
02430   bool relay = relayEnabled;
02431 
02432   relayEnabled = true;
02433   temp.setRawData (_str.data (), len);
02434   parseRelay (temp);
02435   temp.resetRawData (_str.data (), len);
02436 
02437   relayEnabled = relay;
02438   return 0;
02439 }
02440 
02441 void IMAP4Protocol::flushOutput(QString contentEncoding)
02442 {
02443   // send out cached data to the application
02444   if (outputBufferIndex == 0)
02445     return;
02446   outputBuffer.close();
02447   outputCache.resize(outputBufferIndex);
02448   if (decodeContent)
02449   {
02450     // get the coding from the MIME header
02451     QByteArray decoded;
02452     if (contentEncoding.find("quoted-printable", 0, false) == 0)
02453       decoded = KCodecs::quotedPrintableDecode(outputCache);
02454     else if (contentEncoding.find("base64", 0, false) == 0)
02455       KCodecs::base64Decode(outputCache, decoded);
02456     else
02457       decoded = outputCache;
02458 
02459     QString mimetype = KMimeType::findByContent( decoded )->name();
02460     kdDebug(7116) << "IMAP4::flushOutput - mimeType " << mimetype << endl;
02461     mimeType(mimetype);
02462     decodeContent = false;
02463     data( decoded );
02464   } else {
02465     data( outputCache );
02466   }
02467   mProcessedSize += outputBufferIndex;
02468   processedSize( mProcessedSize );
02469   outputBufferIndex = 0;
02470   outputCache[0] = '\0';
02471   outputBuffer.setBuffer(outputCache);
02472 }
02473 
02474 ssize_t IMAP4Protocol::myRead(void *data, ssize_t len)
02475 {
02476   if (readBufferLen)
02477   {
02478     ssize_t copyLen = (len < readBufferLen) ? len : readBufferLen;
02479     memcpy(data, readBuffer, copyLen);
02480     readBufferLen -= copyLen;
02481     if (readBufferLen) memcpy(readBuffer, &readBuffer[copyLen], readBufferLen);
02482     return copyLen;
02483   }
02484   if (!isConnectionValid()) return 0;
02485   waitForResponse( responseTimeout() );
02486   return read(data, len);
02487 }
02488 
02489 bool
02490 IMAP4Protocol::assureBox (const QString & aBox, bool readonly)
02491 {
02492   if (aBox.isEmpty()) return false;
02493 
02494   imapCommand *cmd = 0;
02495 
02496   if (aBox != getCurrentBox () || (!getSelected().readWrite() && !readonly))
02497   {
02498     // open the box with the appropriate mode
02499     kdDebug(7116) << "IMAP4Protocol::assureBox - opening box" << endl;
02500     selectInfo = imapInfo();
02501     cmd = doCommand (imapCommand::clientSelect (aBox, readonly));
02502     bool ok = cmd->result() == "OK";
02503     QString cmdInfo = cmd->resultInfo();
02504     completeQueue.removeRef (cmd);
02505 
02506     if (!ok)
02507     {
02508       bool found = false;
02509       cmd = doCommand (imapCommand::clientList ("", aBox));
02510       if (cmd->result () == "OK")
02511       {
02512         for (QValueListIterator < imapList > it = listResponses.begin ();
02513              it != listResponses.end (); ++it)
02514         {
02515           if (aBox == (*it).name ()) found = true;
02516         }
02517       }
02518       completeQueue.removeRef (cmd);
02519       if (found) {
02520         if (cmdInfo.find("permission", 0, false) != -1) {
02521           // not allowed to enter this folder
02522           error(ERR_ACCESS_DENIED, cmdInfo);
02523         } else {
02524           error(ERR_SLAVE_DEFINED, i18n("Unable to open folder %1. The server replied: %2").arg(aBox).arg(cmdInfo));
02525         }
02526       } else {
02527         error(KIO::ERR_DOES_NOT_EXIST, aBox);
02528       }
02529       return false;
02530     }
02531   }
02532   else
02533   {
02534     // Give the server a chance to deliver updates every ten seconds.
02535     // Doing this means a server roundtrip and since assureBox is called
02536     // after every mail, we do it with a timeout.
02537     kdDebug(7116) << "IMAP4Protocol::assureBox - reusing box" << endl;
02538     if ( mTimeOfLastNoop.secsTo( QDateTime::currentDateTime() ) > 10 ) {
02539       cmd = doCommand (imapCommand::clientNoop ());
02540       completeQueue.removeRef (cmd);
02541       mTimeOfLastNoop = QDateTime::currentDateTime();
02542       kdDebug(7116) << "IMAP4Protocol::assureBox - noop timer fired" << endl;
02543     }
02544   }
02545 
02546   // if it is the mode we want
02547   if (!getSelected().readWrite() && !readonly)
02548   {
02549     error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, aBox);
02550     return false;
02551   }
02552 
02553   return true;
02554 }
KDE Home | KDE Accessibility Home | Description of Access Keys