tdeioslave/imap4

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