diff --git a/kioslave/imap4/imap4.cpp b/kioslave/imap4/imap4.cpp index ca0e42e9a..bbe67fe31 100644 --- a/kioslave/imap4/imap4.cpp +++ b/kioslave/imap4/imap4.cpp @@ -1,2653 +1,2653 @@ /********************************************************************** * * 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 "imap4.h" #include #include #include #include #include #include #include #include #include extern "C" { #include } #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); } if (!initSASL()) ::exit(-1); //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; sasl_done(); 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) { CommandPtr cmd = doCommand (imapCommand::clientNoop()); completeQueue.removeAll(cmd); } if (aSequence.isEmpty ()) { aSequence = "1:*"; } mProcessedSize = 0; 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; 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()) { 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") ; 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); 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; } 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 = 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; 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 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 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 = 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; 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; 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) { CommandPtr cmd = doCommand(imapCommand::clientClose()); completeQueue.removeAll(cmd); setState(ISTATE_LOGIN); } // We unsubscribe, otherwise we get ghost folders on UW-IMAP 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) { CommandPtr cmd = doCommand( imapCommand::clientStore("1:*", "+FLAGS.SILENT", "\\DELETED")); if (cmd->result () != "OK") stillOk = false; completeQueue.removeAll(cmd); } if (stillOk) { CommandPtr cmd = doCommand(imapCommand::clientClose()); if (cmd->result () != "OK") stillOk = false; completeQueue.removeAll(cmd); setState(ISTATE_LOGIN); } if (stillOk) { 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: { 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; 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 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); 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); 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); 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, QString::fromLatin1("ACL") ); } break; } case 'M': { // annotatemore int cmd; stream >> cmd; if ( hasCapability( "ANNOTATEMORE" ) ) { specialAnnotateMoreCommand( cmd, stream ); } else { error( ERR_UNSUPPORTED_ACTION, QString::fromLatin1("ANNOTATEMORE") ); } break; } case 'Q': { // quota int cmd; stream >> cmd; if ( hasCapability( "QUOTA" ) ) { specialQuotaCommand( cmd, stream ); } else { error( ERR_UNSUPPORTED_ACTION, QString::fromLatin1("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"; } 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; 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; 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; 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; 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; 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, true)) return; 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; 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; 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"; 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; 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; 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 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); } 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()) { 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 { CommandPtr cmd = doCommand(imapCommand::clientStatus(aBox, aSection)); ok = cmd->result() == "OK"; cmdInfo = cmd->resultInfo(); completeQueue.removeAll(cmd); } if (!ok) { bool found = false; 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 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") { CommandPtr cmd = doCommand (imapCommand::clientExpunge()); completeQueue.removeAll (cmd); } if (getState() != ISTATE_CONNECT) { 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; 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 (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); 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"))) { CommandPtr cmd = doCommand (imapCommand::clientStartTLS()); if (cmd->result () == "OK") { completeQueue.removeAll(cmd); if (startSsl()) { kDebug(7116) <<"TLS mode has been enabled."; 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(); return false; } } else completeQueue.removeAll(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" ); } // starting from Cyrus IMAP 2.3.9, shared seen flags are available QRegExp regExp( "Cyrus\\sIMAP[4]{0,1}\\sv(\\d+)\\.(\\d+)\\.(\\d+)", Qt::CaseInsensitive ); if ( regExp.indexIn( greeting ) >= 0 ) { const int major = regExp.cap( 1 ).toInt(); const int minor = regExp.cap( 2 ).toInt(); const int patch = regExp.cap( 3 ).toInt(); if ( major > 2 || (major == 2 && (minor > 3 || (minor == 3 && patch > 9))) ) { kDebug(7116) << "Cyrus IMAP >= 2.3.9 detected, enabling shared seen flag support"; imapCapabilities.append( "x-kmail-sharedseen" ); } } 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 { 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; } } 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); } // 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); } 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 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 = "/"; + _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) ) + if ( contentEncoding.startsWith(QLatin1String("quoted-printable"), Qt::CaseInsensitive) ) decoded = KCodecs::quotedPrintableDecode(outputCache); - else if ( contentEncoding.startsWith("base64", Qt::CaseInsensitive) ) + else if ( contentEncoding.startsWith(QLatin1String("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; 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/imapinfo.cpp b/kioslave/imap4/imapinfo.cpp index e9177ff5b..125b888e3 100644 --- a/kioslave/imap4/imapinfo.cpp +++ b/kioslave/imap4/imapinfo.cpp @@ -1,239 +1,239 @@ /********************************************************************** * * imapinfo.cc - IMAP4rev1 SELECT / EXAMINE handler * Copyright (C) 2000 Sven Carstens * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Send comments and bug fixes to * *********************************************************************/ /* References: RFC 2060 - Internet Message Access Protocol - Version 4rev1 - December 1996 RFC 2192 - IMAP URL Scheme - September 1997 RFC 1731 - IMAP Authentication Mechanisms - December 1994 (Discusses KERBEROSv4, GSSAPI, and S/Key) RFC 2195 - IMAP/POP AUTHorize Extension for Simple Challenge/Response - September 1997 (CRAM-MD5 authentication method) RFC 2104 - HMAC: Keyed-Hashing for Message Authentication - February 1997 Supported URLs: imap://server/ - Prompt for user/pass, list all folders in home directory imap://user:pass@server/ - Uses LOGIN to log in imap://user;AUTH=method:pass@server/ - Uses AUTHENTICATE to log in imap://server/folder/ - List messages in folder */ #include "imapinfo.h" #include "imapparser.h" #include imapInfo::imapInfo ():count_ (0), recent_ (0), unseen_ (0), uidValidity_ (0), uidNext_ (0), flags_ (0), permanentFlags_ (0), readWrite_ (false), countAvailable_ (false), recentAvailable_ (false), unseenAvailable_ (false), uidValidityAvailable_ (false), uidNextAvailable_ (false), flagsAvailable_ (false), permanentFlagsAvailable_ (false), readWriteAvailable_ (false) { } imapInfo::imapInfo (const imapInfo & mi):count_ (mi.count_), recent_ (mi.recent_), unseen_ (mi.unseen_), uidValidity_ (mi.uidValidity_), uidNext_ (mi.uidNext_), flags_ (mi.flags_), permanentFlags_ (mi.permanentFlags_), readWrite_ (mi.readWrite_), countAvailable_ (mi.countAvailable_), recentAvailable_ (mi.recentAvailable_), unseenAvailable_ (mi.unseenAvailable_), uidValidityAvailable_ (mi.uidValidityAvailable_), uidNextAvailable_ (mi.uidNextAvailable_), flagsAvailable_ (mi.flagsAvailable_), permanentFlagsAvailable_ (mi.permanentFlagsAvailable_), readWriteAvailable_ (mi.readWriteAvailable_) { } imapInfo & imapInfo::operator = (const imapInfo & mi) { // Avoid a = a. if (this == &mi) return *this; count_ = mi.count_; recent_ = mi.recent_; unseen_ = mi.unseen_; uidValidity_ = mi.uidValidity_; uidNext_ = mi.uidNext_; flags_ = mi.flags_; permanentFlags_ = mi.permanentFlags_; readWrite_ = mi.readWrite_; countAvailable_ = mi.countAvailable_; recentAvailable_ = mi.recentAvailable_; unseenAvailable_ = mi.unseenAvailable_; uidValidityAvailable_ = mi.uidValidityAvailable_; uidNextAvailable_ = mi.uidNextAvailable_; flagsAvailable_ = mi.flagsAvailable_; permanentFlagsAvailable_ = mi.permanentFlagsAvailable_; readWriteAvailable_ = mi.readWriteAvailable_; return *this; } imapInfo::imapInfo (const QStringList & list):count_ (0), recent_ (0), unseen_ (0), uidValidity_ (0), uidNext_ (0), flags_ (0), permanentFlags_ (0), readWrite_ (false), countAvailable_ (false), recentAvailable_ (false), unseenAvailable_ (false), uidValidityAvailable_ (false), uidNextAvailable_ (false), flagsAvailable_ (false), permanentFlagsAvailable_ (false), readWriteAvailable_ (false) { for (QStringList::ConstIterator it (list.begin ()); it != list.end (); ++it) { QString line (*it); line.truncate(line.length() - 2); QStringList tokens(line.split (' ', QString::SkipEmptyParts)); kDebug(7116) <<"Processing:" << line; if (tokens[0] != "*") continue; if (tokens[1] == "OK") { if (tokens[2] == "[UNSEEN") setUnseen (tokens[3].left (tokens[3].length () - 1).toULong ()); else if (tokens[2] == "[UIDVALIDITY") setUidValidity (tokens[3].left (tokens[3].length () - 1).toULong ()); else if (tokens[2] == "[UIDNEXT") setUidNext (tokens[3].left (tokens[3].length () - 1).toULong ()); else if (tokens[2] == "[PERMANENTFLAGS") { int flagsStart = line.indexOf('('); int flagsEnd = line.indexOf(')'); kDebug(7116) <<"Checking permFlags from" << flagsStart <<" to" << flagsEnd; if ((-1 != flagsStart) && (-1 != flagsEnd) && flagsStart < flagsEnd) setPermanentFlags (_flags (line.mid (flagsStart, flagsEnd).toLatin1())); } else if (tokens[2] == "[READ-WRITE") { setReadWrite (true); } else if (tokens[2] == "[READ-ONLY") { setReadWrite (false); } else { kDebug(7116) <<"unknown token2:" << tokens[2]; } } else if (tokens[1] == "FLAGS") { int flagsStart = line.indexOf ('('); int flagsEnd = line.indexOf (')'); if ((-1 != flagsStart) && (-1 != flagsEnd) && flagsStart < flagsEnd) setFlags (_flags (line.mid (flagsStart, flagsEnd).toLatin1() )); } else { if (tokens[2] == "EXISTS") setCount (tokens[1].toULong ()); else if (tokens[2] == "RECENT") setRecent (tokens[1].toULong ()); else kDebug(7116) <<"unknown token1/2:" << tokens[1] << tokens[2]; } } } ulong imapInfo::_flags( const QByteArray &inFlags ) { ulong flags = 0; parseString flagsString; flagsString.data = inFlags; if ( flagsString.isEmpty() ) { return flags; } if ( flagsString[0] == '(' ) { flagsString.pos++; } while( !flagsString.isEmpty () && flagsString[0] != ')' ) { QByteArray entry = imapParser::parseOneWord(flagsString).toUpper(); if (entry.isEmpty ()) flagsString.clear(); else if (0 != entry.contains ("\\SEEN")) flags ^= Seen; else if (0 != entry.contains ("\\ANSWERED")) flags ^= Answered; else if (0 != entry.contains ("\\FLAGGED")) flags ^= Flagged; else if (0 != entry.contains ("\\DELETED")) flags ^= Deleted; else if (0 != entry.contains ("\\DRAFT")) flags ^= Draft; else if (0 != entry.contains ("\\RECENT")) flags ^= Recent; else if (0 != entry.contains ("\\*")) flags ^= User; - // non standard kmail falgs + // non standard kmail flags else if ( entry.contains( "KMAILFORWARDED" ) || entry.contains( "$FORWARDED" ) ) flags = flags | Forwarded; else if ( entry.contains( "KMAILTODO" ) || entry.contains( "$TODO" ) ) flags = flags | Todo; else if ( entry.contains( "KMAILWATCHED" ) || entry.contains( "$WATCHED" ) ) flags = flags | Watched; else if ( entry.contains( "KMAILIGNORED" ) || entry.contains( "$IGNORED" ) ) flags = flags | Ignored; } return flags; } diff --git a/kioslave/imap4/imapparser.cpp b/kioslave/imap4/imapparser.cpp index e9df7fca4..d8400b299 100644 --- a/kioslave/imap4/imapparser.cpp +++ b/kioslave/imap4/imapparser.cpp @@ -1,2039 +1,2039 @@ /********************************************************************** * * 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 extern "C" { #include } #include #include #include #include #include #include #include #include #include #include using namespace KIMAP; 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 } }; imapParser::imapParser () { currentState = ISTATE_NO; commandCounter = 0; lastHandled = 0; } imapParser::~imapParser () { delete lastHandled; lastHandled = 0; } CommandPtr imapParser::doCommand (CommandPtr aCmd) { int pl = 0; sendCommand (aCmd); while (pl != -1 && !aCmd->isComplete ()) { while ((pl = parseLoop ()) == 0) ; } return 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) { CommandPtr cmd; bool retVal = false; cmd = 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); return retVal; } 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; } bool imapParser::clientAuthenticate ( KIO::SlaveBase *slave, KIO::AuthInfo &ai, const QString & aFQDN, const QString & aAuth, bool isSSL, QString & resultInfo) { bool retVal = false; 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; } 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 (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. 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 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(QString::fromUtf8(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"; + 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"; + inSection = '0'; // set the section if ( !outSection.isEmpty() ) { localPart->setPartSpecifier(outSection); } else { localPart->setPartSpecifier(inSection); } // is multipart (otherwise it is 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")) + else if (word.startsWith("RFC822")) //krazy:exclude=strings { // 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 { 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)) + if (temp.startsWith(QLatin1String("section="), Qt::CaseInsensitive)) _section = temp.right (temp.length () - 8); - else if (temp.startsWith("type=", Qt::CaseInsensitive)) + else if (temp.startsWith(QLatin1String("type="), Qt::CaseInsensitive)) _type = temp.right (temp.length () - 5); - else if (temp.startsWith("uid=", Qt::CaseInsensitive)) + else if (temp.startsWith(QLatin1String("uid="), Qt::CaseInsensitive)) _uid = temp.right (temp.length () - 4); - else if (temp.startsWith("uidvalidity=", Qt::CaseInsensitive)) + else if (temp.startsWith(QLatin1String("uidvalidity="), Qt::CaseInsensitive)) _validity = temp.right (temp.length () - 12); - else if (temp.startsWith("info=", Qt::CaseInsensitive)) + else if (temp.startsWith(QLatin1String("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/mimeheader.cpp b/kioslave/imap4/mimeheader.cpp index 28547f6c8..3c8917c5f 100644 --- a/kioslave/imap4/mimeheader.cpp +++ b/kioslave/imap4/mimeheader.cpp @@ -1,726 +1,726 @@ /*************************************************************************** mimeheader.cc - description ------------------- begin : Fri Oct 20 2000 copyright : (C) 2000 by Sven Carstens email : s.carstens@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mimeheader.h" #include "mimehdrline.h" #include "mailheader.h" #include // #include #include #include #include #include #include #include #include using namespace KIMAP; mimeHeader::mimeHeader () : typeList (), dispositionList (), _contentType("application/octet-stream"), _contentDisposition(), _contentDescription() { // Case insensitive hashes are killing us. Also are they too small? nestedMessage = NULL; contentLength = 0; } mimeHeader::~mimeHeader () { } /* QPtrList mimeHeader::getAllParts() { QPtrList retVal; // caller is responsible for clearing retVal.setAutoDelete( false ); nestedParts.setAutoDelete( false ); // shallow copy retVal = nestedParts; // can't have duplicate pointers nestedParts.clear(); // restore initial state nestedParts.setAutoDelete( true ); return retVal; } */ void mimeHeader::addHdrLine (mimeHdrLine * aHdrLine) { mimeHdrLine *addLine = new mimeHdrLine (aHdrLine); if (addLine) { originalHdrLines.append (addLine); if (qstrnicmp (addLine->getLabel (), "Content-", 8)) { additionalHdrLines.append (addLine); } else { int skip; const char *aCStr = addLine->getValue ().data (); QHash < QString, QString > *aList = 0; skip = mimeHdrLine::parseSeparator (';', aCStr); if (skip > 0) { int cut = 0; if (skip >= 2) { if (aCStr[skip - 1] == '\r') cut++; if (aCStr[skip - 1] == '\n') cut++; if (aCStr[skip - 2] == '\r') cut++; if (aCStr[skip - 1] == ';') cut++; } QByteArray mimeValue(aCStr, skip - cut); if (!qstricmp (addLine->getLabel (), "Content-Disposition")) { aList = &dispositionList; setDisposition( mimeValue ); } else if (!qstricmp (addLine->getLabel (), "Content-Type")) { aList = &typeList; setType( mimeValue ); } else if (!qstricmp (addLine->getLabel (), "Content-Transfer-Encoding")) { setEncoding( mimeValue ); } else if (!qstricmp (addLine->getLabel (), "Content-ID")) { setID( mimeValue ); } else if (!qstricmp (addLine->getLabel (), "Content-Description")) { setDescription( mimeValue ); } else if (!qstricmp (addLine->getLabel (), "Content-MD5")) { setMD5( mimeValue ); } else if (!qstricmp (addLine->getLabel (), "Content-Length")) { contentLength = mimeValue.toUInt (); } else { additionalHdrLines.append (addLine); } // cout << addLine->getLabel().data() << ": '" << mimeValue.data() << "'" << endl; aCStr += skip; while ((skip = mimeHdrLine::parseSeparator (';', aCStr))) { if (skip > 0) { if (aList) addParameter (QByteArray (aCStr, skip).simplified(), *aList); mimeValue = QByteArray (addLine->getValue ().data (), skip); aCStr += skip; } else break; } } } } } void mimeHeader::addParameter (const QByteArray& aParameter, QHash < QString, QString > &aList) { QString aValue; QByteArray aLabel; int pos = aParameter.indexOf ('='); // cout << aParameter.left(pos).data(); aValue = QString::fromLatin1 (aParameter.right (aParameter.length () - pos - 1)); aLabel = aParameter.left (pos); if (aValue[0] == '"') aValue = aValue.mid (1, aValue.length () - 2); aList.insert (aLabel.toLower(), aValue); // cout << "=" << aValue->data() << endl; } QString mimeHeader::getDispositionParm (const QByteArray& aStr) { return getParameter (aStr, dispositionList); } QString mimeHeader::getTypeParm (const QByteArray& aStr) { return getParameter (aStr, typeList); } void mimeHeader::setDispositionParm (const QByteArray& aLabel, const QString& aValue) { setParameter (aLabel, aValue, dispositionList); return; } void mimeHeader::setTypeParm (const QByteArray& aLabel, const QString& aValue) { setParameter (aLabel, aValue, typeList); } QHashIterator < QString, QString > mimeHeader::getDispositionIterator () { return QHashIterator < QString, QString > (dispositionList); } QHashIterator < QString, QString > mimeHeader::getTypeIterator () { return QHashIterator < QString, QString > (typeList); } QListIterator < mimeHdrLine *> mimeHeader::getOriginalIterator () { return QListIterator < mimeHdrLine *> (originalHdrLines); } QListIterator < mimeHdrLine *> mimeHeader::getAdditionalIterator () { return QListIterator < mimeHdrLine *> (additionalHdrLines); } void mimeHeader::outputHeader (mimeIO & useIO) { if (!getDisposition ().isEmpty ()) { useIO.outputMimeLine (QByteArray ("Content-Disposition: ") + getDisposition () + outputParameter (dispositionList)); } if (!getType ().isEmpty ()) { useIO.outputMimeLine (QByteArray ("Content-Type: ") + getType () + outputParameter (typeList)); } if (!getDescription ().isEmpty ()) useIO.outputMimeLine (QByteArray ("Content-Description: ") + getDescription ()); if (!getID ().isEmpty ()) useIO.outputMimeLine (QByteArray ("Content-ID: ") + getID ()); if (!getMD5 ().isEmpty ()) useIO.outputMimeLine (QByteArray ("Content-MD5: ") + getMD5 ()); if (!getEncoding ().isEmpty ()) useIO.outputMimeLine (QByteArray ("Content-Transfer-Encoding: ") + getEncoding ()); QListIterator < mimeHdrLine *> ait = getAdditionalIterator (); mimeHdrLine *hdrline; while (ait.hasNext ()) { hdrline = ait.next(); useIO.outputMimeLine (hdrline->getLabel () + ": " + hdrline->getValue ()); } useIO.outputMimeLine (QByteArray ("")); } QString mimeHeader::getParameter (const QByteArray& aStr, QHash < QString, QString > &aDict) { QString retVal, found; //see if it is a normal parameter found = aDict.value ( aStr ); if ( found.isEmpty() ) { //might be a continuated or encoded parameter found = aDict.value ( aStr + '*' ); if ( found.isEmpty() ) { //continuated parameter QString decoded, encoded; int part = 0; do { QByteArray search; search.setNum (part); search = aStr + '*' + search; found = aDict.value (search); if ( found.isEmpty() ) { found = aDict.value (search + '*'); if ( !found.isEmpty() ) encoded += KIMAP::encodeRFC2231String (found); } else { encoded += found; } part++; } while ( !found.isEmpty() ); if (encoded.contains ('\'')) { retVal = KIMAP::decodeRFC2231String (encoded.toLocal8Bit ()); } else { retVal = KIMAP::decodeRFC2231String (QByteArray ("''") + encoded.toLocal8Bit ()); } } else { //simple encoded parameter retVal = KIMAP::decodeRFC2231String (found.toLocal8Bit ()); } } else { retVal = found; } return retVal; } void mimeHeader::setParameter (const QByteArray& aLabel, const QString& aValue, QHash < QString, QString > &aDict) { bool encoded = true; uint vlen, llen; QString val = aValue; //see if it needs to get encoded if (encoded && !aLabel.contains('*')) { val = KIMAP::encodeRFC2231String (aValue); } //kDebug(7116) <<"mimeHeader::setParameter() - val = '" << val <<"'"; //see if it needs to be truncated vlen = val.length(); llen = aLabel.length(); if (vlen + llen + 4 > 80 && llen < 80 - 8 - 2 ) { const int limit = 80 - 8 - 2 - (int)llen; // the -2 is there to allow extending the length of a part of val // by 1 or 2 in order to prevent an encoded character from being // split in half int i = 0; QString shortValue; QByteArray shortLabel; while (!val.isEmpty ()) { int partLen; // the length of the next part of the value if ( limit >= int(vlen) ) { // the rest of the value fits completely into one continued header partLen = vlen; } else { partLen = limit; // make sure that we don't split an encoded char in half if ( val[partLen-1] == '%' ) { partLen += 2; } else if ( partLen > 1 && val[partLen-2] == '%' ) { partLen += 1; } // make sure partLen does not exceed vlen (could happen in case of // an incomplete encoded char) if ( partLen > int(vlen) ) { partLen = vlen; } } shortValue = val.left( partLen ); shortLabel.setNum (i); shortLabel = aLabel + '*' + shortLabel; val = val.right( vlen - partLen ); vlen = vlen - partLen; if (encoded) { if (i == 0) { shortValue = "''" + shortValue; } shortLabel += '*'; } //kDebug(7116) <<"mimeHeader::setParameter() - shortLabel = '" << shortLabel <<"'"; //kDebug(7116) <<"mimeHeader::setParameter() - shortValue = '" << shortValue <<"'"; //kDebug(7116) <<"mimeHeader::setParameter() - val = '" << val <<"'"; aDict.insert (shortLabel.toLower(), shortValue); i++; } } else { aDict.insert (aLabel.toLower(), val); } } QByteArray mimeHeader::outputParameter (QHash < QString, QString > &aDict) { QByteArray retVal; QHashIterator < QString, QString > it (aDict); while (it.hasNext ()) { it.next(); retVal += (";\n\t" + it.key() + '=').toLatin1 (); if (it.value().indexOf (' ') > 0 || it.value().indexOf (';') > 0) { retVal += '"' + it.value().toUtf8 () + '"'; } else { retVal += it.value().toUtf8 (); } } retVal += '\n'; return retVal; } void mimeHeader::outputPart (mimeIO & useIO) { QListIterator < mimeHeader *> nestedParts = getNestedIterator (); QByteArray boundary; if (!getTypeParm ("boundary").isEmpty ()) boundary = getTypeParm ("boundary").toLatin1 (); outputHeader (useIO); if (!getPreBody ().isEmpty ()) useIO.outputMimeLine (getPreBody ()); if (getNestedMessage ()) getNestedMessage ()->outputPart (useIO); mimeHeader *mimeline; while (nestedParts.hasNext()) { mimeline = nestedParts.next(); if (!boundary.isEmpty ()) useIO.outputMimeLine ("--" + boundary); mimeline->outputPart (useIO); } if (!boundary.isEmpty ()) useIO.outputMimeLine ("--" + boundary + "--"); if (!getPostBody ().isEmpty ()) useIO.outputMimeLine (getPostBody ()); } #if 0 int mimeHeader::parsePart (mimeIO & useIO, const QString& boundary) { int retVal = 0; bool mbox = false; QByteArray preNested, postNested; mbox = parseHeader (useIO); kDebug(7116) <<"mimeHeader::parsePart - parsing part '" << getType () <<"'"; if (!qstrnicmp (getType (), "Multipart", 9)) { retVal = parseBody (useIO, preNested, getTypeParm ("boundary")); //this is a message in mime format stuff setPreBody (preNested); int localRetVal; do { mimeHeader *aHeader = new mimeHeader; // set default type for multipart/digest if (!qstrnicmp (getType (), "Multipart/Digest", 16)) aHeader->setType ("Message/RFC822"); localRetVal = aHeader->parsePart (useIO, getTypeParm ("boundary")); addNestedPart (aHeader); } while (localRetVal); //get nested stuff } if (!qstrnicmp (getType (), "Message/RFC822", 14)) { mailHeader *msgHeader = new mailHeader; retVal = msgHeader->parsePart (useIO, boundary); setNestedMessage (msgHeader); } else { retVal = parseBody (useIO, postNested, boundary, mbox); //just a simple part remaining setPostBody (postNested); } return retVal; } int mimeHeader::parseBody (mimeIO & useIO, QByteArray & messageBody, const QString& boundary, bool mbox) { QByteArray inputStr; QByteArray buffer; QString partBoundary; QString partEnd; int retVal = 0; //default is last part if (!boundary.isEmpty ()) { partBoundary = QString ("--") + boundary; partEnd = QString ("--") + boundary + "--"; } while (useIO.inputLine (inputStr)) { //check for the end of all parts if (!partEnd.isEmpty () && !qstrnicmp (inputStr, partEnd.toLatin1 (), partEnd.length () - 1)) { retVal = 0; //end of these parts break; } else if (!partBoundary.isEmpty () && !qstrnicmp (inputStr, partBoundary.toLatin1 (), partBoundary.length () - 1)) { retVal = 1; //continue with next part break; } else if (mbox && inputStr.startsWith ("From ") ) { retVal = 0; // end of mbox break; } buffer += inputStr; if (buffer.length () > 16384) { messageBody += buffer; buffer = ""; } } messageBody += buffer; return retVal; } #endif bool mimeHeader::parseHeader (mimeIO & useIO) { bool mbox = false; bool first = true; mimeHdrLine my_line; QByteArray inputStr; kDebug(7116) <<"mimeHeader::parseHeader - starting parsing"; while (useIO.inputLine (inputStr)) { int appended; - if (!inputStr.startsWith ("From ") || !first) + if (!inputStr.startsWith("From ") || !first) //krazy:exclude=strings { first = false; appended = my_line.appendStr (inputStr); if (!appended) { addHdrLine (&my_line); appended = my_line.setStr (inputStr); } if (appended <= 0) break; } else { mbox = true; first = false; } inputStr = QByteArray(); } kDebug(7116) <<"mimeHeader::parseHeader - finished parsing"; return mbox; } mimeHeader * mimeHeader::bodyPart (const QString & _str) { // see if it is nested a little deeper int pt = _str.indexOf('.'); if (pt != -1) { QString tempStr = _str; mimeHeader *tempPart; tempStr = _str.right (_str.length () - pt - 1); if (nestedMessage) { kDebug(7116) <<"mimeHeader::bodyPart - recursing message"; tempPart = nestedMessage->nestedParts.at (_str.left(pt).toULong() - 1); } else { kDebug(7116) <<"mimeHeader::bodyPart - recursing mixed"; tempPart = nestedParts.at (_str.left(pt).toULong() - 1); } if (tempPart) tempPart = tempPart->bodyPart (tempStr); return tempPart; } kDebug(7116) <<"mimeHeader::bodyPart - returning part" << _str; // or pick just the plain part if (nestedMessage) { kDebug(7116) <<"mimeHeader::bodyPart - message"; return nestedMessage->nestedParts.at (_str.toULong () - 1); } kDebug(7116) <<"mimeHeader::bodyPart - mixed"; return nestedParts.at (_str.toULong () - 1); } void mimeHeader::serialize(QDataStream& stream) { int nestedcount = nestedParts.count(); if (nestedParts.isEmpty() && nestedMessage) nestedcount = 1; stream << nestedcount; stream << _contentType; stream << QString (getTypeParm ("name")); stream << _contentDescription; stream << _contentDisposition; stream << _contentEncoding; stream << contentLength; stream << partSpecifier; // serialize nested message if (nestedMessage) nestedMessage->serialize(stream); // serialize nested parts if (!nestedParts.isEmpty()) { QListIterator < mimeHeader *> it(nestedParts); mimeHeader* part; while ( it.hasNext() ) { part = it.next(); part->serialize( stream ); } } } #ifdef KMAIL_COMPATIBLE // compatibility subroutines QString mimeHeader::bodyDecoded () { kDebug(7116) <<"mimeHeader::bodyDecoded"; QByteArray temp = bodyDecodedBinary (); return QString::fromLatin1 (temp.data (), temp.count ()); } QByteArray mimeHeader::bodyDecodedBinary () { QByteArray retVal; - if (contentEncoding.startsWith ("quoted-printable", Qt::CaseInsensitive) ) + if (contentEncoding.startsWith (QLatin1String("quoted-printable"), Qt::CaseInsensitive) ) retVal = KCodecs::quotedPrintableDecode(postMultipartBody); - else if (contentEncoding.startsWith ("base64", Qt::CaseInsensitive) ) + else if (contentEncoding.startsWith (QLatin1String("base64"), Qt::CaseInsensitive) ) KCodecs::base64Decode(postMultipartBody, retVal); else retVal = postMultipartBody; - kDebug(7116) <<"mimeHeader::bodyDecodedBinary - size is" << retVal.size (); + kDebug(7116) << "mimeHeader::bodyDecodedBinary - size is" << retVal.size (); return retVal; } void mimeHeader::setBodyEncodedBinary (const QByteArray & _arr) { setBodyEncoded (_arr); } void mimeHeader::setBodyEncoded (const QByteArray & _arr) { QByteArray setVal; kDebug(7116) <<"mimeHeader::setBodyEncoded - in size" << _arr.size (); - if (contentEncoding.startsWith ("quoted-printable", Qt::CaseInsensitive) ) + if (contentEncoding.startsWith (QLatin1String("quoted-printable"), Qt::CaseInsensitive) ) setVal = KCodecs::quotedPrintableEncode(_arr); - else if (contentEncoding.startsWith ("base64", Qt::CaseInsensitive) ) + else if (contentEncoding.startsWith (QLatin1String("base64"), Qt::CaseInsensitive) ) KCodecs::base64Encode(_arr, setVal); else setVal.duplicate (_arr); kDebug(7116) <<"mimeHeader::setBodyEncoded - out size" << setVal.size (); postMultipartBody.duplicate (setVal); kDebug(7116) <<"mimeHeader::setBodyEncoded - out size" << postMultipartBody.size (); } QString mimeHeader::iconName () { QString fileName = KMimeType::mimeType (contentType.toLower ())->icon (QString(), false); QString iconFileName = KGlobal::mainComponent().iconLoader ()->iconPath (fileName, KIconLoader::Desktop); // if (iconFileName.isEmpty()) // iconFileName = KGlobal::mainComponent().iconLoader()->iconPath( "unknown", KIconLoader::Desktop ); return iconFileName; } void mimeHeader::setNestedMessage (mailHeader * inPart, bool destroy) { // if(nestedMessage && destroy) delete nestedMessage; nestedMessage = inPart; } QString mimeHeader::headerAsString () { mimeIOQString myIO; outputHeader (myIO); return myIO.getString (); } QString mimeHeader::magicSetType (bool aAutoDecode) { QByteArray body; if (aAutoDecode) body = bodyDecodedBinary (); else body = postMultipartBody; KMimeType::Ptr mime = KMimeType::findByContent (body); QString mimetype = mime->name(); contentType = mimetype; return mimetype; } #endif diff --git a/kioslave/imap4/mimeio.cpp b/kioslave/imap4/mimeio.cpp index 8352ec319..fa3537894 100644 --- a/kioslave/imap4/mimeio.cpp +++ b/kioslave/imap4/mimeio.cpp @@ -1,177 +1,179 @@ /*************************************************************************** mimeio.cc - description ------------------- begin : Wed Oct 25 2000 copyright : (C) 2000 by Sven Carstens email : s.carstens@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mimeio.h" #include using namespace std; mimeIO::mimeIO () { theCRLF = "\r\n"; crlfLen = 2; } mimeIO::~mimeIO () { } int mimeIO::inputLine (QByteArray & aLine) { char input; aLine = QByteArray(); while (inputChar (input)) { aLine += input; if (input == '\n') break; } // cout << aLine.length() << " - " << aLine; return aLine.length (); } int mimeIO::outputLine (const QByteArray & aLine, int len) { int i; if (len == -1) { len = aLine.length(); } int start = len; - for (i = 0; i < start; i++) - if (!outputChar (aLine[i])) + for (i = 0; i < start; ++i) { + if (!outputChar (aLine[i])) { break; + } + } return i; } int mimeIO::outputMimeLine (const QByteArray & inLine) { int retVal = 0; QByteArray aLine = inLine; int len = inLine.length(); int theLF = aLine.lastIndexOf ('\n'); if (theLF == len - 1 && theLF != -1) { //we have a trailing LF, now check for CR if (aLine[theLF - 1] == '\r') theLF--; //truncate the line aLine.truncate(theLF); len = theLF; theLF = -1; } //now truncate the line { int start, end, offset; start = 0; end = aLine.indexOf ('\n', start); while (end >= 0) { offset = 1; if (end && aLine[end - 1] == '\r') { offset++; end--; } outputLine (aLine.mid (start, end - start) + theCRLF, end - start + crlfLen); start = end + offset; end = aLine.indexOf ('\n', start); } outputLine (aLine.mid (start, len - start) + theCRLF, len - start + crlfLen); } return retVal; } int mimeIO::inputChar (char &aChar) { if (cin.eof ()) { // cout << "EOF" << endl; return 0; } cin.get (aChar); return 1; } int mimeIO::outputChar (char aChar) { cout << aChar; return 1; } // void mimeIO::setCRLF (const char *aCRLF) // { // theCRLF = aCRLF; // crlfLen = strlen(aCRLF); // } mimeIOQFile::mimeIOQFile (const QString & aName): mimeIO (), myFile (aName) { myFile.open (QIODevice::ReadOnly); } mimeIOQFile::~mimeIOQFile () { myFile.close (); } int mimeIOQFile::outputLine (const QByteArray &, int) { return 0; } int mimeIOQFile::inputLine (QByteArray & data) { data.resize( 1024 ); myFile.readLine (data.data(), 1024); return data.length (); } mimeIOQString::mimeIOQString () { } mimeIOQString::~mimeIOQString () { } int mimeIOQString::outputLine (const QByteArray & _str, int len) { if (len == -1) { len = _str.length(); } theString += _str; return len; } int mimeIOQString::inputLine (QByteArray & _str) { if (theString.isEmpty ()) return 0; int i = theString.indexOf ('\n'); if (i == -1) return 0; _str = theString.left (i + 1).toLatin1 (); theString = theString.right (theString.length () - i - 1); return _str.length (); } diff --git a/kioslave/nntp/nntp.cpp b/kioslave/nntp/nntp.cpp index 3269f7beb..1ccf8fa24 100644 --- a/kioslave/nntp/nntp.cpp +++ b/kioslave/nntp/nntp.cpp @@ -1,964 +1,964 @@ /* This file is part of KDE Copyright (C) 2000 by Wolfram Diestel Copyright (C) 2005 by Tim Way Copyright (C) 2005 by Volker Krause This is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. */ #include "nntp.h" #include #include #include #include #include #include #include #include #include #include #include #define DBG_AREA 7114 #define DBG kDebug(DBG_AREA) #define ERR kError(DBG_AREA) using namespace KIO; extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } int kdemain(int argc, char **argv) { KComponentData componentData("kio_nntp"); if (argc != 4) { fprintf(stderr, "Usage: kio_nntp protocol domain-socket1 domain-socket2\n"); exit(-1); } NNTPProtocol *slave; // Are we going to use SSL? if (strcasecmp(argv[1], "nntps") == 0) { slave = new NNTPProtocol(argv[2], argv[3], true); } else { slave = new NNTPProtocol(argv[2], argv[3], false); } slave->dispatchLoop(); delete slave; return 0; } /****************** NNTPProtocol ************************/ NNTPProtocol::NNTPProtocol ( const QByteArray & pool, const QByteArray & app, bool isSSL ) : TCPSlaveBase((isSSL ? "nntps" : "nntp"), pool, app, isSSL ), isAuthenticated( false ) { DBG << "=============> NNTPProtocol::NNTPProtocol"; readBufferLen = 0; m_defaultPort = isSSL ? DEFAULT_NNTPS_PORT : DEFAULT_NNTP_PORT; m_port = m_defaultPort; } NNTPProtocol::~NNTPProtocol() { DBG << "<============= NNTPProtocol::~NNTPProtocol"; // close connection nntp_close(); } void NNTPProtocol::setHost ( const QString & host, quint16 port, const QString & user, const QString & pass ) { DBG << ( ! user.isEmpty() ? (user+'@') : QString("")) << host << ":" << ( ( port == 0 ) ? m_defaultPort : port ); if ( isConnected() && (mHost != host || m_port != port || mUser != user || mPass != pass) ) nntp_close(); mHost = host; m_port = ( ( port == 0 ) ? m_defaultPort : port ); mUser = user; mPass = pass; } void NNTPProtocol::get( const KUrl& url ) { DBG << url.prettyUrl(); QString path = QDir::cleanPath(url.path()); // path should be like: /group/ or /group/ if ( path.startsWith( QDir::separator() ) ) path.remove( 0, 1 ); int pos = path.indexOf( QDir::separator() ); QString group; QString msg_id; if ( pos > 0 ) { group = path.left( pos ); msg_id = path.mid( pos + 1 ); } if ( group.isEmpty() || msg_id.isEmpty() ) { error(ERR_DOES_NOT_EXIST,path); return; } int res_code; DBG << "group:" << group << "msg:" << msg_id; if ( !nntp_open() ) return; // select group if necessary if ( mCurrentGroup != group && !group.isEmpty() ) { infoMessage( i18n("Selecting group %1...", group ) ); res_code = sendCommand( "GROUP " + group ); if ( res_code == 411 ){ error( ERR_DOES_NOT_EXIST, path ); mCurrentGroup.clear(); return; } else if ( res_code != 211 ) { unexpected_response( res_code, "GROUP" ); mCurrentGroup.clear(); return; } mCurrentGroup = group; } // get article infoMessage( i18n("Downloading article...") ); res_code = sendCommand( "ARTICLE " + msg_id ); if ( res_code == 423 || res_code == 430 ) { error( ERR_DOES_NOT_EXIST, path ); return; } else if (res_code != 220) { unexpected_response(res_code,"ARTICLE"); return; } // read and send data char tmp[MAX_PACKET_LEN]; while ( true ) { if ( !waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return; } int len = readLine( tmp, MAX_PACKET_LEN ); const char* buffer = tmp; if ( len <= 0 ) break; if ( len == 3 && tmp[0] == '.' && tmp[1] == '\r' && tmp[2] == '\n') break; if ( len > 1 && tmp[0] == '.' && tmp[1] == '.' ) { ++buffer; --len; } data( QByteArray::fromRawData( buffer, len ) ); } // end of data data(QByteArray()); // finish finished(); } void NNTPProtocol::put( const KUrl &/*url*/, int /*permissions*/, KIO::JobFlags /*flags*/ ) { if ( !nntp_open() ) return; if ( post_article() ) finished(); } void NNTPProtocol::special(const QByteArray& data) { // 1 = post article int cmd; QDataStream stream(data); if ( !nntp_open() ) return; stream >> cmd; if (cmd == 1) { if (post_article()) finished(); } else { error(ERR_UNSUPPORTED_ACTION,i18n("Invalid special command %1", cmd)); } } bool NNTPProtocol::post_article() { DBG; // send post command infoMessage( i18n("Sending article...") ); int res_code = sendCommand( "POST" ); if (res_code == 440) { // posting not allowed error(ERR_WRITE_ACCESS_DENIED, mHost); return false; } else if (res_code != 340) { // 340: ok, send article unexpected_response(res_code,"POST"); return false; } // send article now int result; bool last_chunk_had_line_ending = true; do { QByteArray buffer; dataReq(); result = readData( buffer ); DBG << "receiving data:" << buffer; // treat the buffer data if ( result > 0 ) { // translate "\r\n." to "\r\n.." int pos = 0; if ( last_chunk_had_line_ending && buffer[0] == '.' ) { buffer.insert( 0, '.' ); pos += 2; } - last_chunk_had_line_ending = ( buffer.endsWith( "\r\n" ) ); + last_chunk_had_line_ending = ( buffer.endsWith( "\r\n" ) ); //krazy:exclude=strings while ( (pos = buffer.indexOf( "\r\n.", pos )) > 0) { buffer.insert( pos + 2, '.' ); pos += 4; } // send data to socket, write() doesn't send the terminating 0 write( buffer, buffer.length() ); DBG << "writing:" << buffer; } } while ( result > 0 ); // error occurred? if (result<0) { ERR << "error while getting article data for posting"; nntp_close(); return false; } // send end mark write( "\r\n.\r\n", 5 ); // get answer res_code = evalResponse( readBuffer, readBufferLen ); if (res_code == 441) { // posting failed error(ERR_COULD_NOT_WRITE, mHost); return false; } else if (res_code != 240) { unexpected_response(res_code,"POST"); return false; } return true; } void NNTPProtocol::stat( const KUrl& url ) { DBG << url.prettyUrl(); UDSEntry entry; QString path = QDir::cleanPath(url.path()); QRegExp regGroup = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/?$",Qt::CaseInsensitive); QRegExp regMsgId = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", Qt::CaseInsensitive); int pos; QString group; QString msg_id; // / = group list if (path.isEmpty() || path == "/") { DBG << "root"; fillUDSEntry( entry, QString(), 0, false, ( S_IWUSR | S_IWGRP | S_IWOTH ) ); // /group = message list } else if (regGroup.indexIn(path) == 0) { if ( path.startsWith( '/' ) ) path.remove(0,1); if ((pos = path.indexOf('/')) > 0) group = path.left(pos); else group = path; DBG << "group:" << group; // postingAllowed should be ored here with "group not moderated" flag // as size the num of messages (GROUP cmd) could be given fillUDSEntry( entry, group, 0, false, ( S_IWUSR | S_IWGRP | S_IWOTH ) ); // /group/ = message } else if (regMsgId.indexIn(path) == 0) { pos = path.indexOf('<'); group = path.left(pos); msg_id = KUrl::fromPercentEncoding( path.right(path.length()-pos).toLatin1() ); if ( group.startsWith( '/' ) ) group.remove( 0, 1 ); if ((pos = group.indexOf('/')) > 0) group = group.left(pos); DBG << "group:" << group << "msg:" << msg_id; fillUDSEntry( entry, msg_id, 0, true ); // invalid url } else { error(ERR_DOES_NOT_EXIST,path); return; } statEntry(entry); finished(); } void NNTPProtocol::listDir( const KUrl& url ) { DBG << url.prettyUrl(); if ( !nntp_open() ) return; QString path = QDir::cleanPath(url.path()); if (path.isEmpty()) { KUrl newURL(url); newURL.setPath("/"); DBG << "redirecting to" << newURL.prettyUrl(); redirection(newURL); finished(); return; } else if ( path == "/" ) { fetchGroups( url.queryItem( "since" ), url.queryItem( "desc" ) == "true" ); finished(); } else { // if path = /group int pos; QString group; if ( path.startsWith( '/' ) ) path.remove( 0, 1 ); if ((pos = path.indexOf('/')) > 0) group = path.left(pos); else group = path; QString first = url.queryItem( "first" ); QString max = url.queryItem( "max" ); if ( fetchGroup( group, first.toULong(), max.toULong() ) ) finished(); } } void NNTPProtocol::fetchGroups( const QString &since, bool desc ) { int expected; int res; if ( since.isEmpty() ) { // full listing infoMessage( i18n("Downloading group list...") ); res = sendCommand( "LIST" ); expected = 215; } else { // incremental listing infoMessage( i18n("Looking for new groups...") ); res = sendCommand( "NEWGROUPS " + since ); expected = 231; } if ( res != expected ) { unexpected_response( res, "LIST" ); return; } // read newsgroups line by line QByteArray line; QString group; int pos, pos2; long msg_cnt; long access; UDSEntry entry; QHash entryMap; // read in data and process each group. one line at a time while ( true ) { if ( ! waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return; } readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); line = QByteArray( readBuffer, readBufferLen ); if ( line == ".\r\n" ) break; // group name if ((pos = line.indexOf(' ')) > 0) { group = line.left(pos); // number of messages line.remove(0,pos+1); long last = 0; access = 0; if (((pos = line.indexOf(' ')) > 0 || (pos = line.indexOf('\t')) > 0) && ((pos2 = line.indexOf(' ',pos+1)) > 0 || (pos2 = line.indexOf('\t',pos+1)) > 0)) { last = line.left(pos).toLongLong(); long first = line.mid(pos+1,pos2-pos-1).toLongLong(); msg_cnt = abs(last-first+1); // group access rights switch ( line[pos2 + 1] ) { case 'n': access = 0; break; case 'm': access = S_IWUSR | S_IWGRP; break; case 'y': access = S_IWUSR | S_IWGRP | S_IWOTH; break; } } else { msg_cnt = 0; } entry.clear(); fillUDSEntry( entry, group, msg_cnt, false, access ); if ( !desc ) listEntry( entry, false ); else entryMap.insert( group, entry ); } } // handle group descriptions QHash::Iterator it = entryMap.begin(); if ( desc ) { infoMessage( i18n("Downloading group descriptions...") ); totalSize( entryMap.size() ); } while ( desc ) { // request all group descriptions if ( since.isEmpty() ) res = sendCommand( "LIST NEWSGROUPS" ); else { // request only descriptions for new groups if ( it == entryMap.end() ) break; res = sendCommand( "LIST NEWSGROUPS " + it.key() ); ++it; if( res == 503 ) { // Information not available (RFC 2980 ยง2.1.6), try next group continue; } } if ( res != 215 ) { // No group description available or not implemented break; } // download group descriptions while ( true ) { if ( ! waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return; } readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); line = QByteArray( readBuffer, readBufferLen ); if ( line == ".\r\n" ) break; //DBG << " fetching group description: " << QString( line ).trimmed(); int pos = line.indexOf( ' ' ); pos = pos < 0 ? line.indexOf( '\t' ) : qMin( pos, line.indexOf( '\t' ) ); group = line.left( pos ); QString groupDesc = line.right( line.length() - pos ).trimmed(); if ( entryMap.contains( group ) ) { entry = entryMap.take( group ); entry.insert( KIO::UDSEntry::UDS_EXTRA, groupDesc ); listEntry( entry, false ); } } if ( since.isEmpty() ) break; } // take care of groups without descriptions for ( QHash::Iterator it = entryMap.begin(); it != entryMap.end(); ++it ) listEntry( it.value(), false ); entry.clear(); listEntry( entry, true ); } bool NNTPProtocol::fetchGroup( QString &group, unsigned long first, unsigned long max ) { int res_code; QString resp_line; // select group infoMessage( i18n("Selecting group %1...", group ) ); res_code = sendCommand( "GROUP " + group ); if ( res_code == 411 ) { error( ERR_DOES_NOT_EXIST, group ); mCurrentGroup.clear(); return false; } else if ( res_code != 211 ) { unexpected_response( res_code, "GROUP" ); mCurrentGroup.clear(); return false; } mCurrentGroup = group; // repsonse to "GROUP " command is 211 then find the message count (cnt) // and the first and last message followed by the group name unsigned long firstSerNum, lastSerNum; resp_line = QString::fromLatin1( readBuffer ); QRegExp re ( "211\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"); if ( re.indexIn( resp_line ) != -1 ) { firstSerNum = re.cap( 2 ).toLong(); lastSerNum = re.cap( 3 ).toLong(); } else { error( ERR_INTERNAL, i18n("Could not extract message serial numbers from server response:\n%1", resp_line ) ); return false; } if (firstSerNum == 0) return true; first = qMax( first, firstSerNum ); if ( max > 0 && lastSerNum - first > max ) first = lastSerNum - max + 1; DBG << "Starting from serial number: " << first << " of " << firstSerNum << " - " << lastSerNum; setMetaData( "FirstSerialNumber", QString::number( firstSerNum ) ); setMetaData( "LastSerialNumber", QString::number( lastSerNum ) ); infoMessage( i18n("Downloading new headers...") ); totalSize( lastSerNum - first ); bool notSupported = true; if ( fetchGroupXOVER( first, notSupported ) ) return true; else if ( notSupported ) return fetchGroupRFC977( first ); return false; } bool NNTPProtocol::fetchGroupRFC977( unsigned long first ) { UDSEntry entry; // set article pointer to first article and get msg-id of it int res_code = sendCommand( "STAT " + QString::number( first ) ); QString resp_line = readBuffer; if (res_code != 223) { unexpected_response(res_code,"STAT"); return false; } //STAT res_line: 223 nnn ... QString msg_id; int pos, pos2; if ((pos = resp_line.indexOf('<')) > 0 && (pos2 = resp_line.indexOf('>',pos+1))) { msg_id = resp_line.mid(pos,pos2-pos+1); fillUDSEntry( entry, msg_id, 0, true ); listEntry( entry, false ); } else { error(ERR_INTERNAL,i18n("Could not extract first message id from server response:\n%1", resp_line)); return false; } // go through all articles while (true) { res_code = sendCommand("NEXT"); if (res_code == 421) { // last artice reached entry.clear(); listEntry( entry, true ); return true; } else if (res_code != 223) { unexpected_response(res_code,"NEXT"); return false; } //res_line: 223 nnn ... resp_line = readBuffer; if ((pos = resp_line.indexOf('<')) > 0 && (pos2 = resp_line.indexOf('>',pos+1))) { msg_id = resp_line.mid(pos,pos2-pos+1); entry.clear(); fillUDSEntry( entry, msg_id, 0, true ); listEntry( entry, false ); } else { error(ERR_INTERNAL,i18n("Could not extract message id from server response:\n%1", resp_line)); return false; } } return true; // Not reached } bool NNTPProtocol::fetchGroupXOVER( unsigned long first, bool ¬Supported ) { notSupported = false; QString line; QStringList headers; int res = sendCommand( "LIST OVERVIEW.FMT" ); if ( res == 215 ) { while ( true ) { if ( ! waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return false; } readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); line = QString::fromLatin1( readBuffer, readBufferLen ); if ( line == ".\r\n" ) break; headers << line.trimmed(); DBG << "OVERVIEW.FMT:" << line.trimmed(); } } else { // fallback to defaults headers << "Subject:" << "From:" << "Date:" << "Message-ID:" << "References:" << "Bytes:" << "Lines:"; } res = sendCommand( "XOVER " + QString::number( first ) + '-' ); if ( res == 420 ) return true; // no articles selected if ( res == 500 ) notSupported = true; // unknwon command if ( res != 224 ) return false; long msgSize; QString name; UDSEntry entry; int udsType; QStringList fields; while ( true ) { if ( ! waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return false; } readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); line = QString::fromLatin1( readBuffer, readBufferLen ); if ( line == ".\r\n" ) { entry.clear(); listEntry( entry, true ); return true; } fields = line.split( '\t', QString::KeepEmptyParts); msgSize = 0; entry.clear(); udsType = KIO::UDSEntry::UDS_EXTRA; QStringList::ConstIterator it = headers.constBegin(); QStringList::ConstIterator it2 = fields.constBegin(); // first entry is the serial number name = (*it2); ++it2; for ( ; it != headers.constEnd() && it2 != fields.constEnd(); ++it, ++it2 ) { if ( (*it) == "Bytes:" ) { msgSize = (*it2).toLong(); continue; } QString atomStr; - if ( (*it).endsWith( "full" ) ) + if ( (*it).endsWith( QLatin1String( "full" ) ) ) if ( (*it2).trimmed().isEmpty() ) atomStr = (*it).left( (*it).indexOf( ':' ) + 1 ); // strip of the 'full' suffix else atomStr = (*it2).trimmed(); else atomStr = (*it) + ' ' + (*it2).trimmed(); entry.insert( udsType++, atomStr ); if ( udsType >= KIO::UDSEntry::UDS_EXTRA_END ) break; } fillUDSEntry( entry, name, msgSize, true ); listEntry( entry, false ); } return true; // not reached } void NNTPProtocol::fillUDSEntry( UDSEntry& entry, const QString& name, long size, bool is_article, long access ) { long posting=0; // entry name entry.insert(KIO::UDSEntry::UDS_NAME, name); // entry size entry.insert(KIO::UDSEntry::UDS_SIZE, size); // file type entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, is_article? S_IFREG : S_IFDIR); // access permissions posting = postingAllowed? access : 0; long long accessVal = (is_article)? (S_IRUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | posting); entry.insert(KIO::UDSEntry::UDS_ACCESS, accessVal); entry.insert(KIO::UDSEntry::UDS_USER, mUser.isEmpty() ? QString::fromLatin1("root") : mUser); /* entry->insert(UDS_GROUP, QString::fromLatin1("root")); */ // MIME type if (is_article) { entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("message/news") ); } } void NNTPProtocol::nntp_close () { if ( isConnected() ) { write( "QUIT\r\n", 6 ); disconnectFromHost(); isAuthenticated = false; } mCurrentGroup.clear(); } bool NNTPProtocol::nntp_open() { // if still connected reuse connection if ( isConnected() ) { DBG << "reusing old connection"; return true; } DBG << " nntp_open -- creating a new connection to" << mHost << ":" << m_port; // create a new connection (connectToHost() includes error handling) infoMessage( i18n("Connecting to server...") ); if ( connectToHost( (isAutoSsl() ? "nntps" : "nntp"), mHost.toLatin1(), m_port ) ) { DBG << " nntp_open -- connection is open"; // read greeting int res_code = evalResponse( readBuffer, readBufferLen ); /* expect one of 200 server ready - posting allowed 201 server ready - no posting allowed */ if ( ! ( res_code == 200 || res_code == 201 ) ) { unexpected_response(res_code,"CONNECT"); return false; } DBG << " nntp_open -- greating was read res_code :" << res_code; res_code = sendCommand("MODE READER"); // TODO: not in RFC 977, so we should not abort here if ( !(res_code == 200 || res_code == 201) ) { unexpected_response( res_code, "MODE READER" ); return false; } // let local class know whether posting is allowed or not postingAllowed = (res_code == 200); // activate TLS if requested if ( metaData("tls") == "on" ) { if ( sendCommand( "STARTTLS" ) != 382 ) { error( ERR_COULD_NOT_CONNECT, i18n("This server does not support TLS") ); return false; } if ( !startSsl() ) { error( ERR_COULD_NOT_CONNECT, i18n("TLS negotiation failed") ); return false; } } // *try* to authenticate now (see bug#167718) authenticate(); return true; } return false; } int NNTPProtocol::sendCommand( const QString &cmd ) { int res_code = 0; if ( !nntp_open() ) { ERR << "NOT CONNECTED, cannot send cmd" << cmd; return 0; } DBG << "cmd:" << cmd; write( cmd.toLatin1(), cmd.length() ); // check the command for proper termination - if ( !cmd.endsWith( "\r\n" ) ) + if ( !cmd.endsWith( QLatin1String( "\r\n" ) ) ) write( "\r\n", 2 ); res_code = evalResponse( readBuffer, readBufferLen ); // if authorization needed send user info if (res_code == 480) { DBG << "auth needed, sending user info"; if ( mUser.isEmpty() || mPass.isEmpty() ) { KIO::AuthInfo authInfo; authInfo.username = mUser; authInfo.password = mPass; if ( openPasswordDialog( authInfo ) ) { mUser = authInfo.username; mPass = authInfo.password; } } if ( mUser.isEmpty() || mPass.isEmpty() ) return res_code; res_code = authenticate(); if (res_code != 281) { // error should be handled by invoking function return res_code; } // ok now, resend command write( cmd.toLatin1(), cmd.length() ); - if ( !cmd.endsWith( "\r\n" ) ) + if ( !cmd.endsWith( QLatin1String( "\r\n" ) ) ) write( "\r\n", 2 ); res_code = evalResponse( readBuffer, readBufferLen ); } return res_code; } int NNTPProtocol::authenticate() { int res_code = 0; if( isAuthenticated ) { // already authenticated return 281; } if( mUser.isEmpty() || mPass.isEmpty() ) { return 281; // failsafe : maybe add a "relax" mode to optionally ask user/pwd. } // send username to server and confirm response write( "AUTHINFO USER ", 14 ); write( mUser.toLatin1(), mUser.length() ); write( "\r\n", 2 ); res_code = evalResponse( readBuffer, readBufferLen ); if( res_code == 281 ) { // no password needed (RFC 2980 3.1.1 does not required one) return res_code; } if (res_code != 381) { // error should be handled by invoking function return res_code; } // send password write( "AUTHINFO PASS ", 14 ); write( mPass.toLatin1(), mPass.length() ); write( "\r\n", 2 ); res_code = evalResponse( readBuffer, readBufferLen ); if( res_code == 281 ) { isAuthenticated = true; } return res_code; } void NNTPProtocol::unexpected_response( int res_code, const QString &command ) { ERR << "Unexpected response to" << command << "command: (" << res_code << ")" << readBuffer; KIO::Error errCode; switch ( res_code ) { case 480: errCode = ERR_COULD_NOT_LOGIN; break; default: errCode = ERR_INTERNAL; } error( errCode, i18n("Unexpected server response to %1 command:\n%2", command, readBuffer ) ); nntp_close(); } int NNTPProtocol::evalResponse ( char *data, ssize_t &len ) { if ( !waitForResponse( responseTimeout() ) ) { error( ERR_SERVER_TIMEOUT , mHost ); nntp_close(); return -1; } len = readLine( data, MAX_PACKET_LEN ); if ( len < 3 ) return -1; // get the first three characters. should be the response code int respCode = ( ( data[0] - 48 ) * 100 ) + ( ( data[1] - 48 ) * 10 ) + ( ( data[2] - 48 ) ); DBG << "got:" << respCode; return respCode; } /* not really necessary, because the slave has to use the KIO::Error's instead, but let this here for documentation of the NNTP response codes and may by later use. QString& NNTPProtocol::errorStr(int resp_code) { QString ret; switch (resp_code) { case 100: ret = "help text follows"; break; case 199: ret = "debug output"; break; case 200: ret = "server ready - posting allowed"; break; case 201: ret = "server ready - no posting allowed"; break; case 202: ret = "slave status noted"; break; case 205: ret = "closing connection - goodbye!"; break; case 211: ret = "group selected"; break; case 215: ret = "list of newsgroups follows"; break; case 220: ret = "article retrieved - head and body follow"; break; case 221: ret = "article retrieved - head follows"; break; case 222: ret = "article retrieved - body follows"; break; case 223: ret = "article retrieved - request text separately"; break; case 230: ret = "list of new articles by message-id follows"; break; case 231: ret = "list of new newsgroups follows"; break; case 235: ret = "article transferred ok"; break; case 240: ret = "article posted ok"; break; case 335: ret = "send article to be transferred"; break; case 340: ret = "send article to be posted"; break; case 400: ret = "service discontinued"; break; case 411: ret = "no such news group"; break; case 412: ret = "no newsgroup has been selected"; break; case 420: ret = "no current article has been selected"; break; case 421: ret = "no next article in this group"; break; case 422: ret = "no previous article in this group"; break; case 423: ret = "no such article number in this group"; break; case 430: ret = "no such article found"; break; case 435: ret = "article not wanted - do not send it"; break; case 436: ret = "transfer failed - try again later"; break; case 437: ret = "article rejected - do not try again"; break; case 440: ret = "posting not allowed"; break; case 441: ret = "posting failed"; break; case 500: ret = "command not recognized"; break; case 501: ret = "command syntax error"; break; case 502: ret = "access restriction or permission denied"; break; case 503: ret = "program fault - command not performed"; break; default: ret = QString("unknown NNTP response code %1").arg(resp_code); } return ret; } */ diff --git a/kioslave/nntp/nntp.h b/kioslave/nntp/nntp.h index 9fa28d174..46849c866 100644 --- a/kioslave/nntp/nntp.h +++ b/kioslave/nntp/nntp.h @@ -1,141 +1,141 @@ /* This file is part of KDE Copyright (C) 2000 by Wolfram Diestel Copyright (C) 2005 by Tim Way Copyright (C) 2005 by Volker Krause This is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. */ #ifndef _NNTP_H #define _NNTP_H #include #include #define MAX_PACKET_LEN 8192 /* TODO: - test special post command - progress information in get, and maybe post - remove unnecessary debug stuff */ class NNTPProtocol:public KIO::TCPSlaveBase { public: /** Default Constructor * @param isSSL is a true or false to indicate whether ssl is to be used */ NNTPProtocol ( const QByteArray & pool, const QByteArray & app, bool isSSL ); virtual ~NNTPProtocol(); virtual void get(const KUrl& url ); virtual void put( const KUrl& url, int permissions, KIO::JobFlags flags ); virtual void stat(const KUrl& url ); virtual void listDir(const KUrl& url ); virtual void setHost(const QString& host, quint16 port, const QString& user, const QString& pass); /** * Special command: 1 = post article * it takes no other args, the article data are * requested by dataReq() and should be valid * as in RFC850. It's not checked for correctness here. * @deprecated use put() for posting */ virtual void special(const QByteArray& data); protected: /** * Send a command to the server. Returns the response code and * the response line */ int sendCommand( const QString &cmd ); /** * Attempt to properly shut down the NNTP connection by sending * "QUIT\r\n" before closing the socket. */ void nntp_close (); /** * Attempt to initiate a NNTP connection via a TCP socket, if no existing * connection could be reused. */ bool nntp_open(); /** * Post article. Invoked by special() and put() */ bool post_article(); private: QString mHost, mUser, mPass; quint16 m_port, m_defaultPort; bool postingAllowed, isAuthenticated; char readBuffer[MAX_PACKET_LEN]; ssize_t readBufferLen; /// Current selected newsgroup QString mCurrentGroup; /** * Fetch all new groups since the given date or (if the date is empty) * all available groups. * @param since Date as specified in RFC 977 for the NEWGROUPS command * @param desc Fetch group description (needs more memory) */ void fetchGroups( const QString &since, bool desc ); /** * Fetch message listing from the given newsgroup. * This will use RFC2980 XOVER if available, plain RFC977 STAT/NEXT * otherwise. * @param group The newsgroup name * @param first Serial number of the first message, 0 lists all messages. * @param max Maximal number of returned messages, 0 means unlimited. * @return true on success, false otherwise. */ bool fetchGroup ( QString &group, unsigned long first = 0, unsigned long max = 0 ); /** * Fetch message listing from the current group using RFC977 STAT/NEXT * commands. * @param first message number of the first article * @return true on success, false otherwise. */ bool fetchGroupRFC977( unsigned long first ); /** * Fetch message listing from the current group using the RFC2980 XOVER * command. * Additional headers provided by XOVER are added as UDS_EXTRA entries * to the listing. * @param first message number of the first article * @param notSupported boolean reference to indicate if command failed * due to missing XOVER support on the server. * @return true on success, false otherwise */ bool fetchGroupXOVER( unsigned long first, bool ¬Supported ); /// creates an UDSEntry with file information used in stat and listDir void fillUDSEntry ( KIO::UDSEntry & entry, const QString & name, long size, bool is_article, long access = 0 ); /// error handling for unexpected responses void unexpected_response ( int res_code, const QString & command ); /** * grabs the response line from the server. used after most send_cmd calls. max * length for the returned string ( char *data ) is 4096 characters including * the "\r\n" terminator. */ int evalResponse ( char *data, ssize_t &len ); /** * Try to authenticate to the server. * @return the response code from the server if the mUser/mPassword - * are available; 281 (successfull authentication) otherwise. + * are available; 281 (successful authentication) otherwise. */ int authenticate(); }; #endif diff --git a/kioslave/smtp/capabilities.cpp b/kioslave/smtp/capabilities.cpp index 892b366bf..00f51835f 100644 --- a/kioslave/smtp/capabilities.cpp +++ b/kioslave/smtp/capabilities.cpp @@ -1,114 +1,116 @@ /* -*- c++ -*- capabilities.cc This file is part of kio_smtp, the KDE SMTP kioslave. Copyright (c) 2003 Marc Mutz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "capabilities.h" #include "response.h" namespace KioSMTP { Capabilities Capabilities::fromResponse( const Response & ehlo ) { Capabilities c; // first, check whether the response was valid and indicates success: if ( !ehlo.isOk() || ehlo.code() / 10 != 25 // ### restrict to 250 only? || ehlo.lines().empty() ) return c; QCStringList l = ehlo.lines(); for ( QCStringList::const_iterator it = ++l.constBegin() ; it != l.constEnd() ; ++it ) c.add( *it ); return c; } void Capabilities::add( const QString & cap, bool replace ) { QStringList tokens = cap.toUpper().split( ' '); if ( tokens.empty() ) return; QString name = tokens.front(); tokens.pop_front(); add( name, tokens, replace ); } void Capabilities::add( const QString & name, const QStringList & args, bool replace ) { if ( replace ) mCapabilities[name] = args; else mCapabilities[name] += args; } QString Capabilities::createSpecialResponse( bool tls ) const { QStringList result; if ( tls ) result.push_back( "STARTTLS" ); result += saslMethodsQSL(); if ( have( "PIPELINING" ) ) result.push_back( "PIPELINING" ); if ( have( "8BITMIME" ) ) result.push_back( "8BITMIME" ); if ( have( "SIZE" ) ) { bool ok = false; unsigned int size = 0; if ( !mCapabilities["SIZE"].isEmpty() ) mCapabilities["SIZE"].front().toUInt( &ok ); if ( ok && !size ) result.push_back( "SIZE=*" ); // any size else if ( ok ) result.push_back( "SIZE=" + QString::number( size ) ); // fixed max else result.push_back( "SIZE" ); // indetermined } return result.join( " " ); } QStringList Capabilities::saslMethodsQSL() const { QStringList result; - for ( QMap::const_iterator it = mCapabilities.begin() ; it != mCapabilities.end() ; ++it ) { - if ( it.key() == "AUTH" ) + for ( QMap::const_iterator it = mCapabilities.begin(); + it != mCapabilities.end(); ++it ) { + if ( it.key() == "AUTH" ) { result += it.value(); - else if ( it.key().startsWith( "AUTH=" ) ) { + } else if ( it.key().startsWith( QLatin1String( "AUTH=" ) ) ) { result.push_back( it.key().mid( qstrlen("AUTH=") ) ); result += it.value(); } } result.sort(); for (int i = 0, j = 1; j < result.count(); i = j++ ) { - if ( result.at(i) == result.at(j) ) + if ( result.at(i) == result.at(j) ) { result.removeAt( j-- ); + } } return result; } } // namespace KioSMTP diff --git a/kioslave/smtp/transactionstate.h b/kioslave/smtp/transactionstate.h index 88c322af0..a7c26d461 100644 --- a/kioslave/smtp/transactionstate.h +++ b/kioslave/smtp/transactionstate.h @@ -1,194 +1,194 @@ /* -*- c++ -*- transactionstate.h This file is part of kio_smtp, the KDE SMTP kioslave. Copyright (c) 2003 Marc Mutz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef __KIOSMTP_TRANSACTIONSTATE_H__ #define __KIOSMTP_TRANSACTIONSTATE_H__ #include "response.h" #include namespace KioSMTP { /** @short A class modelling an SMTP transaction's state - + This class models SMTP transaction state, ie. the collective result of the MAIL FROM:, RCPT TO: and DATA commands. This is needed since e.g. a single failed RCPT TO: command does not - neccessarily fail the whole transaction (servers are free to + necessarily fail the whole transaction (servers are free to accept delivery for some recipients, but not for others). The class can operate in two modes, which differ in the way failed recipients are handled. If @p rcptToDenyIsFailure is true (the default), then any failing RCPT TO: will cause the transaction to fail. Since at the point of RCPT TO: failure detection, the DATA command may have already been sent (pipelining), the only way to cancel the transaction is to take down the connection hard (ie. without proper quit). Since that is not very nice behaviour, a second mode that is more to the spirit of SMTP is provided that can cope with partially failed RCPT TO: commands. */ class TransactionState { public: struct RecipientRejection { RecipientRejection( const QString & who=QString(), const QString & why=QString() ) : recipient( who ), reason( why ) {} QString recipient; QString reason; #ifdef KIOSMTP_COMPARATORS bool operator==( const RecipientRejection & other ) const { return recipient == other.recipient && reason == other.reason; } #endif }; typedef QList RejectedRecipientList; TransactionState( bool rcptToDenyIsFailure=true ) : mErrorCode( 0 ), mRcptToDenyIsFailure( rcptToDenyIsFailure ), mAtLeastOneRecipientWasAccepted( false ), mDataCommandIssued( false ), mDataCommandSucceeded( false ), mFailed( false ), mFailedFatally( false ), mComplete( false ) {} /** * @return whether the transaction failed (e.g. the server * rejected all recipients. Graceful failure is handled after * transaction ends. */ bool failed() const { return mFailed || mFailedFatally; } void setFailed() { mFailed = true; } /** * @return whether the failure was so grave that an immediate * untidy connection shutdown is in order (ie. @ref * smtp_close(false)). Fatal failure is handled immediately */ bool failedFatally() const { return mFailedFatally; } void setFailedFatally( int code=0, const QString & msg=QString() ); /** @return whether the transaction was completed successfully */ bool complete() const { return mComplete; } void setComplete() { mComplete = true; } /** * @return an appropriate KIO error code in case the transaction * failed, or 0 otherwise */ int errorCode() const; /** * @return an appropriate error message in case the transaction * failed or QString() otherwise */ QString errorMessage() const; void setMailFromFailed( const QString & addr, const Response & r ); bool dataCommandIssued() const { return mDataCommandIssued; } void setDataCommandIssued( bool issued ) { mDataCommandIssued = issued; } bool dataCommandSucceeded() const { return mDataCommandIssued && mDataCommandSucceeded; } void setDataCommandSucceeded( bool succeeded, const Response & r ); Response dataResponse() const { return mDataResponse; } bool atLeastOneRecipientWasAccepted() const { return mAtLeastOneRecipientWasAccepted; } void setRecipientAccepted() { mAtLeastOneRecipientWasAccepted = true; } bool haveRejectedRecipients() const { return !mRejectedRecipients.empty(); } RejectedRecipientList rejectedRecipients() const { return mRejectedRecipients; } void addRejectedRecipient( const RecipientRejection & r ); void addRejectedRecipient( const QString & who, const QString & why ) { addRejectedRecipient( RecipientRejection( who, why ) ); } void clear() { mRejectedRecipients.clear(); mDataResponse.clear(); mAtLeastOneRecipientWasAccepted = mDataCommandIssued = mDataCommandSucceeded = mFailed = mFailedFatally = mComplete = false; } #ifdef KIOSMTP_COMPARATORS bool operator==( const TransactionState & other ) const { return mAtLeastOneRecipientWasAccepted == other.mAtLeastOneRecipientWasAccepted && mDataCommandIssued == other.mDataCommandIssued && mDataCommandSucceeded == other.mDataCommandSucceeded && mFailed == other.mFailed && mFailedFatally == other.mFailedFatally && mComplete == other.mComplete && mDataResponse.code() == other.mDataResponse.code() && mRejectedRecipients == other.mRejectedRecipients; } #endif private: RejectedRecipientList mRejectedRecipients; Response mDataResponse; QString mErrorMessage; int mErrorCode; bool mRcptToDenyIsFailure; bool mAtLeastOneRecipientWasAccepted; bool mDataCommandIssued; bool mDataCommandSucceeded; bool mFailed; bool mFailedFatally; bool mComplete; }; } // namespace KioSMTP #endif // __KIOSMTP_TRANSACTIONSTATE_H__