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

kioslave/imap4

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

KDE's Doxygen guidelines are available online.

kioslave/imap4

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

kdepimlibs API Reference

Skip menu "kdepimlibs API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

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