tdeioslave/imap4

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