• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.9.4 API Reference
  • KDE Home
  • Contact Us
 

kioslave/imap4

  • kioslave
  • imap4
imapparser.cpp
1 /**********************************************************************
2  *
3  * imapparser.cc - IMAP4rev1 Parser
4  * Copyright (C) 2001-2002 Michael Haeckel <haeckel@kde.org>
5  * Copyright (C) 2000 Sven Carstens <s.carstens@gmx.de>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  * Send comments and bug fixes to s.carstens@gmx.de
22  *
23  *********************************************************************/
24 
25 #include "imapparser.h"
26 #include "imapinfo.h"
27 #include "mailheader.h"
28 #include "mimeheader.h"
29 #include "mailaddress.h"
30 
31 #include <sys/types.h>
32 
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <QList>
36 
37 extern "C" {
38 #include <sasl/sasl.h>
39 }
40 
41 #include <QRegExp>
42 #include <QBuffer>
43 #include <QString>
44 #include <QStringList>
45 
46 #include <kascii.h>
47 #include <kdebug.h>
48 #include <kcodecs.h>
49 #include <kglobal.h>
50 #include <kurl.h>
51 
52 #include <kimap/rfccodecs.h>
53 using namespace KIMAP;
54 
55 static sasl_callback_t callbacks[] = {
56  { SASL_CB_ECHOPROMPT, NULL, NULL },
57  { SASL_CB_NOECHOPROMPT, NULL, NULL },
58  { SASL_CB_GETREALM, NULL, NULL },
59  { SASL_CB_USER, NULL, NULL },
60  { SASL_CB_AUTHNAME, NULL, NULL },
61  { SASL_CB_PASS, NULL, NULL },
62  { SASL_CB_CANON_USER, NULL, NULL },
63  { SASL_CB_LIST_END, NULL, NULL }
64 };
65 
66 imapParser::imapParser ()
67 {
68  currentState = ISTATE_NO;
69  commandCounter = 0;
70  lastHandled = 0;
71 }
72 
73 imapParser::~imapParser ()
74 {
75  delete lastHandled;
76  lastHandled = 0;
77 }
78 
79 CommandPtr
80 imapParser::doCommand (CommandPtr aCmd)
81 {
82  int pl = 0;
83  sendCommand (aCmd);
84  while (pl != -1 && !aCmd->isComplete ()) {
85  while ((pl = parseLoop ()) == 0)
86  ;
87  }
88 
89  return aCmd;
90 }
91 
92 CommandPtr
93 imapParser::sendCommand (CommandPtr aCmd)
94 {
95  aCmd->setId (QString::number(commandCounter++));
96  sentQueue.append (aCmd);
97 
98  continuation.resize(0);
99  const QString& command = aCmd->command();
100 
101  if (command == "SELECT" || command == "EXAMINE")
102  {
103  // we need to know which box we are selecting
104  parseString p;
105  p.fromString(aCmd->parameter());
106  currentBox = parseOneWord(p);
107  kDebug(7116) <<"imapParser::sendCommand - setting current box to" << currentBox;
108  }
109  else if (command == "CLOSE")
110  {
111  // we no longer have a box open
112  currentBox.clear();
113  }
114  else if (command.contains("SEARCH")
115  || command == "GETACL"
116  || command == "LISTRIGHTS"
117  || command == "MYRIGHTS"
118  || command == "GETANNOTATION"
119  || command == "NAMESPACE"
120  || command == "GETQUOTAROOT"
121  || command == "GETQUOTA"
122  || command == "X-GET-OTHER-USERS"
123  || command == "X-GET-DELEGATES"
124  || command == "X-GET-OUT-OF-OFFICE")
125  {
126  lastResults.clear ();
127  }
128  else if (command == "LIST"
129  || command == "LSUB")
130  {
131  listResponses.clear ();
132  }
133  parseWriteLine (aCmd->getStr ());
134  return aCmd;
135 }
136 
137 bool
138 imapParser::clientLogin (const QString & aUser, const QString & aPass,
139  QString & resultInfo)
140 {
141  CommandPtr cmd;
142  bool retVal = false;
143 
144  cmd =
145  doCommand ( CommandPtr( new
146  imapCommand ("LOGIN", "\"" + KIMAP::quoteIMAP(aUser)
147  + "\" \"" + KIMAP::quoteIMAP(aPass) + "\"")) );
148 
149  if (cmd->result () == "OK")
150  {
151  currentState = ISTATE_LOGIN;
152  retVal = true;
153  }
154  resultInfo = cmd->resultInfo();
155  completeQueue.removeAll (cmd);
156  return retVal;
157 }
158 
159 static bool sasl_interact( KIO::SlaveBase *slave, KIO::AuthInfo &ai, void *in )
160 {
161  kDebug(7116) <<"sasl_interact";
162  sasl_interact_t *interact = ( sasl_interact_t * ) in;
163 
164  //some mechanisms do not require username && pass, so it doesn't need a popup
165  //window for getting this info
166  for ( ; interact->id != SASL_CB_LIST_END; interact++ ) {
167  if ( interact->id == SASL_CB_AUTHNAME ||
168  interact->id == SASL_CB_PASS ) {
169 
170  if ( ai.username.isEmpty() || ai.password.isEmpty() ) {
171  if (!slave->openPasswordDialog(ai))
172  return false;
173  }
174  break;
175  }
176  }
177 
178  interact = ( sasl_interact_t * ) in;
179  while( interact->id != SASL_CB_LIST_END ) {
180  kDebug(7116) <<"SASL_INTERACT id:" << interact->id;
181  switch( interact->id ) {
182  case SASL_CB_USER:
183  case SASL_CB_AUTHNAME:
184  kDebug(7116) <<"SASL_CB_[USER|AUTHNAME]: '" << ai.username <<"'";
185  interact->result = strdup( ai.username.toUtf8() );
186  interact->len = strlen( (const char *) interact->result );
187  break;
188  case SASL_CB_PASS:
189  kDebug(7116) <<"SASL_CB_PASS: [hidden]";
190  interact->result = strdup( ai.password.toUtf8() );
191  interact->len = strlen( (const char *) interact->result );
192  break;
193  default:
194  interact->result = 0;
195  interact->len = 0;
196  break;
197  }
198  interact++;
199  }
200  return true;
201 }
202 
203 bool
204 imapParser::clientAuthenticate ( KIO::SlaveBase *slave, KIO::AuthInfo &ai,
205  const QString & aFQDN, const QString & aAuth, bool isSSL, QString & resultInfo)
206 {
207  bool retVal = false;
208  int result;
209  sasl_conn_t *conn = 0;
210  sasl_interact_t *client_interact = 0;
211  const char *out = 0;
212  uint outlen = 0;
213  const char *mechusing = 0;
214  QByteArray tmp, challenge;
215 
216  kDebug(7116) <<"aAuth:" << aAuth <<" FQDN:" << aFQDN <<" isSSL:" << isSSL;
217 
218  // see if server supports this authenticator
219  if (!hasCapability ("AUTH=" + aAuth))
220  return false;
221 
222 // result = sasl_client_new( isSSL ? "imaps" : "imap",
223  result = sasl_client_new( "imap", /* FIXME: with cyrus-imapd, even imaps' digest-uri
224  must be 'imap'. I don't know if it's good or bad. */
225  aFQDN.toLatin1(),
226  0, 0, callbacks, 0, &conn );
227 
228  if ( result != SASL_OK ) {
229  kDebug(7116) <<"sasl_client_new failed with:" << result;
230  resultInfo = QString::fromUtf8( sasl_errdetail( conn ) );
231  return false;
232  }
233 
234  do {
235  result = sasl_client_start(conn, aAuth.toLatin1(), &client_interact,
236  hasCapability("SASL-IR") ? &out : 0, &outlen, &mechusing);
237 
238  if ( result == SASL_INTERACT ) {
239  if ( !sasl_interact( slave, ai, client_interact ) ) {
240  sasl_dispose( &conn );
241  return false;
242  }
243  }
244  } while ( result == SASL_INTERACT );
245 
246  if ( result != SASL_CONTINUE && result != SASL_OK ) {
247  kDebug(7116) <<"sasl_client_start failed with:" << result;
248  resultInfo = QString::fromUtf8( sasl_errdetail( conn ) );
249  sasl_dispose( &conn );
250  return false;
251  }
252  CommandPtr cmd;
253 
254  tmp = QByteArray::fromRawData( out, outlen );
255  challenge = tmp.toBase64();
256  tmp.clear();
257  // then lets try it
258  QString firstCommand = aAuth;
259  if ( !challenge.isEmpty() ) {
260  firstCommand += ' ';
261  firstCommand += QString::fromLatin1( challenge.data(), challenge.size() );
262  }
263  cmd = sendCommand (CommandPtr(new imapCommand ("AUTHENTICATE", firstCommand.toLatin1())));
264 
265  int pl = 0;
266  while ( pl != -1 && !cmd->isComplete () ) {
267  //read the next line
268  while ( ( pl = parseLoop() ) == 0) {
269  ;
270  }
271 
272  if (!continuation.isEmpty())
273  {
274 // kDebug(7116) <<"S:" << QCString(continuation.data(),continuation.size()+1);
275  if ( continuation.size() > 4 ) {
276  tmp = QByteArray::fromRawData( continuation.data() + 2, continuation.size() - 4 );
277  challenge = QByteArray::fromBase64( tmp );
278 // kDebug(7116) <<"S-1:" << QCString(challenge.data(),challenge.size()+1);
279  tmp.clear();
280  }
281 
282  do {
283  result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(),
284  challenge.size(),
285  &client_interact,
286  &out, &outlen);
287 
288  if (result == SASL_INTERACT) {
289  if ( !sasl_interact( slave, ai, client_interact ) ) {
290  sasl_dispose( &conn );
291  return false;
292  }
293  }
294  } while ( result == SASL_INTERACT );
295 
296  if ( result != SASL_CONTINUE && result != SASL_OK ) {
297  kDebug(7116) <<"sasl_client_step failed with:" << result;
298  resultInfo = QString::fromUtf8( sasl_errdetail( conn ) );
299  sasl_dispose( &conn );
300  return false;
301  }
302 
303  tmp = QByteArray::fromRawData( out, outlen );
304 // kDebug(7116) <<"C-1:" << QCString(tmp.data(),tmp.size()+1);
305  challenge = tmp.toBase64();
306  tmp.clear();
307 // kDebug(7116) <<"C:" << QCString(challenge.data(),challenge.size()+1);
308  parseWriteLine (challenge);
309  continuation.resize(0);
310  }
311  }
312 
313  if (cmd->result () == "OK")
314  {
315  currentState = ISTATE_LOGIN;
316  retVal = true;
317  }
318  resultInfo = cmd->resultInfo();
319  completeQueue.removeAll (cmd);
320 
321  sasl_dispose( &conn ); //we don't use sasl_en/decode(), so it's safe to dispose the connection.
322  return retVal;
323 }
324 
325 void
326 imapParser::parseUntagged (parseString & result)
327 {
328  //kDebug(7116) <<"imapParser::parseUntagged - '" << result.cstr() <<"'";
329 
330  parseOneWord(result); // *
331  QByteArray what = parseLiteral (result); // see whats coming next
332 
333  switch (what[0])
334  {
335  //the status responses
336  case 'B': // BAD or BYE
337  if (qstrncmp(what, "BAD", what.size()) == 0)
338  {
339  parseResult (what, result);
340  }
341  else if (qstrncmp(what, "BYE", what.size()) == 0)
342  {
343  parseResult (what, result);
344  if ( sentQueue.count() ) {
345  // BYE that interrupts a command -> copy the reason for it
346  CommandPtr current = sentQueue.at (0);
347  current->setResultInfo(result.cstr());
348  }
349  currentState = ISTATE_NO;
350  }
351  break;
352 
353  case 'N': // NO
354  if (what[1] == 'O' && what.size() == 2)
355  {
356  parseResult (what, result);
357  }
358  else if (qstrncmp(what, "NAMESPACE", what.size()) == 0)
359  {
360  parseNamespace (result);
361  }
362  break;
363 
364  case 'O': // OK
365  if (what[1] == 'K' && what.size() == 2)
366  {
367  parseResult (what, result);
368  } else if (qstrncmp(what, "OTHER-USER", 10) == 0) { // X-GET-OTHER-USER
369  parseOtherUser (result);
370  } else if (qstrncmp(what, "OUT-OF-OFFICE", 13) == 0) { // X-GET-OUT-OF-OFFICE
371  parseOutOfOffice (result);
372  }
373  break;
374  case 'D':
375  if (qstrncmp(what, "DELEGATE", 8) == 0) { // X-GET-DELEGATES
376  parseDelegate (result);
377  }
378  break;
379 
380  case 'P': // PREAUTH
381  if (qstrncmp(what, "PREAUTH", what.size()) == 0)
382  {
383  parseResult (what, result);
384  currentState = ISTATE_LOGIN;
385  }
386  break;
387 
388  // parse the other responses
389  case 'C': // CAPABILITY
390  if (qstrncmp(what, "CAPABILITY", what.size()) == 0)
391  {
392  parseCapability (result);
393  }
394  break;
395 
396  case 'F': // FLAGS
397  if (qstrncmp(what, "FLAGS", what.size()) == 0)
398  {
399  parseFlags (result);
400  }
401  break;
402 
403  case 'L': // LIST or LSUB or LISTRIGHTS
404  if (qstrncmp(what, "LIST", what.size()) == 0)
405  {
406  parseList (result);
407  }
408  else if (qstrncmp(what, "LSUB", what.size()) == 0)
409  {
410  parseLsub (result);
411  }
412  else if (qstrncmp(what, "LISTRIGHTS", what.size()) == 0)
413  {
414  parseListRights (result);
415  }
416  break;
417 
418  case 'M': // MYRIGHTS
419  if (qstrncmp(what, "MYRIGHTS", what.size()) == 0)
420  {
421  parseMyRights (result);
422  }
423  break;
424  case 'S': // SEARCH or STATUS
425  if (qstrncmp(what, "SEARCH", what.size()) == 0)
426  {
427  parseSearch (result);
428  }
429  else if (qstrncmp(what, "STATUS", what.size()) == 0)
430  {
431  parseStatus (result);
432  }
433  break;
434 
435  case 'A': // ACL or ANNOTATION
436  if (qstrncmp(what, "ACL", what.size()) == 0)
437  {
438  parseAcl (result);
439  }
440  else if (qstrncmp(what, "ANNOTATION", what.size()) == 0)
441  {
442  parseAnnotation (result);
443  }
444  break;
445  case 'Q': // QUOTA or QUOTAROOT
446  if ( what.size() > 5 && qstrncmp(what, "QUOTAROOT", what.size()) == 0)
447  {
448  parseQuotaRoot( result );
449  }
450  else if (qstrncmp(what, "QUOTA", what.size()) == 0)
451  {
452  parseQuota( result );
453  }
454  break;
455  case 'X': // Custom command
456  {
457  parseCustom( result );
458  }
459  break;
460  default:
461  //better be a number
462  {
463  ulong number;
464  bool valid;
465 
466  number = what.toUInt(&valid);
467  if (valid)
468  {
469  what = parseLiteral (result);
470  switch (what[0])
471  {
472  case 'E':
473  if (qstrncmp(what, "EXISTS", what.size()) == 0)
474  {
475  parseExists (number, result);
476  }
477  else if (qstrncmp(what, "EXPUNGE", what.size()) == 0)
478  {
479  parseExpunge (number, result);
480  }
481  break;
482 
483  case 'F':
484  if (qstrncmp(what, "FETCH", what.size()) == 0)
485  {
486  seenUid.clear();
487  parseFetch (number, result);
488  }
489  break;
490 
491  case 'S':
492  if (qstrncmp(what, "STORE", what.size()) == 0) // deprecated store
493  {
494  seenUid.clear();
495  parseFetch (number, result);
496  }
497  break;
498 
499  case 'R':
500  if (qstrncmp(what, "RECENT", what.size()) == 0)
501  {
502  parseRecent (number, result);
503  }
504  break;
505  default:
506  break;
507  }
508  }
509  }
510  break;
511  } //switch
512 } //func
513 
514 
515 void
516 imapParser::parseResult (QByteArray & result, parseString & rest,
517  const QString & command)
518 {
519  if (command == "SELECT")
520  selectInfo.setReadWrite(true);
521 
522  if (rest[0] == '[')
523  {
524  rest.pos++;
525  QByteArray option = parseOneWord(rest, true);
526 
527  switch (option[0])
528  {
529  case 'A': // ALERT
530  if (option == "ALERT")
531  {
532  rest.pos = rest.data.indexOf(']', rest.pos) + 1;
533  // The alert text is after [ALERT].
534  // Is this correct or do we need to care about litterals?
535  selectInfo.setAlert( rest.cstr() );
536  }
537  break;
538 
539  case 'N': // NEWNAME
540  if (option == "NEWNAME")
541  {
542  }
543  break;
544 
545  case 'P': //PARSE or PERMANENTFLAGS
546  if (option == "PARSE")
547  {
548  }
549  else if (option == "PERMANENTFLAGS")
550  {
551  uint end = rest.data.indexOf(']', rest.pos);
552  QByteArray flags(rest.data.data() + rest.pos, end - rest.pos);
553  selectInfo.setPermanentFlags (flags);
554  rest.pos = end;
555  }
556  break;
557 
558  case 'R': //READ-ONLY or READ-WRITE
559  if (option == "READ-ONLY")
560  {
561  selectInfo.setReadWrite (false);
562  }
563  else if (option == "READ-WRITE")
564  {
565  selectInfo.setReadWrite (true);
566  }
567  break;
568 
569  case 'T': //TRYCREATE
570  if (option == "TRYCREATE")
571  {
572  }
573  break;
574 
575  case 'U': //UIDVALIDITY or UNSEEN
576  if (option == "UIDVALIDITY")
577  {
578  ulong value;
579  if (parseOneNumber (rest, value))
580  selectInfo.setUidValidity (value);
581  }
582  else if (option == "UNSEEN")
583  {
584  ulong value;
585  if (parseOneNumber (rest, value))
586  selectInfo.setUnseen (value);
587  }
588  else if (option == "UIDNEXT")
589  {
590  ulong value;
591  if (parseOneNumber (rest, value))
592  selectInfo.setUidNext (value);
593  }
594  else
595  break;
596 
597  }
598  if (rest[0] == ']')
599  rest.pos++; //tie off ]
600  skipWS (rest);
601  }
602 
603  if (command.isEmpty())
604  {
605  // This happens when parsing an intermediate result line (those that start with '*').
606  // No state change involved, so we can stop here.
607  return;
608  }
609 
610  switch (command[0].toLatin1 ())
611  {
612  case 'A':
613  if (command == "AUTHENTICATE")
614  if (qstrncmp(result, "OK", result.size()) == 0)
615  currentState = ISTATE_LOGIN;
616  break;
617 
618  case 'L':
619  if (command == "LOGIN")
620  if (qstrncmp(result, "OK", result.size()) == 0)
621  currentState = ISTATE_LOGIN;
622  break;
623 
624  case 'E':
625  if (command == "EXAMINE")
626  {
627  if (qstrncmp(result, "OK", result.size()) == 0)
628  currentState = ISTATE_SELECT;
629  else
630  {
631  if (currentState == ISTATE_SELECT)
632  currentState = ISTATE_LOGIN;
633  currentBox.clear();
634  }
635  kDebug(7116) <<"imapParser::parseResult - current box is now" << currentBox;
636  }
637  break;
638 
639  case 'S':
640  if (command == "SELECT")
641  {
642  if (qstrncmp(result, "OK", result.size()) == 0)
643  currentState = ISTATE_SELECT;
644  else
645  {
646  if (currentState == ISTATE_SELECT)
647  currentState = ISTATE_LOGIN;
648  currentBox.clear();
649  }
650  kDebug(7116) <<"imapParser::parseResult - current box is now" << currentBox;
651  }
652  break;
653 
654  default:
655  break;
656  }
657 
658 }
659 
660 void imapParser::parseCapability (parseString & result)
661 {
662  QByteArray data = result.cstr();
663  kAsciiToLower( data.data() );
664  imapCapabilities = QString::fromLatin1(data).split ( ' ', QString::SkipEmptyParts );
665 }
666 
667 void imapParser::parseFlags (parseString & result)
668 {
669  selectInfo.setFlags(result.cstr());
670 }
671 
672 void imapParser::parseList (parseString & result)
673 {
674  imapList this_one;
675 
676  if (result[0] != '(')
677  return; //not proper format for us
678 
679  result.pos++; // tie off (
680 
681  this_one.parseAttributes( result );
682 
683  result.pos++; // tie off )
684  skipWS (result);
685 
686  this_one.setHierarchyDelimiter(parseLiteral(result));
687  this_one.setName(QString::fromUtf8(KIMAP::decodeImapFolderName( parseLiteral(result)))); // decode modified UTF7
688 
689  listResponses.append (this_one);
690 }
691 
692 void imapParser::parseLsub (parseString & result)
693 {
694  imapList this_one (result.cstr(), *this);
695  listResponses.append (this_one);
696 }
697 
698 void imapParser::parseListRights (parseString & result)
699 {
700  parseOneWord (result); // skip mailbox name
701  parseOneWord (result); // skip user id
702  while ( true ) {
703  const QByteArray word = parseOneWord (result);
704  if ( word.isEmpty() )
705  break;
706  lastResults.append (word);
707  }
708 }
709 
710 void imapParser::parseAcl (parseString & result)
711 {
712  parseOneWord (result); // skip mailbox name
713  // The result is user1 perm1 user2 perm2 etc. The caller will sort it out.
714  while ( !result.isEmpty() ) {
715  const QByteArray word = parseLiteral(result);
716  if ( word.isEmpty() )
717  break;
718  lastResults.append (word);
719  }
720 }
721 
722 void imapParser::parseAnnotation (parseString & result)
723 {
724  parseOneWord (result); // skip mailbox name
725  skipWS (result);
726  parseOneWord (result); // skip entry name (we know it since we don't allow wildcards in it)
727  skipWS (result);
728  if (result.isEmpty() || result[0] != '(')
729  return;
730  result.pos++;
731  skipWS (result);
732  // The result is name1 value1 name2 value2 etc. The caller will sort it out.
733  while ( !result.isEmpty() && result[0] != ')' ) {
734  const QByteArray word = parseLiteral (result);
735  if ( word.isEmpty() )
736  break;
737  lastResults.append (word);
738  }
739 }
740 
741 
742 void imapParser::parseQuota (parseString & result)
743 {
744  // quota_response ::= "QUOTA" SP astring SP quota_list
745  // quota_list ::= "(" #quota_resource ")"
746  // quota_resource ::= atom SP number SP number
747  QByteArray root = parseOneWord( result );
748  if ( root.isEmpty() ) {
749  lastResults.append( "" );
750  } else {
751  lastResults.append( root );
752  }
753  if (result.isEmpty() || result[0] != '(')
754  return;
755  result.pos++;
756  skipWS (result);
757  QStringList triplet;
758  while ( !result.isEmpty() && result[0] != ')' ) {
759  const QByteArray word = parseLiteral(result);
760  if ( word.isEmpty() )
761  break;
762  triplet.append(word);
763  }
764  lastResults.append( triplet.join(" ") );
765 }
766 
767 void imapParser::parseQuotaRoot (parseString & result)
768 {
769  // quotaroot_response
770  // ::= "QUOTAROOT" SP astring *(SP astring)
771  parseOneWord (result); // skip mailbox name
772  skipWS (result);
773  if ( result.isEmpty() )
774  return;
775  QStringList roots;
776  while ( !result.isEmpty() ) {
777  const QByteArray word = parseLiteral (result);
778  if ( word.isEmpty() )
779  break;
780  roots.append (word);
781  }
782  lastResults.append( roots.isEmpty() ? "" : roots.join( " " ) );
783 }
784 
785 void imapParser::parseCustom (parseString & result)
786 {
787  QByteArray word = parseLiteral (result, false, false);
788  lastResults.append( word );
789 }
790 
791 void imapParser::parseOtherUser (parseString & result)
792 {
793  lastResults.append( parseOneWord ( result ) );
794 }
795 
796 void imapParser::parseDelegate (parseString & result)
797 {
798  const QString email = parseOneWord ( result );
799 
800  QStringList rights;
801  while ( !result.isEmpty() ) {
802  QByteArray word = parseLiteral ( result, false, false );
803  rights.append( word );
804  }
805 
806  lastResults.append( email + ':' + rights.join( "," ) );
807 }
808 
809 void imapParser::parseOutOfOffice (parseString & result)
810 {
811  const QString state = parseOneWord (result);
812  parseOneWord (result); // skip encoding
813 
814  QByteArray msg = parseLiteral (result, false, false);
815 
816  lastResults.append( state + '^' + QString::fromUtf8( msg ) );
817 }
818 
819 void imapParser::parseMyRights (parseString & result)
820 {
821  parseOneWord (result); // skip mailbox name
822  Q_ASSERT( lastResults.isEmpty() ); // we can only be called once
823  lastResults.append (parseOneWord (result) );
824 }
825 
826 void imapParser::parseSearch (parseString & result)
827 {
828  ulong value;
829 
830  while (parseOneNumber (result, value))
831  {
832  lastResults.append (QString::number(value));
833  }
834 }
835 
836 void imapParser::parseStatus (parseString & inWords)
837 {
838  lastStatus = imapInfo ();
839 
840  parseLiteral(inWords); // swallow the box
841  if (inWords[0] != '(')
842  return;
843 
844  inWords.pos++;
845  skipWS (inWords);
846 
847  while (!inWords.isEmpty() && inWords[0] != ')')
848  {
849  ulong value;
850 
851  QByteArray label = parseOneWord(inWords);
852  if (parseOneNumber (inWords, value))
853  {
854  if (label == "MESSAGES")
855  lastStatus.setCount (value);
856  else if (label == "RECENT")
857  lastStatus.setRecent (value);
858  else if (label == "UIDVALIDITY")
859  lastStatus.setUidValidity (value);
860  else if (label == "UNSEEN")
861  lastStatus.setUnseen (value);
862  else if (label == "UIDNEXT")
863  lastStatus.setUidNext (value);
864  }
865  }
866 
867  if (inWords[0] == ')')
868  inWords.pos++;
869  skipWS (inWords);
870 }
871 
872 void imapParser::parseExists (ulong value, parseString & result)
873 {
874  selectInfo.setCount (value);
875  result.pos = result.data.size();
876 }
877 
878 void imapParser::parseExpunge (ulong value, parseString & result)
879 {
880  Q_UNUSED(value);
881  Q_UNUSED(result);
882 }
883 
884 void imapParser::parseAddressList (parseString & inWords, QList<mailAddress *>& list)
885 {
886  if ( inWords.isEmpty() )
887  return;
888  if (inWords[0] != '(')
889  {
890  parseOneWord (inWords); // parse NIL
891  }
892  else
893  {
894  inWords.pos++;
895  skipWS (inWords);
896 
897  while (!inWords.isEmpty () && inWords[0] != ')')
898  {
899  if (inWords[0] == '(') {
900  mailAddress *addr = new mailAddress;
901  parseAddress(inWords, *addr);
902  list.append(addr);
903  } else {
904  break;
905  }
906  }
907 
908  if (!inWords.isEmpty() && inWords[0] == ')')
909  inWords.pos++;
910  skipWS (inWords);
911  }
912 }
913 
914 const mailAddress& imapParser::parseAddress (parseString & inWords, mailAddress& retVal)
915 {
916  inWords.pos++;
917  skipWS (inWords);
918 
919  retVal.setFullName(parseLiteral(inWords));
920  retVal.setCommentRaw(parseLiteral(inWords));
921  retVal.setUser(parseLiteral(inWords));
922  retVal.setHost(parseLiteral(inWords));
923 
924  if (!inWords.isEmpty() && inWords[0] == ')')
925  inWords.pos++;
926  skipWS (inWords);
927 
928  return retVal;
929 }
930 
931 mailHeader * imapParser::parseEnvelope (parseString & inWords)
932 {
933  mailHeader *envelope = 0;
934 
935  if (inWords[0] != '(')
936  return envelope;
937  inWords.pos++;
938  skipWS (inWords);
939 
940  envelope = new mailHeader;
941 
942  //date
943  envelope->setDate(parseLiteral(inWords));
944 
945  //subject
946  envelope->setSubject(parseLiteral(inWords));
947 
948  QList<mailAddress *> list;
949 
950  //from
951  parseAddressList(inWords, list);
952  if (!list.isEmpty()) {
953  envelope->setFrom(*list.last());
954  list.clear();
955  }
956 
957  //sender
958  parseAddressList(inWords, list);
959  if (!list.isEmpty()) {
960  envelope->setSender(*list.last());
961  list.clear();
962  }
963 
964  //reply-to
965  parseAddressList(inWords, list);
966  if (!list.isEmpty()) {
967  envelope->setReplyTo(*list.last());
968  list.clear();
969  }
970 
971  //to
972  parseAddressList (inWords, envelope->to());
973 
974  //cc
975  parseAddressList (inWords, envelope->cc());
976 
977  //bcc
978  parseAddressList (inWords, envelope->bcc());
979 
980  //in-reply-to
981  envelope->setInReplyTo(parseLiteral(inWords));
982 
983  //message-id
984  envelope->setMessageId(parseLiteral(inWords));
985 
986  // see if we have more to come
987  while (!inWords.isEmpty () && inWords[0] != ')')
988  {
989  //eat the extensions to this part
990  if (inWords[0] == '(')
991  parseSentence (inWords);
992  else
993  parseLiteral (inWords);
994  }
995 
996  if (!inWords.isEmpty() && inWords[0] == ')')
997  inWords.pos++;
998  skipWS (inWords);
999 
1000  return envelope;
1001 }
1002 
1003 // parse parameter pairs into a dictionary
1004 // caller must clean up the dictionary items
1005 QHash < QByteArray, QString > imapParser::parseDisposition (parseString & inWords)
1006 {
1007  QByteArray disposition;
1008  QHash < QByteArray, QString > retVal;
1009 
1010  if (inWords[0] != '(')
1011  {
1012  //disposition only
1013  disposition = parseOneWord (inWords);
1014  }
1015  else
1016  {
1017  inWords.pos++;
1018  skipWS (inWords);
1019 
1020  //disposition
1021  disposition = parseOneWord (inWords);
1022 
1023  retVal = parseParameters (inWords);
1024  if (inWords[0] != ')')
1025  return retVal;
1026  inWords.pos++;
1027  skipWS (inWords);
1028  }
1029 
1030  if (!disposition.isEmpty ())
1031  {
1032  retVal.insert ("content-disposition", QString(disposition));
1033  }
1034 
1035  return retVal;
1036 }
1037 
1038 // parse parameter pairs into a dictionary
1039 // caller must clean up the dictionary items
1040 QHash < QByteArray, QString > imapParser::parseParameters (parseString & inWords)
1041 {
1042  QHash < QByteArray, QString > retVal;
1043 
1044  if (inWords[0] != '(')
1045  {
1046  //better be NIL
1047  parseOneWord (inWords);
1048  }
1049  else
1050  {
1051  inWords.pos++;
1052  skipWS (inWords);
1053 
1054  while (!inWords.isEmpty () && inWords[0] != ')')
1055  {
1056  const QByteArray l1 = parseLiteral(inWords);
1057  const QByteArray l2 = parseLiteral(inWords);
1058  retVal.insert (l1.toLower(), QString(l2));
1059  }
1060 
1061  if (inWords[0] != ')')
1062  return retVal;
1063  inWords.pos++;
1064  skipWS (inWords);
1065  }
1066 
1067  return retVal;
1068 }
1069 
1070 mimeHeader * imapParser::parseSimplePart (parseString & inWords,
1071  QString & inSection, mimeHeader * localPart)
1072 {
1073  QByteArray subtype;
1074  QByteArray typeStr;
1075  QHash < QByteArray, QString > parameters;
1076  ulong size;
1077 
1078  if (inWords[0] != '(')
1079  return 0;
1080 
1081  if (!localPart)
1082  localPart = new mimeHeader;
1083 
1084  localPart->setPartSpecifier (inSection);
1085 
1086  inWords.pos++;
1087  skipWS (inWords);
1088 
1089  //body type
1090  typeStr = parseLiteral(inWords);
1091 
1092  //body subtype
1093  subtype = parseLiteral(inWords);
1094 
1095  localPart->setType (typeStr + '/' + subtype);
1096 
1097  //body parameter parenthesized list
1098  parameters = parseParameters (inWords);
1099  {
1100  QHashIterator < QByteArray, QString > it (parameters);
1101 
1102  while (it.hasNext ())
1103  {
1104  it.next();
1105  localPart->setTypeParm (it.key (), it.value ());
1106  }
1107  parameters.clear ();
1108  }
1109 
1110  //body id
1111  localPart->setID (parseLiteral(inWords));
1112 
1113  //body description
1114  localPart->setDescription (parseLiteral(inWords));
1115 
1116  //body encoding
1117  localPart->setEncoding (parseLiteral(inWords));
1118 
1119  //body size
1120  if (parseOneNumber (inWords, size))
1121  localPart->setLength (size);
1122 
1123  // type specific extensions
1124  if (localPart->getType().toUpper() == "MESSAGE/RFC822")
1125  {
1126  //envelope structure
1127  mailHeader *envelope = parseEnvelope (inWords);
1128 
1129  //body structure
1130  parseBodyStructure (inWords, inSection, envelope);
1131 
1132  localPart->setNestedMessage (envelope);
1133 
1134  //text lines
1135  ulong lines;
1136  parseOneNumber (inWords, lines);
1137  }
1138  else
1139  {
1140  if (typeStr == "TEXT")
1141  {
1142  //text lines
1143  ulong lines;
1144  parseOneNumber (inWords, lines);
1145  }
1146 
1147  // md5
1148  parseLiteral(inWords);
1149 
1150  // body disposition
1151  parameters = parseDisposition (inWords);
1152  {
1153  QString disposition = parameters["content-disposition"];
1154 
1155  localPart->setDisposition (disposition.toAscii ());
1156  QHashIterator < QByteArray, QString > it (parameters);
1157  while (it.hasNext ())
1158  {
1159  it.next();
1160  localPart->setDispositionParm (it.key (), it.value ());
1161  }
1162  parameters.clear ();
1163  }
1164 
1165  // body language
1166  parseSentence (inWords);
1167  }
1168 
1169  // see if we have more to come
1170  while (!inWords.isEmpty () && inWords[0] != ')')
1171  {
1172  //eat the extensions to this part
1173  if (inWords[0] == '(')
1174  parseSentence (inWords);
1175  else
1176  parseLiteral(inWords);
1177  }
1178 
1179  if (inWords[0] == ')')
1180  inWords.pos++;
1181  skipWS (inWords);
1182 
1183  return localPart;
1184 }
1185 
1186 mimeHeader * imapParser::parseBodyStructure (parseString & inWords,
1187  QString & inSection, mimeHeader * localPart)
1188 {
1189  bool init = false;
1190  if (inSection.isEmpty())
1191  {
1192  // first run
1193  init = true;
1194  // assume one part
1195  inSection = '1';
1196  }
1197  int section = 0;
1198 
1199  if (inWords[0] != '(')
1200  {
1201  // skip ""
1202  parseOneWord (inWords);
1203  return 0;
1204  }
1205  inWords.pos++;
1206  skipWS (inWords);
1207 
1208  if (inWords[0] == '(')
1209  {
1210  QByteArray subtype;
1211  QHash< QByteArray, QString > parameters;
1212  QString outSection;
1213 
1214  if (!localPart)
1215  localPart = new mimeHeader;
1216  else
1217  {
1218  // might be filled from an earlier run
1219  localPart->clearNestedParts ();
1220  localPart->clearTypeParameters ();
1221  localPart->clearDispositionParameters ();
1222  // an envelope was passed in so this is the multipart header
1223  outSection = inSection + ".HEADER";
1224  }
1225  if (inWords[0] == '(' && init)
1226  inSection = '0';
1227 
1228  // set the section
1229  if ( !outSection.isEmpty() ) {
1230  localPart->setPartSpecifier(outSection);
1231  } else {
1232  localPart->setPartSpecifier(inSection);
1233  }
1234 
1235  // is multipart (otherwise it is a simplepart and handled later)
1236  while (inWords[0] == '(')
1237  {
1238  outSection = QString::number(++section);
1239  if (!init)
1240  outSection = inSection + '.' + outSection;
1241  mimeHeader *subpart = parseBodyStructure (inWords, outSection, 0);
1242  localPart->addNestedPart (subpart);
1243  }
1244 
1245  // fetch subtype
1246  subtype = parseOneWord (inWords);
1247 
1248  localPart->setType ("MULTIPART/" + subtype);
1249 
1250  // fetch parameters
1251  parameters = parseParameters (inWords);
1252  {
1253  QHashIterator < QByteArray, QString > it (parameters);
1254 
1255  while (it.hasNext ())
1256  {
1257  it.next();
1258  localPart->setTypeParm (it.key (), it.value ());
1259  }
1260  parameters.clear ();
1261  }
1262 
1263  // body disposition
1264  parameters = parseDisposition (inWords);
1265  {
1266  QString disposition = parameters["content-disposition"];
1267 
1268  localPart->setDisposition (disposition.toAscii ());
1269  QHashIterator < QByteArray, QString > it (parameters);
1270  while (it.hasNext ())
1271  {
1272  it.next();
1273  localPart->setDispositionParm (it.key (), it.value ());
1274  }
1275  parameters.clear ();
1276  }
1277 
1278  // body language
1279  parseSentence (inWords);
1280 
1281  }
1282  else
1283  {
1284  // is simple part
1285  inWords.pos--;
1286  inWords.data[inWords.pos] = '('; //fake a sentence
1287  if ( localPart )
1288  inSection = inSection + ".1";
1289  localPart = parseSimplePart (inWords, inSection, localPart);
1290  inWords.pos--;
1291  inWords.data[inWords.pos] = ')'; //remove fake
1292  }
1293 
1294  // see if we have more to come
1295  while (!inWords.isEmpty () && inWords[0] != ')')
1296  {
1297  //eat the extensions to this part
1298  if (inWords[0] == '(')
1299  parseSentence (inWords);
1300  else
1301  parseLiteral(inWords);
1302  }
1303 
1304  if (inWords[0] == ')')
1305  inWords.pos++;
1306  skipWS (inWords);
1307 
1308  return localPart;
1309 }
1310 
1311 void imapParser::parseBody (parseString & inWords)
1312 {
1313  // see if we got a part specifier
1314  if (inWords[0] == '[')
1315  {
1316  QByteArray specifier;
1317  QByteArray label;
1318  inWords.pos++;
1319 
1320  specifier = parseOneWord (inWords, true);
1321 
1322  if (inWords[0] == '(')
1323  {
1324  inWords.pos++;
1325 
1326  while (!inWords.isEmpty () && inWords[0] != ')')
1327  {
1328  label = parseOneWord (inWords);
1329  }
1330 
1331  if (inWords[0] == ')')
1332  inWords.pos++;
1333  }
1334  if (inWords[0] == ']')
1335  inWords.pos++;
1336  skipWS (inWords);
1337 
1338  // parse the header
1339  if (qstrncmp(specifier, "0", specifier.size()) == 0)
1340  {
1341  mailHeader *envelope = 0;
1342  if (lastHandled)
1343  envelope = lastHandled->getHeader ();
1344 
1345  if (!envelope || seenUid.isEmpty ())
1346  {
1347  kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii ();
1348  // don't know where to put it, throw it away
1349  parseLiteral(inWords, true);
1350  }
1351  else
1352  {
1353  kDebug(7116) <<"imapParser::parseBody - reading" << envelope << seenUid.toAscii ();
1354  // fill it up with data
1355  QString theHeader = parseLiteral(inWords, true);
1356  mimeIOQString myIO;
1357 
1358  myIO.setString (theHeader);
1359  envelope->parseHeader (myIO);
1360 
1361  }
1362  }
1363  else if (qstrncmp(specifier, "HEADER.FIELDS", specifier.size()) == 0)
1364  {
1365  // BODY[HEADER.FIELDS (References)] {n}
1366  //kDebug(7116) <<"imapParser::parseBody - HEADER.FIELDS:"
1367  // << QCString(label.data(), label.size()+1);
1368  if (qstrncmp(label, "REFERENCES", label.size()) == 0)
1369  {
1370  mailHeader *envelope = 0;
1371  if (lastHandled)
1372  envelope = lastHandled->getHeader ();
1373 
1374  if (!envelope || seenUid.isEmpty ())
1375  {
1376  kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii ();
1377  // don't know where to put it, throw it away
1378  parseLiteral (inWords, true);
1379  }
1380  else
1381  {
1382  QByteArray references = parseLiteral(inWords, true);
1383  int start = references.indexOf ('<');
1384  int end = references.lastIndexOf ('>');
1385  if (start < end)
1386  references = references.mid (start, end - start + 1);
1387  envelope->setReferences(references.simplified());
1388  }
1389  }
1390  else
1391  { // not a header we care about throw it away
1392  parseLiteral(inWords, true);
1393  }
1394  }
1395  else
1396  {
1397  if (specifier.contains(".MIME") )
1398  {
1399  mailHeader *envelope = new mailHeader;
1400  QString theHeader = parseLiteral(inWords, false);
1401  mimeIOQString myIO;
1402  myIO.setString (theHeader);
1403  envelope->parseHeader (myIO);
1404  if (lastHandled)
1405  lastHandled->setHeader (envelope);
1406  return;
1407  }
1408  // throw it away
1409  kDebug(7116) <<"imapParser::parseBody - discarding" << seenUid.toAscii ();
1410  parseLiteral(inWords, true);
1411  }
1412 
1413  }
1414  else // no part specifier
1415  {
1416  mailHeader *envelope = 0;
1417  if (lastHandled)
1418  envelope = lastHandled->getHeader ();
1419 
1420  if (!envelope || seenUid.isEmpty ())
1421  {
1422  kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii ();
1423  // don't know where to put it, throw it away
1424  parseSentence (inWords);
1425  }
1426  else
1427  {
1428  kDebug(7116) <<"imapParser::parseBody - reading" << envelope << seenUid.toAscii ();
1429  // fill it up with data
1430  QString section;
1431  mimeHeader *body = parseBodyStructure (inWords, section, envelope);
1432  if (body != envelope)
1433  delete body;
1434  }
1435  }
1436 }
1437 
1438 void imapParser::parseFetch (ulong /* value */, parseString & inWords)
1439 {
1440  if (inWords[0] != '(')
1441  return;
1442  inWords.pos++;
1443  skipWS (inWords);
1444 
1445  delete lastHandled;
1446  lastHandled = 0;
1447 
1448  while (!inWords.isEmpty () && inWords[0] != ')')
1449  {
1450  if (inWords[0] == '(')
1451  parseSentence (inWords);
1452  else
1453  {
1454  const QByteArray word = parseLiteral(inWords, false, true);
1455 
1456  switch (word[0])
1457  {
1458  case 'E':
1459  if (word == "ENVELOPE")
1460  {
1461  mailHeader *envelope = 0;
1462 
1463  if (lastHandled)
1464  envelope = lastHandled->getHeader ();
1465  else
1466  lastHandled = new imapCache();
1467 
1468  if (envelope && !envelope->getMessageId ().isEmpty ())
1469  {
1470  // we have seen this one already
1471  // or don't know where to put it
1472  parseSentence (inWords);
1473  }
1474  else
1475  {
1476  envelope = parseEnvelope (inWords);
1477  if (envelope)
1478  {
1479  envelope->setPartSpecifier (seenUid + ".0");
1480  lastHandled->setHeader (envelope);
1481  lastHandled->setUid (seenUid.toULong ());
1482  }
1483  }
1484  }
1485  break;
1486 
1487  case 'B':
1488  if (word == "BODY")
1489  {
1490  parseBody (inWords);
1491  }
1492  else if (word == "BODY[]" )
1493  {
1494  // Do the same as with "RFC822"
1495  parseLiteral(inWords, true);
1496  }
1497  else if (word == "BODYSTRUCTURE")
1498  {
1499  mailHeader *envelope = 0;
1500 
1501  if (lastHandled)
1502  envelope = lastHandled->getHeader ();
1503 
1504  // fill it up with data
1505  QString section;
1506  mimeHeader *body =
1507  parseBodyStructure (inWords, section, envelope);
1508  QByteArray data;
1509  QDataStream stream( &data, QIODevice::WriteOnly );
1510  if ( body )
1511  body->serialize(stream);
1512  parseRelay(data);
1513 
1514  delete body;
1515  }
1516  break;
1517 
1518  case 'U':
1519  if (word == "UID")
1520  {
1521  seenUid = parseOneWord(inWords);
1522  mailHeader *envelope = 0;
1523  if (lastHandled)
1524  envelope = lastHandled->getHeader ();
1525  else
1526  lastHandled = new imapCache();
1527 
1528  if (seenUid.isEmpty ())
1529  {
1530  // unknown what to do
1531  kDebug(7116) <<"imapParser::parseFetch - UID empty";
1532  }
1533  else
1534  {
1535  lastHandled->setUid (seenUid.toULong ());
1536  }
1537  if (envelope)
1538  envelope->setPartSpecifier (seenUid);
1539  }
1540  break;
1541 
1542  case 'R':
1543  if (word == "RFC822.SIZE")
1544  {
1545  ulong size;
1546  parseOneNumber (inWords, size);
1547 
1548  if (!lastHandled) lastHandled = new imapCache();
1549  lastHandled->setSize (size);
1550  }
1551  else if (word.startsWith("RFC822")) //krazy:exclude=strings
1552  {
1553  // might be RFC822 RFC822.TEXT RFC822.HEADER
1554  parseLiteral(inWords, true);
1555  }
1556  break;
1557 
1558  case 'I':
1559  if (word == "INTERNALDATE")
1560  {
1561  const QByteArray date = parseOneWord(inWords);
1562  if (!lastHandled) lastHandled = new imapCache();
1563  lastHandled->setDate(date);
1564  }
1565  break;
1566 
1567  case 'F':
1568  if (word == "FLAGS")
1569  {
1570  //kDebug(7116) <<"GOT FLAGS" << inWords.cstr();
1571  if (!lastHandled) lastHandled = new imapCache();
1572  lastHandled->setFlags (imapInfo::_flags (inWords.cstr()));
1573  }
1574  break;
1575 
1576  default:
1577  parseLiteral(inWords);
1578  break;
1579  }
1580  }
1581  }
1582 
1583  // see if we have more to come
1584  while (!inWords.isEmpty () && inWords[0] != ')')
1585  {
1586  //eat the extensions to this part
1587  if (inWords[0] == '(')
1588  parseSentence (inWords);
1589  else
1590  parseLiteral(inWords);
1591  }
1592 
1593  if (inWords.isEmpty() || inWords[0] != ')')
1594  return;
1595  inWords.pos++;
1596  skipWS (inWords);
1597 }
1598 
1599 
1600 // default parser
1601 void imapParser::parseSentence (parseString & inWords)
1602 {
1603  bool first = true;
1604  int stack = 0;
1605 
1606  //find the first nesting parentheses
1607 
1608  while (!inWords.isEmpty () && (stack != 0 || first))
1609  {
1610  first = false;
1611  skipWS (inWords);
1612 
1613  unsigned char ch = inWords[0];
1614  switch (ch)
1615  {
1616  case '(':
1617  inWords.pos++;
1618  ++stack;
1619  break;
1620  case ')':
1621  inWords.pos++;
1622  --stack;
1623  break;
1624  case '[':
1625  inWords.pos++;
1626  ++stack;
1627  break;
1628  case ']':
1629  inWords.pos++;
1630  --stack;
1631  break;
1632  default:
1633  parseLiteral(inWords);
1634  skipWS (inWords);
1635  break;
1636  }
1637  }
1638  skipWS (inWords);
1639 }
1640 
1641 void imapParser::parseRecent (ulong value, parseString & result)
1642 {
1643  selectInfo.setRecent (value);
1644  result.pos = result.data.size();
1645 }
1646 
1647 void imapParser::parseNamespace (parseString & result)
1648 {
1649  if ( result[0] != '(' )
1650  return;
1651 
1652  QString delimEmpty;
1653  if ( namespaceToDelimiter.contains( QString() ) )
1654  delimEmpty = namespaceToDelimiter[QString()];
1655 
1656  namespaceToDelimiter.clear();
1657  imapNamespaces.clear();
1658 
1659  // remember what section we're in (user, other users, shared)
1660  int ns = -1;
1661  bool personalAvailable = false;
1662  while ( !result.isEmpty() )
1663  {
1664  if ( result[0] == '(' )
1665  {
1666  result.pos++; // tie off (
1667  if ( result[0] == '(' )
1668  {
1669  // new namespace section
1670  result.pos++; // tie off (
1671  ++ns;
1672  }
1673  // namespace prefix
1674  QString prefix = QString::fromLatin1( parseOneWord( result ) );
1675  // delimiter
1676  QString delim = QString::fromLatin1( parseOneWord( result ) );
1677  kDebug(7116) <<"imapParser::parseNamespace ns='" << prefix <<"',delim='" << delim <<"'";
1678  if ( ns == 0 )
1679  {
1680  // at least one personal ns
1681  personalAvailable = true;
1682  }
1683  QString nsentry = QString::number( ns ) + '=' + prefix + '=' + delim;
1684  imapNamespaces.append( nsentry );
1685  if ( prefix.right( 1 ) == delim ) {
1686  // strip delimiter to get a correct entry for comparisons
1687  prefix.resize( prefix.length() );
1688  }
1689  namespaceToDelimiter[prefix] = delim;
1690 
1691  result.pos++; // tie off )
1692  skipWS( result );
1693  } else if ( result[0] == ')' )
1694  {
1695  result.pos++; // tie off )
1696  skipWS( result );
1697  } else if ( result[0] == 'N' )
1698  {
1699  // drop NIL
1700  ++ns;
1701  parseOneWord( result );
1702  } else {
1703  // drop whatever it is
1704  parseOneWord( result );
1705  }
1706  }
1707  if ( !delimEmpty.isEmpty() ) {
1708  // remember default delimiter
1709  namespaceToDelimiter[QString()] = delimEmpty;
1710  if ( !personalAvailable )
1711  {
1712  // at least one personal ns would be nice
1713  kDebug(7116) <<"imapParser::parseNamespace - registering own personal ns";
1714  QString nsentry = "0==" + delimEmpty;
1715  imapNamespaces.append( nsentry );
1716  }
1717  }
1718 }
1719 
1720 int imapParser::parseLoop ()
1721 {
1722  parseString result;
1723 
1724  if (!parseReadLine(result.data)) return -1;
1725 
1726  //kDebug(7116) << result.cstr(); // includes \n
1727 
1728  if (result.data.isEmpty())
1729  return 0;
1730  if (!sentQueue.count ())
1731  {
1732  // maybe greeting or BYE everything else SHOULD not happen, use NOOP or IDLE
1733  kDebug(7116) <<"imapParser::parseLoop - unhandledResponse:" << result.cstr();
1734  unhandled << result.cstr();
1735  }
1736  else
1737  {
1738  CommandPtr current = sentQueue.at (0);
1739  switch (result[0])
1740  {
1741  case '*':
1742  result.data.resize(result.data.size() - 2); // tie off CRLF
1743  parseUntagged (result);
1744  break;
1745  case '+':
1746  continuation = result.data;
1747  break;
1748  default:
1749  {
1750  QByteArray tag = parseLiteral(result);
1751  if (current->id() == tag.data())
1752  {
1753  result.data.resize(result.data.size() - 2); // tie off CRLF
1754  QByteArray resultCode = parseLiteral (result); //the result
1755  current->setResult (resultCode);
1756  current->setResultInfo(result.cstr());
1757  current->setComplete ();
1758 
1759  sentQueue.removeAll (current);
1760  completeQueue.append (current);
1761  if (result.length())
1762  parseResult (resultCode, result, current->command());
1763  }
1764  else
1765  {
1766  kDebug(7116) <<"imapParser::parseLoop - unknown tag '" << tag <<"'";
1767  QByteArray cstr = tag + ' ' + result.cstr();
1768  result.data = cstr;
1769  result.pos = 0;
1770  result.data.resize(cstr.length());
1771  }
1772  }
1773  break;
1774  }
1775  }
1776 
1777  return 1;
1778 }
1779 
1780 void
1781 imapParser::parseRelay (const QByteArray & buffer)
1782 {
1783  Q_UNUSED(buffer);
1784  qWarning
1785  ("imapParser::parseRelay - virtual function not reimplemented - data lost");
1786 }
1787 
1788 void
1789 imapParser::parseRelay (ulong len)
1790 {
1791  Q_UNUSED(len);
1792  qWarning
1793  ("imapParser::parseRelay - virtual function not reimplemented - announcement lost");
1794 }
1795 
1796 bool imapParser::parseRead (QByteArray & buffer, long len, long relay)
1797 {
1798  Q_UNUSED(buffer);
1799  Q_UNUSED(len);
1800  Q_UNUSED(relay);
1801  qWarning
1802  ("imapParser::parseRead - virtual function not reimplemented - no data read");
1803  return false;
1804 }
1805 
1806 bool imapParser::parseReadLine (QByteArray & buffer, long relay)
1807 {
1808  Q_UNUSED(buffer);
1809  Q_UNUSED(relay);
1810  qWarning
1811  ("imapParser::parseReadLine - virtual function not reimplemented - no data read");
1812  return false;
1813 }
1814 
1815 void
1816 imapParser::parseWriteLine (const QString & str)
1817 {
1818  Q_UNUSED(str);
1819  qWarning
1820  ("imapParser::parseWriteLine - virtual function not reimplemented - no data written");
1821 }
1822 
1823 void
1824 imapParser::parseURL (const KUrl & _url, QString & _box, QString & _section,
1825  QString & _type, QString & _uid, QString & _validity, QString & _info)
1826 {
1827  QStringList parameters;
1828 
1829  _box = _url.path ();
1830  kDebug(7116) <<"imapParser::parseURL" << _box;
1831  int paramStart = _box.indexOf("/;");
1832  if ( paramStart > -1 )
1833  {
1834  QString paramString = _box.right( _box.length() - paramStart-2 );
1835  parameters = paramString.split (';', QString::SkipEmptyParts); //split parameters
1836  _box.truncate( paramStart ); // strip parameters
1837  }
1838  // extract parameters
1839  for (QStringList::ConstIterator it (parameters.constBegin ());
1840  it != parameters.constEnd (); ++it)
1841  {
1842  QString temp = (*it);
1843 
1844  // if we have a '/' separator we'll just nuke it
1845  int pt = temp.indexOf ('/');
1846  if (pt > 0)
1847  temp.truncate(pt);
1848  if (temp.startsWith(QLatin1String("section="), Qt::CaseInsensitive))
1849  _section = temp.right (temp.length () - 8);
1850  else if (temp.startsWith(QLatin1String("type="), Qt::CaseInsensitive))
1851  _type = temp.right (temp.length () - 5);
1852  else if (temp.startsWith(QLatin1String("uid="), Qt::CaseInsensitive))
1853  _uid = temp.right (temp.length () - 4);
1854  else if (temp.startsWith(QLatin1String("uidvalidity="), Qt::CaseInsensitive))
1855  _validity = temp.right (temp.length () - 12);
1856  else if (temp.startsWith(QLatin1String("info="), Qt::CaseInsensitive))
1857  _info = temp.right (temp.length () - 5);
1858  }
1859 // kDebug(7116) <<"URL: section=" << _section <<", type=" << _type <<", uid=" << _uid;
1860 // kDebug(7116) <<"URL: user()" << _url.user();
1861 // kDebug(7116) <<"URL: path()" << _url.path();
1862 // kDebug(7116) <<"URL: encodedPathAndQuery()" << _url.encodedPathAndQuery();
1863 
1864  if (!_box.isEmpty ())
1865  {
1866  // strip /
1867  if (_box[0] == '/')
1868  _box = _box.right (_box.length () - 1);
1869  if (!_box.isEmpty () && _box[_box.length () - 1] == '/')
1870  _box.truncate(_box.length() - 1);
1871  }
1872  kDebug(7116) <<"URL: box=" << _box <<", section=" << _section <<", type="
1873  << _type << ", uid=" << _uid << ", validity=" << _validity << ", info=" << _info;
1874 }
1875 
1876 
1877 QByteArray imapParser::parseLiteral(parseString & inWords, bool relay, bool stopAtBracket) {
1878 
1879  if (!inWords.isEmpty() && inWords[0] == '{')
1880  {
1881  QByteArray retVal;
1882  int runLen = inWords.find ('}', 1);
1883  if (runLen > 0)
1884  {
1885  bool proper;
1886  long runLenSave = runLen + 1;
1887  QByteArray tmpstr(runLen, '\0');
1888  inWords.takeMidNoResize(tmpstr, 1, runLen - 1);
1889  runLen = tmpstr.toULong (&proper);
1890  inWords.pos += runLenSave;
1891  if (proper)
1892  {
1893  //now get the literal from the server
1894  if (relay)
1895  parseRelay (runLen);
1896  QByteArray rv;
1897  parseRead (rv, runLen, relay ? runLen : 0);
1898  rv.resize(qMax(runLen, rv.size())); // what's the point?
1899  retVal = rv;
1900  inWords.clear();
1901  parseReadLine (inWords.data); // must get more
1902 
1903  // no duplicate data transfers
1904  relay = false;
1905  }
1906  else
1907  {
1908  kDebug(7116) <<"imapParser::parseLiteral - error parsing {} -" /*<< strLen*/;
1909  }
1910  }
1911  else
1912  {
1913  inWords.clear();
1914  kDebug(7116) <<"imapParser::parseLiteral - error parsing unmatched {";
1915  }
1916  skipWS (inWords);
1917  return retVal;
1918  }
1919 
1920  return parseOneWord(inWords, stopAtBracket);
1921 }
1922 
1923 // does not know about literals ( {7} literal )
1924 QByteArray imapParser::parseOneWord (parseString & inWords, bool stopAtBracket)
1925 {
1926  uint len = inWords.length();
1927  if (len == 0) {
1928  return QByteArray();
1929  }
1930 
1931  if (len > 0 && inWords[0] == '"')
1932  {
1933  unsigned int i = 1;
1934  bool quote = false;
1935  while (i < len && (inWords[i] != '"' || quote))
1936  {
1937  if (inWords[i] == '\\') quote = !quote;
1938  else quote = false;
1939  i++;
1940  }
1941  if (i < len)
1942  {
1943  QByteArray retVal;
1944  retVal.resize(i);
1945  inWords.pos++;
1946  inWords.takeLeftNoResize(retVal, i - 1);
1947  len = i - 1;
1948  int offset = 0;
1949  for (unsigned int j = 0; j < len; j++) {
1950  if (retVal[j] == '\\') {
1951  offset++;
1952  j++;
1953  }
1954  retVal[j - offset] = retVal[j];
1955  }
1956  retVal.resize( len - offset );
1957  inWords.pos += i;
1958  skipWS (inWords);
1959  return retVal;
1960  }
1961  else
1962  {
1963  kDebug(7116) <<"imapParser::parseOneWord - error parsing unmatched \"";
1964  QByteArray retVal = inWords.cstr();
1965  inWords.clear();
1966  return retVal;
1967  }
1968  }
1969  else
1970  {
1971  // not quoted
1972  unsigned int i;
1973  // search for end
1974  for (i = 0; i < len; ++i) {
1975  char ch = inWords[i];
1976  if (ch <= ' ' || ch == '(' || ch == ')' ||
1977  (stopAtBracket && (ch == '[' || ch == ']')))
1978  break;
1979  }
1980 
1981  QByteArray retVal;
1982  retVal.resize(i);
1983  inWords.takeLeftNoResize(retVal, i);
1984  inWords.pos += i;
1985 
1986  if (retVal == "NIL") {
1987  retVal.truncate(0);
1988  }
1989  skipWS (inWords);
1990  return retVal;
1991  }
1992 }
1993 
1994 bool imapParser::parseOneNumber (parseString & inWords, ulong & num)
1995 {
1996  bool valid;
1997  num = parseOneWord(inWords, true).toULong(&valid);
1998  return valid;
1999 }
2000 
2001 bool imapParser::hasCapability (const QString & cap)
2002 {
2003  QString c = cap.toLower();
2004 // kDebug(7116) <<"imapParser::hasCapability - Looking for '" << cap <<"'";
2005  for (QStringList::ConstIterator it = imapCapabilities.constBegin ();
2006  it != imapCapabilities.constEnd (); ++it)
2007  {
2008 // kDebug(7116) <<"imapParser::hasCapability - Examining '" << (*it) <<"'";
2009  if ( !(kasciistricmp(c.toAscii(), (*it).toAscii())) )
2010  {
2011  return true;
2012  }
2013  }
2014  return false;
2015 }
2016 
2017 void imapParser::removeCapability (const QString & cap)
2018 {
2019  imapCapabilities.removeAll(cap.toLower());
2020 }
2021 
2022 QString imapParser::namespaceForBox( const QString & box )
2023 {
2024  kDebug(7116) <<"imapParse::namespaceForBox" << box;
2025  QString myNamespace;
2026  if ( !box.isEmpty() )
2027  {
2028  const QList<QString> list = namespaceToDelimiter.keys();
2029  QString cleanPrefix;
2030  for ( QList<QString>::ConstIterator it = list.begin(); it != list.end(); ++it )
2031  {
2032  if ( !(*it).isEmpty() && box.contains( *it ) )
2033  return (*it);
2034  }
2035  }
2036  return myNamespace;
2037 }
2038 
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Dec 10 2012 13:47:08 by doxygen 1.8.1.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

kioslave/imap4

Skip menu "kioslave/imap4"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Related Pages

kdepimlibs-4.9.4 API Reference

Skip menu "kdepimlibs-4.9.4 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal