diff options
Diffstat (limited to 'kioslaves')
74 files changed, 14097 insertions, 0 deletions
diff --git a/kioslaves/Mainpage.dox b/kioslaves/Mainpage.dox new file mode 100644 index 000000000..24e1ca685 --- /dev/null +++ b/kioslaves/Mainpage.dox @@ -0,0 +1,9 @@ +/** @mainpage +* +* IO Slaves. +* +* KDE PIM contains a few KIOSlaves. The most important +* of these is the IMAP4Protocol slave which is used for IMAP operations. +* Similarly, the MBoxProtocol slave is used to access mbox files. +* +*/ diff --git a/kioslaves/Makefile.am b/kioslaves/Makefile.am new file mode 100644 index 000000000..8258295ef --- /dev/null +++ b/kioslaves/Makefile.am @@ -0,0 +1,11 @@ +## $Id$ + +if compile_kio_sieve + SIEVE_SUBDIR = sieve +endif + +SUBDIRS = imap4 mbox $(SIEVE_SUBDIR) + +DOXYGEN_REFERENCES = kioslaves/imap4 kioslaves/mbox +include $(top_srcdir)/admin/Doxyfile.am + diff --git a/kioslaves/configure.in.bot b/kioslaves/configure.in.bot new file mode 100644 index 000000000..fdad4b633 --- /dev/null +++ b/kioslaves/configure.in.bot @@ -0,0 +1,6 @@ +if test "x$with_sasl" = xcheck && test -z "$SASL2_LIBS"; then + echo "" + echo "cyrus-sasl 2 library is missing. The sieve ioslave will not be built, and imap4 will lack of a lot of authentication methods." + echo "" + all_tests=bad +fi diff --git a/kioslaves/configure.in.in b/kioslaves/configure.in.in new file mode 100644 index 000000000..47b98682b --- /dev/null +++ b/kioslaves/configure.in.in @@ -0,0 +1,27 @@ +KDE_CHECK_SSL + +AC_ARG_WITH(sasl, + [AC_HELP_STRING(--with-sasl, + [enable support for authentication through cyrus-sasl @<:@default=check@:>@])], + [], with_sasl=check) + +sasl2_header="no" +SASL2_LIBS="" +if test "x$with_sasl" != xno; then + KDE_CHECK_HEADERS(sasl/sasl.h, sasl2_header="yes") + if test "$sasl2_header" = "yes" ; then + KDE_CHECK_LIB(sasl2, sasl_client_init, SASL2_LIBS="-lsasl2") + fi + + if test "x$SASL2_LIBS" != "x" ; then + AC_DEFINE_UNQUOTED(HAVE_LIBSASL2, 1, [Define if you have cyrus-sasl2 libraries]) + fi + + if test "x$with_sasl" != xcheck && test -z "$SASL2_LIBS"; then + AC_MSG_ERROR([--with-sasl was given, but test for cyrus-sasl failed]) + fi +fi + +AC_SUBST(SASL2_LIBS) + +AM_CONDITIONAL(compile_kio_sieve, test -n "$SASL2_LIBS") diff --git a/kioslaves/imap4/Makefile.am b/kioslaves/imap4/Makefile.am new file mode 100644 index 000000000..d71987279 --- /dev/null +++ b/kioslaves/imap4/Makefile.am @@ -0,0 +1,26 @@ +INCLUDES= -I$(top_srcdir)/libkmime \ + -I$(srcdir)/.. $(SSL_INCLUDES) \ + -I$(top_srcdir)/libemailfunctions \ + $(all_includes) + +####### Files + +kde_module_LTLIBRARIES = kio_imap4.la + +kio_imap4_la_SOURCES = imapcommand.cc imaplist.cc mailaddress.cc \ + mimeheader.cc rfcdecoder.cc imap4.cc imapinfo.cc imapparser.cc mailheader.cc \ + mimehdrline.cc mimeio.cc +kio_imap4_la_LIBADD = $(LIB_KIO) $(SASL2_LIBS) ../../libkmime/libkmime.la \ + ../../libemailfunctions/libemailfunctions.la +kio_imap4_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) -module $(KDE_PLUGIN) + +noinst_HEADERS = imap4.h +EXTRA_DIST = README + +kdelnk_DATA = imap4.protocol imaps.protocol +kdelnkdir = $(kde_servicesdir) + +messages: + $(XGETTEXT) *.cc -o $(podir)/kio_imap4.pot + +include $(top_srcdir)/admin/Doxyfile.am diff --git a/kioslaves/imap4/PATCHING b/kioslaves/imap4/PATCHING new file mode 100644 index 000000000..39bcdce95 --- /dev/null +++ b/kioslaves/imap4/PATCHING @@ -0,0 +1,7 @@ +If you are patching this code, please be very sensitive to performance issues. +The parser is already very slow and resource intensive. Be careful not to add +any extra string iterations (copies, QCString.length(), etc), mallocs, or +implicit object creation/copies. Use calltree before and after your patch to +verify that it is not too expensive, along with cpu usage timing and even +wall clock time. + diff --git a/kioslaves/imap4/README b/kioslaves/imap4/README new file mode 100644 index 000000000..bc05146ac --- /dev/null +++ b/kioslaves/imap4/README @@ -0,0 +1,48 @@ +This is s.carstens@gmx.de release of KDE 2.0 kioslave +for the IMAP protocol. + +It supports LOGIN, AUTHENTICATE LOGIN, AUTHENTICATE ANONYMOUS and +AUTHENTICATE CRAM-MD5. +It supports the rfc2192 URL naming convention. + +- UIDVALIDITY check is conditional +- put will check if the mailbox exists and create it + or will append the data to that mailbox + (no append after create) + use edit->new->textfile from konqueror +- move will try to guess the correct destination + as konqueror appends the source mailbox name to + the destination +- del will currently delete empty directories, + mark messages for deletion. + If deleting a directory konqueror does the following: + - list the box + - take the box url + file name and try to delete it + - delete the box + As the konqueror created urls are invalid we ignore them + at the moment. +- relative URL's are not supported because + konqueror will not handle them +- there are 2 additional section keywords + ENVELOPE will do a FETCH ENVELOPE + STRUCTURE will do a FETCH BODYSTRUCTURE + normal behaviour is FETCH BODY.PEEK[section] + +- the mime types delivered are not really consistent + with the returned data + - it will return inode/directory on list entries + which contain inferiors + - it will return message/digest on selectable mailboxes + with file type S_IFDIR + - type message/rfc822-imap on selected messages + and type S_IFREG + +In Konqueror set the mimetype message/rfc822 to use +the inline viewer. + +Try it: imap://user@host/ + imap://user;AUTH=*@host/ + imap://user;AUTH=LOGIN@host/ + imap://user;AUTH=CRAM-MD5@host/ + +comments to s.carstens@gmx.de diff --git a/kioslaves/imap4/configure.in.in b/kioslaves/imap4/configure.in.in new file mode 100644 index 000000000..680e26e0a --- /dev/null +++ b/kioslaves/imap4/configure.in.in @@ -0,0 +1 @@ +KDE_CHECK_SSL 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; +} diff --git a/kioslaves/imap4/imap4.h b/kioslaves/imap4/imap4.h new file mode 100644 index 000000000..b86a3815b --- /dev/null +++ b/kioslaves/imap4/imap4.h @@ -0,0 +1,205 @@ +#ifndef _IMAP4_H +#define _IMAP4_H +/********************************************************************** + * + * imap4.h - 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 + * + *********************************************************************/ + +#include "imapparser.h" +#include "mimeio.h" + +#include <kio/tcpslavebase.h> +#include <qbuffer.h> + +#define IMAP_BUFFER 8192 + +/** @brief type of object the url refers too */ +enum IMAP_TYPE +{ + ITYPE_UNKNOWN, /*< unknown type */ + ITYPE_DIR, /*< Object is a directory. i.e. does not contain message, just mailboxes */ + ITYPE_BOX, /*< Object is a mailbox. i.e. contains mails */ + ITYPE_DIR_AND_BOX, /*< Object contains both mails and mailboxes */ + ITYPE_MSG, /*< Object is a mail */ + ITYPE_ATTACH /*< Object is an attachment */ +}; + +/** @brief IOSlave derived class */ +class IMAP4Protocol:public + KIO::TCPSlaveBase, + public + imapParser, + public + mimeIO +{ + +public: + + // reimplement the TCPSlave + IMAP4Protocol (const QCString & pool, const QCString & app, bool isSSL); + virtual ~IMAP4Protocol (); + + virtual void openConnection(); + virtual void closeConnection(); + + virtual void setHost (const QString & _host, int _port, const QString & _user, + const QString & _pass); + /** + * @brief get a message or part of a message + * the data is normally send as we get it from the server + * if you want the slave to decode the content (e.g. for attachments) + * then append an additional INFO=DECODE to the URL + */ + virtual void get (const KURL & _url); + /** + * @brief stat a mailbox, message, attachment + */ + virtual void stat (const KURL & _url); + virtual void slave_status (); + /** + * @brief delete a mailbox + */ + virtual void del (const KURL & _url, bool isFile); + /** + * @brief Capabilites, NOOP, (Un)subscribe, Change status, + * Change ACL + */ + virtual void special (const QByteArray & data); + /** + * @brief list a directory/mailbox + */ + virtual void listDir (const KURL & _url); + virtual void setSubURL (const KURL & _url); + virtual void dispatch (int command, const QByteArray & data); + /** + * @brief create a mailbox + */ + virtual void mkdir (const KURL & url, int permissions); + virtual void put (const KURL & url, int permissions, bool overwrite, + bool resume); + virtual void rename (const KURL & src, const KURL & dest, bool overwrite); + virtual void copy (const KURL & src, const KURL & dest, int permissions, + bool overwrite); + + /** @brief reimplement the parser + * relay hook to send the fetched data directly to an upper level + */ + virtual void parseRelay (const QByteArray & buffer); + + /** @brief reimplement the parser + * relay hook to announce the fetched data directly to an upper level + */ + virtual void parseRelay (ulong); + + /** @brief reimplement the parser + * read at least len bytes */ + virtual bool parseRead (QByteArray &buffer,ulong len,ulong relay=0); + + /** @brief reimplement the parser + * @brief read at least a line (up to CRLF) */ + virtual bool parseReadLine (QByteArray & buffer, ulong relay = 0); + + /** @brief reimplement the parser + * @brief write argument to the server */ + virtual void parseWriteLine (const QString &); + + /** @brief reimplement the mimeIO */ + virtual int outputLine (const QCString & _str, int len = -1); + + /** @brief send out cached data to the application */ + virtual void flushOutput(QString contentEncoding = QString::null); + +protected: + + // select or examine the box if needed + bool assureBox (const QString & aBox, bool readonly); + + ssize_t myRead(void *data, ssize_t len); + + /** + * @brief Parses the given URL + * The return values are set by parsing the URL and querying the server + * + * If you set caching to true the server is not queried but the type is always + * set to ITYPE_DIR_AND_BOX + */ + enum IMAP_TYPE + parseURL (const KURL & _url, QString & _box, QString & _section, + QString & _type, QString & _uid, QString & _validity, + QString & _hierarchyDelimiter, QString & _info, + bool cache = false); + QString getMimeType (enum IMAP_TYPE); + + bool makeLogin (); + + void outputLineStr (const QString & _str) + { + outputLine (_str.latin1 (), _str.length()); + } + void doListEntry (const KURL & _url, int stretch, imapCache * cache = NULL, + bool withFlags = FALSE, bool withSubject = FALSE); + + /** + * Send a list entry (folder) to the application + * If @p appendPath is true the foldername will be appended + * to the path of @p url + */ + void doListEntry (const KURL & url, const QString & myBox, + const imapList & item, bool appendPath = true); + + /** Send an ACL command which is identified by @p command */ + void specialACLCommand( int command, QDataStream& stream ); + + /** Send an annotation command which is identified by @p command */ + void specialAnnotateMoreCommand( int command, QDataStream& stream ); + void specialQuotaCommand( int command, QDataStream& stream ); + + /** Search current folder, the search string is passed as SECTION */ + void specialSearchCommand( QDataStream& ); + + /** Send a custom command to the server */ + void specialCustomCommand( QDataStream& ); + +private: + + // This method behaves like the above method but takes an already encoded url, + // so you don't have to call KURL::url() for every mail. + void doListEntry (const QString & encodedUrl, int stretch, imapCache * cache = NULL, + bool withFlags = FALSE, bool withSubject = FALSE); + + QString myHost, myUser, myPass, myAuth, myTLS; + int myPort; + bool mySSL; + + bool relayEnabled, cacheOutput, decodeContent; + QByteArray outputCache; + QBuffer outputBuffer; + Q_ULONG outputBufferIndex; + KIO::filesize_t mProcessedSize; + + char readBuffer[IMAP_BUFFER]; + ssize_t readBufferLen; + int readSize; + QDateTime mTimeOfLastNoop; +}; + +#endif diff --git a/kioslaves/imap4/imap4.protocol b/kioslaves/imap4/imap4.protocol new file mode 100644 index 000000000..1ab920429 --- /dev/null +++ b/kioslaves/imap4/imap4.protocol @@ -0,0 +1,29 @@ +[Protocol] +# The executable, of course +#### Temporary name +exec=kio_imap4 +# protocol that will appear in URLs +protocol=imap + +# input/output can be one of: filesystem, stream, none +input=stream +output=filesystem + +# Headings for file listings? +listing=Name,Type,Size,Owner +deleting=true +linking=false +# For now, reading yes, writing no +reading=true +writing=false +# For now, no moving +moving=false + +# Can be source protocol +source=true + +# List of capabilities (e.g. special() commands) +Capabilities=Subscription,ACL,Quota + +Icon=folder_inbox +DocPath=kioslave/imap.html diff --git a/kioslaves/imap4/imapcommand.cc b/kioslaves/imap4/imapcommand.cc new file mode 100644 index 000000000..e5eee776f --- /dev/null +++ b/kioslaves/imap4/imapcommand.cc @@ -0,0 +1,408 @@ +/********************************************************************** + * + * imapcommand.cc - IMAP4rev1 command handler + * Copyright (C) 2000 s.carstens@gmx.de + * + * 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 s.carstens@gmx.de + * + *********************************************************************/ + +#include "imapcommand.h" +#include "rfcdecoder.h" + +/*#include <stdlib.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/stat.h> + +#include <fcntl.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <netdb.h> +#include <unistd.h> +#include <stdlib.h> + +#include <qregexp.h> +#include <qbuffer.h> + +#include <kprotocolmanager.h> +#include <ksock.h> +#include <kdebug.h> +#include <kinstance.h> +#include <kio/connection.h> +#include <kio/slaveinterface.h> +#include <kio/passdlg.h> +#include <klocale.h> */ + +imapCommand::imapCommand () +{ + mComplete = false; + mId = QString::null; +} + +imapCommand::imapCommand (const QString & command, const QString & parameter) +// aCommand(NULL), +// mResult(NULL), +// mParameter(NULL) +{ + mComplete = false; + aCommand = command; + aParameter = parameter; + mId = QString::null; +} + +bool +imapCommand::isComplete () +{ + return mComplete; +} + +const QString & +imapCommand::result () +{ + return mResult; +} + +const QString & +imapCommand::resultInfo () +{ + return mResultInfo; +} + +const QString & +imapCommand::id () +{ + return mId; +} + +const QString & +imapCommand::parameter () +{ + return aParameter; +} + +const QString & +imapCommand::command () +{ + return aCommand; +} + +void +imapCommand::setId (const QString & id) +{ + if (mId.isEmpty ()) + mId = id; +} + +void +imapCommand::setComplete () +{ + mComplete = true; +} + +void +imapCommand::setResult (const QString & result) +{ + mResult = result; +} + +void +imapCommand::setResultInfo (const QString & result) +{ + mResultInfo = result; +} + +void +imapCommand::setCommand (const QString & command) +{ + aCommand = command; +} + +void +imapCommand::setParameter (const QString & parameter) +{ + aParameter = parameter; +} + +const QString +imapCommand::getStr () +{ + if (parameter().isEmpty()) + return id() + " " + command() + "\r\n"; + else + return id() + " " + command() + " " + parameter() + "\r\n"; +} + +imapCommand * +imapCommand::clientNoop () +{ + return new imapCommand ("NOOP", ""); +} + +imapCommand * +imapCommand::clientFetch (ulong uid, const QString & fields, bool nouid) +{ + return clientFetch (uid, uid, fields, nouid); +} + +imapCommand * +imapCommand::clientFetch (ulong fromUid, ulong toUid, const QString & fields, + bool nouid) +{ + QString uid = QString::number(fromUid); + + if (fromUid != toUid) + { + uid += ":"; + if (toUid < fromUid) + uid += "*"; + else + uid += QString::number(toUid); + } + return clientFetch (uid, fields, nouid); +} + +imapCommand * +imapCommand::clientFetch (const QString & sequence, const QString & fields, + bool nouid) +{ + return new imapCommand (nouid ? "FETCH" : "UID FETCH", + sequence + " (" + fields + ")"); +} + +imapCommand * +imapCommand::clientList (const QString & reference, const QString & path, + bool lsub) +{ + return new imapCommand (lsub ? "LSUB" : "LIST", + QString ("\"") + rfcDecoder::toIMAP (reference) + + "\" \"" + rfcDecoder::toIMAP (path) + "\""); +} + +imapCommand * +imapCommand::clientSelect (const QString & path, bool examine) +{ + Q_UNUSED(examine); + /** @note We use always SELECT, because UW-IMAP doesn't check for new mail, when + used with the "mbox driver" and the folder is opened with EXAMINE + and Courier can't append to a mailbox that is in EXAMINE state */ + return new imapCommand ("SELECT", + QString ("\"") + rfcDecoder::toIMAP (path) + "\""); +} + +imapCommand * +imapCommand::clientClose() +{ + return new imapCommand("CLOSE", ""); +} + +imapCommand * +imapCommand::clientCopy (const QString & box, const QString & sequence, + bool nouid) +{ + return new imapCommand (nouid ? "COPY" : "UID COPY", + sequence + " \"" + rfcDecoder::toIMAP (box) + "\""); +} + +imapCommand * +imapCommand::clientAppend (const QString & box, const QString & flags, + ulong size) +{ + return new imapCommand ("APPEND", + "\"" + rfcDecoder::toIMAP (box) + "\" " + + ((flags.isEmpty()) ? "" : ("(" + flags + ") ")) + + "{" + QString::number(size) + "}"); +} + +imapCommand * +imapCommand::clientStatus (const QString & path, const QString & parameters) +{ + return new imapCommand ("STATUS", + QString ("\"") + rfcDecoder::toIMAP (path) + + "\" (" + parameters + ")"); +} + +imapCommand * +imapCommand::clientCreate (const QString & path) +{ + return new imapCommand ("CREATE", + QString ("\"") + rfcDecoder::toIMAP (path) + "\""); +} + +imapCommand * +imapCommand::clientDelete (const QString & path) +{ + return new imapCommand ("DELETE", + QString ("\"") + rfcDecoder::toIMAP (path) + "\""); +} + +imapCommand * +imapCommand::clientSubscribe (const QString & path) +{ + return new imapCommand ("SUBSCRIBE", + QString ("\"") + rfcDecoder::toIMAP (path) + "\""); +} + +imapCommand * +imapCommand::clientUnsubscribe (const QString & path) +{ + return new imapCommand ("UNSUBSCRIBE", + QString ("\"") + rfcDecoder::toIMAP (path) + "\""); +} + +imapCommand * +imapCommand::clientExpunge () +{ + return new imapCommand ("EXPUNGE", QString ("")); +} + +imapCommand * +imapCommand::clientRename (const QString & src, const QString & dest) +{ + return new imapCommand ("RENAME", + QString ("\"") + rfcDecoder::toIMAP (src) + + "\" \"" + rfcDecoder::toIMAP (dest) + "\""); +} + +imapCommand * +imapCommand::clientSearch (const QString & search, bool nouid) +{ + return new imapCommand (nouid ? "SEARCH" : "UID SEARCH", search); +} + +imapCommand * +imapCommand::clientStore (const QString & set, const QString & item, + const QString & data, bool nouid) +{ + return new imapCommand (nouid ? "STORE" : "UID STORE", + set + " " + item + " (" + data + ")"); +} + +imapCommand * +imapCommand::clientLogout () +{ + return new imapCommand ("LOGOUT", ""); +} + +imapCommand * +imapCommand::clientStartTLS () +{ + return new imapCommand ("STARTTLS", ""); +} + +imapCommand * +imapCommand::clientSetACL( const QString& box, const QString& user, const QString& acl ) +{ + return new imapCommand ("SETACL", QString("\"") + rfcDecoder::toIMAP (box) + + "\" \"" + rfcDecoder::toIMAP (user) + + "\" \"" + rfcDecoder::toIMAP (acl) + "\""); +} + +imapCommand * +imapCommand::clientDeleteACL( const QString& box, const QString& user ) +{ + return new imapCommand ("DELETEACL", QString("\"") + rfcDecoder::toIMAP (box) + + "\" \"" + rfcDecoder::toIMAP (user) + + "\""); +} + +imapCommand * +imapCommand::clientGetACL( const QString& box ) +{ + return new imapCommand ("GETACL", QString("\"") + rfcDecoder::toIMAP (box) + + "\""); +} + +imapCommand * +imapCommand::clientListRights( const QString& box, const QString& user ) +{ + return new imapCommand ("LISTRIGHTS", QString("\"") + rfcDecoder::toIMAP (box) + + "\" \"" + rfcDecoder::toIMAP (user) + + "\""); +} + +imapCommand * +imapCommand::clientMyRights( const QString& box ) +{ + return new imapCommand ("MYRIGHTS", QString("\"") + rfcDecoder::toIMAP (box) + + "\""); +} + +imapCommand * +imapCommand::clientSetAnnotation( const QString& box, const QString& entry, const QMap<QString, QString>& attributes ) +{ + QString parameter = QString("\"") + rfcDecoder::toIMAP (box) + + "\" \"" + rfcDecoder::toIMAP (entry) + + "\" ("; + for( QMap<QString,QString>::ConstIterator it = attributes.begin(); it != attributes.end(); ++it ) { + parameter += "\""; + parameter += rfcDecoder::toIMAP (it.key()); + parameter += "\" \""; + parameter += rfcDecoder::toIMAP (it.data()); + parameter += "\" "; + } + // Turn last space into a ')' + parameter[parameter.length()-1] = ')'; + + return new imapCommand ("SETANNOTATION", parameter); +} + +imapCommand * +imapCommand::clientGetAnnotation( const QString& box, const QString& entry, const QStringList& attributeNames ) +{ + QString parameter = QString("\"") + rfcDecoder::toIMAP (box) + + "\" \"" + rfcDecoder::toIMAP (entry) + + "\" "; + if ( attributeNames.count() == 1 ) + parameter += "\"" + rfcDecoder::toIMAP (attributeNames.first()) + '"'; + else { + parameter += '('; + for( QStringList::ConstIterator it = attributeNames.begin(); it != attributeNames.end(); ++it ) { + parameter += "\"" + rfcDecoder::toIMAP (*it) + "\" "; + } + // Turn last space into a ')' + parameter[parameter.length()-1] = ')'; + } + return new imapCommand ("GETANNOTATION", parameter); +} + +imapCommand * +imapCommand::clientNamespace() +{ + return new imapCommand("NAMESPACE", ""); +} + +imapCommand * +imapCommand::clientGetQuotaroot( const QString& box ) +{ + QString parameter = QString("\"") + rfcDecoder::toIMAP (box) + '"'; + return new imapCommand ("GETQUOTAROOT", parameter); +} + +imapCommand * +imapCommand::clientCustom( const QString& command, const QString& arguments ) +{ + return new imapCommand (command, arguments); +} + diff --git a/kioslaves/imap4/imapcommand.h b/kioslaves/imap4/imapcommand.h new file mode 100644 index 000000000..f06c5af86 --- /dev/null +++ b/kioslaves/imap4/imapcommand.h @@ -0,0 +1,394 @@ +#ifndef _IMAPCOMMAND_H +#define _IMAPCOMMAND_H +/********************************************************************** + * + * imapcommand.h - IMAP4rev1 command handler + * Copyright (C) 2000 Sven Carstens + * + * 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 + * + *********************************************************************/ + +#include <qstringlist.h> +#include <qstring.h> +#include <qmap.h> + +/** + * @brief encapulate a IMAP command + * @author Svenn Carstens + * @date 2000 + * @todo fix the documentation + */ + +class imapCommand +{ +public: + + /** + * @brief Constructor + */ + imapCommand (); + /** + * @fn imapCommand (const QString & command, const QString & parameter); + * @brief Constructor + * @param command Imap command + * @param parameter Parameters to the command + * @return none + */ + imapCommand (const QString & command, const QString & parameter); + /** + * @fn bool isComplete (); + * @brief is it complete? + * @return whether the command is completed + */ + bool isComplete (); + /** + * @fn const QString & result (); + * @brief get the result of the command + * @return The result, i.e. first word of the result line, like OK + */ + const QString & result (); + /** + * @fn const QString & resultInfo (); + * @brief get information about the result + * @return Information about the result, i.e. the rest of the result line + */ + const QString & resultInfo (); + /** + * @fn const QString & parameter (); + * @brief get the parameter + * @return the parameter + */ + const QString & parameter (); + /** + * @fn const QString & command (); + * @brief get the command + * @return the command + */ + const QString & command (); + /** + * @fn const QString & id (); + * @brief get the id + * @return the id + */ + const QString & id (); + + /** + * @fn void setId (const QString &); + * @brief set the id + * @param id the id used by the command + * @return none + */ + void setId (const QString &); + /** + * @fn void setComplete (); + * @brief set the completed state + * @return none + */ + void setComplete (); + /** + * @fn void setResult (const QString &); + * @brief set the completed state + * @param result the command result + * @return none + */ + void setResult (const QString &); + /** + * @fn void setResultInfo (const QString &); + * @brief set the completed state + * @param result the command result information + * @return none + */ + void setResultInfo (const QString &); + /** + * @fn void setCommand (const QString &); + * @brief set the command + * @param command the imap command + * @return none + */ + void setCommand (const QString &); + /** + * @fn void setParameter (const QString &); + * @brief set the command parameter(s) + * @param parameter the comand parameter(s) + * @return none + */ + void setParameter (const QString &); + /** + * @fn const QString getStr (); + * @brief returns the data to send to the server + * The function returns the complete data to be sent to + * the server (\<id\> \<command\> [\<parameter\>]) + * @return the data to send to the server + * @todo possibly rename function to be clear of it's purpose + */ + const QString getStr (); + + /** + * @fn static imapCommand *clientNoop (); + * @brief Create a NOOP command + * @return a NOOP imapCommand + */ + static imapCommand *clientNoop (); + /** + * @fn static imapCommand *clientFetch (ulong uid, const QString & fields, bool nouid = false); + * @brief Create a FETCH command + * @param uid Uid of the message to fetch + * @param fields options to pass to the server + * @param nouid Perform a FETCH or UID FETCH command + * @return a FETCH imapCommand + * Fetch a single uid + */ + static imapCommand *clientFetch (ulong uid, const QString & fields, + bool nouid = false); + /** + * @fn static imapCommand *clientFetch (ulong fromUid, ulong toUid, const QString & fields, bool nouid = false); + * @brief Create a FETCH command + * @param fromUid start uid of the messages to fetch + * @param toUid last uid of the messages to fetch + * @param fields options to pass to the server + * @param nouid Perform a FETCH or UID FETCH command + * @return a FETCH imapCommand + * Fetch a range of uids + */ + static imapCommand *clientFetch (ulong fromUid, ulong toUid, + const QString & fields, bool nouid = + false); + /** + * @fn static imapCommand *clientFetch (const QString & sequence, const QString & fields, bool nouid = false); + * @brief Create a FETCH command + * @param sequence a IMAP FETCH sequence string + * @param fields options to pass to the server + * @param nouid Perform a FETCH or UID FETCH command + * @return a FETCH imapCommand + * Fetch a range of uids. The other clientFetch functions are just + * wrappers around this function. + */ + static imapCommand *clientFetch (const QString & sequence, + const QString & fields, bool nouid = + false); + /** + * @fn static imapCommand *clientList (const QString & reference, const QString & path, bool lsub = false); + * @brief Create a LIST command + * @param reference + * @param path The path to list + * @param lsub Perform a LIST or a LSUB command + * @return a LIST imapCommand + */ + static imapCommand *clientList (const QString & reference, + const QString & path, bool lsub = false); + /** + * @fn static imapCommand *clientSelect (const QString & path, bool examine = false); + * @brief Create a SELECT command + * @param path The path to select + * @param lsub Perform a SELECT or a EXAMINE command + * @return a SELECT imapCommand + */ + static imapCommand *clientSelect (const QString & path, bool examine = + false); + /** + * @fn static imapCommand *clientClose(); + * @brief Create a CLOSE command + * @return a CLOSE imapCommand + */ + static imapCommand *clientClose(); + /** + * @brief Create a STATUS command + * @param path + * @param parameters + * @return a STATUS imapCommand + */ + static imapCommand *clientStatus (const QString & path, + const QString & parameters); + /** + * @brief Create a COPY command + * @param box + * @param sequence + * @param nouid Perform a COPY or UID COPY command + * @return a COPY imapCommand + */ + static imapCommand *clientCopy (const QString & box, + const QString & sequence, bool nouid = + false); + /** + * @brief Create a APPEND command + * @param box + * @param flags + * @param size + * @return a APPEND imapCommand + */ + static imapCommand *clientAppend (const QString & box, + const QString & flags, ulong size); + /** + * @brief Create a CREATE command + * @param path + * @return a CREATE imapCommand + */ + static imapCommand *clientCreate (const QString & path); + /** + * @brief Create a DELETE command + * @param path + * @return a DELETE imapCommand + */ + static imapCommand *clientDelete (const QString & path); + /** + * @brief Create a SUBSCRIBE command + * @param path + * @return a SUBSCRIBE imapCommand + */ + static imapCommand *clientSubscribe (const QString & path); + /** + * @brief Create a UNSUBSCRIBE command + * @param path + * @return a UNSUBSCRIBE imapCommand + */ + static imapCommand *clientUnsubscribe (const QString & path); + /** + * @brief Create a EXPUNGE command + * @return a EXPUNGE imapCommand + */ + static imapCommand *clientExpunge (); + /** + * @brief Create a RENAME command + * @param src Source + * @param dest Destination + * @return a RENAME imapCommand + */ + static imapCommand *clientRename (const QString & src, + const QString & dest); + /** + * @brief Create a SEARCH command + * @param search + * @param nouid Perform a UID SEARCH or a SEARCH command + * @return a SEARCH imapCommand + */ + static imapCommand *clientSearch (const QString & search, bool nouid = + false); + /** + * @brief Create a STORE command + * @param set + * @param item + * @param data + * @param nouid Perform a UID STORE or a STORE command + * @return a STORE imapCommand + */ + static imapCommand *clientStore (const QString & set, const QString & item, + const QString & data, bool nouid = false); + /** + * @brief Create a LOGOUT command + * @return a LOGOUT imapCommand + */ + static imapCommand *clientLogout (); + /** + * @brief Create a STARTTLS command + * @return a STARTTLS imapCommand + */ + static imapCommand *clientStartTLS (); + + //////////// ACL support (RFC 2086) ///////////// + /** + * @brief Create a SETACL command + * @param box mailbox name + * @param user authentication identifier + * @param acl access right modification (starting with optional +/-) + * @return a SETACL imapCommand + */ + static imapCommand *clientSetACL ( const QString& box, const QString& user, const QString& acl ); + + /** + * @brief Create a DELETEACL command + * @param box mailbox name + * @param user authentication identifier + * @return a DELETEACL imapCommand + */ + static imapCommand *clientDeleteACL ( const QString& box, const QString& user ); + + /** + * @brief Create a GETACL command + * @param box mailbox name + * @return a GETACL imapCommand + */ + static imapCommand *clientGetACL ( const QString& box ); + + /** + * @brief Create a LISTRIGHTS command + * @param box mailbox name + * @param user authentication identifier + * @return a LISTRIGHTS imapCommand + */ + static imapCommand *clientListRights ( const QString& box, const QString& user ); + + /** + * @brief Create a MYRIGHTS command + * @param box mailbox name + * @return a MYRIGHTS imapCommand + */ + static imapCommand *clientMyRights ( const QString& box ); + + //////////// ANNOTATEMORE support ///////////// + /** + * @brief Create a SETANNOTATION command + * @param box mailbox name + * @param entry entry specifier + * @param attributes map of attribute names + values + * @return a SETANNOTATION imapCommand + */ + static imapCommand *clientSetAnnotation ( const QString& box, const QString& entry, const QMap<QString, QString>& attributes ); + + /** + * @brief Create a GETANNOTATION command + * @param box mailbox name + * @param entry entry specifier + * @param attributeNames attribute specifier + * @return a GETANNOTATION imapCommand + */ + static imapCommand *clientGetAnnotation ( const QString& box, const QString& entry, const QStringList& attributeNames ); + + /** + * @brief Create a NAMESPACE command + * @return a NAMESPACE imapCommand + */ + static imapCommand *clientNamespace (); + + /** + * @brief Create a GETQUOTAROOT command + * @param box mailbox name + * @return a GETQUOTAROOT imapCommand + */ + static imapCommand *clientGetQuotaroot ( const QString& box ); + + /** + * @brief Create a custom command + * @param command The custom command + * @param arguments The custom arguments + * @return a custom imapCommand + */ + static imapCommand *clientCustom ( const QString& command, const QString& arguments ); + +protected: + QString aCommand; + QString mId; + bool mComplete; + QString aParameter; + QString mResult; + QString mResultInfo; + +private: + imapCommand & operator = (const imapCommand &); +}; + +#endif diff --git a/kioslaves/imap4/imapinfo.cc b/kioslaves/imap4/imapinfo.cc new file mode 100644 index 000000000..d06618d75 --- /dev/null +++ b/kioslaves/imap4/imapinfo.cc @@ -0,0 +1,236 @@ +/********************************************************************** + * + * imapinfo.cc - IMAP4rev1 SELECT / EXAMINE handler + * Copyright (C) 2000 Sven Carstens + * + * 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 + * + *********************************************************************/ + +/* + 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 + + Supported URLs: + imap://server/ - Prompt for user/pass, list all folders in home directory + imap://user:pass@server/ - Uses LOGIN to log in + imap://user;AUTH=method:pass@server/ - Uses AUTHENTICATE to log in + + imap://server/folder/ - List messages in folder + */ + +#include "imapinfo.h" +#include "imapparser.h" + +#include <kdebug.h> + +imapInfo::imapInfo ():count_ (0), +recent_ (0), +unseen_ (0), +uidValidity_ (0), +uidNext_ (0), +flags_ (0), +permanentFlags_ (0), +readWrite_ (false), +countAvailable_ (false), +recentAvailable_ (false), +unseenAvailable_ (false), +uidValidityAvailable_ (false), +uidNextAvailable_ (false), +flagsAvailable_ (false), +permanentFlagsAvailable_ (false), readWriteAvailable_ (false) +{ +} + +imapInfo::imapInfo (const imapInfo & mi):count_ (mi.count_), +recent_ (mi.recent_), +unseen_ (mi.unseen_), +uidValidity_ (mi.uidValidity_), +uidNext_ (mi.uidNext_), +flags_ (mi.flags_), +permanentFlags_ (mi.permanentFlags_), +readWrite_ (mi.readWrite_), +countAvailable_ (mi.countAvailable_), +recentAvailable_ (mi.recentAvailable_), +unseenAvailable_ (mi.unseenAvailable_), +uidValidityAvailable_ (mi.uidValidityAvailable_), +uidNextAvailable_ (mi.uidNextAvailable_), +flagsAvailable_ (mi.flagsAvailable_), +permanentFlagsAvailable_ (mi.permanentFlagsAvailable_), +readWriteAvailable_ (mi.readWriteAvailable_) +{ +} + +imapInfo & imapInfo::operator = (const imapInfo & mi) +{ + // Avoid a = a. + if (this == &mi) + return *this; + + count_ = mi.count_; + recent_ = mi.recent_; + unseen_ = mi.unseen_; + uidValidity_ = mi.uidValidity_; + uidNext_ = mi.uidNext_; + flags_ = mi.flags_; + permanentFlags_ = mi.permanentFlags_; + readWrite_ = mi.readWrite_; + countAvailable_ = mi.countAvailable_; + recentAvailable_ = mi.recentAvailable_; + unseenAvailable_ = mi.unseenAvailable_; + uidValidityAvailable_ = mi.uidValidityAvailable_; + uidNextAvailable_ = mi.uidNextAvailable_; + flagsAvailable_ = mi.flagsAvailable_; + permanentFlagsAvailable_ = mi.permanentFlagsAvailable_; + readWriteAvailable_ = mi.readWriteAvailable_; + + return *this; +} + +imapInfo::imapInfo (const QStringList & list):count_ (0), +recent_ (0), +unseen_ (0), +uidValidity_ (0), +uidNext_ (0), +flags_ (0), +permanentFlags_ (0), +readWrite_ (false), +countAvailable_ (false), +recentAvailable_ (false), +unseenAvailable_ (false), +uidValidityAvailable_ (false), +uidNextAvailable_ (false), +flagsAvailable_ (false), +permanentFlagsAvailable_ (false), readWriteAvailable_ (false) +{ + for (QStringList::ConstIterator it (list.begin ()); it != list.end (); ++it) + { + QString line (*it); + + line.truncate(line.length() - 2); + QStringList tokens(QStringList::split (' ', line)); + + kdDebug(7116) << "Processing: " << line << endl; + if (tokens[0] != "*") + continue; + + if (tokens[1] == "OK") + { + if (tokens[2] == "[UNSEEN") + setUnseen (tokens[3].left (tokens[3].length () - 1).toULong ()); + + else if (tokens[2] == "[UIDVALIDITY") + setUidValidity (tokens[3].left (tokens[3].length () - 1).toULong ()); + + else if (tokens[2] == "[UIDNEXT") + setUidNext (tokens[3].left (tokens[3].length () - 1).toULong ()); + + else if (tokens[2] == "[PERMANENTFLAGS") + { + int flagsStart = line.find('('); + int flagsEnd = line.find(')'); + + kdDebug(7116) << "Checking permFlags from " << flagsStart << " to " << flagsEnd << endl; + if ((-1 != flagsStart) && (-1 != flagsEnd) && flagsStart < flagsEnd) + setPermanentFlags (_flags (line.mid (flagsStart, flagsEnd).latin1())); + + } + else if (tokens[2] == "[READ-WRITE") + { + setReadWrite (true); + } + else if (tokens[2] == "[READ-ONLY") + { + setReadWrite (false); + } + else + { + kdDebug(7116) << "unknown token2: " << tokens[2] << endl; + } + } + else if (tokens[1] == "FLAGS") + { + int flagsStart = line.find ('('); + int flagsEnd = line.find (')'); + + if ((-1 != flagsStart) && (-1 != flagsEnd) && flagsStart < flagsEnd) + setFlags (_flags (line.mid (flagsStart, flagsEnd).latin1() )); + } + else + { + if (tokens[2] == "EXISTS") + setCount (tokens[1].toULong ()); + + else if (tokens[2] == "RECENT") + setRecent (tokens[1].toULong ()); + + else + kdDebug(7116) << "unknown token1/2: " << tokens[1] << " " << tokens[2] << endl; + } + } + +} + +ulong imapInfo::_flags (const QCString & inFlags) +{ + ulong flags = 0; + parseString flagsString; + flagsString.data.duplicate(inFlags.data(), inFlags.length()); + + if (flagsString[0] == '(') + flagsString.pos++; + + while (!flagsString.isEmpty () && flagsString[0] != ')') + { + QCString entry = imapParser::parseOneWordC(flagsString).upper(); + + if (entry.isEmpty ()) + flagsString.clear(); + else if (0 != entry.contains ("\\SEEN")) + flags ^= Seen; + else if (0 != entry.contains ("\\ANSWERED")) + flags ^= Answered; + else if (0 != entry.contains ("\\FLAGGED")) + flags ^= Flagged; + else if (0 != entry.contains ("\\DELETED")) + flags ^= Deleted; + else if (0 != entry.contains ("\\DRAFT")) + flags ^= Draft; + else if (0 != entry.contains ("\\RECENT")) + flags ^= Recent; + else if (0 != entry.contains ("\\*")) + flags ^= User; + + // non standard kmail falgs + else if ( entry.contains( "KMAILFORWARDED" ) || entry.contains( "$FORWARDED" ) ) + flags = flags | Forwarded; + else if ( entry.contains( "KMAILTODO" ) || entry.contains( "$TODO" ) ) + flags = flags | Todo; + else if ( entry.contains( "KMAILWATCHED" ) || entry.contains( "$WATCHED" ) ) + flags = flags | Watched; + else if ( entry.contains( "KMAILIGNORED" ) || entry.contains( "$IGNORED" ) ) + flags = flags | Ignored; + } + + return flags; +} diff --git a/kioslaves/imap4/imapinfo.h b/kioslaves/imap4/imapinfo.h new file mode 100644 index 000000000..068e6db54 --- /dev/null +++ b/kioslaves/imap4/imapinfo.h @@ -0,0 +1,232 @@ +#ifndef _IMAPINFO_H +#define _IMAPINFO_H +/********************************************************************** + * + * imapinfo.h - IMAP4rev1 SELECT / EXAMINE handler + * Copyright (C) 2000 Sven Carstens + * + * 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 + * + *********************************************************************/ + +#include <qstringlist.h> +#include <qstring.h> + +//class handling the info we get on EXAMINE and SELECT +class imapInfo +{ +public: + + + enum MessageAttribute + { + Seen = 1 << 0, + Answered = 1 << 1, + Flagged = 1 << 2, + Deleted = 1 << 3, + Draft = 1 << 4, + Recent = 1 << 5, + User = 1 << 6, + // non standard flags + Forwarded = 1 << 7, + Todo = 1 << 8, + Watched = 1 << 9, + Ignored = 1 << 10 + }; + + + imapInfo (); + imapInfo (const QStringList &); + imapInfo (const imapInfo &); + imapInfo & operator = (const imapInfo &); + + static ulong _flags (const QCString &); + + void setCount (ulong l) + { + countAvailable_ = true; + count_ = l; + } + + void setRecent (ulong l) + { + recentAvailable_ = true; + recent_ = l; + } + + void setUnseen (ulong l) + { + unseenAvailable_ = true; + unseen_ = l; + } + + void setUidValidity (ulong l) + { + uidValidityAvailable_ = true; + uidValidity_ = l; + } + + void setUidNext (ulong l) + { + uidNextAvailable_ = true; + uidNext_ = l; + } + + void setFlags (ulong l) + { + flagsAvailable_ = true; + flags_ = l; + } + + void setFlags (const QCString & inFlag) + { + flagsAvailable_ = true; + flags_ = _flags (inFlag); + } + + void setPermanentFlags (ulong l) + { + permanentFlagsAvailable_ = true; + permanentFlags_ = l; + } + + void setPermanentFlags (const QCString & inFlag) + { + permanentFlagsAvailable_ = true; + permanentFlags_ = _flags (inFlag); + } + + void setReadWrite (bool b) + { + readWriteAvailable_ = true; + readWrite_ = b; + } + + void setAlert( const char* cstr ) + { + alert_ = cstr; + } + + ulong count () const + { + return count_; + } + + ulong recent () const + { + return recent_; + } + + ulong unseen () const + { + return unseen_; + } + + ulong uidValidity () const + { + return uidValidity_; + } + + ulong uidNext () const + { + return uidNext_; + } + + ulong flags () const + { + return flags_; + } + + ulong permanentFlags () const + { + return permanentFlags_; + } + + bool readWrite () const + { + return readWrite_; + } + + ulong countAvailable () const + { + return countAvailable_; + } + + ulong recentAvailable () const + { + return recentAvailable_; + } + + ulong unseenAvailable () const + { + return unseenAvailable_; + } + + ulong uidValidityAvailable () const + { + return uidValidityAvailable_; + } + + ulong uidNextAvailable () const + { + return uidNextAvailable_; + } + + ulong flagsAvailable () const + { + return flagsAvailable_; + } + + ulong permanentFlagsAvailable () const + { + return permanentFlagsAvailable_; + } + + bool readWriteAvailable () const + { + return readWriteAvailable_; + } + + QCString alert() const + { + return alert_; + } + +private: + + QCString alert_; + + ulong count_; + ulong recent_; + ulong unseen_; + ulong uidValidity_; + ulong uidNext_; + ulong flags_; + ulong permanentFlags_; + bool readWrite_; + + bool countAvailable_; + bool recentAvailable_; + bool unseenAvailable_; + bool uidValidityAvailable_; + bool uidNextAvailable_; + bool flagsAvailable_; + bool permanentFlagsAvailable_; + bool readWriteAvailable_; +}; + +#endif diff --git a/kioslaves/imap4/imaplist.cc b/kioslaves/imap4/imaplist.cc new file mode 100644 index 000000000..6054535c6 --- /dev/null +++ b/kioslaves/imap4/imaplist.cc @@ -0,0 +1,135 @@ +/********************************************************************** + * + * imapinfo.cc - IMAP4rev1 EXAMINE / SELECT handler + * Copyright (C) 2000 Sven Carstens + * + * 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 + * + *********************************************************************/ + +/* + 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 + + Supported URLs: + imap://server/ - Prompt for user/pass, list all folders in home directory + imap://user:pass@server/ - Uses LOGIN to log in + imap://user;AUTH=method:pass@server/ - Uses AUTHENTICATE to log in + + imap://server/folder/ - List messages in folder + */ + +#include "rfcdecoder.h" +#include "imaplist.h" +#include "imapparser.h" + +#include <kdebug.h> + +imapList::imapList (): parser_(0), noInferiors_ (false), +noSelect_ (false), marked_ (false), unmarked_ (false), +hasChildren_ (false), hasNoChildren_ (false) +{ +} + +imapList::imapList (const imapList & lr):parser_(lr.parser_), +hierarchyDelimiter_ (lr.hierarchyDelimiter_), +name_ (lr.name_), +noInferiors_ (lr.noInferiors_), +noSelect_ (lr.noSelect_), marked_ (lr.marked_), unmarked_ (lr.unmarked_), +hasChildren_ (lr.hasChildren_), hasNoChildren_ (lr.hasNoChildren_), +attributes_ (lr.attributes_) +{ +} + +imapList & imapList::operator = (const imapList & lr) +{ + // Avoid a = a. + if (this == &lr) + return *this; + + parser_ = lr.parser_; + hierarchyDelimiter_ = lr.hierarchyDelimiter_; + name_ = lr.name_; + noInferiors_ = lr.noInferiors_; + noSelect_ = lr.noSelect_; + marked_ = lr.marked_; + unmarked_ = lr.unmarked_; + hasChildren_ = lr.hasChildren_; + hasNoChildren_ = lr.hasNoChildren_; + attributes_ = lr.attributes_; + + return *this; +} + +imapList::imapList (const QString & inStr, imapParser &parser) +: parser_(&parser), +noInferiors_ (false), +noSelect_ (false), +marked_ (false), unmarked_ (false), hasChildren_ (false), +hasNoChildren_ (false) +{ + parseString s; + s.data.duplicate(inStr.latin1(), inStr.length()); + + if (s[0] != '(') + return; //not proper format for us + + s.pos++; // tie off ( + + parseAttributes( s ); + + s.pos++; // tie off ) + parser_->skipWS (s); + + hierarchyDelimiter_ = parser_->parseOneWordC(s); + if (hierarchyDelimiter_ == "NIL") + hierarchyDelimiter_ = QString::null; + name_ = rfcDecoder::fromIMAP (parser_->parseLiteral (s)); // decode modified UTF7 +} + +void imapList::parseAttributes( parseString & str ) +{ + QCString attribute, orig; + + while ( !str.isEmpty () && str[0] != ')' ) + { + orig = parser_->parseOneWordC(str); + attributes_ << orig; + attribute = orig.lower(); + if (-1 != attribute.find ("\\noinferiors")) + noInferiors_ = true; + else if (-1 != attribute.find ("\\noselect")) + noSelect_ = true; + else if (-1 != attribute.find ("\\marked")) + marked_ = true; + else if (-1 != attribute.find ("\\unmarked")) + unmarked_ = true; + else if (-1 != attribute.find ("\\haschildren")) + hasChildren_ = true; + else if (-1 != attribute.find ("\\hasnochildren")) + hasNoChildren_ = true; + else + kdDebug(7116) << "imapList::imapList: bogus attribute " << attribute << endl; + } +} + diff --git a/kioslaves/imap4/imaplist.h b/kioslaves/imap4/imaplist.h new file mode 100644 index 000000000..5945011f4 --- /dev/null +++ b/kioslaves/imap4/imaplist.h @@ -0,0 +1,137 @@ +#ifndef _IMAPLIST_H +#define _IMAPLIST_H +/********************************************************************** + * + * imaplist.h - IMAP4rev1 list response handler + * Copyright (C) 2000 Sven Carstens + * + * 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 + * + *********************************************************************/ + +#include <qstringlist.h> +#include <qstring.h> + +class parseString; +class imapParser; + +//the class handling the responses from list +class imapList +{ +public: + + imapList (); + imapList (const QString &, imapParser &); + imapList (const imapList &); + imapList & operator = (const imapList &); + + // process the attributes + void parseAttributes( parseString & ); + + // return all atributes concatenated + QString attributesAsString() const + { + return attributes_.join(","); + } + + QString hierarchyDelimiter () const + { + return hierarchyDelimiter_; + } + void setHierarchyDelimiter (const QString & _str) + { + hierarchyDelimiter_ = _str; + } + + QString name () const + { + return name_; + } + void setName (const QString & _str) + { + name_ = _str; + } + + bool noInferiors () const + { + return noInferiors_; + } + void setNoInferiors (bool _val) + { + noInferiors_ = _val; + } + + bool noSelect () const + { + return noSelect_; + } + void setNoSelect (bool _val) + { + noSelect_ = _val; + } + + bool hasChildren () const + { + return hasChildren_; + } + void setHasChildren (bool _val) + { + hasChildren_ = _val; + } + + bool hasNoChildren () const + { + return hasNoChildren_; + } + void setHasNoChildren (bool _val) + { + hasNoChildren_ = _val; + } + + bool marked () const + { + return marked_; + } + void setMarked (bool _val) + { + marked_ = _val; + } + + bool unmarked () const + { + return unmarked_; + } + void setUnmarked (bool _val) + { + unmarked_ = _val; + } + +private: + + imapParser* parser_; + QString hierarchyDelimiter_; + QString name_; + bool noInferiors_; + bool noSelect_; + bool marked_; + bool unmarked_; + bool hasChildren_; + bool hasNoChildren_; + QStringList attributes_; +}; + +#endif diff --git a/kioslaves/imap4/imapparser.cc b/kioslaves/imap4/imapparser.cc new file mode 100644 index 000000000..cf3465a4c --- /dev/null +++ b/kioslaves/imap4/imapparser.cc @@ -0,0 +1,2085 @@ +/********************************************************************** + * + * imapparser.cc - IMAP4rev1 Parser + * Copyright (C) 2001-2002 Michael Haeckel <haeckel@kde.org> + * Copyright (C) 2000 s.carstens@gmx.de + * + * 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 s.carstens@gmx.de + * + *********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "rfcdecoder.h" + +#include "imapparser.h" + +#include "imapinfo.h" + +#include "mailheader.h" +#include "mimeheader.h" +#include "mailaddress.h" + +#include <sys/types.h> + +#include <stdlib.h> +#include <unistd.h> + +#ifdef HAVE_LIBSASL2 +extern "C" { +#include <sasl/sasl.h> +} +#endif + +#include <qregexp.h> +#include <qbuffer.h> +#include <qstring.h> +#include <qstringlist.h> + +#include <kdebug.h> +#include <kmdcodec.h> +#include <kurl.h> + +#include <kasciistricmp.h> +#include <kasciistringtools.h> + +#ifdef HAVE_LIBSASL2 +static sasl_callback_t callbacks[] = { + { SASL_CB_ECHOPROMPT, NULL, NULL }, + { SASL_CB_NOECHOPROMPT, NULL, NULL }, + { SASL_CB_GETREALM, NULL, NULL }, + { SASL_CB_USER, NULL, NULL }, + { SASL_CB_AUTHNAME, NULL, NULL }, + { SASL_CB_PASS, NULL, NULL }, + { SASL_CB_CANON_USER, NULL, NULL }, + { SASL_CB_LIST_END, NULL, NULL } +}; +#endif + +imapParser::imapParser () +{ + sentQueue.setAutoDelete (false); + completeQueue.setAutoDelete (true); + currentState = ISTATE_NO; + commandCounter = 0; + lastHandled = 0; +} + +imapParser::~imapParser () +{ + delete lastHandled; + lastHandled = 0; +} + +imapCommand * +imapParser::doCommand (imapCommand * aCmd) +{ + int pl = 0; + sendCommand (aCmd); + while (pl != -1 && !aCmd->isComplete ()) { + while ((pl = parseLoop ()) == 0) + ; + } + + return aCmd; +} + +imapCommand * +imapParser::sendCommand (imapCommand * aCmd) +{ + aCmd->setId (QString::number(commandCounter++)); + sentQueue.append (aCmd); + + continuation.resize(0); + const QString& command = aCmd->command(); + + if (command == "SELECT" || command == "EXAMINE") + { + // we need to know which box we are selecting + parseString p; + p.fromString(aCmd->parameter()); + currentBox = parseOneWordC(p); + kdDebug(7116) << "imapParser::sendCommand - setting current box to " << currentBox << endl; + } + else if (command == "CLOSE") + { + // we no longer have a box open + currentBox = QString::null; + } + else if (command.find ("SEARCH") != -1 + || command == "GETACL" + || command == "LISTRIGHTS" + || command == "MYRIGHTS" + || command == "GETANNOTATION" + || command == "NAMESPACE" + || command == "GETQUOTAROOT" + || command == "GETQUOTA" + || command == "X-GET-OTHER-USERS" + || command == "X-GET-DELEGATES" + || command == "X-GET-OUT-OF-OFFICE") + { + lastResults.clear (); + } + else if (command == "LIST" + || command == "LSUB") + { + listResponses.clear (); + } + parseWriteLine (aCmd->getStr ()); + return aCmd; +} + +bool +imapParser::clientLogin (const QString & aUser, const QString & aPass, + QString & resultInfo) +{ + imapCommand *cmd; + bool retVal = false; + + cmd = + doCommand (new + imapCommand ("LOGIN", "\"" + rfcDecoder::quoteIMAP(aUser) + + "\" \"" + rfcDecoder::quoteIMAP(aPass) + "\"")); + + if (cmd->result () == "OK") + { + currentState = ISTATE_LOGIN; + retVal = true; + } + resultInfo = cmd->resultInfo(); + completeQueue.removeRef (cmd); + + return retVal; +} + +#ifdef HAVE_LIBSASL2 +static bool sasl_interact( KIO::SlaveBase *slave, KIO::AuthInfo &ai, void *in ) +{ + kdDebug(7116) << "sasl_interact" << endl; + sasl_interact_t *interact = ( sasl_interact_t * ) in; + + //some mechanisms do not require username && pass, so it doesn't need a popup + //window for getting this info + for ( ; interact->id != SASL_CB_LIST_END; interact++ ) { + if ( interact->id == SASL_CB_AUTHNAME || + interact->id == SASL_CB_PASS ) { + + if ( ai.username.isEmpty() || ai.password.isEmpty() ) { + if (!slave->openPassDlg(ai)) + return false; + } + break; + } + } + + interact = ( sasl_interact_t * ) in; + while( interact->id != SASL_CB_LIST_END ) { + kdDebug(7116) << "SASL_INTERACT id: " << interact->id << endl; + switch( interact->id ) { + case SASL_CB_USER: + case SASL_CB_AUTHNAME: + kdDebug(7116) << "SASL_CB_[USER|AUTHNAME]: '" << ai.username << "'" << endl; + interact->result = strdup( ai.username.utf8() ); + interact->len = strlen( (const char *) interact->result ); + break; + case SASL_CB_PASS: + kdDebug(7116) << "SASL_CB_PASS: [hidden] " << endl; + interact->result = strdup( ai.password.utf8() ); + interact->len = strlen( (const char *) interact->result ); + break; + default: + interact->result = 0; + interact->len = 0; + break; + } + interact++; + } + return true; +} +#endif + +bool +imapParser::clientAuthenticate ( KIO::SlaveBase *slave, KIO::AuthInfo &ai, + const QString & aFQDN, const QString & aAuth, bool isSSL, QString & resultInfo) +{ + bool retVal = false; +#ifdef HAVE_LIBSASL2 + int result; + sasl_conn_t *conn = 0; + sasl_interact_t *client_interact = 0; + const char *out = 0; + uint outlen = 0; + const char *mechusing = 0; + QByteArray tmp, challenge; + + kdDebug(7116) << "aAuth: " << aAuth << " FQDN: " << aFQDN << " isSSL: " << isSSL << endl; + + // see if server supports this authenticator + if (!hasCapability ("AUTH=" + aAuth)) + return false; + +// result = sasl_client_new( isSSL ? "imaps" : "imap", + result = sasl_client_new( "imap", /* FIXME: with cyrus-imapd, even imaps' digest-uri + must be 'imap'. I don't know if it's good or bad. */ + aFQDN.latin1(), + 0, 0, callbacks, 0, &conn ); + + if ( result != SASL_OK ) { + kdDebug(7116) << "sasl_client_new failed with: " << result << endl; + resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); + return false; + } + + do { + result = sasl_client_start(conn, aAuth.latin1(), &client_interact, + hasCapability("SASL-IR") ? &out : 0, &outlen, &mechusing); + + if ( result == SASL_INTERACT ) { + if ( !sasl_interact( slave, ai, client_interact ) ) { + sasl_dispose( &conn ); + return false; + } + } + } while ( result == SASL_INTERACT ); + + if ( result != SASL_CONTINUE && result != SASL_OK ) { + kdDebug(7116) << "sasl_client_start failed with: " << result << endl; + resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); + sasl_dispose( &conn ); + return false; + } + imapCommand *cmd; + + tmp.setRawData( out, outlen ); + KCodecs::base64Encode( tmp, challenge ); + tmp.resetRawData( out, outlen ); + // then lets try it + QString firstCommand = aAuth; + if ( !challenge.isEmpty() ) { + firstCommand += " "; + firstCommand += QString::fromLatin1( challenge.data(), challenge.size() ); + } + cmd = sendCommand (new imapCommand ("AUTHENTICATE", firstCommand.latin1())); + + while ( true ) + { + //read the next line + while (parseLoop() == 0) ; + if ( cmd->isComplete() ) break; + + if (!continuation.isEmpty()) + { +// kdDebug(7116) << "S: " << QCString(continuation.data(),continuation.size()+1) << endl; + if ( continuation.size() > 4 ) { + tmp.setRawData( continuation.data() + 2, continuation.size() - 4 ); + KCodecs::base64Decode( tmp, challenge ); +// kdDebug(7116) << "S-1: " << QCString(challenge.data(),challenge.size()+1) << endl; + tmp.resetRawData( continuation.data() + 2, continuation.size() - 4 ); + } + + do { + result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(), + challenge.size(), + &client_interact, + &out, &outlen); + + if (result == SASL_INTERACT) { + if ( !sasl_interact( slave, ai, client_interact ) ) { + sasl_dispose( &conn ); + return false; + } + } + } while ( result == SASL_INTERACT ); + + if ( result != SASL_CONTINUE && result != SASL_OK ) { + kdDebug(7116) << "sasl_client_step failed with: " << result << endl; + resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); + sasl_dispose( &conn ); + return false; + } + + tmp.setRawData( out, outlen ); +// kdDebug(7116) << "C-1: " << QCString(tmp.data(),tmp.size()+1) << endl; + KCodecs::base64Encode( tmp, challenge ); + tmp.resetRawData( out, outlen ); +// kdDebug(7116) << "C: " << QCString(challenge.data(),challenge.size()+1) << endl; + parseWriteLine (challenge); + continuation.resize(0); + } + } + + if (cmd->result () == "OK") + { + currentState = ISTATE_LOGIN; + retVal = true; + } + resultInfo = cmd->resultInfo(); + completeQueue.removeRef (cmd); + + sasl_dispose( &conn ); //we don't use sasl_en/decode(), so it's safe to dispose the connection. +#endif //HAVE_LIBSASL2 + return retVal; +} + +void +imapParser::parseUntagged (parseString & result) +{ + //kdDebug(7116) << "imapParser::parseUntagged - '" << result.cstr() << "'" << endl; + + parseOneWordC(result); // * + QByteArray what = parseLiteral (result); // see whats coming next + + switch (what[0]) + { + //the status responses + case 'B': // BAD or BYE + if (qstrncmp(what, "BAD", what.size()) == 0) + { + parseResult (what, result); + } + else if (qstrncmp(what, "BYE", what.size()) == 0) + { + parseResult (what, result); + if ( sentQueue.count() ) { + // BYE that interrupts a command -> copy the reason for it + imapCommand *current = sentQueue.at (0); + current->setResultInfo(result.cstr()); + } + currentState = ISTATE_NO; + } + break; + + case 'N': // NO + if (what[1] == 'O' && what.size() == 2) + { + parseResult (what, result); + } + else if (qstrncmp(what, "NAMESPACE", what.size()) == 0) + { + parseNamespace (result); + } + break; + + case 'O': // OK + if (what[1] == 'K' && what.size() == 2) + { + parseResult (what, result); + } else if (qstrncmp(what, "OTHER-USER", 10) == 0) { // X-GET-OTHER-USER + parseOtherUser (result); + } else if (qstrncmp(what, "OUT-OF-OFFICE", 13) == 0) { // X-GET-OUT-OF-OFFICE + parseOutOfOffice (result); + } + break; + case 'D': + if (qstrncmp(what, "DELEGATE", 8) == 0) { // X-GET-DELEGATES + parseDelegate (result); + } + break; + + case 'P': // PREAUTH + if (qstrncmp(what, "PREAUTH", what.size()) == 0) + { + parseResult (what, result); + currentState = ISTATE_LOGIN; + } + break; + + // parse the other responses + case 'C': // CAPABILITY + if (qstrncmp(what, "CAPABILITY", what.size()) == 0) + { + parseCapability (result); + } + break; + + case 'F': // FLAGS + if (qstrncmp(what, "FLAGS", what.size()) == 0) + { + parseFlags (result); + } + break; + + case 'L': // LIST or LSUB or LISTRIGHTS + if (qstrncmp(what, "LIST", what.size()) == 0) + { + parseList (result); + } + else if (qstrncmp(what, "LSUB", what.size()) == 0) + { + parseLsub (result); + } + else if (qstrncmp(what, "LISTRIGHTS", what.size()) == 0) + { + parseListRights (result); + } + break; + + case 'M': // MYRIGHTS + if (qstrncmp(what, "MYRIGHTS", what.size()) == 0) + { + parseMyRights (result); + } + break; + case 'S': // SEARCH or STATUS + if (qstrncmp(what, "SEARCH", what.size()) == 0) + { + parseSearch (result); + } + else if (qstrncmp(what, "STATUS", what.size()) == 0) + { + parseStatus (result); + } + break; + + case 'A': // ACL or ANNOTATION + if (qstrncmp(what, "ACL", what.size()) == 0) + { + parseAcl (result); + } + else if (qstrncmp(what, "ANNOTATION", what.size()) == 0) + { + parseAnnotation (result); + } + break; + case 'Q': // QUOTA or QUOTAROOT + if ( what.size() > 5 && qstrncmp(what, "QUOTAROOT", what.size()) == 0) + { + parseQuotaRoot( result ); + } + else if (qstrncmp(what, "QUOTA", what.size()) == 0) + { + parseQuota( result ); + } + break; + case 'X': // Custom command + { + parseCustom( result ); + } + break; + default: + //better be a number + { + ulong number; + bool valid; + + number = QCString(what, what.size() + 1).toUInt(&valid); + if (valid) + { + what = parseLiteral (result); + switch (what[0]) + { + case 'E': + if (qstrncmp(what, "EXISTS", what.size()) == 0) + { + parseExists (number, result); + } + else if (qstrncmp(what, "EXPUNGE", what.size()) == 0) + { + parseExpunge (number, result); + } + break; + + case 'F': + if (qstrncmp(what, "FETCH", what.size()) == 0) + { + seenUid = QString::null; + parseFetch (number, result); + } + break; + + case 'S': + if (qstrncmp(what, "STORE", what.size()) == 0) // deprecated store + { + seenUid = QString::null; + parseFetch (number, result); + } + break; + + case 'R': + if (qstrncmp(what, "RECENT", what.size()) == 0) + { + parseRecent (number, result); + } + break; + default: + break; + } + } + } + break; + } //switch +} //func + + +void +imapParser::parseResult (QByteArray & result, parseString & rest, + const QString & command) +{ + if (command == "SELECT") + selectInfo.setReadWrite(true); + + if (rest[0] == '[') + { + rest.pos++; + QCString option = parseOneWordC(rest, TRUE); + + switch (option[0]) + { + case 'A': // ALERT + if (option == "ALERT") + { + rest.pos = rest.data.find(']', rest.pos) + 1; + // The alert text is after [ALERT]. + // Is this correct or do we need to care about litterals? + selectInfo.setAlert( rest.cstr() ); + } + break; + + case 'N': // NEWNAME + if (option == "NEWNAME") + { + } + break; + + case 'P': //PARSE or PERMANENTFLAGS + if (option == "PARSE") + { + } + else if (option == "PERMANENTFLAGS") + { + uint end = rest.data.find(']', rest.pos); + QCString flags(rest.data.data() + rest.pos, end - rest.pos); + selectInfo.setPermanentFlags (flags); + rest.pos = end; + } + break; + + case 'R': //READ-ONLY or READ-WRITE + if (option == "READ-ONLY") + { + selectInfo.setReadWrite (false); + } + else if (option == "READ-WRITE") + { + selectInfo.setReadWrite (true); + } + break; + + case 'T': //TRYCREATE + if (option == "TRYCREATE") + { + } + break; + + case 'U': //UIDVALIDITY or UNSEEN + if (option == "UIDVALIDITY") + { + ulong value; + if (parseOneNumber (rest, value)) + selectInfo.setUidValidity (value); + } + else if (option == "UNSEEN") + { + ulong value; + if (parseOneNumber (rest, value)) + selectInfo.setUnseen (value); + } + else if (option == "UIDNEXT") + { + ulong value; + if (parseOneNumber (rest, value)) + selectInfo.setUidNext (value); + } + else + break; + + } + if (rest[0] == ']') + rest.pos++; //tie off ] + skipWS (rest); + } + + if (command.isEmpty()) + { + // This happens when parsing an intermediate result line (those that start with '*'). + // No state change involved, so we can stop here. + return; + } + + switch (command[0].latin1 ()) + { + case 'A': + if (command == "AUTHENTICATE") + if (qstrncmp(result, "OK", result.size()) == 0) + currentState = ISTATE_LOGIN; + break; + + case 'L': + if (command == "LOGIN") + if (qstrncmp(result, "OK", result.size()) == 0) + currentState = ISTATE_LOGIN; + break; + + case 'E': + if (command == "EXAMINE") + { + if (qstrncmp(result, "OK", result.size()) == 0) + currentState = ISTATE_SELECT; + else + { + if (currentState == ISTATE_SELECT) + currentState = ISTATE_LOGIN; + currentBox = QString::null; + } + kdDebug(7116) << "imapParser::parseResult - current box is now " << currentBox << endl; + } + break; + + case 'S': + if (command == "SELECT") + { + if (qstrncmp(result, "OK", result.size()) == 0) + currentState = ISTATE_SELECT; + else + { + if (currentState == ISTATE_SELECT) + currentState = ISTATE_LOGIN; + currentBox = QString::null; + } + kdDebug(7116) << "imapParser::parseResult - current box is now " << currentBox << endl; + } + break; + + default: + break; + } + +} + +void imapParser::parseCapability (parseString & result) +{ + QCString temp( result.cstr() ); + imapCapabilities = QStringList::split ( ' ', KPIM::kAsciiToLower( temp.data() ) ); +} + +void imapParser::parseFlags (parseString & result) +{ + selectInfo.setFlags(result.cstr()); +} + +void imapParser::parseList (parseString & result) +{ + imapList this_one; + + if (result[0] != '(') + return; //not proper format for us + + result.pos++; // tie off ( + + this_one.parseAttributes( result ); + + result.pos++; // tie off ) + skipWS (result); + + this_one.setHierarchyDelimiter(parseLiteralC(result)); + this_one.setName (rfcDecoder::fromIMAP(parseLiteralC(result))); // decode modified UTF7 + + listResponses.append (this_one); +} + +void imapParser::parseLsub (parseString & result) +{ + imapList this_one (result.cstr(), *this); + listResponses.append (this_one); +} + +void imapParser::parseListRights (parseString & result) +{ + parseOneWordC (result); // skip mailbox name + parseOneWordC (result); // skip user id + int outlen = 1; + while ( outlen ) { + QCString word = parseOneWordC (result, false, &outlen); + lastResults.append (word); + } +} + +void imapParser::parseAcl (parseString & result) +{ + parseOneWordC (result); // skip mailbox name + int outlen = 1; + // The result is user1 perm1 user2 perm2 etc. The caller will sort it out. + while ( outlen && !result.isEmpty() ) { + QCString word = parseLiteralC (result, false, false, &outlen); + lastResults.append (word); + } +} + +void imapParser::parseAnnotation (parseString & result) +{ + parseOneWordC (result); // skip mailbox name + skipWS (result); + parseOneWordC (result); // skip entry name (we know it since we don't allow wildcards in it) + skipWS (result); + if (result.isEmpty() || result[0] != '(') + return; + result.pos++; + skipWS (result); + int outlen = 1; + // The result is name1 value1 name2 value2 etc. The caller will sort it out. + while ( outlen && !result.isEmpty() && result[0] != ')' ) { + QCString word = parseLiteralC (result, false, false, &outlen); + lastResults.append (word); + } +} + + +void imapParser::parseQuota (parseString & result) +{ + // quota_response ::= "QUOTA" SP astring SP quota_list + // quota_list ::= "(" #quota_resource ")" + // quota_resource ::= atom SP number SP number + QCString root = parseOneWordC( result ); + if ( root.isEmpty() ) { + lastResults.append( "" ); + } else { + lastResults.append( root ); + } + if (result.isEmpty() || result[0] != '(') + return; + result.pos++; + skipWS (result); + QStringList triplet; + int outlen = 1; + while ( outlen && !result.isEmpty() && result[0] != ')' ) { + QCString word = parseLiteralC (result, false, false, &outlen); + triplet.append(word); + } + lastResults.append( triplet.join(" ") ); +} + +void imapParser::parseQuotaRoot (parseString & result) +{ + // quotaroot_response + // ::= "QUOTAROOT" SP astring *(SP astring) + parseOneWordC (result); // skip mailbox name + skipWS (result); + if ( result.isEmpty() ) + return; + QStringList roots; + int outlen = 1; + while ( outlen && !result.isEmpty() ) { + QCString word = parseLiteralC (result, false, false, &outlen); + roots.append (word); + } + lastResults.append( roots.isEmpty()? "" : roots.join(" ") ); +} + +void imapParser::parseCustom (parseString & result) +{ + int outlen = 1; + QCString word = parseLiteralC (result, false, false, &outlen); + lastResults.append( word ); +} + +void imapParser::parseOtherUser (parseString & result) +{ + lastResults.append( parseOneWordC( result ) ); +} + +void imapParser::parseDelegate (parseString & result) +{ + const QString email = parseOneWordC( result ); + + QStringList rights; + int outlen = 1; + while ( outlen && !result.isEmpty() ) { + QCString word = parseLiteralC( result, false, false, &outlen ); + rights.append( word ); + } + + lastResults.append( email + ":" + rights.join( "," ) ); +} + +void imapParser::parseOutOfOffice (parseString & result) +{ + const QString state = parseOneWordC (result); + parseOneWordC (result); // skip encoding + + int outlen = 1; + QCString msg = parseLiteralC (result, false, false, &outlen); + + lastResults.append( state + "^" + QString::fromUtf8( msg ) ); +} + +void imapParser::parseMyRights (parseString & result) +{ + parseOneWordC (result); // skip mailbox name + Q_ASSERT( lastResults.isEmpty() ); // we can only be called once + lastResults.append (parseOneWordC (result) ); +} + +void imapParser::parseSearch (parseString & result) +{ + ulong value; + + while (parseOneNumber (result, value)) + { + lastResults.append (QString::number(value)); + } +} + +void imapParser::parseStatus (parseString & inWords) +{ + lastStatus = imapInfo (); + + parseLiteralC(inWords); // swallow the box + if (inWords.isEmpty() || inWords[0] != '(') + return; + + inWords.pos++; + skipWS (inWords); + + while (!inWords.isEmpty() && inWords[0] != ')') + { + ulong value; + + QCString label = parseOneWordC(inWords); + if (parseOneNumber (inWords, value)) + { + if (label == "MESSAGES") + lastStatus.setCount (value); + else if (label == "RECENT") + lastStatus.setRecent (value); + else if (label == "UIDVALIDITY") + lastStatus.setUidValidity (value); + else if (label == "UNSEEN") + lastStatus.setUnseen (value); + else if (label == "UIDNEXT") + lastStatus.setUidNext (value); + } + } + + if (inWords[0] == ')') + inWords.pos++; + skipWS (inWords); +} + +void imapParser::parseExists (ulong value, parseString & result) +{ + selectInfo.setCount (value); + result.pos = result.data.size(); +} + +void imapParser::parseExpunge (ulong value, parseString & result) +{ + Q_UNUSED(value); + Q_UNUSED(result); +} + +void imapParser::parseAddressList (parseString & inWords, QPtrList<mailAddress>& list) +{ + if (inWords.isEmpty()) + return; + if (inWords[0] != '(') + { + parseOneWordC (inWords); // parse NIL + } + else + { + inWords.pos++; + skipWS (inWords); + + while (!inWords.isEmpty () && inWords[0] != ')') + { + if (inWords[0] == '(') { + mailAddress *addr = new mailAddress; + parseAddress(inWords, *addr); + list.append(addr); + } else { + break; + } + } + + if (!inWords.isEmpty() && inWords[0] == ')') + inWords.pos++; + skipWS (inWords); + } +} + +const mailAddress& imapParser::parseAddress (parseString & inWords, mailAddress& retVal) +{ + inWords.pos++; + skipWS (inWords); + + retVal.setFullName(parseLiteralC(inWords)); + retVal.setCommentRaw(parseLiteralC(inWords)); + retVal.setUser(parseLiteralC(inWords)); + retVal.setHost(parseLiteralC(inWords)); + + if (!inWords.isEmpty() && inWords[0] == ')') + inWords.pos++; + skipWS (inWords); + + return retVal; +} + +mailHeader * imapParser::parseEnvelope (parseString & inWords) +{ + mailHeader *envelope = 0; + + if (inWords[0] != '(') + return envelope; + inWords.pos++; + skipWS (inWords); + + envelope = new mailHeader; + + //date + envelope->setDate(parseLiteralC(inWords)); + + //subject + envelope->setSubject(parseLiteralC(inWords)); + + QPtrList<mailAddress> list; + list.setAutoDelete(true); + + //from + parseAddressList(inWords, list); + if (!list.isEmpty()) { + envelope->setFrom(*list.last()); + list.clear(); + } + + //sender + parseAddressList(inWords, list); + if (!list.isEmpty()) { + envelope->setSender(*list.last()); + list.clear(); + } + + //reply-to + parseAddressList(inWords, list); + if (!list.isEmpty()) { + envelope->setReplyTo(*list.last()); + list.clear(); + } + + //to + parseAddressList (inWords, envelope->to()); + + //cc + parseAddressList (inWords, envelope->cc()); + + //bcc + parseAddressList (inWords, envelope->bcc()); + + //in-reply-to + envelope->setInReplyTo(parseLiteralC(inWords)); + + //message-id + envelope->setMessageId(parseLiteralC(inWords)); + + // see if we have more to come + while (!inWords.isEmpty () && inWords[0] != ')') + { + //eat the extensions to this part + if (inWords[0] == '(') + parseSentence (inWords); + else + parseLiteralC (inWords); + } + + if (!inWords.isEmpty() && inWords[0] == ')') + inWords.pos++; + skipWS (inWords); + + return envelope; +} + +// parse parameter pairs into a dictionary +// caller must clean up the dictionary items +QAsciiDict < QString > imapParser::parseDisposition (parseString & inWords) +{ + QCString disposition; + QAsciiDict < QString > retVal (17, false); + + // return value is a shallow copy + retVal.setAutoDelete (false); + + if (inWords[0] != '(') + { + //disposition only + disposition = parseOneWordC (inWords); + } + else + { + inWords.pos++; + skipWS (inWords); + + //disposition + disposition = parseOneWordC (inWords); + retVal = parseParameters (inWords); + if (inWords[0] != ')') + return retVal; + inWords.pos++; + skipWS (inWords); + } + + if (!disposition.isEmpty ()) + { + retVal.insert ("content-disposition", new QString(disposition)); + } + + return retVal; +} + +// parse parameter pairs into a dictionary +// caller must clean up the dictionary items +QAsciiDict < QString > imapParser::parseParameters (parseString & inWords) +{ + QAsciiDict < QString > retVal (17, false); + + // return value is a shallow copy + retVal.setAutoDelete (false); + + if (inWords[0] != '(') + { + //better be NIL + parseOneWordC (inWords); + } + else + { + inWords.pos++; + skipWS (inWords); + + while (!inWords.isEmpty () && inWords[0] != ')') + { + QCString l1 = parseLiteralC(inWords); + QCString l2 = parseLiteralC(inWords); + retVal.insert (l1, new QString(l2)); + } + + if (inWords[0] != ')') + return retVal; + inWords.pos++; + skipWS (inWords); + } + + return retVal; +} + +mimeHeader * imapParser::parseSimplePart (parseString & inWords, + QString & inSection, mimeHeader * localPart) +{ + QCString subtype; + QCString typeStr; + QAsciiDict < QString > parameters (17, false); + ulong size; + + parameters.setAutoDelete (true); + + if (inWords[0] != '(') + return 0; + + if (!localPart) + localPart = new mimeHeader; + + localPart->setPartSpecifier (inSection); + + inWords.pos++; + skipWS (inWords); + + //body type + typeStr = parseLiteralC(inWords); + + //body subtype + subtype = parseLiteralC(inWords); + + localPart->setType (typeStr + "/" + subtype); + + //body parameter parenthesized list + parameters = parseParameters (inWords); + { + QAsciiDictIterator < QString > it (parameters); + + while (it.current ()) + { + localPart->setTypeParm (it.currentKey (), *(it.current ())); + ++it; + } + parameters.clear (); + } + + //body id + localPart->setID (parseLiteralC(inWords)); + + //body description + localPart->setDescription (parseLiteralC(inWords)); + + //body encoding + localPart->setEncoding (parseLiteralC(inWords)); + + //body size + if (parseOneNumber (inWords, size)) + localPart->setLength (size); + + // type specific extensions + if (localPart->getType().upper() == "MESSAGE/RFC822") + { + //envelope structure + mailHeader *envelope = parseEnvelope (inWords); + + //body structure + parseBodyStructure (inWords, inSection, envelope); + + localPart->setNestedMessage (envelope); + + //text lines + ulong lines; + parseOneNumber (inWords, lines); + } + else + { + if (typeStr == "TEXT") + { + //text lines + ulong lines; + parseOneNumber (inWords, lines); + } + + // md5 + parseLiteralC(inWords); + + // body disposition + parameters = parseDisposition (inWords); + { + QString *disposition = parameters["content-disposition"]; + + if (disposition) + localPart->setDisposition (disposition->ascii ()); + parameters.remove ("content-disposition"); + QAsciiDictIterator < QString > it (parameters); + while (it.current ()) + { + localPart->setDispositionParm (it.currentKey (), + *(it.current ())); + ++it; + } + + parameters.clear (); + } + + // body language + parseSentence (inWords); + } + + // see if we have more to come + while (!inWords.isEmpty () && inWords[0] != ')') + { + //eat the extensions to this part + if (inWords[0] == '(') + parseSentence (inWords); + else + parseLiteralC(inWords); + } + if (inWords[0] == ')') + inWords.pos++; + skipWS (inWords); + + return localPart; +} + +mimeHeader * imapParser::parseBodyStructure (parseString & inWords, + QString & inSection, mimeHeader * localPart) +{ + bool init = false; + if (inSection.isEmpty()) + { + // first run + init = true; + // assume one part + inSection = "1"; + } + int section = 0; + + if (inWords[0] != '(') + { + // skip "" + parseOneWordC (inWords); + return 0; + } + inWords.pos++; + skipWS (inWords); + + if (inWords[0] == '(') + { + QByteArray subtype; + QAsciiDict < QString > parameters (17, false); + QString outSection; + parameters.setAutoDelete (true); + if (!localPart) + localPart = new mimeHeader; + else + { + // might be filled from an earlier run + localPart->clearNestedParts (); + localPart->clearTypeParameters (); + localPart->clearDispositionParameters (); + // an envelope was passed in so this is the multipart header + outSection = inSection + ".HEADER"; + } + if (inWords[0] == '(' && init) + inSection = "0"; + + // set the section + if ( !outSection.isEmpty() ) { + localPart->setPartSpecifier(outSection); + } else { + localPart->setPartSpecifier(inSection); + } + + // is multipart (otherwise its a simplepart and handled later) + while (inWords[0] == '(') + { + outSection = QString::number(++section); + if (!init) + outSection = inSection + "." + outSection; + mimeHeader *subpart = parseBodyStructure (inWords, outSection, 0); + localPart->addNestedPart (subpart); + } + + // fetch subtype + subtype = parseOneWordC (inWords); + + localPart->setType ("MULTIPART/" + b2c(subtype)); + + // fetch parameters + parameters = parseParameters (inWords); + { + QAsciiDictIterator < QString > it (parameters); + + while (it.current ()) + { + localPart->setTypeParm (it.currentKey (), *(it.current ())); + ++it; + } + parameters.clear (); + } + + // body disposition + parameters = parseDisposition (inWords); + { + QString *disposition = parameters["content-disposition"]; + + if (disposition) + localPart->setDisposition (disposition->ascii ()); + parameters.remove ("content-disposition"); + QAsciiDictIterator < QString > it (parameters); + while (it.current ()) + { + localPart->setDispositionParm (it.currentKey (), + *(it.current ())); + ++it; + } + parameters.clear (); + } + + // body language + parseSentence (inWords); + + } + else + { + // is simple part + inWords.pos--; + inWords.data[inWords.pos] = '('; //fake a sentence + if ( localPart ) + inSection = inSection + ".1"; + localPart = parseSimplePart (inWords, inSection, localPart); + inWords.pos--; + inWords.data[inWords.pos] = ')'; //remove fake + } + + // see if we have more to come + while (!inWords.isEmpty () && inWords[0] != ')') + { + //eat the extensions to this part + if (inWords[0] == '(') + parseSentence (inWords); + else + parseLiteralC(inWords); + } + + if (inWords[0] == ')') + inWords.pos++; + skipWS (inWords); + + return localPart; +} + +void imapParser::parseBody (parseString & inWords) +{ + // see if we got a part specifier + if (inWords[0] == '[') + { + QCString specifier; + QCString label; + inWords.pos++; + + specifier = parseOneWordC (inWords, TRUE); + + if (inWords[0] == '(') + { + inWords.pos++; + + while (!inWords.isEmpty () && inWords[0] != ')') + { + label = parseOneWordC (inWords); + } + + if (inWords[0] == ')') + inWords.pos++; + } + if (inWords[0] == ']') + inWords.pos++; + skipWS (inWords); + + // parse the header + if (specifier == "0") + { + mailHeader *envelope = 0; + if (lastHandled) + envelope = lastHandled->getHeader (); + + if (!envelope || seenUid.isEmpty ()) + { + kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl; + // don't know where to put it, throw it away + parseLiteralC(inWords, true); + } + else + { + kdDebug(7116) << "imapParser::parseBody - reading " << envelope << " " << seenUid.ascii () << endl; + // fill it up with data + QString theHeader = parseLiteralC(inWords, true); + mimeIOQString myIO; + + myIO.setString (theHeader); + envelope->parseHeader (myIO); + + } + } + else if (specifier == "HEADER.FIELDS") + { + // BODY[HEADER.FIELDS (References)] {n} + //kdDebug(7116) << "imapParser::parseBody - HEADER.FIELDS: " + // << QCString(label.data(), label.size()+1) << endl; + if (label == "REFERENCES") + { + mailHeader *envelope = 0; + if (lastHandled) + envelope = lastHandled->getHeader (); + + if (!envelope || seenUid.isEmpty ()) + { + kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl; + // don't know where to put it, throw it away + parseLiteralC (inWords, true); + } + else + { + QCString references = parseLiteralC(inWords, true); + int start = references.find ('<'); + int end = references.findRev ('>'); + if (start < end) + references = references.mid (start, end - start + 1); + envelope->setReferences(references.simplifyWhiteSpace()); + } + } + else + { // not a header we care about throw it away + parseLiteralC(inWords, true); + } + } + else + { + if (specifier.find(".MIME") != -1) + { + mailHeader *envelope = new mailHeader; + QString theHeader = parseLiteralC(inWords, false); + mimeIOQString myIO; + myIO.setString (theHeader); + envelope->parseHeader (myIO); + if (lastHandled) + lastHandled->setHeader (envelope); + return; + } + // throw it away + kdDebug(7116) << "imapParser::parseBody - discarding " << seenUid.ascii () << endl; + parseLiteralC(inWords, true); + } + + } + else // no part specifier + { + mailHeader *envelope = 0; + if (lastHandled) + envelope = lastHandled->getHeader (); + + if (!envelope || seenUid.isEmpty ()) + { + kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl; + // don't know where to put it, throw it away + parseSentence (inWords); + } + else + { + kdDebug(7116) << "imapParser::parseBody - reading " << envelope << " " << seenUid.ascii () << endl; + // fill it up with data + QString section; + mimeHeader *body = parseBodyStructure (inWords, section, envelope); + if (body != envelope) + delete body; + } + } +} + +void imapParser::parseFetch (ulong /* value */, parseString & inWords) +{ + if (inWords[0] != '(') + return; + inWords.pos++; + skipWS (inWords); + + delete lastHandled; + lastHandled = 0; + + while (!inWords.isEmpty () && inWords[0] != ')') + { + if (inWords[0] == '(') + parseSentence (inWords); + else + { + QCString word = parseLiteralC(inWords, false, true); + + switch (word[0]) + { + case 'E': + if (word == "ENVELOPE") + { + mailHeader *envelope = 0; + + if (lastHandled) + envelope = lastHandled->getHeader (); + else + lastHandled = new imapCache(); + + if (envelope && !envelope->getMessageId ().isEmpty ()) + { + // we have seen this one already + // or don't know where to put it + parseSentence (inWords); + } + else + { + envelope = parseEnvelope (inWords); + if (envelope) + { + envelope->setPartSpecifier (seenUid + ".0"); + lastHandled->setHeader (envelope); + lastHandled->setUid (seenUid.toULong ()); + } + } + } + break; + + case 'B': + if (word == "BODY") + { + parseBody (inWords); + } + else if (word == "BODY[]" ) + { + // Do the same as with "RFC822" + parseLiteralC(inWords, true); + } + else if (word == "BODYSTRUCTURE") + { + mailHeader *envelope = 0; + + if (lastHandled) + envelope = lastHandled->getHeader (); + + // fill it up with data + QString section; + mimeHeader *body = + parseBodyStructure (inWords, section, envelope); + QByteArray data; + QDataStream stream( data, IO_WriteOnly ); + if (body) body->serialize(stream); + parseRelay(data); + + delete body; + } + break; + + case 'U': + if (word == "UID") + { + seenUid = parseOneWordC(inWords); + mailHeader *envelope = 0; + if (lastHandled) + envelope = lastHandled->getHeader (); + else + lastHandled = new imapCache(); + + if (seenUid.isEmpty ()) + { + // unknown what to do + kdDebug(7116) << "imapParser::parseFetch - UID empty" << endl; + } + else + { + lastHandled->setUid (seenUid.toULong ()); + } + if (envelope) + envelope->setPartSpecifier (seenUid); + } + break; + + case 'R': + if (word == "RFC822.SIZE") + { + ulong size; + parseOneNumber (inWords, size); + + if (!lastHandled) lastHandled = new imapCache(); + lastHandled->setSize (size); + } + else if (word.find ("RFC822") == 0) + { + // might be RFC822 RFC822.TEXT RFC822.HEADER + parseLiteralC(inWords, true); + } + break; + + case 'I': + if (word == "INTERNALDATE") + { + QCString date = parseOneWordC(inWords); + if (!lastHandled) lastHandled = new imapCache(); + lastHandled->setDate(date); + } + break; + + case 'F': + if (word == "FLAGS") + { + //kdDebug(7116) << "GOT FLAGS " << inWords.cstr() << endl; + if (!lastHandled) lastHandled = new imapCache(); + lastHandled->setFlags (imapInfo::_flags (inWords.cstr())); + } + break; + + default: + parseLiteralC(inWords); + break; + } + } + } + + // see if we have more to come + while (!inWords.isEmpty () && inWords[0] != ')') + { + //eat the extensions to this part + if (inWords[0] == '(') + parseSentence (inWords); + else + parseLiteralC(inWords); + } + + if (inWords.isEmpty() || inWords[0] != ')') + return; + inWords.pos++; + skipWS (inWords); +} + + +// default parser +void imapParser::parseSentence (parseString & inWords) +{ + bool first = true; + int stack = 0; + + //find the first nesting parentheses + + while (!inWords.isEmpty () && (stack != 0 || first)) + { + first = false; + skipWS (inWords); + + unsigned char ch = inWords[0]; + switch (ch) + { + case '(': + inWords.pos++; + ++stack; + break; + case ')': + inWords.pos++; + --stack; + break; + case '[': + inWords.pos++; + ++stack; + break; + case ']': + inWords.pos++; + --stack; + break; + default: + parseLiteralC(inWords); + skipWS (inWords); + break; + } + } + skipWS (inWords); +} + +void imapParser::parseRecent (ulong value, parseString & result) +{ + selectInfo.setRecent (value); + result.pos = result.data.size(); +} + +void imapParser::parseNamespace (parseString & result) +{ + if ( result[0] != '(' ) + return; + + QString delimEmpty; + if ( namespaceToDelimiter.contains( QString::null ) ) + delimEmpty = namespaceToDelimiter[QString::null]; + + namespaceToDelimiter.clear(); + imapNamespaces.clear(); + + // remember what section we're in (user, other users, shared) + int ns = -1; + bool personalAvailable = false; + while ( !result.isEmpty() ) + { + if ( result[0] == '(' ) + { + result.pos++; // tie off ( + if ( result[0] == '(' ) + { + // new namespace section + result.pos++; // tie off ( + ++ns; + } + // namespace prefix + QCString prefix = parseOneWordC( result ); + // delimiter + QCString delim = parseOneWordC( result ); + kdDebug(7116) << "imapParser::parseNamespace ns='" << prefix << + "',delim='" << delim << "'" << endl; + if ( ns == 0 ) + { + // at least one personal ns + personalAvailable = true; + } + QString nsentry = QString::number( ns ) + "=" + QString(prefix) + + "=" + QString(delim); + imapNamespaces.append( nsentry ); + if ( prefix.right( 1 ) == delim ) { + // strip delimiter to get a correct entry for comparisons + prefix.resize( prefix.length() ); + } + namespaceToDelimiter[prefix] = delim; + + result.pos++; // tie off ) + skipWS( result ); + } else if ( result[0] == ')' ) + { + result.pos++; // tie off ) + skipWS( result ); + } else if ( result[0] == 'N' ) + { + // drop NIL + ++ns; + parseOneWordC( result ); + } else { + // drop whatever it is + parseOneWordC( result ); + } + } + if ( !delimEmpty.isEmpty() ) { + // remember default delimiter + namespaceToDelimiter[QString::null] = delimEmpty; + if ( !personalAvailable ) + { + // at least one personal ns would be nice + kdDebug(7116) << "imapParser::parseNamespace - registering own personal ns" << endl; + QString nsentry = "0==" + delimEmpty; + imapNamespaces.append( nsentry ); + } + } +} + +int imapParser::parseLoop () +{ + parseString result; + + if (!parseReadLine(result.data)) return -1; + + //kdDebug(7116) << result.cstr(); // includes \n + + if (result.data.isEmpty()) + return 0; + if (!sentQueue.count ()) + { + // maybe greeting or BYE everything else SHOULD not happen, use NOOP or IDLE + kdDebug(7116) << "imapParser::parseLoop - unhandledResponse: \n" << result.cstr() << endl; + unhandled << result.cstr(); + } + else + { + imapCommand *current = sentQueue.at (0); + switch (result[0]) + { + case '*': + result.data.resize(result.data.size() - 2); // tie off CRLF + parseUntagged (result); + break; + case '+': + continuation.duplicate(result.data); + break; + default: + { + QCString tag = parseLiteralC(result); + if (current->id() == tag.data()) + { + result.data.resize(result.data.size() - 2); // tie off CRLF + QByteArray resultCode = parseLiteral (result); //the result + current->setResult (resultCode); + current->setResultInfo(result.cstr()); + current->setComplete (); + + sentQueue.removeRef (current); + completeQueue.append (current); + if (result.length()) + parseResult (resultCode, result, current->command()); + } + else + { + kdDebug(7116) << "imapParser::parseLoop - unknown tag '" << tag << "'" << endl; + QCString cstr = tag + " " + result.cstr(); + result.data = cstr; + result.pos = 0; + result.data.resize(cstr.length()); + } + } + break; + } + } + + return 1; +} + +void +imapParser::parseRelay (const QByteArray & buffer) +{ + Q_UNUSED(buffer); + qWarning + ("imapParser::parseRelay - virtual function not reimplemented - data lost"); +} + +void +imapParser::parseRelay (ulong len) +{ + Q_UNUSED(len); + qWarning + ("imapParser::parseRelay - virtual function not reimplemented - announcement lost"); +} + +bool imapParser::parseRead (QByteArray & buffer, ulong len, ulong relay) +{ + Q_UNUSED(buffer); + Q_UNUSED(len); + Q_UNUSED(relay); + qWarning + ("imapParser::parseRead - virtual function not reimplemented - no data read"); + return FALSE; +} + +bool imapParser::parseReadLine (QByteArray & buffer, ulong relay) +{ + Q_UNUSED(buffer); + Q_UNUSED(relay); + qWarning + ("imapParser::parseReadLine - virtual function not reimplemented - no data read"); + return FALSE; +} + +void +imapParser::parseWriteLine (const QString & str) +{ + Q_UNUSED(str); + qWarning + ("imapParser::parseWriteLine - virtual function not reimplemented - no data written"); +} + +void +imapParser::parseURL (const KURL & _url, QString & _box, QString & _section, + QString & _type, QString & _uid, QString & _validity, QString & _info) +{ + QStringList parameters; + + _box = _url.path (); + kdDebug(7116) << "imapParser::parseURL " << _box << endl; + int paramStart = _box.find("/;"); + if ( paramStart > -1 ) + { + QString paramString = _box.right( _box.length() - paramStart-2 ); + parameters = QStringList::split (';', paramString); //split parameters + _box.truncate( paramStart ); // strip parameters + } + // extract parameters + for (QStringList::ConstIterator it (parameters.begin ()); + it != parameters.end (); ++it) + { + QString temp = (*it); + + int pt = temp.find ('/'); + if (pt > 0) + { + if (temp.findRev ('"', pt) == -1 || temp.find('"', pt) == -1) + { + // if we have non-quoted '/' separator we'll just nuke it + temp.truncate(pt); + } + } + if (temp.find ("section=", 0, false) == 0) + _section = temp.right (temp.length () - 8); + else if (temp.find ("type=", 0, false) == 0) + _type = temp.right (temp.length () - 5); + else if (temp.find ("uid=", 0, false) == 0) + _uid = temp.right (temp.length () - 4); + else if (temp.find ("uidvalidity=", 0, false) == 0) + _validity = temp.right (temp.length () - 12); + else if (temp.find ("info=", 0, false) == 0) + _info = temp.right (temp.length () - 5); + } +// kdDebug(7116) << "URL: section= " << _section << ", type= " << _type << ", uid= " << _uid << endl; +// kdDebug(7116) << "URL: user() " << _url.user() << endl; +// kdDebug(7116) << "URL: path() " << _url.path() << endl; +// kdDebug(7116) << "URL: encodedPathAndQuery() " << _url.encodedPathAndQuery() << endl; + + if (!_box.isEmpty ()) + { + // strip / + if (_box[0] == '/') + _box = _box.right (_box.length () - 1); + if (!_box.isEmpty () && _box[_box.length () - 1] == '/') + _box.truncate(_box.length() - 1); + } + kdDebug(7116) << "URL: box= " << _box << ", section= " << _section << ", type= " + << _type << ", uid= " << _uid << ", validity= " << _validity << ", info= " << _info << endl; +} + + +QCString imapParser::parseLiteralC(parseString & inWords, bool relay, bool stopAtBracket, int *outlen) { + + if (!inWords.isEmpty() && inWords[0] == '{') + { + QCString retVal; + ulong runLen = inWords.find ('}', 1); + if (runLen > 0) + { + bool proper; + ulong runLenSave = runLen + 1; + QCString tmpstr(runLen); + inWords.takeMidNoResize(tmpstr, 1, runLen - 1); + runLen = tmpstr.toULong (&proper); + inWords.pos += runLenSave; + if (proper) + { + //now get the literal from the server + if (relay) + parseRelay (runLen); + QByteArray rv; + parseRead (rv, runLen, relay ? runLen : 0); + rv.resize(QMAX(runLen, rv.size())); // what's the point? + retVal = b2c(rv); + inWords.clear(); + parseReadLine (inWords.data); // must get more + + // no duplicate data transfers + relay = false; + } + else + { + kdDebug(7116) << "imapParser::parseLiteral - error parsing {} - " /*<< strLen*/ << endl; + } + } + else + { + inWords.clear(); + kdDebug(7116) << "imapParser::parseLiteral - error parsing unmatched {" << endl; + } + if (outlen) { + *outlen = retVal.length(); // optimize me + } + skipWS (inWords); + return retVal; + } + + return parseOneWordC(inWords, stopAtBracket, outlen); +} + +// does not know about literals ( {7} literal ) +QCString imapParser::parseOneWordC (parseString & inWords, bool stopAtBracket, int *outLen) +{ + uint retValSize = 0; + uint len = inWords.length(); + if (len == 0) { + return QCString(); + } + + if (len > 0 && inWords[0] == '"') + { + unsigned int i = 1; + bool quote = FALSE; + while (i < len && (inWords[i] != '"' || quote)) + { + if (inWords[i] == '\\') quote = !quote; + else quote = FALSE; + i++; + } + if (i < len) + { + QCString retVal(i); + inWords.pos++; + inWords.takeLeftNoResize(retVal, i - 1); + len = i - 1; + int offset = 0; + for (unsigned int j = 0; j <= len; j++) { + if (retVal[j] == '\\') { + offset++; + j++; + } + retVal[j - offset] = retVal[j]; + } + retVal[len - offset] = 0; + retValSize = len - offset; + inWords.pos += i; + skipWS (inWords); + if (outLen) { + *outLen = retValSize; + } + return retVal; + } + else + { + kdDebug(7116) << "imapParser::parseOneWord - error parsing unmatched \"" << endl; + QCString retVal = inWords.cstr(); + retValSize = len; + inWords.clear(); + if (outLen) { + *outLen = retValSize; + } + return retVal; + } + } + else + { + // not quoted + unsigned int i; + // search for end + for (i = 0; i < len; ++i) { + char ch = inWords[i]; + if (ch <= ' ' || ch == '(' || ch == ')' || + (stopAtBracket && (ch == '[' || ch == ']'))) + break; + } + + QCString retVal(i+1); + inWords.takeLeftNoResize(retVal, i); + retValSize = i; + inWords.pos += i; + + if (retVal == "NIL") { + retVal.truncate(0); + retValSize = 0; + } + skipWS (inWords); + if (outLen) { + *outLen = retValSize; + } + return retVal; + } +} + +bool imapParser::parseOneNumber (parseString & inWords, ulong & num) +{ + bool valid; + num = parseOneWordC(inWords, TRUE).toULong(&valid); + return valid; +} + +bool imapParser::hasCapability (const QString & cap) +{ + QString c = cap.lower(); +// kdDebug(7116) << "imapParser::hasCapability - Looking for '" << cap << "'" << endl; + for (QStringList::ConstIterator it = imapCapabilities.begin (); + it != imapCapabilities.end (); ++it) + { +// kdDebug(7116) << "imapParser::hasCapability - Examining '" << (*it) << "'" << endl; + if ( !(kasciistricmp(c.ascii(), (*it).ascii())) ) + { + return true; + } + } + return false; +} + +void imapParser::removeCapability (const QString & cap) +{ + imapCapabilities.remove(cap.lower()); +} + +QString imapParser::namespaceForBox( const QString & box ) +{ + kdDebug(7116) << "imapParse::namespaceForBox " << box << endl; + QString myNamespace; + if ( !box.isEmpty() ) + { + QValueList<QString> list = namespaceToDelimiter.keys(); + QString cleanPrefix; + for ( QValueList<QString>::Iterator it = list.begin(); it != list.end(); ++it ) + { + if ( !(*it).isEmpty() && box.find( *it ) != -1 ) + return (*it); + } + } + return myNamespace; +} + diff --git a/kioslaves/imap4/imapparser.h b/kioslaves/imap4/imapparser.h new file mode 100644 index 000000000..74fad1457 --- /dev/null +++ b/kioslaves/imap4/imapparser.h @@ -0,0 +1,500 @@ +#ifndef _IMAPPARSER_H +#define _IMAPPARSER_H +/********************************************************************** + * + * imapparser.h - IMAP4rev1 Parser + * Copyright (C) 2001-2002 Michael Haeckel <haeckel@kde.org> + * Copyright (C) 2000 s.carstens@gmx.de + * + * 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 s.carstens@gmx.de + * + *********************************************************************/ + +#include <qstringlist.h> +#include <qvaluelist.h> +#include <qptrlist.h> +#include <qasciidict.h> + +#include <kio/authinfo.h> +#include <kio/slavebase.h> + +#include "imaplist.h" +#include "imapcommand.h" +#include "imapinfo.h" + +#include "mailheader.h" + +class KURL; +class QString; +class mailAddress; +class mimeHeader; + + +/** @brief a string used during parsing + * the string allows you to move the effective start of the string using + * str.pos++ and str.pos--. + * @bug it is possible to move past the beginning and end of the string + */ +class parseString +{ +public: + parseString() { pos = 0; } + char operator[](uint i) const { return data[i + pos]; } + bool isEmpty() const { return pos >= data.size(); } + QCString cstr() const + { + if (pos >= data.size()) return QCString(); + return QCString(data.data() + pos, data.size() - pos + 1); + } + int find(char c, int index = 0) + { + int res = data.find(c, index + pos); + return (res == -1) ? res : (res - pos); + } + // Warning: does not check for going past end of "data" + void takeLeft(QCString& dest, uint len) const + { + dest.resize(len + 1); + qmemmove(dest.data(), data.data() + pos, len); + } + // Warning: does not check for going past end of "data" + void takeLeftNoResize(QCString& dest, uint len) const + { + qmemmove(dest.data(), data.data() + pos, len); + } + // Warning: does not check for going past end of "data" + void takeMid(QCString& dest, uint start, uint len) const + { + dest.resize(len + 1); + qmemmove(dest.data(), data.data() + pos + start, len); + } + // Warning: does not check for going past end of "data" + void takeMidNoResize(QCString& dest, uint start, uint len) const + { + qmemmove(dest.data(), data.data() + pos + start, len); + } + void clear() + { + data.resize(0); + pos = 0; + } + uint length() + { + return data.size() - pos; + } + void fromString(const QString &s) + { + clear(); + data.duplicate(s.latin1(), s.length()); + } + QByteArray data; + uint pos; +}; + +class imapCache +{ +public: + imapCache () + { + myHeader = NULL; + mySize = 0; + myFlags = 0; + myUid = 0; + } + + ~imapCache () + { + if (myHeader) delete myHeader; + } + + mailHeader *getHeader () + { + return myHeader; + } + void setHeader (mailHeader * inHeader) + { + myHeader = inHeader; + } + + ulong getSize () + { + return mySize; + } + void setSize (ulong inSize) + { + mySize = inSize; + } + + ulong getUid () + { + return myUid; + } + void setUid (ulong inUid) + { + myUid = inUid; + } + + ulong getFlags () + { + return myFlags; + } + void setFlags (ulong inFlags) + { + myFlags = inFlags; + } + + QCString getDate () + { + return myDate; + } + void setDate (const QCString & _str) + { + myDate = _str; + } + void clear() + { + if (myHeader) delete myHeader; + myHeader = NULL; + mySize = 0; + myFlags = 0; + myDate = QCString(); + myUid = 0; + } + +protected: + mailHeader * myHeader; + ulong mySize; + ulong myFlags; + ulong myUid; + QCString myDate; +}; + + +class imapParser +{ + +public: + + /** the different states the client can be in */ + enum IMAP_STATE + { + ISTATE_NO, /**< Not connected */ + ISTATE_CONNECT, /**< Connected but not logged in */ + ISTATE_LOGIN, /**< Logged in */ + ISTATE_SELECT /**< A folder is currently selected */ + }; + +public: + imapParser (); + virtual ~ imapParser (); + + /** @brief Get the current state */ + enum IMAP_STATE getState () { return currentState; } + /** @brief Set the current state */ + void setState(enum IMAP_STATE state) { currentState = state; } + + /* @brief return the currently selected mailbox */ + const QString getCurrentBox () + { + return rfcDecoder::fromIMAP(currentBox); + }; + + /** + * @brief do setup and send the command to parseWriteLine + * @param aCmd The command to perform + * @return The completed command + */ + imapCommand *sendCommand (imapCommand * aCmd); + /** + * @brief perform a command and wait to parse the result + * @param aCmd The command to perform + * @return The completed command + */ + imapCommand *doCommand (imapCommand * aCmd); + + + /** + * @brief plaintext login + * @param aUser Username + * @param aPass Password + * @param resultInfo The resultinfo from the command + * @return success or failure + */ + bool clientLogin (const QString & aUser, const QString & aPass, QString & resultInfo); + /** + * @brief non-plaintext login + * @param aUser Username + * @param aPass Password + * @param aAuth authentication method + * @param isSSL are we using SSL + * @param resultInfo The resultinfo from the command + * @return success or failure + */ + bool clientAuthenticate (KIO::SlaveBase *slave, KIO::AuthInfo &ai, const QString & aFQDN, + const QString & aAuth, bool isSSL, QString & resultInfo); + + /** + * main loop for the parser + * reads one line and dispatches it to the appropriate sub parser + */ + int parseLoop (); + + /** + * @brief parses all untagged responses and passes them on to the + * following parsers + */ + void parseUntagged (parseString & result); + + /** @brief parse a RECENT line */ + void parseRecent (ulong value, parseString & result); + /** @brief parse a RESULT line */ + void parseResult (QByteArray & result, parseString & rest, + const QString & command = QString::null); + /** @brief parse a CAPABILITY line */ + void parseCapability (parseString & result); + /** @brief parse a FLAGS line */ + void parseFlags (parseString & result); + /** @brief parse a LIST line */ + void parseList (parseString & result); + /** @brief parse a LSUB line */ + void parseLsub (parseString & result); + /** @brief parse a LISTRIGHTS line */ + void parseListRights (parseString & result); + /** @brief parse a MYRIGHTS line */ + void parseMyRights (parseString & result); + /** @brief parse a SEARCH line */ + void parseSearch (parseString & result); + /** @brief parse a STATUS line */ + void parseStatus (parseString & result); + /** @brief parse a EXISTS line */ + void parseExists (ulong value, parseString & result); + /** @brief parse a EXPUNGE line */ + void parseExpunge (ulong value, parseString & result); + /** @brief parse a ACL line */ + void parseAcl (parseString & result); + /** @brief parse a ANNOTATION line */ + void parseAnnotation (parseString & result); + /** @brief parse a NAMESPACE line */ + void parseNamespace (parseString & result); + /** @brief parse a QUOTAROOT line */ + void parseQuotaRoot (parseString & result); + /** @brief parse a QUOTA line */ + void parseQuota (parseString & result); + /** @brief parse a custom command line */ + void parseCustom (parseString & result); + /** @brief parse a OTHER-USER line */ + void parseOtherUser (parseString & result); + /** @brief parse a DELEGATE line */ + void parseDelegate (parseString & result); + /** @brief parse a OUT-OF-OFFICE line */ + void parseOutOfOffice (parseString & result); + + /** + * parses the results of a fetch command + * processes it with the following sub parsers + */ + void parseFetch (ulong value, parseString & inWords); + + /** read a envelope from imap and parse the addresses */ + mailHeader *parseEnvelope (parseString & inWords); + /** @brief parse an address list and return a list of addresses */ + void parseAddressList (parseString & inWords, QPtrList<mailAddress>& list); + /** @brief parse an address and return the ref again */ + const mailAddress& parseAddress (parseString & inWords, mailAddress& buffer); + + /** parse the result of the body command */ + void parseBody (parseString & inWords); + + /** parse the body structure recursively */ + mimeHeader *parseBodyStructure (parseString & inWords, + QString & section, mimeHeader * inHeader = 0); + + /** parse only one not nested part */ + mimeHeader *parseSimplePart (parseString & inWords, QString & section, + mimeHeader * localPart = 0); + + /** parse a parameter list (name value pairs) */ + QAsciiDict < QString > parseParameters (parseString & inWords); + + /** + * parse the disposition list (disposition (name value pairs)) + * the disposition has the key 'content-disposition' + */ + QAsciiDict < QString > parseDisposition (parseString & inWords); + + // reimplement these + + /** relay hook to send the fetched data directly to an upper level */ + virtual void parseRelay (const QByteArray & buffer); + + /** relay hook to announce the fetched data directly to an upper level + */ + virtual void parseRelay (ulong); + + /** read at least len bytes */ + virtual bool parseRead (QByteArray & buffer, ulong len, ulong relay = 0); + + /** read at least a line (up to CRLF) */ + virtual bool parseReadLine (QByteArray & buffer, ulong relay = 0); + + /** write argument to server */ + virtual void parseWriteLine (const QString &); + + // generic parser routines + + /** parse a parenthesized list */ + void parseSentence (parseString & inWords); + + /** parse a literal or word, may require more data */ + QCString parseLiteralC(parseString & inWords, bool relay = false, + bool stopAtBracket = false, int *outlen = 0); + inline QByteArray parseLiteral (parseString & inWords, bool relay = false, + bool stopAtBracket = false) { + int len = 0; // string size + // Choice: we can create an extra QCString, or we can get the buffer in + // the wrong size to start. Let's try option b. + QCString tmp = parseLiteralC(inWords, relay, stopAtBracket, &len); + return QByteArray().duplicate(tmp.data(), len); + } + + // static parser routines, can be used elsewhere + + static QCString b2c(const QByteArray &ba) + { return QCString(ba.data(), ba.size() + 1); } + + /** parse one word (maybe quoted) upto next space " ) ] } */ + static QCString parseOneWordC (parseString & inWords, + bool stopAtBracket = FALSE, int *len = 0); + + /** parse one number using parseOneWord */ + static bool parseOneNumber (parseString & inWords, ulong & num); + + /** extract the box,section,list type, uid, uidvalidity,info from an url */ + static void parseURL (const KURL & _url, QString & _box, QString & _section, + QString & _type, QString & _uid, QString & _validity, + QString & _info); + + + /** @brief return the last handled foo + * @todo work out what a foo is + */ + imapCache *getLastHandled () + { + return lastHandled; + }; + +/** @brief return the last results */ + const QStringList & getResults () + { + return lastResults; + }; + + /** @brief return the last status code */ + const imapInfo & getStatus () + { + return lastStatus; + }; + /** return the select info */ + const imapInfo & getSelected () + { + return selectInfo; + }; + + const QByteArray & getContinuation () + { + return continuation; + }; + + /** @brief see if server has a capability */ + bool hasCapability (const QString &); + + void removeCapability (const QString & cap); + + static inline void skipWS (parseString & inWords) + { + char c; + while (!inWords.isEmpty() && + ((c = inWords[0]) == ' ' || c == '\t' || c == '\r' || c == '\n')) + { + inWords.pos++; + } + } + + /** @brief find the namespace for the given box */ + QString namespaceForBox( const QString & box ); + + +protected: + + /** the current state we're in */ + enum IMAP_STATE currentState; + + /** the box selected */ + QString currentBox; + + /** @brief here we store the result from select/examine and unsolicited updates */ + imapInfo selectInfo; + + /** @brief the results from the last status command */ + imapInfo lastStatus; + + /** @brief the results from the capabilities, split at ' ' */ + QStringList imapCapabilities; + + /** @brief the results from list/lsub/listrights commands */ + QValueList < imapList > listResponses; + + /** @brief queues handling the running commands */ + QPtrList < imapCommand > sentQueue; // no autodelete + QPtrList < imapCommand > completeQueue; // autodelete !! + + /** + * everything we didn't handle, everything but the greeting is bogus + */ + QStringList unhandled; + + /** the last continuation request (there MUST not be more than one pending) */ + QByteArray continuation; + + /** the last uid seen while a fetch */ + QString seenUid; + imapCache *lastHandled; + + ulong commandCounter; + + /** @brief the results from search/acl commands */ + QStringList lastResults; + + /** + * @brief namespace prefix - delimiter association + * The namespace is cleaned before so that it does not contain the delimiter + */ + QMap<QString, QString> namespaceToDelimiter; + + /** + * @brief list of namespaces in the form: section=namespace=delimiter + * section is 0 (personal), 1 (other users) or 2 (shared) + */ + QStringList imapNamespaces; + +private: + + /** we don't want to be able to copy this object */ + imapParser & operator = (const imapParser &); // hide the copy ctor + +}; +#endif diff --git a/kioslaves/imap4/imaps.protocol b/kioslaves/imap4/imaps.protocol new file mode 100644 index 000000000..1846bd3cb --- /dev/null +++ b/kioslaves/imap4/imaps.protocol @@ -0,0 +1,30 @@ +[Protocol] +# The executable, of course +#### Temporary name +exec=kio_imap4 +# protocol that will appear in URLs +#### This ioslave is temporarily named imaps, while imaps remains in kdebase +protocol=imaps + +# input/output can be one of: filesystem, stream, none +input=stream +output=filesystem + +# Headings for file listings? +listing=Name,Type,Size,Owner +deleting=true +linking=false +# For now, reading yes, writing no +reading=true +writing=false +# For now, no moving +moving=false + +# Can be source protocol +source=true + +# List of capabilities (e.g. special() commands) +Capabilities=Subscription,ACL,Quota + +Icon=folder_inbox +DocPath=kioslave/imaps.html diff --git a/kioslaves/imap4/mailaddress.cc b/kioslaves/imap4/mailaddress.cc new file mode 100644 index 000000000..a70b2d591 --- /dev/null +++ b/kioslaves/imap4/mailaddress.cc @@ -0,0 +1,323 @@ +/********************************************************************** + * + * mailaddress.cc - mail address parser + * Copyright (C) 2000 Sven Carstens + * + * 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 + * + *********************************************************************/ + + +#include "mailaddress.h" +#include "rfcdecoder.h" +#include "mimehdrline.h" +#include <kmime_util.h> + +mailAddress::mailAddress () +{ +} + +mailAddress::mailAddress (const mailAddress & lr): +user (lr.user), +host (lr.host), +rawFullName (lr.rawFullName), +rawComment (lr.rawComment) +{ +// kdDebug(7116) << "mailAddress::mailAddress - " << getStr() << endl; +} + +mailAddress & mailAddress::operator = (const mailAddress & lr) +{ + // Avoid a = a. + if (this == &lr) + return *this; + + user = lr.user; + host = lr.host; + rawFullName = lr.rawFullName; + rawComment = lr.rawComment; + +// kdDebug(7116) << "mailAddress::operator= - " << getStr() << endl; + + return *this; +} + + + + +mailAddress::~mailAddress () +{ +} + +mailAddress::mailAddress (char *aCStr) +{ + parseAddress (aCStr); +} + +int +mailAddress::parseAddress (char *aCStr) +{ + int retVal = 0; + int skip; + uint len; + int pt; + + if (aCStr) + { + //skip leading white space + skip = mimeHdrLine::skipWS ((const char *) aCStr); + if (skip > 0) + { + aCStr += skip; + retVal += skip; + } + while (*aCStr) + { + int advance; + + switch (*aCStr) + { + case '"': + advance = mimeHdrLine::parseQuoted ('"', '"', aCStr); + rawFullName += QCString (aCStr, advance + 1); + break; + case '(': + advance = mimeHdrLine::parseQuoted ('(', ')', aCStr); + rawComment += QCString (aCStr, advance + 1); + break; + case '<': + advance = mimeHdrLine::parseQuoted ('<', '>', aCStr); + user = QCString (aCStr, advance + 1); // copy it + len = advance; + user = user.mid (1, len - 2); // strip <> + len -= 2; + pt = user.find('@'); + host = user.right (len - pt - 1); // split it into host + user.truncate(pt); // and user + break; + default: + advance = mimeHdrLine::parseWord ((const char *) aCStr); + //if we've seen a FQ mailname the rest must be quoted or is just junk + if (user.isEmpty ()) + { + if (*aCStr != ',') + { + rawFullName += QCString (aCStr, advance + 1); + if (mimeHdrLine::skipWS ((const char *) &aCStr[advance]) > 0) + { + rawFullName += ' '; + } + } + } + break; + } + if (advance) + { + retVal += advance; + aCStr += advance; + } + else + break; + advance = mimeHdrLine::skipWS ((const char *) aCStr); + if (advance > 0) + { + retVal += advance; + aCStr += advance; + } + //reached end of current address + if (*aCStr == ',') + { + advance++; + break; + } + } + //let's see what we've got + if (rawFullName.isEmpty ()) + { + if (user.isEmpty ()) + retVal = 0; + else + { + if (host.isEmpty ()) + { + rawFullName = user; + user.truncate(0); + } + } + } + else if (user.isEmpty ()) + { + pt = rawFullName.find ('@'); + if (pt >= 0) + { + user = rawFullName; + host = user.right (user.length () - pt - 1); + user.truncate(pt); + rawFullName.truncate(0); + } + } + +#if 0 +// dead + if (!rawFullName.isEmpty ()) + { +// if(fullName[0] == '"') +// fullName = fullName.mid(1,fullName.length()-2); +// fullName = fullName.simplifyWhiteSpace().stripWhiteSpace(); +// fullName = rfcDecoder::decodeRFC2047String(fullName.ascii()); + } +#endif + if (!rawComment.isEmpty ()) + { + if (rawComment[0] == '(') + rawComment = rawComment.mid (1, rawComment.length () - 2); + rawComment = rawComment.stripWhiteSpace (); +// comment = rfcDecoder::decodeRFC2047String(comment.ascii()); + } + } + else + { + //debug(); + } + return retVal; +} + +const QCString +mailAddress::getStr () +{ + QCString retVal(128); // Should be generally big enough + + if (!rawFullName.isEmpty ()) + { + KMime::addQuotes( rawFullName, false ); + retVal = rawFullName + " "; + } + if (!user.isEmpty ()) + { + retVal += '<'; + retVal += user; + if (!host.isEmpty ()) { + retVal += '@'; + retVal += host; + } + retVal += '>'; + } + if (!rawComment.isEmpty ()) + { + retVal = '(' + rawComment + ')'; + } +// kdDebug(7116) << "mailAddress::getStr - '" << retVal << "'" << endl; + return retVal; +} + +bool +mailAddress::isEmpty () const +{ + return user.isEmpty (); +} + +void +mailAddress::setFullName (const QString & _str) +{ + rawFullName = rfcDecoder::encodeRFC2047String (_str).latin1 (); +} +const QString +mailAddress::getFullName () const +{ + return rfcDecoder::decodeRFC2047String (rawFullName); +} + +void +mailAddress::setCommentRaw (const QCString & _str) +{ + rawComment = _str; +} + +void +mailAddress::setComment (const QString & _str) +{ + rawComment = rfcDecoder::encodeRFC2047String (_str).latin1 (); +} +const QString +mailAddress::getComment () const +{ + return rfcDecoder::decodeRFC2047String (rawComment); +} + +const QCString & +mailAddress::getCommentRaw () const +{ + return rawComment; +} + +QString +mailAddress::emailAddrAsAnchor (const mailAddress & adr, bool shortAdr) +{ + QString retVal; + if (!adr.getFullName ().isEmpty ()) + { + // should do some umlaut escaping + retVal += adr.getFullName () + " "; + } + if (!adr.getUser ().isEmpty () && !shortAdr) + { + retVal += "<" + adr.getUser (); + if (!adr.getHost ().isEmpty ()) + retVal += "@" + adr.getHost (); + retVal += "> "; + } + if (!adr.getComment ().isEmpty ()) + { + // should do some umlaut escaping + retVal = '(' + adr.getComment () + ')'; + } + + if (!adr.getUser ().isEmpty ()) + { + QString mail; + mail = adr.getUser (); + if (!mail.isEmpty () && !adr.getHost ().isEmpty ()) + mail += "@" + adr.getHost (); + if (!mail.isEmpty ()) + retVal = "<A HREF=\"mailto:" + mail + "\">" + retVal + "</A>"; + } + return retVal; +} + +QString +mailAddress::emailAddrAsAnchor (const QPtrList < mailAddress > &list, bool value) +{ + QString retVal; + QPtrListIterator < mailAddress > it (list); + + while (it.current ()) + { + retVal += emailAddrAsAnchor ((*it.current ()), value) + "<BR></BR>\n"; + ++it; + } + + return retVal; +} + + +void mailAddress::clear() { + user.truncate(0); + host.truncate(0); + rawFullName.truncate(0); + rawComment.truncate(0); +} + diff --git a/kioslaves/imap4/mailaddress.h b/kioslaves/imap4/mailaddress.h new file mode 100644 index 000000000..4ee68a396 --- /dev/null +++ b/kioslaves/imap4/mailaddress.h @@ -0,0 +1,81 @@ +#ifndef _MAILADDRESS_H +#define _MAILADDRESS_H +/********************************************************************** + * + * mailaddress.h - mail address handler + * Copyright (C) 2000 s.carstens@gmx.de + * + * 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 s.carstens@gmx.de + * + *********************************************************************/ + +#include <qptrlist.h> +#include <qstring.h> +#include <qcstring.h> +#include "rfcdecoder.h" + +class mailAddress +{ +public: + mailAddress (); + ~mailAddress (); + mailAddress (char *aCStr); + mailAddress (const mailAddress &); + mailAddress & operator = (const mailAddress &); + + void setUser (const QCString & aUser) + { + user = aUser; + } + const QCString & getUser () const + { + return user; + } + void setHost (const QCString & aHost) + { + host = aHost; + } + const QCString & getHost () const + { + return host; + } + + void setFullName (const QString & aFull); + const QString getFullName () const; + + void setComment (const QString & aComment); + void setCommentRaw (const QCString &); + const QString getComment () const; + const QCString & getCommentRaw () const; + + int parseAddress (char *); + const QCString getStr (); + bool isEmpty () const; + + static QString emailAddrAsAnchor (const mailAddress &, bool); + static QString emailAddrAsAnchor (const QPtrList < mailAddress > &, bool); + + void clear(); + +private: + QCString user; + QCString host; + QCString rawFullName; + QCString rawComment; +}; + +#endif diff --git a/kioslaves/imap4/mailheader.cc b/kioslaves/imap4/mailheader.cc new file mode 100644 index 000000000..960e5ba52 --- /dev/null +++ b/kioslaves/imap4/mailheader.cc @@ -0,0 +1,203 @@ +/*************************************************************************** + mailheader.cc - description + ------------------- + begin : Tue Oct 24 2000 + copyright : (C) 2000 by Sven Carstens + email : s.carstens@gmx.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "mailheader.h" +#include "rfcdecoder.h" + +mailHeader::mailHeader () +{ + toAdr.setAutoDelete (true); + ccAdr.setAutoDelete (true); + bccAdr.setAutoDelete (true); + setType ("text/plain"); + gmt_offset = 0; +} + +mailHeader::~mailHeader () +{ +} + +void +mailHeader::addHdrLine (mimeHdrLine * inLine) +{ + mimeHdrLine *addLine = new mimeHdrLine (inLine); + + const QCString label(addLine->getLabel()); + const QCString value(addLine->getValue()); + + if (!qstricmp (label, "Return-Path")) { + returnpathAdr.parseAddress (value.data ()); + goto out; + } + if (!qstricmp (label, "Sender")) { + senderAdr.parseAddress (value.data ()); + goto out; + } + if (!qstricmp (label, "From")) { + fromAdr.parseAddress (value.data ()); + goto out; + } + if (!qstricmp (label, "Reply-To")) { + replytoAdr.parseAddress (value.data ()); + goto out; + } + if (!qstricmp (label, "To")) { + mailHeader::parseAddressList (value, &toAdr); + goto out; + } + if (!qstricmp (label, "CC")) { + mailHeader::parseAddressList (value, &ccAdr); + goto out; + } + if (!qstricmp (label, "BCC")) { + mailHeader::parseAddressList (value, &bccAdr); + goto out; + } + if (!qstricmp (label, "Subject")) { + _subject = value.simplifyWhiteSpace(); + goto out; + } + if (!qstricmp (label.data (), "Date")) { + mDate = value; + goto out; + } + if (!qstricmp (label.data (), "Message-ID")) { + int start = value.findRev ('<'); + int end = value.findRev ('>'); + if (start < end) + messageID = value.mid (start, end - start + 1); + else { + qWarning("bad Message-ID"); + /* messageID = value; */ + } + goto out; + } + if (!qstricmp (label.data (), "In-Reply-To")) { + int start = value.findRev ('<'); + int end = value.findRev ('>'); + if (start < end) + inReplyTo = value.mid (start, end - start + 1); + goto out; + } + + // everything else is handled by mimeHeader + mimeHeader::addHdrLine (inLine); + delete addLine; + return; + + out: +// cout << label.data() << ": '" << value.data() << "'" << endl; + + //need only to add this line if not handled by mimeHeader + originalHdrLines.append (addLine); +} + +void +mailHeader::outputHeader (mimeIO & useIO) +{ + static const QCString __returnPath("Return-Path: ", 14); + static const QCString __from ("From: ", 7); + static const QCString __sender ("Sender: ", 9); + static const QCString __replyTo ("Reply-To: ", 11); + static const QCString __to ("To: ", 5); + static const QCString __cc ("CC: ", 5); + static const QCString __bcc ("BCC: ", 6); + static const QCString __subject ("Subject: ", 10); + static const QCString __messageId ("Message-ID: ", 13); + static const QCString __inReplyTo ("In-Reply-To: ", 14); + static const QCString __references("References: ", 13); + static const QCString __date ("Date: ", 7); + + if (!returnpathAdr.isEmpty()) + useIO.outputMimeLine(__returnPath + returnpathAdr.getStr()); + if (!fromAdr.isEmpty()) + useIO.outputMimeLine(__from + fromAdr.getStr()); + if (!senderAdr.isEmpty()) + useIO.outputMimeLine(__sender + senderAdr.getStr()); + if (!replytoAdr.isEmpty()) + useIO.outputMimeLine(__replyTo + replytoAdr.getStr()); + + if (toAdr.count()) + useIO.outputMimeLine(mimeHdrLine::truncateLine(__to + + mailHeader::getAddressStr(&toAdr))); + if (ccAdr.count()) + useIO.outputMimeLine(mimeHdrLine::truncateLine(__cc + + mailHeader::getAddressStr(&ccAdr))); + if (bccAdr.count()) + useIO.outputMimeLine(mimeHdrLine::truncateLine(__bcc + + mailHeader::getAddressStr(&bccAdr))); + if (!_subject.isEmpty()) + useIO.outputMimeLine(mimeHdrLine::truncateLine(__subject + _subject)); + if (!messageID.isEmpty()) + useIO.outputMimeLine(mimeHdrLine::truncateLine(__messageId + messageID)); + if (!inReplyTo.isEmpty()) + useIO.outputMimeLine(mimeHdrLine::truncateLine(__inReplyTo + inReplyTo)); + if (!references.isEmpty()) + useIO.outputMimeLine(mimeHdrLine::truncateLine(__references + references)); + + if (!mDate.isEmpty()) + useIO.outputMimeLine(__date + mDate); + mimeHeader::outputHeader(useIO); +} + +int +mailHeader::parseAddressList (const char *inCStr, + QPtrList < mailAddress > *aList) +{ + int advance = 0; + int skip = 1; + char *aCStr = (char *) inCStr; + + if (!aCStr || !aList) + return 0; + while (skip > 0) + { + mailAddress *aAddress = new mailAddress; + skip = aAddress->parseAddress (aCStr); + if (skip) + { + aCStr += skip; + if (skip < 0) + advance -= skip; + else + advance += skip; + aList->append (aAddress); + } + else + { + delete aAddress; + break; + } + } + return advance; +} + +QCString +mailHeader::getAddressStr (QPtrList < mailAddress > *aList) +{ + QCString retVal; + + QPtrListIterator < mailAddress > it = QPtrListIterator < mailAddress > (*aList); + while (it.current ()) + { + retVal += it.current ()->getStr (); + ++it; + if (it.current ()) + retVal += ", "; + } + return retVal; +} diff --git a/kioslaves/imap4/mailheader.h b/kioslaves/imap4/mailheader.h new file mode 100644 index 000000000..0586b3a0c --- /dev/null +++ b/kioslaves/imap4/mailheader.h @@ -0,0 +1,190 @@ +/*************************************************************************** + mailheader.h - description + ------------------- + begin : Tue Oct 24 2000 + copyright : (C) 2000 by Sven Carstens + email : s.carstens@gmx.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef MAILHEADER_H +#define MAILHEADER_H + +#include "mimeheader.h" +#include "mailaddress.h" +#include "mimeio.h" +#include "rfcdecoder.h" + +/** + *@author Sven Carstens + */ + +class mailHeader:public mimeHeader +{ +public: + mailHeader (); + ~mailHeader (); + + virtual void addHdrLine (mimeHdrLine *); + virtual void outputHeader (mimeIO &); + + void addTo (const mailAddress & _adr) + { + toAdr.append (new mailAddress (_adr)); + } + void addCC (const mailAddress & _adr) + { + ccAdr.append (new mailAddress (_adr)); + } + void addBCC (const mailAddress & _adr) + { + bccAdr.append (new mailAddress (_adr)); + } + + void setFrom (const mailAddress & _adr) + { + fromAdr = _adr; + } + void setSender (const mailAddress & _adr) + { + senderAdr = _adr; + } + void setReturnPath (const mailAddress & _adr) + { + returnpathAdr = _adr; + } + void setReplyTo (const mailAddress & _adr) + { + replytoAdr = _adr; + } + + const QCString& getMessageId () + { + return messageID; + } + void setMessageId (const QCString & _str) + { + messageID = _str; + } + + const QCString& getInReplyTo () + { + return inReplyTo; + } + void setInReplyTo (const QCString & _str) + { + inReplyTo = _str; + } + + const QCString& getReferences () + { + return references; + } + void setReferences (const QCString & _str) + { + references = _str; + } + + /** + * set a unicode subject + */ + void setSubject (const QString & _str) + { + _subject = rfcDecoder::encodeRFC2047String(_str).latin1(); + } + /** + * set a encoded subject + */ + void setSubjectEncoded (const QCString & _str) + { + _subject = _str.simplifyWhiteSpace(); + } + + /** + * get the unicode subject + */ + const QString getSubject () + { + return rfcDecoder::decodeRFC2047String(_subject); + } + /** + * get the encoded subject + */ + const QCString& getSubjectEncoded () + { + return _subject; + } + + /** + * set the date + */ + void setDate (const QCString & _str) + { + mDate = _str; + } + + /** + * get the date + */ + const QCString& date () + { + return mDate; + } + + static int parseAddressList (const char *, QPtrList < mailAddress > *); + static QCString getAddressStr (QPtrList < mailAddress > *); + QPtrList < mailAddress > &to () + { + return toAdr; + } + QPtrList < mailAddress > &cc () + { + return ccAdr; + } + QPtrList < mailAddress > &bcc () + { + return bccAdr; + } +#ifdef KMAIL_COMPATIBLE + QString subject () + { + return getSubject (); + } + const mailAddress & from () + { + return fromAdr; + } + const mailAddress & replyTo () + { + return replytoAdr; + } + void readConfig (void) + {; + } +#endif + +private: + QPtrList < mailAddress > toAdr; + QPtrList < mailAddress > ccAdr; + QPtrList < mailAddress > bccAdr; + mailAddress fromAdr; + mailAddress senderAdr; + mailAddress returnpathAdr; + mailAddress replytoAdr; + QCString _subject; + QCString mDate; + int gmt_offset; + QCString messageID; + QCString inReplyTo; + QCString references; +}; + +#endif diff --git a/kioslaves/imap4/mimehdrline.cc b/kioslaves/imap4/mimehdrline.cc new file mode 100644 index 000000000..9b03ec923 --- /dev/null +++ b/kioslaves/imap4/mimehdrline.cc @@ -0,0 +1,521 @@ +/*************************************************************************** + mimehdrline.cc - description + ------------------- + begin : Wed Oct 11 2000 + copyright : (C) 2000 by Sven Carstens + email : s.carstens@gmx.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include <config.h> +#include <iostream> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> + +#include "mimehdrline.h" +#include "rfcdecoder.h" + +using namespace std; + +const char *wdays[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +const char *months[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +mimeHdrLine::mimeHdrLine (): +mimeValue ((const char *) NULL), mimeLabel ((const char *) NULL) +{ +} + +mimeHdrLine::mimeHdrLine (const QCString & aLabel, const QCString & aValue): +mimeValue (aValue), +mimeLabel (aLabel) +{ +} + +mimeHdrLine::mimeHdrLine (mimeHdrLine * aHdrLine): +mimeValue (aHdrLine->mimeValue), mimeLabel (aHdrLine->mimeLabel) +{ +} + +mimeHdrLine::~mimeHdrLine () +{ +} + +int +mimeHdrLine::appendStr (const char *inCStr) +{ + int retVal = 0; + int skip; + char *aCStr = (char *) inCStr; + + if (aCStr) + { + skip = skipWS (aCStr); + if (skip && !mimeLabel.isEmpty ()) + { + if (skip > 0) + { + mimeValue += QCString (aCStr, skip + 1); + aCStr += skip; + retVal += skip; + skip = parseFullLine (aCStr); + mimeValue += QCString (aCStr, skip + 1); + retVal += skip; + aCStr += skip; + } + } + else + { + if (mimeLabel.isEmpty ()) + return setStr (aCStr); + } + } + return retVal; +} + +/** parse a Line into the class +move input ptr accordingly +and report characters slurped */ +int +mimeHdrLine::setStr (const char *inCStr) +{ + int retVal = 0; + char *aCStr = (char *) inCStr; +// char *begin = aCStr; + mimeLabel = QCString ((const char *) NULL); + mimeValue = QCString ((const char *) NULL); + + if (aCStr) + { + // can't have spaces on normal lines + if (!skipWS (aCStr)) + { + int label = 0, advance; + while ((advance = parseWord (&aCStr[label]))) + { + label += advance; + } + if (label && aCStr[label - 1] != ':') + retVal = 0; + else + { + mimeLabel = QCString (aCStr, label); //length including zero + retVal += label; + aCStr += label; + } + } + if (retVal) + { + int skip; + skip = skipWS (aCStr); + if (skip < 0) + skip *= -1; + aCStr += skip; + retVal += skip; + skip = parseFullLine (aCStr); + mimeValue = QCString (aCStr, skip + 1); + retVal += skip; + aCStr += skip; + } + else + { + //Skip malformed line + while (*aCStr && *aCStr != '\r' && *aCStr != '\n') + { + retVal--; + aCStr++; + } + if (*aCStr == '\r') + { + retVal--; + aCStr++; + } + if (*aCStr == '\n') + { + retVal--; + aCStr++; + } + } + } + else + { + //debug + } + return retVal; +} + +/** slurp one word*/ +int +mimeHdrLine::parseWord (const char *inCStr) +{ + int retVal = 0; + char *aCStr = (char *) inCStr; + + if (aCStr && *aCStr) + { + if (*aCStr == '"') + return mimeHdrLine::parseQuoted ('"', '"', aCStr); + else + return mimeHdrLine::parseHalfWord (aCStr); + } + else + { + //debug(); + } + return retVal; +} + +/** slurp one word*/ +int +mimeHdrLine::parseQuoted (char startQuote, char endQuote, const char *inCStr) +{ + char *aCStr = (char *) inCStr; + int retVal = 0; + + if (aCStr && *aCStr) + { + if (*aCStr == startQuote) + { + aCStr++; + retVal++; + } + else + return 0; + while (*aCStr && *aCStr != endQuote) + { + //skip over backticks + if (*aCStr == '\\') + { + aCStr++; + retVal++; + } + //eat this + aCStr++; + retVal++; + } + if (*aCStr == endQuote) + { + aCStr++; + retVal++; + } + } + else + { + //debug(); + } + return retVal; +} + +/** slurp one alphanumerical word without continuation*/ +int +mimeHdrLine::parseAlphaNum (const char *inCStr) +{ + int retVal = 0; + char *aCStr = (char *) inCStr; + + if (aCStr) + { + while (*aCStr && isalnum (*aCStr)) + { + //skip over backticks + if (*aCStr == '\\') + { + aCStr++; + retVal++; + } + //eat this + aCStr++; + retVal++; + } + } + else + { + //debug(); + } + return retVal; +} + +int +mimeHdrLine::parseHalfWord (const char *inCStr) +{ + int retVal = 0; + char *aCStr = (char *) inCStr; + + if (aCStr && *aCStr) + { + if (isalnum (*aCStr)) + return mimeHdrLine::parseAlphaNum (aCStr); + //skip over backticks + if (*aCStr == '\\') + { + aCStr++; + retVal++; + } + else if (!isspace (*aCStr)) + { + //eat this + aCStr++; + retVal++; + } + } + else + { + //debug(); + } + return retVal; +} + +/** slurp one line without continuation*/ +int +mimeHdrLine::parseHalfLine (const char *inCStr) +{ + int retVal = 0; + char *aCStr = (char *) inCStr; + + if (aCStr) + { + while (*aCStr && *aCStr != '\n') + { + //skip over backticks + if (*aCStr == '\\') + { + aCStr++; + retVal++; + } + //eat this + aCStr++; + retVal++; + } + if (*aCStr == '\n') + { + aCStr++; + retVal++; + } + } + else + { + //debug(); + } + return retVal; +} + +/** skip all white space characters including continuation*/ +int +mimeHdrLine::skipWS (const char *inCStr) +{ + int retVal = 0; + char *aCStr = (char *) inCStr; + + if (aCStr && *aCStr) + { + while (*aCStr == ' ' || *aCStr == '\t') + { + aCStr++; + retVal++; + } + //check out for continuation lines + if (*aCStr == '\r') + { + aCStr++; + retVal++; + } + if (*aCStr++ == '\n') + if (*aCStr == '\t' || *aCStr == ' ') + { + int skip = mimeHdrLine::skipWS (aCStr); + if (skip < 0) + skip *= -1; + retVal += 1 + skip; + } + else + { + retVal = -retVal - 1; + } + } + else + { + //debug(); + } + return retVal; +} + +/** parses continuated lines */ +int +mimeHdrLine::parseFullLine (const char *inCStr) +{ + int retVal = 0; + char *aCStr = (char *) inCStr; + int skip; + + if (aCStr) + { + //skip leading white space + skip = skipWS (aCStr); + if (skip > 0) + { + aCStr += skip; + retVal += skip; + } + while (*aCStr) + { + int advance; + + if ((advance = parseHalfLine (aCStr))) + { + retVal += advance; + aCStr += advance; + } + else if ((advance = skipWS (aCStr))) + { + if (advance > 0) + { + retVal += advance; + aCStr += advance; + } + else + { + retVal -= advance; + break; + } + } + else + break; + } + } + else + { + //debug(); + } + return retVal; +} + +/** parses continuated lines */ +int +mimeHdrLine::parseSeparator (char separator, const char *inCStr) +{ + char *aCStr = (char *) inCStr; + int retVal = 0; + int skip; + + if (aCStr) + { + //skip leading white space + skip = skipWS (aCStr); + if (skip > 0) + { + aCStr += skip; + retVal += skip; + } + while (*aCStr) + { + int advance; + + if (*aCStr != separator) + { + if ((advance = mimeHdrLine::parseWord (aCStr))) + { + retVal += advance; + aCStr += advance; + } + else if ((advance = mimeHdrLine::skipWS (aCStr))) + { + if (advance > 0) + { + retVal += advance; + aCStr += advance; + } + else + { + retVal -= advance; + break; + } + } + else + break; + } + else + { + //include separator in result + retVal++; + aCStr++; + break; + } + } + } + else + { + //debug(); + } + return retVal; +} + +/** return the label */ + +const QCString& +mimeHdrLine::getLabel () +{ + return mimeLabel; +} + +/** return the value */ +const QCString& +mimeHdrLine::getValue () +{ + return mimeValue; +} + + +// FIXME: very inefficient still +QCString +mimeHdrLine::truncateLine(QCString aLine, unsigned int truncate) +{ + int cutHere; + QCString retVal; + uint len = aLine.length(); + + // see if we have a line of the form "key: value" (like "Subject: bla") + // then we do not want to truncate between key and value + int validStart = aLine.find(": "); + if (validStart > -1) { + validStart += 2; + } + while (len > truncate) { + cutHere = aLine.findRev(' ', truncate); + if (cutHere < 1 || cutHere < validStart) { + cutHere = aLine.findRev('\t', truncate); + if (cutHere < 1) { + cutHere = aLine.find(' ', 1); + if (cutHere < 1) { + cutHere = aLine.find('\t', 1); + if (cutHere < 1) { + // simply truncate + return aLine.left(truncate); + } + } + } + } + + retVal += aLine.left(cutHere) + '\n'; + int chop = len - cutHere; + aLine = aLine.right(chop); + len -= chop; + } + retVal += aLine; + + return retVal; +} + diff --git a/kioslaves/imap4/mimehdrline.h b/kioslaves/imap4/mimehdrline.h new file mode 100644 index 000000000..ae8fdd57c --- /dev/null +++ b/kioslaves/imap4/mimehdrline.h @@ -0,0 +1,67 @@ +/*************************************************************************** + mimehdrline.h - description + ------------------- + begin : Wed Oct 11 2000 + copyright : (C) 2000 by Sven Carstens + email : s.carstens@gmx.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef MIMEHDRLINE_H +#define MIMEHDRLINE_H + + +#include <qcstring.h> +#include <qasciidict.h> + +/** + *@author Sven Carstens + */ + +class mimeHdrLine +{ +public: + mimeHdrLine (); + mimeHdrLine (mimeHdrLine *); + mimeHdrLine (const QCString &, const QCString &); + ~mimeHdrLine (); + /** parse a Line into the class +and report characters slurped */ + int setStr (const char *); + int appendStr (const char *); + /** return the value */ + const QCString& getValue (); + /** return the label */ + const QCString& getLabel (); + static QCString truncateLine (QCString, unsigned int truncate = 80); + static int parseSeparator (char, const char *); + static int parseQuoted (char, char, const char *); + /** skip all white space characters */ + static int skipWS (const char *); + /** slurp one word respecting backticks */ + static int parseHalfWord (const char *); + static int parseWord (const char *); + static int parseAlphaNum (const char *); + +protected: // Protected attributes + /** contains the Value + */ + QCString mimeValue; + /** contains the Label of the line + */ + QCString mimeLabel; +protected: // Protected methods + /** parses a continuated line */ + int parseFullLine (const char *); + int parseHalfLine (const char *); +}; + +#endif diff --git a/kioslaves/imap4/mimeheader.cc b/kioslaves/imap4/mimeheader.cc new file mode 100644 index 000000000..17d3a3fd8 --- /dev/null +++ b/kioslaves/imap4/mimeheader.cc @@ -0,0 +1,745 @@ +/*************************************************************************** + mimeheader.cc - description + ------------------- + begin : Fri Oct 20 2000 + copyright : (C) 2000 by Sven Carstens + email : s.carstens@gmx.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "mimeheader.h" +#include "mimehdrline.h" +#include "mailheader.h" +#include "rfcdecoder.h" + +#include <qregexp.h> + +// #include <iostream.h> +#include <kglobal.h> +#include <kinstance.h> +#include <kiconloader.h> +#include <kmimetype.h> +#include <kmimemagic.h> +#include <kmdcodec.h> +#include <kdebug.h> + +mimeHeader::mimeHeader (): +typeList (17, false), dispositionList (17, false) +{ + // Case insensitive hashes are killing us. Also are they too small? + originalHdrLines.setAutoDelete (true); + additionalHdrLines.setAutoDelete (false); // is also in original lines + nestedParts.setAutoDelete (true); + typeList.setAutoDelete (true); + dispositionList.setAutoDelete (true); + nestedMessage = NULL; + contentLength = 0; + contentType = "application/octet-stream"; +} + +mimeHeader::~mimeHeader () +{ +} + +/* +QPtrList<mimeHeader> mimeHeader::getAllParts() +{ + QPtrList<mimeHeader> retVal; + + // caller is responsible for clearing + retVal.setAutoDelete( false ); + nestedParts.setAutoDelete( false ); + + // shallow copy + retVal = nestedParts; + + // can't have duplicate pointers + nestedParts.clear(); + + // restore initial state + nestedParts.setAutoDelete( true ); + + return retVal; +} */ + +void +mimeHeader::addHdrLine (mimeHdrLine * aHdrLine) +{ + mimeHdrLine *addLine = new mimeHdrLine (aHdrLine); + if (addLine) + { + originalHdrLines.append (addLine); + if (qstrnicmp (addLine->getLabel (), "Content-", 8)) + { + additionalHdrLines.append (addLine); + } + else + { + int skip; + char *aCStr = addLine->getValue ().data (); + QDict < QString > *aList = 0; + + skip = mimeHdrLine::parseSeparator (';', aCStr); + if (skip > 0) + { + int cut = 0; + if (skip >= 2) + { + if (aCStr[skip - 1] == '\r') + cut++; + if (aCStr[skip - 1] == '\n') + cut++; + if (aCStr[skip - 2] == '\r') + cut++; + if (aCStr[skip - 1] == ';') + cut++; + } + QCString mimeValue = QCString (aCStr, skip - cut + 1); // cutting of one because of 0x00 + + + if (!qstricmp (addLine->getLabel (), "Content-Disposition")) + { + aList = &dispositionList; + _contentDisposition = mimeValue; + } + else if (!qstricmp (addLine->getLabel (), "Content-Type")) + { + aList = &typeList; + contentType = mimeValue; + } + else + if (!qstricmp (addLine->getLabel (), "Content-Transfer-Encoding")) + { + contentEncoding = mimeValue; + } + else if (!qstricmp (addLine->getLabel (), "Content-ID")) + { + contentID = mimeValue; + } + else if (!qstricmp (addLine->getLabel (), "Content-Description")) + { + _contentDescription = mimeValue; + } + else if (!qstricmp (addLine->getLabel (), "Content-MD5")) + { + contentMD5 = mimeValue; + } + else if (!qstricmp (addLine->getLabel (), "Content-Length")) + { + contentLength = mimeValue.toULong (); + } + else + { + additionalHdrLines.append (addLine); + } +// cout << addLine->getLabel().data() << ": '" << mimeValue.data() << "'" << endl; + + aCStr += skip; + while ((skip = mimeHdrLine::parseSeparator (';', aCStr))) + { + if (skip > 0) + { + addParameter (QCString (aCStr, skip).simplifyWhiteSpace(), aList); +// cout << "-- '" << aParm.data() << "'" << endl; + mimeValue = QCString (addLine->getValue ().data (), skip); + aCStr += skip; + } + else + break; + } + } + } + } +} + +void +mimeHeader::addParameter (const QCString& aParameter, QDict < QString > *aList) +{ + if ( !aList ) + return; + + QString *aValue; + QCString aLabel; + int pos = aParameter.find ('='); +// cout << aParameter.left(pos).data(); + aValue = new QString (); + aValue->setLatin1 (aParameter.right (aParameter.length () - pos - 1)); + aLabel = aParameter.left (pos); + if ((*aValue)[0] == '"') + *aValue = aValue->mid (1, aValue->length () - 2); + + aList->insert (aLabel, aValue); +// cout << "=" << aValue->data() << endl; +} + +QString +mimeHeader::getDispositionParm (const QCString& aStr) +{ + return getParameter (aStr, &dispositionList); +} + +QString +mimeHeader::getTypeParm (const QCString& aStr) +{ + return getParameter (aStr, &typeList); +} + +void +mimeHeader::setDispositionParm (const QCString& aLabel, const QString& aValue) +{ + setParameter (aLabel, aValue, &dispositionList); + return; +} + +void +mimeHeader::setTypeParm (const QCString& aLabel, const QString& aValue) +{ + setParameter (aLabel, aValue, &typeList); +} + +QDictIterator < QString > mimeHeader::getDispositionIterator () +{ + return QDictIterator < QString > (dispositionList); +} + +QDictIterator < QString > mimeHeader::getTypeIterator () +{ + return QDictIterator < QString > (typeList); +} + +QPtrListIterator < mimeHdrLine > mimeHeader::getOriginalIterator () +{ + return QPtrListIterator < mimeHdrLine > (originalHdrLines); +} + +QPtrListIterator < mimeHdrLine > mimeHeader::getAdditionalIterator () +{ + return QPtrListIterator < mimeHdrLine > (additionalHdrLines); +} + +void +mimeHeader::outputHeader (mimeIO & useIO) +{ + if (!getDisposition ().isEmpty ()) + { + useIO.outputMimeLine (QCString ("Content-Disposition: ") + + getDisposition () + + outputParameter (&dispositionList)); + } + + if (!getType ().isEmpty ()) + { + useIO.outputMimeLine (QCString ("Content-Type: ") + + getType () + outputParameter (&typeList)); + } + if (!getDescription ().isEmpty ()) + useIO.outputMimeLine (QCString ("Content-Description: ") + + getDescription ()); + if (!getID ().isEmpty ()) + useIO.outputMimeLine (QCString ("Content-ID: ") + getID ()); + if (!getMD5 ().isEmpty ()) + useIO.outputMimeLine (QCString ("Content-MD5: ") + getMD5 ()); + if (!getEncoding ().isEmpty ()) + useIO.outputMimeLine (QCString ("Content-Transfer-Encoding: ") + + getEncoding ()); + + QPtrListIterator < mimeHdrLine > ait = getAdditionalIterator (); + while (ait.current ()) + { + useIO.outputMimeLine (ait.current ()->getLabel () + ": " + + ait.current ()->getValue ()); + ++ait; + } + useIO.outputMimeLine (QCString ("")); +} + +QString +mimeHeader::getParameter (const QCString& aStr, QDict < QString > *aDict) +{ + QString retVal, *found; + if (aDict) + { + //see if it is a normal parameter + found = aDict->find (aStr); + if (!found) + { + //might be a continuated or encoded parameter + found = aDict->find (aStr + "*"); + if (!found) + { + //continuated parameter + QString decoded, encoded; + int part = 0; + + do + { + QCString search; + search.setNum (part); + search = aStr + "*" + search; + found = aDict->find (search); + if (!found) + { + found = aDict->find (search + "*"); + if (found) + encoded += rfcDecoder::encodeRFC2231String (*found); + } + else + { + encoded += *found; + } + part++; + } + while (found); + if (encoded.find ('\'') >= 0) + { + retVal = rfcDecoder::decodeRFC2231String (encoded.local8Bit ()); + } + else + { + retVal = + rfcDecoder::decodeRFC2231String (QCString ("''") + + encoded.local8Bit ()); + } + } + else + { + //simple encoded parameter + retVal = rfcDecoder::decodeRFC2231String (found->local8Bit ()); + } + } + else + { + retVal = *found; + } + } + return retVal; +} + +void +mimeHeader::setParameter (const QCString& aLabel, const QString& aValue, + QDict < QString > *aDict) +{ + bool encoded = true; + uint vlen, llen; + QString val = aValue; + + if (aDict) + { + + //see if it needs to get encoded + if (encoded && aLabel.find ('*') == -1) + { + val = rfcDecoder::encodeRFC2231String (aValue); + } + //kdDebug(7116) << "mimeHeader::setParameter() - val = '" << val << "'" << endl; + //see if it needs to be truncated + vlen = val.length(); + llen = aLabel.length(); + if (vlen + llen + 4 > 80 && llen < 80 - 8 - 2 ) + { + const int limit = 80 - 8 - 2 - (int)llen; + // the -2 is there to allow extending the length of a part of val + // by 1 or 2 in order to prevent an encoded character from being + // split in half + int i = 0; + QString shortValue; + QCString shortLabel; + + while (!val.isEmpty ()) + { + int partLen; // the length of the next part of the value + if ( limit >= int(vlen) ) { + // the rest of the value fits completely into one continued header + partLen = vlen; + } + else { + partLen = limit; + // make sure that we don't split an encoded char in half + if ( val[partLen-1] == '%' ) { + partLen += 2; + } + else if ( partLen > 1 && val[partLen-2] == '%' ) { + partLen += 1; + } + // make sure partLen does not exceed vlen (could happen in case of + // an incomplete encoded char) + if ( partLen > int(vlen) ) { + partLen = vlen; + } + } + shortValue = val.left( partLen ); + shortLabel.setNum (i); + shortLabel = aLabel + "*" + shortLabel; + val = val.right( vlen - partLen ); + vlen = vlen - partLen; + if (encoded) + { + if (i == 0) + { + shortValue = "''" + shortValue; + } + shortLabel += "*"; + } + //kdDebug(7116) << "mimeHeader::setParameter() - shortLabel = '" << shortLabel << "'" << endl; + //kdDebug(7116) << "mimeHeader::setParameter() - shortValue = '" << shortValue << "'" << endl; + //kdDebug(7116) << "mimeHeader::setParameter() - val = '" << val << "'" << endl; + aDict->insert (shortLabel, new QString (shortValue)); + i++; + } + } + else + { + aDict->insert (aLabel, new QString (val)); + } + } +} + +QCString +mimeHeader::outputParameter (QDict < QString > *aDict) +{ + QCString retVal; + if (aDict) + { + QDictIterator < QString > it (*aDict); + while (it.current ()) + { + retVal += (";\n\t" + it.currentKey () + "=").latin1 (); + if (it.current ()->find (' ') > 0 || it.current ()->find (';') > 0) + { + retVal += '"' + it.current ()->utf8 () + '"'; + } + else + { + retVal += it.current ()->utf8 (); + } + // << it.current()->utf8() << "'"; + ++it; + } + retVal += "\n"; + } + return retVal; +} + +void +mimeHeader::outputPart (mimeIO & useIO) +{ + QPtrListIterator < mimeHeader > nestedParts = getNestedIterator (); + QCString boundary; + if (!getTypeParm ("boundary").isEmpty ()) + boundary = getTypeParm ("boundary").latin1 (); + + outputHeader (useIO); + if (!getPreBody ().isEmpty ()) + useIO.outputMimeLine (getPreBody ()); + if (getNestedMessage ()) + getNestedMessage ()->outputPart (useIO); + while (nestedParts.current ()) + { + if (!boundary.isEmpty ()) + useIO.outputMimeLine ("--" + boundary); + nestedParts.current ()->outputPart (useIO); + ++nestedParts; + } + if (!boundary.isEmpty ()) + useIO.outputMimeLine ("--" + boundary + "--"); + if (!getPostBody ().isEmpty ()) + useIO.outputMimeLine (getPostBody ()); +} + +int +mimeHeader::parsePart (mimeIO & useIO, const QString& boundary) +{ + int retVal = 0; + bool mbox = false; + QCString preNested, postNested; + mbox = parseHeader (useIO); + + kdDebug(7116) << "mimeHeader::parsePart - parsing part '" << getType () << "'" << endl; + if (!qstrnicmp (getType (), "Multipart", 9)) + { + retVal = parseBody (useIO, preNested, getTypeParm ("boundary")); //this is a message in mime format stuff + setPreBody (preNested); + int localRetVal; + do + { + mimeHeader *aHeader = new mimeHeader; + + // set default type for multipart/digest + if (!qstrnicmp (getType (), "Multipart/Digest", 16)) + aHeader->setType ("Message/RFC822"); + + localRetVal = aHeader->parsePart (useIO, getTypeParm ("boundary")); + addNestedPart (aHeader); + } + while (localRetVal); //get nested stuff + } + if (!qstrnicmp (getType (), "Message/RFC822", 14)) + { + mailHeader *msgHeader = new mailHeader; + retVal = msgHeader->parsePart (useIO, boundary); + setNestedMessage (msgHeader); + } + else + { + retVal = parseBody (useIO, postNested, boundary, mbox); //just a simple part remaining + setPostBody (postNested); + } + return retVal; +} + +int +mimeHeader::parseBody (mimeIO & useIO, QCString & messageBody, + const QString& boundary, bool mbox) +{ + QCString inputStr; + QCString buffer; + QString partBoundary; + QString partEnd; + int retVal = 0; //default is last part + + if (!boundary.isEmpty ()) + { + partBoundary = QString ("--") + boundary; + partEnd = QString ("--") + boundary + "--"; + } + + while (useIO.inputLine (inputStr)) + { + //check for the end of all parts + if (!partEnd.isEmpty () + && !qstrnicmp (inputStr, partEnd.latin1 (), partEnd.length () - 1)) + { + retVal = 0; //end of these parts + break; + } + else if (!partBoundary.isEmpty () + && !qstrnicmp (inputStr, partBoundary.latin1 (), + partBoundary.length () - 1)) + { + retVal = 1; //continue with next part + break; + } + else if (mbox && inputStr.find ("From ") == 0) + { + retVal = 0; // end of mbox + break; + } + buffer += inputStr; + if (buffer.length () > 16384) + { + messageBody += buffer; + buffer = ""; + } + } + + messageBody += buffer; + return retVal; +} + +bool +mimeHeader::parseHeader (mimeIO & useIO) +{ + bool mbox = false; + bool first = true; + mimeHdrLine my_line; + QCString inputStr; + + kdDebug(7116) << "mimeHeader::parseHeader - starting parsing" << endl; + while (useIO.inputLine (inputStr)) + { + int appended; + if (inputStr.find ("From ") != 0 || !first) + { + first = false; + appended = my_line.appendStr (inputStr); + if (!appended) + { + addHdrLine (&my_line); + appended = my_line.setStr (inputStr); + } + if (appended <= 0) + break; + } + else + { + mbox = true; + first = false; + } + inputStr = (const char *) NULL; + } + + kdDebug(7116) << "mimeHeader::parseHeader - finished parsing" << endl; + return mbox; +} + +mimeHeader * +mimeHeader::bodyPart (const QString & _str) +{ + // see if it is nested a little deeper + int pt = _str.find('.'); + if (pt != -1) + { + QString tempStr = _str; + mimeHeader *tempPart; + + tempStr = _str.right (_str.length () - pt - 1); + if (nestedMessage) + { + kdDebug(7116) << "mimeHeader::bodyPart - recursing message" << endl; + tempPart = nestedMessage->nestedParts.at (_str.left(pt).toULong() - 1); + } + else + { + kdDebug(7116) << "mimeHeader::bodyPart - recursing mixed" << endl; + tempPart = nestedParts.at (_str.left(pt).toULong() - 1); + } + if (tempPart) + tempPart = tempPart->bodyPart (tempStr); + return tempPart; + } + + kdDebug(7116) << "mimeHeader::bodyPart - returning part " << _str << endl; + // or pick just the plain part + if (nestedMessage) + { + kdDebug(7116) << "mimeHeader::bodyPart - message" << endl; + return nestedMessage->nestedParts.at (_str.toULong () - 1); + } + kdDebug(7116) << "mimeHeader::bodyPart - mixed" << endl; + return nestedParts.at (_str.toULong () - 1); +} + +void mimeHeader::serialize(QDataStream& stream) +{ + int nestedcount = nestedParts.count(); + if (nestedParts.isEmpty() && nestedMessage) + nestedcount = 1; + stream << nestedcount << contentType << QString (getTypeParm ("name")) << _contentDescription + << _contentDisposition << contentEncoding << contentLength << partSpecifier; + // serialize nested message + if (nestedMessage) + nestedMessage->serialize(stream); + + // serialize nested parts + if (!nestedParts.isEmpty()) + { + QPtrListIterator < mimeHeader > it(nestedParts); + mimeHeader* part; + while ( (part = it.current()) != 0 ) + { + ++it; + part->serialize(stream); + } + } +} + +#ifdef KMAIL_COMPATIBLE +// compatibility subroutines +QString +mimeHeader::bodyDecoded () +{ + kdDebug(7116) << "mimeHeader::bodyDecoded" << endl; + QByteArray temp; + + temp = bodyDecodedBinary (); + return QString::fromLatin1 (temp.data (), temp.count ()); +} + +QByteArray +mimeHeader::bodyDecodedBinary () +{ + QByteArray retVal; + + if (contentEncoding.find ("quoted-printable", 0, false) == 0) + retVal = KCodecs::quotedPrintableDecode(postMultipartBody); + else if (contentEncoding.find ("base64", 0, false) == 0) + KCodecs::base64Decode(postMultipartBody, retVal); + else retVal = postMultipartBody; + + kdDebug(7116) << "mimeHeader::bodyDecodedBinary - size is " << retVal.size () << endl; + return retVal; +} + +void +mimeHeader::setBodyEncodedBinary (const QByteArray & _arr) +{ + setBodyEncoded (_arr); +} + +void +mimeHeader::setBodyEncoded (const QByteArray & _arr) +{ + QByteArray setVal; + + kdDebug(7116) << "mimeHeader::setBodyEncoded - in size " << _arr.size () << endl; + if (contentEncoding.find ("quoted-printable", 0, false) == 0) + setVal = KCodecs::quotedPrintableEncode(_arr); + else if (contentEncoding.find ("base64", 0, false) == 0) + KCodecs::base64Encode(_arr, setVal); + else + setVal.duplicate (_arr); + kdDebug(7116) << "mimeHeader::setBodyEncoded - out size " << setVal.size () << endl; + + postMultipartBody.duplicate (setVal); + kdDebug(7116) << "mimeHeader::setBodyEncoded - out size " << postMultipartBody.size () << endl; +} + +QString +mimeHeader::iconName () +{ + QString fileName; + + // FIXME: bug? Why throw away this data? + fileName = + KMimeType::mimeType (contentType.lower ())->icon (QString::null, false); + fileName = + KGlobal::instance ()->iconLoader ()->iconPath (fileName, KIcon::Desktop); +// if (fileName.isEmpty()) +// fileName = KGlobal::instance()->iconLoader()->iconPath( "unknown", KIcon::Desktop ); + return fileName; +} + +void +mimeHeader::setNestedMessage (mailHeader * inPart, bool destroy) +{ +// if(nestedMessage && destroy) delete nestedMessage; + nestedMessage = inPart; +} + +QString +mimeHeader::headerAsString () +{ + mimeIOQString myIO; + + outputHeader (myIO); + return myIO.getString (); +} + +QString +mimeHeader::magicSetType (bool aAutoDecode) +{ + QString mimetype; + QByteArray body; + KMimeMagicResult *result; + + KMimeMagic::self ()->setFollowLinks (TRUE); // is it necessary ? + + if (aAutoDecode) + body = bodyDecodedBinary (); + else + body = postMultipartBody; + + result = KMimeMagic::self ()->findBufferType (body); + mimetype = result->mimeType (); + contentType = mimetype; + return mimetype; +} +#endif diff --git a/kioslaves/imap4/mimeheader.h b/kioslaves/imap4/mimeheader.h new file mode 100644 index 000000000..a56ec6cb8 --- /dev/null +++ b/kioslaves/imap4/mimeheader.h @@ -0,0 +1,337 @@ +/*************************************************************************** + mimeheader.h - description + ------------------- + begin : Fri Oct 20 2000 + copyright : (C) 2000 by Sven Carstens + email : s.carstens@gmx.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef MIMEHEADER_H +#define MIMEHEADER_H + +#include <qptrlist.h> +#include <qdict.h> + +#include "mimehdrline.h" +#include "mimeio.h" +#include "rfcdecoder.h" + +/** + *@author Sven Carstens + */ + +class mimeHeader +{ +public: + mimeHeader (); + virtual ~ mimeHeader (); + + virtual void addHdrLine (mimeHdrLine *); + virtual void outputHeader (mimeIO &); + virtual void outputPart (mimeIO &); + + + QCString outputParameter (QDict < QString > *); + + int parsePart (mimeIO &, const QString&); + int parseBody (mimeIO &, QCString &, const QString&, bool mbox = false); + + // parse a header. returns true if it had a leading 'From ' line + bool parseHeader (mimeIO &); + + QString getDispositionParm (const QCString&); + void setDispositionParm (const QCString&, const QString&); + QDictIterator < QString > getDispositionIterator (); + + QString getTypeParm (const QCString&); + void setTypeParm (const QCString&, const QString&); + QDictIterator < QString > getTypeIterator (); + + // recursively serialize all important contents to the QDataStream + void serialize(QDataStream& stream); + + const QCString& getType () + { + return contentType; + } + void setType (const QCString & _str) + { + contentType = _str; + } + + const QCString& getDescription () + { + return _contentDescription; + } + void setDescription (const QCString & _str) + { + _contentDescription = _str; + } + + QCString getDisposition () + { + return _contentDisposition; + } + void setDisposition (const QCString & _str) + { + _contentDisposition = _str; + } + + QCString getEncoding () + { + return contentEncoding; + } + void setEncoding (const QCString & _str) + { + contentEncoding = _str; + } + + QCString getMD5 () + { + return contentMD5; + } + void setMD5 (const QCString & _str) + { + contentMD5 = _str; + } + + QCString getID () + { + return contentID; + } + void setID (const QCString & _str) + { + contentID = _str; + } + + unsigned long getLength () + { + return contentLength; + } + void setLength (unsigned long _len) + { + contentLength = _len; + } + + const QString & getPartSpecifier () + { + return partSpecifier; + } + void setPartSpecifier (const QString & _str) + { + partSpecifier = _str; + } + + QPtrListIterator < mimeHdrLine > getOriginalIterator (); + QPtrListIterator < mimeHdrLine > getAdditionalIterator (); + void setContent (const QCString &aContent) + { + mimeContent = aContent; + } + QCString getContent () + { + return mimeContent; + } + + QCString getBody () + { + return preMultipartBody + postMultipartBody; + } + QCString getPreBody () + { + return preMultipartBody; + } + void setPreBody (QCString & inBody) + { + preMultipartBody = inBody; + } + + QCString getPostBody () + { + return postMultipartBody; + } + void setPostBody (QCString & inBody) + { + postMultipartBody = inBody; + contentLength = inBody.length (); + } + + mimeHeader *getNestedMessage () + { + return nestedMessage; + } + void setNestedMessage (mimeHeader * inPart, bool destroy = true) + { + if (nestedMessage && destroy) + delete nestedMessage; + nestedMessage = inPart; + } + +// mimeHeader *getNestedPart() { return nestedPart; }; + void addNestedPart (mimeHeader * inPart) + { + nestedParts.append (inPart); + } + QPtrListIterator < mimeHeader > getNestedIterator () + { + return QPtrListIterator < mimeHeader > (nestedParts); + } + + // clears all parts and deletes them from memory + void clearNestedParts () + { + nestedParts.clear (); + } + + // clear all parameters to content-type + void clearTypeParameters () + { + typeList.clear (); + } + + // clear all parameters to content-disposition + void clearDispositionParameters () + { + dispositionList.clear (); + } + + // return the specified body part or NULL + mimeHeader *bodyPart (const QString &); + +#ifdef KMAIL_COMPATIBLE + ulong msgSize () + { + return contentLength; + } + uint numBodyParts () + { + return nestedParts.count (); + } + mimeHeader *bodyPart (int which, mimeHeader ** ret = NULL) + { + if (ret) + (*ret) = nestedParts.at (which); + return nestedParts.at (which); + } + void write (const QString &) + { + } + QString typeStr () + { + return QString (contentType.left (contentType.find ('/'))); + } + void setTypeStr (const QString & _str) + { + contentType = QCString (_str.latin1 ()) + "/" + subtypeStr ().latin1 (); + } + QString subtypeStr () + { + return QString (contentType. + right (contentType.length () - contentType.find ('/') - + 1)); + } + void setSubtypeStr (const QString & _str) + { + contentType = QCString (typeStr ().latin1 ()) + "/" + _str.latin1 (); + } + QString cteStr () + { + return QString (getEncoding ()); + } + void setCteStr (const QString & _str) + { + setEncoding (_str.latin1 ()); + } + QString contentDisposition () + { + return QString (_contentDisposition); + } + QString body () + { + return QString (postMultipartBody); + } + QString charset () + { + return getTypeParm ("charset"); + } + QString bodyDecoded (); + void setBodyEncoded (const QByteArray &); + void setBodyEncodedBinary (const QByteArray &); + QByteArray bodyDecodedBinary (); + QString name () + { + return QString (getTypeParm ("name")); + } + void setName (const QString & _str) + { + setTypeParm ("name", _str); + } + QString fileName () + { + return QString (getDispositionParm ("filename")); + } + QString contentDescription () + { + return QString (rfcDecoder::decodeRFC2047String (_contentDescription)); + } + void setContentDescription (const QString & _str) + { + _contentDescription = rfcDecoder::encodeRFC2047String (_str).latin1 (); + } + QString msgIdMD5 () + { + return QString (contentMD5); + } + QString iconName (); + QString magicSetType (bool aAutoDecode = true); + QString headerAsString (); + ulong size () + { + return 0; + } + void fromString (const QByteArray &) + {; + } + void setContentDisposition (const QString & _str) + { + setDisposition (_str.latin1 ()); + } +#endif + +protected: + static void addParameter (const QCString&, QDict < QString > *); + static QString getParameter (const QCString&, QDict < QString > *); + static void setParameter (const QCString&, const QString&, QDict < QString > *); + + QPtrList < mimeHdrLine > originalHdrLines; + +private: + QPtrList < mimeHdrLine > additionalHdrLines; + QDict < QString > typeList; + QDict < QString > dispositionList; + QCString contentType; + QCString _contentDisposition; + QCString contentEncoding; + QCString _contentDescription; + QCString contentID; + QCString contentMD5; + unsigned long contentLength; + QCString mimeContent; + QCString preMultipartBody; + QCString postMultipartBody; + mimeHeader *nestedMessage; + QPtrList < mimeHeader > nestedParts; + QString partSpecifier; + +}; + +#endif diff --git a/kioslaves/imap4/mimeio.cc b/kioslaves/imap4/mimeio.cc new file mode 100644 index 000000000..7a41c843a --- /dev/null +++ b/kioslaves/imap4/mimeio.cc @@ -0,0 +1,188 @@ +/*************************************************************************** + mimeio.cc - description + ------------------- + begin : Wed Oct 25 2000 + copyright : (C) 2000 by Sven Carstens + email : s.carstens@gmx.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include <iostream> +using namespace std; + +#include "mimeio.h" + +mimeIO::mimeIO () +{ + theCRLF = "\r\n"; + crlfLen = 2; +} + +mimeIO::~mimeIO () +{ +} + +int +mimeIO::inputLine (QCString & aLine) +{ + char input; + + aLine = (const char *) NULL; + while (inputChar (input)) + { + aLine += input; + if (input == '\n') + break; + } +// cout << aLine.length() << " - " << aLine; + return aLine.length (); +} + +int +mimeIO::outputLine (const QCString & aLine, int len) +{ + int i; + + if (len == -1) { + len = aLine.length(); + } + int start = len; + for (i = 0; i < start; i++) + if (!outputChar (aLine[i])) + break; + return i; +} + +int +mimeIO::outputMimeLine (const QCString & inLine) +{ + int retVal = 0; + QCString aLine = inLine; + int len = inLine.length(); + + int theLF = aLine.findRev ('\n'); + if (theLF == len - 1 && theLF != -1) + { + //we have a trailing LF, now check for CR + if (aLine[theLF - 1] == '\r') + theLF--; + //truncate the line + aLine.truncate(theLF); + len = theLF; + theLF = -1; + } + //now truncate the line + { + int start, end, offset; + start = 0; + end = aLine.find ('\n', start); + while (end >= 0) + { + offset = 1; + if (end && aLine[end - 1] == '\r') + { + offset++; + end--; + } + outputLine (aLine.mid (start, end - start) + theCRLF, end - start + crlfLen); + start = end + offset; + end = aLine.find ('\n', start); + } + outputLine (aLine.mid (start, len - start) + theCRLF, len - start + crlfLen); + } + return retVal; +} + +int +mimeIO::inputChar (char &aChar) +{ + if (cin.eof ()) + { +// cout << "EOF" << endl; + return 0; + } + cin.get (aChar); + return 1; +} + +int +mimeIO::outputChar (char aChar) +{ + cout << aChar; + return 1; +} + +void +mimeIO::setCRLF (const char *aCRLF) +{ + theCRLF = aCRLF; + crlfLen = strlen(aCRLF); +} + +mimeIOQFile::mimeIOQFile (const QString & aName): +mimeIO (), +myFile (aName) +{ + myFile.open (IO_ReadOnly); +} + +mimeIOQFile::~mimeIOQFile () +{ + myFile.close (); +} + +int +mimeIOQFile::outputLine (const QCString &, int) +{ + return 0; +} + +int +mimeIOQFile::inputLine (QCString & data) +{ + data.resize( 1024 ); + myFile.readLine (data.data(), 1024); + + return data.length (); +} + +mimeIOQString::mimeIOQString () +{ +} + +mimeIOQString::~mimeIOQString () +{ +} + +int +mimeIOQString::outputLine (const QCString & _str, int len) +{ + if (len == -1) { + len = _str.length(); + } + theString += _str; + return len; +} + +int +mimeIOQString::inputLine (QCString & _str) +{ + if (theString.isEmpty ()) + return 0; + + int i = theString.find ('\n'); + + if (i == -1) + return 0; + _str = theString.left (i + 1).latin1 (); + theString = theString.right (theString.length () - i - 1); + return _str.length (); +} diff --git a/kioslaves/imap4/mimeio.h b/kioslaves/imap4/mimeio.h new file mode 100644 index 000000000..0de2fbd4d --- /dev/null +++ b/kioslaves/imap4/mimeio.h @@ -0,0 +1,79 @@ +/*************************************************************************** + mimeio.h - description + ------------------- + begin : Wed Oct 25 2000 + copyright : (C) 2000 by Sven Carstens + email : s.carstens@gmx.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef MIMEIO_H +#define MIMEIO_H + +#include <qcstring.h> +#include <qfile.h> + +/** + *@author Sven Carstens + */ + +class mimeIO +{ +public: + mimeIO (); + virtual ~ mimeIO (); + + virtual int outputLine (const QCString &, int len = -1); + virtual int outputMimeLine (const QCString &); + virtual int inputLine (QCString &); + virtual int outputChar (char); + virtual int inputChar (char &); + + void setCRLF (const char *); + +protected: + QCString theCRLF; + int crlfLen; +}; + +class mimeIOQFile:public mimeIO +{ +public: + mimeIOQFile (const QString &); + virtual ~ mimeIOQFile (); + virtual int outputLine (const QCString &, int len = -1); + virtual int inputLine (QCString &); + +protected: + QFile myFile; +}; + +class mimeIOQString:public mimeIO +{ +public: + mimeIOQString (); + virtual ~ mimeIOQString (); + virtual int outputLine (const QCString &, int len = -1); + virtual int inputLine (QCString &); + const QString& getString () const + { + return theString; + } + void setString (const QString & _str) + { + theString = _str; + } + +protected: + QString theString; +}; + +#endif diff --git a/kioslaves/imap4/rfcdecoder.cc b/kioslaves/imap4/rfcdecoder.cc new file mode 100644 index 000000000..0e2bc9f73 --- /dev/null +++ b/kioslaves/imap4/rfcdecoder.cc @@ -0,0 +1,668 @@ +/********************************************************************** + * + * rfcdecoder.cc - handler for various rfc/mime encodings + * Copyright (C) 2000 s.carstens@gmx.de + * + * 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 s.carstens@gmx.de + * + *********************************************************************/ +#include "rfcdecoder.h" + +#include <ctype.h> +#include <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> + +#include <qtextcodec.h> +#include <qbuffer.h> +#include <qregexp.h> +#include <kmdcodec.h> + +// This part taken from rfc 2192 IMAP URL Scheme. C. Newman. September 1997. +// adapted to QT-Toolkit by Sven Carstens <s.carstens@gmx.de> 2000 + +static unsigned char base64chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; +#define UNDEFINED 64 +#define MAXLINE 76 + +/* UTF16 definitions */ +#define UTF16MASK 0x03FFUL +#define UTF16SHIFT 10 +#define UTF16BASE 0x10000UL +#define UTF16HIGHSTART 0xD800UL +#define UTF16HIGHEND 0xDBFFUL +#define UTF16LOSTART 0xDC00UL +#define UTF16LOEND 0xDFFFUL + +/* Convert an IMAP mailbox to a Unicode path + */ +QString rfcDecoder::fromIMAP (const QString & inSrc) +{ + unsigned char c, i, bitcount; + unsigned long ucs4, utf16, bitbuf; + unsigned char base64[256], utf8[6]; + unsigned long srcPtr = 0; + QCString dst; + QCString src = inSrc.ascii (); + uint srcLen = inSrc.length(); + + /* initialize modified base64 decoding table */ + memset (base64, UNDEFINED, sizeof (base64)); + for (i = 0; i < sizeof (base64chars); ++i) + { + base64[(int)base64chars[i]] = i; + } + + /* loop until end of string */ + while (srcPtr < srcLen) + { + c = src[srcPtr++]; + /* deal with literal characters and &- */ + if (c != '&' || src[srcPtr] == '-') + { + /* encode literally */ + dst += c; + /* skip over the '-' if this is an &- sequence */ + if (c == '&') + srcPtr++; + } + else + { + /* convert modified UTF-7 -> UTF-16 -> UCS-4 -> UTF-8 -> HEX */ + bitbuf = 0; + bitcount = 0; + ucs4 = 0; + while ((c = base64[(unsigned char) src[srcPtr]]) != UNDEFINED) + { + ++srcPtr; + bitbuf = (bitbuf << 6) | c; + bitcount += 6; + /* enough bits for a UTF-16 character? */ + if (bitcount >= 16) + { + bitcount -= 16; + utf16 = (bitcount ? bitbuf >> bitcount : bitbuf) & 0xffff; + /* convert UTF16 to UCS4 */ + if (utf16 >= UTF16HIGHSTART && utf16 <= UTF16HIGHEND) + { + ucs4 = (utf16 - UTF16HIGHSTART) << UTF16SHIFT; + continue; + } + else if (utf16 >= UTF16LOSTART && utf16 <= UTF16LOEND) + { + ucs4 += utf16 - UTF16LOSTART + UTF16BASE; + } + else + { + ucs4 = utf16; + } + /* convert UTF-16 range of UCS4 to UTF-8 */ + if (ucs4 <= 0x7fUL) + { + utf8[0] = ucs4; + i = 1; + } + else if (ucs4 <= 0x7ffUL) + { + utf8[0] = 0xc0 | (ucs4 >> 6); + utf8[1] = 0x80 | (ucs4 & 0x3f); + i = 2; + } + else if (ucs4 <= 0xffffUL) + { + utf8[0] = 0xe0 | (ucs4 >> 12); + utf8[1] = 0x80 | ((ucs4 >> 6) & 0x3f); + utf8[2] = 0x80 | (ucs4 & 0x3f); + i = 3; + } + else + { + utf8[0] = 0xf0 | (ucs4 >> 18); + utf8[1] = 0x80 | ((ucs4 >> 12) & 0x3f); + utf8[2] = 0x80 | ((ucs4 >> 6) & 0x3f); + utf8[3] = 0x80 | (ucs4 & 0x3f); + i = 4; + } + /* copy it */ + for (c = 0; c < i; ++c) + { + dst += utf8[c]; + } + } + } + /* skip over trailing '-' in modified UTF-7 encoding */ + if (src[srcPtr] == '-') + ++srcPtr; + } + } + return QString::fromUtf8 (dst.data ()); +} + +/* replace " with \" and \ with \\ " and \ characters */ +QString rfcDecoder::quoteIMAP(const QString &src) +{ + uint len = src.length(); + QString result; + result.reserve(2 * len); + for (unsigned int i = 0; i < len; i++) + { + if (src[i] == '"' || src[i] == '\\') + result += '\\'; + result += src[i]; + } + //result.squeeze(); - unnecessary and slow + return result; +} + +/* Convert Unicode path to modified UTF-7 IMAP mailbox + */ +QString rfcDecoder::toIMAP (const QString & inSrc) +{ + unsigned int utf8pos, utf8total, c, utf7mode, bitstogo, utf16flag; + unsigned long ucs4, bitbuf; + QCString src = inSrc.utf8 (); + QString dst; + + ulong srcPtr = 0; + utf7mode = 0; + utf8total = 0; + bitstogo = 0; + utf8pos = 0; + bitbuf = 0; + ucs4 = 0; + while (srcPtr < src.length ()) + { + c = (unsigned char) src[srcPtr++]; + /* normal character? */ + if (c >= ' ' && c <= '~') + { + /* switch out of UTF-7 mode */ + if (utf7mode) + { + if (bitstogo) + { + dst += base64chars[(bitbuf << (6 - bitstogo)) & 0x3F]; + bitstogo = 0; + } + dst += '-'; + utf7mode = 0; + } + dst += c; + /* encode '&' as '&-' */ + if (c == '&') + { + dst += '-'; + } + continue; + } + /* switch to UTF-7 mode */ + if (!utf7mode) + { + dst += '&'; + utf7mode = 1; + } + /* Encode US-ASCII characters as themselves */ + if (c < 0x80) + { + ucs4 = c; + utf8total = 1; + } + else if (utf8total) + { + /* save UTF8 bits into UCS4 */ + ucs4 = (ucs4 << 6) | (c & 0x3FUL); + if (++utf8pos < utf8total) + { + continue; + } + } + else + { + utf8pos = 1; + if (c < 0xE0) + { + utf8total = 2; + ucs4 = c & 0x1F; + } + else if (c < 0xF0) + { + utf8total = 3; + ucs4 = c & 0x0F; + } + else + { + /* NOTE: can't convert UTF8 sequences longer than 4 */ + utf8total = 4; + ucs4 = c & 0x03; + } + continue; + } + /* loop to split ucs4 into two utf16 chars if necessary */ + utf8total = 0; + do + { + if (ucs4 >= UTF16BASE) + { + ucs4 -= UTF16BASE; + bitbuf = (bitbuf << 16) | ((ucs4 >> UTF16SHIFT) + UTF16HIGHSTART); + ucs4 = (ucs4 & UTF16MASK) + UTF16LOSTART; + utf16flag = 1; + } + else + { + bitbuf = (bitbuf << 16) | ucs4; + utf16flag = 0; + } + bitstogo += 16; + /* spew out base64 */ + while (bitstogo >= 6) + { + bitstogo -= 6; + dst += base64chars[(bitstogo ? (bitbuf >> bitstogo) : bitbuf) & 0x3F]; + } + } + while (utf16flag); + } + /* if in UTF-7 mode, finish in ASCII */ + if (utf7mode) + { + if (bitstogo) + { + dst += base64chars[(bitbuf << (6 - bitstogo)) & 0x3F]; + } + dst += '-'; + } + return quoteIMAP(dst); +} + +//----------------------------------------------------------------------------- +QString rfcDecoder::decodeQuoting(const QString &aStr) +{ + QString result; + unsigned int strLength(aStr.length()); + for (unsigned int i = 0; i < strLength ; i++) + { + if (aStr[i] == "\\") i++; + result += aStr[i]; + } + return result; +} + +//----------------------------------------------------------------------------- +QTextCodec * +rfcDecoder::codecForName (const QString & _str) +{ + if (_str.isEmpty ()) + return NULL; + return QTextCodec::codecForName (_str.lower (). + replace ("windows", "cp").latin1 ()); +} + +//----------------------------------------------------------------------------- +const QString +rfcDecoder::decodeRFC2047String (const QString & _str) +{ + QString throw_away; + + return decodeRFC2047String (_str, throw_away); +} + +//----------------------------------------------------------------------------- +const QString +rfcDecoder::decodeRFC2047String (const QString & _str, QString & charset) +{ + QString throw_away; + + return decodeRFC2047String (_str, charset, throw_away); +} + +//----------------------------------------------------------------------------- +const QString +rfcDecoder::decodeRFC2047String (const QString & _str, QString & charset, + QString & language) +{ + //do we have a rfc string + if (_str.find("=?") < 0) + return _str; + + QCString aStr = _str.ascii (); // QString.length() means Unicode chars + QCString result; + char *pos, *beg, *end, *mid = NULL; + QCString str; + char encoding = 0, ch; + bool valid; + const int maxLen = 200; + int i; + +// result.truncate(aStr.length()); + for (pos = aStr.data (); *pos; pos++) + { + if (pos[0] != '=' || pos[1] != '?') + { + result += *pos; + continue; + } + beg = pos + 2; + end = beg; + valid = TRUE; + // parse charset name + for (i = 2, pos += 2; + i < maxLen && (*pos != '?' && (ispunct (*pos) || isalnum (*pos))); + i++) + pos++; + if (*pos != '?' || i < 4 || i >= maxLen) + valid = FALSE; + else + { + charset = QCString (beg, i - 1); // -2 + 1 for the zero + int pt = charset.findRev('*'); + if (pt != -1) + { + // save language for later usage + language = charset.right (charset.length () - pt - 1); + + // tie off language as defined in rfc2047 + charset.truncate(pt); + } + // get encoding and check delimiting question marks + encoding = toupper (pos[1]); + if (pos[2] != '?' + || (encoding != 'Q' && encoding != 'B' && encoding != 'q' + && encoding != 'b')) + valid = FALSE; + pos += 3; + i += 3; +// kdDebug(7116) << "rfcDecoder::decodeRFC2047String - charset " << charset << " - language " << language << " - '" << pos << "'" << endl; + } + if (valid) + { + mid = pos; + // search for end of encoded part + while (i < maxLen && *pos && !(*pos == '?' && *(pos + 1) == '=')) + { + i++; + pos++; + } + end = pos + 2; //end now points to the first char after the encoded string + if (i >= maxLen || !*pos) + valid = FALSE; + } + if (valid) + { + ch = *pos; + *pos = '\0'; + str = QCString (mid).left ((int) (mid - pos - 1)); + if (encoding == 'Q') + { + // decode quoted printable text + for (i = str.length () - 1; i >= 0; i--) + if (str[i] == '_') + str[i] = ' '; +// kdDebug(7116) << "rfcDecoder::decodeRFC2047String - before QP '" << str << "'" << endl; + + str = KCodecs::quotedPrintableDecode(str); +// kdDebug(7116) << "rfcDecoder::decodeRFC2047String - after QP '" << str << "'" << endl; + } + else + { + // decode base64 text + str = KCodecs::base64Decode(str); + } + *pos = ch; + int len = str.length(); + for (i = 0; i < len; i++) + result += (char) (QChar) str[i]; + + pos = end - 1; + } + else + { +// kdDebug(7116) << "rfcDecoder::decodeRFC2047String - invalid" << endl; + //result += "=?"; + //pos = beg -1; // because pos gets increased shortly afterwards + pos = beg - 2; + result += *pos++; + result += *pos; + } + } + if (!charset.isEmpty ()) + { + QTextCodec *aCodec = codecForName (charset.ascii ()); + if (aCodec) + { +// kdDebug(7116) << "Codec is " << aCodec->name() << endl; + return aCodec->toUnicode (result); + } + } + return result; +} + + +//----------------------------------------------------------------------------- +const char especials[17] = "()<>@,;:\"/[]?.= "; + +const QString +rfcDecoder::encodeRFC2047String (const QString & _str) +{ + if (_str.isEmpty ()) + return _str; + const signed char *latin = reinterpret_cast<const signed char *>(_str.latin1()), *l, *start, *stop; + char hexcode; + int numQuotes, i; + int rptr = 0; + // My stats show this number results in 12 resize() out of 73,000 + int resultLen = 3 * _str.length() / 2; + QCString result(resultLen); + + while (*latin) + { + l = latin; + start = latin; + while (*l) + { + if (*l == 32) + start = l + 1; + if (*l < 0) + break; + l++; + } + if (*l) + { + numQuotes = 1; + while (*l) + { + /* The encoded word must be limited to 75 character */ + for (i = 0; i < 16; i++) + if (*l == especials[i]) + numQuotes++; + if (*l < 0) + numQuotes++; + /* Stop after 58 = 75 - 17 characters or at "<user@host..." */ + if (l - start + 2 * numQuotes >= 58 || *l == 60) + break; + l++; + } + if (*l) + { + stop = l - 1; + while (stop >= start && *stop != 32) + stop--; + if (stop <= start) + stop = l; + } + else + stop = l; + if (resultLen - rptr - 1 <= start - latin + 1 + 16 /* =?iso-88... */) { + resultLen += (start - latin + 1) * 2 + 20; // more space + result.resize(resultLen); + } + while (latin < start) + { + result[rptr++] = *latin; + latin++; + } + strcpy(&result[rptr], "=?iso-8859-1?q?"); rptr += 15; + if (resultLen - rptr - 1 <= 3*(stop - latin + 1)) { + resultLen += (stop - latin + 1) * 4 + 20; // more space + result.resize(resultLen); + } + while (latin < stop) // can add up to 3 chars/iteration + { + numQuotes = 0; + for (i = 0; i < 16; i++) + if (*latin == especials[i]) + numQuotes = 1; + if (*latin < 0) + numQuotes = 1; + if (numQuotes) + { + result[rptr++] = '='; + hexcode = ((*latin & 0xF0) >> 4) + 48; + if (hexcode >= 58) + hexcode += 7; + result[rptr++] = hexcode; + hexcode = (*latin & 0x0F) + 48; + if (hexcode >= 58) + hexcode += 7; + result[rptr++] = hexcode; + } + else + { + result[rptr++] = *latin; + } + latin++; + } + result[rptr++] = '?'; + result[rptr++] = '='; + } + else + { + while (*latin) + { + if (rptr == resultLen - 1) { + resultLen += 30; + result.resize(resultLen); + } + result[rptr++] = *latin; + latin++; + } + } + } + result[rptr] = 0; + //free (latinStart); + return result; +} + + +//----------------------------------------------------------------------------- +const QString +rfcDecoder::encodeRFC2231String (const QString & _str) +{ + if (_str.isEmpty ()) + return _str; + signed char *latin = (signed char *) calloc (1, _str.length () + 1); + char *latin_us = (char *) latin; + strcpy (latin_us, _str.latin1 ()); + signed char *l = latin; + char hexcode; + int i; + bool quote; + while (*l) + { + if (*l < 0) + break; + l++; + } + if (!*l) { + free(latin); + return _str.ascii (); + } + QCString result; + l = latin; + while (*l) + { + quote = *l < 0; + for (i = 0; i < 16; i++) + if (*l == especials[i]) + quote = true; + if (quote) + { + result += "%"; + hexcode = ((*l & 0xF0) >> 4) + 48; + if (hexcode >= 58) + hexcode += 7; + result += hexcode; + hexcode = (*l & 0x0F) + 48; + if (hexcode >= 58) + hexcode += 7; + result += hexcode; + } + else + { + result += *l; + } + l++; + } + free (latin); + return result; +} + + +//----------------------------------------------------------------------------- +const QString +rfcDecoder::decodeRFC2231String (const QString & _str) +{ + int p = _str.find ('\''); + + //see if it is an rfc string + if (p < 0) + return _str; + + int l = _str.findRev ('\''); + + //second is language + if (p >= l) + return _str; + + //first is charset or empty + QString charset = _str.left (p); + QString st = _str.mid (l + 1); + QString language = _str.mid (p + 1, l - p - 1); + + //kdDebug(7116) << "Charset: " << charset << " Language: " << language << endl; + + char ch, ch2; + p = 0; + while (p < (int) st.length ()) + { + if (st.at (p) == 37) + { + ch = st.at (p + 1).latin1 () - 48; + if (ch > 16) + ch -= 7; + ch2 = st.at (p + 2).latin1 () - 48; + if (ch2 > 16) + ch2 -= 7; + st.at (p) = ch * 16 + ch2; + st.remove (p + 1, 2); + } + p++; + } + return st; +} diff --git a/kioslaves/imap4/rfcdecoder.h b/kioslaves/imap4/rfcdecoder.h new file mode 100644 index 000000000..1df5c0dae --- /dev/null +++ b/kioslaves/imap4/rfcdecoder.h @@ -0,0 +1,89 @@ +#ifndef RFCDECODER_H +#define RFCDECODER_H +/********************************************************************** + * + * rfcdecoder.h - handler for various rfc/mime encodings + * Copyright (C) 2000 s.carstens@gmx.de + * + * 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 s.carstens@gmx.de + * + *********************************************************************/ + +#include <qstring.h> + +class QTextCodec; + +/** + * handler for various rfc/mime encodings + * @author Sven Carstens <s.carstens@gmx.de> + * @date 2000 + * @todo rename to rfcCodecs as it encodes too. + */ +class rfcDecoder +{ + +public: + +/** Convert an IMAP mailbox to a Unicode path + */ + static QString fromIMAP (const QString & src); +/** Convert Unicode path to modified UTF-7 IMAP mailbox + */ + static QString toIMAP (const QString & inSrc); +/** replace " with \" and \ with \\ " and \ characters */ + static QString quoteIMAP (const QString & src); + + /** remove \ from a string + * @bug I'm pretty sure this doesn't do what the author meant it to do + */ + static QString decodeQuoting(const QString &aStr); + + /** + * fetch a codec by name + * @return Text Codec object + */ + static QTextCodec *codecForName (const QString &); + + // decoder for RFC2047 and RFC1522 + /** decode a RFC2047 String */ + static const QString decodeRFC2047String (const QString & _str, + QString & charset, + QString & language); + /** decode a RFC2047 String */ + static const QString decodeRFC2047String (const QString & _str, + QString & charset); + /** decode a RFC2047 String */ + static const QString decodeRFC2047String (const QString & _str); + + // encoder for RFC2047 and RFC1522 + /** encode a RFC2047 String */ + static const QString encodeRFC2047String (const QString & _str, + QString & charset, + QString & language); + /** encode a RFC2047 String */ + static const QString encodeRFC2047String (const QString & _str, + QString & charset); + /** encode a RFC2047 String */ + static const QString encodeRFC2047String (const QString & _str); + + /** encode a RFC2231 String */ + static const QString encodeRFC2231String (const QString & _str); + /** decode a RFC2231 String */ + static const QString decodeRFC2231String (const QString & _str); +}; + +#endif diff --git a/kioslaves/imap4/selectinfo.h b/kioslaves/imap4/selectinfo.h new file mode 100644 index 000000000..c059a952f --- /dev/null +++ b/kioslaves/imap4/selectinfo.h @@ -0,0 +1,202 @@ +#ifndef _IMAPINFO_H +#define _IMAPINFO_H +/********************************************************************** + * + * imapinfo.h - IMAP4rev1 SELECT / EXAMINE handler + * Copyright (C) 2000 + * + * 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 + * + *********************************************************************/ + +#include <qstringlist.h> +#include <qstring.h> + +//class handling the info we get on EXAMINE and SELECT +class imapInfo +{ +public: + + imapInfo (); + imapInfo (const QStringList &); + imapInfo (const imapInfo &); + imapInfo & operator = (const imapInfo &); + + ulong _flags (const QString &) const; + + void setCount (ulong l) + { + countAvailable_ = true; + count_ = l; + } + + void setRecent (ulong l) + { + recentAvailable_ = true; + recent_ = l; + } + + void setUnseen (ulong l) + { + unseenAvailable_ = true; + unseen_ = l; + } + + void setUidValidity (ulong l) + { + uidValidityAvailable_ = true; + uidValidity_ = l; + } + + void setUidNext (ulong l) + { + uidNextAvailable_ = true; + uidNext_ = l; + } + + void setFlags (ulong l) + { + flagsAvailable_ = true; + flags_ = l; + } + + void setFlags (const QString & inFlag) + { + flagsAvailable_ = true; + flags_ = _flags (inFlag); + } + + void setPermanentFlags (ulong l) + { + permanentFlagsAvailable_ = true; + permanentFlags_ = l; + } + + void setPermanentFlags (const QString & inFlag) + { + permanentFlagsAvailable_ = true; + permanentFlags_ = _flags (inFlag); + } + + void setReadWrite (bool b) + { + readWriteAvailable_ = true; + readWrite_ = b; + } + + ulong count () const + { + return count_; + } + + ulong recent () const + { + return recent_; + } + + ulong unseen () const + { + return unseen_; + } + + ulong uidValidity () const + { + return uidValidity_; + } + + ulong uidNext () const + { + return uidNext_; + } + + ulong flags () const + { + return flags_; + } + + ulong permanentFlags () const + { + return permanentFlags_; + } + + bool readWrite () const + { + return readWrite_; + } + + ulong countAvailable () const + { + return countAvailable_; + } + + ulong recentAvailable () const + { + return recentAvailable_; + } + + ulong unseenAvailable () const + { + return unseenAvailable_; + } + + ulong uidValidityAvailable () const + { + return uidValidityAvailable_; + } + + ulong uidNextAvailable () const + { + return uidNextAvailable_; + } + + ulong flagsAvailable () const + { + return flagsAvailable_; + } + + ulong permanentFlagsAvailable () const + { + return permanentFlagsAvailable_; + } + + bool readWriteAvailable () const + { + return readWriteAvailable_; + } + +private: + + ulong count_; + ulong recent_; + ulong unseen_; + ulong uidValidity_; + ulong uidNext_; + ulong flags_; + ulong permanentFlags_; + bool readWrite_; + + bool countAvailable_; + bool recentAvailable_; + bool unseenAvailable_; + bool uidValidityAvailable_; + bool uidNextAvailable_; + bool flagsAvailable_; + bool permanentFlagsAvailable_; + bool readWriteAvailable_; +}; + +#endif diff --git a/kioslaves/mbox/AUTHORS b/kioslaves/mbox/AUTHORS new file mode 100644 index 000000000..333010f6c --- /dev/null +++ b/kioslaves/mbox/AUTHORS @@ -0,0 +1 @@ +Mart Kelder <mart.kde@hccnet.nl> diff --git a/kioslaves/mbox/Makefile.am b/kioslaves/mbox/Makefile.am new file mode 100644 index 000000000..b4840ee72 --- /dev/null +++ b/kioslaves/mbox/Makefile.am @@ -0,0 +1,30 @@ +INCLUDES= $(all_includes) + +####### Files + +METASOURCES = AUTO + +kde_module_LTLIBRARIES = kio_mbox.la + +kio_mbox_la_SOURCES = \ + mbox.cc \ + mboxfile.cc \ + readmbox.cc \ + stat.cc \ + urlinfo.cc +kio_mbox_la_LIBADD = $(LIB_KIO) +kio_mbox_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) -module $(KDE_PLUGIN) + +noinst_HEADERS = \ + mbox.h \ + mboxfile.h \ + readmbox.h \ + stat.h \ + urlinfo.h + +kdelnk_DATA = mbox.protocol +kdelnkdir = $(kde_servicesdir) + + +include $(top_srcdir)/admin/Doxyfile.am + diff --git a/kioslaves/mbox/README b/kioslaves/mbox/README new file mode 100644 index 000000000..f0441661c --- /dev/null +++ b/kioslaves/mbox/README @@ -0,0 +1,7 @@ +This is a simple kioslave for accessing mbox-files with kio. + +At the moment, it doesn't support locking or writing, it is just reading and listing messages. + +Usage: + mbox:/path/to/mbox/file For listing the files in a mbox-file + mbox:/path/to/mbox/file/id For getting an id out of a mbox-file. diff --git a/kioslaves/mbox/mbox.cc b/kioslaves/mbox/mbox.cc new file mode 100644 index 000000000..8ad8d09ab --- /dev/null +++ b/kioslaves/mbox/mbox.cc @@ -0,0 +1,168 @@ +/* + * This is a simple kioslave to handle mbox-files. + * Copyright (C) 2004 Mart Kelder (mart.kde@hccnet.nl) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include "mbox.h" + +#include "readmbox.h" +#include "stat.h" +#include "urlinfo.h" + +#include <qstring.h> +#include <qcstring.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kinstance.h> +#include <kglobal.h> +#include <kurl.h> +#include <kio/global.h> + +#include <stdlib.h> + +#include "kdepimmacros.h" + +#include "mbox.h" + +extern "C" { KDE_EXPORT int kdemain(int argc, char* argv[]); } + +int kdemain( int argc, char * argv[] ) +{ + KLocale::setMainCatalogue("kdelibs"); + KInstance instance("kio_mbox"); + (void) KGlobal::locale(); + + if (argc != 4) { + fprintf(stderr, "Usage: kio_mbox protocol " + "domain-socket1 domain-socket2\n"); + exit(-1); + } + + MBoxProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + return 0; +} + +MBoxProtocol::MBoxProtocol( const QCString& arg1, const QCString& arg2 ) + : KIO::SlaveBase( "mbox2", arg1, arg2 ), + m_errorState( true ) +{ + +} + +MBoxProtocol::~MBoxProtocol() +{ +} + +void MBoxProtocol::get( const KURL& url ) +{ + m_errorState = false; + + UrlInfo info( url, UrlInfo::message ); + QString line; + QByteArray ba_line; + + if( info.type() == UrlInfo::invalid && !m_errorState ) + { + error( KIO::ERR_DOES_NOT_EXIST, info.url() ); + return; + } + + ReadMBox mbox( &info, this ); + + while( !mbox.atEnd() && !m_errorState) + { + line = mbox.currentLine(); + line += '\n'; + ba_line = QCString( line.utf8() ); + ba_line.truncate( ba_line.size() - 1 ); //Removing training '\0' + data( ba_line ); + mbox.nextLine(); + }; + + if( !m_errorState ) + { + data( QByteArray() ); + finished(); + } +} + +void MBoxProtocol::listDir( const KURL& url ) +{ + m_errorState = false; + + KIO::UDSEntry entry; + UrlInfo info( url, UrlInfo::directory ); + ReadMBox mbox( &info, this, hasMetaData( "onlynew" ), hasMetaData( "savetime" ) ); + + if( m_errorState ) + return; + + if( info.type() != UrlInfo::directory ) + { + error( KIO::ERR_DOES_NOT_EXIST, info.url() ); + return; + } + + while( !mbox.atEnd() && !m_errorState ) + { + entry = Stat::stat( mbox, info ); + if( mbox.inListing() ) + listEntry( entry, false ); + } + + listEntry( KIO::UDSEntry(), true ); + finished(); +} + +void MBoxProtocol::stat( const KURL& url ) +{ + UrlInfo info( url ); + if( info.type() == UrlInfo::invalid ) + { + error( KIO::ERR_DOES_NOT_EXIST, url.path() ); + return; + } else + { + statEntry( Stat::stat( info ) ); + } + finished(); +} + +void MBoxProtocol::mimetype( const KURL& url ) +{ + m_errorState = false; + + UrlInfo info( url ); + + if( m_errorState ) + return; + + if( info.type() == UrlInfo::invalid ) + error( KIO::ERR_DOES_NOT_EXIST, i18n( "Invalid URL" ) ); + else + mimeType( info.mimetype() ); + finished(); +} + +void MBoxProtocol::emitError( int errno, const QString& arg ) +{ + m_errorState = true; + error( errno, arg ); +} + diff --git a/kioslaves/mbox/mbox.h b/kioslaves/mbox/mbox.h new file mode 100644 index 000000000..4d3768bd4 --- /dev/null +++ b/kioslaves/mbox/mbox.h @@ -0,0 +1,81 @@ +/* + * This is a simple kioslave to handle mbox-files. + * Copyright (C) 2004 Mart Kelder (mart.kde@hccnet.nl) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef MBOX_H +#define MBOX_H + +#include <kio/slavebase.h> + +class QCString; +class KURL; + +/** + * This class is the main class and implements all function + * which can be called through the user. + */ +class MBoxProtocol : public KIO::SlaveBase +{ +public: + /** + * Constructor, for the parameters, See KIO::SlaveBase + */ + MBoxProtocol( const QCString&, const QCString& ); + /** + * Empty destructor + */ + virtual ~MBoxProtocol(); + + /** + * This functions is used when an user or a program wants to + * get a file from a mbox-file + * @param url The url which points to the virtual file to get + */ + virtual void get( const KURL& url ); + + /** + * This functions gives a listing back. + * @param url The url to the mbox-file. + */ + virtual void listDir( const KURL& url ); + + /** + * This functions gives general properties about a mbox-file, + * or mbox-email back. + */ + virtual void stat( const KURL& url ); + + /** + * This functions determinate the mimetype of a given mbox-file or mbox-email. + * @param url The url to get the mimetype from + */ + virtual void mimetype( const KURL& url ); + + /** + * Through this functions, other class which have an instance to this + * class (classes which are part of kio_mbox) can emit an error with + * this function + * @param errno The error number to be thrown + * @param arg The argument of the error message of the error number. + */ + void emitError( int errno, const QString& arg ); +private: + bool m_errorState; +}; + +#endif + diff --git a/kioslaves/mbox/mbox.protocol b/kioslaves/mbox/mbox.protocol new file mode 100644 index 000000000..8b6b412a8 --- /dev/null +++ b/kioslaves/mbox/mbox.protocol @@ -0,0 +1,14 @@ +[Protocol] +exec=kio_mbox +protocol=mbox +input=none +output=filesystem +listing=Name,Type,Size +reading=true +writing=false +makedir=false +deleting=false +Icon=folder_inbox +maxInstances=2 +DocPath= +Class=:local diff --git a/kioslaves/mbox/mboxfile.cc b/kioslaves/mbox/mboxfile.cc new file mode 100644 index 000000000..271b71019 --- /dev/null +++ b/kioslaves/mbox/mboxfile.cc @@ -0,0 +1,45 @@ +/* + * This is a simple kioslave to handle mbox-files. + * Copyright (C) 2004 Mart Kelder (mart.kde@hccnet.nl) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include "mboxfile.h" + +#include <assert.h> + +MBoxFile::MBoxFile( const UrlInfo* info, MBoxProtocol* parent ) + : m_info( info ), + m_mbox( parent ) +{ + assert( m_info ); +} + +MBoxFile::~MBoxFile() +{ +} + +bool MBoxFile::lock() +{ + //Not implemented + return true; +} + +void MBoxFile::unlock() +{ + //Not implemented +} + + diff --git a/kioslaves/mbox/mboxfile.h b/kioslaves/mbox/mboxfile.h new file mode 100644 index 000000000..b8a98973e --- /dev/null +++ b/kioslaves/mbox/mboxfile.h @@ -0,0 +1,68 @@ +/* + * This is a simple kioslave to handle mbox-files. + * Copyright (C) 2004 Mart Kelder (mart.kde@hccnet.nl) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef MBOXFILE_H +#define MBOXFILE_H +class MBoxProtocol; +class UrlInfo; + +/** + * This class can be used to lock files when implemented. + * It is a base class for all classes that needs locking and/ir + * an UrlInfo*. + */ +class MBoxFile +{ +public: + /** + * Constructor + * @param info The urlinfo which must be used + * @param parent The MBoxProtocol parent instance, used to throw errors. + */ + MBoxFile( const UrlInfo* info, MBoxProtocol* parent ); + + /** + * Empty destructor + */ + ~MBoxFile(); + +protected: + /** + * When implemented, this function handles the locking of the file. + * @return true if the locking was done succesfully. + */ + bool lock(); + + /** + * When implemented, this function unlocks the file. + */ + void unlock(); + +protected: + /** + * This can be used to get information about the file. + * The file specified here is the file that must be used. + */ + const UrlInfo* const m_info; + + /** + * A instance of the parent protocol, meant to throw errors if neccesairy. + */ + MBoxProtocol* const m_mbox; +}; +#endif diff --git a/kioslaves/mbox/readmbox.cc b/kioslaves/mbox/readmbox.cc new file mode 100644 index 000000000..c7513c6eb --- /dev/null +++ b/kioslaves/mbox/readmbox.cc @@ -0,0 +1,204 @@ +/* + * This is a simple kioslave to handle mbox-files. + * Copyright (C) 2004 Mart Kelder (mart.kde@hccnet.nl) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <config.h> + +#include "readmbox.h" + +#include "mbox.h" +#include "urlinfo.h" + +#include <kdebug.h> +#include <kio/global.h> + +#include <qfile.h> +#include <qfileinfo.h> +#include <qstring.h> +#include <qtextstream.h> + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include <utime.h> + +ReadMBox::ReadMBox( const UrlInfo* info, MBoxProtocol* parent, bool onlynew, bool savetime ) + : MBoxFile( info, parent ), + m_file( 0 ), + m_stream( 0 ), + m_current_line( new QString( QString::null ) ), + m_current_id( new QString( QString::null ) ), + m_atend( true ), + m_prev_time( 0 ), + m_only_new( onlynew ), + m_savetime( savetime ), + m_status( false ), + m_prev_status( false ), + m_header( true ) + +{ + if( m_info->type() == UrlInfo::invalid ) + m_mbox->emitError( KIO::ERR_DOES_NOT_EXIST, info->url() ); + + if( !open( savetime ) ) + m_mbox->emitError( KIO::ERR_CANNOT_OPEN_FOR_READING, info->url() ); + + if( m_info->type() == UrlInfo::message ) + if( !searchMessage( m_info->id() ) ) + m_mbox->emitError( KIO::ERR_DOES_NOT_EXIST, info->url() ); +} + +ReadMBox::~ReadMBox() +{ + delete m_current_line; + close(); +} + +QString ReadMBox::currentLine() const +{ + return *m_current_line; +} + +QString ReadMBox::currentID() const +{ + return *m_current_id; +} + +bool ReadMBox::nextLine() +{ + if( !m_stream ) + return true; + + *m_current_line = m_stream->readLine(); + m_atend = m_current_line->isNull(); + if( m_atend ) // Cursor was at EOF + { + *m_current_id = QString::null; + m_prev_status = m_status; + return true; + } + + //New message + if( m_current_line->left( 5 ) == "From " ) + { + *m_current_id = *m_current_line; + m_prev_status = m_status; + m_status = true; + m_header = true; + return true; + } else if( m_only_new ) + { + if( m_header && m_current_line->left( 7 ) == "Status:" && + ! m_current_line->contains( "U" ) && ! m_current_line->contains( "N" ) ) + { + m_status = false; + } + } + + if( m_current_line->stripWhiteSpace().isEmpty() ) + m_header = false; + + return false; +} + +bool ReadMBox::searchMessage( const QString& id ) +{ + if( !m_stream ) + return false; + + while( !m_atend && *m_current_id != id ) + nextLine(); + + return *m_current_id == id; +} + +unsigned int ReadMBox::skipMessage() +{ + unsigned int result = m_current_line->length(); + + if( !m_stream ) + return 0; + + while( !nextLine() ) + result += m_current_line->length(); + + return result; +} + +void ReadMBox::rewind() +{ + if( !m_stream ) + return; //Rewinding not possible + + m_stream->device()->reset(); + m_atend = m_stream->atEnd(); +} + +bool ReadMBox::atEnd() const +{ + if( !m_stream ) + return true; + + return m_atend || ( m_info->type() == UrlInfo::message && *m_current_id != m_info->id() ); +} + +bool ReadMBox::inListing() const +{ + return !m_only_new || m_prev_status; +} + +bool ReadMBox::open( bool savetime ) +{ + if( savetime ) + { + QFileInfo info( m_info->filename() ); + + m_prev_time = new utimbuf; + m_prev_time->actime = info.lastRead().toTime_t(); + m_prev_time->modtime = info.lastModified().toTime_t(); + } + + if( m_file ) + return false; //File already open + + m_file = new QFile( m_info->filename() ); + if( !m_file->open( IO_ReadOnly ) ) + { + delete m_file; + m_file = 0; + return false; + } + m_stream = new QTextStream( m_file ); + skipMessage(); + + return true; +} + +void ReadMBox::close() +{ + if( !m_stream ) + return; + + delete m_stream; m_stream = 0; + m_file->close(); + delete m_file; m_file = 0; + + if( m_prev_time ) + utime( QFile::encodeName( m_info->filename() ), m_prev_time ); +} + diff --git a/kioslaves/mbox/readmbox.h b/kioslaves/mbox/readmbox.h new file mode 100644 index 000000000..01b813df7 --- /dev/null +++ b/kioslaves/mbox/readmbox.h @@ -0,0 +1,132 @@ +/* + * This is a simple kioslave to handle mbox-files. + * Copyright (C) 2004 Mart Kelder (mart.kde@hccnet.nl) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef READMBOX_H +#define READMBOX_H + +#include "mboxfile.h" + +class UrlInfo; +class MBox; + +class QFile; +class QString; +class QTextStream; + +struct utimbuf; + +/** + * This class handels reading from a mbox-file. + */ +class ReadMBox : public MBoxFile +{ +public: + /** + * Constructor + * + * @param info The information of the file to read + * @param parent The instance of the parent MBoxProtocol. + * @param onlynew Only read new messages from the MBox file. + * @param savetime If true, the atime of the mbox-file is preserved (note that this touch the ctime). + */ + ReadMBox( const UrlInfo* info, MBoxProtocol* parent, bool onlynew = false, bool savetime = false ); + + /** + * Destructor + */ + ~ReadMBox(); + + /** + * This functions return the current line + * @return The line last read, or QString::null if there wasn't such last line + */ + QString currentLine() const; + + /** + * This function returns the current id. The id is the first line of an email, + * and is used in filenaming. The id normally starts with "From ". + * @return The current ID, or QString::null if no id was found yet. + */ + QString currentID() const; + + /** + * This function reads the next line. The next line can be read by the currentLine() + * function call. + * + * @return true if succesfull, otherwise false. + */ + bool nextLine(); + + /** + * This function search the file for a certain id. + * If not found, the position is EOF. + * @param id The id of the message to be found. + * @return true if the message was found, false otherwise. + */ + bool searchMessage( const QString& id ); + + /** + * Skips all lines which belongs to the current message. The cursor is on the first line + * of a new message message at the end of this function, or at EOF if the cursor was already + * on the last message. + * @return The number of bytes read while skipping the message. + */ + unsigned int skipMessage(); + + /** + * Sets the cursor back to the beginning of the file + */ + void rewind(); + + /** + * Returns true if the cursor is at EOF. + * @return true if and only if the cursor is at EOF. + */ + bool atEnd() const; + + /** + * Return true if the message is a new message, or all messages are listed + * @return true if it must be listed + */ + bool inListing() const; +private: + /** + * Opens a file + * @return true Returns true if opening was succesful. + */ + bool open( bool savetime ); + + /** + * Closes a file. + */ + void close(); + +private: + QFile* m_file; + QTextStream* m_stream; + QString* m_current_line; + QString* m_current_id; + bool m_atend; + + struct utimbuf* m_prev_time; + + bool m_only_new, m_savetime; + + bool m_status, m_prev_status, m_header; +}; +#endif diff --git a/kioslaves/mbox/stat.cc b/kioslaves/mbox/stat.cc new file mode 100644 index 000000000..e3418cbbd --- /dev/null +++ b/kioslaves/mbox/stat.cc @@ -0,0 +1,115 @@ +/* + * This is a simple kioslave to handle mbox-files. + * Copyright (C) 2004 Mart Kelder (mart.kde@hccnet.nl) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include "stat.h" + +#include "readmbox.h" +#include "urlinfo.h" + +#include <kdebug.h> +#include <kio/global.h> + +#include <sys/stat.h> + +KIO::UDSEntry Stat::stat( const UrlInfo& info ) +{ + if( info.type() == UrlInfo::message ) + return Stat::statMessage( info ); + else if( info.type() == UrlInfo::directory ) + return Stat::statDirectory( info ); + else + return KIO::UDSEntry(); +} + +KIO::UDSEntry Stat::stat( ReadMBox& mbox, const UrlInfo& info ) +{ + kdDebug() << "Stat::stat()" << endl; + KIO::UDSEntry entry; + QString url; + + if( info.type() == UrlInfo::invalid ) + return entry; + else if( info.type() == UrlInfo::message ) + mbox.searchMessage( info.id() ); + + Stat::addAtom( entry, KIO::UDS_FILE_TYPE, S_IFREG ); + Stat::addAtom( entry, KIO::UDS_MIME_TYPE, "message/rfc822" ); + + url = QString( "mbox:%1/%2" ).arg( info.filename(), mbox.currentID() ); + Stat::addAtom( entry, KIO::UDS_URL, url ); + if( mbox.currentID().isEmpty() ) + Stat::addAtom( entry, KIO::UDS_NAME, "foobar" ); + else + Stat::addAtom( entry, KIO::UDS_NAME, mbox.currentID() ); + + + Stat::addAtom( entry, KIO::UDS_SIZE, mbox.skipMessage() ); + + return entry; +} + +KIO::UDSEntry Stat::statDirectory( const UrlInfo& info ) +{ + kdDebug() << "statDirectory()" << endl; + KIO::UDSEntry entry; + + //Specific things for a directory + Stat::addAtom( entry, KIO::UDS_FILE_TYPE, S_IFDIR ); + Stat::addAtom( entry, KIO::UDS_NAME, info.filename() ); + + return entry; +} + +KIO::UDSEntry Stat::statMessage( const UrlInfo& info ) +{ + kdDebug() << "statMessage( " << info.url() << " )" << endl; + KIO::UDSEntry entry; + QString url = QString( "mbox:%1" ).arg( info.url() ); + + //Specific things for a message + Stat::addAtom( entry, KIO::UDS_FILE_TYPE, S_IFREG ); + Stat::addAtom( entry, KIO::UDS_MIME_TYPE, "message/rfc822" ); + + Stat::addAtom( entry, KIO::UDS_URL, url ); + url = url.right( url.length() - url.findRev( "/" ) - 1 ); + Stat::addAtom( entry, KIO::UDS_NAME, url ); + + return entry; +} + +void Stat::addAtom( KIO::UDSEntry& entry, unsigned int uds, const QString& str ) +{ + KIO::UDSAtom atom; + atom.m_uds = uds; + atom.m_str = str; + atom.m_long = 0; + + entry.append( atom ); +} + + +void Stat::addAtom( KIO::UDSEntry& entry, unsigned int uds, long lng ) +{ + KIO::UDSAtom atom; + atom.m_uds = uds; + atom.m_str = QString::null; + atom.m_long = lng; + + entry.append( atom ); +} + diff --git a/kioslaves/mbox/stat.h b/kioslaves/mbox/stat.h new file mode 100644 index 000000000..f26f4338d --- /dev/null +++ b/kioslaves/mbox/stat.h @@ -0,0 +1,82 @@ +/* + * This is a simple kioslave to handle mbox-files. + * Copyright (C) 2004 Mart Kelder (mart.kde@hccnet.nl) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef STAT_H +#define STAT_H + +#include <kio/global.h> + +class ReadMBox; +class UrlInfo; + +class KURL; + +class QString; + +/** + * This class is used to get the stats of a mbox-email or mbox-file. + * This class only uses static members. + */ +class Stat +{ +public: + /** + * Empty constructor + */ + Stat() {} + + /** + * Emtpy destructor + */ + ~Stat() {} + + /** + * This functions gives information with a given UrlInfo. + * @param info The file information + * @return The information of the file as destribed in UrlInfo. + */ + static KIO::UDSEntry stat( const UrlInfo& info ); + /** + * This function gives information with a given ReadMBox and UrlInfo. + * Through this, it is possible to ask the stats of the next message, + * without reopening the mbox-file. + * @param mbox The ReadMBox instance, used to search the mbox-email in. + * @param info The url information. + * @return The requesteds information. + */ + static KIO::UDSEntry stat( ReadMBox& mbox, const UrlInfo& info ); + + /** + * This function gets the stats of a given mbox-file in an UDSEntry. + * @param info The location of the mbox-file. + * @return A list of Atoms. + */ + static KIO::UDSEntry statDirectory( const UrlInfo& info ); + + /** + * This function gets the stats of a geven mbox-message in a UDSEntry. + * @param info The url of the mbox-message. + * @return Information shipped in an UDSEntry. + */ + static KIO::UDSEntry statMessage( const UrlInfo& info ); +private: + static void addAtom( KIO::UDSEntry& entry, unsigned int key, const QString& value ); + static void addAtom( KIO::UDSEntry& entry, unsigned int key, const long value ); +}; + +#endif diff --git a/kioslaves/mbox/urlinfo.cc b/kioslaves/mbox/urlinfo.cc new file mode 100644 index 000000000..710dc6f0c --- /dev/null +++ b/kioslaves/mbox/urlinfo.cc @@ -0,0 +1,133 @@ +/* + * This is a simple kioslave to handle mbox-files. + * Copyright (C) 2004 Mart Kelder (mart.kde@hccnet.nl) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include "urlinfo.h" + +#include <kdebug.h> +#include <kurl.h> + +#include <qfileinfo.h> +#include <qstring.h> + +UrlInfo::UrlInfo( const KURL& url, const UrlType type ) + : m_type( invalid ), + m_filename( new QString ), + m_id( new QString ) +{ + calculateInfo( url, type ); +} + +UrlInfo::~UrlInfo() +{ + delete m_filename; + delete m_id; +} + +QString UrlInfo::mimetype() const +{ + switch( m_type ) + { + case message: + return "message/rfc822"; + case directory: + return "inode/directory"; + case invalid: + default: + return "invalid"; + } +} + +QString UrlInfo::filename() const +{ + return *m_filename; +} + +QString UrlInfo::id() const +{ + return *m_id; +} + +QString UrlInfo::url() const +{ + return *m_filename + "/" + *m_id; +} + + +void UrlInfo::calculateInfo( const KURL& url, const UrlType type ) +{ + bool found = false; + + if( !found && type & UrlInfo::message ) + found = isMessage( url ); + if( !found && type & UrlInfo::directory ) + found = isDirectory( url ); + if( !found ) + { + m_type = invalid; + *m_filename = ""; + *m_id = ""; + } +} + +bool UrlInfo::isDirectory( const KURL& url ) +{ + //Check is url is in the form mbox://{filename} + QString filename = url.path(); + QFileInfo info; + + //Remove ending / + while( filename.length() > 1 && filename.right( 1 ) == "/" ) + filename.remove( filename.length()-2, 1 ); + + //Is this a directory? + info.setFile( filename ); + if( !info.isFile() ) + return false; + + //Setting paramaters + *m_filename = filename; + *m_id = QString::null; + m_type = directory; + kdDebug() << "urlInfo::isDirectory( " << url << " )" << endl; + return true; +} + +bool UrlInfo::isMessage( const KURL& url ) +{ + QString path = url.path(); + QFileInfo info; + int cutindex = path.findRev( '/' ); + + //Does it contain at least one /? + if( cutindex < 0 ) + return false; + + //Does the mbox-file exists? + info.setFile( path.left( cutindex ) ); + if( !info.isFile() ) + return false; + + //Settings parameters + kdDebug() << "urlInfo::isMessage( " << url << " )" << endl; + m_type = message; + *m_id = path.right( path.length() - cutindex - 1 ); + *m_filename = path.left( cutindex ); + + return true; +} + diff --git a/kioslaves/mbox/urlinfo.h b/kioslaves/mbox/urlinfo.h new file mode 100644 index 000000000..c3177db5e --- /dev/null +++ b/kioslaves/mbox/urlinfo.h @@ -0,0 +1,82 @@ +/* + * This is a simple kioslave to handle mbox-files. + * Copyright (C) 2004 Mart Kelder (mart.kde@hccnet.nl) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef URLINFO_H +#define URLINFO_H +class KURL; + +class QString; + +class UrlInfo +{ +public: + /** + * This enum is used to determe the url type. + */ + enum UrlType { invalid = 0, message = 1, directory = 2 }; + + /** + * Constructor + * + * @param url The url: this url is used to split the location data off. + * @param type The possible types of the url + */ + UrlInfo( const KURL &url, const UrlType type = (UrlType)( message | directory ) ); + + /** + * Destructor + */ + ~UrlInfo(); + + /** + * Returns the type of the url + * @return the type of the url + */ + UrlType type() const { return m_type; } + + /** + * @return the mimetype of the url + */ + QString mimetype() const; + + /** + * @return The location of the mbox-file + */ + QString filename() const; + /** + * @return The id given in the url. + */ + QString id() const; + + /** + * @return the while url as QString + */ + QString url() const; +private: + void calculateInfo( const KURL& url, const UrlType type ); + + bool isDirectory( const KURL& url ); + bool isMessage( const KURL& url ); + +private: + UrlType m_type; + QString *m_filename; + QString *m_id; +}; + +#endif diff --git a/kioslaves/opengroupware/Makefile.am b/kioslaves/opengroupware/Makefile.am new file mode 100644 index 000000000..bd4104904 --- /dev/null +++ b/kioslaves/opengroupware/Makefile.am @@ -0,0 +1,17 @@ +INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/kresources/opengroupware/soap \ + $(all_includes) + +noinst_HEADERS = opengroupware.h + +METASOURCES = AUTO + +kdelnkdir = $(kde_servicesdir) +kdelnk_DATA = opengroupware.protocol opengroupwares.protocol + +kde_module_LTLIBRARIES = kio_opengroupware.la + +kio_opengroupware_la_SOURCES = opengroupware.cpp webdavhandler.cpp +kio_opengroupware_la_LIBADD = $(top_builddir)/libkcal/libkcal.la \ + $(top_builddir)/libkdepim/libkdepim.la $(LIB_KIO) +kio_opengroupware_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + diff --git a/kioslaves/opengroupware/opengroupware.cpp b/kioslaves/opengroupware/opengroupware.cpp new file mode 100644 index 000000000..76abeb10e --- /dev/null +++ b/kioslaves/opengroupware/opengroupware.cpp @@ -0,0 +1,250 @@ +/* + This file is part of KDE. + + Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org> + + 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. +*/ + +#include "opengroupware.h" +#include "webdavhandler.h" + +#include <kdebug.h> +#include <kurl.h> +#include <kio/job.h> +#include <kio/davjob.h> +#include <klocale.h> + +#include <libkdepim/kabcresourcecached.h> + +#include <libkcal/freebusy.h> +#include <libkcal/icalformat.h> +#include <libkcal/scheduler.h> +#include <libkcal/calendarlocal.h> + +#include <kabc/vcardconverter.h> + +#include <kinstance.h> +#include <kdebug.h> +#include <klocale.h> +#include <ktempfile.h> + +#include <sys/types.h> +#include <unistd.h> +#include <stdlib.h> + +#include <kdepimmacros.h> + +namespace KABC { + +class ResourceMemory : public ResourceCached +{ + public: + ResourceMemory() : ResourceCached( 0 ) {} + + Ticket *requestSaveTicket() { return 0; } + bool load() { return true; } + bool save( Ticket * ) { return true; } + void releaseSaveTicket( Ticket * ) {} +}; + +} + + +extern "C" { +KDE_EXPORT int kdemain( int argc, char **argv ); +} + +int kdemain( int argc, char **argv ) +{ + KInstance instance( "kio_OpenGroupware" ); + + kdDebug(7000) << "Starting kio_OpenGroupware(pid: " << getpid() << ")" << endl; + + if (argc != 4) { + fprintf( stderr, "Usage: kio_OpenGroupware protocol domain-socket1 domain-socket2\n"); + exit( -1 ); + } + + OpenGroupware slave( argv[1], argv[2], argv[3] ); + slave.dispatchLoop(); + + return 0; +} + +OpenGroupware::OpenGroupware( const QCString &protocol, const QCString &pool, + const QCString &app ) + : SlaveBase( protocol, pool, app ) +{ +} + +void OpenGroupware::get( const KURL &url ) +{ + kdDebug(7000) << "OpenGroupware::get()" << endl; + kdDebug(7000) << " URL: " << url.url() << endl; + #if 1 + kdDebug(7000) << " Path: " << url.path() << endl; + kdDebug(7000) << " Query: " << url.query() << endl; + kdDebug(7000) << " Protocol: " << url.protocol() << endl; + kdDebug(7000) << " Filename: " << url.filename() << endl; + #endif + + mimeType( "text/plain" ); + + QString path = url.path(); + debugMessage( "Path: " + path ); + + if ( path.startsWith( "/freebusy/" ) ) { + getFreeBusy( url ); + } else if ( path.startsWith( "/calendar/" ) ) { + getCalendar( url ); + } else if ( path.startsWith( "/addressbook/" ) ) { + getAddressbook( url ); + } else { + QString error = i18n("Unknown path. Known paths are '/freebusy/', " + "'/calendar/' and '/addressbook/'."); + errorMessage( error ); + } + + kdDebug(7000) << "OpenGroupwareCgiProtocol::get() done" << endl; +} + +void OpenGroupware::getFreeBusy( const KURL &url ) +{ + QString file = url.filename(); + if ( file.right( 4 ) != ".ifb" ) { + QString error = i18n("Illegal filename. File has to have '.ifb' suffix."); + errorMessage( error ); + } else { + QString email = file.left( file.length() - 4 ); + debugMessage( "Email: " + email ); + + QString user = url.user(); + QString pass = url.pass(); + + debugMessage( "URL: " ); + debugMessage( "User: " + user ); + debugMessage( "Password: " + pass ); + + KCal::FreeBusy *fb = new KCal::FreeBusy; + + if ( user.isEmpty() || pass.isEmpty() ) { + errorMessage( i18n("Need username and password.") ); + } else { + // FIXME get from server + + // FIXME: Read range from configuration or URL parameters. + QDate start = QDate::currentDate().addDays( -3 ); + QDate end = QDate::currentDate().addDays( 60 ); + + fb->setDtStart( start ); + fb->setDtEnd( end ); + + kdDebug() << "Login" << endl; + + } + +#if 0 + QDateTime s = QDateTime( QDate( 2004, 9, 27 ), QTime( 10, 0 ) ); + QDateTime e = QDateTime( QDate( 2004, 9, 27 ), QTime( 11, 0 ) ); + + fb->addPeriod( s, e ); +#endif + + KCal::ICalFormat format; + + QString ical = format.createScheduleMessage( fb, KCal::Scheduler::Publish ); + + data( ical.utf8() ); + + finished(); + } +} + + +void OpenGroupware::getCalendar( const KURL &_url ) +{ + + KURL url( _url ); // we'll be changing it + QString user = url.user(); + QString pass = url.pass(); + + QDomDocument props = WebdavHandler::createAllPropsRequest(); + + debugMessage( "URL: " ); + debugMessage( "User: " + user ); + debugMessage( "Password: " + pass ); + + url.setProtocol( "webdav" ); + url.setPath ( "/zidestore/dav/till/" ); + + kdDebug(7000) << "getCalendar: " << url.prettyURL() << endl; + + // FIXME do progress handling + mListEventsJob = KIO::davPropFind( url, props, "0", false ); + connect( mListEventsJob, SIGNAL( result( KIO::Job * ) ), + SLOT( slotGetCalendarListingResult( KIO::Job * ) ) ); +} + +void OpenGroupware::getAddressbook( const KURL &url ) +{ + +} + +void OpenGroupware::errorMessage( const QString &msg ) +{ + error( KIO::ERR_SLAVE_DEFINED, msg ); +} + +void OpenGroupware::debugMessage( const QString &msg ) +{ +#if 0 + data( ( msg + "\n" ).utf8() ); +#else + Q_UNUSED( msg ); +#endif +} + + +void OpenGroupware::slotGetCalendarListingResult( KIO::Job *job ) +{ + + kdDebug(7000) << k_funcinfo << endl; + + if ( job->error() ) { + job->showErrorDialog( 0 ); + } else { + kdDebug() << "ResourceSlox::slotResult() success" << endl; + + QDomDocument doc = mListEventsJob->response(); + + } + KCal::ICalFormat format; + KCal::CalendarLocal calendar; + + QString ical = format.toString( &calendar ); + + data( ical.utf8() ); + + finished(); +} + + +void OpenGroupware::slotGetCalendarResult( KIO::Job *job ) +{ + Q_UNUSED( job ); +} +#include "opengroupware.moc" + diff --git a/kioslaves/opengroupware/opengroupware.h b/kioslaves/opengroupware/opengroupware.h new file mode 100644 index 000000000..04eadf456 --- /dev/null +++ b/kioslaves/opengroupware/opengroupware.h @@ -0,0 +1,56 @@ +/* + This file is part of KDE. + + Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org> + + 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. +*/ +#ifndef GROUPWISE_H +#define GROUPWISE_H + +#include <kio/slavebase.h> + +#include <qobject.h> + +namespace KIO { + class Job; + class DavJob; +} + + +class OpenGroupware : public QObject, public KIO::SlaveBase +{ + Q_OBJECT + public: + OpenGroupware( const QCString &protocol, const QCString &pool, + const QCString &app ); + + void get( const KURL &url ); + + protected: + void debugMessage( const QString & ); + void errorMessage( const QString & ); + + void getFreeBusy( const KURL &url ); + void getCalendar( const KURL &url ); + void getAddressbook( const KURL &url ); + protected slots: + void slotGetCalendarListingResult( KIO::Job* ); + void slotGetCalendarResult( KIO::Job* ); + private: + KIO::DavJob *mListEventsJob; +}; + +#endif diff --git a/kioslaves/opengroupware/opengroupware.protocol b/kioslaves/opengroupware/opengroupware.protocol new file mode 100644 index 000000000..89c0b9ea2 --- /dev/null +++ b/kioslaves/opengroupware/opengroupware.protocol @@ -0,0 +1,7 @@ +[Protocol] +DocPath=kioslave/opengroupware.html +exec=kio_opengroupware +input=none +output=filesystem +protocol=opengroupware +reading=true diff --git a/kioslaves/opengroupware/opengroupwares.protocol b/kioslaves/opengroupware/opengroupwares.protocol new file mode 100644 index 000000000..595057b27 --- /dev/null +++ b/kioslaves/opengroupware/opengroupwares.protocol @@ -0,0 +1,7 @@ +[Protocol] +DocPath=kioslave/opengroupware.html +exec=kio_opengroupware +input=none +output=filesystem +protocol=opengroupwares +reading=true diff --git a/kioslaves/opengroupware/webdavhandler.cpp b/kioslaves/opengroupware/webdavhandler.cpp new file mode 100644 index 000000000..683302003 --- /dev/null +++ b/kioslaves/opengroupware/webdavhandler.cpp @@ -0,0 +1,81 @@ +/* + This file is part of kdepim. + + Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org> + + 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. +*/ + +#include <config.h> + +#include "webdavhandler.h" + +#include <limits.h> + +#include <libkdepim/kpimprefs.h> + +#include <kdebug.h> +#include <kconfig.h> + +#include <qfile.h> + + +WebdavHandler::WebdavHandler() +{ +} + + +QDomElement WebdavHandler::addElement( QDomDocument &doc, QDomNode &node, + const QString &tag ) +{ + QDomElement el = doc.createElement( tag ); + node.appendChild( el ); + return el; +} + +QDomElement WebdavHandler::addDavElement( QDomDocument &doc, QDomNode &node, + const QString &tag ) +{ + QDomElement el = doc.createElementNS( "DAV", tag ); + node.appendChild( el ); + return el; +} + +QDomElement WebdavHandler::addSloxElement( QDomDocument &doc, QDomNode &node, + const QString &tag, + const QString &text ) +{ + QDomElement el = doc.createElementNS( "SLOX", tag ); + if ( !text.isEmpty() ) { + QDomText textnode = doc.createTextNode( text ); + el.appendChild( textnode ); + } + node.appendChild( el ); + return el; +} + +QDomDocument WebdavHandler::createAllPropsRequest() +{ + QDomDocument doc; + + QDomElement root = WebdavHandler::addDavElement( doc, doc, "propfind" ); + QDomElement prop = WebdavHandler::addDavElement( doc, root, "prop" ); + WebdavHandler::addDavElement( doc, prop, "getcontentlength"); + WebdavHandler::addDavElement( doc, prop, "getlastmodified" ); + WebdavHandler::addDavElement( doc, prop, "displayname" ); + WebdavHandler::addDavElement( doc, prop, "resourcetype" ); + prop.appendChild( doc.createElementNS( "http://apache.org/dav/props/", "executable" ) ); + return doc; +} diff --git a/kioslaves/opengroupware/webdavhandler.h b/kioslaves/opengroupware/webdavhandler.h new file mode 100644 index 000000000..3c1320812 --- /dev/null +++ b/kioslaves/opengroupware/webdavhandler.h @@ -0,0 +1,44 @@ +/* + This file is part of kdepim. + + Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org> + + 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. +*/ +#ifndef WEBDAVHANDLER_H +#define WEBDAVHANDLER_H + +#include <qvaluelist.h> +#include <qstring.h> +#include <qdatetime.h> +#include <qdom.h> + +class WebdavHandler +{ + public: + WebdavHandler(); + + + static QDomElement addElement( QDomDocument &, QDomNode &, + const QString &tag ); + static QDomElement addDavElement( QDomDocument &, QDomNode &, + const QString &tag ); + static QDomElement addSloxElement( QDomDocument &, QDomNode &, + const QString &tag, + const QString &text = QString::null ); + static QDomDocument createAllPropsRequest(); +}; + +#endif diff --git a/kioslaves/sieve/Makefile.am b/kioslaves/sieve/Makefile.am new file mode 100644 index 000000000..830c8258e --- /dev/null +++ b/kioslaves/sieve/Makefile.am @@ -0,0 +1,15 @@ +INCLUDES= -I$(srcdir)/../.. -I$(srcdir)/.. $(all_includes) + +kde_module_LTLIBRARIES = kio_sieve.la + +kio_sieve_la_SOURCES = sieve.cpp +kio_sieve_la_LIBADD = $(LIB_KIO) $(SASL2_LIBS) +kio_sieve_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = sieve.h + +kdelnk_DATA = sieve.protocol +kdelnkdir = $(kde_servicesdir) + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_sieve.pot diff --git a/kioslaves/sieve/configure.in.in b/kioslaves/sieve/configure.in.in new file mode 100644 index 000000000..a722cfebb --- /dev/null +++ b/kioslaves/sieve/configure.in.in @@ -0,0 +1,18 @@ +AC_MSG_CHECKING(if we need to talk to broken timsieved's) +AC_ARG_ENABLE(broken-timsieved-workaround, +[AC_HELP_STRING([--enable-broken-timsieved-workaround], + [versions <= 1.1.0 of the timsieved (part of the Cyrus + IMAP suite) do not interpret the HAVESPACE command of + the sieve protocol. This option makes the sieve + KIO::Slave simply omit that command.])], +[case "${enableval}" in + yes) AC_MSG_RESULT(yes) + need_broken_timsieved_support="yes" ;; + no) AC_MSG_RESULT(no) ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-broken-timsieved-workaround]) ;; +esac],[AC_MSG_RESULT(no)]) + +if test "$need_broken_timsieved_support" = "yes"; then + AC_SUBST(HAVE_BROKEN_TIMSIEVED) + AC_DEFINE(HAVE_BROKEN_TIMSIEVED,1,[Define if the sieve KIO::Slave must be able to talk to timsieved <= 1.1.0]) +fi diff --git a/kioslaves/sieve/draft-daboo-sieve-include.txt b/kioslaves/sieve/draft-daboo-sieve-include.txt new file mode 100644 index 000000000..83a08dfb8 --- /dev/null +++ b/kioslaves/sieve/draft-daboo-sieve-include.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-daboo-sieve-include.txt diff --git a/kioslaves/sieve/draft-daboo-sieve-spamtest.txt b/kioslaves/sieve/draft-daboo-sieve-spamtest.txt new file mode 100644 index 000000000..8f22e5607 --- /dev/null +++ b/kioslaves/sieve/draft-daboo-sieve-spamtest.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-daboo-sieve-spamtest.txt diff --git a/kioslaves/sieve/draft-degener-sieve-body-00.txt b/kioslaves/sieve/draft-degener-sieve-body-00.txt new file mode 100644 index 000000000..7b1ee844e --- /dev/null +++ b/kioslaves/sieve/draft-degener-sieve-body-00.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-degener-sieve-body-00.txt diff --git a/kioslaves/sieve/draft-degener-sieve-copy.txt b/kioslaves/sieve/draft-degener-sieve-copy.txt new file mode 100644 index 000000000..8c23855c8 --- /dev/null +++ b/kioslaves/sieve/draft-degener-sieve-copy.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-degener-sieve-copy.txt diff --git a/kioslaves/sieve/draft-degener-sieve-editheader.txt b/kioslaves/sieve/draft-degener-sieve-editheader.txt new file mode 100644 index 000000000..6e9674f9d --- /dev/null +++ b/kioslaves/sieve/draft-degener-sieve-editheader.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-degener-sieve-editheader.txt diff --git a/kioslaves/sieve/draft-degener-sieve-multiscript.txt b/kioslaves/sieve/draft-degener-sieve-multiscript.txt new file mode 100644 index 000000000..53a838226 --- /dev/null +++ b/kioslaves/sieve/draft-degener-sieve-multiscript.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-degener-sieve-multiscript.txt diff --git a/kioslaves/sieve/draft-homme-sieve-variables.txt b/kioslaves/sieve/draft-homme-sieve-variables.txt new file mode 100644 index 000000000..cccaa59c8 --- /dev/null +++ b/kioslaves/sieve/draft-homme-sieve-variables.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-homme-sieve-variables.txt diff --git a/kioslaves/sieve/draft-martin-managesieve-04.txt b/kioslaves/sieve/draft-martin-managesieve-04.txt new file mode 100644 index 000000000..82684ad57 --- /dev/null +++ b/kioslaves/sieve/draft-martin-managesieve-04.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-martin-managesieve-04.txt diff --git a/kioslaves/sieve/draft-martin-sieve-notify-01.txt b/kioslaves/sieve/draft-martin-sieve-notify-01.txt new file mode 100644 index 000000000..6eb1cac33 --- /dev/null +++ b/kioslaves/sieve/draft-martin-sieve-notify-01.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-martin-sieve-notify-01.txt diff --git a/kioslaves/sieve/draft-melnikov-sieve-imapflags.txt b/kioslaves/sieve/draft-melnikov-sieve-imapflags.txt new file mode 100644 index 000000000..71cfbb3ce --- /dev/null +++ b/kioslaves/sieve/draft-melnikov-sieve-imapflags.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-melnikov-sieve-imapflags.txt diff --git a/kioslaves/sieve/draft-murchison-sieve-regex-06.txt b/kioslaves/sieve/draft-murchison-sieve-regex-06.txt new file mode 100644 index 000000000..b3e922ee8 --- /dev/null +++ b/kioslaves/sieve/draft-murchison-sieve-regex-06.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-murchison-sieve-regex-06.txt diff --git a/kioslaves/sieve/draft-murchison-sieve-subaddress-05.txt b/kioslaves/sieve/draft-murchison-sieve-subaddress-05.txt new file mode 100644 index 000000000..95cea85a7 --- /dev/null +++ b/kioslaves/sieve/draft-murchison-sieve-subaddress-05.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-murchison-sieve-subaddress-05.txt diff --git a/kioslaves/sieve/draft-showalter-sieve-vacation-04.txt b/kioslaves/sieve/draft-showalter-sieve-vacation-04.txt new file mode 100644 index 000000000..bd2d33593 --- /dev/null +++ b/kioslaves/sieve/draft-showalter-sieve-vacation-04.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/draft-showalter-sieve-vacation-04.txt diff --git a/kioslaves/sieve/rfc3028.txt b/kioslaves/sieve/rfc3028.txt new file mode 100644 index 000000000..b861e2e14 --- /dev/null +++ b/kioslaves/sieve/rfc3028.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/rfc3028.txt diff --git a/kioslaves/sieve/rfc3431.txt b/kioslaves/sieve/rfc3431.txt new file mode 100644 index 000000000..3f11c3259 --- /dev/null +++ b/kioslaves/sieve/rfc3431.txt @@ -0,0 +1 @@ +http://ktown.kde.org/~dirk/sieve/rfc3431.txt diff --git a/kioslaves/sieve/sieve.cpp b/kioslaves/sieve/sieve.cpp new file mode 100644 index 000000000..012d77d15 --- /dev/null +++ b/kioslaves/sieve/sieve.cpp @@ -0,0 +1,1299 @@ +/*************************************************************************** + sieve.cpp - description + ------------------- + begin : Thu Dec 20 18:47:08 EST 2001 + copyright : (C) 2001 by Hamish Rodda + email : meddie@yoyo.cc.monash.edu.au + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + ***************************************************************************/ + +/** + * Portions adapted from the SMTP ioslave. + * Copyright (c) 2000, 2001 Alex Zepeda <jazepeda@pacbell.net> + * Copyright (c) 2001 Michael Häckel <Michael@Haeckel.Net> + * All rights reserved. + * + * Policy: the function where the error occurs calls error(). A result of + * false, where it signifies an error, thus doesn't need to call error() itself. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +extern "C" { +#include <sasl/sasl.h> +} +#include "sieve.h" + +#include <kdebug.h> +#include <kinstance.h> +#include <klocale.h> +#include <kurl.h> +#include <kmdcodec.h> +#include <kglobal.h> + +#include <qcstring.h> +#include <qregexp.h> + +#include <cstdlib> +using std::exit; +#include <sys/stat.h> + +#include <kdepimmacros.h> + +static const int debugArea = 7122; + +static inline +#ifdef NDEBUG + kndbgstream ksDebug() { return kdDebug( debugArea ); } + kndbgstream ksDebug( bool cond ) { return kdDebug( cond, debugArea ); } +#else + kdbgstream ksDebug() { return kdDebug( debugArea ); } + kdbgstream ksDebug( bool cond ) { return kdDebug( cond, debugArea ); } +#endif + +#define SIEVE_DEFAULT_PORT 2000 + +static sasl_callback_t callbacks[] = { + { SASL_CB_ECHOPROMPT, NULL, NULL }, + { SASL_CB_NOECHOPROMPT, NULL, NULL }, + { SASL_CB_GETREALM, NULL, NULL }, + { SASL_CB_USER, NULL, NULL }, + { SASL_CB_AUTHNAME, NULL, NULL }, + { SASL_CB_PASS, NULL, NULL }, + { SASL_CB_CANON_USER, NULL, NULL }, + { SASL_CB_LIST_END, NULL, NULL } +}; + +static const unsigned int SIEVE_DEFAULT_RECIEVE_BUFFER = 512; + +using namespace KIO; +extern "C" +{ + KDE_EXPORT int kdemain(int argc, char **argv) + { + KInstance instance("kio_sieve" ); + + ksDebug() << "*** Starting kio_sieve " << endl; + + if (argc != 4) { + ksDebug() << "Usage: kio_sieve protocol domain-socket1 domain-socket2" << endl; + exit(-1); + } + + if ( sasl_client_init( NULL ) != SASL_OK ) { + fprintf(stderr, "SASL library initialization failed!\n"); + ::exit (-1); + } + + kio_sieveProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + sasl_done(); + + ksDebug() << "*** kio_sieve Done" << endl; + return 0; + } +} + +/* ---------------------------------------------------------------------------------- */ +kio_sieveResponse::kio_sieveResponse() +{ + clear(); +} + +/* ---------------------------------------------------------------------------------- */ +const uint& kio_sieveResponse::getType() const +{ + return rType; +} + +/* ---------------------------------------------------------------------------------- */ +const uint kio_sieveResponse::getQuantity() const +{ + return quantity; +} + +/* ---------------------------------------------------------------------------------- */ +const QCString& kio_sieveResponse::getAction() const +{ + return key; +} + +/* ---------------------------------------------------------------------------------- */ +const QCString& kio_sieveResponse::getKey() const +{ + return key; +} + +/* ---------------------------------------------------------------------------------- */ +const QCString& kio_sieveResponse::getVal() const +{ + return val; +} + +/* ---------------------------------------------------------------------------------- */ +const QCString& kio_sieveResponse::getExtra() const +{ + return extra; +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveResponse::setQuantity(const uint& newQty) +{ + rType = QUANTITY; + quantity = newQty; +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveResponse::setAction(const QCString& newAction) +{ + rType = ACTION; + key = newAction.copy(); +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveResponse::setKey(const QCString& newKey) +{ + rType = KEY_VAL_PAIR; + key = newKey.copy(); +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveResponse::setVal(const QCString& newVal) +{ + val = newVal.copy(); +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveResponse::setExtra(const QCString& newExtra) +{ + extra = newExtra.copy(); +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveResponse::clear() +{ + rType = NONE; + extra = key = val = QCString(""); + quantity = 0; +} + +/* ---------------------------------------------------------------------------------- */ +kio_sieveProtocol::kio_sieveProtocol(const QCString &pool_socket, const QCString &app_socket) + : TCPSlaveBase( SIEVE_DEFAULT_PORT, "sieve", pool_socket, app_socket, false) + , m_connMode(NORMAL) + , m_supportsTLS(false) + , m_shouldBeConnected(false) +{ +} + +/* ---------------------------------------------------------------------------------- */ +kio_sieveProtocol::~kio_sieveProtocol() +{ + if ( isConnectionValid() ) + disconnect(); +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveProtocol::setHost (const QString &host, int port, const QString &user, const QString &pass) +{ + if ( isConnectionValid() && + ( m_sServer != host || + m_iPort != port || + m_sUser != user || + m_sPass != pass ) ) { + disconnect(); + } + m_sServer = host; + m_iPort = port ? port : m_iDefaultPort; + m_sUser = user; + m_sPass = pass; + m_supportsTLS = false; +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveProtocol::openConnection() +{ + m_connMode = CONNECTION_ORIENTED; + connect(); +} + +bool kio_sieveProtocol::parseCapabilities(bool requestCapabilities/* = false*/) +{ + ksDebug() << k_funcinfo << endl; + + // Setup... + bool ret = false; + + if (requestCapabilities) { + sendData("CAPABILITY"); + } + + while (receiveData()) { + ksDebug() << "Looping receive" << endl; + + if (r.getType() == kio_sieveResponse::ACTION) { + if ( r.getAction().contains("ok", false) != -1 ) { + ksDebug() << "Sieve server ready & awaiting authentication." << endl; + break; + } else + ksDebug() << "Unknown action " << r.getAction() << "." << endl; + + } else if (r.getKey() == "IMPLEMENTATION") { + if (r.getVal().contains("sieve", false) != -1) { + ksDebug() << "Connected to Sieve server: " << r.getVal() << endl; + ret = true; + setMetaData("implementation", r.getVal()); + m_implementation = r.getVal(); + } + + } else if (r.getKey() == "SASL") { + // Save list of available SASL methods + m_sasl_caps = QStringList::split(' ', r.getVal()); + ksDebug() << "Server SASL authentication methods: " << m_sasl_caps.join(", ") << endl; + setMetaData("saslMethods", r.getVal()); + + } else if (r.getKey() == "SIEVE") { + // Save script capabilities; report back as meta data: + ksDebug() << "Server script capabilities: " << QStringList::split(' ', r.getVal()).join(", ") << endl; + setMetaData("sieveExtensions", r.getVal()); + + } else if (r.getKey() == "STARTTLS") { + // The server supports TLS + ksDebug() << "Server supports TLS" << endl; + m_supportsTLS = true; + setMetaData("tlsSupported", "true"); + + } else { + ksDebug() << "Unrecognised key " << r.getKey() << endl; + } + } + + if (!m_supportsTLS) { + setMetaData("tlsSupported", "false"); + } + + return ret; +} + + +/* ---------------------------------------------------------------------------------- */ +/** + * Checks if connection parameters (currently - auth method) have changed. + * If it it, close the current connection + */ +void kio_sieveProtocol::changeCheck( const KURL &url ) +{ + QString auth; + + if (!metaData("sasl").isEmpty()) + auth = metaData("sasl").upper(); + else { + QString query = url.query(); + if ( query.startsWith("?") ) query.remove( 0, 1 ); + QStringList q = QStringList::split( ",", query ); + QStringList::iterator it; + + for ( it = q.begin(); it != q.end(); ++it ) { + if ( ( (*it).section('=',0,0) ).lower() == "x-mech" ) { + auth = ( (*it).section('=',1) ).upper(); + break; + } + } + } + ksDebug() << "auth: " << auth << " m_sAuth: " << m_sAuth << endl; + if ( m_sAuth != auth ) { + m_sAuth = auth; + if ( isConnectionValid() ) + disconnect(); + } +} + +/* ---------------------------------------------------------------------------------- */ +/** + * Connects to the server. + * returns false and calls error() if an error occurred. + */ +bool kio_sieveProtocol::connect(bool useTLSIfAvailable) +{ + ksDebug() << k_funcinfo << endl; + + if (isConnectionValid()) return true; + + infoMessage(i18n("Connecting to %1...").arg( m_sServer)); + + if (m_connMode == CONNECTION_ORIENTED && m_shouldBeConnected) { + error(ERR_CONNECTION_BROKEN, i18n("The connection to the server was lost.")); + return false; + } + + setBlockConnection(true); + + if (!connectToHost(m_sServer, m_iPort, true)) { + return false; + } + + if (!parseCapabilities()) { + closeDescriptor(); + error(ERR_UNSUPPORTED_PROTOCOL, i18n("Server identification failed.")); + return false; + } + + // Attempt to start TLS + // FIXME find a test server and test that this works + if (useTLSIfAvailable && m_supportsTLS && canUseTLS()) { + sendData("STARTTLS"); + if (operationSuccessful()) { + ksDebug() << "TLS has been accepted. Starting TLS..." << endl + << "WARNING this is untested and may fail." << endl; + int retval = startTLS(); + if (retval == 1) { + ksDebug() << "TLS enabled successfully." << endl; + // reparse capabilities: + parseCapabilities( requestCapabilitiesAfterStartTLS() ); + } else { + ksDebug() << "TLS initiation failed, code " << retval << endl; + disconnect(true); + return connect(false); + // error(ERR_INTERNAL, i18n("TLS initiation failed.")); + } + } else + ksDebug() << "Server incapable of TLS. Transmitted documents will be unencrypted." << endl; + } else + ksDebug() << "We are incapable of TLS. Transmitted documents will be unencrypted." << endl; + + infoMessage(i18n("Authenticating user...")); + if (!authenticate()) { + disconnect(); + error(ERR_COULD_NOT_AUTHENTICATE, i18n("Authentication failed.")); + return false; + } + + m_shouldBeConnected = true; + return true; +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveProtocol::closeConnection() +{ + m_connMode = CONNECTION_ORIENTED; + disconnect(); +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveProtocol::disconnect(bool forcibly) +{ + if (!forcibly) { + sendData("LOGOUT"); + + // This crashes under certain conditions as described in + // http://intevation.de/roundup/kolab/issue2442 + // Fixing KIO::TCPSlaveBase::atEnd() for !fd would also work but 3.x is on life support. + //if (!operationSuccessful()) + // ksDebug() << "Server did not logout cleanly." << endl; + } + + closeDescriptor(); + m_shouldBeConnected = false; +} + +/* ---------------------------------------------------------------------------------- */ +/*void kio_sieveProtocol::slave_status() +{ + slaveStatus(isConnectionValid() ? m_sServer : "", isConnectionValid()); + + finished(); +}*/ + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveProtocol::special(const QByteArray &data) +{ + int tmp; + QDataStream stream(data, IO_ReadOnly); + KURL url; + + stream >> tmp; + + switch (tmp) { + case 1: + stream >> url; + if (!activate(url)) + return; + break; + case 2: + if (!deactivate()) + return; + break; + case 3: + parseCapabilities(true); + break; + } + + infoMessage(i18n("Done.")); + + finished(); +} + +/* ---------------------------------------------------------------------------------- */ +bool kio_sieveProtocol::activate(const KURL& url) +{ + changeCheck( url ); + if (!connect()) + return false; + + infoMessage(i18n("Activating script...")); + + QString filename = url.fileName(false); + + if (filename.isEmpty()) { + error(ERR_DOES_NOT_EXIST, url.prettyURL()); + return false; + } + + if (!sendData("SETACTIVE \"" + filename.utf8() + "\"")) + return false; + + if (operationSuccessful()) { + ksDebug() << "Script activation complete." << endl; + return true; + } else { + error(ERR_INTERNAL_SERVER, i18n("There was an error activating the script.")); + return false; + } +} + +/* ---------------------------------------------------------------------------------- */ +bool kio_sieveProtocol::deactivate() +{ + if (!connect()) + return false; + + if (!sendData("SETACTIVE \"\"")) + return false; + + if (operationSuccessful()) { + ksDebug() << "Script deactivation complete." << endl; + return true; + } else { + error(ERR_INTERNAL_SERVER, i18n("There was an error deactivating the script.")); + return false; + } +} + +static void append_lf2crlf( QByteArray & out, const QByteArray & in ) { + if ( in.isEmpty() ) + return; + const unsigned int oldOutSize = out.size(); + out.resize( oldOutSize + 2 * in.size() ); + const char * s = in.begin(); + const char * const end = in.end(); + char * d = out.begin() + oldOutSize; + char last = '\0'; + while ( s < end ) { + if ( *s == '\n' && last != '\r' ) + *d++ = '\r'; + *d++ = last = *s++; + } + out.resize( d - out.begin() ); +} + +void kio_sieveProtocol::put(const KURL& url, int /*permissions*/, bool /*overwrite*/, bool /*resume*/) +{ + changeCheck( url ); + if (!connect()) + return; + + infoMessage(i18n("Sending data...")); + + QString filename = url.fileName(false); + + if (filename.isEmpty()) { + error(ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + QByteArray data; + for (;;) { + dataReq(); + QByteArray buffer; + const int newSize = readData(buffer); + append_lf2crlf( data, buffer ); + if ( newSize < 0 ) { + // read error: network in unknown state so disconnect + error(ERR_COULD_NOT_READ, i18n("KIO data supply error.")); + return; + } + if ( newSize == 0 ) + break; + } + + // script size + int bufLen = (int)data.size(); + totalSize(bufLen); + + // timsieved 1.1.0: + // C: HAVESPACE "rejected" 74 + // S: NO "Number expected" + // C: HAVESPACE 74 + // S: NO "Missing script name" + // S: HAVESPACE "rejected" "74" + // C: NO "Number expected" + // => broken, we can't use it :-( + // (will be fixed in Cyrus 2.1.10) +#ifndef HAVE_BROKEN_TIMSIEVED + // first, check quota (it's a SHOULD in draft std) + if (!sendData("HAVESPACE \"" + filename.utf8() + "\" " + + QCString().setNum( bufLen ))) + return; + + if (!operationSuccessful()) { + error(ERR_DISK_FULL, i18n("Quota exceeded")); + return; + } +#endif + + if (!sendData("PUTSCRIPT \"" + filename.utf8() + "\" {" + + QCString().setNum( bufLen ) + "+}")) + return; + + // atEnd() lies so the code below doesn't work. + /*if (!atEnd()) { + // We are not expecting any data here, so if the server has responded + // with anything but OK we treat it as an error. + char * buf = new char[2]; + while (!atEnd()) { + ksDebug() << "Reading..." << endl; + read(buf, 1); + ksDebug() << "Trailing [" << buf[0] << "]" << endl; + } + ksDebug() << "End of data." << endl; + delete[] buf; + + if (!operationSuccessful()) { + error(ERR_UNSUPPORTED_PROTOCOL, i18n("A protocol error occurred " + "while trying to negotiate script uploading.\n" + "The server responded:\n%1") + .arg(r.getAction().right(r.getAction().length() - 3))); + return; + } + }*/ + + // upload data to the server + if (write(data, bufLen) != bufLen) { + error(ERR_COULD_NOT_WRITE, i18n("Network error.")); + disconnect(true); + return; + } + + // finishing CR/LF + if (!sendData("")) + return; + + processedSize(bufLen); + + infoMessage(i18n("Verifying upload completion...")); + + if (operationSuccessful()) + ksDebug() << "Script upload complete." << endl; + + else { + /* The managesieve server parses received scripts and rejects + * scripts which are not syntactically correct. Here we expect + * to receive a message detailing the error (only the first + * error is reported. */ + if (r.getAction().length() > 3) { + // make a copy of the extra info + QCString extra = r.getAction().right(r.getAction().length() - 3); + + // send the extra message off for re-processing + receiveData(false, &extra); + + if (r.getType() == kio_sieveResponse::QUANTITY) { + // length of the error message + uint len = r.getQuantity(); + + QCString errmsg(len + 1); + + read(errmsg.data(), len); + + error(ERR_INTERNAL_SERVER, + i18n("The script did not upload successfully.\n" + "This is probably due to errors in the script.\n" + "The server responded:\n%1").arg(errmsg)); + + // clear the rest of the incoming data + receiveData(); + } else if (r.getType() == kio_sieveResponse::KEY_VAL_PAIR) { + error(ERR_INTERNAL_SERVER, + i18n("The script did not upload successfully.\n" + "This is probably due to errors in the script.\n" + "The server responded:\n%1").arg(r.getKey())); + } else + error(ERR_INTERNAL_SERVER, + i18n("The script did not upload successfully.\n" + "The script may contain errors.")); + } else + error(ERR_INTERNAL_SERVER, + i18n("The script did not upload successfully.\n" + "The script may contain errors.")); + } + + //if ( permissions != -1 ) + // chmod( url, permissions ); + + infoMessage(i18n("Done.")); + + finished(); +} + +static void inplace_crlf2lf( QByteArray & in ) { + if ( in.isEmpty() ) + return; + QByteArray & out = in; // inplace + const char * s = in.begin(); + const char * const end = in.end(); + char * d = out.begin(); + char last = '\0'; + while ( s < end ) { + if ( *s == '\n' && last == '\r' ) + --d; + *d++ = last = *s++; + } + out.resize( d - out.begin() ); +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveProtocol::get(const KURL& url) +{ + changeCheck( url ); + if (!connect()) + return; + + infoMessage(i18n("Retrieving data...")); + + QString filename = url.fileName(false); + + if (filename.isEmpty()) { + error(ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + //SlaveBase::mimetype( QString("text/plain") ); // "application/sieve"); + + if (!sendData("GETSCRIPT \"" + filename.utf8() + "\"")) + return; + + if (receiveData() && r.getType() == kio_sieveResponse::QUANTITY) { + // determine script size + ssize_t total_len = r.getQuantity(); + totalSize( total_len ); + + int recv_len = 0; + do { + // wait for data... + if ( !waitForResponse( 600 ) ) { + error( KIO::ERR_SERVER_TIMEOUT, m_sServer ); + disconnect( true ); + return; + } + + // ...read data... + // Only read as much as we need, otherwise we slurp in the OK that + // operationSuccessful() is expecting below. + QByteArray dat( kMin( total_len - recv_len, ssize_t(64 * 1024 )) ); + ssize_t this_recv_len = read( dat.data(), dat.size() ); + + if ( this_recv_len < 1 && !isConnectionValid() ) { + error( KIO::ERR_CONNECTION_BROKEN, m_sServer ); + disconnect( true ); + return; + } + + dat.resize( this_recv_len ); + inplace_crlf2lf( dat ); + // send data to slaveinterface + data( dat ); + + recv_len += this_recv_len; + processedSize( recv_len ); + } while ( recv_len < total_len ); + + infoMessage(i18n("Finishing up...") ); + data(QByteArray()); + + if (operationSuccessful()) + ksDebug() << "Script retrieval complete." << endl; + else + ksDebug() << "Script retrieval failed." << endl; + } else { + error(ERR_UNSUPPORTED_PROTOCOL, i18n("A protocol error occurred " + "while trying to negotiate script downloading.")); + return; + } + + infoMessage(i18n("Done.")); + finished(); +} + +void kio_sieveProtocol::del(const KURL &url, bool isfile) +{ + if (!isfile) { + error(ERR_INTERNAL, i18n("Folders are not supported.")); + return; + } + + changeCheck( url ); + if (!connect()) + return; + + infoMessage(i18n("Deleting file...")); + + QString filename = url.fileName(false); + + if (filename.isEmpty()) { + error(ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + if (!sendData("DELETESCRIPT \"" + filename.utf8() + "\"")) + return; + + if (operationSuccessful()) + ksDebug() << "Script deletion successful." << endl; + else { + error(ERR_INTERNAL_SERVER, i18n("The server would not delete the file.")); + return; + } + + infoMessage(i18n("Done.")); + + finished(); +} + +void kio_sieveProtocol::chmod(const KURL& url, int permissions) +{ + switch ( permissions ) { + case 0700: // activate + activate(url); + break; + case 0600: // deactivate + deactivate(); + break; + default: // unsupported + error(ERR_CANNOT_CHMOD, i18n("Cannot chmod to anything but 0700 (active) or 0600 (inactive script).")); + return; + } + + finished(); +} + +#if defined(_AIX) && defined(stat) +#undef stat +#endif + +void kio_sieveProtocol::stat(const KURL& url) +{ + changeCheck( url ); + if (!connect()) + return; + + UDSEntry entry; + + QString filename = url.fileName(false); + + if (filename.isEmpty()) { + UDSAtom atom; + atom.m_uds = KIO::UDS_NAME; + atom.m_str = "/"; + entry.append(atom); + + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + entry.append(atom); + + atom.m_uds = KIO::UDS_ACCESS; + atom.m_long = 0700; + entry.append(atom); + + statEntry(entry); + + } else { + if (!sendData("LISTSCRIPTS")) + return; + + while(receiveData()) { + if (r.getType() == kio_sieveResponse::ACTION) { + if (r.getAction().contains("OK", false) == 1) + // Script list completed + break; + + } else + if (filename == QString::fromUtf8(r.getKey())) { + entry.clear(); + + UDSAtom atom; + atom.m_uds = KIO::UDS_NAME; + atom.m_str = QString::fromUtf8(r.getKey()); + entry.append(atom); + + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFREG; + entry.append(atom); + + atom.m_uds = KIO::UDS_ACCESS; + if ( r.getExtra() == "ACTIVE" ) + atom.m_long = 0700; // mark exec'able + else + atom.m_long = 0600; + entry.append(atom); + + atom.m_uds = KIO::UDS_MIME_TYPE; + atom.m_str = "application/sieve"; + entry.append(atom); + + //setMetaData("active", (r.getExtra() == "ACTIVE") ? "yes" : "no"); + + statEntry(entry); + // cannot break here because we need to clear + // the rest of the incoming data. + } + } + } + + finished(); +} + +void kio_sieveProtocol::listDir(const KURL& url) +{ + changeCheck( url ); + if (!connect()) + return; + + if (!sendData("LISTSCRIPTS")) + return; + + UDSEntry entry; + + while(receiveData()) { + if (r.getType() == kio_sieveResponse::ACTION) { + if (r.getAction().contains("OK", false) == 1) + // Script list completed. + break; + + } else { + entry.clear(); + + UDSAtom atom; + atom.m_uds = KIO::UDS_NAME; + atom.m_str = QString::fromUtf8(r.getKey()); + entry.append(atom); + + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFREG; + entry.append(atom); + + atom.m_uds = KIO::UDS_ACCESS; + if ( r.getExtra() == "ACTIVE" ) + atom.m_long = 0700; // mark exec'able + else + atom.m_long = 0600; + entry.append(atom); + + atom.m_uds = KIO::UDS_MIME_TYPE; + atom.m_str = "application/sieve"; + entry.append(atom); + + //asetMetaData("active", (r.getExtra() == "ACTIVE") ? "true" : "false"); + + ksDebug() << "Listing script " << r.getKey() << endl; + listEntry(entry , false); + } + } + + listEntry(entry, true); + + finished(); +} + +/* ---------------------------------------------------------------------------------- */ +bool kio_sieveProtocol::saslInteract( void *in, AuthInfo &ai ) +{ + ksDebug() << "sasl_interact" << endl; + sasl_interact_t *interact = ( sasl_interact_t * ) in; + + //some mechanisms do not require username && pass, so it doesn't need a popup + //window for getting this info + for ( ; interact->id != SASL_CB_LIST_END; interact++ ) { + if ( interact->id == SASL_CB_AUTHNAME || + interact->id == SASL_CB_PASS ) { + + if (m_sUser.isEmpty() || m_sPass.isEmpty()) { + if (!openPassDlg(ai)) { + error(ERR_ABORTED, i18n("No authentication details supplied.")); + return false; + } + m_sUser = ai.username; + m_sPass = ai.password; + } + break; + } + } + + interact = ( sasl_interact_t * ) in; + while( interact->id != SASL_CB_LIST_END ) { + ksDebug() << "SASL_INTERACT id: " << interact->id << endl; + switch( interact->id ) { + case SASL_CB_USER: + case SASL_CB_AUTHNAME: + ksDebug() << "SASL_CB_[AUTHNAME|USER]: '" << m_sUser << "'" << endl; + interact->result = strdup( m_sUser.utf8() ); + interact->len = strlen( (const char *) interact->result ); + break; + case SASL_CB_PASS: + ksDebug() << "SASL_CB_PASS: [hidden] " << endl; + interact->result = strdup( m_sPass.utf8() ); + interact->len = strlen( (const char *) interact->result ); + break; + default: + interact->result = NULL; interact->len = 0; + break; + } + interact++; + } + return true; +} + +#define SASLERROR error(ERR_COULD_NOT_AUTHENTICATE, i18n("An error occurred during authentication: %1").arg( \ + QString::fromUtf8( sasl_errdetail( conn ) ))); + +bool kio_sieveProtocol::authenticate() +{ + int result; + sasl_conn_t *conn = NULL; + sasl_interact_t *client_interact = NULL; + const char *out = NULL; + uint outlen; + const char *mechusing = NULL; + QByteArray challenge, tmp; + + /* Retrieve authentication details from user. + * Note: should this require realm as well as user & pass details + * before it automatically skips the prompt? + * Note2: encoding issues with PLAIN login? */ + AuthInfo ai; + ai.url.setProtocol("sieve"); + ai.url.setHost(m_sServer); + ai.url.setPort(m_iPort); + ai.username = m_sUser; + ai.password = m_sPass; + ai.keepPassword = true; + ai.caption = i18n("Sieve Authentication Details"); + ai.comment = i18n("Please enter your authentication details for your sieve account " + "(usually the same as your email password):"); + + result = sasl_client_new( "sieve", + m_sServer.latin1(), + 0, 0, callbacks, 0, &conn ); + + if ( result != SASL_OK ) { + ksDebug() << "sasl_client_new failed with: " << result << endl; + SASLERROR + return false; + } + + QStringList strList; +// strList.append("NTLM"); + + if ( !m_sAuth.isEmpty() ) + strList.append( m_sAuth ); + else + strList = m_sasl_caps; + + do { + result = sasl_client_start(conn, strList.join(" ").latin1(), &client_interact, + &out, &outlen, &mechusing); + + if (result == SASL_INTERACT) + if ( !saslInteract( client_interact, ai ) ) { + sasl_dispose( &conn ); + return false; + }; + } while ( result == SASL_INTERACT ); + + if ( result != SASL_CONTINUE && result != SASL_OK ) { + ksDebug() << "sasl_client_start failed with: " << result << endl; + SASLERROR + sasl_dispose( &conn ); + return false; + } + + ksDebug() << "Preferred authentication method is " << mechusing << "." << endl; + + QString firstCommand = "AUTHENTICATE \"" + QString::fromLatin1( mechusing ) + "\""; + tmp.setRawData( out, outlen ); + KCodecs::base64Encode( tmp, challenge ); + tmp.resetRawData( out, outlen ); + if ( !challenge.isEmpty() ) { + firstCommand += " \""; + firstCommand += QString::fromLatin1( challenge.data(), challenge.size() ); + firstCommand += "\""; + } + + if (!sendData( firstCommand.latin1() )) + return false; + + QCString command; + + do { + receiveData(); + + if (operationResult() != OTHER) + break; + + ksDebug() << "Challenge len " << r.getQuantity() << endl; + + if (r.getType() != kio_sieveResponse::QUANTITY) { + sasl_dispose( &conn ); + error(ERR_SLAVE_DEFINED, + i18n("A protocol error occurred during authentication.\n" + "Choose a different authentication method to %1.").arg(mechusing)); + return false; + } + + uint qty = r.getQuantity(); + + receiveData(); + + if (r.getType() != kio_sieveResponse::ACTION && r.getAction().length() != qty) { + sasl_dispose( &conn ); + error(ERR_UNSUPPORTED_PROTOCOL, + i18n("A protocol error occurred during authentication.\n" + "Choose a different authentication method to %1.").arg(mechusing)); + return false; + } + + tmp.setRawData( r.getAction().data(), qty ); + KCodecs::base64Decode( tmp, challenge ); + tmp.resetRawData( r.getAction().data(), qty ); +// ksDebug() << "S: [" << r.getAction() << "]." << endl; +// ksDebug() << "S-1: [" << QCString(challenge.data(), challenge.size()+1) << "]." << endl; + + do { + result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(), + challenge.size(), + &client_interact, + &out, &outlen); + + if (result == SASL_INTERACT) + if ( !saslInteract( client_interact, ai ) ) { + sasl_dispose( &conn ); + return false; + }; + } while ( result == SASL_INTERACT ); + + ksDebug() << "sasl_client_step: " << result << endl; + if ( result != SASL_CONTINUE && result != SASL_OK ) { + ksDebug() << "sasl_client_step failed with: " << result << endl; + SASLERROR + sasl_dispose( &conn ); + return false; + } + + tmp.setRawData( out, outlen ); + KCodecs::base64Encode( tmp, challenge ); + tmp.resetRawData( out, outlen ); + sendData("\"" + QCString( challenge.data(), challenge.size()+1 ) + "\""); +// ksDebug() << "C: [" << QCString(challenge.data(), challenge.size()+1) << "]." << endl; +// ksDebug() << "C-1: [" << out << "]." << endl; + } while ( true ); + + ksDebug() << "Challenges finished." << endl; + sasl_dispose( &conn ); + + if (operationResult() == OK) { + // Authentication succeeded. + return true; + } else { + // Authentication failed. + error(ERR_COULD_NOT_AUTHENTICATE, i18n("Authentication failed.\nMost likely the password is wrong.\nThe server responded:\n%1").arg( r.getAction() ) ); + return false; + } +} + +/* --------------------------------------------------------------------------- */ +void kio_sieveProtocol::mimetype(const KURL & url) +{ + ksDebug() << "Requesting mimetype for " << url.prettyURL() << endl; + + if (url.fileName(false).isEmpty()) + mimeType( "inode/directory" ); + else + mimeType( "application/sieve" ); + + finished(); +} + + +/* --------------------------------------------------------------------------- */ +bool kio_sieveProtocol::sendData(const QCString &data) +{ + QCString write_buf = data + "\r\n"; + + //ksDebug() << "C: " << data << endl; + + // Write the command + ssize_t write_buf_len = write_buf.length(); + if (write(write_buf.data(), write_buf_len) != write_buf_len) { + error(ERR_COULD_NOT_WRITE, i18n("Network error.")); + disconnect(true); + return false; + } + + return true; +} + +/* --------------------------------------------------------------------------- */ +bool kio_sieveProtocol::receiveData(bool waitForData, QCString *reparse) +{ + QCString interpret; + int start, end; + + if (!reparse) { + if (!waitForData) + // is there data waiting? + if (atEnd()) return false; + + // read data from the server + char buffer[SIEVE_DEFAULT_RECIEVE_BUFFER]; + readLine(buffer, SIEVE_DEFAULT_RECIEVE_BUFFER - 1); + buffer[SIEVE_DEFAULT_RECIEVE_BUFFER-1] = '\0'; + + // strip LF/CR + interpret = QCString(buffer).left(qstrlen(buffer) - 2); + + } else { + interpret = reparse->copy(); + } + + r.clear(); + + //ksDebug() << "S: " << interpret << endl; + + switch(interpret[0]) { + case '{': + { + // expecting {quantity} + start = 0; + end = interpret.find("+}", start + 1); + // some older versions of Cyrus enclose the literal size just in { } instead of { +} + if ( end == -1 ) + end = interpret.find('}', start + 1); + + bool ok = false; + r.setQuantity(interpret.mid(start + 1, end - start - 1).toUInt( &ok )); + if (!ok) { + disconnect(); + error(ERR_INTERNAL_SERVER, i18n("A protocol error occurred.")); + return false; + } + + return true; + } + case '"': + // expecting "key" "value" pairs + break; + default: + // expecting single string + r.setAction(interpret); + return true; + } + + start = 0; + + end = interpret.find(34, start + 1); + if (end == -1) { + ksDebug() << "Possible insufficient buffer size." << endl; + r.setKey(interpret.right(interpret.length() - start)); + return true; + } + + r.setKey(interpret.mid(start + 1, end - start - 1)); + + start = interpret.find(34, end + 1); + if (start == -1) { + if ((int)interpret.length() > end) + // skip " and space + r.setExtra(interpret.right(interpret.length() - end - 2)); + + return true; + } + + end = interpret.find(34, start + 1); + if (end == -1) { + ksDebug() << "Possible insufficient buffer size." << endl; + r.setVal(interpret.right(interpret.length() - start)); + return true; + } + + r.setVal(interpret.mid(start + 1, end - start - 1)); + return true; +} + +bool kio_sieveProtocol::operationSuccessful() +{ + while (receiveData(false)) { + if (r.getType() == kio_sieveResponse::ACTION) { + QCString response = r.getAction().left(2); + if (response == "OK") { + return true; + } else if (response == "NO") { + return false; + } + } + } + return false; +} + +int kio_sieveProtocol::operationResult() +{ + if (r.getType() == kio_sieveResponse::ACTION) { + QCString response = r.getAction().left(2); + if (response == "OK") { + return OK; + } else if (response == "NO") { + return NO; + } else if (response == "BY"/*E*/) { + return BYE; + } + } + + return OTHER; +} + +bool kio_sieveProtocol::requestCapabilitiesAfterStartTLS() const +{ + // Cyrus didn't send CAPABILITIES after STARTTLS until 2.3.11, which is + // not standard conform, but we need to support that anyway. + // m_implementation looks like this 'Cyrus timsieved v2.2.12' for Cyrus btw. + QRegExp regExp( "Cyrus\\stimsieved\\sv(\\d+)\\.(\\d+)\\.(\\d+)([-\\w]*)", false ); + if ( regExp.search( m_implementation ) >= 0 ) { + const int major = regExp.cap( 1 ).toInt(); + const int minor = regExp.cap( 2 ).toInt(); + const int patch = regExp.cap( 3 ).toInt(); + const QString vendor = regExp.cap( 4 ); + if ( major < 2 || (major == 2 && (minor < 3 || (minor == 3 && patch < 11))) || (vendor == "-kolab-nocaps") ) { + ksDebug() << k_funcinfo << "Enabling compat mode for Cyrus < 2.3.11 or Cyrus marked as \"kolab-nocaps\"" << endl; + return true; + } + } + return false; +} diff --git a/kioslaves/sieve/sieve.h b/kioslaves/sieve/sieve.h new file mode 100644 index 000000000..1e064bbf6 --- /dev/null +++ b/kioslaves/sieve/sieve.h @@ -0,0 +1,132 @@ +/*************************************************************************** + sieve.h - description + ------------------- + begin : Thu Dec 20 18:47:08 EST 2001 + copyright : (C) 2001 by Hamish Rodda + email : meddie@yoyo.cc.monash.edu.au + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + ***************************************************************************/ +#ifndef __sieve_h__ +#define __sieve_h__ + +#include <kio/tcpslavebase.h> +#include <kio/authinfo.h> + +#include <qstring.h> +#include <qcstring.h> +#include <qstringlist.h> + +class KDESasl; +class KURL; + + +class kio_sieveResponse +{ +public: + enum responses { NONE, KEY_VAL_PAIR, ACTION, QUANTITY }; + + kio_sieveResponse(); + + const uint& getType() const; + + const QCString& getAction() const; + const uint getQuantity() const; + const QCString& getKey() const; + const QCString& getVal() const; + const QCString& getExtra() const; + + void setQuantity(const uint& quantity); + void setAction(const QCString& newAction); + void setKey(const QCString& newKey); + void setVal(const QCString& newVal); + void setExtra(const QCString& newExtra); + + void clear(); + +protected: + uint rType; + uint quantity; + QCString key; + QCString val; + QCString extra; +}; + +class kio_sieveProtocol : public KIO::TCPSlaveBase +{ + +public: + enum connectionModes { NORMAL, CONNECTION_ORIENTED }; + enum Results { OK, NO, BYE, OTHER }; + + kio_sieveProtocol(const QCString &pool_socket, const QCString &app_socket); + virtual ~kio_sieveProtocol(); + + virtual void mimetype(const KURL& url); + virtual void get(const KURL& url); + virtual void put(const KURL& url, int permissions, bool overwrite, bool resume); + virtual void del(const KURL &url, bool isfile); + + virtual void listDir(const KURL& url); + virtual void chmod(const KURL& url, int permissions); + virtual void stat(const KURL& url); + + virtual void setHost(const QString &host, int port, const QString &user, const QString &pass); + virtual void openConnection(); + virtual void closeConnection(); + //virtual void slave_status(); + + /** + * Special commands supported by this slave: + * 1 - activate script + * 2 - deactivate (all - only one active at any one time) scripts + * 3 - request capabilities, returned as metadata + */ + virtual void special(const QByteArray &data); + bool activate(const KURL& url); + bool deactivate(); + +protected: + bool connect(bool useTLSIfAvailable = true); + bool authenticate(); + void disconnect(bool forcibly = false); + void changeCheck( const KURL &url ); + + bool sendData(const QCString &data); + bool receiveData(bool waitForData = true, QCString *reparse = 0); + bool operationSuccessful(); + int operationResult(); + + bool parseCapabilities(bool requestCapabilities = false); + bool saslInteract( void *in, KIO::AuthInfo &ai ); + + // IOSlave global data + uint m_connMode; + + // Host-specific data + QStringList m_sasl_caps; + bool m_supportsTLS; + + // Global server respose class + kio_sieveResponse r; + + // connection details + QString m_sServer; + QString m_sUser; + QString m_sPass; + QString m_sAuth; + bool m_shouldBeConnected; + +private: + bool requestCapabilitiesAfterStartTLS() const; + + QString m_implementation; +}; + +#endif diff --git a/kioslaves/sieve/sieve.protocol b/kioslaves/sieve/sieve.protocol new file mode 100644 index 000000000..3bd00e25e --- /dev/null +++ b/kioslaves/sieve/sieve.protocol @@ -0,0 +1,54 @@ +[Protocol] +exec=kio_sieve +protocol=sieve +input=none +output=filesystem +listing=Name,Access,Type,MimeType, +reading=true +writing=true +makedir=false +deleting=true +linking=false +moving=false +Icon=remote +Description=An ioslave for the Sieve mail filtering protocol +Description[af]='n <i>IOSlave</i> vir die Sieve e-pos filter protokol +Description[ca]=Un ioslave pel protocol de filtrar de correu Sieve +Description[cs]=ioslave pro protokol filtrování zpráv Sieve +Description[da]=En ioslave for Sieve mail filtreringsprotokollen +Description[de]=Ein-/Ausgabemodul für das E-Mail-Filterprotokoll "Sieve" +Description[el]=Ένας ioslave για το πρωτόκολλο φιλτραρίσματος αλληλογραφίας Sieve +Description[es]=Un ioslave para el protocolo de filtrado de correo Sieve +Description[et]=Sieve e-kirjade filtreerimise protokolli IO-moodul +Description[eu]=Sieve posta iragazketa protokoloarentztko irteerako/sarrerako mendeko bat +Description[fa]=یک ioslave برای قرارداد پالایش نامۀ Sieve +Description[fi]=Siirräntätyöskentelijä Sieve-sähköpostiensuodatusyhteyskäytännölle +Description[fr]=Un module d'entrées / sorties pour le protocole de filtrage de messagerie Sieve +Description[fy]=In ioslave foar it Sieve-mailfilterprotokol +Description[gl]=Un esclavo io para o protocolo de filtraxe de correo Sieve +Description[hu]=KDE-protokoll a Sieve levélszűrő protokollhoz +Description[is]=Ioslave fyrir Sieve tölvupóstsíu samskiptaregluna +Description[it]=Un ioslave per il protocollo di filtraggio posta Sieve +Description[ja]=Sieve メールフィルタプロトコル用 ioslave +Description[ka]=Sieve ფოსტის ფილტრის შეტანა-გამოტანის განაწესი +Description[kk]=Sieve поштаны сүзгілеу протоколының ioslave модулі +Description[km]=ioslave សម្រាប់ពិធីការត្រងសំបុត្រ Sieve +Description[ms]=Hamba io untuk protokol tapisan mel saringan +Description[nb]=En i/u-slave for e-postfilterprotokollen Sieve +Description[nds]=In-/Utgaavmoduul för't Nettpostfilter-Protokoll Sieve +Description[ne]=मेल फिल्टरिङ प्रोटोकल सिभ गर्नका लागि एउटा आइओस्लेभ +Description[nl]=Een ioslave voor het Sieve-mailfilterprotocol +Description[nn]=Ein i/u-slave for e-postfilterprotokollen Sieve +Description[pl]=Wtyczka protokołu filtrowania poczty Sieve +Description[pt]=Um 'ioslave' para o protocolo de filtragem de correio Sieve +Description[pt_BR]=Um IO-Slave para o protocolo de filtragem de email Sieve +Description[ru]=Канал протокола фильтра почты Sieve +Description[sk]=ioslave pre protokol Sieve filtrovanie pošty +Description[sl]=ioslave za protokol poštnega filtriranja Sieve +Description[sr]=IOslave протокола за филтрирање поште Sieve +Description[sr@Latn]=IOslave protokola za filtriranje pošte Sieve +Description[sv]=En I/O-slav för brevfiltreringsprotokollet Sieve +Description[ta]=Sieve அஞ்சல் வடிகட்டும் நெறிமுறைக்கான ஒரு ioslave +Description[tr]=Sieve e-posta filtreleme protokolü için bir ioslave +Description[uk]=Підлеглий В/В для протоколу фільтрування пошти Sieve +Description[zh_CN]=邮件过滤协议仆人 |