summaryrefslogtreecommitdiffstats
path: root/kioslaves/imap4/imap4.cc
diff options
context:
space:
mode:
Diffstat (limited to 'kioslaves/imap4/imap4.cc')
-rw-r--r--kioslaves/imap4/imap4.cc2721
1 files changed, 2721 insertions, 0 deletions
diff --git a/kioslaves/imap4/imap4.cc b/kioslaves/imap4/imap4.cc
new file mode 100644
index 000000000..eeef10fd4
--- /dev/null
+++ b/kioslaves/imap4/imap4.cc
@@ -0,0 +1,2721 @@
+/**********************************************************************
+ *
+ * imap4.cc - IMAP4rev1 KIOSlave
+ * Copyright (C) 2001-2002 Michael Haeckel <haeckel@kde.org>
+ * Copyright (C) 1999 John Corey
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Send comments and bug fixes to jcorey@fruity.ath.cx
+ *
+ *********************************************************************/
+
+/**
+ * @class IMAP4Protocol
+ * @note References:
+ * - RFC 2060 - Internet Message Access Protocol - Version 4rev1 - December 1996
+ * - RFC 2192 - IMAP URL Scheme - September 1997
+ * - RFC 1731 - IMAP Authentication Mechanisms - December 1994
+ * (Discusses KERBEROSv4, GSSAPI, and S/Key)
+ * - RFC 2195 - IMAP/POP AUTHorize Extension for Simple Challenge/Response
+ * - September 1997 (CRAM-MD5 authentication method)
+ * - RFC 2104 - HMAC: Keyed-Hashing for Message Authentication - February 1997
+ * - RFC 2086 - IMAP4 ACL extension - January 1997
+ * - http://www.ietf.org/internet-drafts/draft-daboo-imap-annotatemore-05.txt
+ * IMAP ANNOTATEMORE draft - April 2004.
+ *
+ *
+ * Supported URLs:
+ * \verbatim
+imap://server/
+imap://user:pass@server/
+imap://user;AUTH=method:pass@server/
+imap://server/folder/
+ * \endverbatim
+ * These URLs cause the following actions (in order):
+ * - Prompt for user/pass, list all folders in home directory
+ * - Uses LOGIN to log in
+ * - Uses AUTHENTICATE to log in
+ * - List messages in folder
+ *
+ * @note API notes:
+ * Not receiving the required write access for a folder means
+ * ERR_CANNOT_OPEN_FOR_WRITING.
+ * ERR_DOES_NOT_EXIST is reserved for folders.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "imap4.h"
+
+#include "rfcdecoder.h"
+
+#include <sys/stat.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#ifdef HAVE_LIBSASL2
+extern "C" {
+#include <sasl/sasl.h>
+}
+#endif
+
+#include <qbuffer.h>
+#include <qdatetime.h>
+#include <qregexp.h>
+#include <kprotocolmanager.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kio/connection.h>
+#include <kio/slaveinterface.h>
+#include <kio/passdlg.h>
+#include <klocale.h>
+#include <kmimetype.h>
+#include <kmdcodec.h>
+
+#include "kdepimmacros.h"
+
+#define IMAP_PROTOCOL "imap"
+#define IMAP_SSL_PROTOCOL "imaps"
+
+using namespace KIO;
+
+extern "C"
+{
+ void sigalrm_handler (int);
+ KDE_EXPORT int kdemain (int argc, char **argv);
+}
+
+int
+kdemain (int argc, char **argv)
+{
+ kdDebug(7116) << "IMAP4::kdemain" << endl;
+
+ KInstance instance ("kio_imap4");
+ if (argc != 4)
+ {
+ fprintf(stderr, "Usage: kio_imap4 protocol domain-socket1 domain-socket2\n");
+ ::exit (-1);
+ }
+
+#ifdef HAVE_LIBSASL2
+ if ( sasl_client_init( NULL ) != SASL_OK ) {
+ fprintf(stderr, "SASL library initialization failed!\n");
+ ::exit (-1);
+ }
+#endif
+
+ //set debug handler
+
+ IMAP4Protocol *slave;
+ if (strcasecmp (argv[1], IMAP_SSL_PROTOCOL) == 0)
+ slave = new IMAP4Protocol (argv[2], argv[3], true);
+ else if (strcasecmp (argv[1], IMAP_PROTOCOL) == 0)
+ slave = new IMAP4Protocol (argv[2], argv[3], false);
+ else
+ abort ();
+ slave->dispatchLoop ();
+ delete slave;
+
+#ifdef HAVE_LIBSASL2
+ sasl_done();
+#endif
+
+ return 0;
+}
+
+void
+sigchld_handler (int signo)
+{
+ int pid, status;
+
+ while (true && signo == SIGCHLD)
+ {
+ pid = waitpid (-1, &status, WNOHANG);
+ if (pid <= 0)
+ {
+ // Reinstall signal handler, since Linux resets to default after
+ // the signal occurred ( BSD handles it different, but it should do
+ // no harm ).
+ signal (SIGCHLD, sigchld_handler);
+ return;
+ }
+ }
+}
+
+IMAP4Protocol::IMAP4Protocol (const QCString & pool, const QCString & app, bool isSSL):TCPSlaveBase ((isSSL ? 993 : 143),
+ (isSSL ? IMAP_SSL_PROTOCOL : IMAP_PROTOCOL), pool,
+ app, isSSL), imapParser (), mimeIO (), outputBuffer(outputCache)
+{
+ outputBufferIndex = 0;
+ mySSL = isSSL;
+ readBuffer[0] = 0x00;
+ relayEnabled = false;
+ readBufferLen = 0;
+ cacheOutput = false;
+ decodeContent = false;
+ mTimeOfLastNoop = QDateTime();
+}
+
+IMAP4Protocol::~IMAP4Protocol ()
+{
+ closeDescriptor();
+ kdDebug(7116) << "IMAP4: Finishing" << endl;
+}
+
+void
+IMAP4Protocol::get (const KURL & _url)
+{
+ if (!makeLogin()) return;
+ kdDebug(7116) << "IMAP4::get - " << _url.prettyURL() << endl;
+ QString aBox, aSequence, aType, aSection, aValidity, aDelimiter, aInfo;
+ enum IMAP_TYPE aEnum =
+ parseURL (_url, aBox, aSection, aType, aSequence, aValidity, aDelimiter, aInfo);
+ if (aEnum != ITYPE_ATTACH)
+ mimeType (getMimeType(aEnum));
+ if (aInfo == "DECODE")
+ decodeContent = true;
+
+ if (aSequence == "0:0" && getState() == ISTATE_SELECT)
+ {
+ imapCommand *cmd = doCommand (imapCommand::clientNoop());
+ completeQueue.removeRef(cmd);
+ }
+
+ if (aSequence.isEmpty ())
+ {
+ aSequence = "1:*";
+ }
+
+ mProcessedSize = 0;
+ imapCommand *cmd = NULL;
+ if (!assureBox (aBox, true)) return;
+
+#ifdef USE_VALIDITY
+ if (selectInfo.uidValidityAvailable () && !aValidity.isEmpty ()
+ && selectInfo.uidValidity () != aValidity.toULong ())
+ {
+ // this url is stale
+ error (ERR_COULD_NOT_READ, _url.prettyURL());
+ return;
+ }
+ else
+#endif
+ {
+ // The "section" specified by the application can be:
+ // * empty (which means body, size and flags)
+ // * a known keyword, like STRUCTURE, ENVELOPE, HEADER, BODY.PEEK[...]
+ // (in which case the slave has some logic to add the necessary items)
+ // * Otherwise, it specifies the exact data items to request. In this case, all
+ // the logic is in the app.
+
+ QString aUpper = aSection.upper();
+ if (aUpper.find ("STRUCTURE") != -1)
+ {
+ aSection = "BODYSTRUCTURE";
+ }
+ else if (aUpper.find ("ENVELOPE") != -1)
+ {
+ aSection = "UID RFC822.SIZE FLAGS ENVELOPE";
+ if (hasCapability("IMAP4rev1")) {
+ aSection += " BODY.PEEK[HEADER.FIELDS (REFERENCES)]";
+ } else {
+ // imap4 does not know HEADER.FIELDS
+ aSection += " RFC822.HEADER.LINES (REFERENCES)";
+ }
+ }
+ else if (aUpper == "HEADER")
+ {
+ aSection = "UID RFC822.HEADER RFC822.SIZE FLAGS";
+ }
+ else if (aUpper.find ("BODY.PEEK[") != -1)
+ {
+ if (aUpper.find ("BODY.PEEK[]") != -1)
+ {
+ if (!hasCapability("IMAP4rev1")) // imap4 does not know BODY.PEEK[]
+ aSection.replace("BODY.PEEK[]", "RFC822.PEEK");
+ }
+ aSection.prepend("UID RFC822.SIZE FLAGS ");
+ }
+ else if (aSection.isEmpty())
+ {
+ aSection = "UID BODY[] RFC822.SIZE FLAGS";
+ }
+ if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX)
+ {
+ // write the digest header
+ cacheOutput = true;
+ outputLine
+ ("Content-Type: multipart/digest; boundary=\"IMAPDIGEST\"\r\n", 55);
+ if (selectInfo.recentAvailable ())
+ outputLineStr ("X-Recent: " +
+ QString::number(selectInfo.recent ()) + "\r\n");
+ if (selectInfo.countAvailable ())
+ outputLineStr ("X-Count: " + QString::number(selectInfo.count ()) +
+ "\r\n");
+ if (selectInfo.unseenAvailable ())
+ outputLineStr ("X-Unseen: " +
+ QString::number(selectInfo.unseen ()) + "\r\n");
+ if (selectInfo.uidValidityAvailable ())
+ outputLineStr ("X-uidValidity: " +
+ QString::number(selectInfo.uidValidity ()) +
+ "\r\n");
+ if (selectInfo.uidNextAvailable ())
+ outputLineStr ("X-UidNext: " +
+ QString::number(selectInfo.uidNext ()) + "\r\n");
+ if (selectInfo.flagsAvailable ())
+ outputLineStr ("X-Flags: " + QString::number(selectInfo.flags ()) +
+ "\r\n");
+ if (selectInfo.permanentFlagsAvailable ())
+ outputLineStr ("X-PermanentFlags: " +
+ QString::number(selectInfo.permanentFlags ()) + "\r\n");
+ if (selectInfo.readWriteAvailable ()) {
+ if (selectInfo.readWrite()) {
+ outputLine ("X-Access: Read/Write\r\n", 22);
+ } else {
+ outputLine ("X-Access: Read only\r\n", 21);
+ }
+ }
+ outputLine ("\r\n", 2);
+ flushOutput(QString::null);
+ cacheOutput = false;
+ }
+
+ if (aEnum == ITYPE_MSG || (aEnum == ITYPE_ATTACH && !decodeContent))
+ relayEnabled = true; // normal mode, relay data
+
+ if (aSequence != "0:0")
+ {
+ QString contentEncoding;
+ if (aEnum == ITYPE_ATTACH && decodeContent)
+ {
+ // get the MIME header and fill getLastHandled()
+ QString mySection = aSection;
+ mySection.replace("]", ".MIME]");
+ cmd = sendCommand (imapCommand::clientFetch (aSequence, mySection));
+ do
+ {
+ while (!parseLoop ()) ;
+ }
+ while (!cmd->isComplete ());
+ completeQueue.removeRef (cmd);
+ // get the content encoding now because getLastHandled will be cleared
+ if (getLastHandled() && getLastHandled()->getHeader())
+ contentEncoding = getLastHandled()->getHeader()->getEncoding();
+
+ // from here on collect the data
+ // it is send to the client in flushOutput in one go
+ // needed to decode the content
+ cacheOutput = true;
+ }
+
+ cmd = sendCommand (imapCommand::clientFetch (aSequence, aSection));
+ int res;
+ aUpper = aSection.upper();
+ do
+ {
+ while (!(res = parseLoop())) ;
+ if (res == -1) break;
+
+ mailHeader *lastone = 0;
+ imapCache *cache = getLastHandled ();
+ if (cache)
+ lastone = cache->getHeader ();
+
+ if (cmd && !cmd->isComplete ())
+ {
+ if ((aUpper.find ("BODYSTRUCTURE") != -1)
+ || (aUpper.find ("FLAGS") != -1)
+ || (aUpper.find ("UID") != -1)
+ || (aUpper.find ("ENVELOPE") != -1)
+ || (aUpper.find ("BODY.PEEK[0]") != -1
+ && (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX)))
+ {
+ if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX)
+ {
+ // write the mime header (default is here message/rfc822)
+ outputLine ("--IMAPDIGEST\r\n", 14);
+ cacheOutput = true;
+ if (cache && cache->getUid () != 0)
+ outputLineStr ("X-UID: " +
+ QString::number(cache->getUid ()) + "\r\n");
+ if (cache && cache->getSize () != 0)
+ outputLineStr ("X-Length: " +
+ QString::number(cache->getSize ()) + "\r\n");
+ if (cache && !cache->getDate ().isEmpty())
+ outputLineStr ("X-Date: " + cache->getDate () + "\r\n");
+ if (cache && cache->getFlags () != 0)
+ outputLineStr ("X-Flags: " +
+ QString::number(cache->getFlags ()) + "\r\n");
+ } else cacheOutput = true;
+ if ( lastone && !decodeContent )
+ lastone->outputPart (*this);
+ cacheOutput = false;
+ flushOutput(contentEncoding);
+ }
+ } // if not complete
+ }
+ while (cmd && !cmd->isComplete ());
+ if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX)
+ {
+ // write the end boundary
+ outputLine ("--IMAPDIGEST--\r\n", 16);
+ }
+
+ completeQueue.removeRef (cmd);
+ }
+ }
+
+ // just to keep everybody happy when no data arrived
+ data (QByteArray ());
+
+ finished ();
+ relayEnabled = false;
+ cacheOutput = false;
+ kdDebug(7116) << "IMAP4::get - finished" << endl;
+}
+
+void
+IMAP4Protocol::listDir (const KURL & _url)
+{
+ kdDebug(7116) << " IMAP4::listDir - " << _url.prettyURL() << endl;
+
+ if (_url.path().isEmpty())
+ {
+ KURL url = _url;
+ url.setPath("/");
+ redirection( url );
+ finished();
+ return;
+ }
+
+ QString myBox, mySequence, myLType, mySection, myValidity, myDelimiter, myInfo;
+ // parseURL with caching
+ enum IMAP_TYPE myType =
+ parseURL (_url, myBox, mySection, myLType, mySequence, myValidity,
+ myDelimiter, myInfo, true);
+
+ if (!makeLogin()) return;
+
+ if (myType == ITYPE_DIR || myType == ITYPE_DIR_AND_BOX)
+ {
+ QString listStr = myBox;
+ imapCommand *cmd;
+
+ if (!listStr.isEmpty () && !listStr.endsWith(myDelimiter) &&
+ mySection != "FOLDERONLY")
+ listStr += myDelimiter;
+
+ if (mySection.isEmpty())
+ {
+ listStr += "%";
+ } else if (mySection == "COMPLETE") {
+ listStr += "*";
+ }
+ kdDebug(7116) << "IMAP4Protocol::listDir - listStr=" << listStr << endl;
+ cmd =
+ doCommand (imapCommand::clientList ("", listStr,
+ (myLType == "LSUB" || myLType == "LSUBNOCHECK")));
+ if (cmd->result () == "OK")
+ {
+ QString mailboxName;
+ UDSEntry entry;
+ UDSAtom atom;
+ KURL aURL = _url;
+ if (aURL.path().find(';') != -1)
+ aURL.setPath(aURL.path().left(aURL.path().find(';')));
+
+ kdDebug(7116) << "IMAP4Protocol::listDir - got " << listResponses.count () << endl;
+
+ if (myLType == "LSUB")
+ {
+ // fire the same command as LIST to check if the box really exists
+ QValueList<imapList> listResponsesSave = listResponses;
+ doCommand (imapCommand::clientList ("", listStr, false));
+ for (QValueListIterator < imapList > it = listResponsesSave.begin ();
+ it != listResponsesSave.end (); ++it)
+ {
+ bool boxOk = false;
+ for (QValueListIterator < imapList > it2 = listResponses.begin ();
+ it2 != listResponses.end (); ++it2)
+ {
+ if ((*it2).name() == (*it).name())
+ {
+ boxOk = true;
+ // copy the flags from the LIST-command
+ (*it) = (*it2);
+ break;
+ }
+ }
+ if (boxOk)
+ doListEntry (aURL, myBox, (*it), (mySection != "FOLDERONLY"));
+ else // this folder is dead
+ kdDebug(7116) << "IMAP4Protocol::listDir - suppress " << (*it).name() << endl;
+ }
+ listResponses = listResponsesSave;
+ }
+ else // LIST or LSUBNOCHECK
+ {
+ for (QValueListIterator < imapList > it = listResponses.begin ();
+ it != listResponses.end (); ++it)
+ {
+ doListEntry (aURL, myBox, (*it), (mySection != "FOLDERONLY"));
+ }
+ }
+ entry.clear ();
+ listEntry (entry, true);
+ }
+ else
+ {
+ error (ERR_CANNOT_ENTER_DIRECTORY, _url.prettyURL());
+ completeQueue.removeRef (cmd);
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ }
+ if ((myType == ITYPE_BOX || myType == ITYPE_DIR_AND_BOX)
+ && myLType != "LIST" && myLType != "LSUB" && myLType != "LSUBNOCHECK")
+ {
+ KURL aURL = _url;
+ aURL.setQuery (QString::null);
+ const QString encodedUrl = aURL.url(0, 106); // utf-8
+
+ if (!_url.query ().isEmpty ())
+ {
+ QString query = KURL::decode_string (_url.query ());
+ query = query.right (query.length () - 1);
+ if (!query.isEmpty())
+ {
+ imapCommand *cmd = NULL;
+
+ if (!assureBox (myBox, true)) return;
+
+ if (!selectInfo.countAvailable() || selectInfo.count())
+ {
+ cmd = doCommand (imapCommand::clientSearch (query));
+ if (cmd->result() != "OK")
+ {
+ error(ERR_UNSUPPORTED_ACTION, _url.prettyURL());
+ completeQueue.removeRef (cmd);
+ return;
+ }
+ completeQueue.removeRef (cmd);
+
+ QStringList list = getResults ();
+ int stretch = 0;
+
+ if (selectInfo.uidNextAvailable ())
+ stretch = QString::number(selectInfo.uidNext ()).length ();
+ UDSEntry entry;
+ imapCache fake;
+
+ for (QStringList::ConstIterator it = list.begin(); it != list.end();
+ ++it)
+ {
+ fake.setUid((*it).toULong());
+ doListEntry (encodedUrl, stretch, &fake);
+ }
+ entry.clear ();
+ listEntry (entry, true);
+ }
+ }
+ }
+ else
+ {
+ if (!assureBox (myBox, true)) return;
+
+ kdDebug(7116) << "IMAP4: select returned:" << endl;
+ if (selectInfo.recentAvailable ())
+ kdDebug(7116) << "Recent: " << selectInfo.recent () << "d" << endl;
+ if (selectInfo.countAvailable ())
+ kdDebug(7116) << "Count: " << selectInfo.count () << "d" << endl;
+ if (selectInfo.unseenAvailable ())
+ kdDebug(7116) << "Unseen: " << selectInfo.unseen () << "d" << endl;
+ if (selectInfo.uidValidityAvailable ())
+ kdDebug(7116) << "uidValidity: " << selectInfo.uidValidity () << "d" << endl;
+ if (selectInfo.flagsAvailable ())
+ kdDebug(7116) << "Flags: " << selectInfo.flags () << "d" << endl;
+ if (selectInfo.permanentFlagsAvailable ())
+ kdDebug(7116) << "PermanentFlags: " << selectInfo.permanentFlags () << "d" << endl;
+ if (selectInfo.readWriteAvailable ())
+ kdDebug(7116) << "Access: " << (selectInfo.readWrite ()? "Read/Write" : "Read only") << endl;
+
+#ifdef USE_VALIDITY
+ if (selectInfo.uidValidityAvailable ()
+ && selectInfo.uidValidity () != myValidity.toULong ())
+ {
+ //redirect
+ KURL newUrl = _url;
+
+ newUrl.setPath ("/" + myBox + ";UIDVALIDITY=" +
+ QString::number(selectInfo.uidValidity ()));
+ kdDebug(7116) << "IMAP4::listDir - redirecting to " << newUrl.prettyURL() << endl;
+ redirection (newUrl);
+
+
+ }
+ else
+#endif
+ if (selectInfo.count () > 0)
+ {
+ int stretch = 0;
+
+ if (selectInfo.uidNextAvailable ())
+ stretch = QString::number(selectInfo.uidNext ()).length ();
+ // kdDebug(7116) << selectInfo.uidNext() << "d used to stretch " << stretch << endl;
+ UDSEntry entry;
+
+ if (mySequence.isEmpty()) mySequence = "1:*";
+
+ bool withSubject = mySection.isEmpty();
+ if (mySection.isEmpty()) mySection = "UID RFC822.SIZE ENVELOPE";
+
+ bool withFlags = mySection.upper().find("FLAGS") != -1;
+ imapCommand *fetch =
+ sendCommand (imapCommand::
+ clientFetch (mySequence, mySection));
+ imapCache *cache;
+ do
+ {
+ while (!parseLoop ()) ;
+
+ cache = getLastHandled ();
+
+ if (cache && !fetch->isComplete())
+ doListEntry (encodedUrl, stretch, cache, withFlags, withSubject);
+ }
+ while (!fetch->isComplete ());
+ entry.clear ();
+ listEntry (entry, true);
+ }
+ }
+ }
+ if ( !selectInfo.alert().isNull() ) {
+ if ( !myBox.isEmpty() ) {
+ warning( i18n( "Message from %1 while processing '%2': %3" ).arg( myHost, myBox, selectInfo.alert() ) );
+ } else {
+ warning( i18n( "Message from %1: %2" ).arg( myHost, selectInfo.alert() ) );
+ }
+ selectInfo.setAlert( 0 );
+ }
+
+ kdDebug(7116) << "IMAP4Protocol::listDir - Finishing listDir" << endl;
+ finished ();
+}
+
+void
+IMAP4Protocol::setHost (const QString & _host, int _port,
+ const QString & _user, const QString & _pass)
+{
+ if (myHost != _host || myPort != _port || myUser != _user || myPass != _pass)
+ { // what's the point of doing 4 string compares to avoid 4 string copies?
+ // DF: I guess to avoid calling closeConnection() unnecessarily.
+ if (!myHost.isEmpty ())
+ closeConnection ();
+ myHost = _host;
+ myPort = _port;
+ myUser = _user;
+ myPass = _pass;
+ }
+}
+
+void
+IMAP4Protocol::parseRelay (const QByteArray & buffer)
+{
+ if (relayEnabled) {
+ // relay data immediately
+ data( buffer );
+ mProcessedSize += buffer.size();
+ processedSize( mProcessedSize );
+ } else if (cacheOutput)
+ {
+ // collect data
+ if ( !outputBuffer.isOpen() ) {
+ outputBuffer.open(IO_WriteOnly);
+ }
+ outputBuffer.at(outputBufferIndex);
+ outputBuffer.writeBlock(buffer, buffer.size());
+ outputBufferIndex += buffer.size();
+ }
+}
+
+void
+IMAP4Protocol::parseRelay (ulong len)
+{
+ if (relayEnabled)
+ totalSize (len);
+}
+
+
+bool IMAP4Protocol::parseRead(QByteArray & buffer, ulong len, ulong relay)
+{
+ char buf[8192];
+ while (buffer.size() < len)
+ {
+ ssize_t readLen = myRead(buf, QMIN(len - buffer.size(), sizeof(buf) - 1));
+ if (readLen == 0)
+ {
+ kdDebug(7116) << "parseRead: readLen == 0 - connection broken" << endl;
+ error (ERR_CONNECTION_BROKEN, myHost);
+ setState(ISTATE_CONNECT);
+ closeConnection();
+ return FALSE;
+ }
+ if (relay > buffer.size())
+ {
+ QByteArray relayData;
+ ssize_t relbuf = relay - buffer.size();
+ int currentRelay = QMIN(relbuf, readLen);
+ relayData.setRawData(buf, currentRelay);
+ parseRelay(relayData);
+ relayData.resetRawData(buf, currentRelay);
+ }
+ {
+ QBuffer stream (buffer);
+ stream.open (IO_WriteOnly);
+ stream.at (buffer.size ());
+ stream.writeBlock (buf, readLen);
+ stream.close ();
+ }
+ }
+ return (buffer.size() == len);
+}
+
+
+bool IMAP4Protocol::parseReadLine (QByteArray & buffer, ulong relay)
+{
+ if (myHost.isEmpty()) return FALSE;
+
+ while (true) {
+ ssize_t copyLen = 0;
+ if (readBufferLen > 0)
+ {
+ while (copyLen < readBufferLen && readBuffer[copyLen] != '\n') copyLen++;
+ if (copyLen < readBufferLen) copyLen++;
+ if (relay > 0)
+ {
+ QByteArray relayData;
+
+ if (copyLen < (ssize_t) relay)
+ relay = copyLen;
+ relayData.setRawData (readBuffer, relay);
+ parseRelay (relayData);
+ relayData.resetRawData (readBuffer, relay);
+// kdDebug(7116) << "relayed : " << relay << "d" << endl;
+ }
+ // append to buffer
+ {
+ QBuffer stream (buffer);
+
+ stream.open (IO_WriteOnly);
+ stream.at (buffer.size ());
+ stream.writeBlock (readBuffer, copyLen);
+ stream.close ();
+// kdDebug(7116) << "appended " << copyLen << "d got now " << buffer.size() << endl;
+ }
+
+ readBufferLen -= copyLen;
+ if (readBufferLen)
+ memmove(readBuffer, &readBuffer[copyLen], readBufferLen);
+ if (buffer[buffer.size() - 1] == '\n') return TRUE;
+ }
+ if (!isConnectionValid())
+ {
+ kdDebug(7116) << "parseReadLine - connection broken" << endl;
+ error (ERR_CONNECTION_BROKEN, myHost);
+ setState(ISTATE_CONNECT);
+ closeConnection();
+ return FALSE;
+ }
+ if (!waitForResponse( responseTimeout() ))
+ {
+ error(ERR_SERVER_TIMEOUT, myHost);
+ setState(ISTATE_CONNECT);
+ closeConnection();
+ return FALSE;
+ }
+ readBufferLen = read(readBuffer, IMAP_BUFFER - 1);
+ if (readBufferLen == 0)
+ {
+ kdDebug(7116) << "parseReadLine: readBufferLen == 0 - connection broken" << endl;
+ error (ERR_CONNECTION_BROKEN, myHost);
+ setState(ISTATE_CONNECT);
+ closeConnection();
+ return FALSE;
+ }
+ }
+}
+
+void
+IMAP4Protocol::setSubURL (const KURL & _url)
+{
+ kdDebug(7116) << "IMAP4::setSubURL - " << _url.prettyURL() << endl;
+ KIO::TCPSlaveBase::setSubURL (_url);
+}
+
+void
+IMAP4Protocol::put (const KURL & _url, int, bool, bool)
+{
+ kdDebug(7116) << "IMAP4::put - " << _url.prettyURL() << endl;
+// KIO::TCPSlaveBase::put(_url,permissions,overwrite,resume);
+ QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
+ enum IMAP_TYPE aType =
+ parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
+
+ // see if it is a box
+ if (aType != ITYPE_BOX && aType != ITYPE_DIR_AND_BOX)
+ {
+ if (aBox[aBox.length () - 1] == '/')
+ aBox = aBox.right (aBox.length () - 1);
+ imapCommand *cmd = doCommand (imapCommand::clientCreate (aBox));
+
+ if (cmd->result () != "OK") {
+ error (ERR_COULD_NOT_WRITE, _url.prettyURL());
+ completeQueue.removeRef (cmd);
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ }
+ else
+ {
+ QPtrList < QByteArray > bufferList;
+ int length = 0;
+
+ int result;
+ // Loop until we got 'dataEnd'
+ do
+ {
+ QByteArray *buffer = new QByteArray ();
+ dataReq (); // Request for data
+ result = readData (*buffer);
+ if (result > 0)
+ {
+ bufferList.append (buffer);
+ length += result;
+ } else {
+ delete buffer;
+ }
+ }
+ while (result > 0);
+
+ if (result != 0)
+ {
+ error (ERR_ABORTED, _url.prettyURL());
+ return;
+ }
+
+ imapCommand *cmd =
+ sendCommand (imapCommand::clientAppend (aBox, aSection, length));
+ while (!parseLoop ()) ;
+
+ // see if server is waiting
+ if (!cmd->isComplete () && !getContinuation ().isEmpty ())
+ {
+ bool sendOk = true;
+ ulong wrote = 0;
+
+ QByteArray *buffer;
+ // send data to server
+ while (!bufferList.isEmpty () && sendOk)
+ {
+ buffer = bufferList.take (0);
+
+ sendOk =
+ (write (buffer->data (), buffer->size ()) ==
+ (ssize_t) buffer->size ());
+ wrote += buffer->size ();
+ processedSize(wrote);
+ delete buffer;
+ if (!sendOk)
+ {
+ error (ERR_CONNECTION_BROKEN, myHost);
+ completeQueue.removeRef (cmd);
+ setState(ISTATE_CONNECT);
+ closeConnection();
+ return;
+ }
+ }
+ parseWriteLine ("");
+ // Wait until cmd is complete, or connection breaks.
+ while (!cmd->isComplete () && getState() != ISTATE_NO)
+ parseLoop ();
+ if ( getState() == ISTATE_NO ) {
+ // TODO KDE4: pass cmd->resultInfo() as third argument.
+ // ERR_CONNECTION_BROKEN expects a host, no way to pass details about the problem.
+ error( ERR_CONNECTION_BROKEN, myHost );
+ completeQueue.removeRef (cmd);
+ closeConnection();
+ return;
+ }
+ else if (cmd->result () != "OK") {
+ error( ERR_SLAVE_DEFINED, cmd->resultInfo() );
+ completeQueue.removeRef (cmd);
+ return;
+ }
+ else
+ {
+ if (hasCapability("UIDPLUS"))
+ {
+ QString uid = cmd->resultInfo();
+ if (uid.find("APPENDUID") != -1)
+ {
+ uid = uid.section(" ", 2, 2);
+ uid.truncate(uid.length()-1);
+ infoMessage("UID "+uid);
+ }
+ }
+ // MUST reselect to get the new message
+ else if (aBox == getCurrentBox ())
+ {
+ cmd =
+ doCommand (imapCommand::
+ clientSelect (aBox, !selectInfo.readWrite ()));
+ completeQueue.removeRef (cmd);
+ }
+ }
+ }
+ else
+ {
+ //error (ERR_COULD_NOT_WRITE, myHost);
+ // Better ship the error message, e.g. "Over Quota"
+ error (ERR_SLAVE_DEFINED, cmd->resultInfo());
+ completeQueue.removeRef (cmd);
+ return;
+ }
+
+ completeQueue.removeRef (cmd);
+ }
+
+ finished ();
+}
+
+void
+IMAP4Protocol::mkdir (const KURL & _url, int)
+{
+ kdDebug(7116) << "IMAP4::mkdir - " << _url.prettyURL() << endl;
+ QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
+ parseURL(_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
+ kdDebug(7116) << "IMAP4::mkdir - create " << aBox << endl;
+ imapCommand *cmd = doCommand (imapCommand::clientCreate(aBox));
+
+ if (cmd->result () != "OK")
+ {
+ kdDebug(7116) << "IMAP4::mkdir - " << cmd->resultInfo() << endl;
+ error (ERR_COULD_NOT_MKDIR, _url.prettyURL());
+ completeQueue.removeRef (cmd);
+ return;
+ }
+ completeQueue.removeRef (cmd);
+
+ // start a new listing to find the type of the folder
+ enum IMAP_TYPE type =
+ parseURL(_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
+ if (type == ITYPE_BOX)
+ {
+ bool ask = ( aInfo.find( "ASKUSER" ) != -1 );
+ if ( ask &&
+ messageBox(QuestionYesNo,
+ i18n("The following folder will be created on the server: %1 "
+ "What do you want to store in this folder?").arg( aBox ),
+ i18n("Create Folder"),
+ i18n("&Messages"), i18n("&Subfolders")) == KMessageBox::No )
+ {
+ cmd = doCommand(imapCommand::clientDelete(aBox));
+ completeQueue.removeRef (cmd);
+ cmd = doCommand(imapCommand::clientCreate(aBox + aDelimiter));
+ if (cmd->result () != "OK")
+ {
+ error (ERR_COULD_NOT_MKDIR, _url.prettyURL());
+ completeQueue.removeRef (cmd);
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ }
+ }
+
+ cmd = doCommand(imapCommand::clientSubscribe(aBox));
+ completeQueue.removeRef(cmd);
+
+ finished ();
+}
+
+void
+IMAP4Protocol::copy (const KURL & src, const KURL & dest, int, bool overwrite)
+{
+ kdDebug(7116) << "IMAP4::copy - [" << (overwrite ? "Overwrite" : "NoOverwrite") << "] " << src.prettyURL() << " -> " << dest.prettyURL() << endl;
+ QString sBox, sSequence, sLType, sSection, sValidity, sDelimiter, sInfo;
+ QString dBox, dSequence, dLType, dSection, dValidity, dDelimiter, dInfo;
+ enum IMAP_TYPE sType =
+ parseURL (src, sBox, sSection, sLType, sSequence, sValidity, sDelimiter, sInfo);
+ enum IMAP_TYPE dType =
+ parseURL (dest, dBox, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo);
+
+ // see if we have to create anything
+ if (dType != ITYPE_BOX && dType != ITYPE_DIR_AND_BOX)
+ {
+ // this might be konqueror
+ int sub = dBox.find (sBox);
+
+ // might be moving to upper folder
+ if (sub > 0)
+ {
+ KURL testDir = dest;
+
+ QString subDir = dBox.right (dBox.length () - dBox.findRev ('/'));
+ QString topDir = dBox.left (sub);
+ testDir.setPath ("/" + topDir);
+ dType =
+ parseURL (testDir, topDir, dSection, dLType, dSequence, dValidity,
+ dDelimiter, dInfo);
+
+ kdDebug(7116) << "IMAP4::copy - checking this destination " << topDir << endl;
+ // see if this is what the user wants
+ if (dType == ITYPE_BOX || dType == ITYPE_DIR_AND_BOX)
+ {
+ kdDebug(7116) << "IMAP4::copy - assuming this destination " << topDir << endl;
+ dBox = topDir;
+ }
+ else
+ {
+
+ // maybe if we create a new mailbox
+ topDir = "/" + topDir + subDir;
+ testDir.setPath (topDir);
+ kdDebug(7116) << "IMAP4::copy - checking this destination " << topDir << endl;
+ dType =
+ parseURL (testDir, topDir, dSection, dLType, dSequence, dValidity,
+ dDelimiter, dInfo);
+ if (dType != ITYPE_BOX && dType != ITYPE_DIR_AND_BOX)
+ {
+ // ok then we'll create a mailbox
+ imapCommand *cmd = doCommand (imapCommand::clientCreate (topDir));
+
+ // on success we'll use it, else we'll just try to create the given dir
+ if (cmd->result () == "OK")
+ {
+ kdDebug(7116) << "IMAP4::copy - assuming this destination " << topDir << endl;
+ dType = ITYPE_BOX;
+ dBox = topDir;
+ }
+ else
+ {
+ completeQueue.removeRef (cmd);
+ cmd = doCommand (imapCommand::clientCreate (dBox));
+ if (cmd->result () == "OK")
+ dType = ITYPE_BOX;
+ else
+ error (ERR_COULD_NOT_WRITE, dest.prettyURL());
+ }
+ completeQueue.removeRef (cmd);
+ }
+ }
+
+ }
+ }
+ if (sType == ITYPE_MSG || sType == ITYPE_BOX || sType == ITYPE_DIR_AND_BOX)
+ {
+ //select the source box
+ if (!assureBox(sBox, true)) return;
+ kdDebug(7116) << "IMAP4::copy - " << sBox << " -> " << dBox << endl;
+
+ //issue copy command
+ imapCommand *cmd =
+ doCommand (imapCommand::clientCopy (dBox, sSequence));
+ if (cmd->result () != "OK")
+ {
+ kdError(5006) << "IMAP4::copy - " << cmd->resultInfo() << endl;
+ error (ERR_COULD_NOT_WRITE, dest.prettyURL());
+ completeQueue.removeRef (cmd);
+ return;
+ } else {
+ if (hasCapability("UIDPLUS"))
+ {
+ QString uid = cmd->resultInfo();
+ if (uid.find("COPYUID") != -1)
+ {
+ uid = uid.section(" ", 2, 3);
+ uid.truncate(uid.length()-1);
+ infoMessage("UID "+uid);
+ }
+ }
+ }
+ completeQueue.removeRef (cmd);
+ }
+ else
+ {
+ error (ERR_ACCESS_DENIED, src.prettyURL());
+ return;
+ }
+ finished ();
+}
+
+void
+IMAP4Protocol::del (const KURL & _url, bool isFile)
+{
+ kdDebug(7116) << "IMAP4::del - [" << (isFile ? "File" : "NoFile") << "] " << _url.prettyURL() << endl;
+ QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
+ enum IMAP_TYPE aType =
+ parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
+
+ switch (aType)
+ {
+ case ITYPE_BOX:
+ case ITYPE_DIR_AND_BOX:
+ if (!aSequence.isEmpty ())
+ {
+ if (aSequence == "*")
+ {
+ if (!assureBox (aBox, false)) return;
+ imapCommand *cmd = doCommand (imapCommand::clientExpunge ());
+ if (cmd->result () != "OK") {
+ error (ERR_CANNOT_DELETE, _url.prettyURL());
+ completeQueue.removeRef (cmd);
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ }
+ else
+ {
+ // if open for read/write
+ if (!assureBox (aBox, false)) return;
+ imapCommand *cmd =
+ doCommand (imapCommand::
+ clientStore (aSequence, "+FLAGS.SILENT", "\\DELETED"));
+ if (cmd->result () != "OK") {
+ error (ERR_CANNOT_DELETE, _url.prettyURL());
+ completeQueue.removeRef (cmd);
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ }
+ }
+ else
+ {
+ if (getCurrentBox() == aBox)
+ {
+ imapCommand *cmd = doCommand(imapCommand::clientClose());
+ completeQueue.removeRef(cmd);
+ setState(ISTATE_LOGIN);
+ }
+ // We unsubscribe, otherwise we get ghost folders on UW-IMAP
+ imapCommand *cmd = doCommand(imapCommand::clientUnsubscribe(aBox));
+ completeQueue.removeRef(cmd);
+ cmd = doCommand(imapCommand::clientDelete (aBox));
+ // If this doesn't work, we try to empty the mailbox first
+ if (cmd->result () != "OK")
+ {
+ completeQueue.removeRef(cmd);
+ if (!assureBox(aBox, false)) return;
+ bool stillOk = true;
+ if (stillOk)
+ {
+ imapCommand *cmd = doCommand(
+ imapCommand::clientStore("1:*", "+FLAGS.SILENT", "\\DELETED"));
+ if (cmd->result () != "OK") stillOk = false;
+ completeQueue.removeRef(cmd);
+ }
+ if (stillOk)
+ {
+ imapCommand *cmd = doCommand(imapCommand::clientClose());
+ if (cmd->result () != "OK") stillOk = false;
+ completeQueue.removeRef(cmd);
+ setState(ISTATE_LOGIN);
+ }
+ if (stillOk)
+ {
+ imapCommand *cmd = doCommand (imapCommand::clientDelete(aBox));
+ if (cmd->result () != "OK") stillOk = false;
+ completeQueue.removeRef(cmd);
+ }
+ if (!stillOk)
+ {
+ error (ERR_COULD_NOT_RMDIR, _url.prettyURL());
+ return;
+ }
+ } else {
+ completeQueue.removeRef (cmd);
+ }
+ }
+ break;
+
+ case ITYPE_DIR:
+ {
+ imapCommand *cmd = doCommand (imapCommand::clientDelete (aBox));
+ if (cmd->result () != "OK") {
+ error (ERR_COULD_NOT_RMDIR, _url.prettyURL());
+ completeQueue.removeRef (cmd);
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ }
+ break;
+
+ case ITYPE_MSG:
+ {
+ // if open for read/write
+ if (!assureBox (aBox, false)) return;
+ imapCommand *cmd =
+ doCommand (imapCommand::
+ clientStore (aSequence, "+FLAGS.SILENT", "\\DELETED"));
+ if (cmd->result () != "OK") {
+ error (ERR_CANNOT_DELETE, _url.prettyURL());
+ completeQueue.removeRef (cmd);
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ }
+ break;
+
+ case ITYPE_UNKNOWN:
+ case ITYPE_ATTACH:
+ error (ERR_CANNOT_DELETE, _url.prettyURL());
+ break;
+ }
+ finished ();
+}
+
+/*
+ * Copy a mail: data = 'C' + srcURL (KURL) + destURL (KURL)
+ * Capabilities: data = 'c'. Result shipped in infoMessage() signal
+ * No-op: data = 'N'
+ * Namespace: data = 'n'. Result shipped in infoMessage() signal
+ * The format is: section=namespace=delimiter
+ * Note that the namespace can be empty
+ * Unsubscribe: data = 'U' + URL (KURL)
+ * Subscribe: data = 'u' + URL (KURL)
+ * Change the status: data = 'S' + URL (KURL) + Flags (QCString)
+ * ACL commands: data = 'A' + command + URL (KURL) + command-dependent args
+ * AnnotateMore commands: data = 'M' + 'G'et/'S'et + URL + entry + command-dependent args
+ * Search: data = 'E' + URL (KURL)
+ * Quota commands: data = 'Q' + 'R'oot/'G'et/'S'et + URL + entry + command-dependent args
+ * Custom command: data = 'X' + 'N'ormal/'E'xtended + command + command-dependent args
+ */
+void
+IMAP4Protocol::special (const QByteArray & aData)
+{
+ kdDebug(7116) << "IMAP4Protocol::special" << endl;
+ if (!makeLogin()) return;
+
+ QDataStream stream(aData, IO_ReadOnly);
+
+ int tmp;
+ stream >> tmp;
+
+ switch (tmp) {
+ case 'C':
+ {
+ // copy
+ KURL src;
+ KURL dest;
+ stream >> src >> dest;
+ copy(src, dest, 0, FALSE);
+ break;
+ }
+ case 'c':
+ {
+ // capabilities
+ infoMessage(imapCapabilities.join(" "));
+ finished();
+ break;
+ }
+ case 'N':
+ {
+ // NOOP
+ imapCommand *cmd = doCommand(imapCommand::clientNoop());
+ if (cmd->result () != "OK")
+ {
+ kdDebug(7116) << "NOOP did not succeed - connection broken" << endl;
+ completeQueue.removeRef (cmd);
+ error (ERR_CONNECTION_BROKEN, myHost);
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ finished();
+ break;
+ }
+ case 'n':
+ {
+ // namespace in the form "section=namespace=delimiter"
+ // entries are separated by ,
+ infoMessage( imapNamespaces.join(",") );
+ finished();
+ break;
+ }
+ case 'U':
+ {
+ // unsubscribe
+ KURL _url;
+ stream >> _url;
+ QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
+ parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
+ imapCommand *cmd = doCommand(imapCommand::clientUnsubscribe(aBox));
+ if (cmd->result () != "OK")
+ {
+ completeQueue.removeRef (cmd);
+ error(ERR_SLAVE_DEFINED, i18n("Unsubscribe of folder %1 "
+ "failed. The server returned: %2")
+ .arg(_url.prettyURL())
+ .arg(cmd->resultInfo()));
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ finished();
+ break;
+ }
+ case 'u':
+ {
+ // subscribe
+ KURL _url;
+ stream >> _url;
+ QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
+ parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
+ imapCommand *cmd = doCommand(imapCommand::clientSubscribe(aBox));
+ if (cmd->result () != "OK")
+ {
+ completeQueue.removeRef (cmd);
+ error(ERR_SLAVE_DEFINED, i18n("Subscribe of folder %1 "
+ "failed. The server returned: %2")
+ .arg(_url.prettyURL())
+ .arg(cmd->resultInfo()));
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ finished();
+ break;
+ }
+ case 'A':
+ {
+ // acl
+ int cmd;
+ stream >> cmd;
+ if ( hasCapability( "ACL" ) ) {
+ specialACLCommand( cmd, stream );
+ } else {
+ error( ERR_UNSUPPORTED_ACTION, "ACL" );
+ }
+ break;
+ }
+ case 'M':
+ {
+ // annotatemore
+ int cmd;
+ stream >> cmd;
+ if ( hasCapability( "ANNOTATEMORE" ) ) {
+ specialAnnotateMoreCommand( cmd, stream );
+ } else {
+ error( ERR_UNSUPPORTED_ACTION, "ANNOTATEMORE" );
+ }
+ break;
+ }
+ case 'Q':
+ {
+ // quota
+ int cmd;
+ stream >> cmd;
+ if ( hasCapability( "QUOTA" ) ) {
+ specialQuotaCommand( cmd, stream );
+ } else {
+ error( ERR_UNSUPPORTED_ACTION, "QUOTA" );
+ }
+ break;
+ }
+ case 'S':
+ {
+ // status
+ KURL _url;
+ QCString newFlags;
+ stream >> _url >> newFlags;
+
+ QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
+ parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
+ if (!assureBox(aBox, false)) return;
+
+ // make sure we only touch flags we know
+ QCString knownFlags = "\\SEEN \\ANSWERED \\FLAGGED \\DRAFT";
+ const imapInfo info = getSelected();
+ if ( info.permanentFlagsAvailable() && (info.permanentFlags() & imapInfo::User) ) {
+ knownFlags += " KMAILFORWARDED KMAILTODO KMAILWATCHED KMAILIGNORED $FORWARDED $TODO $WATCHED $IGNORED";
+ }
+
+ imapCommand *cmd = doCommand (imapCommand::
+ clientStore (aSequence, "-FLAGS.SILENT", knownFlags));
+ if (cmd->result () != "OK")
+ {
+ completeQueue.removeRef (cmd);
+ error(ERR_COULD_NOT_WRITE, i18n("Changing the flags of message %1 "
+ "failed.").arg(_url.prettyURL()));
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ if (!newFlags.isEmpty())
+ {
+ cmd = doCommand (imapCommand::
+ clientStore (aSequence, "+FLAGS.SILENT", newFlags));
+ if (cmd->result () != "OK")
+ {
+ completeQueue.removeRef (cmd);
+ error(ERR_COULD_NOT_WRITE, i18n("Changing the flags of message %1 "
+ "failed.").arg(_url.prettyURL()));
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ }
+ finished();
+ break;
+ }
+ case 's':
+ {
+ // seen
+ KURL _url;
+ bool seen;
+ QCString newFlags;
+ stream >> _url >> seen;
+
+ QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
+ parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
+ if ( !assureBox(aBox, true) ) // read-only because changing SEEN should be possible even then
+ return;
+
+ imapCommand *cmd;
+ if ( seen )
+ cmd = doCommand( imapCommand::clientStore( aSequence, "+FLAGS.SILENT", "\\SEEN" ) );
+ else
+ cmd = doCommand( imapCommand::clientStore( aSequence, "-FLAGS.SILENT", "\\SEEN" ) );
+
+ if (cmd->result () != "OK")
+ {
+ completeQueue.removeRef (cmd);
+ error(ERR_COULD_NOT_WRITE, i18n("Changing the flags of message %1 "
+ "failed.").arg(_url.prettyURL()));
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ finished();
+ break;
+ }
+
+ case 'E':
+ {
+ // search
+ specialSearchCommand( stream );
+ break;
+ }
+ case 'X':
+ {
+ // custom command
+ specialCustomCommand( stream );
+ break;
+ }
+ default:
+ kdWarning(7116) << "Unknown command in special(): " << tmp << endl;
+ error( ERR_UNSUPPORTED_ACTION, QString(QChar(tmp)) );
+ break;
+ }
+}
+
+void
+IMAP4Protocol::specialACLCommand( int command, QDataStream& stream )
+{
+ // All commands start with the URL to the box
+ KURL _url;
+ stream >> _url;
+ QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
+ parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
+
+ switch( command ) {
+ case 'S': // SETACL
+ {
+ QString user, acl;
+ stream >> user >> acl;
+ kdDebug(7116) << "SETACL " << aBox << " " << user << " " << acl << endl;
+ imapCommand *cmd = doCommand(imapCommand::clientSetACL(aBox, user, acl));
+ if (cmd->result () != "OK")
+ {
+ error(ERR_SLAVE_DEFINED, i18n("Setting the Access Control List on folder %1 "
+ "for user %2 failed. The server returned: %3")
+ .arg(_url.prettyURL())
+ .arg(user)
+ .arg(cmd->resultInfo()));
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ finished();
+ break;
+ }
+ case 'D': // DELETEACL
+ {
+ QString user;
+ stream >> user;
+ kdDebug(7116) << "DELETEACL " << aBox << " " << user << endl;
+ imapCommand *cmd = doCommand(imapCommand::clientDeleteACL(aBox, user));
+ if (cmd->result () != "OK")
+ {
+ error(ERR_SLAVE_DEFINED, i18n("Deleting the Access Control List on folder %1 "
+ "for user %2 failed. The server returned: %3")
+ .arg(_url.prettyURL())
+ .arg(user)
+ .arg(cmd->resultInfo()));
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ finished();
+ break;
+ }
+ case 'G': // GETACL
+ {
+ kdDebug(7116) << "GETACL " << aBox << endl;
+ imapCommand *cmd = doCommand(imapCommand::clientGetACL(aBox));
+ if (cmd->result () != "OK")
+ {
+ error(ERR_SLAVE_DEFINED, i18n("Retrieving the Access Control List on folder %1 "
+ "failed. The server returned: %2")
+ .arg(_url.prettyURL())
+ .arg(cmd->resultInfo()));
+ return;
+ }
+ // Returning information to the application from a special() command isn't easy.
+ // I'm reusing the infoMessage trick seen above (for capabilities), but this
+ // limits me to a string instead of a stringlist. Using DQUOTE as separator,
+ // because it's forbidden in userids by rfc3501
+ kdDebug(7116) << getResults() << endl;
+ infoMessage(getResults().join( "\"" ));
+ finished();
+ break;
+ }
+ case 'L': // LISTRIGHTS
+ {
+ // Do we need this one? It basically shows which rights are tied together, but that's all?
+ error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) );
+ break;
+ }
+ case 'M': // MYRIGHTS
+ {
+ kdDebug(7116) << "MYRIGHTS " << aBox << endl;
+ imapCommand *cmd = doCommand(imapCommand::clientMyRights(aBox));
+ if (cmd->result () != "OK")
+ {
+ error(ERR_SLAVE_DEFINED, i18n("Retrieving the Access Control List on folder %1 "
+ "failed. The server returned: %2")
+ .arg(_url.prettyURL())
+ .arg(cmd->resultInfo()));
+ return;
+ }
+ QStringList lst = getResults();
+ kdDebug(7116) << "myrights results: " << lst << endl;
+ if ( !lst.isEmpty() ) {
+ Q_ASSERT( lst.count() == 1 );
+ infoMessage( lst.first() );
+ }
+ finished();
+ break;
+ }
+ default:
+ kdWarning(7116) << "Unknown special ACL command:" << command << endl;
+ error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) );
+ }
+}
+
+void
+IMAP4Protocol::specialSearchCommand( QDataStream& stream )
+{
+ kdDebug(7116) << "IMAP4Protocol::specialSearchCommand" << endl;
+ KURL _url;
+ stream >> _url;
+ QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
+ parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
+ if (!assureBox(aBox, false)) return;
+
+ imapCommand *cmd = doCommand (imapCommand::clientSearch( aSection ));
+ if (cmd->result () != "OK")
+ {
+ error(ERR_SLAVE_DEFINED, i18n("Searching of folder %1 "
+ "failed. The server returned: %2")
+ .arg(aBox)
+ .arg(cmd->resultInfo()));
+ return;
+ }
+ completeQueue.removeRef(cmd);
+ QStringList lst = getResults();
+ kdDebug(7116) << "IMAP4Protocol::specialSearchCommand '" << aSection <<
+ "' returns " << lst << endl;
+ infoMessage( lst.join( " " ) );
+
+ finished();
+}
+
+void
+IMAP4Protocol::specialCustomCommand( QDataStream& stream )
+{
+ kdDebug(7116) << "IMAP4Protocol::specialCustomCommand" << endl;
+
+ QString command, arguments;
+ int type;
+ stream >> type;
+ stream >> command >> arguments;
+
+ /**
+ * In 'normal' mode we send the command with all information in one go
+ * and retrieve the result.
+ */
+ if ( type == 'N' ) {
+ kdDebug(7116) << "IMAP4Protocol::specialCustomCommand: normal mode" << endl;
+ imapCommand *cmd = doCommand (imapCommand::clientCustom( command, arguments ));
+ if (cmd->result () != "OK")
+ {
+ error(ERR_SLAVE_DEFINED, i18n("Custom command %1:%2 "
+ "failed. The server returned: %3")
+ .arg(command)
+ .arg(arguments)
+ .arg(cmd->resultInfo()));
+ return;
+ }
+ completeQueue.removeRef(cmd);
+ QStringList lst = getResults();
+ kdDebug(7116) << "IMAP4Protocol::specialCustomCommand '" << command <<
+ ":" << arguments <<
+ "' returns " << lst << endl;
+ infoMessage( lst.join( " " ) );
+
+ finished();
+ } else
+ /**
+ * In 'extended' mode we send a first header and push the data of the request in
+ * streaming mode.
+ */
+ if ( type == 'E' ) {
+ kdDebug(7116) << "IMAP4Protocol::specialCustomCommand: extended mode" << endl;
+ imapCommand *cmd = sendCommand (imapCommand::clientCustom( command, QString() ));
+ while ( !parseLoop () ) ;
+
+ // see if server is waiting
+ if (!cmd->isComplete () && !getContinuation ().isEmpty ())
+ {
+ const QByteArray buffer = arguments.utf8();
+
+ // send data to server
+ bool sendOk = (write (buffer.data (), buffer.size ()) == (ssize_t)buffer.size ());
+ processedSize( buffer.size() );
+
+ if ( !sendOk ) {
+ error ( ERR_CONNECTION_BROKEN, myHost );
+ completeQueue.removeRef ( cmd );
+ setState(ISTATE_CONNECT);
+ closeConnection();
+ return;
+ }
+ }
+ parseWriteLine ("");
+
+ do
+ {
+ while (!parseLoop ()) ;
+ }
+ while (!cmd->isComplete ());
+
+ completeQueue.removeRef (cmd);
+
+ QStringList lst = getResults();
+ kdDebug(7116) << "IMAP4Protocol::specialCustomCommand: returns " << lst << endl;
+ infoMessage( lst.join( " " ) );
+
+ finished ();
+ }
+}
+
+void
+IMAP4Protocol::specialAnnotateMoreCommand( int command, QDataStream& stream )
+{
+ // All commands start with the URL to the box
+ KURL _url;
+ stream >> _url;
+ QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
+ parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
+
+ switch( command ) {
+ case 'S': // SETANNOTATION
+ {
+ // Params:
+ // KURL URL of the mailbox
+ // QString entry (should be an actual entry name, no % or *; empty for server entries)
+ // QMap<QString,QString> attributes (name and value)
+ QString entry;
+ QMap<QString, QString> attributes;
+ stream >> entry >> attributes;
+ kdDebug(7116) << "SETANNOTATION " << aBox << " " << entry << " " << attributes.count() << " attributes" << endl;
+ imapCommand *cmd = doCommand(imapCommand::clientSetAnnotation(aBox, entry, attributes));
+ if (cmd->result () != "OK")
+ {
+ error(ERR_SLAVE_DEFINED, i18n("Setting the annotation %1 on folder %2 "
+ " failed. The server returned: %3")
+ .arg(entry)
+ .arg(_url.prettyURL())
+ .arg(cmd->resultInfo()));
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ finished();
+ break;
+ }
+ case 'G': // GETANNOTATION.
+ {
+ // Params:
+ // KURL URL of the mailbox
+ // QString entry (should be an actual entry name, no % or *; empty for server entries)
+ // QStringList attributes (list of attributes to be retrieved, possibly with % or *)
+ QString entry;
+ QStringList attributeNames;
+ stream >> entry >> attributeNames;
+ kdDebug(7116) << "GETANNOTATION " << aBox << " " << entry << " " << attributeNames << endl;
+ imapCommand *cmd = doCommand(imapCommand::clientGetAnnotation(aBox, entry, attributeNames));
+ if (cmd->result () != "OK")
+ {
+ error(ERR_SLAVE_DEFINED, i18n("Retrieving the annotation %1 on folder %2 "
+ "failed. The server returned: %3")
+ .arg(entry)
+ .arg(_url.prettyURL())
+ .arg(cmd->resultInfo()));
+ return;
+ }
+ // Returning information to the application from a special() command isn't easy.
+ // I'm reusing the infoMessage trick seen above (for capabilities and acls), but this
+ // limits me to a string instead of a stringlist. Let's use \r as separator.
+ kdDebug(7116) << getResults() << endl;
+ infoMessage(getResults().join( "\r" ));
+ finished();
+ break;
+ }
+ default:
+ kdWarning(7116) << "Unknown special annotate command:" << command << endl;
+ error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) );
+ }
+}
+
+void
+IMAP4Protocol::specialQuotaCommand( int command, QDataStream& stream )
+{
+ // All commands start with the URL to the box
+ KURL _url;
+ stream >> _url;
+ QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
+ parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
+
+ switch( command ) {
+ case 'R': // GETQUOTAROOT
+ {
+ kdDebug(7116) << "QUOTAROOT " << aBox << endl;
+ imapCommand *cmd = doCommand(imapCommand::clientGetQuotaroot( aBox ) );
+ if (cmd->result () != "OK")
+ {
+ error(ERR_SLAVE_DEFINED, i18n("Retrieving the quota root information on folder %1 "
+ "failed. The server returned: %2")
+ .arg(_url.prettyURL())
+ .arg(cmd->resultInfo()));
+ return;
+ }
+ infoMessage(getResults().join( "\r" ));
+ finished();
+ break;
+ }
+ case 'G': // GETQUOTA
+ {
+ kdDebug(7116) << "GETQUOTA command" << endl;
+ kdWarning(7116) << "UNIMPLEMENTED" << endl;
+ break;
+ }
+ case 'S': // SETQUOTA
+ {
+ kdDebug(7116) << "SETQUOTA command" << endl;
+ kdWarning(7116) << "UNIMPLEMENTED" << endl;
+ break;
+ }
+ default:
+ kdWarning(7116) << "Unknown special quota command:" << command << endl;
+ error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) );
+ }
+}
+
+void
+IMAP4Protocol::rename (const KURL & src, const KURL & dest, bool overwrite)
+{
+ kdDebug(7116) << "IMAP4::rename - [" << (overwrite ? "Overwrite" : "NoOverwrite") << "] " << src.prettyURL() << " -> " << dest.prettyURL() << endl;
+ QString sBox, sSequence, sLType, sSection, sValidity, sDelimiter, sInfo;
+ QString dBox, dSequence, dLType, dSection, dValidity, dDelimiter, dInfo;
+ enum IMAP_TYPE sType =
+ parseURL (src, sBox, sSection, sLType, sSequence, sValidity, sDelimiter, sInfo, false);
+ enum IMAP_TYPE dType =
+ parseURL (dest, dBox, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo, false);
+
+ if (dType == ITYPE_UNKNOWN)
+ {
+ switch (sType)
+ {
+ case ITYPE_BOX:
+ case ITYPE_DIR:
+ case ITYPE_DIR_AND_BOX:
+ {
+ if (getState() == ISTATE_SELECT && sBox == getCurrentBox())
+ {
+ kdDebug(7116) << "IMAP4::rename - close " << getCurrentBox() << endl;
+ // mailbox can only be renamed if it is closed
+ imapCommand *cmd = doCommand (imapCommand::clientClose());
+ bool ok = cmd->result() == "OK";
+ completeQueue.removeRef(cmd);
+ if (!ok)
+ {
+ error(ERR_CANNOT_RENAME, i18n("Unable to close mailbox."));
+ return;
+ }
+ setState(ISTATE_LOGIN);
+ }
+ imapCommand *cmd = doCommand (imapCommand::clientRename (sBox, dBox));
+ if (cmd->result () != "OK") {
+ error (ERR_CANNOT_RENAME, cmd->result ());
+ completeQueue.removeRef (cmd);
+ return;
+ }
+ completeQueue.removeRef (cmd);
+ }
+ break;
+
+ case ITYPE_MSG:
+ case ITYPE_ATTACH:
+ case ITYPE_UNKNOWN:
+ error (ERR_CANNOT_RENAME, src.prettyURL());
+ break;
+ }
+ }
+ else
+ {
+ error (ERR_CANNOT_RENAME, src.prettyURL());
+ return;
+ }
+ finished ();
+}
+
+void
+IMAP4Protocol::slave_status ()
+{
+ bool connected = (getState() != ISTATE_NO) && isConnectionValid();
+ kdDebug(7116) << "IMAP4::slave_status " << connected << endl;
+ slaveStatus ( connected ? myHost : QString::null, connected );
+}
+
+void
+IMAP4Protocol::dispatch (int command, const QByteArray & data)
+{
+ kdDebug(7116) << "IMAP4::dispatch - command=" << command << endl;
+ KIO::TCPSlaveBase::dispatch (command, data);
+}
+
+void
+IMAP4Protocol::stat (const KURL & _url)
+{
+ kdDebug(7116) << "IMAP4::stat - " << _url.prettyURL() << endl;
+ QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
+ // parseURL with caching
+ enum IMAP_TYPE aType =
+ parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter,
+ aInfo, true);
+
+ UDSEntry entry;
+ UDSAtom atom;
+
+ atom.m_uds = UDS_NAME;
+ atom.m_str = aBox;
+ entry.append (atom);
+
+ if (!aSection.isEmpty())
+ {
+ if (getState() == ISTATE_SELECT && aBox == getCurrentBox())
+ {
+ imapCommand *cmd = doCommand (imapCommand::clientClose());
+ bool ok = cmd->result() == "OK";
+ completeQueue.removeRef(cmd);
+ if (!ok)
+ {
+ error(ERR_COULD_NOT_STAT, aBox);
+ return;
+ }
+ setState(ISTATE_LOGIN);
+ }
+ bool ok = false;
+ QString cmdInfo;
+ if (aType == ITYPE_MSG || aType == ITYPE_ATTACH)
+ ok = true;
+ else
+ {
+ imapCommand *cmd = doCommand(imapCommand::clientStatus(aBox, aSection));
+ ok = cmd->result() == "OK";
+ cmdInfo = cmd->resultInfo();
+ completeQueue.removeRef(cmd);
+ }
+ if (!ok)
+ {
+ bool found = false;
+ imapCommand *cmd = doCommand (imapCommand::clientList ("", aBox));
+ if (cmd->result () == "OK")
+ {
+ for (QValueListIterator < imapList > it = listResponses.begin ();
+ it != listResponses.end (); ++it)
+ {
+ if (aBox == (*it).name ()) found = true;
+ }
+ }
+ completeQueue.removeRef (cmd);
+ if (found)
+ error(ERR_COULD_NOT_STAT, aBox);
+ else
+ error(KIO::ERR_DOES_NOT_EXIST, aBox);
+ return;
+ }
+ if ((aSection == "UIDNEXT" && getStatus().uidNextAvailable())
+ || (aSection == "UNSEEN" && getStatus().unseenAvailable()))
+ {
+ atom.m_uds = UDS_SIZE;
+ atom.m_str = QString::null;
+ atom.m_long = (aSection == "UIDNEXT") ? getStatus().uidNext()
+ : getStatus().unseen();
+ entry.append(atom);
+ }
+ } else
+ if (aType == ITYPE_BOX || aType == ITYPE_DIR_AND_BOX || aType == ITYPE_MSG ||
+ aType == ITYPE_ATTACH)
+ {
+ ulong validity = 0;
+ // see if the box is already in select/examine state
+ if (aBox == getCurrentBox ())
+ validity = selectInfo.uidValidity ();
+ else
+ {
+ // do a status lookup on the box
+ // only do this if the box is not selected
+ // the server might change the validity for new select/examine
+ imapCommand *cmd =
+ doCommand (imapCommand::clientStatus (aBox, "UIDVALIDITY"));
+ completeQueue.removeRef (cmd);
+ validity = getStatus ().uidValidity ();
+ }
+ validity = 0; // temporary
+
+ if (aType == ITYPE_BOX || aType == ITYPE_DIR_AND_BOX)
+ {
+ // has no or an invalid uidvalidity
+ if (validity > 0 && validity != aValidity.toULong ())
+ {
+ //redirect
+ KURL newUrl = _url;
+
+ newUrl.setPath ("/" + aBox + ";UIDVALIDITY=" +
+ QString::number(validity));
+ kdDebug(7116) << "IMAP4::stat - redirecting to " << newUrl.prettyURL() << endl;
+ redirection (newUrl);
+ }
+ }
+ else if (aType == ITYPE_MSG || aType == ITYPE_ATTACH)
+ {
+ //must determine if this message exists
+ //cause konqueror will check this on paste operations
+
+ // has an invalid uidvalidity
+ // or no messages in box
+ if (validity > 0 && validity != aValidity.toULong ())
+ {
+ aType = ITYPE_UNKNOWN;
+ kdDebug(7116) << "IMAP4::stat - url has invalid validity [" << validity << "d] " << _url.prettyURL() << endl;
+ }
+ }
+ }
+
+ atom.m_uds = UDS_MIME_TYPE;
+ atom.m_str = getMimeType (aType);
+ entry.append (atom);
+
+ kdDebug(7116) << "IMAP4: stat: " << atom.m_str << endl;
+ switch (aType)
+ {
+ case ITYPE_DIR:
+ atom.m_uds = UDS_FILE_TYPE;
+ atom.m_str = QString::null;
+ atom.m_long = S_IFDIR;
+ entry.append (atom);
+ break;
+
+ case ITYPE_BOX:
+ case ITYPE_DIR_AND_BOX:
+ atom.m_uds = UDS_FILE_TYPE;
+ atom.m_str = QString::null;
+ atom.m_long = S_IFDIR;
+ entry.append (atom);
+ break;
+
+ case ITYPE_MSG:
+ case ITYPE_ATTACH:
+ atom.m_uds = UDS_FILE_TYPE;
+ atom.m_str = QString::null;
+ atom.m_long = S_IFREG;
+ entry.append (atom);
+ break;
+
+ case ITYPE_UNKNOWN:
+ error (ERR_DOES_NOT_EXIST, _url.prettyURL());
+ break;
+ }
+
+ statEntry (entry);
+ kdDebug(7116) << "IMAP4::stat - Finishing stat" << endl;
+ finished ();
+}
+
+void IMAP4Protocol::openConnection()
+{
+ if (makeLogin()) connected();
+}
+
+void IMAP4Protocol::closeConnection()
+{
+ if (getState() == ISTATE_NO) return;
+ if (getState() == ISTATE_SELECT && metaData("expunge") == "auto")
+ {
+ imapCommand *cmd = doCommand (imapCommand::clientExpunge());
+ completeQueue.removeRef (cmd);
+ }
+ if (getState() != ISTATE_CONNECT)
+ {
+ imapCommand *cmd = doCommand (imapCommand::clientLogout());
+ completeQueue.removeRef (cmd);
+ }
+ closeDescriptor();
+ setState(ISTATE_NO);
+ completeQueue.clear();
+ sentQueue.clear();
+ lastHandled = 0;
+ currentBox = QString::null;
+ readBufferLen = 0;
+}
+
+bool IMAP4Protocol::makeLogin ()
+{
+ if (getState () == ISTATE_LOGIN || getState () == ISTATE_SELECT)
+ return true;
+
+ kdDebug(7116) << "IMAP4::makeLogin - checking login" << endl;
+ bool alreadyConnected = getState() == ISTATE_CONNECT;
+ kdDebug(7116) << "IMAP4::makeLogin - alreadyConnected " << alreadyConnected << endl;
+ if (alreadyConnected || connectToHost (myHost.latin1(), myPort))
+ {
+// fcntl (m_iSock, F_SETFL, (fcntl (m_iSock, F_GETFL) | O_NDELAY));
+
+ setState(ISTATE_CONNECT);
+
+ myAuth = metaData("auth");
+ myTLS = metaData("tls");
+ kdDebug(7116) << "myAuth: " << myAuth << endl;
+
+ imapCommand *cmd;
+
+ unhandled.clear ();
+ if (!alreadyConnected) while (!parseLoop ()) ; //get greeting
+ QString greeting;
+ if (!unhandled.isEmpty()) greeting = unhandled.first().stripWhiteSpace();
+ unhandled.clear (); //get rid of it
+ cmd = doCommand (new imapCommand ("CAPABILITY", ""));
+
+ kdDebug(7116) << "IMAP4: setHost: capability" << endl;
+ for (QStringList::Iterator it = imapCapabilities.begin ();
+ it != imapCapabilities.end (); ++it)
+ {
+ kdDebug(7116) << "'" << (*it) << "'" << endl;
+ }
+ completeQueue.removeRef (cmd);
+
+ if (!hasCapability("IMAP4") && !hasCapability("IMAP4rev1"))
+ {
+ error(ERR_COULD_NOT_LOGIN, i18n("The server %1 supports neither "
+ "IMAP4 nor IMAP4rev1.\nIt identified itself with: %2")
+ .arg(myHost).arg(greeting));
+ closeConnection();
+ return false;
+ }
+
+ if (metaData("nologin") == "on") return TRUE;
+
+ if (myTLS == "on" && !hasCapability(QString("STARTTLS")))
+ {
+ error(ERR_COULD_NOT_LOGIN, i18n("The server does not support TLS.\n"
+ "Disable this security feature to connect unencrypted."));
+ closeConnection();
+ return false;
+ }
+ if ((myTLS == "on" || (canUseTLS() && myTLS != "off")) &&
+ hasCapability(QString("STARTTLS")))
+ {
+ imapCommand *cmd = doCommand (imapCommand::clientStartTLS());
+ if (cmd->result () == "OK")
+ {
+ completeQueue.removeRef(cmd);
+ int tlsrc = startTLS();
+ if (tlsrc == 1)
+ {
+ kdDebug(7116) << "TLS mode has been enabled." << endl;
+ imapCommand *cmd2 = doCommand (new imapCommand ("CAPABILITY", ""));
+ for (QStringList::Iterator it = imapCapabilities.begin ();
+ it != imapCapabilities.end (); ++it)
+ {
+ kdDebug(7116) << "'" << (*it) << "'" << endl;
+ }
+ completeQueue.removeRef (cmd2);
+ } else {
+ kdWarning(7116) << "TLS mode setup has failed. Aborting." << endl;
+ error (ERR_COULD_NOT_LOGIN, i18n("Starting TLS failed."));
+ closeConnection();
+ return false;
+ }
+ } else completeQueue.removeRef(cmd);
+ }
+
+ if (myAuth.isEmpty () || myAuth == "*") {
+ if (hasCapability (QString ("LOGINDISABLED"))) {
+ error (ERR_COULD_NOT_LOGIN, i18n("LOGIN is disabled by the server."));
+ closeConnection();
+ return false;
+ }
+ }
+ else {
+ if (!hasCapability (QString ("AUTH=") + myAuth)) {
+ error (ERR_COULD_NOT_LOGIN, i18n("The authentication method %1 is not "
+ "supported by the server.").arg(myAuth));
+ closeConnection();
+ return false;
+ }
+ }
+
+ if ( greeting.contains( QRegExp( "Cyrus IMAP4 v2.1" ) ) ) {
+ removeCapability( "ANNOTATEMORE" );
+ }
+
+ kdDebug(7116) << "IMAP4::makeLogin - attempting login" << endl;
+
+ KIO::AuthInfo authInfo;
+ authInfo.username = myUser;
+ authInfo.password = myPass;
+ authInfo.prompt = i18n ("Username and password for your IMAP account:");
+
+ kdDebug(7116) << "IMAP4::makeLogin - open_PassDlg said user=" << myUser << " pass=xx" << endl;
+
+ QString resultInfo;
+ if (myAuth.isEmpty () || myAuth == "*")
+ {
+ if (myUser.isEmpty () || myPass.isEmpty ()) {
+ if(openPassDlg (authInfo)) {
+ myUser = authInfo.username;
+ myPass = authInfo.password;
+ }
+ }
+ if (!clientLogin (myUser, myPass, resultInfo))
+ error(KIO::ERR_COULD_NOT_AUTHENTICATE, i18n("Unable to login. Probably the "
+ "password is wrong.\nThe server %1 replied:\n%2").arg(myHost).arg(resultInfo));
+ }
+ else
+ {
+#ifdef HAVE_LIBSASL2
+ if (!clientAuthenticate (this, authInfo, myHost, myAuth, mySSL, resultInfo))
+ error(KIO::ERR_COULD_NOT_AUTHENTICATE, i18n("Unable to authenticate via %1.\n"
+ "The server %2 replied:\n%3").arg(myAuth).arg(myHost).arg(resultInfo));
+ else {
+ myUser = authInfo.username;
+ myPass = authInfo.password;
+ }
+#else
+ error(KIO::ERR_COULD_NOT_LOGIN, i18n("SASL authentication is not compiled into kio_imap4."));
+#endif
+ }
+ if ( hasCapability("NAMESPACE") )
+ {
+ // get all namespaces and save the namespace - delimiter association
+ cmd = doCommand( imapCommand::clientNamespace() );
+ if (cmd->result () == "OK")
+ {
+ kdDebug(7116) << "makeLogin - registered namespaces" << endl;
+ }
+ completeQueue.removeRef (cmd);
+ }
+ // get the default delimiter (empty listing)
+ cmd = doCommand( imapCommand::clientList("", "") );
+ if (cmd->result () == "OK")
+ {
+ QValueListIterator < imapList > it = listResponses.begin();
+ if ( it == listResponses.end() )
+ {
+ // empty answer - this is a buggy imap server
+ // as a fallback we fire a normal listing and take the first answer
+ completeQueue.removeRef (cmd);
+ cmd = doCommand( imapCommand::clientList("", "%") );
+ if (cmd->result () == "OK")
+ {
+ it = listResponses.begin();
+ }
+ }
+ if ( it != listResponses.end() )
+ {
+ namespaceToDelimiter[QString::null] = (*it).hierarchyDelimiter();
+ kdDebug(7116) << "makeLogin - delimiter for empty ns='" <<
+ (*it).hierarchyDelimiter() << "'" << endl;
+ if ( !hasCapability("NAMESPACE") )
+ {
+ // server does not support namespaces
+ QString nsentry = QString::number( 0 ) + "=="
+ + (*it).hierarchyDelimiter();
+ imapNamespaces.append( nsentry );
+ }
+ }
+ }
+ completeQueue.removeRef (cmd);
+ } else {
+ kdDebug(7116) << "makeLogin - NO login" << endl;
+ }
+
+ return getState() == ISTATE_LOGIN;
+}
+
+void
+IMAP4Protocol::parseWriteLine (const QString & aStr)
+{
+ //kdDebug(7116) << "Writing: " << aStr << endl;
+ QCString writer = aStr.utf8();
+ int len = writer.length();
+
+ // append CRLF if necessary
+ if (len == 0 || (writer[len - 1] != '\n')) {
+ len += 2;
+ writer += "\r\n";
+ }
+
+ // write it
+ write(writer.data(), len);
+}
+
+QString
+IMAP4Protocol::getMimeType (enum IMAP_TYPE aType)
+{
+ switch (aType)
+ {
+ case ITYPE_DIR:
+ return "inode/directory";
+ break;
+
+ case ITYPE_BOX:
+ return "message/digest";
+ break;
+
+ case ITYPE_DIR_AND_BOX:
+ return "message/directory";
+ break;
+
+ case ITYPE_MSG:
+ return "message/rfc822";
+ break;
+
+ // this should be handled by flushOutput
+ case ITYPE_ATTACH:
+ return "application/octet-stream";
+ break;
+
+ case ITYPE_UNKNOWN:
+ default:
+ return "unknown/unknown";
+ }
+}
+
+
+
+void
+IMAP4Protocol::doListEntry (const KURL & _url, int stretch, imapCache * cache,
+ bool withFlags, bool withSubject)
+{
+ KURL aURL = _url;
+ aURL.setQuery (QString::null);
+ const QString encodedUrl = aURL.url(0, 106); // utf-8
+ doListEntry(encodedUrl, stretch, cache, withFlags, withSubject);
+}
+
+
+
+void
+IMAP4Protocol::doListEntry (const QString & encodedUrl, int stretch, imapCache * cache,
+ bool withFlags, bool withSubject)
+{
+ if (cache)
+ {
+ UDSEntry entry;
+ UDSAtom atom;
+
+ entry.clear ();
+
+ const QString uid = QString::number(cache->getUid());
+
+ atom.m_uds = UDS_NAME;
+ atom.m_str = uid;
+ atom.m_long = 0;
+ if (stretch > 0)
+ {
+ atom.m_str = "0000000000000000" + atom.m_str;
+ atom.m_str = atom.m_str.right (stretch);
+ }
+ if (withSubject)
+ {
+ mailHeader *header = cache->getHeader();
+ if (header)
+ atom.m_str += " " + header->getSubject();
+ }
+ entry.append (atom);
+
+ atom.m_uds = UDS_URL;
+ atom.m_str = encodedUrl; // utf-8
+ if (atom.m_str[atom.m_str.length () - 1] != '/')
+ atom.m_str += '/';
+ atom.m_str += ";UID=" + uid;
+ atom.m_long = 0;
+ entry.append (atom);
+
+ atom.m_uds = UDS_FILE_TYPE;
+ atom.m_str = QString::null;
+ atom.m_long = S_IFREG;
+ entry.append (atom);
+
+ atom.m_uds = UDS_SIZE;
+ atom.m_long = cache->getSize();
+ entry.append (atom);
+
+ atom.m_uds = UDS_MIME_TYPE;
+ atom.m_str = "message/rfc822";
+ atom.m_long = 0;
+ entry.append (atom);
+
+ atom.m_uds = UDS_USER;
+ atom.m_str = myUser;
+ entry.append (atom);
+
+ atom.m_uds = KIO::UDS_ACCESS;
+ atom.m_long = (withFlags) ? cache->getFlags() : S_IRUSR | S_IXUSR | S_IWUSR;
+ entry.append (atom);
+
+ listEntry (entry, false);
+ }
+}
+
+void
+IMAP4Protocol::doListEntry (const KURL & _url, const QString & myBox,
+ const imapList & item, bool appendPath)
+{
+ KURL aURL = _url;
+ aURL.setQuery (QString::null);
+ UDSEntry entry;
+ UDSAtom atom;
+ int hdLen = item.hierarchyDelimiter().length();
+
+ {
+ // mailboxName will be appended to the path if appendPath is true
+ QString mailboxName = item.name ();
+
+ // some beautification
+ if (mailboxName.find (myBox) == 0 && mailboxName.length() > myBox.length())
+ {
+ mailboxName =
+ mailboxName.right (mailboxName.length () - myBox.length ());
+ }
+ if (mailboxName[0] == '/')
+ mailboxName = mailboxName.right (mailboxName.length () - 1);
+ if (mailboxName.left(hdLen) == item.hierarchyDelimiter())
+ mailboxName = mailboxName.right(mailboxName.length () - hdLen);
+ if (mailboxName.right(hdLen) == item.hierarchyDelimiter())
+ mailboxName.truncate(mailboxName.length () - hdLen);
+
+ atom.m_uds = UDS_NAME;
+ if (!item.hierarchyDelimiter().isEmpty() &&
+ mailboxName.find(item.hierarchyDelimiter()) != -1)
+ atom.m_str = mailboxName.section(item.hierarchyDelimiter(), -1);
+ else
+ atom.m_str = mailboxName;
+
+ // konqueror will die with an assertion failure otherwise
+ if (atom.m_str.isEmpty ())
+ atom.m_str = "..";
+
+ if (!atom.m_str.isEmpty ())
+ {
+ atom.m_long = 0;
+ entry.append (atom);
+
+ if (!item.noSelect ())
+ {
+ atom.m_uds = UDS_MIME_TYPE;
+ if (!item.noInferiors ())
+ {
+ atom.m_str = "message/directory";
+ } else {
+ atom.m_str = "message/digest";
+ }
+ atom.m_long = 0;
+ entry.append (atom);
+ mailboxName += '/';
+
+ // explicitly set this as a directory for KFileDialog
+ atom.m_uds = UDS_FILE_TYPE;
+ atom.m_str = QString::null;
+ atom.m_long = S_IFDIR;
+ entry.append (atom);
+ }
+ else if (!item.noInferiors ())
+ {
+ atom.m_uds = UDS_MIME_TYPE;
+ atom.m_str = "inode/directory";
+ atom.m_long = 0;
+ entry.append (atom);
+ mailboxName += '/';
+
+ // explicitly set this as a directory for KFileDialog
+ atom.m_uds = UDS_FILE_TYPE;
+ atom.m_str = QString::null;
+ atom.m_long = S_IFDIR;
+ entry.append (atom);
+ }
+ else
+ {
+ atom.m_uds = UDS_MIME_TYPE;
+ atom.m_str = "unknown/unknown";
+ atom.m_long = 0;
+ entry.append (atom);
+ }
+
+ atom.m_uds = UDS_URL;
+ QString path = aURL.path();
+ atom.m_str = aURL.url (0, 106); // utf-8
+ if (appendPath)
+ {
+ if (path[path.length() - 1] == '/' && !path.isEmpty() && path != "/")
+ path.truncate(path.length() - 1);
+ if (!path.isEmpty() && path != "/"
+ && path.right(hdLen) != item.hierarchyDelimiter()) {
+ path += item.hierarchyDelimiter();
+ }
+ path += mailboxName;
+ if (path.upper() == "/INBOX/") {
+ // make sure the client can rely on INBOX
+ path = path.upper();
+ }
+ }
+ aURL.setPath(path);
+ atom.m_str = aURL.url(0, 106); // utf-8
+ atom.m_long = 0;
+ entry.append (atom);
+
+ atom.m_uds = UDS_USER;
+ atom.m_str = myUser;
+ entry.append (atom);
+
+ atom.m_uds = UDS_ACCESS;
+ atom.m_long = S_IRUSR | S_IXUSR | S_IWUSR;
+ entry.append (atom);
+
+ atom.m_uds = UDS_EXTRA;
+ atom.m_str = item.attributesAsString();
+ atom.m_long = 0;
+ entry.append (atom);
+
+ listEntry (entry, false);
+ }
+ }
+}
+
+enum IMAP_TYPE
+IMAP4Protocol::parseURL (const KURL & _url, QString & _box,
+ QString & _section, QString & _type, QString & _uid,
+ QString & _validity, QString & _hierarchyDelimiter,
+ QString & _info, bool cache)
+{
+ enum IMAP_TYPE retVal;
+ retVal = ITYPE_UNKNOWN;
+
+ imapParser::parseURL (_url, _box, _section, _type, _uid, _validity, _info);
+// kdDebug(7116) << "URL: query - '" << KURL::decode_string(_url.query()) << "'" << endl;
+
+ // get the delimiter
+ QString myNamespace = namespaceForBox( _box );
+ kdDebug(7116) << "IMAP4::parseURL - namespace=" << myNamespace << endl;
+ if ( namespaceToDelimiter.contains(myNamespace) )
+ {
+ _hierarchyDelimiter = namespaceToDelimiter[myNamespace];
+ kdDebug(7116) << "IMAP4::parseURL - delimiter=" << _hierarchyDelimiter << endl;
+ }
+
+ if (!_box.isEmpty ())
+ {
+ kdDebug(7116) << "IMAP4::parseURL - box=" << _box << endl;
+
+ if (makeLogin ())
+ {
+ if (getCurrentBox () != _box ||
+ _type == "LIST" || _type == "LSUB" || _type == "LSUBNOCHECK")
+ {
+ if ( cache )
+ {
+ // assume a normal box
+ retVal = ITYPE_DIR_AND_BOX;
+ } else
+ {
+ // start a listing for the box to get the type
+ imapCommand *cmd;
+
+ cmd = doCommand (imapCommand::clientList ("", _box));
+ if (cmd->result () == "OK")
+ {
+ for (QValueListIterator < imapList > it = listResponses.begin ();
+ it != listResponses.end (); ++it)
+ {
+ //kdDebug(7116) << "IMAP4::parseURL - checking " << _box << " to " << (*it).name() << endl;
+ if (_box == (*it).name ())
+ {
+ if ( !(*it).hierarchyDelimiter().isEmpty() )
+ _hierarchyDelimiter = (*it).hierarchyDelimiter();
+ if ((*it).noSelect ())
+ {
+ retVal = ITYPE_DIR;
+ }
+ else if ((*it).noInferiors ())
+ {
+ retVal = ITYPE_BOX;
+ }
+ else
+ {
+ retVal = ITYPE_DIR_AND_BOX;
+ }
+ }
+ }
+ // if we got no list response for the box see if it's a prefix
+ if ( retVal == ITYPE_UNKNOWN &&
+ namespaceToDelimiter.contains(_box) ) {
+ retVal = ITYPE_DIR;
+ }
+ } else {
+ kdDebug(7116) << "IMAP4::parseURL - got error for " << _box << endl;
+ }
+ completeQueue.removeRef (cmd);
+ } // cache
+ }
+ else // current == box
+ {
+ retVal = ITYPE_BOX;
+ }
+ }
+ else
+ kdDebug(7116) << "IMAP4::parseURL: no login!" << endl;
+
+ }
+ else // empty box
+ {
+ // the root is just a dir
+ kdDebug(7116) << "IMAP4: parseURL: box [root]" << endl;
+ retVal = ITYPE_DIR;
+ }
+
+ // see if it is a real sequence or a simple uid
+ if (retVal == ITYPE_BOX || retVal == ITYPE_DIR_AND_BOX)
+ {
+ if (!_uid.isEmpty ())
+ {
+ if (_uid.find (':') == -1 && _uid.find (',') == -1
+ && _uid.find ('*') == -1)
+ retVal = ITYPE_MSG;
+ }
+ }
+ if (retVal == ITYPE_MSG)
+ {
+ if ( (_section.find ("BODY.PEEK[", 0, false) != -1 ||
+ _section.find ("BODY[", 0, false) != -1) &&
+ _section.find(".MIME") == -1 &&
+ _section.find(".HEADER") == -1 )
+ retVal = ITYPE_ATTACH;
+ }
+ if ( _hierarchyDelimiter.isEmpty() &&
+ (_type == "LIST" || _type == "LSUB" || _type == "LSUBNOCHECK") )
+ {
+ // this shouldn't happen but when the delimiter is really empty
+ // we try to reconstruct it from the URL
+ if (!_box.isEmpty())
+ {
+ int start = _url.path().findRev(_box);
+ if (start != -1)
+ _hierarchyDelimiter = _url.path().mid(start-1, start);
+ kdDebug(7116) << "IMAP4::parseURL - reconstructed delimiter:" << _hierarchyDelimiter
+ << " from URL " << _url.path() << endl;
+ }
+ if (_hierarchyDelimiter.isEmpty())
+ _hierarchyDelimiter = "/";
+ }
+ kdDebug(7116) << "IMAP4::parseURL - return " << retVal << endl;
+
+ return retVal;
+}
+
+int
+IMAP4Protocol::outputLine (const QCString & _str, int len)
+{
+ if (len == -1) {
+ len = _str.length();
+ }
+
+ if (cacheOutput)
+ {
+ if ( !outputBuffer.isOpen() ) {
+ outputBuffer.open(IO_WriteOnly);
+ }
+ outputBuffer.at(outputBufferIndex);
+ outputBuffer.writeBlock(_str.data(), len);
+ outputBufferIndex += len;
+ return 0;
+ }
+
+ QByteArray temp;
+ bool relay = relayEnabled;
+
+ relayEnabled = true;
+ temp.setRawData (_str.data (), len);
+ parseRelay (temp);
+ temp.resetRawData (_str.data (), len);
+
+ relayEnabled = relay;
+ return 0;
+}
+
+void IMAP4Protocol::flushOutput(QString contentEncoding)
+{
+ // send out cached data to the application
+ if (outputBufferIndex == 0)
+ return;
+ outputBuffer.close();
+ outputCache.resize(outputBufferIndex);
+ if (decodeContent)
+ {
+ // get the coding from the MIME header
+ QByteArray decoded;
+ if (contentEncoding.find("quoted-printable", 0, false) == 0)
+ decoded = KCodecs::quotedPrintableDecode(outputCache);
+ else if (contentEncoding.find("base64", 0, false) == 0)
+ KCodecs::base64Decode(outputCache, decoded);
+ else
+ decoded = outputCache;
+
+ QString mimetype = KMimeType::findByContent( decoded )->name();
+ kdDebug(7116) << "IMAP4::flushOutput - mimeType " << mimetype << endl;
+ mimeType(mimetype);
+ decodeContent = false;
+ data( decoded );
+ } else {
+ data( outputCache );
+ }
+ mProcessedSize += outputBufferIndex;
+ processedSize( mProcessedSize );
+ outputBufferIndex = 0;
+ outputCache[0] = '\0';
+ outputBuffer.setBuffer(outputCache);
+}
+
+ssize_t IMAP4Protocol::myRead(void *data, ssize_t len)
+{
+ if (readBufferLen)
+ {
+ ssize_t copyLen = (len < readBufferLen) ? len : readBufferLen;
+ memcpy(data, readBuffer, copyLen);
+ readBufferLen -= copyLen;
+ if (readBufferLen) memcpy(readBuffer, &readBuffer[copyLen], readBufferLen);
+ return copyLen;
+ }
+ if (!isConnectionValid()) return 0;
+ waitForResponse( responseTimeout() );
+ return read(data, len);
+}
+
+bool
+IMAP4Protocol::assureBox (const QString & aBox, bool readonly)
+{
+ if (aBox.isEmpty()) return false;
+
+ imapCommand *cmd = 0;
+
+ if (aBox != getCurrentBox () || (!getSelected().readWrite() && !readonly))
+ {
+ // open the box with the appropriate mode
+ kdDebug(7116) << "IMAP4Protocol::assureBox - opening box" << endl;
+ selectInfo = imapInfo();
+ cmd = doCommand (imapCommand::clientSelect (aBox, readonly));
+ bool ok = cmd->result() == "OK";
+ QString cmdInfo = cmd->resultInfo();
+ completeQueue.removeRef (cmd);
+
+ if (!ok)
+ {
+ bool found = false;
+ cmd = doCommand (imapCommand::clientList ("", aBox));
+ if (cmd->result () == "OK")
+ {
+ for (QValueListIterator < imapList > it = listResponses.begin ();
+ it != listResponses.end (); ++it)
+ {
+ if (aBox == (*it).name ()) found = true;
+ }
+ }
+ completeQueue.removeRef (cmd);
+ if (found) {
+ if (cmdInfo.find("permission", 0, false) != -1) {
+ // not allowed to enter this folder
+ error(ERR_ACCESS_DENIED, cmdInfo);
+ } else {
+ error(ERR_SLAVE_DEFINED, i18n("Unable to open folder %1. The server replied: %2").arg(aBox).arg(cmdInfo));
+ }
+ } else {
+ error(KIO::ERR_DOES_NOT_EXIST, aBox);
+ }
+ return false;
+ }
+ }
+ else
+ {
+ // Give the server a chance to deliver updates every ten seconds.
+ // Doing this means a server roundtrip and since assureBox is called
+ // after every mail, we do it with a timeout.
+ kdDebug(7116) << "IMAP4Protocol::assureBox - reusing box" << endl;
+ if ( mTimeOfLastNoop.secsTo( QDateTime::currentDateTime() ) > 10 ) {
+ cmd = doCommand (imapCommand::clientNoop ());
+ completeQueue.removeRef (cmd);
+ mTimeOfLastNoop = QDateTime::currentDateTime();
+ kdDebug(7116) << "IMAP4Protocol::assureBox - noop timer fired" << endl;
+ }
+ }
+
+ // if it is the mode we want
+ if (!getSelected().readWrite() && !readonly)
+ {
+ error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, aBox);
+ return false;
+ }
+
+ return true;
+}