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 
encapulate a IMAP command
Definition: imapcommand.h:38
const TQString & id()
get the id
Definition: imapcommand.cpp:94
const TQString getStr()
returns the data to send to the server The function returns the complete data to be sent to the serve...
void setComplete()
set the completed state
const TQString & parameter()
get the parameter
bool isComplete()
is it complete?
Definition: imapcommand.cpp:76
const TQString & command()
get the command
void setId(const TQString &)
set the id
void setResultInfo(const TQString &)
set the completed state
const TQString & resultInfo()
get information about the result
Definition: imapcommand.cpp:88
void setResult(const TQString &)
set the completed state
const TQString & result()
get the result of the command
Definition: imapcommand.cpp:82
void setDate(const TQCString &_str)
set the date
Definition: mailheader.h:129
void setSubject(const TQString &_str)
set a unicode subject
Definition: mailheader.h:99
a string used during parsing the string allows you to move the effective start of the string using st...
Definition: imapparser.h:53
static TQString quoteIMAP(const TQString &src)
replace " with \" and \ with \ " and \ characters
Definition: rfcdecoder.cpp:158
static TQString fromIMAP(const TQString &src)
Convert an IMAP mailbox to a Unicode path.
Definition: rfcdecoder.cpp:55