/********************************************************************** * * 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 "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( QByteArray(QByteArray("X-Date: ") + cache->getDate() + QByteArray("\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.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.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, KIO::DefaultFlags ); 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_SLAVE_DEFINED, 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_SLAVE_DEFINED, 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_SLAVE_DEFINED, 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_SLAVE_DEFINED, 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( ERR_SLAVE_DEFINED, i18n( "Unable to login. Probably the password is wrong.\n" "The server %1 replied:\n%2", myHost, resultInfo ) ); } } else { if ( !clientAuthenticate( this, authInfo, myHost, myAuth, mySSL, resultInfo ) ) { error( ERR_SLAVE_DEFINED, 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 = '/'; } } 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( QLatin1String( "quoted-printable" ), Qt::CaseInsensitive ) ) { decoded = KCodecs::quotedPrintableDecode( outputCache ); } 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 ) { memmove( 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; }