kioslave/imap4
imapparser.cpp
00001 /********************************************************************** 00002 * 00003 * imapparser.cc - IMAP4rev1 Parser 00004 * Copyright (C) 2001-2002 Michael Haeckel <haeckel@kde.org> 00005 * Copyright (C) 2000 Sven Carstens <s.carstens@gmx.de> 00006 * 00007 * This program is free software; you can redistribute it and/or modify 00008 * it under the terms of the GNU General Public License as published by 00009 * the Free Software Foundation; either version 2 of the License, or 00010 * (at your option) any later version. 00011 * 00012 * This program is distributed in the hope that it will be useful, 00013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00015 * GNU General Public License for more details. 00016 * 00017 * You should have received a copy of the GNU General Public License 00018 * along with this program; if not, write to the Free Software 00019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00020 * 00021 * Send comments and bug fixes to s.carstens@gmx.de 00022 * 00023 *********************************************************************/ 00024 00025 #include "imapparser.h" 00026 #include "imapinfo.h" 00027 #include "mailheader.h" 00028 #include "mimeheader.h" 00029 #include "mailaddress.h" 00030 00031 #include <sys/types.h> 00032 00033 #include <stdlib.h> 00034 #include <unistd.h> 00035 #include <QList> 00036 00037 extern "C" { 00038 #include <sasl/sasl.h> 00039 } 00040 00041 #include <QRegExp> 00042 #include <QBuffer> 00043 #include <QString> 00044 #include <QStringList> 00045 00046 #include <kascii.h> 00047 #include <kdebug.h> 00048 #include <kcodecs.h> 00049 #include <kglobal.h> 00050 #include <kurl.h> 00051 00052 #include <kimap/rfccodecs.h> 00053 using namespace KIMAP; 00054 00055 static sasl_callback_t callbacks[] = { 00056 { SASL_CB_ECHOPROMPT, NULL, NULL }, 00057 { SASL_CB_NOECHOPROMPT, NULL, NULL }, 00058 { SASL_CB_GETREALM, NULL, NULL }, 00059 { SASL_CB_USER, NULL, NULL }, 00060 { SASL_CB_AUTHNAME, NULL, NULL }, 00061 { SASL_CB_PASS, NULL, NULL }, 00062 { SASL_CB_CANON_USER, NULL, NULL }, 00063 { SASL_CB_LIST_END, NULL, NULL } 00064 }; 00065 00066 imapParser::imapParser () 00067 { 00068 currentState = ISTATE_NO; 00069 commandCounter = 0; 00070 lastHandled = 0; 00071 } 00072 00073 imapParser::~imapParser () 00074 { 00075 delete lastHandled; 00076 lastHandled = 0; 00077 } 00078 00079 CommandPtr 00080 imapParser::doCommand (CommandPtr aCmd) 00081 { 00082 int pl = 0; 00083 sendCommand (aCmd); 00084 while (pl != -1 && !aCmd->isComplete ()) { 00085 while ((pl = parseLoop ()) == 0) 00086 ; 00087 } 00088 00089 return aCmd; 00090 } 00091 00092 CommandPtr 00093 imapParser::sendCommand (CommandPtr aCmd) 00094 { 00095 aCmd->setId (QString::number(commandCounter++)); 00096 sentQueue.append (aCmd); 00097 00098 continuation.resize(0); 00099 const QString& command = aCmd->command(); 00100 00101 if (command == "SELECT" || command == "EXAMINE") 00102 { 00103 // we need to know which box we are selecting 00104 parseString p; 00105 p.fromString(aCmd->parameter()); 00106 currentBox = parseOneWord(p); 00107 kDebug(7116) <<"imapParser::sendCommand - setting current box to" << currentBox; 00108 } 00109 else if (command == "CLOSE") 00110 { 00111 // we no longer have a box open 00112 currentBox.clear(); 00113 } 00114 else if (command.contains("SEARCH") 00115 || command == "GETACL" 00116 || command == "LISTRIGHTS" 00117 || command == "MYRIGHTS" 00118 || command == "GETANNOTATION" 00119 || command == "NAMESPACE" 00120 || command == "GETQUOTAROOT" 00121 || command == "GETQUOTA" 00122 || command == "X-GET-OTHER-USERS" 00123 || command == "X-GET-DELEGATES" 00124 || command == "X-GET-OUT-OF-OFFICE") 00125 { 00126 lastResults.clear (); 00127 } 00128 else if (command == "LIST" 00129 || command == "LSUB") 00130 { 00131 listResponses.clear (); 00132 } 00133 parseWriteLine (aCmd->getStr ()); 00134 return aCmd; 00135 } 00136 00137 bool 00138 imapParser::clientLogin (const QString & aUser, const QString & aPass, 00139 QString & resultInfo) 00140 { 00141 CommandPtr cmd; 00142 bool retVal = false; 00143 00144 cmd = 00145 doCommand ( CommandPtr( new 00146 imapCommand ("LOGIN", "\"" + KIMAP::quoteIMAP(aUser) 00147 + "\" \"" + KIMAP::quoteIMAP(aPass) + "\"")) ); 00148 00149 if (cmd->result () == "OK") 00150 { 00151 currentState = ISTATE_LOGIN; 00152 retVal = true; 00153 } 00154 resultInfo = cmd->resultInfo(); 00155 completeQueue.removeAll (cmd); 00156 return retVal; 00157 } 00158 00159 static bool sasl_interact( KIO::SlaveBase *slave, KIO::AuthInfo &ai, void *in ) 00160 { 00161 kDebug(7116) <<"sasl_interact"; 00162 sasl_interact_t *interact = ( sasl_interact_t * ) in; 00163 00164 //some mechanisms do not require username && pass, so it doesn't need a popup 00165 //window for getting this info 00166 for ( ; interact->id != SASL_CB_LIST_END; interact++ ) { 00167 if ( interact->id == SASL_CB_AUTHNAME || 00168 interact->id == SASL_CB_PASS ) { 00169 00170 if ( ai.username.isEmpty() || ai.password.isEmpty() ) { 00171 if (!slave->openPasswordDialog(ai)) 00172 return false; 00173 } 00174 break; 00175 } 00176 } 00177 00178 interact = ( sasl_interact_t * ) in; 00179 while( interact->id != SASL_CB_LIST_END ) { 00180 kDebug(7116) <<"SASL_INTERACT id:" << interact->id; 00181 switch( interact->id ) { 00182 case SASL_CB_USER: 00183 case SASL_CB_AUTHNAME: 00184 kDebug(7116) <<"SASL_CB_[USER|AUTHNAME]: '" << ai.username <<"'"; 00185 interact->result = strdup( ai.username.toUtf8() ); 00186 interact->len = strlen( (const char *) interact->result ); 00187 break; 00188 case SASL_CB_PASS: 00189 kDebug(7116) <<"SASL_CB_PASS: [hidden]"; 00190 interact->result = strdup( ai.password.toUtf8() ); 00191 interact->len = strlen( (const char *) interact->result ); 00192 break; 00193 default: 00194 interact->result = 0; 00195 interact->len = 0; 00196 break; 00197 } 00198 interact++; 00199 } 00200 return true; 00201 } 00202 00203 bool 00204 imapParser::clientAuthenticate ( KIO::SlaveBase *slave, KIO::AuthInfo &ai, 00205 const QString & aFQDN, const QString & aAuth, bool isSSL, QString & resultInfo) 00206 { 00207 bool retVal = false; 00208 int result; 00209 sasl_conn_t *conn = 0; 00210 sasl_interact_t *client_interact = 0; 00211 const char *out = 0; 00212 uint outlen = 0; 00213 const char *mechusing = 0; 00214 QByteArray tmp, challenge; 00215 00216 kDebug(7116) <<"aAuth:" << aAuth <<" FQDN:" << aFQDN <<" isSSL:" << isSSL; 00217 00218 // see if server supports this authenticator 00219 if (!hasCapability ("AUTH=" + aAuth)) 00220 return false; 00221 00222 // result = sasl_client_new( isSSL ? "imaps" : "imap", 00223 result = sasl_client_new( "imap", /* FIXME: with cyrus-imapd, even imaps' digest-uri 00224 must be 'imap'. I don't know if it's good or bad. */ 00225 aFQDN.toLatin1(), 00226 0, 0, callbacks, 0, &conn ); 00227 00228 if ( result != SASL_OK ) { 00229 kDebug(7116) <<"sasl_client_new failed with:" << result; 00230 resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); 00231 return false; 00232 } 00233 00234 do { 00235 result = sasl_client_start(conn, aAuth.toLatin1(), &client_interact, 00236 hasCapability("SASL-IR") ? &out : 0, &outlen, &mechusing); 00237 00238 if ( result == SASL_INTERACT ) { 00239 if ( !sasl_interact( slave, ai, client_interact ) ) { 00240 sasl_dispose( &conn ); 00241 return false; 00242 } 00243 } 00244 } while ( result == SASL_INTERACT ); 00245 00246 if ( result != SASL_CONTINUE && result != SASL_OK ) { 00247 kDebug(7116) <<"sasl_client_start failed with:" << result; 00248 resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); 00249 sasl_dispose( &conn ); 00250 return false; 00251 } 00252 CommandPtr cmd; 00253 00254 tmp = QByteArray::fromRawData( out, outlen ); 00255 challenge = tmp.toBase64(); 00256 tmp.clear(); 00257 // then lets try it 00258 QString firstCommand = aAuth; 00259 if ( !challenge.isEmpty() ) { 00260 firstCommand += ' '; 00261 firstCommand += QString::fromLatin1( challenge.data(), challenge.size() ); 00262 } 00263 cmd = sendCommand (CommandPtr(new imapCommand ("AUTHENTICATE", firstCommand.toLatin1()))); 00264 00265 int pl = 0; 00266 while ( pl != -1 && !cmd->isComplete () ) { 00267 //read the next line 00268 while ( ( pl = parseLoop() ) == 0) { 00269 ; 00270 } 00271 00272 if (!continuation.isEmpty()) 00273 { 00274 // kDebug(7116) <<"S:" << QCString(continuation.data(),continuation.size()+1); 00275 if ( continuation.size() > 4 ) { 00276 tmp = QByteArray::fromRawData( continuation.data() + 2, continuation.size() - 4 ); 00277 challenge = QByteArray::fromBase64( tmp ); 00278 // kDebug(7116) <<"S-1:" << QCString(challenge.data(),challenge.size()+1); 00279 tmp.clear(); 00280 } 00281 00282 do { 00283 result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(), 00284 challenge.size(), 00285 &client_interact, 00286 &out, &outlen); 00287 00288 if (result == SASL_INTERACT) { 00289 if ( !sasl_interact( slave, ai, client_interact ) ) { 00290 sasl_dispose( &conn ); 00291 return false; 00292 } 00293 } 00294 } while ( result == SASL_INTERACT ); 00295 00296 if ( result != SASL_CONTINUE && result != SASL_OK ) { 00297 kDebug(7116) <<"sasl_client_step failed with:" << result; 00298 resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); 00299 sasl_dispose( &conn ); 00300 return false; 00301 } 00302 00303 tmp = QByteArray::fromRawData( out, outlen ); 00304 // kDebug(7116) <<"C-1:" << QCString(tmp.data(),tmp.size()+1); 00305 challenge = tmp.toBase64(); 00306 tmp.clear(); 00307 // kDebug(7116) <<"C:" << QCString(challenge.data(),challenge.size()+1); 00308 parseWriteLine (challenge); 00309 continuation.resize(0); 00310 } 00311 } 00312 00313 if (cmd->result () == "OK") 00314 { 00315 currentState = ISTATE_LOGIN; 00316 retVal = true; 00317 } 00318 resultInfo = cmd->resultInfo(); 00319 completeQueue.removeAll (cmd); 00320 00321 sasl_dispose( &conn ); //we don't use sasl_en/decode(), so it's safe to dispose the connection. 00322 return retVal; 00323 } 00324 00325 void 00326 imapParser::parseUntagged (parseString & result) 00327 { 00328 //kDebug(7116) <<"imapParser::parseUntagged - '" << result.cstr() <<"'"; 00329 00330 parseOneWord(result); // * 00331 QByteArray what = parseLiteral (result); // see whats coming next 00332 00333 switch (what[0]) 00334 { 00335 //the status responses 00336 case 'B': // BAD or BYE 00337 if (qstrncmp(what, "BAD", what.size()) == 0) 00338 { 00339 parseResult (what, result); 00340 } 00341 else if (qstrncmp(what, "BYE", what.size()) == 0) 00342 { 00343 parseResult (what, result); 00344 if ( sentQueue.count() ) { 00345 // BYE that interrupts a command -> copy the reason for it 00346 CommandPtr current = sentQueue.at (0); 00347 current->setResultInfo(result.cstr()); 00348 } 00349 currentState = ISTATE_NO; 00350 } 00351 break; 00352 00353 case 'N': // NO 00354 if (what[1] == 'O' && what.size() == 2) 00355 { 00356 parseResult (what, result); 00357 } 00358 else if (qstrncmp(what, "NAMESPACE", what.size()) == 0) 00359 { 00360 parseNamespace (result); 00361 } 00362 break; 00363 00364 case 'O': // OK 00365 if (what[1] == 'K' && what.size() == 2) 00366 { 00367 parseResult (what, result); 00368 } else if (qstrncmp(what, "OTHER-USER", 10) == 0) { // X-GET-OTHER-USER 00369 parseOtherUser (result); 00370 } else if (qstrncmp(what, "OUT-OF-OFFICE", 13) == 0) { // X-GET-OUT-OF-OFFICE 00371 parseOutOfOffice (result); 00372 } 00373 break; 00374 case 'D': 00375 if (qstrncmp(what, "DELEGATE", 8) == 0) { // X-GET-DELEGATES 00376 parseDelegate (result); 00377 } 00378 break; 00379 00380 case 'P': // PREAUTH 00381 if (qstrncmp(what, "PREAUTH", what.size()) == 0) 00382 { 00383 parseResult (what, result); 00384 currentState = ISTATE_LOGIN; 00385 } 00386 break; 00387 00388 // parse the other responses 00389 case 'C': // CAPABILITY 00390 if (qstrncmp(what, "CAPABILITY", what.size()) == 0) 00391 { 00392 parseCapability (result); 00393 } 00394 break; 00395 00396 case 'F': // FLAGS 00397 if (qstrncmp(what, "FLAGS", what.size()) == 0) 00398 { 00399 parseFlags (result); 00400 } 00401 break; 00402 00403 case 'L': // LIST or LSUB or LISTRIGHTS 00404 if (qstrncmp(what, "LIST", what.size()) == 0) 00405 { 00406 parseList (result); 00407 } 00408 else if (qstrncmp(what, "LSUB", what.size()) == 0) 00409 { 00410 parseLsub (result); 00411 } 00412 else if (qstrncmp(what, "LISTRIGHTS", what.size()) == 0) 00413 { 00414 parseListRights (result); 00415 } 00416 break; 00417 00418 case 'M': // MYRIGHTS 00419 if (qstrncmp(what, "MYRIGHTS", what.size()) == 0) 00420 { 00421 parseMyRights (result); 00422 } 00423 break; 00424 case 'S': // SEARCH or STATUS 00425 if (qstrncmp(what, "SEARCH", what.size()) == 0) 00426 { 00427 parseSearch (result); 00428 } 00429 else if (qstrncmp(what, "STATUS", what.size()) == 0) 00430 { 00431 parseStatus (result); 00432 } 00433 break; 00434 00435 case 'A': // ACL or ANNOTATION 00436 if (qstrncmp(what, "ACL", what.size()) == 0) 00437 { 00438 parseAcl (result); 00439 } 00440 else if (qstrncmp(what, "ANNOTATION", what.size()) == 0) 00441 { 00442 parseAnnotation (result); 00443 } 00444 break; 00445 case 'Q': // QUOTA or QUOTAROOT 00446 if ( what.size() > 5 && qstrncmp(what, "QUOTAROOT", what.size()) == 0) 00447 { 00448 parseQuotaRoot( result ); 00449 } 00450 else if (qstrncmp(what, "QUOTA", what.size()) == 0) 00451 { 00452 parseQuota( result ); 00453 } 00454 break; 00455 case 'X': // Custom command 00456 { 00457 parseCustom( result ); 00458 } 00459 break; 00460 default: 00461 //better be a number 00462 { 00463 ulong number; 00464 bool valid; 00465 00466 number = what.toUInt(&valid); 00467 if (valid) 00468 { 00469 what = parseLiteral (result); 00470 switch (what[0]) 00471 { 00472 case 'E': 00473 if (qstrncmp(what, "EXISTS", what.size()) == 0) 00474 { 00475 parseExists (number, result); 00476 } 00477 else if (qstrncmp(what, "EXPUNGE", what.size()) == 0) 00478 { 00479 parseExpunge (number, result); 00480 } 00481 break; 00482 00483 case 'F': 00484 if (qstrncmp(what, "FETCH", what.size()) == 0) 00485 { 00486 seenUid.clear(); 00487 parseFetch (number, result); 00488 } 00489 break; 00490 00491 case 'S': 00492 if (qstrncmp(what, "STORE", what.size()) == 0) // deprecated store 00493 { 00494 seenUid.clear(); 00495 parseFetch (number, result); 00496 } 00497 break; 00498 00499 case 'R': 00500 if (qstrncmp(what, "RECENT", what.size()) == 0) 00501 { 00502 parseRecent (number, result); 00503 } 00504 break; 00505 default: 00506 break; 00507 } 00508 } 00509 } 00510 break; 00511 } //switch 00512 } //func 00513 00514 00515 void 00516 imapParser::parseResult (QByteArray & result, parseString & rest, 00517 const QString & command) 00518 { 00519 if (command == "SELECT") 00520 selectInfo.setReadWrite(true); 00521 00522 if (rest[0] == '[') 00523 { 00524 rest.pos++; 00525 QByteArray option = parseOneWord(rest, true); 00526 00527 switch (option[0]) 00528 { 00529 case 'A': // ALERT 00530 if (option == "ALERT") 00531 { 00532 rest.pos = rest.data.indexOf(']', rest.pos) + 1; 00533 // The alert text is after [ALERT]. 00534 // Is this correct or do we need to care about litterals? 00535 selectInfo.setAlert( rest.cstr() ); 00536 } 00537 break; 00538 00539 case 'N': // NEWNAME 00540 if (option == "NEWNAME") 00541 { 00542 } 00543 break; 00544 00545 case 'P': //PARSE or PERMANENTFLAGS 00546 if (option == "PARSE") 00547 { 00548 } 00549 else if (option == "PERMANENTFLAGS") 00550 { 00551 uint end = rest.data.indexOf(']', rest.pos); 00552 QByteArray flags(rest.data.data() + rest.pos, end - rest.pos); 00553 selectInfo.setPermanentFlags (flags); 00554 rest.pos = end; 00555 } 00556 break; 00557 00558 case 'R': //READ-ONLY or READ-WRITE 00559 if (option == "READ-ONLY") 00560 { 00561 selectInfo.setReadWrite (false); 00562 } 00563 else if (option == "READ-WRITE") 00564 { 00565 selectInfo.setReadWrite (true); 00566 } 00567 break; 00568 00569 case 'T': //TRYCREATE 00570 if (option == "TRYCREATE") 00571 { 00572 } 00573 break; 00574 00575 case 'U': //UIDVALIDITY or UNSEEN 00576 if (option == "UIDVALIDITY") 00577 { 00578 ulong value; 00579 if (parseOneNumber (rest, value)) 00580 selectInfo.setUidValidity (value); 00581 } 00582 else if (option == "UNSEEN") 00583 { 00584 ulong value; 00585 if (parseOneNumber (rest, value)) 00586 selectInfo.setUnseen (value); 00587 } 00588 else if (option == "UIDNEXT") 00589 { 00590 ulong value; 00591 if (parseOneNumber (rest, value)) 00592 selectInfo.setUidNext (value); 00593 } 00594 else 00595 break; 00596 00597 } 00598 if (rest[0] == ']') 00599 rest.pos++; //tie off ] 00600 skipWS (rest); 00601 } 00602 00603 if (command.isEmpty()) 00604 { 00605 // This happens when parsing an intermediate result line (those that start with '*'). 00606 // No state change involved, so we can stop here. 00607 return; 00608 } 00609 00610 switch (command[0].toLatin1 ()) 00611 { 00612 case 'A': 00613 if (command == "AUTHENTICATE") 00614 if (qstrncmp(result, "OK", result.size()) == 0) 00615 currentState = ISTATE_LOGIN; 00616 break; 00617 00618 case 'L': 00619 if (command == "LOGIN") 00620 if (qstrncmp(result, "OK", result.size()) == 0) 00621 currentState = ISTATE_LOGIN; 00622 break; 00623 00624 case 'E': 00625 if (command == "EXAMINE") 00626 { 00627 if (qstrncmp(result, "OK", result.size()) == 0) 00628 currentState = ISTATE_SELECT; 00629 else 00630 { 00631 if (currentState == ISTATE_SELECT) 00632 currentState = ISTATE_LOGIN; 00633 currentBox.clear(); 00634 } 00635 kDebug(7116) <<"imapParser::parseResult - current box is now" << currentBox; 00636 } 00637 break; 00638 00639 case 'S': 00640 if (command == "SELECT") 00641 { 00642 if (qstrncmp(result, "OK", result.size()) == 0) 00643 currentState = ISTATE_SELECT; 00644 else 00645 { 00646 if (currentState == ISTATE_SELECT) 00647 currentState = ISTATE_LOGIN; 00648 currentBox.clear(); 00649 } 00650 kDebug(7116) <<"imapParser::parseResult - current box is now" << currentBox; 00651 } 00652 break; 00653 00654 default: 00655 break; 00656 } 00657 00658 } 00659 00660 void imapParser::parseCapability (parseString & result) 00661 { 00662 QByteArray data = result.cstr(); 00663 kAsciiToLower( data.data() ); 00664 imapCapabilities = QString::fromLatin1(data).split ( ' ', QString::SkipEmptyParts ); 00665 } 00666 00667 void imapParser::parseFlags (parseString & result) 00668 { 00669 selectInfo.setFlags(result.cstr()); 00670 } 00671 00672 void imapParser::parseList (parseString & result) 00673 { 00674 imapList this_one; 00675 00676 if (result[0] != '(') 00677 return; //not proper format for us 00678 00679 result.pos++; // tie off ( 00680 00681 this_one.parseAttributes( result ); 00682 00683 result.pos++; // tie off ) 00684 skipWS (result); 00685 00686 this_one.setHierarchyDelimiter(parseLiteral(result)); 00687 this_one.setName(QString::fromUtf8(KIMAP::decodeImapFolderName( parseLiteral(result)))); // decode modified UTF7 00688 00689 listResponses.append (this_one); 00690 } 00691 00692 void imapParser::parseLsub (parseString & result) 00693 { 00694 imapList this_one (result.cstr(), *this); 00695 listResponses.append (this_one); 00696 } 00697 00698 void imapParser::parseListRights (parseString & result) 00699 { 00700 parseOneWord (result); // skip mailbox name 00701 parseOneWord (result); // skip user id 00702 while ( true ) { 00703 const QByteArray word = parseOneWord (result); 00704 if ( word.isEmpty() ) 00705 break; 00706 lastResults.append (word); 00707 } 00708 } 00709 00710 void imapParser::parseAcl (parseString & result) 00711 { 00712 parseOneWord (result); // skip mailbox name 00713 // The result is user1 perm1 user2 perm2 etc. The caller will sort it out. 00714 while ( !result.isEmpty() ) { 00715 const QByteArray word = parseLiteral(result); 00716 if ( word.isEmpty() ) 00717 break; 00718 lastResults.append (word); 00719 } 00720 } 00721 00722 void imapParser::parseAnnotation (parseString & result) 00723 { 00724 parseOneWord (result); // skip mailbox name 00725 skipWS (result); 00726 parseOneWord (result); // skip entry name (we know it since we don't allow wildcards in it) 00727 skipWS (result); 00728 if (result.isEmpty() || result[0] != '(') 00729 return; 00730 result.pos++; 00731 skipWS (result); 00732 // The result is name1 value1 name2 value2 etc. The caller will sort it out. 00733 while ( !result.isEmpty() && result[0] != ')' ) { 00734 const QByteArray word = parseLiteral (result); 00735 if ( word.isEmpty() ) 00736 break; 00737 lastResults.append (word); 00738 } 00739 } 00740 00741 00742 void imapParser::parseQuota (parseString & result) 00743 { 00744 // quota_response ::= "QUOTA" SP astring SP quota_list 00745 // quota_list ::= "(" #quota_resource ")" 00746 // quota_resource ::= atom SP number SP number 00747 QByteArray root = parseOneWord( result ); 00748 if ( root.isEmpty() ) { 00749 lastResults.append( "" ); 00750 } else { 00751 lastResults.append( root ); 00752 } 00753 if (result.isEmpty() || result[0] != '(') 00754 return; 00755 result.pos++; 00756 skipWS (result); 00757 QStringList triplet; 00758 while ( !result.isEmpty() && result[0] != ')' ) { 00759 const QByteArray word = parseLiteral(result); 00760 if ( word.isEmpty() ) 00761 break; 00762 triplet.append(word); 00763 } 00764 lastResults.append( triplet.join(" ") ); 00765 } 00766 00767 void imapParser::parseQuotaRoot (parseString & result) 00768 { 00769 // quotaroot_response 00770 // ::= "QUOTAROOT" SP astring *(SP astring) 00771 parseOneWord (result); // skip mailbox name 00772 skipWS (result); 00773 if ( result.isEmpty() ) 00774 return; 00775 QStringList roots; 00776 while ( !result.isEmpty() ) { 00777 const QByteArray word = parseLiteral (result); 00778 if ( word.isEmpty() ) 00779 break; 00780 roots.append (word); 00781 } 00782 lastResults.append( roots.isEmpty() ? "" : roots.join( " " ) ); 00783 } 00784 00785 void imapParser::parseCustom (parseString & result) 00786 { 00787 QByteArray word = parseLiteral (result, false, false); 00788 lastResults.append( word ); 00789 } 00790 00791 void imapParser::parseOtherUser (parseString & result) 00792 { 00793 lastResults.append( parseOneWord ( result ) ); 00794 } 00795 00796 void imapParser::parseDelegate (parseString & result) 00797 { 00798 const QString email = parseOneWord ( result ); 00799 00800 QStringList rights; 00801 while ( !result.isEmpty() ) { 00802 QByteArray word = parseLiteral ( result, false, false ); 00803 rights.append( word ); 00804 } 00805 00806 lastResults.append( email + ':' + rights.join( "," ) ); 00807 } 00808 00809 void imapParser::parseOutOfOffice (parseString & result) 00810 { 00811 const QString state = parseOneWord (result); 00812 parseOneWord (result); // skip encoding 00813 00814 QByteArray msg = parseLiteral (result, false, false); 00815 00816 lastResults.append( state + '^' + QString::fromUtf8( msg ) ); 00817 } 00818 00819 void imapParser::parseMyRights (parseString & result) 00820 { 00821 parseOneWord (result); // skip mailbox name 00822 Q_ASSERT( lastResults.isEmpty() ); // we can only be called once 00823 lastResults.append (parseOneWord (result) ); 00824 } 00825 00826 void imapParser::parseSearch (parseString & result) 00827 { 00828 ulong value; 00829 00830 while (parseOneNumber (result, value)) 00831 { 00832 lastResults.append (QString::number(value)); 00833 } 00834 } 00835 00836 void imapParser::parseStatus (parseString & inWords) 00837 { 00838 lastStatus = imapInfo (); 00839 00840 parseLiteral(inWords); // swallow the box 00841 if (inWords[0] != '(') 00842 return; 00843 00844 inWords.pos++; 00845 skipWS (inWords); 00846 00847 while (!inWords.isEmpty() && inWords[0] != ')') 00848 { 00849 ulong value; 00850 00851 QByteArray label = parseOneWord(inWords); 00852 if (parseOneNumber (inWords, value)) 00853 { 00854 if (label == "MESSAGES") 00855 lastStatus.setCount (value); 00856 else if (label == "RECENT") 00857 lastStatus.setRecent (value); 00858 else if (label == "UIDVALIDITY") 00859 lastStatus.setUidValidity (value); 00860 else if (label == "UNSEEN") 00861 lastStatus.setUnseen (value); 00862 else if (label == "UIDNEXT") 00863 lastStatus.setUidNext (value); 00864 } 00865 } 00866 00867 if (inWords[0] == ')') 00868 inWords.pos++; 00869 skipWS (inWords); 00870 } 00871 00872 void imapParser::parseExists (ulong value, parseString & result) 00873 { 00874 selectInfo.setCount (value); 00875 result.pos = result.data.size(); 00876 } 00877 00878 void imapParser::parseExpunge (ulong value, parseString & result) 00879 { 00880 Q_UNUSED(value); 00881 Q_UNUSED(result); 00882 } 00883 00884 void imapParser::parseAddressList (parseString & inWords, QList<mailAddress *>& list) 00885 { 00886 if ( inWords.isEmpty() ) 00887 return; 00888 if (inWords[0] != '(') 00889 { 00890 parseOneWord (inWords); // parse NIL 00891 } 00892 else 00893 { 00894 inWords.pos++; 00895 skipWS (inWords); 00896 00897 while (!inWords.isEmpty () && inWords[0] != ')') 00898 { 00899 if (inWords[0] == '(') { 00900 mailAddress *addr = new mailAddress; 00901 parseAddress(inWords, *addr); 00902 list.append(addr); 00903 } else { 00904 break; 00905 } 00906 } 00907 00908 if (!inWords.isEmpty() && inWords[0] == ')') 00909 inWords.pos++; 00910 skipWS (inWords); 00911 } 00912 } 00913 00914 const mailAddress& imapParser::parseAddress (parseString & inWords, mailAddress& retVal) 00915 { 00916 inWords.pos++; 00917 skipWS (inWords); 00918 00919 retVal.setFullName(parseLiteral(inWords)); 00920 retVal.setCommentRaw(parseLiteral(inWords)); 00921 retVal.setUser(parseLiteral(inWords)); 00922 retVal.setHost(parseLiteral(inWords)); 00923 00924 if (!inWords.isEmpty() && inWords[0] == ')') 00925 inWords.pos++; 00926 skipWS (inWords); 00927 00928 return retVal; 00929 } 00930 00931 mailHeader * imapParser::parseEnvelope (parseString & inWords) 00932 { 00933 mailHeader *envelope = 0; 00934 00935 if (inWords[0] != '(') 00936 return envelope; 00937 inWords.pos++; 00938 skipWS (inWords); 00939 00940 envelope = new mailHeader; 00941 00942 //date 00943 envelope->setDate(parseLiteral(inWords)); 00944 00945 //subject 00946 envelope->setSubject(parseLiteral(inWords)); 00947 00948 QList<mailAddress *> list; 00949 00950 //from 00951 parseAddressList(inWords, list); 00952 if (!list.isEmpty()) { 00953 envelope->setFrom(*list.last()); 00954 list.clear(); 00955 } 00956 00957 //sender 00958 parseAddressList(inWords, list); 00959 if (!list.isEmpty()) { 00960 envelope->setSender(*list.last()); 00961 list.clear(); 00962 } 00963 00964 //reply-to 00965 parseAddressList(inWords, list); 00966 if (!list.isEmpty()) { 00967 envelope->setReplyTo(*list.last()); 00968 list.clear(); 00969 } 00970 00971 //to 00972 parseAddressList (inWords, envelope->to()); 00973 00974 //cc 00975 parseAddressList (inWords, envelope->cc()); 00976 00977 //bcc 00978 parseAddressList (inWords, envelope->bcc()); 00979 00980 //in-reply-to 00981 envelope->setInReplyTo(parseLiteral(inWords)); 00982 00983 //message-id 00984 envelope->setMessageId(parseLiteral(inWords)); 00985 00986 // see if we have more to come 00987 while (!inWords.isEmpty () && inWords[0] != ')') 00988 { 00989 //eat the extensions to this part 00990 if (inWords[0] == '(') 00991 parseSentence (inWords); 00992 else 00993 parseLiteral (inWords); 00994 } 00995 00996 if (!inWords.isEmpty() && inWords[0] == ')') 00997 inWords.pos++; 00998 skipWS (inWords); 00999 01000 return envelope; 01001 } 01002 01003 // parse parameter pairs into a dictionary 01004 // caller must clean up the dictionary items 01005 QHash < QByteArray, QString > imapParser::parseDisposition (parseString & inWords) 01006 { 01007 QByteArray disposition; 01008 QHash < QByteArray, QString > retVal; 01009 01010 if (inWords[0] != '(') 01011 { 01012 //disposition only 01013 disposition = parseOneWord (inWords); 01014 } 01015 else 01016 { 01017 inWords.pos++; 01018 skipWS (inWords); 01019 01020 //disposition 01021 disposition = parseOneWord (inWords); 01022 01023 retVal = parseParameters (inWords); 01024 if (inWords[0] != ')') 01025 return retVal; 01026 inWords.pos++; 01027 skipWS (inWords); 01028 } 01029 01030 if (!disposition.isEmpty ()) 01031 { 01032 retVal.insert ("content-disposition", QString(disposition)); 01033 } 01034 01035 return retVal; 01036 } 01037 01038 // parse parameter pairs into a dictionary 01039 // caller must clean up the dictionary items 01040 QHash < QByteArray, QString > imapParser::parseParameters (parseString & inWords) 01041 { 01042 QHash < QByteArray, QString > retVal; 01043 01044 if (inWords[0] != '(') 01045 { 01046 //better be NIL 01047 parseOneWord (inWords); 01048 } 01049 else 01050 { 01051 inWords.pos++; 01052 skipWS (inWords); 01053 01054 while (!inWords.isEmpty () && inWords[0] != ')') 01055 { 01056 const QByteArray l1 = parseLiteral(inWords); 01057 const QByteArray l2 = parseLiteral(inWords); 01058 retVal.insert (l1.toLower(), QString(l2)); 01059 } 01060 01061 if (inWords[0] != ')') 01062 return retVal; 01063 inWords.pos++; 01064 skipWS (inWords); 01065 } 01066 01067 return retVal; 01068 } 01069 01070 mimeHeader * imapParser::parseSimplePart (parseString & inWords, 01071 QString & inSection, mimeHeader * localPart) 01072 { 01073 QByteArray subtype; 01074 QByteArray typeStr; 01075 QHash < QByteArray, QString > parameters; 01076 ulong size; 01077 01078 if (inWords[0] != '(') 01079 return 0; 01080 01081 if (!localPart) 01082 localPart = new mimeHeader; 01083 01084 localPart->setPartSpecifier (inSection); 01085 01086 inWords.pos++; 01087 skipWS (inWords); 01088 01089 //body type 01090 typeStr = parseLiteral(inWords); 01091 01092 //body subtype 01093 subtype = parseLiteral(inWords); 01094 01095 localPart->setType (typeStr + '/' + subtype); 01096 01097 //body parameter parenthesized list 01098 parameters = parseParameters (inWords); 01099 { 01100 QHashIterator < QByteArray, QString > it (parameters); 01101 01102 while (it.hasNext ()) 01103 { 01104 it.next(); 01105 localPart->setTypeParm (it.key (), it.value ()); 01106 } 01107 parameters.clear (); 01108 } 01109 01110 //body id 01111 localPart->setID (parseLiteral(inWords)); 01112 01113 //body description 01114 localPart->setDescription (parseLiteral(inWords)); 01115 01116 //body encoding 01117 localPart->setEncoding (parseLiteral(inWords)); 01118 01119 //body size 01120 if (parseOneNumber (inWords, size)) 01121 localPart->setLength (size); 01122 01123 // type specific extensions 01124 if (localPart->getType().toUpper() == "MESSAGE/RFC822") 01125 { 01126 //envelope structure 01127 mailHeader *envelope = parseEnvelope (inWords); 01128 01129 //body structure 01130 parseBodyStructure (inWords, inSection, envelope); 01131 01132 localPart->setNestedMessage (envelope); 01133 01134 //text lines 01135 ulong lines; 01136 parseOneNumber (inWords, lines); 01137 } 01138 else 01139 { 01140 if (typeStr == "TEXT") 01141 { 01142 //text lines 01143 ulong lines; 01144 parseOneNumber (inWords, lines); 01145 } 01146 01147 // md5 01148 parseLiteral(inWords); 01149 01150 // body disposition 01151 parameters = parseDisposition (inWords); 01152 { 01153 QString disposition = parameters["content-disposition"]; 01154 01155 localPart->setDisposition (disposition.toAscii ()); 01156 QHashIterator < QByteArray, QString > it (parameters); 01157 while (it.hasNext ()) 01158 { 01159 it.next(); 01160 localPart->setDispositionParm (it.key (), it.value ()); 01161 } 01162 parameters.clear (); 01163 } 01164 01165 // body language 01166 parseSentence (inWords); 01167 } 01168 01169 // see if we have more to come 01170 while (!inWords.isEmpty () && inWords[0] != ')') 01171 { 01172 //eat the extensions to this part 01173 if (inWords[0] == '(') 01174 parseSentence (inWords); 01175 else 01176 parseLiteral(inWords); 01177 } 01178 01179 if (inWords[0] == ')') 01180 inWords.pos++; 01181 skipWS (inWords); 01182 01183 return localPart; 01184 } 01185 01186 mimeHeader * imapParser::parseBodyStructure (parseString & inWords, 01187 QString & inSection, mimeHeader * localPart) 01188 { 01189 bool init = false; 01190 if (inSection.isEmpty()) 01191 { 01192 // first run 01193 init = true; 01194 // assume one part 01195 inSection = '1'; 01196 } 01197 int section = 0; 01198 01199 if (inWords[0] != '(') 01200 { 01201 // skip "" 01202 parseOneWord (inWords); 01203 return 0; 01204 } 01205 inWords.pos++; 01206 skipWS (inWords); 01207 01208 if (inWords[0] == '(') 01209 { 01210 QByteArray subtype; 01211 QHash< QByteArray, QString > parameters; 01212 QString outSection; 01213 01214 if (!localPart) 01215 localPart = new mimeHeader; 01216 else 01217 { 01218 // might be filled from an earlier run 01219 localPart->clearNestedParts (); 01220 localPart->clearTypeParameters (); 01221 localPart->clearDispositionParameters (); 01222 // an envelope was passed in so this is the multipart header 01223 outSection = inSection + ".HEADER"; 01224 } 01225 if (inWords[0] == '(' && init) 01226 inSection = '0'; 01227 01228 // set the section 01229 if ( !outSection.isEmpty() ) { 01230 localPart->setPartSpecifier(outSection); 01231 } else { 01232 localPart->setPartSpecifier(inSection); 01233 } 01234 01235 // is multipart (otherwise it is a simplepart and handled later) 01236 while (inWords[0] == '(') 01237 { 01238 outSection = QString::number(++section); 01239 if (!init) 01240 outSection = inSection + '.' + outSection; 01241 mimeHeader *subpart = parseBodyStructure (inWords, outSection, 0); 01242 localPart->addNestedPart (subpart); 01243 } 01244 01245 // fetch subtype 01246 subtype = parseOneWord (inWords); 01247 01248 localPart->setType ("MULTIPART/" + subtype); 01249 01250 // fetch parameters 01251 parameters = parseParameters (inWords); 01252 { 01253 QHashIterator < QByteArray, QString > it (parameters); 01254 01255 while (it.hasNext ()) 01256 { 01257 it.next(); 01258 localPart->setTypeParm (it.key (), it.value ()); 01259 } 01260 parameters.clear (); 01261 } 01262 01263 // body disposition 01264 parameters = parseDisposition (inWords); 01265 { 01266 QString disposition = parameters["content-disposition"]; 01267 01268 localPart->setDisposition (disposition.toAscii ()); 01269 QHashIterator < QByteArray, QString > it (parameters); 01270 while (it.hasNext ()) 01271 { 01272 it.next(); 01273 localPart->setDispositionParm (it.key (), it.value ()); 01274 } 01275 parameters.clear (); 01276 } 01277 01278 // body language 01279 parseSentence (inWords); 01280 01281 } 01282 else 01283 { 01284 // is simple part 01285 inWords.pos--; 01286 inWords.data[inWords.pos] = '('; //fake a sentence 01287 if ( localPart ) 01288 inSection = inSection + ".1"; 01289 localPart = parseSimplePart (inWords, inSection, localPart); 01290 inWords.pos--; 01291 inWords.data[inWords.pos] = ')'; //remove fake 01292 } 01293 01294 // see if we have more to come 01295 while (!inWords.isEmpty () && inWords[0] != ')') 01296 { 01297 //eat the extensions to this part 01298 if (inWords[0] == '(') 01299 parseSentence (inWords); 01300 else 01301 parseLiteral(inWords); 01302 } 01303 01304 if (inWords[0] == ')') 01305 inWords.pos++; 01306 skipWS (inWords); 01307 01308 return localPart; 01309 } 01310 01311 void imapParser::parseBody (parseString & inWords) 01312 { 01313 // see if we got a part specifier 01314 if (inWords[0] == '[') 01315 { 01316 QByteArray specifier; 01317 QByteArray label; 01318 inWords.pos++; 01319 01320 specifier = parseOneWord (inWords, true); 01321 01322 if (inWords[0] == '(') 01323 { 01324 inWords.pos++; 01325 01326 while (!inWords.isEmpty () && inWords[0] != ')') 01327 { 01328 label = parseOneWord (inWords); 01329 } 01330 01331 if (inWords[0] == ')') 01332 inWords.pos++; 01333 } 01334 if (inWords[0] == ']') 01335 inWords.pos++; 01336 skipWS (inWords); 01337 01338 // parse the header 01339 if (qstrncmp(specifier, "0", specifier.size()) == 0) 01340 { 01341 mailHeader *envelope = 0; 01342 if (lastHandled) 01343 envelope = lastHandled->getHeader (); 01344 01345 if (!envelope || seenUid.isEmpty ()) 01346 { 01347 kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii (); 01348 // don't know where to put it, throw it away 01349 parseLiteral(inWords, true); 01350 } 01351 else 01352 { 01353 kDebug(7116) <<"imapParser::parseBody - reading" << envelope << seenUid.toAscii (); 01354 // fill it up with data 01355 QString theHeader = parseLiteral(inWords, true); 01356 mimeIOQString myIO; 01357 01358 myIO.setString (theHeader); 01359 envelope->parseHeader (myIO); 01360 01361 } 01362 } 01363 else if (qstrncmp(specifier, "HEADER.FIELDS", specifier.size()) == 0) 01364 { 01365 // BODY[HEADER.FIELDS (References)] {n} 01366 //kDebug(7116) <<"imapParser::parseBody - HEADER.FIELDS:" 01367 // << QCString(label.data(), label.size()+1); 01368 if (qstrncmp(label, "REFERENCES", label.size()) == 0) 01369 { 01370 mailHeader *envelope = 0; 01371 if (lastHandled) 01372 envelope = lastHandled->getHeader (); 01373 01374 if (!envelope || seenUid.isEmpty ()) 01375 { 01376 kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii (); 01377 // don't know where to put it, throw it away 01378 parseLiteral (inWords, true); 01379 } 01380 else 01381 { 01382 QByteArray references = parseLiteral(inWords, true); 01383 int start = references.indexOf ('<'); 01384 int end = references.lastIndexOf ('>'); 01385 if (start < end) 01386 references = references.mid (start, end - start + 1); 01387 envelope->setReferences(references.simplified()); 01388 } 01389 } 01390 else 01391 { // not a header we care about throw it away 01392 parseLiteral(inWords, true); 01393 } 01394 } 01395 else 01396 { 01397 if (specifier.contains(".MIME") ) 01398 { 01399 mailHeader *envelope = new mailHeader; 01400 QString theHeader = parseLiteral(inWords, false); 01401 mimeIOQString myIO; 01402 myIO.setString (theHeader); 01403 envelope->parseHeader (myIO); 01404 if (lastHandled) 01405 lastHandled->setHeader (envelope); 01406 return; 01407 } 01408 // throw it away 01409 kDebug(7116) <<"imapParser::parseBody - discarding" << seenUid.toAscii (); 01410 parseLiteral(inWords, true); 01411 } 01412 01413 } 01414 else // no part specifier 01415 { 01416 mailHeader *envelope = 0; 01417 if (lastHandled) 01418 envelope = lastHandled->getHeader (); 01419 01420 if (!envelope || seenUid.isEmpty ()) 01421 { 01422 kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii (); 01423 // don't know where to put it, throw it away 01424 parseSentence (inWords); 01425 } 01426 else 01427 { 01428 kDebug(7116) <<"imapParser::parseBody - reading" << envelope << seenUid.toAscii (); 01429 // fill it up with data 01430 QString section; 01431 mimeHeader *body = parseBodyStructure (inWords, section, envelope); 01432 if (body != envelope) 01433 delete body; 01434 } 01435 } 01436 } 01437 01438 void imapParser::parseFetch (ulong /* value */, parseString & inWords) 01439 { 01440 if (inWords[0] != '(') 01441 return; 01442 inWords.pos++; 01443 skipWS (inWords); 01444 01445 delete lastHandled; 01446 lastHandled = 0; 01447 01448 while (!inWords.isEmpty () && inWords[0] != ')') 01449 { 01450 if (inWords[0] == '(') 01451 parseSentence (inWords); 01452 else 01453 { 01454 const QByteArray word = parseLiteral(inWords, false, true); 01455 01456 switch (word[0]) 01457 { 01458 case 'E': 01459 if (word == "ENVELOPE") 01460 { 01461 mailHeader *envelope = 0; 01462 01463 if (lastHandled) 01464 envelope = lastHandled->getHeader (); 01465 else 01466 lastHandled = new imapCache(); 01467 01468 if (envelope && !envelope->getMessageId ().isEmpty ()) 01469 { 01470 // we have seen this one already 01471 // or don't know where to put it 01472 parseSentence (inWords); 01473 } 01474 else 01475 { 01476 envelope = parseEnvelope (inWords); 01477 if (envelope) 01478 { 01479 envelope->setPartSpecifier (seenUid + ".0"); 01480 lastHandled->setHeader (envelope); 01481 lastHandled->setUid (seenUid.toULong ()); 01482 } 01483 } 01484 } 01485 break; 01486 01487 case 'B': 01488 if (word == "BODY") 01489 { 01490 parseBody (inWords); 01491 } 01492 else if (word == "BODY[]" ) 01493 { 01494 // Do the same as with "RFC822" 01495 parseLiteral(inWords, true); 01496 } 01497 else if (word == "BODYSTRUCTURE") 01498 { 01499 mailHeader *envelope = 0; 01500 01501 if (lastHandled) 01502 envelope = lastHandled->getHeader (); 01503 01504 // fill it up with data 01505 QString section; 01506 mimeHeader *body = 01507 parseBodyStructure (inWords, section, envelope); 01508 QByteArray data; 01509 QDataStream stream( &data, QIODevice::WriteOnly ); 01510 if ( body ) 01511 body->serialize(stream); 01512 parseRelay(data); 01513 01514 delete body; 01515 } 01516 break; 01517 01518 case 'U': 01519 if (word == "UID") 01520 { 01521 seenUid = parseOneWord(inWords); 01522 mailHeader *envelope = 0; 01523 if (lastHandled) 01524 envelope = lastHandled->getHeader (); 01525 else 01526 lastHandled = new imapCache(); 01527 01528 if (seenUid.isEmpty ()) 01529 { 01530 // unknown what to do 01531 kDebug(7116) <<"imapParser::parseFetch - UID empty"; 01532 } 01533 else 01534 { 01535 lastHandled->setUid (seenUid.toULong ()); 01536 } 01537 if (envelope) 01538 envelope->setPartSpecifier (seenUid); 01539 } 01540 break; 01541 01542 case 'R': 01543 if (word == "RFC822.SIZE") 01544 { 01545 ulong size; 01546 parseOneNumber (inWords, size); 01547 01548 if (!lastHandled) lastHandled = new imapCache(); 01549 lastHandled->setSize (size); 01550 } 01551 else if (word.startsWith("RFC822")) //krazy:exclude=strings 01552 { 01553 // might be RFC822 RFC822.TEXT RFC822.HEADER 01554 parseLiteral(inWords, true); 01555 } 01556 break; 01557 01558 case 'I': 01559 if (word == "INTERNALDATE") 01560 { 01561 const QByteArray date = parseOneWord(inWords); 01562 if (!lastHandled) lastHandled = new imapCache(); 01563 lastHandled->setDate(date); 01564 } 01565 break; 01566 01567 case 'F': 01568 if (word == "FLAGS") 01569 { 01570 //kDebug(7116) <<"GOT FLAGS" << inWords.cstr(); 01571 if (!lastHandled) lastHandled = new imapCache(); 01572 lastHandled->setFlags (imapInfo::_flags (inWords.cstr())); 01573 } 01574 break; 01575 01576 default: 01577 parseLiteral(inWords); 01578 break; 01579 } 01580 } 01581 } 01582 01583 // see if we have more to come 01584 while (!inWords.isEmpty () && inWords[0] != ')') 01585 { 01586 //eat the extensions to this part 01587 if (inWords[0] == '(') 01588 parseSentence (inWords); 01589 else 01590 parseLiteral(inWords); 01591 } 01592 01593 if (inWords.isEmpty() || inWords[0] != ')') 01594 return; 01595 inWords.pos++; 01596 skipWS (inWords); 01597 } 01598 01599 01600 // default parser 01601 void imapParser::parseSentence (parseString & inWords) 01602 { 01603 bool first = true; 01604 int stack = 0; 01605 01606 //find the first nesting parentheses 01607 01608 while (!inWords.isEmpty () && (stack != 0 || first)) 01609 { 01610 first = false; 01611 skipWS (inWords); 01612 01613 unsigned char ch = inWords[0]; 01614 switch (ch) 01615 { 01616 case '(': 01617 inWords.pos++; 01618 ++stack; 01619 break; 01620 case ')': 01621 inWords.pos++; 01622 --stack; 01623 break; 01624 case '[': 01625 inWords.pos++; 01626 ++stack; 01627 break; 01628 case ']': 01629 inWords.pos++; 01630 --stack; 01631 break; 01632 default: 01633 parseLiteral(inWords); 01634 skipWS (inWords); 01635 break; 01636 } 01637 } 01638 skipWS (inWords); 01639 } 01640 01641 void imapParser::parseRecent (ulong value, parseString & result) 01642 { 01643 selectInfo.setRecent (value); 01644 result.pos = result.data.size(); 01645 } 01646 01647 void imapParser::parseNamespace (parseString & result) 01648 { 01649 if ( result[0] != '(' ) 01650 return; 01651 01652 QString delimEmpty; 01653 if ( namespaceToDelimiter.contains( QString() ) ) 01654 delimEmpty = namespaceToDelimiter[QString()]; 01655 01656 namespaceToDelimiter.clear(); 01657 imapNamespaces.clear(); 01658 01659 // remember what section we're in (user, other users, shared) 01660 int ns = -1; 01661 bool personalAvailable = false; 01662 while ( !result.isEmpty() ) 01663 { 01664 if ( result[0] == '(' ) 01665 { 01666 result.pos++; // tie off ( 01667 if ( result[0] == '(' ) 01668 { 01669 // new namespace section 01670 result.pos++; // tie off ( 01671 ++ns; 01672 } 01673 // namespace prefix 01674 QString prefix = QString::fromLatin1( parseOneWord( result ) ); 01675 // delimiter 01676 QString delim = QString::fromLatin1( parseOneWord( result ) ); 01677 kDebug(7116) <<"imapParser::parseNamespace ns='" << prefix <<"',delim='" << delim <<"'"; 01678 if ( ns == 0 ) 01679 { 01680 // at least one personal ns 01681 personalAvailable = true; 01682 } 01683 QString nsentry = QString::number( ns ) + '=' + prefix + '=' + delim; 01684 imapNamespaces.append( nsentry ); 01685 if ( prefix.right( 1 ) == delim ) { 01686 // strip delimiter to get a correct entry for comparisons 01687 prefix.resize( prefix.length() ); 01688 } 01689 namespaceToDelimiter[prefix] = delim; 01690 01691 result.pos++; // tie off ) 01692 skipWS( result ); 01693 } else if ( result[0] == ')' ) 01694 { 01695 result.pos++; // tie off ) 01696 skipWS( result ); 01697 } else if ( result[0] == 'N' ) 01698 { 01699 // drop NIL 01700 ++ns; 01701 parseOneWord( result ); 01702 } else { 01703 // drop whatever it is 01704 parseOneWord( result ); 01705 } 01706 } 01707 if ( !delimEmpty.isEmpty() ) { 01708 // remember default delimiter 01709 namespaceToDelimiter[QString()] = delimEmpty; 01710 if ( !personalAvailable ) 01711 { 01712 // at least one personal ns would be nice 01713 kDebug(7116) <<"imapParser::parseNamespace - registering own personal ns"; 01714 QString nsentry = "0==" + delimEmpty; 01715 imapNamespaces.append( nsentry ); 01716 } 01717 } 01718 } 01719 01720 int imapParser::parseLoop () 01721 { 01722 parseString result; 01723 01724 if (!parseReadLine(result.data)) return -1; 01725 01726 //kDebug(7116) << result.cstr(); // includes \n 01727 01728 if (result.data.isEmpty()) 01729 return 0; 01730 if (!sentQueue.count ()) 01731 { 01732 // maybe greeting or BYE everything else SHOULD not happen, use NOOP or IDLE 01733 kDebug(7116) <<"imapParser::parseLoop - unhandledResponse:" << result.cstr(); 01734 unhandled << result.cstr(); 01735 } 01736 else 01737 { 01738 CommandPtr current = sentQueue.at (0); 01739 switch (result[0]) 01740 { 01741 case '*': 01742 result.data.resize(result.data.size() - 2); // tie off CRLF 01743 parseUntagged (result); 01744 break; 01745 case '+': 01746 continuation = result.data; 01747 break; 01748 default: 01749 { 01750 QByteArray tag = parseLiteral(result); 01751 if (current->id() == tag.data()) 01752 { 01753 result.data.resize(result.data.size() - 2); // tie off CRLF 01754 QByteArray resultCode = parseLiteral (result); //the result 01755 current->setResult (resultCode); 01756 current->setResultInfo(result.cstr()); 01757 current->setComplete (); 01758 01759 sentQueue.removeAll (current); 01760 completeQueue.append (current); 01761 if (result.length()) 01762 parseResult (resultCode, result, current->command()); 01763 } 01764 else 01765 { 01766 kDebug(7116) <<"imapParser::parseLoop - unknown tag '" << tag <<"'"; 01767 QByteArray cstr = tag + ' ' + result.cstr(); 01768 result.data = cstr; 01769 result.pos = 0; 01770 result.data.resize(cstr.length()); 01771 } 01772 } 01773 break; 01774 } 01775 } 01776 01777 return 1; 01778 } 01779 01780 void 01781 imapParser::parseRelay (const QByteArray & buffer) 01782 { 01783 Q_UNUSED(buffer); 01784 qWarning 01785 ("imapParser::parseRelay - virtual function not reimplemented - data lost"); 01786 } 01787 01788 void 01789 imapParser::parseRelay (ulong len) 01790 { 01791 Q_UNUSED(len); 01792 qWarning 01793 ("imapParser::parseRelay - virtual function not reimplemented - announcement lost"); 01794 } 01795 01796 bool imapParser::parseRead (QByteArray & buffer, long len, long relay) 01797 { 01798 Q_UNUSED(buffer); 01799 Q_UNUSED(len); 01800 Q_UNUSED(relay); 01801 qWarning 01802 ("imapParser::parseRead - virtual function not reimplemented - no data read"); 01803 return false; 01804 } 01805 01806 bool imapParser::parseReadLine (QByteArray & buffer, long relay) 01807 { 01808 Q_UNUSED(buffer); 01809 Q_UNUSED(relay); 01810 qWarning 01811 ("imapParser::parseReadLine - virtual function not reimplemented - no data read"); 01812 return false; 01813 } 01814 01815 void 01816 imapParser::parseWriteLine (const QString & str) 01817 { 01818 Q_UNUSED(str); 01819 qWarning 01820 ("imapParser::parseWriteLine - virtual function not reimplemented - no data written"); 01821 } 01822 01823 void 01824 imapParser::parseURL (const KUrl & _url, QString & _box, QString & _section, 01825 QString & _type, QString & _uid, QString & _validity, QString & _info) 01826 { 01827 QStringList parameters; 01828 01829 _box = _url.path (); 01830 kDebug(7116) <<"imapParser::parseURL" << _box; 01831 int paramStart = _box.indexOf("/;"); 01832 if ( paramStart > -1 ) 01833 { 01834 QString paramString = _box.right( _box.length() - paramStart-2 ); 01835 parameters = paramString.split (';', QString::SkipEmptyParts); //split parameters 01836 _box.truncate( paramStart ); // strip parameters 01837 } 01838 // extract parameters 01839 for (QStringList::ConstIterator it (parameters.constBegin ()); 01840 it != parameters.constEnd (); ++it) 01841 { 01842 QString temp = (*it); 01843 01844 // if we have a '/' separator we'll just nuke it 01845 int pt = temp.indexOf ('/'); 01846 if (pt > 0) 01847 temp.truncate(pt); 01848 if (temp.startsWith(QLatin1String("section="), Qt::CaseInsensitive)) 01849 _section = temp.right (temp.length () - 8); 01850 else if (temp.startsWith(QLatin1String("type="), Qt::CaseInsensitive)) 01851 _type = temp.right (temp.length () - 5); 01852 else if (temp.startsWith(QLatin1String("uid="), Qt::CaseInsensitive)) 01853 _uid = temp.right (temp.length () - 4); 01854 else if (temp.startsWith(QLatin1String("uidvalidity="), Qt::CaseInsensitive)) 01855 _validity = temp.right (temp.length () - 12); 01856 else if (temp.startsWith(QLatin1String("info="), Qt::CaseInsensitive)) 01857 _info = temp.right (temp.length () - 5); 01858 } 01859 // kDebug(7116) <<"URL: section=" << _section <<", type=" << _type <<", uid=" << _uid; 01860 // kDebug(7116) <<"URL: user()" << _url.user(); 01861 // kDebug(7116) <<"URL: path()" << _url.path(); 01862 // kDebug(7116) <<"URL: encodedPathAndQuery()" << _url.encodedPathAndQuery(); 01863 01864 if (!_box.isEmpty ()) 01865 { 01866 // strip / 01867 if (_box[0] == '/') 01868 _box = _box.right (_box.length () - 1); 01869 if (!_box.isEmpty () && _box[_box.length () - 1] == '/') 01870 _box.truncate(_box.length() - 1); 01871 } 01872 kDebug(7116) <<"URL: box=" << _box <<", section=" << _section <<", type=" 01873 << _type << ", uid=" << _uid << ", validity=" << _validity << ", info=" << _info; 01874 } 01875 01876 01877 QByteArray imapParser::parseLiteral(parseString & inWords, bool relay, bool stopAtBracket) { 01878 01879 if (!inWords.isEmpty() && inWords[0] == '{') 01880 { 01881 QByteArray retVal; 01882 int runLen = inWords.find ('}', 1); 01883 if (runLen > 0) 01884 { 01885 bool proper; 01886 long runLenSave = runLen + 1; 01887 QByteArray tmpstr(runLen, '\0'); 01888 inWords.takeMidNoResize(tmpstr, 1, runLen - 1); 01889 runLen = tmpstr.toULong (&proper); 01890 inWords.pos += runLenSave; 01891 if (proper) 01892 { 01893 //now get the literal from the server 01894 if (relay) 01895 parseRelay (runLen); 01896 QByteArray rv; 01897 parseRead (rv, runLen, relay ? runLen : 0); 01898 rv.resize(qMax(runLen, rv.size())); // what's the point? 01899 retVal = rv; 01900 inWords.clear(); 01901 parseReadLine (inWords.data); // must get more 01902 01903 // no duplicate data transfers 01904 relay = false; 01905 } 01906 else 01907 { 01908 kDebug(7116) <<"imapParser::parseLiteral - error parsing {} -" /*<< strLen*/; 01909 } 01910 } 01911 else 01912 { 01913 inWords.clear(); 01914 kDebug(7116) <<"imapParser::parseLiteral - error parsing unmatched {"; 01915 } 01916 skipWS (inWords); 01917 return retVal; 01918 } 01919 01920 return parseOneWord(inWords, stopAtBracket); 01921 } 01922 01923 // does not know about literals ( {7} literal ) 01924 QByteArray imapParser::parseOneWord (parseString & inWords, bool stopAtBracket) 01925 { 01926 uint len = inWords.length(); 01927 if (len == 0) { 01928 return QByteArray(); 01929 } 01930 01931 if (len > 0 && inWords[0] == '"') 01932 { 01933 unsigned int i = 1; 01934 bool quote = false; 01935 while (i < len && (inWords[i] != '"' || quote)) 01936 { 01937 if (inWords[i] == '\\') quote = !quote; 01938 else quote = false; 01939 i++; 01940 } 01941 if (i < len) 01942 { 01943 QByteArray retVal; 01944 retVal.resize(i); 01945 inWords.pos++; 01946 inWords.takeLeftNoResize(retVal, i - 1); 01947 len = i - 1; 01948 int offset = 0; 01949 for (unsigned int j = 0; j < len; j++) { 01950 if (retVal[j] == '\\') { 01951 offset++; 01952 j++; 01953 } 01954 retVal[j - offset] = retVal[j]; 01955 } 01956 retVal.resize( len - offset ); 01957 inWords.pos += i; 01958 skipWS (inWords); 01959 return retVal; 01960 } 01961 else 01962 { 01963 kDebug(7116) <<"imapParser::parseOneWord - error parsing unmatched \""; 01964 QByteArray retVal = inWords.cstr(); 01965 inWords.clear(); 01966 return retVal; 01967 } 01968 } 01969 else 01970 { 01971 // not quoted 01972 unsigned int i; 01973 // search for end 01974 for (i = 0; i < len; ++i) { 01975 char ch = inWords[i]; 01976 if (ch <= ' ' || ch == '(' || ch == ')' || 01977 (stopAtBracket && (ch == '[' || ch == ']'))) 01978 break; 01979 } 01980 01981 QByteArray retVal; 01982 retVal.resize(i); 01983 inWords.takeLeftNoResize(retVal, i); 01984 inWords.pos += i; 01985 01986 if (retVal == "NIL") { 01987 retVal.truncate(0); 01988 } 01989 skipWS (inWords); 01990 return retVal; 01991 } 01992 } 01993 01994 bool imapParser::parseOneNumber (parseString & inWords, ulong & num) 01995 { 01996 bool valid; 01997 num = parseOneWord(inWords, true).toULong(&valid); 01998 return valid; 01999 } 02000 02001 bool imapParser::hasCapability (const QString & cap) 02002 { 02003 QString c = cap.toLower(); 02004 // kDebug(7116) <<"imapParser::hasCapability - Looking for '" << cap <<"'"; 02005 for (QStringList::ConstIterator it = imapCapabilities.constBegin (); 02006 it != imapCapabilities.constEnd (); ++it) 02007 { 02008 // kDebug(7116) <<"imapParser::hasCapability - Examining '" << (*it) <<"'"; 02009 if ( !(kasciistricmp(c.toAscii(), (*it).toAscii())) ) 02010 { 02011 return true; 02012 } 02013 } 02014 return false; 02015 } 02016 02017 void imapParser::removeCapability (const QString & cap) 02018 { 02019 imapCapabilities.removeAll(cap.toLower()); 02020 } 02021 02022 QString imapParser::namespaceForBox( const QString & box ) 02023 { 02024 kDebug(7116) <<"imapParse::namespaceForBox" << box; 02025 QString myNamespace; 02026 if ( !box.isEmpty() ) 02027 { 02028 const QList<QString> list = namespaceToDelimiter.keys(); 02029 QString cleanPrefix; 02030 for ( QList<QString>::ConstIterator it = list.begin(); it != list.end(); ++it ) 02031 { 02032 if ( !(*it).isEmpty() && box.contains( *it ) ) 02033 return (*it); 02034 } 02035 } 02036 return myNamespace; 02037 } 02038
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Apr 30 2012 21:48:35 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Apr 30 2012 21:48:35 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.