You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdepim/tdeioslave/imap4/imap4.cc

2747 lines
81 KiB

/**********************************************************************
*
* imap4.cc - IMAP4rev1 KIOSlave
* Copyright (C) 2001-2002 Michael Haeckel <haeckel@kde.org>
* Copyright (C) 1999 John Corey <jcorey@fruity.ath.cx>
*
* 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>
#include <errno.h>
#ifdef HAVE_LIBSASL2
extern "C" {
#include <sasl/sasl.h>
}
#endif
#include <tqbuffer.h>
#include <tqdatetime.h>
#include <tqregexp.h>
#include <tdeprotocolmanager.h>
#include <tdemessagebox.h>
#include <kdebug.h>
#include <tdeio/connection.h>
#include <tdeio/slaveinterface.h>
#include <tdeio/passdlg.h>
#include <tdelocale.h>
#include <kmimetype.h>
#include <kmdcodec.h>
#include "tdepimmacros.h"
#define IMAP_PROTOCOL "imap"
#define IMAP_SSL_PROTOCOL "imaps"
const int ImapPort = 143;
const int ImapsPort = 993;
using namespace TDEIO;
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;
TDEInstance instance ("tdeio_imap4");
if (argc != 4)
{
fprintf(stderr, "Usage: tdeio_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)
{
// A signal handler that calls for example waitpid has to save errno
// before and restore it afterwards.
// (cf. https://www.securecoding.cert.org/confluence/display/cplusplus/ERR32-CPP.+Do+not+rely+on+indeterminate+values+of+errno)
const int save_errno = errno;
int pid, status;
while (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);
break;
}
}
errno = save_errno;
}
IMAP4Protocol::IMAP4Protocol (const TQCString & pool, const TQCString & 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 = TQDateTime();
}
IMAP4Protocol::~IMAP4Protocol ()
{
closeDescriptor();
kdDebug(7116) << "IMAP4: Finishing" << endl;
}
void
IMAP4Protocol::get (const KURL & _url)
{
if (!makeLogin()) return;
kdDebug(7116) << "IMAP4::get - " << _url.prettyURL() << endl;
TQString 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.
TQString 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: " +
TQString::number(selectInfo.recent ()) + "\r\n");
if (selectInfo.countAvailable ())
outputLineStr ("X-Count: " + TQString::number(selectInfo.count ()) +
"\r\n");
if (selectInfo.unseenAvailable ())
outputLineStr ("X-Unseen: " +
TQString::number(selectInfo.unseen ()) + "\r\n");
if (selectInfo.uidValidityAvailable ())
outputLineStr ("X-uidValidity: " +
TQString::number(selectInfo.uidValidity ()) +
"\r\n");
if (selectInfo.uidNextAvailable ())
outputLineStr ("X-UidNext: " +
TQString::number(selectInfo.uidNext ()) + "\r\n");
if (selectInfo.flagsAvailable ())
outputLineStr ("X-Flags: " + TQString::number(selectInfo.flags ()) +
"\r\n");
if (selectInfo.permanentFlagsAvailable ())
outputLineStr ("X-PermanentFlags: " +
TQString::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(TQString());
cacheOutput = false;
}
if (aEnum == ITYPE_MSG || (aEnum == ITYPE_ATTACH && !decodeContent))
relayEnabled = true; // normal mode, relay data
if (aSequence != "0:0")
{
TQString contentEncoding;
if (aEnum == ITYPE_ATTACH && decodeContent)
{
// get the MIME header and fill getLastHandled()
TQString 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: " +
TQString::number(cache->getUid ()) + "\r\n");
if (cache && cache->getSize () != 0)
outputLineStr ("X-Length: " +
TQString::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: " +
TQString::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 (TQByteArray ());
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;
}
TQString 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)
{
TQString 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")
{
TQString 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
TQValueList<imapList> listResponsesSave = listResponses;
doCommand (imapCommand::clientList ("", listStr, false));
for (TQValueListIterator < imapList > it = listResponsesSave.begin ();
it != listResponsesSave.end (); ++it)
{
bool boxOk = false;
for (TQValueListIterator < 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 (TQValueListIterator < 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 (TQString());
const TQString encodedUrl = aURL.url(0, 106); // utf-8
if (!_url.query ().isEmpty ())
{
TQString 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);
TQStringList list = getResults ();
int stretch = 0;
if (selectInfo.uidNextAvailable ())
stretch = TQString::number(selectInfo.uidNext ()).length ();
UDSEntry entry;
imapCache fake;
for (TQStringList::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=" +
TQString::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 = TQString::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, TQString(selectInfo.alert()) ) );
}
selectInfo.setAlert( 0 );
}
kdDebug(7116) << "IMAP4Protocol::listDir - Finishing listDir" << endl;
finished ();
}
void
IMAP4Protocol::setHost (const TQString & _host, int _port,
const TQString & _user, const TQString & _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;
if (_port == 0)
myPort = (mySSL) ? ImapsPort : ImapPort;
else
myPort = _port;
myUser = _user;
myPass = _pass;
}
}
void
IMAP4Protocol::parseRelay (const TQByteArray & 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(TQByteArray & buffer, ulong len, ulong relay)
{
char buf[8192];
while (buffer.size() < len)
{
ssize_t readLen = myRead(buf, TQMIN(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())
{
TQByteArray relayData;
ssize_t relbuf = relay - buffer.size();
int currentRelay = TQMIN(relbuf, readLen);
relayData.setRawData(buf, currentRelay);
parseRelay(relayData);
relayData.resetRawData(buf, currentRelay);
}
{
TQBuffer stream (buffer);
stream.open (IO_WriteOnly);
stream.at (buffer.size ());
stream.writeBlock (buf, readLen);
stream.close ();
}
}
return (buffer.size() == len);
}
bool IMAP4Protocol::parseReadLine (TQByteArray & 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)
{
TQByteArray 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
{
TQBuffer 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;
TDEIO::TCPSlaveBase::setSubURL (_url);
}
void
IMAP4Protocol::put (const KURL & _url, int, bool, bool)
{
kdDebug(7116) << "IMAP4::put - " << _url.prettyURL() << endl;
// TDEIO::TCPSlaveBase::put(_url,permissions,overwrite,resume);
TQString 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
{
TQPtrList < TQByteArray > bufferList;
int length = 0;
int result;
// Loop until we got 'dataEnd'
do
{
TQByteArray *buffer = new TQByteArray ();
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;
TQByteArray *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"))
{
TQString 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;
TQString 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;
TQString sBox, sSequence, sLType, sSection, sValidity, sDelimiter, sInfo;
TQString 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;
TQString subDir = dBox.right (dBox.length () - dBox.findRev ('/'));
TQString 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"))
{
TQString 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;
TQString 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 (TQCString)
* 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 TQByteArray & aData)
{
kdDebug(7116) << "IMAP4Protocol::special" << endl;
if (!makeLogin()) return;
TQDataStream 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;
TQString 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;
TQString 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;
TQCString newFlags;
stream >> _url >> newFlags;
TQString 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
TQCString 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;
TQCString newFlags;
stream >> _url >> seen;
TQString 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, TQString(TQChar(tmp)) );
break;
}
}
void
IMAP4Protocol::specialACLCommand( int command, TQDataStream& stream )
{
// All commands start with the URL to the box
KURL _url;
stream >> _url;
TQString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
switch( command ) {
case 'S': // SETACL
{
TQString 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
{
TQString 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, TQString(TQChar(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;
}
TQStringList 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, TQString(TQChar(command)) );
}
}
void
IMAP4Protocol::specialSearchCommand( TQDataStream& stream )
{
kdDebug(7116) << "IMAP4Protocol::specialSearchCommand" << endl;
KURL _url;
stream >> _url;
TQString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
if (!assureBox(aBox, true)) 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);
TQStringList lst = getResults();
kdDebug(7116) << "IMAP4Protocol::specialSearchCommand '" << aSection <<
"' returns " << lst << endl;
infoMessage( lst.join( " " ) );
finished();
}
void
IMAP4Protocol::specialCustomCommand( TQDataStream& stream )
{
kdDebug(7116) << "IMAP4Protocol::specialCustomCommand" << endl;
TQString 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);
TQStringList 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, TQString() ));
while ( !parseLoop () ) ;
// see if server is waiting
if (!cmd->isComplete () && !getContinuation ().isEmpty ())
{
const TQByteArray 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);
TQStringList lst = getResults();
kdDebug(7116) << "IMAP4Protocol::specialCustomCommand: returns " << lst << endl;
infoMessage( lst.join( " " ) );
finished ();
}
}
void
IMAP4Protocol::specialAnnotateMoreCommand( int command, TQDataStream& stream )
{
// All commands start with the URL to the box
KURL _url;
stream >> _url;
TQString 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
// TQString entry (should be an actual entry name, no % or *; empty for server entries)
// TQMap<TQString,TQString> attributes (name and value)
TQString entry;
TQMap<TQString, TQString> 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
// TQString entry (should be an actual entry name, no % or *; empty for server entries)
// TQStringList attributes (list of attributes to be retrieved, possibly with % or *)
TQString entry;
TQStringList 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, TQString(TQChar(command)) );
}
}
void
IMAP4Protocol::specialQuotaCommand( int command, TQDataStream& stream )
{
// All commands start with the URL to the box
KURL _url;
stream >> _url;
TQString 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, TQString(TQChar(command)) );
}
}
void
IMAP4Protocol::rename (const KURL & src, const KURL & dest, bool overwrite)
{
kdDebug(7116) << "IMAP4::rename - [" << (overwrite ? "Overwrite" : "NoOverwrite") << "] " << src.prettyURL() << " -> " << dest.prettyURL() << endl;
TQString sBox, sSequence, sLType, sSection, sValidity, sDelimiter, sInfo;
TQString 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)
{
kdWarning(7116) << "Unable to close mailbox!" << endl;
error(ERR_CANNOT_RENAME, src.path());
return;
}
setState(ISTATE_LOGIN);
}
imapCommand *cmd = doCommand (imapCommand::clientRename (sBox, dBox));
if (cmd->result () != "OK") {
error (ERR_CANNOT_RENAME, src.path());
completeQueue.removeRef (cmd);
return;
}
completeQueue.removeRef (cmd);
}
break;
case ITYPE_MSG:
case ITYPE_ATTACH:
case ITYPE_UNKNOWN:
error (ERR_CANNOT_RENAME, src.path());
break;
}
}
else
{
error (ERR_CANNOT_RENAME, src.path());
return;
}
finished ();
}
void
IMAP4Protocol::slave_status ()
{
bool connected = (getState() != ISTATE_NO) && isConnectionValid();
kdDebug(7116) << "IMAP4::slave_status " << connected << endl;
slaveStatus ( connected ? myHost : TQString(), connected );
}
void
IMAP4Protocol::dispatch (int command, const TQByteArray & data)
{
kdDebug(7116) << "IMAP4::dispatch - command=" << command << endl;
TDEIO::TCPSlaveBase::dispatch (command, data);
}
void
IMAP4Protocol::stat (const KURL & _url)
{
kdDebug(7116) << "IMAP4::stat - " << _url.prettyURL() << endl;
TQString 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;
TQString cmdInfo;
if (aType == ITYPE_MSG || aType == ITYPE_ATTACH)
ok = true;
else
{
imapCommand *cmd = doCommand(imapCommand::clienStatus(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 (TQValueListIterator < 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(TDEIO::ERR_DOES_NOT_EXIST, aBox);
return;
}
if ((aSection == "UIDNEXT" && geStatus().uidNextAvailable())
|| (aSection == "UNSEEN" && geStatus().unseenAvailable()))
{
atom.m_uds = UDS_SIZE;
atom.m_str = TQString();
atom.m_long = (aSection == "UIDNEXT") ? geStatus().uidNext()
: geStatus().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::clienStatus (aBox, "UIDVALIDITY"));
completeQueue.removeRef (cmd);
validity = geStatus ().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=" +
TQString::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 = TQString();
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 = TQString();
atom.m_long = S_IFDIR;
entry.append (atom);
break;
case ITYPE_MSG:
case ITYPE_ATTACH:
atom.m_uds = UDS_FILE_TYPE;
atom.m_str = TQString();
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 = TQString();
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
TQString 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 (TQStringList::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(TQString("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(TQString("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 (TQStringList::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 (TQString ("LOGINDISABLED"))) {
error (ERR_COULD_NOT_LOGIN, i18n("LOGIN is disabled by the server."));
closeConnection();
return false;
}
}
else {
if (!hasCapability (TQString ("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( TQRegExp( "Cyrus IMAP4 v2.1" ) ) ) {
removeCapability( "ANNOTATEMORE" );
}
// starting from Cyrus IMAP 2.3.9, shared seen flags are available
TQRegExp regExp( "Cyrus\\sIMAP[4]{0,1}\\sv(\\d+)\\.(\\d+)\\.(\\d+)", false );
if ( regExp.search( greeting ) >= 0 ) {
const int major = regExp.cap( 1 ).toInt();
const int minor = regExp.cap( 2 ).toInt();
const int patch = regExp.cap( 3 ).toInt();
if ( major > 2 || (major == 2 && (minor > 3 || (minor == 3 && patch > 9))) ) {
kdDebug(7116) << k_funcinfo << "Cyrus IMAP >= 2.3.9 detected, enabling shared seen flag support" << endl;
imapCapabilities.append( "x-kmail-sharedseen" );
}
}
kdDebug(7116) << "IMAP4::makeLogin - attempting login" << endl;
TDEIO::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;
TQString 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(TDEIO::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(TDEIO::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(TDEIO::ERR_COULD_NOT_LOGIN, i18n("SASL authentication is not compiled into tdeio_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")
{
TQValueListIterator < 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[TQString()] = (*it).hierarchyDelimiter();
kdDebug(7116) << "makeLogin - delimiter for empty ns='" <<
(*it).hierarchyDelimiter() << "'" << endl;
if ( !hasCapability("NAMESPACE") )
{
// server does not support namespaces
TQString nsentry = TQString::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 TQString & aStr)
{
//kdDebug(7116) << "Writing: " << aStr << endl;
TQCString 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);
}
TQString
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 (TQString());
const TQString encodedUrl = aURL.url(0, 106); // utf-8
doListEntry(encodedUrl, stretch, cache, withFlags, withSubject);
}
void
IMAP4Protocol::doListEntry (const TQString & encodedUrl, int stretch, imapCache * cache,
bool withFlags, bool withSubject)
{
if (cache)
{
UDSEntry entry;
UDSAtom atom;
entry.clear ();
const TQString uid = TQString::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 = TQString();
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 = TDEIO::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 TQString & myBox,
const imapList & item, bool appendPath)
{
KURL aURL = _url;
aURL.setQuery (TQString());
UDSEntry entry;
UDSAtom atom;
int hdLen = item.hierarchyDelimiter().length();
{
// mailboxName will be appended to the path if appendPath is true
TQString 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 = TQString();
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 = TQString();
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;
TQString 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, TQString & _box,
TQString & _section, TQString & _type, TQString & _uid,
TQString & _validity, TQString & _hierarchyDelimiter,
TQString & _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
TQString 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 (TQValueListIterator < 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 TQCString & _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;
}
TQByteArray 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(TQString 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
TQByteArray 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;
TQString 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) memmove(readBuffer, &readBuffer[copyLen], readBufferLen);
return copyLen;
}
if (!isConnectionValid()) return 0;
waitForResponse( responseTimeout() );
return read(data, len);
}
bool
IMAP4Protocol::assureBox (const TQString & 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";
TQString cmdInfo = cmd->resultInfo();
completeQueue.removeRef (cmd);
if (!ok)
{
bool found = false;
cmd = doCommand (imapCommand::clientList ("", aBox));
if (cmd->result () == "OK")
{
for (TQValueListIterator < 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(TDEIO::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( TQDateTime::currentDateTime() ) > 10 ) {
cmd = doCommand (imapCommand::clientNoop ());
completeQueue.removeRef (cmd);
mTimeOfLastNoop = TQDateTime::currentDateTime();
kdDebug(7116) << "IMAP4Protocol::assureBox - noop timer fired" << endl;
}
}
// if it is the mode we want
if (!getSelected().readWrite() && !readonly)
{
error(TDEIO::ERR_CANNOT_OPEN_FOR_WRITING, aBox);
return false;
}
return true;
}