diff --git a/kioslave/imap4/CMakeLists.txt b/kioslave/imap4/CMakeLists.txt index c96d324d7..d098bdfcb 100644 --- a/kioslave/imap4/CMakeLists.txt +++ b/kioslave/imap4/CMakeLists.txt @@ -1,43 +1,44 @@ set(imap4_optional_includes) set(imap4_optional_libs) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") if (SASL2_FOUND) set(imap4_optional_includes ${imap4_optional_includes} ${SASL2_INCLUDE_DIR}) set(imap4_optional_libs ${imap4_optional_libs} ${SASL2_LIBRARIES}) set(HAVE_LIBSASL2 1) else (SASL2_FOUND) set(HAVE_LIBSASL2 0) endif (SASL2_FOUND) configure_file(imap4-config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/imap4-config.h) -include_directories(${imap4_optional_includes}) +include_directories(${imap4_optional_includes} ${Boost_INCLUDE_DIR}) ########### next target ############### set(kio_imap4_PART_SRCS imapcommand.cpp imaplist.cpp mailaddress.cpp mimeheader.cpp imap4.cpp imapinfo.cpp imapparser.cpp mailheader.cpp mimehdrline.cpp mimeio.cpp) kde4_add_plugin(kio_imap4 ${kio_imap4_PART_SRCS}) target_link_libraries(kio_imap4 kmime kimap ${KDE4_KIO_LIBS}) if (SASL2_FOUND) target_link_libraries(kio_imap4 ${SASL2_LIBRARIES} ) endif(SASL2_FOUND) install(TARGETS kio_imap4 DESTINATION ${PLUGIN_INSTALL_DIR}) ########### install files ############### install(FILES imap4.protocol imaps.protocol DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/kioslave/imap4/imap4.cpp b/kioslave/imap4/imap4.cpp index 6618f18ac..7e09f7e78 100644 --- a/kioslave/imap4/imap4.cpp +++ b/kioslave/imap4/imap4.cpp @@ -1,2658 +1,2652 @@ /********************************************************************** * * imap4.cc - IMAP4rev1 KIOSlave * Copyright (C) 2001-2002 Michael Haeckel * 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. */ #include // for KDE_signal, remove in KDEPIM 4.2 #include "imap4.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBSASL2 extern "C" { #include } #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "kdemacros.h" #define IMAP_PROTOCOL "imap" #define IMAP_SSL_PROTOCOL "imaps" const int ImapPort = 143; const int ImapsPort = 993; using namespace KIO; extern "C" { void sigalrm_handler (int); KDE_EXPORT int kdemain (int argc, char **argv); } int kdemain (int argc, char **argv) { kDebug(7116) <<"IMAP4::kdemain"; KComponentData instance ("kio_imap4"); if (argc != 4) { fprintf(stderr, "Usage: kio_imap4 protocol domain-socket1 domain-socket2\n"); ::exit (-1); } #ifdef HAVE_LIBSASL2 if (!initSASL()) ::exit(-1); #endif //set debug handler IMAP4Protocol *slave; if (strcasecmp (argv[1], IMAP_SSL_PROTOCOL) == 0) slave = new IMAP4Protocol (argv[2], argv[3], true); else if (strcasecmp (argv[1], IMAP_PROTOCOL) == 0) slave = new IMAP4Protocol (argv[2], argv[3], false); else abort (); slave->dispatchLoop (); delete slave; #ifdef HAVE_LIBSASL2 sasl_done(); #endif return 0; } void sigchld_handler (int signo) { // A signal handler that calls for example waitpid has to save errno // before and restore it afterwards. // (cf. https://www.securecoding.cert.org/confluence/display/cplusplus/ERR32-CPP.+Do+not+rely+on+indeterminate+values+of+errno) const int save_errno = errno; int pid, status; while (signo == SIGCHLD) { pid = waitpid (-1, &status, WNOHANG); if (pid <= 0) { // Reinstall signal handler, since Linux resets to default after // the signal occurred ( BSD handles it different, but it should do // no harm ). KDE_signal (SIGCHLD, sigchld_handler); break; } } errno = save_errno; } IMAP4Protocol::IMAP4Protocol (const QByteArray & pool, const QByteArray & app, bool isSSL) :TCPSlaveBase ((isSSL ? IMAP_SSL_PROTOCOL : IMAP_PROTOCOL), pool, app, isSSL), imapParser (), mimeIO (), mySSL( isSSL ), relayEnabled( false ), cacheOutput( false ), decodeContent( false ), outputBuffer(&outputCache), outputBufferIndex(0), mProcessedSize( 0 ), readBufferLen( 0 ), mTimeOfLastNoop( QDateTime() ) { readBuffer[0] = 0x00; } IMAP4Protocol::~IMAP4Protocol () { disconnectFromHost(); kDebug(7116) <<"IMAP4: Finishing"; } void IMAP4Protocol::get (const KUrl & _url) { if (!makeLogin()) return; kDebug(7116) <<"IMAP4::get -" << _url.prettyUrl(); 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()); + CommandPtr cmd = doCommand (imapCommand::clientNoop()); completeQueue.removeAll(cmd); } if (aSequence.isEmpty ()) { aSequence = "1:*"; } mProcessedSize = 0; - imapCommand *cmd = NULL; + CommandPtr cmd; 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.toUpper(); if (aUpper.contains("STRUCTURE")) { aSection = "BODYSTRUCTURE"; } else if (aUpper.contains("ENVELOPE")) { 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.contains("BODY.PEEK[")) { if (aUpper.contains("BODY.PEEK[]")) { 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()); 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.removeAll (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.toUpper(); 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.contains("BODYSTRUCTURE") || aUpper.contains("FLAGS") || aUpper.contains("UID") || aUpper.contains("ENVELOPE") || (aUpper.contains("BODY.PEEK[0]") && (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->getUid () != 0) outputLineStr ("X-UID: " + QString::number(cache->getUid ()) + "\r\n"); if (cache->getSize () != 0) outputLineStr ("X-Length: " + QString::number(cache->getSize ()) + "\r\n"); if (!cache->getDate ().isEmpty()) outputLineStr ("X-Date: " + cache->getDate () + "\r\n"); if (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.removeAll (cmd); } } // just to keep everybody happy when no data arrived data (QByteArray ()); finished (); relayEnabled = false; cacheOutput = false; kDebug(7116) <<"IMAP4::get - finished"; } void IMAP4Protocol::listDir (const KUrl & _url) { kDebug(7116) <<" IMAP4::listDir -" << _url.prettyUrl(); 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; + CommandPtr cmd; if (!listStr.isEmpty () && !listStr.endsWith(myDelimiter) && mySection != "FOLDERONLY") listStr += myDelimiter; if (mySection.isEmpty()) { listStr += '%'; } else if (mySection == "COMPLETE") { listStr += '*'; } kDebug(7116) <<"IMAP4Protocol::listDir - listStr=" << listStr; cmd = doCommand (imapCommand::clientList ("", listStr, (myLType == "LSUB" || myLType == "LSUBNOCHECK"))); if (cmd->result () == "OK") { QString mailboxName; UDSEntry entry; KUrl aURL = _url; if ( aURL.path().contains(';') ) aURL.setPath(aURL.path().left(aURL.path().indexOf(';'))); kDebug(7116) <<"IMAP4Protocol::listDir - got" << listResponses.count (); if (myLType == "LSUB") { // fire the same command as LIST to check if the box really exists QList listResponsesSave = listResponses; doCommand (imapCommand::clientList ("", listStr, false)); for (QList< imapList >::Iterator it = listResponsesSave.begin (); it != listResponsesSave.end (); ++it) { bool boxOk = false; for (QList< imapList >::Iterator 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 kDebug(7116) <<"IMAP4Protocol::listDir - suppress" << (*it).name(); } listResponses = listResponsesSave; } else // LIST or LSUBNOCHECK { for (QList< imapList >::Iterator 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.removeAll (cmd); return; } completeQueue.removeAll (cmd); } if ((myType == ITYPE_BOX || myType == ITYPE_DIR_AND_BOX) && myLType != "LIST" && myLType != "LSUB" && myLType != "LSUBNOCHECK") { KUrl aURL = _url; aURL.setQuery (QString()); const QString encodedUrl = aURL.url(KUrl::LeaveTrailingSlash); // utf-8 if (!_url.query ().isEmpty ()) { QString query = KUrl::fromPercentEncoding (_url.query().toLatin1()); query = query.right (query.length () - 1); if (!query.isEmpty()) { - imapCommand *cmd = NULL; + CommandPtr cmd; 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.removeAll (cmd); return; } completeQueue.removeAll (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.constBegin(); it != list.constEnd(); ++it) { fake.setUid((*it).toULong()); doListEntry (encodedUrl, stretch, &fake); } entry.clear (); listEntry (entry, true); } } } else { if (!assureBox (myBox, true)) return; kDebug(7116) <<"IMAP4: select returned:"; if (selectInfo.recentAvailable ()) kDebug(7116) <<"Recent:" << selectInfo.recent () <<"d"; if (selectInfo.countAvailable ()) kDebug(7116) <<"Count:" << selectInfo.count () <<"d"; if (selectInfo.unseenAvailable ()) kDebug(7116) <<"Unseen:" << selectInfo.unseen () <<"d"; if (selectInfo.uidValidityAvailable ()) kDebug(7116) <<"uidValidity:" << selectInfo.uidValidity () <<"d"; if (selectInfo.flagsAvailable ()) kDebug(7116) <<"Flags:" << selectInfo.flags () <<"d"; if (selectInfo.permanentFlagsAvailable ()) kDebug(7116) <<"PermanentFlags:" << selectInfo.permanentFlags () <<"d"; if (selectInfo.readWriteAvailable ()) kDebug(7116) <<"Access:" << (selectInfo.readWrite ()?"Read/Write" :"Read only"); #ifdef USE_VALIDITY if (selectInfo.uidValidityAvailable () && selectInfo.uidValidity () != myValidity.toULong ()) { //redirect KUrl newUrl = _url; newUrl.setPath ('/' + myBox + ";UIDVALIDITY=" + QString::number(selectInfo.uidValidity ())); kDebug(7116) <<"IMAP4::listDir - redirecting to" << newUrl.prettyUrl(); redirection (newUrl); } else #endif if (selectInfo.count () > 0) { int stretch = 0; if (selectInfo.uidNextAvailable ()) stretch = QString::number(selectInfo.uidNext ()).length (); // kDebug(7116) << selectInfo.uidNext() <<"d used to stretch" << stretch; UDSEntry entry; if (mySequence.isEmpty()) mySequence = "1:*"; bool withSubject = mySection.isEmpty(); if (mySection.isEmpty()) mySection = "UID RFC822.SIZE ENVELOPE"; bool withFlags = mySection.toUpper().contains("FLAGS") ; - imapCommand *fetch = + CommandPtr 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", myHost, myBox, selectInfo.alert() ) ); } else { warning( i18n( "Message from %1: %2", myHost, selectInfo.alert() ) ); } selectInfo.setAlert( 0 ); } kDebug(7116) <<"IMAP4Protocol::listDir - Finishing listDir"; finished (); } void IMAP4Protocol::setHost (const QString & _host, quint16 _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; if (_port == 0) myPort = (mySSL) ? ImapsPort : ImapPort; else 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(QIODevice::WriteOnly); } outputBuffer.seek( outputBufferIndex ); outputBuffer.write(buffer, buffer.size()); outputBufferIndex += buffer.size(); } } void IMAP4Protocol::parseRelay (ulong len) { if (relayEnabled) totalSize (len); } bool IMAP4Protocol::parseRead(QByteArray & buffer, long len, long relay) { const long int bufLen = 8192; char buf[bufLen]; // FIXME while (buffer.size() < len ) { ssize_t readLen = myRead(buf, qMin(len - buffer.size(), bufLen - 1)); if (readLen == 0) { kDebug(7116) <<"parseRead: readLen == 0 - connection broken"; 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 = QByteArray::fromRawData(buf, currentRelay); parseRelay(relayData); relayData.clear(); } { QBuffer stream( &buffer ); stream.open (QIODevice::WriteOnly); stream.seek (buffer.size ()); stream.write (buf, readLen); stream.close (); } } return (buffer.size() == len); } bool IMAP4Protocol::parseReadLine (QByteArray & buffer, long 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 = QByteArray::fromRawData (readBuffer, relay); parseRelay (relayData); relayData.clear(); // kDebug(7116) <<"relayed :" << relay <<"d"; } // append to buffer { int oldsize = buffer.size(); buffer.resize(oldsize + copyLen); memcpy(buffer.data() + oldsize, readBuffer, copyLen); // kDebug(7116) <<"appended" << copyLen <<"d got now" << buffer.size(); } readBufferLen -= copyLen; if (readBufferLen) memmove(readBuffer, &readBuffer[copyLen], readBufferLen); if (buffer[buffer.size() - 1] == '\n') return true; } if (!isConnected()) { kDebug(7116) <<"parseReadLine - connection broken"; 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) { kDebug(7116) <<"parseReadLine: readBufferLen == 0 - connection broken"; error (ERR_CONNECTION_BROKEN, myHost); setState(ISTATE_CONNECT); closeConnection(); return false; } } } void IMAP4Protocol::setSubURL (const KUrl & _url) { kDebug(7116) <<"IMAP4::setSubURL -" << _url.prettyUrl(); KIO::TCPSlaveBase::setSubUrl (_url); } void IMAP4Protocol::put (const KUrl & _url, int, KIO::JobFlags) { kDebug(7116) <<"IMAP4::put -" << _url.prettyUrl(); // KIO::TCPSlaveBase::put(_url,permissions,flags) 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)); + CommandPtr cmd = doCommand (imapCommand::clientCreate (aBox)); if (cmd->result () != "OK") { error (ERR_COULD_NOT_WRITE, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } else { QList < 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 = + CommandPtr 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; QListIterator it(bufferList); // send data to server while (it.hasNext() && sendOk) { buffer = it.next(); 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.removeAll (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.removeAll (cmd); closeConnection(); return; } else if (cmd->result () != "OK") { error( ERR_SLAVE_DEFINED, cmd->resultInfo() ); completeQueue.removeAll (cmd); return; } else { if (hasCapability("UIDPLUS")) { QString uid = cmd->resultInfo(); if ( uid.contains("APPENDUID") ) { 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.removeAll (cmd); } } } else { //error (ERR_COULD_NOT_WRITE, myHost); // Better ship the error message, e.g. "Over Quota" error (ERR_SLAVE_DEFINED, cmd->resultInfo()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } finished (); } void IMAP4Protocol::mkdir (const KUrl & _url, int) { kDebug(7116) <<"IMAP4::mkdir -" << _url.prettyUrl(); QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL(_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); kDebug(7116) <<"IMAP4::mkdir - create" << aBox; - imapCommand *cmd = doCommand (imapCommand::clientCreate(aBox)); + CommandPtr cmd = doCommand (imapCommand::clientCreate(aBox)); if (cmd->result () != "OK") { kDebug(7116) <<"IMAP4::mkdir -" << cmd->resultInfo(); error (ERR_COULD_NOT_MKDIR, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (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.contains( "ASKUSER" ) ); if ( ask && messageBox(QuestionYesNo, i18n("The following folder will be created on the server: %1 " "What do you want to store in this folder?", aBox ), i18n("Create Folder"), i18n("&Messages"), i18n("&Subfolders")) == KMessageBox::No ) { cmd = doCommand(imapCommand::clientDelete(aBox)); completeQueue.removeAll (cmd); cmd = doCommand(imapCommand::clientCreate(aBox + aDelimiter)); if (cmd->result () != "OK") { error (ERR_COULD_NOT_MKDIR, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } } cmd = doCommand(imapCommand::clientSubscribe(aBox)); completeQueue.removeAll(cmd); finished (); } void IMAP4Protocol::copy (const KUrl & src, const KUrl & dest, int, KIO::JobFlags flags) { kDebug(7116) <<"IMAP4::copy - [" << ((flags & KIO::Overwrite) ?"Overwrite" :"NoOverwrite") <<"]" << src.prettyUrl() <<" ->" << dest.prettyUrl(); 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.indexOf (sBox); // might be moving to upper folder if (sub > 0) { KUrl testDir = dest; QString subDir = dBox.right (dBox.length () - dBox.lastIndexOf ('/')); QString topDir = dBox.left (sub); testDir.setPath ('/' + topDir); dType = parseURL (testDir, topDir, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo); kDebug(7116) <<"IMAP4::copy - checking this destination" << topDir; // see if this is what the user wants if (dType == ITYPE_BOX || dType == ITYPE_DIR_AND_BOX) { kDebug(7116) <<"IMAP4::copy - assuming this destination" << topDir; dBox = topDir; } else { // maybe if we create a new mailbox topDir = '/' + topDir + subDir; testDir.setPath (topDir); kDebug(7116) <<"IMAP4::copy - checking this destination" << topDir; 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)); + CommandPtr 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") { kDebug(7116) <<"IMAP4::copy - assuming this destination" << topDir; dType = ITYPE_BOX; dBox = topDir; } else { completeQueue.removeAll (cmd); cmd = doCommand (imapCommand::clientCreate (dBox)); if (cmd->result () == "OK") dType = ITYPE_BOX; else error (ERR_COULD_NOT_WRITE, dest.prettyUrl()); } completeQueue.removeAll (cmd); } } } } if (sType == ITYPE_MSG || sType == ITYPE_BOX || sType == ITYPE_DIR_AND_BOX) { //select the source box if (!assureBox(sBox, true)) return; kDebug(7116) <<"IMAP4::copy -" << sBox <<" ->" << dBox; //issue copy command - imapCommand *cmd = + CommandPtr cmd = doCommand (imapCommand::clientCopy (dBox, sSequence)); if (cmd->result () != "OK") { kError(5006) <<"IMAP4::copy -" << cmd->resultInfo(); error (ERR_COULD_NOT_WRITE, dest.prettyUrl()); completeQueue.removeAll (cmd); return; } else { if (hasCapability("UIDPLUS")) { QString uid = cmd->resultInfo(); if ( uid.contains("COPYUID") ) { uid = uid.section(" ", 2, 3); uid.truncate(uid.length()-1); infoMessage("UID "+uid); } } } completeQueue.removeAll (cmd); } else { error (ERR_ACCESS_DENIED, src.prettyUrl()); return; } finished (); } void IMAP4Protocol::del (const KUrl & _url, bool isFile) { kDebug(7116) <<"IMAP4::del - [" << (isFile ?"File" :"NoFile") <<"]" << _url.prettyUrl(); 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 ()); + CommandPtr cmd = doCommand (imapCommand::clientExpunge ()); if (cmd->result () != "OK") { error (ERR_CANNOT_DELETE, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } else { // if open for read/write if (!assureBox (aBox, false)) return; - imapCommand *cmd = + CommandPtr cmd = doCommand (imapCommand:: clientStore (aSequence, "+FLAGS.SILENT", "\\DELETED")); if (cmd->result () != "OK") { error (ERR_CANNOT_DELETE, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } } else { if (getCurrentBox() == aBox) { - imapCommand *cmd = doCommand(imapCommand::clientClose()); + CommandPtr cmd = doCommand(imapCommand::clientClose()); completeQueue.removeAll(cmd); setState(ISTATE_LOGIN); } // We unsubscribe, otherwise we get ghost folders on UW-IMAP - imapCommand *cmd = doCommand(imapCommand::clientUnsubscribe(aBox)); + CommandPtr cmd = doCommand(imapCommand::clientUnsubscribe(aBox)); completeQueue.removeAll(cmd); cmd = doCommand(imapCommand::clientDelete (aBox)); // If this doesn't work, we try to empty the mailbox first if (cmd->result () != "OK") { completeQueue.removeAll(cmd); if (!assureBox(aBox, false)) return; bool stillOk = true; if (stillOk) { - imapCommand *cmd = doCommand( + CommandPtr cmd = doCommand( imapCommand::clientStore("1:*", "+FLAGS.SILENT", "\\DELETED")); if (cmd->result () != "OK") stillOk = false; completeQueue.removeAll(cmd); } if (stillOk) { - imapCommand *cmd = doCommand(imapCommand::clientClose()); + CommandPtr cmd = doCommand(imapCommand::clientClose()); if (cmd->result () != "OK") stillOk = false; completeQueue.removeAll(cmd); setState(ISTATE_LOGIN); } if (stillOk) { - imapCommand *cmd = doCommand (imapCommand::clientDelete(aBox)); + CommandPtr cmd = doCommand (imapCommand::clientDelete(aBox)); if (cmd->result () != "OK") stillOk = false; completeQueue.removeAll(cmd); } if (!stillOk) { error (ERR_COULD_NOT_RMDIR, _url.prettyUrl()); return; } } else { completeQueue.removeAll (cmd); } } break; case ITYPE_DIR: { - imapCommand *cmd = doCommand (imapCommand::clientDelete (aBox)); + CommandPtr cmd = doCommand (imapCommand::clientDelete (aBox)); if (cmd->result () != "OK") { error (ERR_COULD_NOT_RMDIR, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } break; case ITYPE_MSG: { // if open for read/write if (!assureBox (aBox, false)) return; - imapCommand *cmd = + CommandPtr cmd = doCommand (imapCommand:: clientStore (aSequence, "+FLAGS.SILENT", "\\DELETED")); if (cmd->result () != "OK") { error (ERR_CANNOT_DELETE, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (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) { kDebug(7116) <<"IMAP4Protocol::special"; if (!makeLogin()) return; QDataStream stream( aData ); 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()); + CommandPtr cmd = doCommand(imapCommand::clientNoop()); if (cmd->result () != "OK") { kDebug(7116) <<"NOOP did not succeed - connection broken"; completeQueue.removeAll (cmd); error (ERR_CONNECTION_BROKEN, myHost); return; } completeQueue.removeAll (cmd); - delete 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)); + CommandPtr cmd = doCommand(imapCommand::clientUnsubscribe(aBox)); if (cmd->result () != "OK") { completeQueue.removeAll (cmd); error(ERR_SLAVE_DEFINED, i18n("Unsubscribe of folder %1 " "failed. The server returned: %2", _url.prettyUrl(), cmd->resultInfo())); return; } completeQueue.removeAll (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)); + CommandPtr cmd = doCommand(imapCommand::clientSubscribe(aBox)); if (cmd->result () != "OK") { completeQueue.removeAll (cmd); error(ERR_SLAVE_DEFINED, i18n("Subscribe of folder %1 " "failed. The server returned: %2", _url.prettyUrl(), cmd->resultInfo())); return; } completeQueue.removeAll (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; QByteArray 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 QByteArray 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)); + CommandPtr cmd = doCommand (imapCommand:: + clientStore (aSequence, "-FLAGS.SILENT", knownFlags)); if (cmd->result () != "OK") { completeQueue.removeAll (cmd); error(ERR_SLAVE_DEFINED, i18n("Changing the flags of message %1 " "failed with %2.", _url.prettyUrl(), cmd->result())); return; } completeQueue.removeAll (cmd); if (!newFlags.isEmpty()) { cmd = doCommand (imapCommand:: clientStore (aSequence, "+FLAGS.SILENT", newFlags)); if (cmd->result () != "OK") { completeQueue.removeAll (cmd); error(ERR_SLAVE_DEFINED, i18n("Silent Changing the flags of message %1 " "failed with %2.", _url.prettyUrl(), cmd->result())); return; } completeQueue.removeAll (cmd); } finished(); break; } case 's': { // seen KUrl _url; bool seen; QByteArray 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; + CommandPtr 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.removeAll (cmd); error(ERR_COULD_NOT_WRITE, i18n( "Changing the flags of message %1 failed.", _url.prettyUrl() ) ); return; } completeQueue.removeAll (cmd); finished(); break; } case 'E': { // search specialSearchCommand( stream ); break; } case 'X': { // custom command specialCustomCommand( stream ); break; } default: kWarning(7116) <<"Unknown command in special():" << tmp; 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; kDebug(7116) <<"SETACL" << aBox << user << acl; - imapCommand *cmd = doCommand(imapCommand::clientSetACL(aBox, user, acl)); + CommandPtr 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", _url.prettyUrl(), user, cmd->resultInfo())); return; } completeQueue.removeAll (cmd); finished(); break; } case 'D': // DELETEACL { QString user; stream >> user; kDebug(7116) <<"DELETEACL" << aBox << user; - imapCommand *cmd = doCommand(imapCommand::clientDeleteACL(aBox, user)); + CommandPtr 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", _url.prettyUrl(), user, cmd->resultInfo())); return; } completeQueue.removeAll (cmd); finished(); break; } case 'G': // GETACL { kDebug(7116) <<"GETACL" << aBox; - imapCommand *cmd = doCommand(imapCommand::clientGetACL(aBox)); + CommandPtr 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", _url.prettyUrl(), 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 kDebug(7116) << getResults(); 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 { kDebug(7116) <<"MYRIGHTS" << aBox; - imapCommand *cmd = doCommand(imapCommand::clientMyRights(aBox)); + CommandPtr 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", _url.prettyUrl(), cmd->resultInfo())); return; } QStringList lst = getResults(); kDebug(7116) <<"myrights results:" << lst; if ( !lst.isEmpty() ) { Q_ASSERT( lst.count() == 1 ); infoMessage( lst.first() ); } finished(); break; } default: kWarning(7116) <<"Unknown special ACL command:" << command; error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) ); } } void IMAP4Protocol::specialSearchCommand( QDataStream& stream ) { kDebug(7116) <<"IMAP4Protocol::specialSearchCommand"; 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 )); + CommandPtr cmd = doCommand (imapCommand::clientSearch( aSection )); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Searching of folder %1 " "failed. The server returned: %2", aBox, cmd->resultInfo())); return; } completeQueue.removeAll(cmd); QStringList lst = getResults(); kDebug(7116) <<"IMAP4Protocol::specialSearchCommand '" << aSection << "' returns" << lst; infoMessage( lst.join( " " ) ); finished(); } void IMAP4Protocol::specialCustomCommand( QDataStream& stream ) { kDebug(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' ) { kDebug(7116) << "IMAP4Protocol::specialCustomCommand: normal mode" << endl; - imapCommand *cmd = doCommand (imapCommand::clientCustom( command, arguments )); + CommandPtr cmd = doCommand (imapCommand::clientCustom( command, arguments )); if (cmd->result () != "OK") { error( ERR_SLAVE_DEFINED, i18n( "Custom command %1:%2 failed. The server returned: %3", command, arguments, cmd->resultInfo() ) ); return; } completeQueue.removeAll(cmd); QStringList lst = getResults(); kDebug(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' ) { kDebug(7116) << "IMAP4Protocol::specialCustomCommand: extended mode" << endl; - imapCommand *cmd = sendCommand (imapCommand::clientCustom( command, QString() )); + CommandPtr cmd = sendCommand (imapCommand::clientCustom( command, QString() )); while ( !parseLoop () ) {}; // see if server is waiting if (!cmd->isComplete () && !getContinuation ().isEmpty ()) { const QByteArray buffer = arguments.toUtf8(); // 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.removeAll ( cmd ); setState(ISTATE_CONNECT); closeConnection(); return; } } parseWriteLine (""); do { while (!parseLoop ()) {}; } while (!cmd->isComplete ()); completeQueue.removeAll (cmd); QStringList lst = getResults(); kDebug(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 attributes (name and value) QString entry; QMap attributes; stream >> entry >> attributes; kDebug(7116) <<"SETANNOTATION" << aBox << entry << attributes.count() <<" attributes"; - imapCommand *cmd = doCommand(imapCommand::clientSetAnnotation(aBox, entry, attributes)); + CommandPtr 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", entry, _url.prettyUrl(), cmd->resultInfo())); return; } completeQueue.removeAll (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; kDebug(7116) <<"GETANNOTATION" << aBox << entry << attributeNames; - imapCommand *cmd = doCommand(imapCommand::clientGetAnnotation(aBox, entry, attributeNames)); + CommandPtr 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", entry, _url.prettyUrl(), 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. kDebug(7116) << getResults(); infoMessage(getResults().join( "\r" )); finished(); break; } default: kWarning(7116) <<"Unknown special annotate command:" << command; 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 { kDebug(7116) <<"QUOTAROOT" << aBox; - imapCommand *cmd = doCommand(imapCommand::clientGetQuotaroot( aBox ) ); + CommandPtr 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", _url.prettyUrl(), cmd->resultInfo())); return; } infoMessage(getResults().join( "\r" )); finished(); break; } case 'G': // GETQUOTA { kDebug(7116) <<"GETQUOTA command"; kWarning(7116) <<"UNIMPLEMENTED"; break; } case 'S': // SETQUOTA { kDebug(7116) <<"SETQUOTA command"; kWarning(7116) <<"UNIMPLEMENTED"; break; } default: kWarning(7116) <<"Unknown special quota command:" << command; error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) ); } } void IMAP4Protocol::rename (const KUrl & src, const KUrl & dest, KIO::JobFlags flags) { kDebug(7116) <<"IMAP4::rename - [" << ((flags & KIO::Overwrite) ?"Overwrite" :"NoOverwrite") <<"]" << src <<" ->" << dest; 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()) { kDebug(7116) <<"IMAP4::rename - close" << getCurrentBox(); // mailbox can only be renamed if it is closed - imapCommand *cmd = doCommand (imapCommand::clientClose()); + CommandPtr cmd = doCommand (imapCommand::clientClose()); bool ok = cmd->result() == "OK"; completeQueue.removeAll(cmd); if (!ok) { error(ERR_CANNOT_RENAME, i18n("Unable to close mailbox.")); return; } setState(ISTATE_LOGIN); } - imapCommand *cmd = doCommand (imapCommand::clientRename (sBox, dBox)); + CommandPtr cmd = doCommand (imapCommand::clientRename (sBox, dBox)); if (cmd->result () != "OK") { error (ERR_CANNOT_RENAME, cmd->result ()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (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) && isConnected(); kDebug(7116) <<"IMAP4::slave_status" << connected; slaveStatus ( connected ? myHost : QString(), connected ); } void IMAP4Protocol::dispatch (int command, const QByteArray & data) { kDebug(7116) <<"IMAP4::dispatch - command=" << command; KIO::TCPSlaveBase::dispatch (command, data); } void IMAP4Protocol::stat (const KUrl & _url) { kDebug(7116) <<"IMAP4::stat -" << _url.prettyUrl(); 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; entry.insert( UDSEntry::UDS_NAME, aBox); if (!aSection.isEmpty()) { if (getState() == ISTATE_SELECT && aBox == getCurrentBox()) { - imapCommand *cmd = doCommand (imapCommand::clientClose()); + CommandPtr cmd = doCommand (imapCommand::clientClose()); bool ok = cmd->result() == "OK"; completeQueue.removeAll(cmd); if (!ok) { error(ERR_COULD_NOT_STAT, i18n("Unable to close mailbox.")); 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)); + CommandPtr cmd = doCommand(imapCommand::clientStatus(aBox, aSection)); ok = cmd->result() == "OK"; cmdInfo = cmd->resultInfo(); completeQueue.removeAll(cmd); } if (!ok) { bool found = false; - imapCommand *cmd = doCommand (imapCommand::clientList ("", aBox)); + CommandPtr cmd = doCommand (imapCommand::clientList ("", aBox)); if (cmd->result () == "OK") { for (QList< imapList >::Iterator it = listResponses.begin (); it != listResponses.end (); ++it) { if (aBox == (*it).name ()) found = true; } } completeQueue.removeAll (cmd); if (found) error(ERR_COULD_NOT_STAT, i18n("Unable to get information about folder %1. The server replied: %2", aBox, cmdInfo)); else error(KIO::ERR_DOES_NOT_EXIST, aBox); return; } if ((aSection == "UIDNEXT" && getStatus().uidNextAvailable()) || (aSection == "UNSEEN" && getStatus().unseenAvailable())) { entry.insert( UDSEntry::UDS_SIZE, (aSection == "UIDNEXT") ? getStatus().uidNext() : getStatus().unseen()); } } 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 = + CommandPtr cmd = doCommand (imapCommand::clientStatus (aBox, "UIDVALIDITY")); completeQueue.removeAll (cmd); validity = getStatus ().uidValidity (); } #ifdef __GNUC__ #warning This is temporary since Dec 2000 and makes most of the below code invalid #endif 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)); kDebug(7116) <<"IMAP4::stat - redirecting to" << newUrl.prettyUrl(); 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; kDebug(7116) <<"IMAP4::stat - url has invalid validity [" << validity <<"d]" << _url.prettyUrl(); } } } entry.insert( UDSEntry::UDS_MIME_TYPE,getMimeType (aType)); //kDebug(7116) <<"IMAP4: stat:" << atom.m_str; switch (aType) { case ITYPE_DIR: entry.insert( UDSEntry::UDS_FILE_TYPE, S_IFDIR); break; case ITYPE_BOX: case ITYPE_DIR_AND_BOX: entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); break; case ITYPE_MSG: case ITYPE_ATTACH: entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFREG); break; case ITYPE_UNKNOWN: error (ERR_DOES_NOT_EXIST, _url.prettyUrl()); break; } statEntry (entry); kDebug(7116) <<"IMAP4::stat - Finishing stat"; 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()); + CommandPtr cmd = doCommand (imapCommand::clientExpunge()); completeQueue.removeAll (cmd); } if (getState() != ISTATE_CONNECT) { - imapCommand *cmd = doCommand (imapCommand::clientLogout()); + CommandPtr cmd = doCommand (imapCommand::clientLogout()); completeQueue.removeAll (cmd); } disconnectFromHost(); setState(ISTATE_NO); completeQueue.clear(); sentQueue.clear(); lastHandled = 0; currentBox.clear(); readBufferLen = 0; } bool IMAP4Protocol::makeLogin () { if (getState () == ISTATE_LOGIN || getState () == ISTATE_SELECT) return true; kDebug(7116) <<"IMAP4::makeLogin - checking login"; bool alreadyConnected = getState() == ISTATE_CONNECT; kDebug(7116) <<"IMAP4::makeLogin - alreadyConnected" << alreadyConnected; if (alreadyConnected || connectToHost (( mySSL ? IMAP_SSL_PROTOCOL : IMAP_PROTOCOL ), myHost, myPort)) { // fcntl (m_iSock, F_SETFL, (fcntl (m_iSock, F_GETFL) | O_NDELAY)); setState(ISTATE_CONNECT); myAuth = metaData("auth"); myTLS = metaData("tls"); kDebug(7116) <<"myAuth:" << myAuth; - imapCommand *cmd; + CommandPtr cmd; unhandled.clear (); if (!alreadyConnected) while (!parseLoop ()) {} //get greeting QString greeting; if (!unhandled.isEmpty()) greeting = unhandled.first().trimmed(); unhandled.clear (); //get rid of it - cmd = doCommand (new imapCommand ("CAPABILITY", "")); + cmd = doCommand (CommandPtr(new imapCommand ("CAPABILITY", ""))); kDebug(7116) <<"IMAP4: setHost: capability"; for (QStringList::const_iterator it = imapCapabilities.constBegin (); it != imapCapabilities.constEnd (); ++it) { kDebug(7116) <<"'" << (*it) <<"'"; } completeQueue.removeAll (cmd); - delete 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", myHost, 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()); + CommandPtr cmd = doCommand (imapCommand::clientStartTLS()); if (cmd->result () == "OK") { completeQueue.removeAll(cmd); if (startSsl()) { kDebug(7116) <<"TLS mode has been enabled."; - imapCommand *cmd2 = doCommand (new imapCommand ("CAPABILITY", "")); + CommandPtr cmd2 = doCommand (CommandPtr(new imapCommand ("CAPABILITY", ""))); for (QStringList::const_iterator it = imapCapabilities.constBegin (); it != imapCapabilities.constEnd (); ++it) { kDebug(7116) <<"'" << (*it) <<"'"; } completeQueue.removeAll (cmd2); } else { kWarning(7116) <<"TLS mode setup has failed. Aborting."; error (ERR_COULD_NOT_LOGIN, i18n("Starting TLS failed.")); closeConnection(); - delete cmd; return false; } } else completeQueue.removeAll(cmd); - delete cmd; } if (!myAuth.isEmpty () && myAuth != "*" && !hasCapability (QString ("AUTH=") + myAuth)) { error (ERR_COULD_NOT_LOGIN, i18n("The authentication method %1 is not " "supported by the server.", myAuth)); closeConnection(); return false; } if ( greeting.contains( QRegExp( "Cyrus IMAP4 v2.1" ) ) ) { removeCapability( "ANNOTATEMORE" ); } kDebug(7116) <<"IMAP4::makeLogin - attempting login"; KIO::AuthInfo authInfo; authInfo.username = myUser; authInfo.password = myPass; authInfo.prompt = i18n ("Username and password for your IMAP account:"); kDebug(7116) <<"IMAP4::makeLogin - open_PassDlg said user=" << myUser <<" pass=xx"; QString resultInfo; if (myAuth.isEmpty () || myAuth == "*") { if (myUser.isEmpty () || myPass.isEmpty ()) { if(openPasswordDialog (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", myHost, 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", myAuth, myHost, 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") { kDebug(7116) <<"makeLogin - registered namespaces"; } completeQueue.removeAll (cmd); - delete cmd; } // get the default delimiter (empty listing) cmd = doCommand( imapCommand::clientList("", "") ); if (cmd->result () == "OK") { QList< imapList >::Iterator it = listResponses.begin(); if ( it != listResponses.end() ) { namespaceToDelimiter[QString()] = (*it).hierarchyDelimiter(); kDebug(7116) <<"makeLogin - delimiter for empty ns='" << (*it).hierarchyDelimiter() <<"'"; if ( !hasCapability("NAMESPACE") ) { // server does not support namespaces QString nsentry = QString::number( 0 ) + "==" + (*it).hierarchyDelimiter(); imapNamespaces.append( nsentry ); } } } completeQueue.removeAll (cmd); - delete cmd; } else { kDebug(7116) <<"makeLogin - NO login"; } return getState() == ISTATE_LOGIN; } void IMAP4Protocol::parseWriteLine (const QString & aStr) { //kDebug(7116) <<"Writing:" << aStr; QByteArray writer = aStr.toUtf8(); 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()); const QString encodedUrl = aURL.url(KUrl::LeaveTrailingSlash); // 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; entry.clear (); const QString uid = QString::number(cache->getUid()); QString tmp = uid; if (stretch > 0) { tmp = "0000000000000000" + uid; tmp = tmp.right (stretch); } if (withSubject) { mailHeader *header = cache->getHeader(); if (header) tmp += ' ' + header->getSubject(); } entry.insert (UDSEntry::UDS_NAME,tmp); tmp = encodedUrl; // utf-8 if (tmp[tmp.length () - 1] != '/') tmp += '/'; tmp += ";UID=" + uid; entry.insert( UDSEntry::UDS_URL, tmp); entry.insert(UDSEntry::UDS_FILE_TYPE,S_IFREG); entry.insert(UDSEntry::UDS_SIZE, cache->getSize()); entry.insert( UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("message/rfc822")); entry.insert(UDSEntry::UDS_USER,myUser); entry.insert( KIO::UDSEntry::UDS_ACCESS, (withFlags) ? cache->getFlags() : S_IRUSR | S_IXUSR | S_IWUSR); listEntry (entry, false); } } void IMAP4Protocol::doListEntry (const KUrl & _url, const QString & myBox, const imapList & item, bool appendPath) { KUrl aURL = _url; aURL.setQuery (QString()); UDSEntry entry; int hdLen = item.hierarchyDelimiter().length(); { // mailboxName will be appended to the path if appendPath is true QString mailboxName = item.name (); // some beautification if ( mailboxName.startsWith(myBox) && 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); QString tmp; if (!item.hierarchyDelimiter().isEmpty() && mailboxName.contains(item.hierarchyDelimiter()) ) tmp = mailboxName.section(item.hierarchyDelimiter(), -1); else tmp = mailboxName; // konqueror will die with an assertion failure otherwise if (tmp.isEmpty ()) tmp = ".."; if (!tmp.isEmpty ()) { entry.insert(UDSEntry::UDS_NAME,tmp); if (!item.noSelect ()) { if (!item.noInferiors ()) { tmp = "message/directory"; } else { tmp = "message/digest"; } entry.insert(UDSEntry::UDS_MIME_TYPE,tmp); mailboxName += '/'; // explicitly set this as a directory for KFileDialog entry.insert(UDSEntry::UDS_FILE_TYPE,S_IFDIR); } else if (!item.noInferiors ()) { entry.insert(UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory")); mailboxName += '/'; // explicitly set this as a directory for KFileDialog entry.insert(UDSEntry::UDS_FILE_TYPE,S_IFDIR); } else { entry.insert(UDSEntry::UDS_MIME_TYPE,QString::fromLatin1("unknown/unknown")); } QString path = aURL.path(); 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.toUpper() == "/INBOX/") { // make sure the client can rely on INBOX path = path.toUpper(); } } aURL.setPath(path); tmp = aURL.url(KUrl::LeaveTrailingSlash); // utf-8 entry.insert(UDSEntry::UDS_URL, tmp); entry.insert( UDSEntry::UDS_USER, myUser); entry.insert( UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IWUSR); entry.insert( UDSEntry::UDS_EXTRA,item.attributesAsString()); 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); // kDebug(7116) <<"URL: query - '" << KUrl::fromPercentEncoding(_url.query()) <<"'"; // get the delimiter QString myNamespace = namespaceForBox( _box ); kDebug(7116) <<"IMAP4::parseURL - namespace=" << myNamespace; if ( namespaceToDelimiter.contains(myNamespace) ) { _hierarchyDelimiter = namespaceToDelimiter[myNamespace]; kDebug(7116) <<"IMAP4::parseURL - delimiter=" << _hierarchyDelimiter; } if (!_box.isEmpty ()) { kDebug(7116) <<"IMAP4::parseURL - box=" << _box; 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; + CommandPtr cmd; cmd = doCommand (imapCommand::clientList ("", _box)); if (cmd->result () == "OK") { for (QList< imapList >::Iterator it = listResponses.begin (); it != listResponses.end (); ++it) { //kDebug(7116) <<"IMAP4::parseURL - checking" << _box <<" to" << (*it).name(); 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 { kDebug(7116) <<"IMAP4::parseURL - got error for" << _box; } completeQueue.removeAll (cmd); } // cache } else // current == box { retVal = ITYPE_BOX; } } else kDebug(7116) <<"IMAP4::parseURL: no login!"; } else // empty box { // the root is just a dir kDebug(7116) <<"IMAP4: parseURL: box [root]"; 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.contains(':') && !_uid.contains(',') && !_uid.contains('*') ) retVal = ITYPE_MSG; } } if (retVal == ITYPE_MSG) { if ( ( _section.contains("BODY.PEEK[", Qt::CaseInsensitive) || _section.contains("BODY[", Qt::CaseInsensitive) ) && !_section.contains(".MIME") && !_section.contains(".HEADER") ) 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().lastIndexOf(_box); if (start != -1) _hierarchyDelimiter = _url.path().mid(start-1, start); kDebug(7116) <<"IMAP4::parseURL - reconstructed delimiter:" << _hierarchyDelimiter << "from URL" << _url.path(); } if (_hierarchyDelimiter.isEmpty()) _hierarchyDelimiter = "/"; } kDebug(7116) <<"IMAP4::parseURL - return" << retVal; return retVal; } int IMAP4Protocol::outputLine (const QByteArray & _str, int len) { if (len == -1) { len = _str.length(); } if (cacheOutput) { if ( !outputBuffer.isOpen() ) { outputBuffer.open(QIODevice::WriteOnly); } outputBuffer.seek( outputBufferIndex ); outputBuffer.write(_str.data(), len); outputBufferIndex += len; return 0; } QByteArray temp; bool relay = relayEnabled; relayEnabled = true; temp = QByteArray::fromRawData (_str.data (), len); parseRelay (temp); temp.clear(); relayEnabled = relay; return 0; } void IMAP4Protocol::flushOutput(const 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.startsWith("quoted-printable", Qt::CaseInsensitive) ) decoded = KCodecs::quotedPrintableDecode(outputCache); else if ( contentEncoding.startsWith("base64", Qt::CaseInsensitive) ) decoded = QByteArray::fromBase64( outputCache ); else decoded = outputCache; QString mimetype = KMimeType::findByContent( decoded )->name(); kDebug(7116) <<"IMAP4::flushOutput - mimeType" << mimetype; 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 (!isConnected()) return 0; waitForResponse( responseTimeout() ); return read((char*)data, len); } bool IMAP4Protocol::assureBox (const QString & aBox, bool readonly) { if (aBox.isEmpty()) return false; - imapCommand *cmd = 0; + CommandPtr cmd; if (aBox != getCurrentBox () || (!getSelected().readWrite() && !readonly)) { // open the box with the appropriate mode kDebug(7116) <<"IMAP4Protocol::assureBox - opening box"; selectInfo = imapInfo(); cmd = doCommand (imapCommand::clientSelect (aBox, readonly)); bool ok = cmd->result() == "OK"; QString cmdInfo = cmd->resultInfo(); completeQueue.removeAll (cmd); if (!ok) { bool found = false; cmd = doCommand (imapCommand::clientList ("", aBox)); if (cmd->result () == "OK") { for (QList< imapList >::Iterator it = listResponses.begin (); it != listResponses.end (); ++it) { if (aBox == (*it).name ()) found = true; } } completeQueue.removeAll (cmd); if (found) { if ( cmdInfo.contains("permission", Qt::CaseInsensitive) ) { // 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", aBox, 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. kDebug(7116) <<"IMAP4Protocol::assureBox - reusing box"; if ( mTimeOfLastNoop.secsTo( QDateTime::currentDateTime() ) > 10 ) { cmd = doCommand (imapCommand::clientNoop ()); completeQueue.removeAll (cmd); mTimeOfLastNoop = QDateTime::currentDateTime(); kDebug(7116) <<"IMAP4Protocol::assureBox - noop timer fired"; } } // 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/kioslave/imap4/imapcommand.cpp b/kioslave/imap4/imapcommand.cpp index 6b4c73b5c..781524b46 100644 --- a/kioslave/imap4/imapcommand.cpp +++ b/kioslave/imap4/imapcommand.cpp @@ -1,410 +1,410 @@ /********************************************************************** * * imapcommand.cc - 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 s.carstens@gmx.de * *********************************************************************/ #include "imapcommand.h" #include /*#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include */ using namespace KIMAP; imapCommand::imapCommand () { mComplete = false; mId.clear(); } imapCommand::imapCommand (const QString & command, const QString & parameter) // aCommand(NULL), // mResult(NULL), // mParameter(NULL) { mComplete = false; aCommand = command; aParameter = parameter; mId.clear(); } 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 * +CommandPtr imapCommand::clientNoop () { - return new imapCommand ("NOOP", ""); + return CommandPtr( new imapCommand ("NOOP", "") ); } -imapCommand * +CommandPtr imapCommand::clientFetch (ulong uid, const QString & fields, bool nouid) { - return clientFetch (uid, uid, fields, nouid); + return CommandPtr( clientFetch (uid, uid, fields, nouid) ); } -imapCommand * +CommandPtr 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 * +CommandPtr imapCommand::clientFetch (const QString & sequence, const QString & fields, bool nouid) { - return new imapCommand (nouid ? "FETCH" : "UID FETCH", - sequence + " (" + fields + ')'); + return CommandPtr( new imapCommand (nouid ? "FETCH" : "UID FETCH", + sequence + " (" + fields + ')') ); } -imapCommand * +CommandPtr imapCommand::clientList (const QString & reference, const QString & path, bool lsub) { - return new imapCommand (lsub ? "LSUB" : "LIST", + return CommandPtr( new imapCommand (lsub ? "LSUB" : "LIST", QString ("\"") + KIMAP::encodeImapFolderName (reference) + - "\" \"" + KIMAP::encodeImapFolderName (path) + "\""); + "\" \"" + KIMAP::encodeImapFolderName (path) + "\"") ); } -imapCommand * +CommandPtr 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 ("\"") + KIMAP::encodeImapFolderName (path) + "\""); + return CommandPtr( new imapCommand ("SELECT", + QString ("\"") + KIMAP::encodeImapFolderName (path) + "\"") ); } -imapCommand * +CommandPtr imapCommand::clientClose() { - return new imapCommand("CLOSE", ""); + return CommandPtr( new imapCommand("CLOSE", "") ); } -imapCommand * +CommandPtr imapCommand::clientCopy (const QString & box, const QString & sequence, bool nouid) { - return new imapCommand (nouid ? "COPY" : "UID COPY", - sequence + " \"" + KIMAP::encodeImapFolderName (box) + "\""); + return CommandPtr( new imapCommand (nouid ? "COPY" : "UID COPY", + sequence + " \"" + KIMAP::encodeImapFolderName (box) + "\"") ); } -imapCommand * +CommandPtr imapCommand::clientAppend (const QString & box, const QString & flags, ulong size) { - return new imapCommand ("APPEND", + return CommandPtr( new imapCommand ("APPEND", "\"" + KIMAP::encodeImapFolderName (box) + "\" " + ((flags.isEmpty()) ? "" : ('(' + flags + ") ")) + - '{' + QString::number(size) + '}'); + '{' + QString::number(size) + '}') ); } -imapCommand * +CommandPtr imapCommand::clientStatus (const QString & path, const QString & parameters) { - return new imapCommand ("STATUS", + return CommandPtr( new imapCommand ("STATUS", QString ("\"") + KIMAP::encodeImapFolderName (path) + - "\" (" + parameters + ")"); + "\" (" + parameters + ")") ); } -imapCommand * +CommandPtr imapCommand::clientCreate (const QString & path) { - return new imapCommand ("CREATE", - QString ("\"") + KIMAP::encodeImapFolderName (path) + "\""); + return CommandPtr( new imapCommand ("CREATE", + QString ("\"") + KIMAP::encodeImapFolderName (path) + "\"") ); } -imapCommand * +CommandPtr imapCommand::clientDelete (const QString & path) { - return new imapCommand ("DELETE", - QString ("\"") + KIMAP::encodeImapFolderName (path) + "\""); + return CommandPtr( new imapCommand ("DELETE", + QString ("\"") + KIMAP::encodeImapFolderName (path) + "\"") ); } -imapCommand * +CommandPtr imapCommand::clientSubscribe (const QString & path) { - return new imapCommand ("SUBSCRIBE", - QString ("\"") + KIMAP::encodeImapFolderName (path) + "\""); + return CommandPtr( new imapCommand ("SUBSCRIBE", + QString ("\"") + KIMAP::encodeImapFolderName (path) + "\"") ); } -imapCommand * +CommandPtr imapCommand::clientUnsubscribe (const QString & path) { - return new imapCommand ("UNSUBSCRIBE", - QString ("\"") + KIMAP::encodeImapFolderName (path) + "\""); + return CommandPtr( new imapCommand ("UNSUBSCRIBE", + QString ("\"") + KIMAP::encodeImapFolderName (path) + "\"") ); } -imapCommand * +CommandPtr imapCommand::clientExpunge () { - return new imapCommand ("EXPUNGE", QString ("")); + return CommandPtr( new imapCommand ("EXPUNGE", QString ("")) ); } -imapCommand * +CommandPtr imapCommand::clientRename (const QString & src, const QString & dest) { - return new imapCommand ("RENAME", + return CommandPtr( new imapCommand ("RENAME", QString ("\"") + KIMAP::encodeImapFolderName (src) + - "\" \"" + KIMAP::encodeImapFolderName (dest) + "\""); + "\" \"" + KIMAP::encodeImapFolderName (dest) + "\"") ); } -imapCommand * +CommandPtr imapCommand::clientSearch (const QString & search, bool nouid) { - return new imapCommand (nouid ? "SEARCH" : "UID SEARCH", search); + return CommandPtr( new imapCommand (nouid ? "SEARCH" : "UID SEARCH", search) ); } -imapCommand * +CommandPtr imapCommand::clientStore (const QString & set, const QString & item, const QString & data, bool nouid) { - return new imapCommand (nouid ? "STORE" : "UID STORE", - set + ' ' + item + " (" + data + ')'); + return CommandPtr( new imapCommand (nouid ? "STORE" : "UID STORE", + set + ' ' + item + " (" + data + ')') ); } -imapCommand * +CommandPtr imapCommand::clientLogout () { - return new imapCommand ("LOGOUT", ""); + return CommandPtr( new imapCommand ("LOGOUT", "") ); } -imapCommand * +CommandPtr imapCommand::clientStartTLS () { - return new imapCommand ("STARTTLS", ""); + return CommandPtr( new imapCommand ("STARTTLS", "") ); } -imapCommand * +CommandPtr imapCommand::clientSetACL( const QString& box, const QString& user, const QString& acl ) { - return new imapCommand ("SETACL", QString("\"") + KIMAP::encodeImapFolderName (box) + return CommandPtr( new imapCommand ("SETACL", QString("\"") + KIMAP::encodeImapFolderName (box) + "\" \"" + KIMAP::encodeImapFolderName (user) - + "\" \"" + KIMAP::encodeImapFolderName (acl) + "\""); + + "\" \"" + KIMAP::encodeImapFolderName (acl) + "\"") ); } -imapCommand * +CommandPtr imapCommand::clientDeleteACL( const QString& box, const QString& user ) { - return new imapCommand ("DELETEACL", QString("\"") + KIMAP::encodeImapFolderName (box) + return CommandPtr( new imapCommand ("DELETEACL", QString("\"") + KIMAP::encodeImapFolderName (box) + "\" \"" + KIMAP::encodeImapFolderName (user) - + "\""); + + "\"") ); } -imapCommand * +CommandPtr imapCommand::clientGetACL( const QString& box ) { - return new imapCommand ("GETACL", QString("\"") + KIMAP::encodeImapFolderName (box) - + "\""); + return CommandPtr( new imapCommand ("GETACL", QString("\"") + KIMAP::encodeImapFolderName (box) + + "\"") ); } -imapCommand * +CommandPtr imapCommand::clientListRights( const QString& box, const QString& user ) { - return new imapCommand ("LISTRIGHTS", QString("\"") + KIMAP::encodeImapFolderName (box) + return CommandPtr( new imapCommand ("LISTRIGHTS", QString("\"") + KIMAP::encodeImapFolderName (box) + "\" \"" + KIMAP::encodeImapFolderName (user) - + "\""); + + "\"") ); } -imapCommand * +CommandPtr imapCommand::clientMyRights( const QString& box ) { - return new imapCommand ("MYRIGHTS", QString("\"") + KIMAP::encodeImapFolderName (box) - + "\""); + return CommandPtr( new imapCommand ("MYRIGHTS", QString("\"") + KIMAP::encodeImapFolderName (box) + + "\"") ); } -imapCommand * +CommandPtr imapCommand::clientSetAnnotation( const QString& box, const QString& entry, const QMap& attributes ) { QString parameter = QString("\"") + KIMAP::encodeImapFolderName (box) + "\" \"" + KIMAP::encodeImapFolderName (entry) + "\" ("; for( QMap::ConstIterator it = attributes.begin(); it != attributes.end(); ++it ) { parameter += "\""; parameter += KIMAP::encodeImapFolderName (it.key()); parameter += "\" \""; parameter += KIMAP::encodeImapFolderName (it.value()); parameter += "\" "; } // Turn last space into a ')' parameter[parameter.length()-1] = ')'; - return new imapCommand ("SETANNOTATION", parameter); + return CommandPtr( new imapCommand ("SETANNOTATION", parameter) ); } -imapCommand * +CommandPtr imapCommand::clientGetAnnotation( const QString& box, const QString& entry, const QStringList& attributeNames ) { QString parameter = QString("\"") + KIMAP::encodeImapFolderName (box) + "\" \"" + KIMAP::encodeImapFolderName (entry) + "\" "; if ( attributeNames.count() == 1 ) parameter += "\"" + KIMAP::encodeImapFolderName (attributeNames.first()) + '"'; else { parameter += '('; for( QStringList::ConstIterator it = attributeNames.begin(); it != attributeNames.end(); ++it ) { parameter += "\"" + KIMAP::encodeImapFolderName (*it) + "\" "; } // Turn last space into a ')' parameter[parameter.length()-1] = ')'; } - return new imapCommand ("GETANNOTATION", parameter); + return CommandPtr( new imapCommand ("GETANNOTATION", parameter) ); } -imapCommand * +CommandPtr imapCommand::clientNamespace() { - return new imapCommand("NAMESPACE", ""); + return CommandPtr( new imapCommand("NAMESPACE", "") ); } -imapCommand * +CommandPtr imapCommand::clientGetQuotaroot( const QString& box ) { QString parameter = QString("\"") + KIMAP::encodeImapFolderName (box) + '"'; - return new imapCommand ("GETQUOTAROOT", parameter); + return CommandPtr( new imapCommand ("GETQUOTAROOT", parameter) ); } -imapCommand * +CommandPtr imapCommand::clientCustom( const QString& command, const QString& arguments ) { - return new imapCommand (command, arguments); + return CommandPtr( new imapCommand (command, arguments) ); } diff --git a/kioslave/imap4/imapcommand.h b/kioslave/imap4/imapcommand.h index 3596205d8..620756905 100644 --- a/kioslave/imap4/imapcommand.h +++ b/kioslave/imap4/imapcommand.h @@ -1,394 +1,399 @@ #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 #include #include +#include + +class imapCommand; +typedef boost::shared_ptr CommandPtr; + /** * @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 (\ \ [\]) * @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 (); + static CommandPtr 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); + static CommandPtr 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); + static CommandPtr 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); + static CommandPtr 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); + static CommandPtr 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); + static CommandPtr clientSelect (const QString & path, bool examine = + false); /** * @fn static imapCommand *clientClose(); * @brief Create a CLOSE command * @return a CLOSE imapCommand */ - static imapCommand *clientClose(); + static CommandPtr clientClose(); /** * @brief Create a STATUS command * @param path * @param parameters * @return a STATUS imapCommand */ - static imapCommand *clientStatus (const QString & path, - const QString & parameters); + static CommandPtr 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); + static CommandPtr 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); + static CommandPtr 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); + static CommandPtr clientCreate (const QString & path); /** * @brief Create a DELETE command * @param path * @return a DELETE imapCommand */ - static imapCommand *clientDelete (const QString & path); + static CommandPtr clientDelete (const QString & path); /** * @brief Create a SUBSCRIBE command * @param path * @return a SUBSCRIBE imapCommand */ - static imapCommand *clientSubscribe (const QString & path); + static CommandPtr clientSubscribe (const QString & path); /** * @brief Create a UNSUBSCRIBE command * @param path * @return a UNSUBSCRIBE imapCommand */ - static imapCommand *clientUnsubscribe (const QString & path); + static CommandPtr clientUnsubscribe (const QString & path); /** * @brief Create a EXPUNGE command * @return a EXPUNGE imapCommand */ - static imapCommand *clientExpunge (); + static CommandPtr 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); + static CommandPtr 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); + static CommandPtr 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); + static CommandPtr 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 (); + static CommandPtr clientLogout (); /** * @brief Create a STARTTLS command * @return a STARTTLS imapCommand */ - static imapCommand *clientStartTLS (); + static CommandPtr 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 ); + static CommandPtr 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 ); + static CommandPtr 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 ); + static CommandPtr 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 ); + static CommandPtr 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 ); + static CommandPtr 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& attributes ); + static CommandPtr clientSetAnnotation ( const QString& box, const QString& entry, const QMap& 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 ); + static CommandPtr clientGetAnnotation ( const QString& box, const QString& entry, const QStringList& attributeNames ); /** * @brief Create a NAMESPACE command * @return a NAMESPACE imapCommand */ - static imapCommand *clientNamespace (); + static CommandPtr clientNamespace (); /** * @brief Create a GETQUOTAROOT command * @param box mailbox name * @return a GETQUOTAROOT imapCommand */ - static imapCommand *clientGetQuotaroot ( const QString& box ); + static CommandPtr 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 ); + static CommandPtr 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/kioslave/imap4/imapparser.cpp b/kioslave/imap4/imapparser.cpp index d98548793..d79a1321b 100644 --- a/kioslave/imap4/imapparser.cpp +++ b/kioslave/imap4/imapparser.cpp @@ -1,2048 +1,2047 @@ /********************************************************************** * * imapparser.cc - IMAP4rev1 Parser * Copyright (C) 2001-2002 Michael Haeckel * 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 s.carstens@gmx.de * *********************************************************************/ #include "imapparser.h" #include "imapinfo.h" #include "mailheader.h" #include "mimeheader.h" #include "mailaddress.h" #include #include #include #include #ifdef HAVE_LIBSASL2 extern "C" { #include } #endif #include #include #include #include #include #include #include #include #include #include using namespace KIMAP; #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 () { currentState = ISTATE_NO; commandCounter = 0; lastHandled = 0; } imapParser::~imapParser () { delete lastHandled; lastHandled = 0; } -imapCommand * -imapParser::doCommand (imapCommand * aCmd) +CommandPtr +imapParser::doCommand (CommandPtr aCmd) { int pl = 0; sendCommand (aCmd); while (pl != -1 && !aCmd->isComplete ()) { while ((pl = parseLoop ()) == 0) ; } return aCmd; } -imapCommand * -imapParser::sendCommand (imapCommand * aCmd) +CommandPtr +imapParser::sendCommand (CommandPtr 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 = parseOneWord(p); kDebug(7116) <<"imapParser::sendCommand - setting current box to" << currentBox; } else if (command == "CLOSE") { // we no longer have a box open currentBox.clear(); } else if (command.contains("SEARCH") || 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; + CommandPtr cmd; bool retVal = false; cmd = - doCommand (new - imapCommand ("LOGIN", "\"" + KIMAP::quoteIMAP(aUser) - + "\" \"" + KIMAP::quoteIMAP(aPass) + "\"")); + doCommand ( CommandPtr( new + imapCommand ("LOGIN", "\"" + KIMAP::quoteIMAP(aUser) + + "\" \"" + KIMAP::quoteIMAP(aPass) + "\"")) ); if (cmd->result () == "OK") { currentState = ISTATE_LOGIN; retVal = true; } resultInfo = cmd->resultInfo(); completeQueue.removeAll (cmd); - delete cmd; return retVal; } #ifdef HAVE_LIBSASL2 static bool sasl_interact( KIO::SlaveBase *slave, KIO::AuthInfo &ai, void *in ) { kDebug(7116) <<"sasl_interact"; 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->openPasswordDialog(ai)) return false; } break; } } interact = ( sasl_interact_t * ) in; while( interact->id != SASL_CB_LIST_END ) { kDebug(7116) <<"SASL_INTERACT id:" << interact->id; switch( interact->id ) { case SASL_CB_USER: case SASL_CB_AUTHNAME: kDebug(7116) <<"SASL_CB_[USER|AUTHNAME]: '" << ai.username <<"'"; interact->result = strdup( ai.username.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; case SASL_CB_PASS: kDebug(7116) <<"SASL_CB_PASS: [hidden]"; interact->result = strdup( ai.password.toUtf8() ); 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; kDebug(7116) <<"aAuth:" << aAuth <<" FQDN:" << aFQDN <<" isSSL:" << isSSL; // 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.toLatin1(), 0, 0, callbacks, 0, &conn ); if ( result != SASL_OK ) { kDebug(7116) <<"sasl_client_new failed with:" << result; resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); return false; } do { result = sasl_client_start(conn, aAuth.toLatin1(), &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 ) { kDebug(7116) <<"sasl_client_start failed with:" << result; resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); sasl_dispose( &conn ); return false; } - imapCommand *cmd; + CommandPtr cmd; tmp = QByteArray::fromRawData( out, outlen ); challenge = tmp.toBase64(); tmp.clear(); // then lets try it QString firstCommand = aAuth; if ( !challenge.isEmpty() ) { firstCommand += ' '; firstCommand += QString::fromLatin1( challenge.data(), challenge.size() ); } - cmd = sendCommand (new imapCommand ("AUTHENTICATE", firstCommand.toLatin1())); + cmd = sendCommand (CommandPtr(new imapCommand ("AUTHENTICATE", firstCommand.toLatin1()))); while ( true ) { //read the next line while (parseLoop() == 0) { ; } if ( cmd->isComplete() ) break; if (!continuation.isEmpty()) { // kDebug(7116) <<"S:" << QCString(continuation.data(),continuation.size()+1); if ( continuation.size() > 4 ) { tmp = QByteArray::fromRawData( continuation.data() + 2, continuation.size() - 4 ); challenge = QByteArray::fromBase64( tmp ); // kDebug(7116) <<"S-1:" << QCString(challenge.data(),challenge.size()+1); tmp.clear(); } 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 ) { kDebug(7116) <<"sasl_client_step failed with:" << result; resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); sasl_dispose( &conn ); return false; } tmp = QByteArray::fromRawData( out, outlen ); // kDebug(7116) <<"C-1:" << QCString(tmp.data(),tmp.size()+1); challenge = tmp.toBase64(); tmp.clear(); // kDebug(7116) <<"C:" << QCString(challenge.data(),challenge.size()+1); parseWriteLine (challenge); continuation.resize(0); } } if (cmd->result () == "OK") { currentState = ISTATE_LOGIN; retVal = true; } resultInfo = cmd->resultInfo(); completeQueue.removeAll (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) { //kDebug(7116) <<"imapParser::parseUntagged - '" << result.cstr() <<"'"; parseOneWord(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); + CommandPtr 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 = what.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.clear(); parseFetch (number, result); } break; case 'S': if (qstrncmp(what, "STORE", what.size()) == 0) // deprecated store { seenUid.clear(); 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++; QByteArray option = parseOneWord(rest, true); switch (option[0]) { case 'A': // ALERT if (option == "ALERT") { rest.pos = rest.data.indexOf(']', 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.indexOf(']', rest.pos); QByteArray 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].toLatin1 ()) { 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.clear(); } kDebug(7116) <<"imapParser::parseResult - current box is now" << currentBox; } 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.clear(); } kDebug(7116) <<"imapParser::parseResult - current box is now" << currentBox; } break; default: break; } } void imapParser::parseCapability (parseString & result) { QByteArray data = result.cstr(); kAsciiToLower( data.data() ); imapCapabilities = QString::fromLatin1(data).split ( ' ', QString::SkipEmptyParts ); } 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(parseLiteral(result)); this_one.setName (KIMAP::decodeImapFolderName( parseLiteral(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) { parseOneWord (result); // skip mailbox name parseOneWord (result); // skip user id while ( true ) { const QByteArray word = parseOneWord (result); if ( word.isEmpty() ) break; lastResults.append (word); } } void imapParser::parseAcl (parseString & result) { parseOneWord (result); // skip mailbox name // The result is user1 perm1 user2 perm2 etc. The caller will sort it out. while ( !result.isEmpty() ) { const QByteArray word = parseLiteral(result); if ( word.isEmpty() ) break; lastResults.append (word); } } void imapParser::parseAnnotation (parseString & result) { parseOneWord (result); // skip mailbox name skipWS (result); parseOneWord (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); // The result is name1 value1 name2 value2 etc. The caller will sort it out. while ( !result.isEmpty() && result[0] != ')' ) { const QByteArray word = parseLiteral (result); if ( word.isEmpty() ) break; 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 QByteArray root = parseOneWord( result ); if ( root.isEmpty() ) { lastResults.append( "" ); } else { lastResults.append( root ); } if (result.isEmpty() || result[0] != '(') return; result.pos++; skipWS (result); QStringList triplet; while ( !result.isEmpty() && result[0] != ')' ) { const QByteArray word = parseLiteral(result); if ( word.isEmpty() ) break; triplet.append(word); } lastResults.append( triplet.join(" ") ); } void imapParser::parseQuotaRoot (parseString & result) { // quotaroot_response // ::= "QUOTAROOT" SP astring *(SP astring) parseOneWord (result); // skip mailbox name skipWS (result); if ( result.isEmpty() ) return; QStringList roots; while ( !result.isEmpty() ) { const QByteArray word = parseLiteral (result); if ( word.isEmpty() ) break; roots.append (word); } lastResults.append( roots.isEmpty() ? "" : roots.join( " " ) ); } void imapParser::parseCustom (parseString & result) { QByteArray word = parseLiteral (result, false, false); lastResults.append( word ); } void imapParser::parseOtherUser (parseString & result) { lastResults.append( parseOneWord ( result ) ); } void imapParser::parseDelegate (parseString & result) { const QString email = parseOneWord ( result ); QStringList rights; while ( !result.isEmpty() ) { QByteArray word = parseLiteral ( result, false, false ); rights.append( word ); } lastResults.append( email + ':' + rights.join( "," ) ); } void imapParser::parseOutOfOffice (parseString & result) { const QString state = parseOneWord (result); parseOneWord (result); // skip encoding QByteArray msg = parseLiteral (result, false, false); lastResults.append( state + '^' + QString::fromUtf8( msg ) ); } void imapParser::parseMyRights (parseString & result) { parseOneWord (result); // skip mailbox name Q_ASSERT( lastResults.isEmpty() ); // we can only be called once lastResults.append (parseOneWord (result) ); } void imapParser::parseSearch (parseString & result) { ulong value; while (parseOneNumber (result, value)) { lastResults.append (QString::number(value)); } } void imapParser::parseStatus (parseString & inWords) { lastStatus = imapInfo (); parseLiteral(inWords); // swallow the box if (inWords[0] != '(') return; inWords.pos++; skipWS (inWords); while (!inWords.isEmpty() && inWords[0] != ')') { ulong value; QByteArray label = parseOneWord(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, QList& list) { if ( inWords.isEmpty() ) return; if (inWords[0] != '(') { parseOneWord (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(parseLiteral(inWords)); retVal.setCommentRaw(parseLiteral(inWords)); retVal.setUser(parseLiteral(inWords)); retVal.setHost(parseLiteral(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(parseLiteral(inWords)); //subject envelope->setSubject(parseLiteral(inWords)); QList list; //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(parseLiteral(inWords)); //message-id envelope->setMessageId(parseLiteral(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 parseLiteral (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 QHash < QByteArray, QString > imapParser::parseDisposition (parseString & inWords) { QByteArray disposition; QHash < QByteArray, QString > retVal; if (inWords[0] != '(') { //disposition only disposition = parseOneWord (inWords); } else { inWords.pos++; skipWS (inWords); //disposition disposition = parseOneWord (inWords); retVal = parseParameters (inWords); if (inWords[0] != ')') return retVal; inWords.pos++; skipWS (inWords); } if (!disposition.isEmpty ()) { retVal.insert ("content-disposition", QString(disposition)); } return retVal; } // parse parameter pairs into a dictionary // caller must clean up the dictionary items QHash < QByteArray, QString > imapParser::parseParameters (parseString & inWords) { QHash < QByteArray, QString > retVal; if (inWords[0] != '(') { //better be NIL parseOneWord (inWords); } else { inWords.pos++; skipWS (inWords); while (!inWords.isEmpty () && inWords[0] != ')') { const QByteArray l1 = parseLiteral(inWords); const QByteArray l2 = parseLiteral(inWords); retVal.insert (l1.toLower(), QString(l2)); } if (inWords[0] != ')') return retVal; inWords.pos++; skipWS (inWords); } return retVal; } mimeHeader * imapParser::parseSimplePart (parseString & inWords, QString & inSection, mimeHeader * localPart) { QByteArray subtype; QByteArray typeStr; QHash < QByteArray, QString > parameters; ulong size; if (inWords[0] != '(') return 0; if (!localPart) localPart = new mimeHeader; localPart->setPartSpecifier (inSection); inWords.pos++; skipWS (inWords); //body type typeStr = parseLiteral(inWords); //body subtype subtype = parseLiteral(inWords); localPart->setType (typeStr + '/' + subtype); //body parameter parenthesized list parameters = parseParameters (inWords); { QHashIterator < QByteArray, QString > it (parameters); while (it.hasNext ()) { it.next(); localPart->setTypeParm (it.key (), it.value ()); } parameters.clear (); } //body id localPart->setID (parseLiteral(inWords)); //body description localPart->setDescription (parseLiteral(inWords)); //body encoding localPart->setEncoding (parseLiteral(inWords)); //body size if (parseOneNumber (inWords, size)) localPart->setLength (size); // type specific extensions if (localPart->getType().toUpper() == "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 parseLiteral(inWords); // body disposition parameters = parseDisposition (inWords); { QString disposition = parameters["content-disposition"]; localPart->setDisposition (disposition.toAscii ()); QHashIterator < QByteArray, QString > it (parameters); while (it.hasNext ()) { it.next(); localPart->setDispositionParm (it.key (), it.value ()); } 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 parseLiteral(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 "" parseOneWord (inWords); return 0; } inWords.pos++; skipWS (inWords); if (inWords[0] == '(') { QByteArray subtype; QHash< QByteArray, QString > parameters; QString outSection; 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 = parseOneWord (inWords); localPart->setType ("MULTIPART/" + subtype); // fetch parameters parameters = parseParameters (inWords); { QHashIterator < QByteArray, QString > it (parameters); while (it.hasNext ()) { it.next(); localPart->setTypeParm (it.key (), it.value ()); } parameters.clear (); } // body disposition parameters = parseDisposition (inWords); { QString disposition = parameters["content-disposition"]; localPart->setDisposition (disposition.toAscii ()); QHashIterator < QByteArray, QString > it (parameters); while (it.hasNext ()) { it.next(); localPart->setDispositionParm (it.key (), it.value ()); } 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 parseLiteral(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] == '[') { QByteArray specifier; QByteArray label; inWords.pos++; specifier = parseOneWord (inWords, true); if (inWords[0] == '(') { inWords.pos++; while (!inWords.isEmpty () && inWords[0] != ')') { label = parseOneWord (inWords); } if (inWords[0] == ')') inWords.pos++; } if (inWords[0] == ']') inWords.pos++; skipWS (inWords); // parse the header if (qstrncmp(specifier, "0", specifier.size()) == 0) { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); if (!envelope || seenUid.isEmpty ()) { kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii (); // don't know where to put it, throw it away parseLiteral(inWords, true); } else { kDebug(7116) <<"imapParser::parseBody - reading" << envelope << seenUid.toAscii (); // fill it up with data QString theHeader = parseLiteral(inWords, true); mimeIOQString myIO; myIO.setString (theHeader); envelope->parseHeader (myIO); } } else if (qstrncmp(specifier, "HEADER.FIELDS", specifier.size()) == 0) { // BODY[HEADER.FIELDS (References)] {n} //kDebug(7116) <<"imapParser::parseBody - HEADER.FIELDS:" // << QCString(label.data(), label.size()+1); if (qstrncmp(label, "REFERENCES", label.size()) == 0) { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); if (!envelope || seenUid.isEmpty ()) { kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii (); // don't know where to put it, throw it away parseLiteral (inWords, true); } else { QByteArray references = parseLiteral(inWords, true); int start = references.indexOf ('<'); int end = references.lastIndexOf ('>'); if (start < end) references = references.mid (start, end - start + 1); envelope->setReferences(references.simplified()); } } else { // not a header we care about throw it away parseLiteral(inWords, true); } } else { if (specifier.contains(".MIME") ) { mailHeader *envelope = new mailHeader; QString theHeader = parseLiteral(inWords, false); mimeIOQString myIO; myIO.setString (theHeader); envelope->parseHeader (myIO); if (lastHandled) lastHandled->setHeader (envelope); return; } // throw it away kDebug(7116) <<"imapParser::parseBody - discarding" << seenUid.toAscii (); parseLiteral(inWords, true); } } else // no part specifier { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); if (!envelope || seenUid.isEmpty ()) { kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii (); // don't know where to put it, throw it away parseSentence (inWords); } else { kDebug(7116) <<"imapParser::parseBody - reading" << envelope << seenUid.toAscii (); // 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 { const QByteArray word = parseLiteral(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" parseLiteral(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, QIODevice::WriteOnly ); if ( body ) body->serialize(stream); parseRelay(data); delete body; } break; case 'U': if (word == "UID") { seenUid = parseOneWord(inWords); mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); else lastHandled = new imapCache(); if (seenUid.isEmpty ()) { // unknown what to do kDebug(7116) <<"imapParser::parseFetch - UID empty"; } 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.startsWith("RFC822")) { // might be RFC822 RFC822.TEXT RFC822.HEADER parseLiteral(inWords, true); } break; case 'I': if (word == "INTERNALDATE") { const QByteArray date = parseOneWord(inWords); if (!lastHandled) lastHandled = new imapCache(); lastHandled->setDate(date); } break; case 'F': if (word == "FLAGS") { //kDebug(7116) <<"GOT FLAGS" << inWords.cstr(); if (!lastHandled) lastHandled = new imapCache(); lastHandled->setFlags (imapInfo::_flags (inWords.cstr())); } break; default: parseLiteral(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 parseLiteral(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: parseLiteral(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() ) ) delimEmpty = namespaceToDelimiter[QString()]; 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 QString prefix = QString::fromLatin1( parseOneWord( result ) ); // delimiter QString delim = QString::fromLatin1( parseOneWord( result ) ); kDebug(7116) <<"imapParser::parseNamespace ns='" << prefix <<"',delim='" << delim <<"'"; if ( ns == 0 ) { // at least one personal ns personalAvailable = true; } QString nsentry = QString::number( ns ) + '=' + prefix + '=' + 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; parseOneWord( result ); } else { // drop whatever it is parseOneWord( result ); } } if ( !delimEmpty.isEmpty() ) { // remember default delimiter namespaceToDelimiter[QString()] = delimEmpty; if ( !personalAvailable ) { // at least one personal ns would be nice kDebug(7116) <<"imapParser::parseNamespace - registering own personal ns"; QString nsentry = "0==" + delimEmpty; imapNamespaces.append( nsentry ); } } } int imapParser::parseLoop () { parseString result; if (!parseReadLine(result.data)) return -1; //kDebug(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 kDebug(7116) <<"imapParser::parseLoop - unhandledResponse:" << result.cstr(); unhandled << result.cstr(); } else { - imapCommand *current = sentQueue.at (0); + CommandPtr current = sentQueue.at (0); switch (result[0]) { case '*': result.data.resize(result.data.size() - 2); // tie off CRLF parseUntagged (result); break; case '+': continuation = result.data; break; default: { QByteArray tag = parseLiteral(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.removeAll (current); completeQueue.append (current); if (result.length()) parseResult (resultCode, result, current->command()); } else { kDebug(7116) <<"imapParser::parseLoop - unknown tag '" << tag <<"'"; QByteArray 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, long len, long 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, long 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 (); kDebug(7116) <<"imapParser::parseURL" << _box; int paramStart = _box.indexOf("/;"); if ( paramStart > -1 ) { QString paramString = _box.right( _box.length() - paramStart-2 ); parameters = paramString.split (';', QString::SkipEmptyParts); //split parameters _box.truncate( paramStart ); // strip parameters } // extract parameters for (QStringList::ConstIterator it (parameters.constBegin ()); it != parameters.constEnd (); ++it) { QString temp = (*it); // if we have a '/' separator we'll just nuke it int pt = temp.indexOf ('/'); if (pt > 0) temp.truncate(pt); if (temp.startsWith("section=", Qt::CaseInsensitive)) _section = temp.right (temp.length () - 8); else if (temp.startsWith("type=", Qt::CaseInsensitive)) _type = temp.right (temp.length () - 5); else if (temp.startsWith("uid=", Qt::CaseInsensitive)) _uid = temp.right (temp.length () - 4); else if (temp.startsWith("uidvalidity=", Qt::CaseInsensitive)) _validity = temp.right (temp.length () - 12); else if (temp.startsWith("info=", Qt::CaseInsensitive)) _info = temp.right (temp.length () - 5); } // kDebug(7116) <<"URL: section=" << _section <<", type=" << _type <<", uid=" << _uid; // kDebug(7116) <<"URL: user()" << _url.user(); // kDebug(7116) <<"URL: path()" << _url.path(); // kDebug(7116) <<"URL: encodedPathAndQuery()" << _url.encodedPathAndQuery(); 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); } kDebug(7116) <<"URL: box=" << _box <<", section=" << _section <<", type=" << _type << ", uid=" << _uid << ", validity=" << _validity << ", info=" << _info; } QByteArray imapParser::parseLiteral(parseString & inWords, bool relay, bool stopAtBracket) { if (!inWords.isEmpty() && inWords[0] == '{') { QByteArray retVal; int runLen = inWords.find ('}', 1); if (runLen > 0) { bool proper; long runLenSave = runLen + 1; QByteArray tmpstr(runLen, '\0'); 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 = rv; inWords.clear(); parseReadLine (inWords.data); // must get more // no duplicate data transfers relay = false; } else { kDebug(7116) <<"imapParser::parseLiteral - error parsing {} -" /*<< strLen*/; } } else { inWords.clear(); kDebug(7116) <<"imapParser::parseLiteral - error parsing unmatched {"; } skipWS (inWords); return retVal; } return parseOneWord(inWords, stopAtBracket); } // does not know about literals ( {7} literal ) QByteArray imapParser::parseOneWord (parseString & inWords, bool stopAtBracket) { uint len = inWords.length(); if (len == 0) { return QByteArray(); } 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) { QByteArray retVal; retVal.resize(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.resize( len - offset ); inWords.pos += i; skipWS (inWords); return retVal; } else { kDebug(7116) <<"imapParser::parseOneWord - error parsing unmatched \""; QByteArray retVal = inWords.cstr(); inWords.clear(); 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; } QByteArray retVal; retVal.resize(i); inWords.takeLeftNoResize(retVal, i); inWords.pos += i; if (retVal == "NIL") { retVal.truncate(0); } skipWS (inWords); return retVal; } } bool imapParser::parseOneNumber (parseString & inWords, ulong & num) { bool valid; num = parseOneWord(inWords, true).toULong(&valid); return valid; } bool imapParser::hasCapability (const QString & cap) { QString c = cap.toLower(); // kDebug(7116) <<"imapParser::hasCapability - Looking for '" << cap <<"'"; for (QStringList::ConstIterator it = imapCapabilities.constBegin (); it != imapCapabilities.constEnd (); ++it) { // kDebug(7116) <<"imapParser::hasCapability - Examining '" << (*it) <<"'"; if ( !(kasciistricmp(c.toAscii(), (*it).toAscii())) ) { return true; } } return false; } void imapParser::removeCapability (const QString & cap) { imapCapabilities.removeAll(cap.toLower()); } QString imapParser::namespaceForBox( const QString & box ) { kDebug(7116) <<"imapParse::namespaceForBox" << box; QString myNamespace; if ( !box.isEmpty() ) { const QList list = namespaceToDelimiter.keys(); QString cleanPrefix; for ( QList::ConstIterator it = list.begin(); it != list.end(); ++it ) { if ( !(*it).isEmpty() && box.contains( *it ) ) return (*it); } } return myNamespace; } diff --git a/kioslave/imap4/imapparser.h b/kioslave/imap4/imapparser.h index d39dde363..6666492b2 100644 --- a/kioslave/imap4/imapparser.h +++ b/kioslave/imap4/imapparser.h @@ -1,476 +1,476 @@ #ifndef _IMAPPARSER_H #define _IMAPPARSER_H /********************************************************************** * * imapparser.h - IMAP4rev1 Parser * Copyright (C) 2001-2002 Michael Haeckel * 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 s.carstens@gmx.de * *********************************************************************/ #include "imap4-config.h" #include "imaplist.h" #include "imapcommand.h" #include "imapinfo.h" #include "mailheader.h" #include #include #include #include #include #include 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[](int i) const { return data.length() > (i + pos) ? data[i + pos] : '\0'; } bool isEmpty() const { return pos >= data.size(); } QByteArray cstr() const { if (pos >= data.size()) return QByteArray(); return QByteArray(data.data() + pos, data.size() - pos); } int find(char c, int index = 0) const { int res = data.indexOf(c, index + pos); return (res == -1) ? res : (res - pos); } // Warning: does not check for going past end of "data" void takeLeftNoResize(QByteArray& dest, uint len) const { memmove(dest.data(), data.data() + pos, len); } // Warning: does not check for going past end of "data" void takeMidNoResize(QByteArray& dest, uint start, uint len) const { memmove(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 = s.toLatin1(); } QByteArray data; int 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; } QByteArray getDate () { return myDate; } void setDate (const QByteArray & _str) { myDate = _str; } void clear() { if (myHeader) delete myHeader; myHeader = NULL; mySize = 0; myFlags = 0; myDate = QByteArray(); myUid = 0; } protected: mailHeader * myHeader; ulong mySize; ulong myFlags; ulong myUid; QByteArray 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 KIMAP::decodeImapFolderName(currentBox); } /** * @brief do setup and send the command to parseWriteLine * @param aCmd The command to perform * @return The completed command */ - imapCommand *sendCommand (imapCommand * aCmd); + CommandPtr sendCommand (CommandPtr 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); + CommandPtr doCommand (CommandPtr 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()); /** @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, QList& 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) */ QHash< QByteArray, QString > parseParameters (parseString & inWords); /** * parse the disposition list (disposition (name value pairs)) * the disposition has the key 'content-disposition' */ QHash< QByteArray, 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, long len, long relay = 0); /** read at least a line (up to CRLF) */ virtual bool parseReadLine (QByteArray & buffer, long 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 */ QByteArray parseLiteral(parseString & inWords, bool relay = false, bool stopAtBracket = false); // static parser routines, can be used elsewhere /** parse one word (maybe quoted) upto next space " ) ] } */ static QByteArray parseOneWord (parseString & inWords, bool stopAtBracket = false); /** 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 */ QList < imapList > listResponses; /** @brief queues handling the running commands */ - QList < imapCommand *> sentQueue; // no autodelete - QList < imapCommand *> completeQueue; // autodelete !! + QList sentQueue; + QList completeQueue; /** * 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 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