/********************************************************************** * * imapparser.cpp - IMAP4rev1 Parser * Copyright (C) 2001-2002 Michael Haeckel * Copyright (C) 2000 s.carstens@gmx.de * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Send comments and bug fixes to s.carstens@gmx.de * *********************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include "rfcdecoder.h" #include "imapparser.h" #include "imapinfo.h" #include "mailheader.h" #include "mimeheader.h" #include "mailaddress.h" #include #include #include #ifdef HAVE_LIBSASL2 extern "C" { #include } #endif #include #include #include #include #include #include #include #include #ifdef HAVE_LIBSASL2 static sasl_callback_t callbacks[] = { { SASL_CB_ECHOPROMPT, NULL, NULL }, { SASL_CB_NOECHOPROMPT, NULL, NULL }, { SASL_CB_GETREALM, NULL, NULL }, { SASL_CB_USER, NULL, NULL }, { SASL_CB_AUTHNAME, NULL, NULL }, { SASL_CB_PASS, NULL, NULL }, { SASL_CB_CANON_USER, NULL, NULL }, { SASL_CB_LIST_END, NULL, NULL } }; #endif imapParser::imapParser () { sentQueue.setAutoDelete (false); completeQueue.setAutoDelete (true); currentState = ISTATE_NO; commandCounter = 0; lastHandled = 0; } imapParser::~imapParser () { delete lastHandled; lastHandled = 0; } imapCommand * imapParser::doCommand (imapCommand * aCmd) { int pl = 0; sendCommand (aCmd); while (pl != -1 && !aCmd->isComplete ()) { while ((pl = parseLoop ()) == 0) ; } return aCmd; } imapCommand * imapParser::sendCommand (imapCommand * aCmd) { aCmd->setId (TQString::number(commandCounter++)); sentQueue.append (aCmd); continuation.resize(0); const TQString& command = aCmd->command(); if (command == "SELECT" || command == "EXAMINE") { // we need to know which box we are selecting parseString p; p.fromString(aCmd->parameter()); currentBox = parseOneWordC(p); kdDebug(7116) << "imapParser::sendCommand - setting current box to " << currentBox << endl; } else if (command == "CLOSE") { // we no longer have a box open currentBox = TQString(); } else if (command.find ("SEARCH") != -1 || command == "GETACL" || command == "LISTRIGHTS" || command == "MYRIGHTS" || command == "GETANNOTATION" || command == "NAMESPACE" || command == "GETQUOTAROOT" || command == "GETQUOTA" || command == "X-GET-OTHER-USERS" || command == "X-GET-DELEGATES" || command == "X-GET-OUT-OF-OFFICE") { lastResults.clear (); } else if (command == "LIST" || command == "LSUB") { listResponses.clear (); } parseWriteLine (aCmd->getStr ()); return aCmd; } bool imapParser::clientLogin (const TQString & aUser, const TQString & aPass, TQString & resultInfo) { imapCommand *cmd; bool retVal = false; cmd = doCommand (new imapCommand ("LOGIN", "\"" + rfcDecoder::quoteIMAP(aUser) + "\" \"" + rfcDecoder::quoteIMAP(aPass) + "\"")); if (cmd->result () == "OK") { currentState = ISTATE_LOGIN; retVal = true; } resultInfo = cmd->resultInfo(); completeQueue.removeRef (cmd); return retVal; } #ifdef HAVE_LIBSASL2 static bool sasl_interact( TDEIO::SlaveBase *slave, TDEIO::AuthInfo &ai, void *in ) { kdDebug(7116) << "sasl_interact" << endl; sasl_interact_t *interact = ( sasl_interact_t * ) in; //some mechanisms do not require username && pass, so it doesn't need a popup //window for getting this info for ( ; interact->id != SASL_CB_LIST_END; interact++ ) { if ( interact->id == SASL_CB_AUTHNAME || interact->id == SASL_CB_PASS ) { if ( ai.username.isEmpty() || ai.password.isEmpty() ) { if (!slave->openPassDlg(ai)) return false; } break; } } interact = ( sasl_interact_t * ) in; while( interact->id != SASL_CB_LIST_END ) { kdDebug(7116) << "SASL_INTERACT id: " << interact->id << endl; switch( interact->id ) { case SASL_CB_USER: case SASL_CB_AUTHNAME: kdDebug(7116) << "SASL_CB_[USER|AUTHNAME]: '" << ai.username << "'" << endl; interact->result = strdup( ai.username.utf8() ); interact->len = strlen( (const char *) interact->result ); break; case SASL_CB_PASS: kdDebug(7116) << "SASL_CB_PASS: [hidden] " << endl; interact->result = strdup( ai.password.utf8() ); interact->len = strlen( (const char *) interact->result ); break; default: interact->result = 0; interact->len = 0; break; } interact++; } return true; } #endif bool imapParser::clientAuthenticate ( TDEIO::SlaveBase *slave, TDEIO::AuthInfo &ai, const TQString & aFTQDN, const TQString & aAuth, bool isSSL, TQString & resultInfo) { bool retVal = false; #ifdef HAVE_LIBSASL2 int result; sasl_conn_t *conn = 0; sasl_interact_t *client_interact = 0; const char *out = 0; uint outlen = 0; const char *mechusing = 0; TQByteArray tmp, challenge; kdDebug(7116) << "aAuth: " << aAuth << " FTQDN: " << aFTQDN << " isSSL: " << isSSL << endl; // see if server supports this authenticator if (!hasCapability ("AUTH=" + aAuth)) return false; // result = sasl_client_new( isSSL ? "imaps" : "imap", result = sasl_client_new( "imap", /* FIXME: with cyrus-imapd, even imaps' digest-uri must be 'imap'. I don't know if it's good or bad. */ aFTQDN.latin1(), 0, 0, callbacks, 0, &conn ); if ( result != SASL_OK ) { kdDebug(7116) << "sasl_client_new failed with: " << result << endl; resultInfo = TQString::fromUtf8( sasl_errdetail( conn ) ); return false; } do { result = sasl_client_start(conn, aAuth.latin1(), &client_interact, hasCapability("SASL-IR") ? &out : 0, &outlen, &mechusing); if ( result == SASL_INTERACT ) { if ( !sasl_interact( slave, ai, client_interact ) ) { sasl_dispose( &conn ); return false; } } } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { kdDebug(7116) << "sasl_client_start failed with: " << result << endl; resultInfo = TQString::fromUtf8( sasl_errdetail( conn ) ); sasl_dispose( &conn ); return false; } imapCommand *cmd; tmp.setRawData( out, outlen ); KCodecs::base64Encode( tmp, challenge ); tmp.resetRawData( out, outlen ); // then lets try it TQString firstCommand = aAuth; if ( !challenge.isEmpty() ) { firstCommand += " "; firstCommand += TQString::fromLatin1( challenge.data(), challenge.size() ); } cmd = sendCommand (new imapCommand ("AUTHENTICATE", firstCommand.latin1())); int pl = 0; while ( pl != -1 && !cmd->isComplete () ) { //read the next line while ((pl = parseLoop()) == 0) ; if (!continuation.isEmpty()) { // kdDebug(7116) << "S: " << TQCString(continuation.data(),continuation.size()+1) << endl; if ( continuation.size() > 4 ) { tmp.setRawData( continuation.data() + 2, continuation.size() - 4 ); KCodecs::base64Decode( tmp, challenge ); // kdDebug(7116) << "S-1: " << TQCString(challenge.data(),challenge.size()+1) << endl; tmp.resetRawData( continuation.data() + 2, continuation.size() - 4 ); } do { result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(), challenge.size(), &client_interact, &out, &outlen); if (result == SASL_INTERACT) { if ( !sasl_interact( slave, ai, client_interact ) ) { sasl_dispose( &conn ); return false; } } } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { kdDebug(7116) << "sasl_client_step failed with: " << result << endl; resultInfo = TQString::fromUtf8( sasl_errdetail( conn ) ); sasl_dispose( &conn ); return false; } tmp.setRawData( out, outlen ); // kdDebug(7116) << "C-1: " << TQCString(tmp.data(),tmp.size()+1) << endl; KCodecs::base64Encode( tmp, challenge ); tmp.resetRawData( out, outlen ); // kdDebug(7116) << "C: " << TQCString(challenge.data(),challenge.size()+1) << endl; parseWriteLine (challenge); continuation.resize(0); } } if (cmd->result () == "OK") { currentState = ISTATE_LOGIN; retVal = true; } resultInfo = cmd->resultInfo(); completeQueue.removeRef (cmd); sasl_dispose( &conn ); //we don't use sasl_en/decode(), so it's safe to dispose the connection. #endif //HAVE_LIBSASL2 return retVal; } void imapParser::parseUntagged (parseString & result) { //kdDebug(7116) << "imapParser::parseUntagged - '" << result.cstr() << "'" << endl; parseOneWordC(result); // * TQByteArray what = parseLiteral (result); // see whats coming next if(!what.isEmpty ()) { switch (what[0]) { //the status responses case 'B': // BAD or BYE if (tqstrncmp(what, "BAD", what.size()) == 0) { parseResult (what, result); } else if (tqstrncmp(what, "BYE", what.size()) == 0) { parseResult (what, result); if ( sentQueue.count() ) { // BYE that interrupts a command -> copy the reason for it imapCommand *current = sentQueue.at (0); current->setResultInfo(result.cstr()); } currentState = ISTATE_NO; } break; case 'N': // NO if (what[1] == 'O' && what.size() == 2) { parseResult (what, result); } else if (tqstrncmp(what, "NAMESPACE", what.size()) == 0) { parseNamespace (result); } break; case 'O': // OK if (what[1] == 'K' && what.size() == 2) { parseResult (what, result); } else if (tqstrncmp(what, "OTHER-USER", 10) == 0) { // X-GET-OTHER-USER parseOtherUser (result); } else if (tqstrncmp(what, "OUT-OF-OFFICE", 13) == 0) { // X-GET-OUT-OF-OFFICE parseOutOfOffice (result); } break; case 'D': if (tqstrncmp(what, "DELEGATE", 8) == 0) { // X-GET-DELEGATES parseDelegate (result); } break; case 'P': // PREAUTH if (tqstrncmp(what, "PREAUTH", what.size()) == 0) { parseResult (what, result); currentState = ISTATE_LOGIN; } break; // parse the other responses case 'C': // CAPABILITY if (tqstrncmp(what, "CAPABILITY", what.size()) == 0) { parseCapability (result); } break; case 'F': // FLAGS if (tqstrncmp(what, "FLAGS", what.size()) == 0) { parseFlags (result); } break; case 'L': // LIST or LSUB or LISTRIGHTS if (tqstrncmp(what, "LIST", what.size()) == 0) { parseList (result); } else if (tqstrncmp(what, "LSUB", what.size()) == 0) { parseLsub (result); } else if (tqstrncmp(what, "LISTRIGHTS", what.size()) == 0) { parseListRights (result); } break; case 'M': // MYRIGHTS if (tqstrncmp(what, "MYRIGHTS", what.size()) == 0) { parseMyRights (result); } break; case 'S': // SEARCH or STATUS if (tqstrncmp(what, "SEARCH", what.size()) == 0) { parseSearch (result); } else if (tqstrncmp(what, "STATUS", what.size()) == 0) { parsetStatus (result); } break; case 'A': // ACL or ANNOTATION if (tqstrncmp(what, "ACL", what.size()) == 0) { parseAcl (result); } else if (tqstrncmp(what, "ANNOTATION", what.size()) == 0) { parseAnnotation (result); } break; case 'Q': // QUOTA or QUOTAROOT if ( what.size() > 5 && tqstrncmp(what, "QUOTAROOT", what.size()) == 0) { parseQuotaRoot( result ); } else if (tqstrncmp(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 = TQCString(what, what.size() + 1).toUInt(&valid); if (valid) { what = parseLiteral (result); if(!what.isEmpty ()) { switch (what[0]) { case 'E': if (tqstrncmp(what, "EXISTS", what.size()) == 0) { parseExists (number, result); } else if (tqstrncmp(what, "EXPUNGE", what.size()) == 0) { parseExpunge (number, result); } break; case 'F': if (tqstrncmp(what, "FETCH", what.size()) == 0) { seenUid = TQString(); parseFetch (number, result); } break; case 'S': if (tqstrncmp(what, "STORE", what.size()) == 0) // deprecated store { seenUid = TQString(); parseFetch (number, result); } break; case 'R': if (tqstrncmp(what, "RECENT", what.size()) == 0) { parseRecent (number, result); } break; default: break; } } } } break; } //switch } } //func void imapParser::parseResult (TQByteArray & result, parseString & rest, const TQString & command) { if (command == "SELECT") selectInfo.setReadWrite(true); if (rest[0] == '[') { rest.pos++; TQCString option = parseOneWordC(rest, TRUE); switch (option[0]) { case 'A': // ALERT if (option == "ALERT") { rest.pos = rest.data.find(']', rest.pos) + 1; // The alert text is after [ALERT]. // Is this correct or do we need to care about litterals? selectInfo.setAlert( rest.cstr() ); } break; case 'N': // NEWNAME if (option == "NEWNAME") { } break; case 'P': //PARSE or PERMANENTFLAGS if (option == "PARSE") { } else if (option == "PERMANENTFLAGS") { uint end = rest.data.find(']', rest.pos); TQCString flags(rest.data.data() + rest.pos, end - rest.pos); selectInfo.setPermanentFlags (flags); rest.pos = end; } break; case 'R': //READ-ONLY or READ-WRITE if (option == "READ-ONLY") { selectInfo.setReadWrite (false); } else if (option == "READ-WRITE") { selectInfo.setReadWrite (true); } break; case 'T': //TRYCREATE if (option == "TRYCREATE") { } break; case 'U': //UIDVALIDITY or UNSEEN if (option == "UIDVALIDITY") { ulong value; if (parseOneNumber (rest, value)) selectInfo.setUidValidity (value); } else if (option == "UNSEEN") { ulong value; if (parseOneNumber (rest, value)) selectInfo.setUnseen (value); } else if (option == "UIDNEXT") { ulong value; if (parseOneNumber (rest, value)) selectInfo.setUidNext (value); } else break; } if (rest[0] == ']') rest.pos++; //tie off ] skipWS (rest); } if (command.isEmpty()) { // This happens when parsing an intermediate result line (those that start with '*'). // No state change involved, so we can stop here. return; } switch (command[0].latin1 ()) { case 'A': if (command == "AUTHENTICATE") if (tqstrncmp(result, "OK", result.size()) == 0) currentState = ISTATE_LOGIN; break; case 'L': if (command == "LOGIN") if (tqstrncmp(result, "OK", result.size()) == 0) currentState = ISTATE_LOGIN; break; case 'E': if (command == "EXAMINE") { if (tqstrncmp(result, "OK", result.size()) == 0) currentState = ISTATE_SELECT; else { if (currentState == ISTATE_SELECT) currentState = ISTATE_LOGIN; currentBox = TQString(); } kdDebug(7116) << "imapParser::parseResult - current box is now " << currentBox << endl; } break; case 'S': if (command == "SELECT") { if (tqstrncmp(result, "OK", result.size()) == 0) currentState = ISTATE_SELECT; else { if (currentState == ISTATE_SELECT) currentState = ISTATE_LOGIN; currentBox = TQString(); } kdDebug(7116) << "imapParser::parseResult - current box is now " << currentBox << endl; } break; default: break; } } void imapParser::parseCapability (parseString & result) { TQCString temp( result.cstr() ); imapCapabilities = TQStringList::split ( ' ', kasciitolower( temp.data() ) ); } void imapParser::parseFlags (parseString & result) { selectInfo.setFlags(result.cstr()); } void imapParser::parseList (parseString & result) { imapList this_one; if (result[0] != '(') return; //not proper format for us result.pos++; // tie off ( this_one.parseAttributes( result ); result.pos++; // tie off ) skipWS (result); this_one.setHierarchyDelimiter(parseLiteralC(result)); this_one.setName (rfcDecoder::fromIMAP(parseLiteralC(result))); // decode modified UTF7 listResponses.append (this_one); } void imapParser::parseLsub (parseString & result) { imapList this_one (result.cstr(), *this); listResponses.append (this_one); } void imapParser::parseListRights (parseString & result) { parseOneWordC (result); // skip mailbox name parseOneWordC (result); // skip user id int outlen = 1; while ( outlen ) { TQCString word = parseOneWordC (result, false, &outlen); lastResults.append (word); } } void imapParser::parseAcl (parseString & result) { parseOneWordC (result); // skip mailbox name int outlen = 1; // The result is user1 perm1 user2 perm2 etc. The caller will sort it out. while ( outlen && !result.isEmpty() ) { TQCString word = parseLiteralC (result, false, false, &outlen); lastResults.append (word); } } void imapParser::parseAnnotation (parseString & result) { parseOneWordC (result); // skip mailbox name skipWS (result); parseOneWordC (result); // skip entry name (we know it since we don't allow wildcards in it) skipWS (result); if (result.isEmpty() || result[0] != '(') return; result.pos++; skipWS (result); int outlen = 1; // The result is name1 value1 name2 value2 etc. The caller will sort it out. while ( outlen && !result.isEmpty() && result[0] != ')' ) { TQCString word = parseLiteralC (result, false, false, &outlen); lastResults.append (word); } } void imapParser::parseQuota (parseString & result) { // quota_response ::= "QUOTA" SP astring SP quota_list // quota_list ::= "(" #quota_resource ")" // quota_resource ::= atom SP number SP number TQCString root = parseOneWordC( result ); if ( root.isEmpty() ) { lastResults.append( "" ); } else { lastResults.append( root ); } if (result.isEmpty() || result[0] != '(') return; result.pos++; skipWS (result); TQStringList triplet; int outlen = 1; while ( outlen && !result.isEmpty() && result[0] != ')' ) { TQCString word = parseLiteralC (result, false, false, &outlen); triplet.append(word); } lastResults.append( triplet.join(" ") ); } void imapParser::parseQuotaRoot (parseString & result) { // quotaroot_response // ::= "QUOTAROOT" SP astring *(SP astring) parseOneWordC (result); // skip mailbox name skipWS (result); if ( result.isEmpty() ) return; TQStringList roots; int outlen = 1; while ( outlen && !result.isEmpty() ) { TQCString word = parseLiteralC (result, false, false, &outlen); roots.append (word); } lastResults.append( roots.isEmpty()? "" : roots.join(" ") ); } void imapParser::parseCustom (parseString & result) { int outlen = 1; TQCString word = parseLiteralC (result, false, false, &outlen); lastResults.append( word ); } void imapParser::parseOtherUser (parseString & result) { lastResults.append( parseOneWordC( result ) ); } void imapParser::parseDelegate (parseString & result) { const TQString email = parseOneWordC( result ); TQStringList rights; int outlen = 1; while ( outlen && !result.isEmpty() ) { TQCString word = parseLiteralC( result, false, false, &outlen ); rights.append( word ); } lastResults.append( email + ':' + rights.join( "," ) ); } void imapParser::parseOutOfOffice (parseString & result) { const TQString state = parseOneWordC (result); parseOneWordC (result); // skip encoding int outlen = 1; TQCString msg = parseLiteralC (result, false, false, &outlen); lastResults.append( state + '^' + TQString::fromUtf8( msg ) ); } void imapParser::parseMyRights (parseString & result) { parseOneWordC (result); // skip mailbox name Q_ASSERT( lastResults.isEmpty() ); // we can only be called once lastResults.append (parseOneWordC (result) ); } void imapParser::parseSearch (parseString & result) { ulong value; while (parseOneNumber (result, value)) { lastResults.append (TQString::number(value)); } } void imapParser::parsetStatus (parseString & inWords) { lasStatus = imapInfo (); parseLiteralC(inWords); // swallow the box if (inWords.isEmpty() || inWords[0] != '(') return; inWords.pos++; skipWS (inWords); while (!inWords.isEmpty() && inWords[0] != ')') { ulong value; TQCString label = parseOneWordC(inWords); if (parseOneNumber (inWords, value)) { if (label == "MESSAGES") lasStatus.setCount (value); else if (label == "RECENT") lasStatus.setRecent (value); else if (label == "UIDVALIDITY") lasStatus.setUidValidity (value); else if (label == "UNSEEN") lasStatus.setUnseen (value); else if (label == "UIDNEXT") lasStatus.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, TQPtrList& list) { if (inWords.isEmpty()) return; if (inWords[0] != '(') { parseOneWordC (inWords); // parse NIL } else { inWords.pos++; skipWS (inWords); while (!inWords.isEmpty () && inWords[0] != ')') { if (inWords[0] == '(') { mailAddress *addr = new mailAddress; parseAddress(inWords, *addr); list.append(addr); } else { break; } } if (!inWords.isEmpty() && inWords[0] == ')') inWords.pos++; skipWS (inWords); } } const mailAddress& imapParser::parseAddress (parseString & inWords, mailAddress& retVal) { inWords.pos++; skipWS (inWords); retVal.setFullName(parseLiteralC(inWords)); retVal.setCommentRaw(parseLiteralC(inWords)); retVal.setUser(parseLiteralC(inWords)); retVal.setHost(parseLiteralC(inWords)); if (!inWords.isEmpty() && inWords[0] == ')') inWords.pos++; skipWS (inWords); return retVal; } mailHeader * imapParser::parseEnvelope (parseString & inWords) { mailHeader *envelope = 0; if (inWords[0] != '(') return envelope; inWords.pos++; skipWS (inWords); envelope = new mailHeader; //date envelope->setDate(parseLiteralC(inWords)); //subject envelope->setSubject(parseLiteralC(inWords)); TQPtrList list; list.setAutoDelete(true); //from parseAddressList(inWords, list); if (!list.isEmpty()) { envelope->setFrom(*list.last()); list.clear(); } //sender parseAddressList(inWords, list); if (!list.isEmpty()) { envelope->setSender(*list.last()); list.clear(); } //reply-to parseAddressList(inWords, list); if (!list.isEmpty()) { envelope->setReplyTo(*list.last()); list.clear(); } //to parseAddressList (inWords, envelope->to()); //cc parseAddressList (inWords, envelope->cc()); //bcc parseAddressList (inWords, envelope->bcc()); //in-reply-to envelope->setInReplyTo(parseLiteralC(inWords)); //message-id envelope->setMessageId(parseLiteralC(inWords)); // see if we have more to come while (!inWords.isEmpty () && inWords[0] != ')') { //eat the extensions to this part if (inWords[0] == '(') parseSentence (inWords); else parseLiteralC (inWords); } if (!inWords.isEmpty() && inWords[0] == ')') inWords.pos++; skipWS (inWords); return envelope; } // parse parameter pairs into a dictionary // caller must clean up the dictionary items TQAsciiDict < TQString > imapParser::parseDisposition (parseString & inWords) { TQCString disposition; TQAsciiDict < TQString > retVal (17, false); // return value is a shallow copy retVal.setAutoDelete (false); if (inWords[0] != '(') { //disposition only disposition = parseOneWordC (inWords); } else { inWords.pos++; skipWS (inWords); //disposition disposition = parseOneWordC (inWords); retVal = parseParameters (inWords); if (inWords[0] != ')') return retVal; inWords.pos++; skipWS (inWords); } if (!disposition.isEmpty ()) { retVal.insert ("content-disposition", new TQString(disposition)); } return retVal; } // parse parameter pairs into a dictionary // caller must clean up the dictionary items TQAsciiDict < TQString > imapParser::parseParameters (parseString & inWords) { TQAsciiDict < TQString > retVal (17, false); // return value is a shallow copy retVal.setAutoDelete (false); if (inWords[0] != '(') { //better be NIL parseOneWordC (inWords); } else { inWords.pos++; skipWS (inWords); while (!inWords.isEmpty () && inWords[0] != ')') { TQCString l1 = parseLiteralC(inWords); TQCString l2 = parseLiteralC(inWords); retVal.insert (l1, new TQString(l2)); } if (inWords[0] != ')') return retVal; inWords.pos++; skipWS (inWords); } return retVal; } mimeHeader * imapParser::parseSimplePart (parseString & inWords, TQString & inSection, mimeHeader * localPart) { TQCString subtype; TQCString typeStr; TQAsciiDict < TQString > parameters (17, false); ulong size; parameters.setAutoDelete (true); if (inWords[0] != '(') return 0; if (!localPart) localPart = new mimeHeader; localPart->setPartSpecifier (inSection); inWords.pos++; skipWS (inWords); //body type typeStr = parseLiteralC(inWords); //body subtype subtype = parseLiteralC(inWords); localPart->setType (typeStr + "/" + subtype); //body parameter parenthesized list parameters = parseParameters (inWords); { TQAsciiDictIterator < TQString > it (parameters); while (it.current ()) { localPart->setTypeParm (it.currentKey (), *(it.current ())); ++it; } parameters.clear (); } //body id localPart->setID (parseLiteralC(inWords)); //body description localPart->setDescription (parseLiteralC(inWords)); //body encoding localPart->setEncoding (parseLiteralC(inWords)); //body size if (parseOneNumber (inWords, size)) localPart->setLength (size); // type specific extensions if (localPart->getType().upper() == "MESSAGE/RFC822") { //envelope structure mailHeader *envelope = parseEnvelope (inWords); //body structure parseBodyStructure (inWords, inSection, envelope); localPart->setNestedMessage (envelope); //text lines ulong lines; parseOneNumber (inWords, lines); } else { if (typeStr == "TEXT") { //text lines ulong lines; parseOneNumber (inWords, lines); } // md5 parseLiteralC(inWords); // body disposition parameters = parseDisposition (inWords); { TQString *disposition = parameters["content-disposition"]; if (disposition) localPart->setDisposition (disposition->ascii ()); parameters.remove ("content-disposition"); TQAsciiDictIterator < TQString > it (parameters); while (it.current ()) { localPart->setDispositionParm (it.currentKey (), *(it.current ())); ++it; } parameters.clear (); } // body language parseSentence (inWords); } // see if we have more to come while (!inWords.isEmpty () && inWords[0] != ')') { //eat the extensions to this part if (inWords[0] == '(') parseSentence (inWords); else parseLiteralC(inWords); } if (inWords[0] == ')') inWords.pos++; skipWS (inWords); return localPart; } mimeHeader * imapParser::parseBodyStructure (parseString & inWords, TQString & inSection, mimeHeader * localPart) { bool init = false; if (inSection.isEmpty()) { // first run init = true; // assume one part inSection = "1"; } int section = 0; if (inWords[0] != '(') { // skip "" parseOneWordC (inWords); return 0; } inWords.pos++; skipWS (inWords); if (inWords[0] == '(') { TQByteArray subtype; TQAsciiDict < TQString > parameters (17, false); TQString outSection; parameters.setAutoDelete (true); if (!localPart) localPart = new mimeHeader; else { // might be filled from an earlier run localPart->clearNestedParts (); localPart->clearTypeParameters (); localPart->clearDispositionParameters (); // an envelope was passed in so this is the multipart header outSection = inSection + ".HEADER"; } if (inWords[0] == '(' && init) inSection = "0"; // set the section if ( !outSection.isEmpty() ) { localPart->setPartSpecifier(outSection); } else { localPart->setPartSpecifier(inSection); } // is multipart (otherwise it is a simplepart and handled later) while (inWords[0] == '(') { outSection = TQString::number(++section); if (!init) outSection = inSection + "." + outSection; mimeHeader *subpart = parseBodyStructure (inWords, outSection, 0); localPart->addNestedPart (subpart); } // fetch subtype subtype = parseOneWordC (inWords); localPart->setType ("MULTIPART/" + b2c(subtype)); // fetch parameters parameters = parseParameters (inWords); { TQAsciiDictIterator < TQString > it (parameters); while (it.current ()) { localPart->setTypeParm (it.currentKey (), *(it.current ())); ++it; } parameters.clear (); } // body disposition parameters = parseDisposition (inWords); { TQString *disposition = parameters["content-disposition"]; if (disposition) localPart->setDisposition (disposition->ascii ()); parameters.remove ("content-disposition"); TQAsciiDictIterator < TQString > it (parameters); while (it.current ()) { localPart->setDispositionParm (it.currentKey (), *(it.current ())); ++it; } parameters.clear (); } // body language parseSentence (inWords); } else { // is simple part inWords.pos--; inWords.data[inWords.pos] = '('; //fake a sentence if ( localPart ) inSection = inSection + ".1"; localPart = parseSimplePart (inWords, inSection, localPart); inWords.pos--; inWords.data[inWords.pos] = ')'; //remove fake } // see if we have more to come while (!inWords.isEmpty () && inWords[0] != ')') { //eat the extensions to this part if (inWords[0] == '(') parseSentence (inWords); else parseLiteralC(inWords); } if (inWords[0] == ')') inWords.pos++; skipWS (inWords); return localPart; } void imapParser::parseBody (parseString & inWords) { // see if we got a part specifier if (inWords[0] == '[') { TQCString specifier; TQCString label; inWords.pos++; specifier = parseOneWordC (inWords, TRUE); if (inWords[0] == '(') { inWords.pos++; while (!inWords.isEmpty () && inWords[0] != ')') { label = parseOneWordC (inWords); } if (!inWords.isEmpty () && inWords[0] == ')') inWords.pos++; } if (!inWords.isEmpty () && inWords[0] == ']') inWords.pos++; skipWS (inWords); // parse the header if (specifier == "0") { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); if (!envelope || seenUid.isEmpty ()) { kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl; // don't know where to put it, throw it away parseLiteralC(inWords, true); } else { kdDebug(7116) << "imapParser::parseBody - reading " << envelope << " " << seenUid.ascii () << endl; // fill it up with data TQString theHeader = parseLiteralC(inWords, true); mimeIOTQString myIO; myIO.setString (theHeader); envelope->parseHeader (myIO); } } else if (specifier == "HEADER.FIELDS") { // BODY[HEADER.FIELDS (References)] {n} //kdDebug(7116) << "imapParser::parseBody - HEADER.FIELDS: " // << TQCString(label.data(), label.size()+1) << endl; if (label == "REFERENCES") { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); if (!envelope || seenUid.isEmpty ()) { kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl; // don't know where to put it, throw it away parseLiteralC (inWords, true); } else { TQCString references = parseLiteralC(inWords, true); int start = references.find ('<'); int end = references.findRev ('>'); if (start < end) references = references.mid (start, end - start + 1); envelope->setReferences(references.simplifyWhiteSpace()); } } else { // not a header we care about throw it away parseLiteralC(inWords, true); } } else { if (specifier.find(".MIME") != -1) { mailHeader *envelope = new mailHeader; TQString theHeader = parseLiteralC(inWords, false); mimeIOTQString myIO; myIO.setString (theHeader); envelope->parseHeader (myIO); if (lastHandled) lastHandled->setHeader (envelope); return; } // throw it away kdDebug(7116) << "imapParser::parseBody - discarding " << seenUid.ascii () << endl; parseLiteralC(inWords, true); } } else // no part specifier { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); if (!envelope || seenUid.isEmpty ()) { kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl; // don't know where to put it, throw it away parseSentence (inWords); } else { kdDebug(7116) << "imapParser::parseBody - reading " << envelope << " " << seenUid.ascii () << endl; // fill it up with data TQString 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 { TQCString word = parseLiteralC(inWords, false, true); if(!word.isEmpty()) { switch (word[0]) { case 'E': if (word == "ENVELOPE") { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); else lastHandled = new imapCache(); if (envelope && !envelope->getMessageId ().isEmpty ()) { // we have seen this one already // or don't know where to put it parseSentence (inWords); } else { envelope = parseEnvelope (inWords); if (envelope) { envelope->setPartSpecifier (seenUid + ".0"); lastHandled->setHeader (envelope); lastHandled->setUid (seenUid.toULong ()); } } } break; case 'B': if (word == "BODY") { parseBody (inWords); } else if (word == "BODY[]" ) { // Do the same as with "RFC822" parseLiteralC(inWords, true); } else if (word == "BODYSTRUCTURE") { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); // fill it up with data TQString section; mimeHeader *body = parseBodyStructure (inWords, section, envelope); TQByteArray data; TQDataStream stream( data, IO_WriteOnly ); if (body) body->serialize(stream); parseRelay(data); delete body; } break; case 'U': if (word == "UID") { seenUid = parseOneWordC(inWords); mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); else lastHandled = new imapCache(); if (seenUid.isEmpty ()) { // unknown what to do kdDebug(7116) << "imapParser::parseFetch - UID empty" << endl; } else { lastHandled->setUid (seenUid.toULong ()); } if (envelope) envelope->setPartSpecifier (seenUid); } break; case 'R': if (word == "RFC822.SIZE") { ulong size; parseOneNumber (inWords, size); if (!lastHandled) lastHandled = new imapCache(); lastHandled->setSize (size); } else if (word.find ("RFC822") == 0) { // might be RFC822 RFC822.TEXT RFC822.HEADER parseLiteralC(inWords, true); } break; case 'I': if (word == "INTERNALDATE") { TQCString date = parseOneWordC(inWords); if (!lastHandled) lastHandled = new imapCache(); lastHandled->setDate(date); } break; case 'F': if (word == "FLAGS") { //kdDebug(7116) << "GOT FLAGS " << inWords.cstr() << endl; if (!lastHandled) lastHandled = new imapCache(); lastHandled->setFlags (imapInfo::_flags (inWords.cstr())); } break; default: parseLiteralC(inWords); break; } } else { parseLiteralC(inWords); } } } // see if we have more to come while (!inWords.isEmpty () && inWords[0] != ')') { //eat the extensions to this part if (inWords[0] == '(') parseSentence (inWords); else parseLiteralC(inWords); } if (inWords.isEmpty() || inWords[0] != ')') return; inWords.pos++; skipWS (inWords); } // default parser void imapParser::parseSentence (parseString & inWords) { bool first = true; int stack = 0; //find the first nesting parentheses while (!inWords.isEmpty () && (stack != 0 || first)) { first = false; skipWS (inWords); unsigned char ch = inWords[0]; switch (ch) { case '(': inWords.pos++; ++stack; break; case ')': inWords.pos++; --stack; break; case '[': inWords.pos++; ++stack; break; case ']': inWords.pos++; --stack; break; default: parseLiteralC(inWords); skipWS (inWords); break; } } skipWS (inWords); } void imapParser::parseRecent (ulong value, parseString & result) { selectInfo.setRecent (value); result.pos = result.data.size(); } void imapParser::parseNamespace (parseString & result) { if ( result[0] != '(' ) return; TQString delimEmpty; if ( namespaceToDelimiter.contains( TQString() ) ) delimEmpty = namespaceToDelimiter[TQString()]; 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 TQCString prefix = parseOneWordC( result ); // delimiter TQCString delim = parseOneWordC( result ); kdDebug(7116) << "imapParser::parseNamespace ns='" << prefix << "',delim='" << delim << "'" << endl; if ( ns == 0 ) { // at least one personal ns personalAvailable = true; } TQString nsentry = TQString::number( ns ) + "=" + TQString(prefix) + "=" + TQString(delim); imapNamespaces.append( nsentry ); if ( prefix.right( 1 ) == delim ) { // strip delimiter to get a correct entry for comparisons prefix.resize( prefix.length() ); } namespaceToDelimiter[prefix] = delim; result.pos++; // tie off ) skipWS( result ); } else if ( result[0] == ')' ) { result.pos++; // tie off ) skipWS( result ); } else if ( result[0] == 'N' ) { // drop NIL ++ns; parseOneWordC( result ); } else { // drop whatever it is parseOneWordC( result ); } } if ( !delimEmpty.isEmpty() ) { // remember default delimiter namespaceToDelimiter[TQString()] = delimEmpty; if ( !personalAvailable ) { // at least one personal ns would be nice kdDebug(7116) << "imapParser::parseNamespace - registering own personal ns" << endl; TQString nsentry = "0==" + delimEmpty; imapNamespaces.append( nsentry ); } } } int imapParser::parseLoop () { parseString result; if (!parseReadLine(result.data)) return -1; //kdDebug(7116) << result.cstr(); // includes \n if (result.data.isEmpty()) return 0; if (!sentQueue.count ()) { // maybe greeting or BYE everything else SHOULD not happen, use NOOP or IDLE kdDebug(7116) << "imapParser::parseLoop - unhandledResponse: \n" << result.cstr() << endl; unhandled << result.cstr(); } else { imapCommand *current = sentQueue.at (0); switch (result[0]) { case '*': result.data.resize(result.data.size() - 2); // tie off CRLF parseUntagged (result); break; case '+': continuation.duplicate(result.data); break; default: { TQCString tag = parseLiteralC(result); if (current->id() == tag.data()) { result.data.resize(result.data.size() - 2); // tie off CRLF TQByteArray resultCode = parseLiteral (result); //the result current->setResult (resultCode); current->setResultInfo(result.cstr()); current->setComplete (); sentQueue.removeRef (current); completeQueue.append (current); if (result.length()) parseResult (resultCode, result, current->command()); } else { kdDebug(7116) << "imapParser::parseLoop - unknown tag '" << tag << "'" << endl; TQCString cstr = tag + " " + result.cstr(); result.data = cstr; result.pos = 0; result.data.resize(cstr.length()); } } break; } } return 1; } void imapParser::parseRelay (const TQByteArray & buffer) { Q_UNUSED(buffer); tqWarning ("imapParser::parseRelay - virtual function not reimplemented - data lost"); } void imapParser::parseRelay (ulong len) { Q_UNUSED(len); tqWarning ("imapParser::parseRelay - virtual function not reimplemented - announcement lost"); } bool imapParser::parseRead (TQByteArray & buffer, ulong len, ulong relay) { Q_UNUSED(buffer); Q_UNUSED(len); Q_UNUSED(relay); tqWarning ("imapParser::parseRead - virtual function not reimplemented - no data read"); return FALSE; } bool imapParser::parseReadLine (TQByteArray & buffer, ulong relay) { Q_UNUSED(buffer); Q_UNUSED(relay); tqWarning ("imapParser::parseReadLine - virtual function not reimplemented - no data read"); return FALSE; } void imapParser::parseWriteLine (const TQString & str) { Q_UNUSED(str); tqWarning ("imapParser::parseWriteLine - virtual function not reimplemented - no data written"); } void imapParser::parseURL (const KURL & _url, TQString & _box, TQString & _section, TQString & _type, TQString & _uid, TQString & _validity, TQString & _info) { TQStringList parameters; _box = _url.path (); kdDebug(7116) << "imapParser::parseURL " << _box << endl; int paramStart = _box.find("/;"); if ( paramStart > -1 ) { TQString paramString = _box.right( _box.length() - paramStart-2 ); parameters = TQStringList::split (';', paramString); //split parameters _box.truncate( paramStart ); // strip parameters } // extract parameters for (TQStringList::ConstIterator it (parameters.begin ()); it != parameters.end (); ++it) { TQString temp = (*it); int pt = temp.find ('/'); if (pt > 0) { if (temp.findRev ('"', pt) == -1 || temp.find('"', pt) == -1) { // if we have non-quoted '/' separator we'll just nuke it temp.truncate(pt); } } if (temp.find ("section=", 0, false) == 0) _section = temp.right (temp.length () - 8); else if (temp.find ("type=", 0, false) == 0) _type = temp.right (temp.length () - 5); else if (temp.find ("uid=", 0, false) == 0) _uid = temp.right (temp.length () - 4); else if (temp.find ("uidvalidity=", 0, false) == 0) _validity = temp.right (temp.length () - 12); else if (temp.find ("info=", 0, false) == 0) _info = temp.right (temp.length () - 5); } // kdDebug(7116) << "URL: section= " << _section << ", type= " << _type << ", uid= " << _uid << endl; // kdDebug(7116) << "URL: user() " << _url.user() << endl; // kdDebug(7116) << "URL: path() " << _url.path() << endl; // kdDebug(7116) << "URL: encodedPathAndQuery() " << _url.encodedPathAndQuery() << endl; if (!_box.isEmpty ()) { // strip / if (_box[0] == '/') _box = _box.right (_box.length () - 1); if (!_box.isEmpty () && _box[_box.length () - 1] == '/') _box.truncate(_box.length() - 1); } kdDebug(7116) << "URL: box= " << _box << ", section= " << _section << ", type= " << _type << ", uid= " << _uid << ", validity= " << _validity << ", info= " << _info << endl; } TQCString imapParser::parseLiteralC(parseString & inWords, bool relay, bool stopAtBracket, int *outlen) { if (!inWords.isEmpty() && inWords[0] == '{') { TQCString retVal; long srunLen = inWords.find ('}', 1); // Can return -1, so use a signed long if (srunLen > 0) { ulong runLen = (ulong)srunLen; bool proper; ulong runLenSave = runLen + 1; TQCString tmpstr(runLen); inWords.takeMidNoResize(tmpstr, 1, runLen - 1); runLen = tmpstr.toULong (&proper); inWords.pos += runLenSave; if (proper) { //now get the literal from the server if (relay) parseRelay (runLen); TQByteArray rv; parseRead (rv, runLen, relay ? runLen : 0); rv.resize(TQMAX(runLen, rv.size())); // what's the point? retVal = b2c(rv); inWords.clear(); parseReadLine (inWords.data); // must get more // no duplicate data transfers relay = false; } else { kdDebug(7116) << "imapParser::parseLiteral - error parsing {} - " /*<< strLen*/ << endl; } } else { inWords.clear(); kdDebug(7116) << "imapParser::parseLiteral - error parsing unmatched {" << endl; } if (outlen) { *outlen = retVal.length(); // optimize me } skipWS (inWords); return retVal; } return parseOneWordC(inWords, stopAtBracket, outlen); } // does not know about literals ( {7} literal ) TQCString imapParser::parseOneWordC (parseString & inWords, bool stopAtBracket, int *outLen) { uint retValSize = 0; uint len = inWords.length(); if (len == 0) { return TQCString(); } 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) { TQCString retVal(i); inWords.pos++; inWords.takeLeftNoResize(retVal, i - 1); len = i - 1; int offset = 0; for (unsigned int j = 0; j <= len; j++) { if (retVal[j] == '\\') { offset++; j++; } retVal[j - offset] = retVal[j]; } retVal[len - offset] = 0; retValSize = len - offset; inWords.pos += i; skipWS (inWords); if (outLen) { *outLen = retValSize; } return retVal; } else { kdDebug(7116) << "imapParser::parseOneWord - error parsing unmatched \"" << endl; TQCString retVal = inWords.cstr(); retValSize = len; inWords.clear(); if (outLen) { *outLen = retValSize; } return retVal; } } else { // not quoted unsigned int i; // search for end for (i = 0; i < len; ++i) { char ch = inWords[i]; if (ch <= ' ' || ch == '(' || ch == ')' || (stopAtBracket && (ch == '[' || ch == ']'))) break; } TQCString retVal(i+1); inWords.takeLeftNoResize(retVal, i); retValSize = i; inWords.pos += i; if (retVal == "NIL") { retVal.truncate(0); retValSize = 0; } skipWS (inWords); if (outLen) { *outLen = retValSize; } return retVal; } } bool imapParser::parseOneNumber (parseString & inWords, ulong & num) { bool valid; num = parseOneWordC(inWords, TRUE).toULong(&valid); return valid; } bool imapParser::hasCapability (const TQString & cap) { TQString c = cap.lower(); // kdDebug(7116) << "imapParser::hasCapability - Looking for '" << cap << "'" << endl; for (TQStringList::ConstIterator it = imapCapabilities.begin (); it != imapCapabilities.end (); ++it) { // kdDebug(7116) << "imapParser::hasCapability - Examining '" << (*it) << "'" << endl; if ( !(kasciistricmp(c.ascii(), (*it).ascii())) ) { return true; } } return false; } void imapParser::removeCapability (const TQString & cap) { imapCapabilities.remove(cap.lower()); } TQString imapParser::namespaceForBox( const TQString & box ) { kdDebug(7116) << "imapParse::namespaceForBox " << box << endl; TQString myNamespace; if ( !box.isEmpty() ) { TQValueList list = namespaceToDelimiter.keys(); TQString cleanPrefix; for ( TQValueList::Iterator it = list.begin(); it != list.end(); ++it ) { if ( !(*it).isEmpty() && box.find( *it ) != -1 ) return (*it); } } return myNamespace; }