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

kioslave/imap4

  • kioslave
  • imap4
imapparser.cpp
1/**********************************************************************
2 *
3 * imapparser.cc - IMAP4rev1 Parser
4 * Copyright (C) 2001-2002 Michael Haeckel <haeckel@kde.org>
5 * Copyright (C) 2000 Sven Carstens <s.carstens@gmx.de>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 * Send comments and bug fixes to s.carstens@gmx.de
22 *
23 *********************************************************************/
24
25#include "imapparser.h"
26#include "imapinfo.h"
27#include "mailheader.h"
28#include "mimeheader.h"
29#include "mailaddress.h"
30
31#include <sys/types.h>
32
33#include <stdlib.h>
34#include <unistd.h>
35#include <QList>
36
37extern "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>
53using namespace KIMAP;
54
55static 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
66imapParser::imapParser ()
67{
68 currentState = ISTATE_NO;
69 commandCounter = 0;
70 lastHandled = 0;
71}
72
73imapParser::~imapParser ()
74{
75 delete lastHandled;
76 lastHandled = 0;
77}
78
79CommandPtr
80imapParser::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
92CommandPtr
93imapParser::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
130bool
131imapParser::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
149static 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
194bool
195imapParser::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
315void
316imapParser::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
470void
471imapParser::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
602void imapParser::parseCapability (parseString & result)
603{
604 QByteArray data = result.cstr();
605 kAsciiToLower( data.data() );
606 imapCapabilities = QString::fromLatin1( data ).split( ' ', QString::SkipEmptyParts );
607}
608
609void imapParser::parseFlags (parseString & result)
610{
611 selectInfo.setFlags( result.cstr() );
612}
613
614void 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
635void imapParser::parseLsub (parseString & result)
636{
637 imapList this_one( result.cstr(), *this );
638 listResponses.append( this_one );
639}
640
641void 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
654void 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
667void 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
688void 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
715void 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
735void imapParser::parseCustom (parseString & result)
736{
737 QByteArray word = parseLiteral( result, false, false );
738 lastResults.append( word );
739}
740
741void imapParser::parseOtherUser (parseString & result)
742{
743 lastResults.append( parseOneWord( result ) );
744}
745
746void 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
759void 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
769void 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
776void imapParser::parseSearch (parseString & result)
777{
778 ulong value;
779
780 while ( parseOneNumber( result, value ) ) {
781 lastResults.append( QString::number( value ) );
782 }
783}
784
785void 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
822void imapParser::parseExists (ulong value, parseString & result)
823{
824 selectInfo.setCount( value );
825 result.pos = result.data.size();
826}
827
828void imapParser::parseExpunge (ulong value, parseString & result)
829{
830 Q_UNUSED( value );
831 Q_UNUSED( result );
832}
833
834void 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
862const 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
879mailHeader * 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
954QHash < 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
985QHash < 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
1011mimeHeader * 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
1124mimeHeader * 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
1243void 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
1353void 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
1504void 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
1542void imapParser::parseRecent (ulong value, parseString & result)
1543{
1544 selectInfo.setRecent( value );
1545 result.pos = result.data.size();
1546}
1547
1548void 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
1616int 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
1672void
1673imapParser::parseRelay (const QByteArray & buffer)
1674{
1675 Q_UNUSED( buffer );
1676 qWarning( "imapParser::parseRelay - virtual function not reimplemented - data lost" );
1677}
1678
1679void
1680imapParser::parseRelay (ulong len)
1681{
1682 Q_UNUSED( len );
1683 qWarning( "imapParser::parseRelay - virtual function not reimplemented - announcement lost" );
1684}
1685
1686bool 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
1695bool 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
1703void
1704imapParser::parseWriteLine (const QString & str)
1705{
1706 Q_UNUSED( str );
1707 qWarning( "imapParser::parseWriteLine - virtual function not reimplemented - no data written" );
1708}
1709
1710void
1711imapParser::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
1766QByteArray 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 )
1806QByteArray 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
1873bool imapParser::parseOneNumber (parseString & inWords, ulong & num)
1874{
1875 bool valid;
1876 num = parseOneWord( inWords, true ).toULong( &valid );
1877 return valid;
1878}
1879
1880bool 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
1894void imapParser::removeCapability (const QString & cap)
1895{
1896 imapCapabilities.removeAll( cap.toLower() );
1897}
1898
1899QString 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}
imapCommand
encapulate a IMAP command
Definition: imapcommand.h:43
mailHeader
Definition: mailheader.h:34
mailHeader::setSubject
void setSubject(const QString &_str)
set a unicode subject
Definition: mailheader.h:102
mailHeader::setDate
void setDate(const QByteArray &_str)
set the date
Definition: mailheader.h:132
mimeHeader
Definition: mimeheader.h:36
parseString
a string used during parsing the string allows you to move the effective start of the string using st...
Definition: imapparser.h:52
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jul 21 2022 00:00:00 by doxygen 1.9.5 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

kioslave/imap4

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

kdepimlibs-4.14.10 API Reference

Skip menu "kdepimlibs-4.14.10 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
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