1/*
2  Copyright (C) 2000-2007 SKYRIX Software AG
3
4  This file is part of SOPE.
5
6  SOPE is free software; you can redistribute it and/or modify it under
7  the terms of the GNU Lesser General Public License as published by the
8  Free Software Foundation; either version 2, or (at your option) any
9  later version.
10
11  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12  WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14/bin/bash: 58: command not found
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with SOPE; see the file COPYING.  If not, write to the
18  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19  02111-1307, USA.
20*/
21
22#include "NGImap4ResponseParser.h"
23#include "NGImap4Support.h"
24#include "NGImap4Envelope.h"
25#include "NGImap4EnvelopeAddress.h"
26#include "imCommon.h"
27
28// TODO(hh): code is now prepared for last-exception, but currently it just
29//           raises and may leak the exception object
30
31@interface NGImap4ResponseParser(ParsingPrivates)
32- (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_;
33- (NSDictionary *)_parseBodyContent;
34- (NSData *) _parseBodyHeaderFields;
35
36- (NSData *)_parseData;
37
38- (BOOL)_parseQuotaResponseIntoHashMap:(NGMutableHashMap *)result_;
39- (void)_parseContinuationResponseIntoHashMap:(NGMutableHashMap *)result_;
40- (BOOL)_parseListOrLSubResponseIntoHashMap:(NGMutableHashMap *)result_;
41- (BOOL)_parseCapabilityResponseIntoHashMap:(NGMutableHashMap *)result_;
42- (BOOL)_parseNamespaceResponseIntoHashMap:(NGMutableHashMap *)result_;
43- (BOOL)_parseSearchResponseIntoHashMap:(NGMutableHashMap *)result_;
44- (BOOL)_parseSortResponseIntoHashMap:(NGMutableHashMap *)result_;
45- (BOOL)_parseQuotaRootResponseIntoHashMap:(NGMutableHashMap *)result_;
46- (BOOL)_parseStatusResponseIntoHashMap:(NGMutableHashMap *)result_;
47- (BOOL)_parseThreadResponseIntoHashMap:(NGMutableHashMap *)result_;
48- (BOOL)_parseVanishedResponseIntoHashMap:(NGMutableHashMap *)result_;
49- (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_;
50- (BOOL)_parseACLResponseIntoHashMap:(NGMutableHashMap *)result_;
51- (BOOL)_parseAnnotationResponseIntoHashMap:(NGMutableHashMap *)result_;
52- (BOOL)_parseMyRightsResponseIntoHashMap:(NGMutableHashMap *)result_;
53- (BOOL)_parseListRightsResponseIntoHashMap:(NGMutableHashMap *)result_;
54
55- (NSArray *)_parseThread;
56
57@end
58
59@implementation NGImap4ResponseParser
60
61#define __la(__SELF__, __PEEKPOS) \
62  ((__SELF__->la == NULL) \
63    ? [__SELF__->buffer la:__PEEKPOS]\
64    : __SELF__->la(__SELF__->buffer, @selector(la:), __PEEKPOS))
65
66static __inline__ int _la(NGImap4ResponseParser *self, unsigned _laCnt) {
67  register unsigned char c = __la(self, _laCnt);
68  return (c == '\r')
69    ? _la(self, _laCnt + 1)
70    : c;
71}
72static __inline__ BOOL _matchesString(NGImap4ResponseParser *self,
73				      const char *s)
74{
75  register unsigned int  i;
76
77  for (i = 0; s[i] != '\0'; i++) {
78    if (_la(self, i) != s[i])
79      return NO;
80  }
81  return YES;
82}
83
84static NSDictionary *_parseBody(NGImap4ResponseParser *self,
85				BOOL isBodyStructure);
86static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self,
87				      BOOL isBodyStructure);
88static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self,
89					 BOOL isBodyStructure);
90
91static NSArray *_parseLanguages();
92
93static NSString *_parseBodyString(NGImap4ResponseParser *self,
94                                  BOOL _convertString);
95static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
96                                        BOOL _convertString,
97                                        BOOL _decode);
98static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self);
99static NSDictionary *_parseContentDisposition(NGImap4ResponseParser *self);
100static NSArray *_parseAddressStructure(NGImap4ResponseParser *self);
101static NSArray *_parseParenthesizedAddressList(NGImap4ResponseParser *self);
102static int _parseTaggedResponse(NGImap4ResponseParser *self,
103                                NGMutableHashMap *result_);
104static void _parseUntaggedResponse(NGImap4ResponseParser *self,
105                                   NGMutableHashMap *result_);
106static NSArray *_parseFlagArray(NGImap4ResponseParser *self);
107static BOOL _parseEnabledUntaggedResponse(NGImap4ResponseParser *self,
108                                          NGMutableHashMap *result_);
109static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self,
110                                        NGMutableHashMap *result_);
111static BOOL _parseOkUntaggedResponse(NGImap4ResponseParser *self,
112                                     NGMutableHashMap *result_);
113static BOOL _parseBadUntaggedResponse(NGImap4ResponseParser *self,
114                                      NGMutableHashMap *result_);
115static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self,
116                                     NGMutableHashMap *result_);
117static NSNumber *_parseUnsigned(NGImap4ResponseParser *self);
118static NSString *_parseUntil(NGImap4ResponseParser *self, char _c);
119static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2);
120
121static __inline__ NSException *_consumeIfMatch
122  (NGImap4ResponseParser *self, unsigned char _m);
123static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt);
124
125static void _parseSieveRespone(NGImap4ResponseParser *self,
126                               NGMutableHashMap *result_);
127static BOOL _parseGreetingsSieveResponse(NGImap4ResponseParser *self,
128                                         NGMutableHashMap *result_);
129static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self,
130                                    NGMutableHashMap *result_);
131static BOOL _parseOkSieveResponse(NGImap4ResponseParser *self,
132                                  NGMutableHashMap *result_);
133static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self,
134                                  NGMutableHashMap *result_);
135static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self);
136static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self);
137
138static unsigned int     LaSize              = 4097;
139static unsigned         UseMemoryMappedData = 0;
140static unsigned         Imap4MMDataBoundary = 0;
141static BOOL             debugOn             = NO;
142static BOOL             debugDataOn         = NO;
143static NSStringEncoding encoding;
144static Class            StrClass  = Nil;
145static Class            NumClass  = Nil;
146static Class            DataClass = Nil;
147static NSStringEncoding defCStringEncoding;
148static NSNumber         *YesNum = nil;
149static NSNumber         *NoNum  = nil;
150static NSNull           *null   = nil;
151
152+ (void)initialize {
153  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
154  static BOOL didInit = NO;
155  if (didInit) return;
156  didInit = YES;
157
158  null = [[NSNull null] retain];
159
160  encoding = [NGMimePartParser defaultHeaderFieldEncoding];
161  defCStringEncoding = [NSString defaultCStringEncoding];
162
163  debugOn             = [ud boolForKey:@"ImapDebugEnabled"];
164  debugDataOn         = [ud boolForKey:@"ImapDebugDataEnabled"];
165  UseMemoryMappedData = [ud boolForKey:@"NoMemoryMappedDataForImapBlobs"]?0:1;
166  Imap4MMDataBoundary = [ud integerForKey:@"Imap4MMDataBoundary"];
167
168  if (Imap4MMDataBoundary < 10)
169    /* Note: this should be larger than a usual header size! */
170    Imap4MMDataBoundary = 2 * LaSize;
171
172  StrClass  = [NSString class];
173  NumClass  = [NSNumber class];
174  DataClass = [NSData class];
175  YesNum    = [[NumClass numberWithBool:YES] retain];
176  NoNum     = [[NumClass numberWithBool:NO]  retain];
177}
178
179+ (id)parserWithStream:(id<NGActiveSocket>)_stream {
180  NGImap4ResponseParser *parser;
181
182  parser = [NGImap4ResponseParser alloc]; /* seperate line to keep gcc happy */
183  return [[parser initWithStream:_stream] autorelease];
184}
185
186- (id)initWithStream:(id<NGActiveSocket>)_stream {
187  // designated initializer
188  if (_stream == nil) {
189    [self logWithFormat:@"%s: got no stream ...", __PRETTY_FUNCTION__];
190    [self release];
191    return nil;
192  }
193
194  if ((self = [super init])) {
195    id s;
196
197    s = [(NGBufferedStream *)[NGBufferedStream alloc] initWithSource:_stream];
198    self->buffer = [NGByteBuffer alloc];
199    self->buffer = [self->buffer initWithSource:s la:LaSize];
200    [s release];
201
202    if ([self->buffer respondsToSelector:@selector(methodForSelector:)])
203      self->la = (int(*)(id, SEL, unsigned))
204        [self->buffer methodForSelector:@selector(la:)];
205
206    self->debug = debugOn;
207  }
208  return self;
209}
210
211- (id)init {
212  [self release];
213  [NSException raise:@"InvalidUseOfMethodException"
214	       format:
215		 @"calling -init on the NGImap4ResponseParser is not allowed"];
216  return nil;
217}
218
219- (void)dealloc {
220  [self->buffer release];
221  if (self->debug)
222    [self->serverResponseDebug release];
223  [super dealloc];
224}
225
226/* exception handling */
227
228- (void)setLastException:(NSException *)_exc {
229  // TODO: support last exception
230  [_exc raise];
231}
232
233/*
234** Parse Sieve Responses
235*/
236
237- (NGHashMap *)parseSieveResponse {
238  NGMutableHashMap *result;
239
240  if (self->debug) {
241    if (self->serverResponseDebug != nil)
242      [self->serverResponseDebug release];
243    self->serverResponseDebug = [[NSMutableString alloc] initWithCapacity:512];
244  }
245  result = [NGMutableHashMap hashMapWithCapacity:64];
246
247  if (_la(self,0) == -1) {
248    [self setLastException:[self->buffer lastException]];
249    return nil;
250  }
251
252  _parseSieveRespone(self, result);
253  return result;
254}
255
256- (NGHashMap *)parseResponseForTagId:(int)_tag exception:(NSException **)ex_ {
257  /* parse a response from the server, _tag!=-1 parse until tagged response */
258  // TODO: is NGHashMap really necessary here?
259  BOOL             endOfCommand;
260  NGMutableHashMap *result;
261
262  if (ex_) *ex_ = nil;
263
264  if (self->debug) {
265    [self->serverResponseDebug release]; self->serverResponseDebug = nil;
266    self->serverResponseDebug = [[NSMutableString alloc] initWithCapacity:512];
267  }
268
269  result = [NGMutableHashMap hashMapWithCapacity:64];
270  for (endOfCommand = NO; !endOfCommand; ) {
271    unsigned char l0;
272
273    l0 = _la(self, 0);
274
275    if (l0 == '*') { /* those starting with '* ' */
276      _parseUntaggedResponse(self, result);
277      if ([result objectForKey:@"bye"]) {
278        endOfCommand = YES;
279      }
280      else {
281        if (_tag == -1) {
282          if ([result objectForKey:@"ok"] != nil)
283            endOfCommand = YES;
284        }
285      }
286    }
287    else if (l0 == '+') { /* starting with a '+'? */
288      [self _parseContinuationResponseIntoHashMap:result];
289      endOfCommand = YES;
290    }
291    else if (isdigit(l0)) {
292      /* those starting with a number '24 ', eg '24 OK Completed' */
293      endOfCommand = (_parseTaggedResponse(self, result) == _tag);
294    }
295    else if (l0 == (unsigned char) -1) {
296      if (ex_) {
297        *ex_ = [self->buffer lastException];
298        if (!*ex_)
299          *ex_
300            = [NSException exceptionWithName:@"UnexpectedEndOfStream"
301                                      reason:(@"the parsed stream ended"
302                                              @" unexpectedly")
303                                    userInfo:nil];
304      } else {
305        [self setLastException: [self->buffer lastException]];
306      }
307      endOfCommand = YES;
308      result = nil;
309    }
310  }
311  return result;
312}
313- (NGHashMap *)parseResponseForTagId:(int)_tag {
314  // DEPRECATED
315  NSException *e = nil;
316  NGHashMap   *hm;
317
318  hm = [self parseResponseForTagId:_tag exception:&e];
319  if (e) {
320    [self setLastException:e];
321    return nil;
322  }
323  return hm;
324}
325
326static void _parseSieveRespone(NGImap4ResponseParser *self,
327                               NGMutableHashMap *result_)
328{
329  if (_parseGreetingsSieveResponse(self, result_))
330    return;
331  if (_parseDataSieveResponse(self, result_))    // la: 1
332    return;
333  if (_parseOkSieveResponse(self, result_))     // la: 2
334    return;
335  if (_parseNoSieveResponse(self, result_))     // la: 2
336    return;
337}
338
339- (NSData *)_parseDataToFile:(unsigned)_size {
340  // TODO: move to own method
341  // TODO: do not use NGFileStream but just fopen/fwrite
342  static NSProcessInfo *Pi = nil;
343  NGFileStream  *stream;
344  NSData        *result;
345  unsigned char buf[LaSize + 2];
346  unsigned char tmpBuf[LaSize + 2];
347  unsigned      wasRead = 0;
348  NSString      *path;
349  signed char   lastChar; // must be signed
350
351  if (debugDataOn) [self logWithFormat:@"  using memory mapped data  ..."];
352
353  if (Pi == nil)
354    Pi = [[NSProcessInfo processInfo] retain];
355
356  path   = [Pi temporaryFileName];
357  stream = [NGFileStream alloc]; /* extra line to keep gcc happy */
358  stream = [stream initWithPath:path];
359
360  if (![stream openInMode:NGFileWriteOnly]) {
361    NSException *e;
362
363    e = [[NGImap4ParserException alloc]
364	  initWithFormat:@"Could not open temporary file %@", path];
365    [self setLastException:[e autorelease]];
366    [self logWithFormat: [e reason]];
367    return nil;
368  }
369
370  lastChar = -1;
371  while (wasRead < _size) {
372    unsigned readCnt, bufCnt, tmpSize, cnt, tmpBufCnt;
373
374    bufCnt = 0;
375
376    if (lastChar != -1) {
377      buf[bufCnt++] = lastChar;
378      lastChar = -1;
379    }
380
381    [self->buffer la:(_size - wasRead <  LaSize)
382	 ? (_size - wasRead)
383	 : LaSize];
384
385    readCnt = [self->buffer readBytes:buf+bufCnt count:_size - wasRead];
386
387    wasRead+=readCnt;
388    bufCnt +=readCnt;
389
390    tmpSize   = bufCnt - 1;
391    cnt       = 0;
392    tmpBufCnt = 0;
393
394    while (cnt < tmpSize) {
395      if ((buf[cnt] == '\r') && (buf[cnt+1] == '\n')) {
396	cnt++;
397      }
398      tmpBuf[tmpBufCnt++] = buf[cnt++];
399    }
400    if (cnt < bufCnt) {
401      lastChar = buf[cnt];
402    }
403    [stream writeBytes:tmpBuf count:tmpBufCnt];
404  }
405  if (lastChar != -1)
406    [stream writeBytes:&lastChar count:1];
407
408  [stream close];
409  [stream release]; stream = nil;
410  result = [DataClass dataWithContentsOfMappedFile:path];
411  [[NSFileManager defaultManager] removeFileAtPath:path handler:nil];
412
413  return result;
414}
415- (NSData *)_parseDataIntoRAM:(unsigned)_size {
416  /* parses data into a RAM buffer (NSData) */
417  unsigned char *buf = NULL;
418  unsigned char *tmpBuf;
419  unsigned      wasRead   = 0;
420  unsigned      cnt, tmpBufCnt, tmpSize;
421  NSData        *result;
422
423  buf = calloc(_size + 10, sizeof(char));
424
425  while (wasRead < _size) {
426    [self->buffer la:(_size - wasRead <  LaSize) ? (_size - wasRead) : LaSize];
427
428    wasRead += [self->buffer readBytes:(buf + wasRead) count:(_size-wasRead)];
429  }
430
431  /* normalize response  \r\n -> \n */
432
433  tmpBuf    = calloc(_size + 10, sizeof(char));
434  cnt       = 0;
435  tmpBufCnt = 0;
436  tmpSize   = _size == 0 ? 0 : _size - 1;
437  while (tmpBufCnt < tmpSize && cnt < _size) {
438    if ((buf[cnt] == '\r') && (buf[cnt + 1] == '\n'))
439      cnt++; /* skip \r */
440
441    tmpBuf[tmpBufCnt] = buf[cnt];
442    tmpBufCnt++;
443    cnt++;
444  }
445  if (cnt < _size) {
446    tmpBuf[tmpBufCnt] = buf[cnt];
447    tmpBufCnt++;
448    cnt++;
449  }
450
451  result = [DataClass dataWithBytesNoCopy:tmpBuf length:tmpBufCnt];
452
453  if (buf != NULL) free(buf); buf = NULL;
454  return result;
455}
456- (NSData *)_parseData {
457  /*
458    parses:
459      { <uint> } \n
460  */
461  // TODO: split up method
462  unsigned size;
463  NSNumber *sizeNum;
464
465  if (_la(self, 0) != '{')
466    return nil;
467
468  if (debugDataOn) [self logWithFormat:@"parse data ..."];
469
470  /* got header */
471
472  _consume(self, 1); // '{'
473  if ((sizeNum = _parseUnsigned(self)) == nil) {
474    NSException *e;
475
476    e = [[NGImap4ParserException alloc]
477	    initWithFormat:@"expect a number between {}"];
478    [self setLastException:[e autorelease]];
479    return nil;
480  }
481  if (debugDataOn) [self logWithFormat:@"  parse data, size: %@", sizeNum];
482  _consumeIfMatch(self, '}');
483  _consumeIfMatch(self, '\n');
484
485  if ((size = [sizeNum intValue]) == 0) {
486    [self logWithFormat:@"ERROR(%s): got content size '0'!",
487            __PRETTY_FUNCTION__];
488    return nil;
489  }
490
491  if (UseMemoryMappedData && (size > Imap4MMDataBoundary))
492    return [self _parseDataToFile:size];
493
494  return [self _parseDataIntoRAM:size];
495}
496
497/*
498  Similair to _parseData but used to parse something like this :
499
500  BODY[HEADER.FIELDS (X-PRIORITY)] {17}
501  X-Priority: 1
502
503  )
504
505  Headers are returned as data, as is.
506*/
507- (NSData *) _parseBodyHeaderFields
508{
509  NSData   *result;
510  unsigned size;
511  NSNumber *sizeNum;
512
513  result = nil;
514
515  /* We skip until we're ready to parse {length}. We must be careful
516     here and not assume we will have a valid length - we could very
517     well receive NIL from the server */
518  _parseUntil(self, ' ');
519
520  if ((_la(self,0)) == '{')
521    {
522      _consume(self, 1);
523
524      if ((sizeNum = _parseUnsigned(self)) == nil) {
525	NSException *e;
526
527	e = [[NGImap4ParserException alloc]
528	      initWithFormat:@"expect a number between {}"];
529	[self setLastException:[e autorelease]];
530	return nil;
531      }
532      _consumeIfMatch(self, '}');
533      _consumeIfMatch(self, '\n');
534
535      if ((size = [sizeNum intValue]) == 0) {
536	[self logWithFormat:@"ERROR(%s): got content size '0'!",
537	      __PRETTY_FUNCTION__];
538	return nil;
539      }
540
541      if (UseMemoryMappedData && (size > Imap4MMDataBoundary))
542	return [self _parseDataToFile:size];
543
544      return [self _parseDataIntoRAM:size];
545    }
546
547  return result;
548}
549
550static int _parseTaggedResponse(NGImap4ResponseParser *self,
551                                NGMutableHashMap *result_)
552{
553  NSDictionary *d;
554  NSNumber *tag  = nil;
555  NSString *res  = nil;
556  NSString *desc = nil;
557  NSString *flag = nil;
558
559  if ((tag  = _parseUnsigned(self)) == nil) {
560    NSException *e;
561
562    if (self->debug) {
563      e = [[NGImap4ParserException alloc]
564	    initWithFormat:@"expect a number at begin of tagged response <%@>",
565	    self->serverResponseDebug];
566    }
567    else {
568      e = [[NGImap4ParserException alloc]
569	    initWithFormat:@"expect a number at begin of tagged response"];
570    }
571    e = [e autorelease];
572    [self setLastException:e];
573    return -1;
574  }
575
576  _consumeIfMatch(self, ' ');
577  res  = [_parseUntil(self, ' ') lowercaseString];
578  if (_la(self, 0) == '[') { /* Found flag like [READ-ONLY] */
579    _consume(self, 1);
580    flag = _parseUntil(self, ']');
581  }
582  desc = _parseUntil(self, '\n');
583  /*
584    ATTENTION: if no flag was set, flag == nil, in this case all key-value
585               pairs after flag will be ignored
586  */
587  d = [[NSDictionary alloc] initWithObjectsAndKeys:
588			      tag,  @"tagId",
589			      res,  @"result",
590			      desc, @"description",
591			      flag, @"flag", nil];
592  [result_ addObject:d forKey:@"ResponseResult"];
593  [d release];
594  return [tag intValue];
595}
596
597static void _parseUntaggedResponse(NGImap4ResponseParser *self,
598                                   NGMutableHashMap *result_)
599{
600  // TODO: is it really required by IMAP4 that responses are uppercase?
601  // TODO: apparently this code *breaks* with lowercase detection on!
602  unsigned char l0, l1 = 0;
603  _consumeIfMatch(self, '*');
604  _consumeIfMatch(self, ' ');
605
606  l0 = _la(self, 0);
607  switch (l0) {
608  case 'A':
609    l1 = _la(self, 1);
610    if (l1 == 'C' && [self _parseACLResponseIntoHashMap:result_])
611      return;
612    if (l1 == 'N' && [self _parseAnnotationResponseIntoHashMap:result_])
613      return;
614    break;
615
616  case 'B':
617    l1 = _la(self, 1);
618    if (l1 == 'A' && _parseBadUntaggedResponse(self, result_))    // la: 3
619      return;
620    if (l1 == 'Y' && [self _parseByeUntaggedResponseIntoHashMap:result_]) // 3
621      return;
622    break;
623
624  case 'C':
625    if ([self _parseCapabilityResponseIntoHashMap:result_])       // la: 10
626      return;
627    break;
628
629  case 'E':
630    if (_parseEnabledUntaggedResponse(self, result_))  // la: 7
631      return;
632    break;
633
634  case 'F':
635    if (_parseFlagsUntaggedResponse(self, result_))  // la: 5
636      return;
637    break;
638
639  // ID untagged response
640  case 'I':
641    _parseUntil(self, '\n');
642    return;
643
644  case 'L':
645    if (_matchesString(self, "LISTRIGHTS")) {
646      if ([self _parseListRightsResponseIntoHashMap:result_])
647	return;
648    }
649    if ([self _parseListOrLSubResponseIntoHashMap:result_])       // la: 4
650      return;
651    break;
652
653  case 'M':
654    if ([self _parseMyRightsResponseIntoHashMap:result_])
655      return;
656    break;
657
658  case 'N':
659    if (_matchesString(self, "NAMESPACE")) {
660      if ([self _parseNamespaceResponseIntoHashMap:result_])
661	return;
662    }
663    if (_parseNoUntaggedResponse(self, result_))     // la: 2
664      return;
665    break;
666
667  case 'O':
668    if (_parseOkUntaggedResponse(self, result_))     // la: 2
669      /* eg "* OK Completed" */
670      return;
671    break;
672
673  case 'R':
674    break;
675
676  case 'S':
677    switch (_la(self, 1)) {
678    case 'O': // SORT
679      if ([self _parseSortResponseIntoHashMap:result_])   // la: 4
680	return;
681      break;
682    case 'E': // SEARCH
683      if ([self _parseSearchResponseIntoHashMap:result_]) // la: 5
684	return;
685      break;
686    case 'T': // STATUS
687      if ([self _parseStatusResponseIntoHashMap:result_]) // la: 6
688	/* eg "* STATUS INBOX (MESSAGES 0 RECENT 0 UNSEEN 0)" */
689	return;
690      break;
691    }
692    break;
693
694  case 'T':
695    if ([self _parseThreadResponseIntoHashMap:result_])    // la: 6
696      return;
697    break;
698
699  case 'V':
700    if ([self _parseVanishedResponseIntoHashMap:result_])    // la: 6
701      return;
702    break;
703
704  case 'Q':
705    if ([self _parseQuotaResponseIntoHashMap:result_])     // la: 6
706      return;
707    if ([self _parseQuotaRootResponseIntoHashMap:result_]) // la: 10
708      return;
709    break;
710
711  case '0': case '1': case '2': case '3': case '4':
712  case '5': case '6': case '7': case '8': case '9':
713    if ([self _parseNumberUntaggedResponse:result_]) // la: 5
714      /* eg "* 928 FETCH ..." */
715      return;
716    break;
717  }
718
719  // TODO: what if none matches?
720  [self logWithFormat:@"%s: no matching tag specifier?", __PRETTY_FUNCTION__];
721  [self logWithFormat:@"  line: '%@'", _parseUntil(self, '\n')];
722}
723
724- (void)_parseContinuationResponseIntoHashMap:(NGMutableHashMap *)result_ {
725  _consumeIfMatch(self, '+');
726  _consumeIfMatch(self, ' ');
727
728  [result_ addObject:YesNum forKey:@"ContinuationResponse"];
729  [result_ addObject:_parseUntil(self, '\n') forKey:@"description"];
730}
731
732static inline void
733_purifyQuotedString(NSMutableString *quotedString) {
734  unichar *currentChar, *qString, *maxC, *startC;
735  unsigned int max, questionMarks;
736  BOOL possiblyQuoted, skipSpaces;
737  NSMutableString *newString;
738
739  newString = [NSMutableString string];
740
741  max = [quotedString length];
742  qString = malloc (sizeof (unichar) * max);
743  [quotedString getCharacters: qString];
744  currentChar = qString;
745  startC = qString;
746  maxC = qString + max;
747
748  possiblyQuoted = NO;
749  skipSpaces = NO;
750
751  questionMarks = 0;
752
753  while (currentChar < maxC) {
754    if (possiblyQuoted) {
755      if (questionMarks == 2) {
756	if ((*currentChar == 'Q' || *currentChar == 'q'
757	     || *currentChar == 'B' || *currentChar == 'b')
758	    && ((currentChar + 1) < maxC
759		&& (*(currentChar + 1) == '?'))) {
760	  currentChar++;
761	  questionMarks = 3;
762	}
763	else {
764	  possiblyQuoted = NO;
765	}
766      }
767      else if (questionMarks == 4) {
768	if (*currentChar == '=') {
769	  skipSpaces = YES;
770	  possiblyQuoted = NO;
771 	  currentChar++;
772	  [newString appendString: [NSString stringWithCharacters: startC
773					     length: (currentChar - startC)]];
774	  startC = currentChar;
775	}
776	else {
777	  possiblyQuoted = NO;
778	}
779      }
780      else {
781	if (*currentChar == '?') {
782	  questionMarks++;
783	}
784	else if (*currentChar == ' ' && questionMarks != 3) {
785	  possiblyQuoted = NO;
786	}
787      }
788    }
789    else if (*currentChar == '='
790	     && ((currentChar + 1) < maxC
791		 && (*(currentChar + 1) == '?'))) {
792      [newString appendString: [NSString stringWithCharacters: startC
793 					 length: (currentChar - startC)]];
794      startC = currentChar;
795      possiblyQuoted = YES;
796      skipSpaces = NO;
797      currentChar++;
798      questionMarks = 1;
799    }
800
801    if (skipSpaces) {
802      /* This part is about skipping the spaces separating two encoded chunks,
803         which occurs when the chunks are on different lines. However we
804         cannot ignore them if the next chunk is not encoded. Basically, we
805         can deduce a case from the other by the fact that it makes no sense
806         per se to have a space separating two encoded chunks. */
807      startC = currentChar;
808      while (currentChar < maxC
809             && (*currentChar == ' ' || *currentChar == '\t'))
810	currentChar++;
811      if (currentChar != startC) {
812        if (currentChar < maxC && *currentChar != '=')
813          [newString appendString: [NSString stringWithCharacters: startC
814                                             length: (currentChar - startC)]];
815        startC = currentChar;
816      }
817      else
818        currentChar++;
819
820      skipSpaces = NO;
821    }
822    else
823      currentChar++;
824  }
825
826  if (startC < maxC)
827    [newString appendString: [NSString stringWithCharacters: startC
828				       length: (currentChar - startC)]];
829
830  [quotedString setString: newString];
831  free (qString);
832}
833
834- (NSString *)_parseQuotedString {
835  NSMutableString *quotedString;
836  NSString *tmpString;
837
838  /* parse a quoted string, eg '"' */
839  if (_la(self, 0) == '"') {
840    _consume(self, 1);
841    quotedString = [NSMutableString string];
842    tmpString = _parseUntil(self, '"');
843    [quotedString appendString: tmpString];
844  }
845  else {
846    quotedString = nil;
847  }
848
849  _purifyQuotedString(quotedString);
850
851  return quotedString;
852}
853
854- (NSString *)_parseQuotedStringOrNIL {
855  unsigned char c0;
856
857  if ((c0 = _la(self, 0)) == '"')
858    return [self _parseQuotedString];
859
860  if (c0 == '{') {
861    /* a size indicator, eg '{112}\nkasdjfkja sdj fhj hasdfj hjasdf' */
862    NSData   *data;
863    NSString *s;
864
865    if ((data = [self _parseData]) == nil)
866      return nil;
867    if (![data isNotEmpty])
868      return @"";
869
870    s = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
871    if (s == nil)
872      s = [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding];
873    if (s == nil) {
874      [self logWithFormat:
875	      @"ERROR(%s): could not convert data (%d bytes) into string.",
876	      __PRETTY_FUNCTION__, [data length]];
877      return @"[ERROR: NGImap4 could not parse IMAP4 data string]";
878    }
879    return [s autorelease];
880  }
881
882  if (c0 == 'N' && _matchesString(self, "NIL")) {
883    _consume(self, 3);
884    return (id)null;
885  }
886  return nil;
887}
888- (id)_parseQuotedStringOrDataOrNIL {
889  if (_la(self, 0) == '"')
890    return [self _parseQuotedString];
891  if (_la(self, 0) == '{')
892    return [self _parseData];
893
894  if (_matchesString(self, "NIL")) {
895    _consume(self, 3);
896    return null;
897  }
898  return nil;
899}
900- (void)_consumeOptionalSpace {
901  if (_la(self, 0) == ' ') _consume(self, 1);
902}
903
904- (BOOL)_parseListOrLSubResponseIntoHashMap:(NGMutableHashMap *)result_ {
905  NSArray  *flags = nil;
906  NSString *delim = nil;
907  NSString *name  = nil;
908  NSDictionary *d;
909
910  if (!_matchesString(self, "LIST ") && !_matchesString(self, "LSUB "))
911    return NO;
912
913  _consume(self, 5); /* consume 'LIST ' or 'LSUB ' */
914  flags = _parseFlagArray(self);
915  _consumeIfMatch(self, ' ');
916
917  if (_la(self, 0) == '"') {
918    delim = [self _parseQuotedString];
919    _consumeIfMatch(self, ' ');
920  }
921  else {
922    _parseUntil(self, ' ');
923    delim = nil;
924  }
925  if (_la(self, 0) == '"') {
926    name = [self _parseQuotedString];
927    _parseUntil(self, '\n');
928  }
929  else if (_la(self, 0) == '{') {
930    name = [self _parseQuotedStringOrNIL];
931    _parseUntil(self, '\n');
932  }
933  else
934    name = _parseUntil(self, '\n');
935
936  d = [[NSDictionary alloc] initWithObjectsAndKeys:
937			      name,  @"folderName",
938			      flags, @"flags",
939			      delim, @"delimiter", nil];
940  [result_ addObject:d forKey:@"list"];
941  [d release];
942  return YES;
943}
944
945- (BOOL)_parseCapabilityResponseIntoHashMap:(NGMutableHashMap *)result_ {
946  NSString *caps;
947  NSEnumerator   *enumerator;
948  id             obj;
949  NSMutableArray *array;
950  NSArray        *tmp;
951
952  if (!_matchesString(self, "CAPABILITY "))
953    return NO;
954
955  caps = _parseUntil(self, '\n');
956
957  array = [[NSMutableArray alloc] initWithCapacity:16];
958
959  enumerator = [[caps componentsSeparatedByString:@" "] objectEnumerator];
960  while ((obj = [enumerator nextObject]) != nil)
961    [array addObject:[obj lowercaseString]];
962
963  tmp = [array copy];
964  [result_ addObject:tmp forKey:@"capability"];
965
966  [array release]; array = nil;
967  [tmp   release]; tmp   = nil;
968  return YES;
969}
970
971/* support for NAMESPACE extension - RFC2342 */
972
973- (NSDictionary *)_parseNamespacePart {
974  NSDictionary *namespacePart;
975  NSString *prefix, *key, *delimiter;
976  NSMutableDictionary *parameters;
977  NSMutableArray *values;
978
979  _consume(self, 1);                             /* ( */
980  prefix = [self _parseQuotedStringOrNIL];       /* "prefix" */
981  _consume(self, 1);                             /* <sp> */
982  delimiter = [self _parseQuotedStringOrNIL];    /* "delimiter" */
983  parameters = [NSMutableDictionary dictionary];
984  while (_la(self, 0) == ' ') {
985    _consume(self, 1);                           /* <sp> */
986    key = [self _parseQuotedString];
987    _consume(self, 1);                           /* <sp> */
988    values = [NSMutableArray new];
989    while (_la(self, 0) != ')') {
990      _consume(self, 1);                         /* ( or <sp> */
991      [values addObject: [self _parseQuotedString]];
992    }
993    _consume(self, 1);                           /* ) */
994    [parameters setObject: values forKey: key];
995    [values release];
996  }
997  _consume(self, 1);                             /* ) */
998
999  namespacePart = [NSDictionary dictionaryWithObjectsAndKeys:
1000                                  prefix, @"prefix",
1001                                delimiter, @"delimiter",
1002                                parameters, @"parameters",
1003                                nil];
1004
1005  return namespacePart;
1006}
1007
1008- (NSArray *)_parseNamespace {
1009  NSMutableArray *namespace;
1010
1011  namespace = [[NSMutableArray alloc] initWithCapacity: 3];
1012  if (_la(self, 0) == 'N') {
1013    namespace = nil;
1014    _consume(self, 3);
1015  } else {
1016    _consume(self, 1); /* ( */
1017    while (_la(self, 0) == '(') {
1018      [namespace addObject: [self _parseNamespacePart]];
1019    }
1020    _consume(self, 1); /* ) */
1021  }
1022
1023  return namespace;
1024}
1025
1026- (BOOL)_parseNamespaceResponseIntoHashMap:(NGMutableHashMap *)result_ {
1027  NSArray *namespace;
1028
1029  if (!_matchesString(self, "NAMESPACE "))
1030    return NO;
1031
1032  _parseUntil(self, ' ');
1033
1034  namespace = [self _parseNamespace];
1035  if (namespace)
1036    [result_ addObject:namespace forKey:@"personal"];
1037  _consume(self, 1);
1038  namespace = [self _parseNamespace];
1039  if (namespace)
1040    [result_ addObject:namespace forKey:@"other users"];
1041  _consume(self, 1);
1042  namespace = [self _parseNamespace];
1043  if (namespace)
1044    [result_ addObject:namespace forKey:@"shared"];
1045  _consume(self, 1); /* \n */
1046
1047  return YES;
1048}
1049
1050- (BOOL)_parseACLResponseIntoHashMap:(NGMutableHashMap *)result_ {
1051  /*
1052    21 GETACL INBOX
1053    * ACL INBOX test.et.di.cete-lyon lrswipcda helge lrwip "a group" lrs fred ""
1054  */
1055  NSString       *uid;
1056  NSString       *userRights;
1057  NSString       *mailbox;
1058  NSMutableArray *uids;
1059  NSMutableArray *rights;
1060  NSDictionary   *result;
1061
1062  if (!_matchesString(self, "ACL "))
1063    return NO;
1064  _consume(self, 4);
1065
1066  if ((mailbox = _parseBodyString(self, YES)) != nil)
1067    [result_ setObject:mailbox forKey:@"mailbox"];
1068  _consumeIfMatch(self, ' ');
1069
1070  uids   = [[NSMutableArray alloc] initWithCapacity:8];
1071  rights = [[NSMutableArray alloc] initWithCapacity:8];
1072
1073  while (_la(self, 0) != '\n') {
1074    if (_la(self, 0) == '"') {
1075      uid = [self _parseQuotedString];
1076      _consumeIfMatch(self, ' ');
1077    }
1078    else
1079      uid = _parseUntil(self, ' ' );
1080
1081    if (_la(self, 0) == '"')
1082      userRights = [self _parseQuotedString];
1083    else
1084      userRights = _parseUntil2(self, ' ', '\n');
1085    [self _consumeOptionalSpace];
1086
1087    [uids addObject:uid];
1088    [rights addObject:userRights];
1089  }
1090  _consume(self,1);
1091
1092  result = [[NSDictionary alloc] initWithObjects:rights forKeys:uids];
1093  [result_ addObject:result forKey:@"acl"];
1094
1095  [uids   release]; uids   = nil;
1096  [rights release]; rights = nil;
1097  [result release]; result = nil;
1098  return YES;
1099}
1100
1101- (BOOL)_parseMyRightsResponseIntoHashMap:(NGMutableHashMap *)result_ {
1102  /*
1103    Raw Sample (Cyrus):
1104      18 myrights INBOX
1105      * MYRIGHTS INBOX lrswipcda
1106      18 OK Completed
1107  */
1108  NSString *rights;
1109  id obj;
1110
1111  if (!_matchesString(self, "MYRIGHTS "))
1112    return NO;
1113  _consume(self, 9);
1114
1115  if ((obj = _parseBodyString(self, YES)) != nil)
1116    [result_ setObject:obj forKey:@"mailbox"];
1117  _consumeIfMatch(self, ' ');
1118
1119  rights = _parseUntil(self, '\n');
1120  [result_ setObject:rights forKey:@"myrights"];
1121  return YES;
1122}
1123
1124- (BOOL)_parseListRightsResponseIntoHashMap:(NGMutableHashMap *)result_ {
1125  /*
1126    Raw Sample (Cyrus):
1127      22 LISTRIGHTS INBOX helge
1128      * LISTRIGHTS INBOX helge "" l r s w i p c d a 0 1 2 3 4 5 6 7 8 9
1129      22 OK Completed
1130 */
1131  NSString *rights;
1132  id obj;
1133
1134  if (!_matchesString(self, "LISTRIGHTS "))
1135    return NO;
1136  _consume(self, 11);
1137
1138  if ((obj = _parseBodyString(self, YES)) != nil)
1139    [result_ setObject:obj forKey:@"mailbox"];
1140  _consumeIfMatch(self, ' ');
1141
1142  if ((obj = _parseBodyString(self, YES)) != nil)
1143    [result_ setObject:obj forKey:@"uid"];
1144  _consumeIfMatch(self, ' ');
1145
1146  if ((obj = _parseUntil(self, ' ')) != nil) {
1147    if ([obj isEqual:@"\"\""])
1148      obj = @"";
1149    [result_ setObject:obj forKey:@"requiredRights"];
1150  }
1151
1152  rights = _parseUntil(self, '\n');
1153  [result_ setObject:[rights componentsSeparatedByString:@" "]
1154	   forKey:@"listrights"];
1155  return YES;
1156}
1157
1158- (BOOL)_parseSearchResponseIntoHashMap:(NGMutableHashMap *)result_ {
1159  NSMutableArray *msn = nil;
1160  NSNumber *n;
1161
1162  if (!_matchesString(self, "SEARCH"))
1163    return NO;
1164
1165  _consume(self, 6);
1166
1167  msn = [NSMutableArray arrayWithCapacity:128];
1168
1169  while (_la(self, 0) == ' ') {
1170      _consume(self, 1);
1171      n = _parseUnsigned(self);
1172      if (n)
1173	[msn addObject:n];
1174  }
1175  _parseUntil(self, '\n');
1176  [result_ addObject:msn forKey:@"search"];
1177  return YES;
1178}
1179
1180- (BOOL)_parseSortResponseIntoHashMap:(NGMutableHashMap *)result_ {
1181  NSMutableArray *msn = nil;
1182
1183  if (!_matchesString(self, "SORT"))
1184    return NO;
1185
1186  _consume(self, 4);
1187
1188  msn = [NSMutableArray arrayWithCapacity:128];
1189
1190  while (_la(self, 0) == ' ') {
1191    _consume(self, 1);
1192    if (_la(self, 0) == '(') {
1193      _consume(self, 1);
1194      if (!_matchesString(self, "MODSEQ "))
1195        return NO;
1196      _consume(self, 7);
1197      [result_ addObject:_parseUnsigned(self) forKey:@"modseq"];
1198      _consume(self, 1); /* final ')' */
1199    }
1200    else
1201      [msn addObject:_parseUnsigned(self)];
1202  }
1203  _parseUntil(self, '\n');
1204  [result_ addObject:msn forKey:@"sort"];
1205  return YES;
1206}
1207
1208- (BOOL)_parseQuotaResponseIntoHashMap:(NGMutableHashMap *)result_ {
1209  NSString            *qRoot;
1210  NSMutableDictionary *parse;
1211  NSMutableDictionary *quota;
1212
1213  if (!_matchesString(self, "QUOTA "))
1214    return NO;
1215
1216  _consume(self, 6);
1217
1218  quota = [result_ objectForKey:@"quota"];
1219
1220  if (quota == nil) {
1221      quota = [NSMutableDictionary dictionaryWithCapacity:2];
1222      [result_ setObject:quota forKey:@"quota"];
1223  }
1224
1225  parse = [NSMutableDictionary dictionaryWithCapacity:3];
1226  if (_la(self, 0) == '"') {
1227    NSMutableString *str;
1228    _consume(self, 1);
1229    str = [NSMutableString stringWithFormat: @"\"%@\"", _parseUntil(self, '"')];
1230    qRoot = str;
1231  }
1232  else {
1233    qRoot = _parseUntil2(self, ' ', '\n');
1234  }
1235
1236  if (_la(self, 0) == ' ') {
1237      _consume(self, 1);
1238
1239      if (_la(self, 0) == '(') {
1240        _consume(self,1);
1241        if (_la(self, 0) == ')') { /* empty quota response */
1242          _consume(self,1);
1243        }
1244        else {
1245          NSString *key;
1246
1247          key = _parseUntil(self, ' ');
1248          key = [key lowercaseString];
1249          if ([key isEqualToString:@"storage"]) {
1250            NSString *used, *max;
1251
1252            used = _parseUntil(self, ' ');
1253            max  = _parseUntil(self, ')');
1254
1255            [parse setObject:used forKey:@"usedSpace"];
1256            [parse setObject:max  forKey:@"maxQuota"];
1257          }
1258          else {
1259            NSString *v;
1260
1261            v = _parseUntil(self, ')');
1262
1263            [parse setObject:v forKey:@"resource"];
1264          }
1265        }
1266      }
1267      [quota setObject:parse forKey:qRoot];
1268  }
1269  _parseUntil(self, '\n');
1270
1271  return YES;
1272}
1273
1274- (BOOL)_parseQuotaRootResponseIntoHashMap:(NGMutableHashMap *)result_ {
1275  NSString *folderName, *folderRoot;
1276  NSMutableDictionary *dict;
1277
1278  if (!_matchesString(self, "QUOTAROOT "))
1279    return NO;
1280
1281  _consume(self, 10);
1282
1283  dict = [result_ objectForKey:@"quotaRoot"];
1284
1285  if (!dict) {
1286    dict = [NSMutableDictionary dictionaryWithCapacity:2];
1287    [result_ setObject:dict forKey:@"quotaRoot"];
1288  }
1289  if (_la(self, 0) == '"') {
1290    _consume(self , 1);
1291    folderName = _parseUntil(self, '"');
1292  }
1293  else {
1294    folderName = _parseUntil2(self, '\n', ' ');
1295  }
1296  if (_la(self, 0) == ' ') {
1297    _consume(self, 1);
1298    folderRoot = _parseUntil(self, '\n');
1299  }
1300  else {
1301    _consume(self, 1);
1302    folderRoot = nil;
1303  }
1304  if ([folderName isNotEmpty] && [folderRoot isNotEmpty])
1305    [dict setObject:folderRoot forKey:folderName];
1306
1307  return YES;
1308}
1309
1310- (NSArray *)_parseThread {
1311  NSMutableArray *array;
1312  NSNumber       *msg;
1313
1314  array = [NSMutableArray arrayWithCapacity:64];
1315
1316  if (_la(self, 0) == '(')
1317    _consume(self, 1);
1318
1319  while (1) {
1320    if (_la(self, 0) == '(') {
1321      NSArray *a;
1322
1323      a = [self _parseThread];
1324      if (a != nil) [array addObject:a];
1325    }
1326    else if ((msg = _parseUnsigned(self))) {
1327      [array addObject:msg];
1328    }
1329    else {
1330      return nil;
1331    }
1332    if (_la(self, 0) == ')')
1333      break;
1334    else if (_la(self, 0) == ' ')
1335      _consume(self, 1);
1336  }
1337  _consumeIfMatch(self, ')');
1338  return array;
1339}
1340
1341
1342- (BOOL)_parseThreadResponseIntoHashMap:(NGMutableHashMap *)result_ {
1343  NSMutableArray *msn;
1344
1345  if (!_matchesString(self, "THREAD"))
1346    return NO;
1347
1348  _consume(self, 6);
1349
1350  if (_la(self, 0) == ' ') {
1351    _consume(self, 1);
1352  }
1353
1354  msn = [NSMutableArray arrayWithCapacity:64];
1355
1356  while ((_la(self, 0) == '(')) {
1357    NSArray *array;
1358
1359    if ((array = [self _parseThread]) != nil)
1360      [msn addObject:array];
1361  }
1362  _parseUntil(self, '\n');
1363  [result_ addObject:msn forKey:@"thread"];
1364  return YES;
1365}
1366
1367- (BOOL)_parseVanishedResponseIntoHashMap:(NGMutableHashMap *)result_ {
1368  // VANISHED (EARLIER) 1:53,55:56,58:113,115,120,126,128'
1369  NSMutableArray *uids;
1370  NSNumber *uid;
1371  NSUInteger count, max;
1372
1373  if (!_matchesString(self, "VANISHED"))
1374    return NO;
1375
1376  _consume(self, 8);
1377
1378  if (_la(self, 0) == ' ') {
1379    _consume(self, 1);
1380  }
1381
1382  if (_la(self, 0) == '(') {
1383    _consume(self, 1);
1384    if (!_matchesString(self, "EARLIER"))
1385      return NO;
1386    _consume(self, 7); /* EARLIER */
1387    _consumeIfMatch(self, ')');
1388    if (_la(self, 0) == ' ') {
1389      _consume(self, 1);
1390    }
1391  }
1392
1393  uids = [NSMutableArray new];
1394
1395  while ((_la(self, 0) != '\n')) {
1396    uid = _parseUnsigned(self);
1397    [uids addObject:uid];
1398    if (_la(self, 0) == ':') {
1399      _consume(self, 1);
1400      count = [uid unsignedIntValue] + 1;
1401      uid = _parseUnsigned(self);
1402      max = [uid unsignedIntValue];
1403      while (count < max) {
1404        [uids addObject: [NSNumber numberWithUnsignedInt: count]];
1405        count++;
1406      }
1407      [uids addObject: uid];
1408    }
1409    if (_la(self, 0) == ',') {
1410      _consume(self, 1);
1411    }
1412  }
1413  _consume(self, 1);
1414  [result_ addObject:uids forKey:@"vanished"];
1415  [uids release];
1416
1417  return YES;
1418}
1419
1420- (BOOL)_parseStatusResponseIntoHashMap:(NGMutableHashMap *)result_ {
1421  NSString            *name  = nil;
1422  NSMutableDictionary *flags = nil;
1423  NSDictionary *d;
1424
1425  if (!_matchesString(self, "STATUS "))
1426    return NO;
1427
1428  _consume(self, 7);
1429
1430  if (_la(self, 0) == '"') {
1431    name = [self _parseQuotedString];
1432//     _consume(self, 1);
1433//     name = _parseUntil(self, '"');
1434    _consumeIfMatch(self, ' ');
1435  }
1436  else if (_la(self, 0) == '{') {
1437    name = [self _parseQuotedStringOrNIL];
1438    _consumeIfMatch(self, ' ');
1439  }
1440  else {
1441    name = _parseUntil(self, ' ');
1442  }
1443  _consumeIfMatch(self, '(');
1444  flags = [NSMutableDictionary dictionaryWithCapacity:8];
1445
1446  while (_la(self, 0) != ')') {
1447    NSString *key   = _parseUntil(self, ' ');
1448    id       value = _parseUntil2(self, ' ', ')');
1449
1450    if (_la(self, 0) == ' ')
1451      _consume(self, 1);
1452
1453    if ([[key lowercaseString] isEqualToString:@"x-guid"])
1454    [flags setObject:value
1455          forKey:[key lowercaseString]];
1456    else
1457    [flags setObject:[NumClass numberWithInt:[value intValue]]
1458          forKey:[key lowercaseString]];
1459  }
1460  _consumeIfMatch(self, ')');
1461  _parseUntil(self, '\n');
1462
1463  d = [[NSDictionary alloc] initWithObjectsAndKeys:
1464			      name,  @"folderName",
1465			      flags, @"flags", nil];
1466  [result_ addObject:d forKey:@"status"];
1467  [d release];
1468  return YES;
1469}
1470
1471- (BOOL)_parseAnnotationResponseIntoHashMap:(NGMutableHashMap *)result_ {
1472  NSString            *name  = nil;
1473  NSString            *entry  = nil;
1474  NSMutableDictionary *attributes = nil;
1475  NSMutableDictionary *d, *f;
1476
1477  if (!_matchesString(self, "ANNOTATION "))
1478    return NO;
1479
1480  _consume(self, 11);
1481
1482  if (_la(self, 0) == '"') {
1483    name = [self _parseQuotedString];
1484    _consumeIfMatch(self, ' ');
1485  }
1486  else if (_la(self, 0) == '{') {
1487    name = [self _parseQuotedStringOrNIL];
1488    _consumeIfMatch(self, ' ');
1489  }
1490  else {
1491    name = _parseUntil(self, ' ');
1492  }
1493
1494  if (_la(self, 0) == '"') {
1495    entry = [self _parseQuotedString];
1496    _consumeIfMatch(self, ' ');
1497  }
1498  else if (_la(self, 0) == '{') {
1499    entry = [self _parseQuotedStringOrNIL];
1500    _consumeIfMatch(self, ' ');
1501  }
1502  else {
1503    entry = _parseUntil(self, ' ');
1504  }
1505
1506  _consumeIfMatch(self, '(');
1507
1508  attributes = [NSMutableDictionary dictionaryWithCapacity:2];
1509  d = [NSMutableDictionary dictionaryWithCapacity:2];
1510  f = [NSMutableDictionary dictionaryWithCapacity:2];
1511
1512  while (_la(self, 0) != ')') {
1513    NSString *key   = [self _parseQuotedString];
1514    _consume(self, 1); // ' '
1515    NSString *value = [self _parseQuotedStringOrNIL];
1516
1517    if (_la(self, 0) == ' ')
1518      _consume(self, 1);
1519
1520    if (value)
1521      {
1522	[attributes setObject:value
1523		       forKey:[key lowercaseString]];
1524
1525	[d setObject:[NSMutableDictionary dictionaryWithDictionary:attributes]
1526	      forKey:entry];
1527      }
1528  }
1529  _consumeIfMatch(self, ')');
1530  _parseUntil(self, '\n');
1531
1532  [f setObject:d forKey:name];
1533  [result_ addObject:f forKey:@"FolderList"];
1534
1535  return YES;
1536}
1537
1538
1539- (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_ {
1540  NSString *reason;
1541
1542  if (!_matchesString(self, "BYE "))
1543    return NO;
1544
1545  _consume(self, 4);
1546  reason = _parseUntil(self, '\n');
1547  [result_ addObject:reason forKey:@"bye"];
1548  return YES;
1549}
1550
1551- (id)_decodeQP:(id)_string headerField:(NSString *)_field {
1552  if (![_string isNotNull])
1553    return _string;
1554
1555  if ([_string isKindOfClass:DataClass])
1556    return [_string decodeQuotedPrintableValueOfMIMEHeaderField:_field];
1557
1558  if ([_string isKindOfClass:StrClass]) {
1559    if ([_string length] <= 6 /* minimum size */)
1560      return _string;
1561
1562    if ([_string rangeOfString:@"=?"].length > 0) {
1563      NSData *data;
1564
1565      if (debugOn)
1566	[self debugWithFormat:@"WARNING: string with quoted printable info!"];
1567
1568      // TODO: this is really expensive ...
1569      data = [_string dataUsingEncoding:NSUTF8StringEncoding];
1570      if (data != nil) {
1571	NSData *qpData;
1572
1573	qpData = [data decodeQuotedPrintableValueOfMIMEHeaderField:_field];
1574	if (qpData != data) return qpData;
1575      }
1576    }
1577    return _string;
1578  }
1579
1580  return _string;
1581}
1582
1583- (NGImap4EnvelopeAddress *)_parseEnvelopeAddressStructure {
1584  /*
1585     Note: returns retained object!
1586
1587     Order:
1588       personal name
1589       SMTP@at-domain-list(source route)
1590       mailbox name
1591       hostname
1592     eg:
1593       ("Helge Hess" NIL "helge.hess" "opengroupware.org")
1594  */
1595  NGImap4EnvelopeAddress *address = nil;
1596  NSString *pname, *route, *mailbox, *host;
1597
1598  if (_la(self, 0) != '(') {
1599    if (_matchesString(self, "NIL")) {
1600      _consume(self, 3);
1601      return (id)[null retain];
1602    }
1603    return nil;
1604  }
1605  _consume(self, 1); // '('
1606
1607  /* parse personal name, can be with quoted printable encoding! */
1608
1609  pname = [self _parseQuotedStringOrNIL];
1610  if ([pname isNotNull]) // TODO: headerField 'subject'?? explain!
1611    pname = [self _decodeQP:pname headerField:@"subject"];
1612  [self _consumeOptionalSpace];
1613
1614  // TODO: I think those forbid QP encoding?
1615  route   = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace];
1616  mailbox = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace];
1617  host    = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace];
1618
1619  if (_la(self, 0) != ')') {
1620    [self logWithFormat:@"WARNING: IMAP4 envelope "
1621	    @"address not properly closed (c0=%c,c1=%c): %@",
1622	    _la(self, 0), _la(self, 1), self->serverResponseDebug];
1623  }
1624  else
1625    _consume(self, 1);
1626
1627  if ([pname isNotNull] || [route isNotNull] || [mailbox isNotNull] || [host isNotNull])
1628    address = [[NGImap4EnvelopeAddress alloc] initWithPersonalName:pname
1629                                                       sourceRoute:route
1630                                                           mailbox:mailbox
1631                                                              host:host];
1632
1633  return address;
1634}
1635
1636- (NSArray *)_parseEnvelopeAddressStructures {
1637  /*
1638    Parses an array of envelopes, most common:
1639      ((NIL NIL "users-admin" "opengroupware.org"))
1640    (just one envelope in the array)
1641  */
1642  NSMutableArray *ma;
1643
1644  if (_la(self, 0) != '(') {
1645    if (_matchesString(self, "NIL")) {
1646      _consume(self, 3);
1647      return (id)[null retain];
1648    }
1649    return nil;
1650  }
1651  _consume(self, 1); // '('
1652
1653  ma = nil;
1654  while (_la(self, 0) != ')') {
1655    NGImap4EnvelopeAddress *address;
1656
1657    if ((address = [self _parseEnvelopeAddressStructure]) == nil) {
1658      [self _consumeOptionalSpace];
1659      continue; // TODO: should we stop parsing?
1660    }
1661    if (![address isNotNull])
1662      continue;
1663
1664    if (ma == nil) ma = [NSMutableArray arrayWithCapacity:4];
1665    [ma addObject:address];
1666    [address release]; /* the parse returns a retained object! */
1667  }
1668
1669  if (_la(self, 0) != ')') {
1670    [self logWithFormat:
1671	    @"WARNING: IMAP4 envelope address not properly closed!"];
1672  }
1673  else
1674    _consume(self, 1);
1675  return ma;
1676}
1677
1678- (id)_parseEnvelope {
1679  /*
1680    http://www.hunnysoft.com/rfc/rfc3501.html
1681
1682    envelope = "(" env-date SP env-subject SP env-from SP env-sender SP
1683	           env-reply-to SP env-to SP env-cc SP env-bcc SP
1684		   env-in-reply-to SP env-message-id ")"
1685
1686    * 1189 FETCH (UID 1189 ENVELOPE
1687       ("Tue, 22 Jun 2004 08:42:01 -0500" ""
1688        (("Jeff Glaspie" NIL "jeff" "glaspie.org"))
1689        (("Jeff Glaspie" NIL "jeff" "glaspie.org"))
1690        (("Jeff Glaspie" NIL "jeff" "glaspie.org"))
1691        ((NIL NIL "helge.hess" "opengroupware.org"))
1692        NIL NIL NIL
1693        "<20040622134354.F11133CEB14@mail.opengroupware.org>"
1694       )
1695      )
1696  */
1697  static NGMimeRFC822DateHeaderFieldParser *dateParser = nil;
1698  NGImap4Envelope *env;
1699  NSString        *dateStr;
1700  id tmp;
1701
1702  if (dateParser == nil)
1703    dateParser = [[NGMimeRFC822DateHeaderFieldParser alloc] init];
1704
1705  if (_la(self, 0) != '(')
1706    return nil;
1707  _consume(self, 1);
1708
1709  env = [[[NGImap4Envelope alloc] init] autorelease];
1710
1711  /* parse date */
1712
1713  dateStr = [self _parseQuotedStringOrNIL];
1714  [self _consumeOptionalSpace];
1715  if ([dateStr isNotNull])
1716    env->date = [[dateParser parseValue:dateStr ofHeaderField:nil] retain];
1717
1718  /* parse subject */
1719
1720  if ((tmp = [self _parseQuotedStringOrDataOrNIL]) != nil) {
1721    // TODO: that one is an issue, the client does know the requested charset
1722    //       but doesn't pass it down to the parser? Requiring the client to
1723    //       deal with NSData's is a bit overkill?
1724    env->subject = [tmp isNotNull]
1725      ? [[self _decodeQP:tmp headerField:@"subject"] copy]
1726      : nil;
1727    [self _consumeOptionalSpace];
1728  }
1729  else {
1730    [self logWithFormat:@"ERROR(%s): failed on subject(%c): %@",
1731	  __PRETTY_FUNCTION__, _la(self, 0), self->serverResponseDebug];
1732    return nil;
1733  }
1734
1735  /* parse addresses */
1736
1737  if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1738    env->from = [tmp isNotNull] ? [tmp copy] : nil;
1739    [self _consumeOptionalSpace];
1740  }
1741  else {
1742    [self logWithFormat:@"ERROR(%s): failed on from.", __PRETTY_FUNCTION__];
1743    return nil;
1744  }
1745  if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1746    env->sender = [tmp isNotNull] ? [[tmp lastObject] copy] : nil;
1747    [self _consumeOptionalSpace];
1748  }
1749  else {
1750    [self logWithFormat:@"ERROR(%s): failed on sender.", __PRETTY_FUNCTION__];
1751    return nil;
1752  }
1753  if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1754    env->replyTo = [tmp isNotNull] ? [tmp copy] : nil;
1755    [self _consumeOptionalSpace];
1756  }
1757
1758  if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1759    env->to = [tmp isNotNull] ? [tmp copy] : nil;
1760    [self _consumeOptionalSpace];
1761  }
1762  if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1763    env->cc = [tmp isNotNull] ? [tmp copy] : nil;
1764    [self _consumeOptionalSpace];
1765  }
1766  if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1767    env->bcc = [tmp isNotNull] ? [tmp copy] : nil;
1768    [self _consumeOptionalSpace];
1769  }
1770
1771  if ((tmp = [self _parseQuotedStringOrNIL])) {
1772    env->inReplyTo = [tmp isNotNull] ? [tmp copy] : nil;
1773    [self _consumeOptionalSpace];
1774  }
1775  if ((tmp = [self _parseQuotedStringOrNIL])) {
1776    env->msgId = [tmp isNotNull] ? [tmp copy] : nil;
1777    [self _consumeOptionalSpace];
1778  }
1779
1780  if (_la(self, 0) != ')') {
1781    [self logWithFormat:@"WARNING: IMAP4 envelope not properly closed"
1782	    @" (c0=%c,c1=%c): %@",
1783	    _la(self, 0), _la(self, 1), self->serverResponseDebug];
1784  }
1785  else
1786    _consume(self, 1);
1787
1788  return env;
1789}
1790
1791- (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_ {
1792  NSMutableDictionary *fetch;
1793  NSNumber *number;
1794  NSString *key    = nil;
1795
1796  if ((number = _parseUnsigned(self)) == nil)
1797    return NO;
1798
1799  _consumeIfMatch(self, ' ');
1800
1801  if (!_matchesString(self, "FETCH ")) {
1802    /* got a number request from select like  exists or recent */
1803    key = _parseUntil(self, '\n');
1804    [result_ addObject:number forKey:[key lowercaseString]];
1805    return YES;
1806  }
1807
1808  /* eg: "FETCH (FLAGS (\Seen) UID 5 RFC822.HEADER {2903}" */
1809  fetch = [[NSMutableDictionary alloc] initWithCapacity:10];
1810
1811  _consume(self, 6); /* "FETCH " */
1812  _consumeIfMatch(self, '(');
1813  while (_la(self, 0) != ')') { /* until closing parent */
1814    NSString *key;
1815
1816    key = [_parseUntil(self, ' ') lowercaseString];
1817#if 0
1818    [self logWithFormat:@"PARSE KEY: %@", key];
1819#endif
1820    if ([key hasPrefix:@"body[header.fields"]) {
1821      NSData *content;
1822
1823      if ((content = [self _parseBodyHeaderFields]) != nil)
1824	[fetch setObject:content forKey:key];
1825      else
1826	[self logWithFormat:@"ERROR: got no body content for key: '%@'",key];
1827    }
1828    else if ([key hasPrefix:@"body["]) {
1829      NSDictionary *content;
1830
1831      if ((content = [self _parseBodyContent]) != nil)
1832	[fetch setObject:content forKey:key];
1833      else
1834	[self logWithFormat:@"ERROR: got no body content for key: '%@'",key];
1835    }
1836    else if ([key isEqualToString:@"body"]) {
1837      [fetch setObject:_parseBody(self, NO) forKey:key];
1838    }
1839    else if ([key isEqualToString:@"bodystructure"]) {
1840      [fetch setObject:_parseBody(self, YES) forKey:key];
1841    }
1842    else if ([key isEqualToString:@"flags"]) {
1843      [fetch setObject:_parseFlagArray(self) forKey:key];
1844    }
1845    else if ([key isEqualToString:@"uid"]) {
1846      [fetch setObject:_parseUnsigned(self) forKey:key];
1847    }
1848    else if ([key isEqualToString:@"modseq"]) {
1849      _consumeIfMatch(self, '(');
1850      [fetch setObject:_parseUnsigned(self) forKey:key];
1851      _consumeIfMatch(self, ')');
1852    }
1853    else if ([key isEqualToString:@"rfc822.size"]) {
1854      [fetch setObject:_parseUnsigned(self) forKey:key];
1855    }
1856    else if ([key hasPrefix:@"rfc822"]) {
1857      NSData *data;
1858
1859      if (_la(self, 0) == '"') {
1860	NSString *str;
1861	_consume(self,1);
1862
1863	str = _parseUntil(self, '"');
1864	data = [str dataUsingEncoding:defCStringEncoding];
1865      }
1866      else
1867	data = [self _parseData];
1868
1869      if (data != nil) [fetch setObject:data forKey:key];
1870    }
1871    else if ([key isEqualToString:@"envelope"]) {
1872      id envelope;
1873
1874      if ((envelope = [self _parseEnvelope]) != nil)
1875	[fetch setObject:envelope forKey:key];
1876      else
1877	[self logWithFormat:@"ERROR: could not parse envelope!"];
1878    }
1879    else if ([key isEqualToString:@"internaldate"]) {
1880      // TODO: implement!
1881      NSException *e;
1882
1883      e = [[NGImap4ParserException alloc]
1884	    initWithFormat:@"INTERNALDATE fetch result not yet supported!"];
1885      [self setLastException:[e autorelease]];
1886      return NO;
1887    }
1888    else {
1889      NSException *e;
1890
1891      e = [[NGImap4ParserException alloc] initWithFormat:
1892					    @"unsupported fetch key: %@",
1893					  key];
1894      [self setLastException:[e autorelease]];
1895      return NO;
1896    }
1897
1898    if (_la(self, 0) == ' ')
1899      _consume(self, 1);
1900  }
1901  if (fetch != nil) {
1902    [fetch setObject:number  forKey:@"msn"];
1903    [result_ addObject:fetch forKey:@"fetch"];
1904    _consume(self, 1); /* consume ')' */
1905    _consumeIfMatch(self, '\n');
1906  }
1907  else { /* no correct fetch line */
1908    _parseUntil(self, '\n');
1909  }
1910
1911  [fetch release]; fetch = nil;
1912  return YES;
1913  }
1914
1915static BOOL _parseGreetingsSieveResponse(NGImap4ResponseParser *self,
1916                                         NGMutableHashMap *result_)
1917{
1918  BOOL isOK;
1919
1920  while (!(isOK = _parseOkSieveResponse(self, result_))) {
1921    NSString *key, *value;
1922
1923    if (!(key = _parseStringSieveResponse(self))) {
1924      break;
1925    }
1926    if (_la(self, 0) == ' ') {
1927      _consume(self, 1);
1928
1929      if (!(value = _parseStringSieveResponse(self))) {
1930        break;
1931      }
1932    }
1933    else {
1934      value = @"";
1935    }
1936    _parseUntil(self, '\n');
1937    [result_ addObject:value forKey:[key lowercaseString]];
1938  }
1939
1940  return isOK;
1941}
1942
1943static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self,
1944				    NGMutableHashMap *result_)
1945{
1946  NSString *str;
1947  NSData   *data;
1948
1949  if ((data = [self _parseData]) == nil)
1950    return NO;
1951
1952  str = [[StrClass alloc] initWithData:data encoding:NSUTF8StringEncoding];
1953  [result_ setObject:str forKey:@"data"];
1954  [str release]; str = nil;
1955
1956  _parseUntil(self, '\n');
1957
1958  return YES;
1959}
1960
1961static BOOL _parseOkSieveResponse(NGImap4ResponseParser *self,
1962                                  NGMutableHashMap *result_)
1963{
1964  if (!((_la(self, 0) == 'O') && (_la(self, 1) == 'K')))
1965    return NO;
1966
1967  _consume(self, 2);
1968
1969  if (_la(self, 0) == ' ') {
1970      NSString *reason;
1971
1972      if ((reason = _parseContentSieveResponse(self)))
1973        [result_ addObject:reason forKey:@"reason"];
1974  }
1975  _parseUntil(self, '\n');
1976
1977  [result_ addObject:YesNum forKey:@"ok"];
1978  return YES;
1979}
1980
1981static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self,
1982                                  NGMutableHashMap *result_)
1983{
1984  NSString *code, *data;
1985
1986  if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' ')))
1987    return NO;
1988
1989  _consume(self, 3);
1990
1991  if (_la(self, 0) == '(')
1992    {
1993      _consume(self, 1);
1994      code = _parseUntil(self, ')');
1995      if (_la(self, 0) == ' ') _consume(self, 1);
1996      if (code) [result_ addObject:code forKey:@"code"];
1997    }
1998
1999  data = _parseContentSieveResponse(self);
2000
2001  [result_ addObject:NoNum forKey:@"ok"];
2002  if (data) [result_ addObject:data forKey:@"reason"];
2003
2004  _parseUntil(self, '\n');
2005
2006  return YES;
2007}
2008
2009static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self) {
2010  NSString *str;
2011  NSData *data;
2012
2013  if ((str = _parseStringSieveResponse(self)))
2014    return str;
2015
2016  if ((data = [self _parseData]) == nil)
2017    return nil;
2018
2019  return [[[StrClass alloc] initWithData:data encoding:NSUTF8StringEncoding]
2020                          autorelease];
2021}
2022
2023static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self) {
2024  if (_la(self, 0) != '"')
2025    return nil;
2026
2027  _consume(self, 1);
2028  return _parseUntil(self, '"');
2029}
2030
2031static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
2032                                        BOOL _convertString,
2033                                        BOOL _decode)
2034{
2035  NSString *str;
2036
2037  if (_la(self, 0) == '"') {
2038    // TODO: can the " be escaped somehow?
2039    _consume(self, 1);
2040    str = _parseUntil(self, '"');
2041  }
2042  else if (_la(self, 0) == '{') {
2043    NSData *data;
2044
2045    if (debugDataOn) [self logWithFormat:@"parse body decode string"];
2046    data = [self _parseData];
2047
2048    if (_decode)
2049      data = [data decodeQuotedPrintableValueOfMIMEHeaderField:nil];
2050
2051    if ([data isKindOfClass: [NSString class]])
2052      return (NSString *) data;
2053    else
2054      {
2055	NSString *s;
2056
2057	// Let's try with the supplied encoding. If it doesn't work,
2058	// we'll then try UTF-8 or fallback to ISO-8859-1
2059	s = [[StrClass alloc] initWithData:data encoding:encoding];
2060
2061	if (!s && encoding != NSUTF8StringEncoding)
2062	  s = [[StrClass alloc] initWithData:data encoding:NSUTF8StringEncoding];
2063
2064	if (!s)
2065	  s = [[StrClass alloc] initWithData:data encoding:NSISOLatin1StringEncoding];
2066
2067	return [s autorelease];
2068      }
2069  }
2070  else {
2071    str = _parseUntil2(self, ' ', ')');
2072  }
2073  if (_convertString) {
2074    if ([[str lowercaseString] isEqualToString:@"nil"])
2075      str = @"";
2076  }
2077  if (_decode) {
2078    id  d;
2079
2080    d = [str dataUsingEncoding:defCStringEncoding];
2081    d = [d decodeQuotedPrintableValueOfMIMEHeaderField:nil];
2082
2083    if ([d isKindOfClass:StrClass])
2084      str = d;
2085    else {
2086      str = [[[StrClass alloc] initWithData:d encoding:encoding]
2087	                       autorelease];
2088    }
2089  }
2090  return str;
2091}
2092
2093static NSString *_parseBodyString(NGImap4ResponseParser *self,
2094                                  BOOL _convertString)
2095{
2096  return _parseBodyDecodeString(self, _convertString, NO /* no decode */);
2097}
2098
2099static NSArray *_parseLanguages(NGImap4ResponseParser *self) {
2100  NSMutableArray *languages;
2101  NSString *language;
2102
2103  languages = [NSMutableArray array];
2104  if (_la(self, 0) == '(') {
2105    while (_la(self, 0) != ')') {
2106      _consume(self,1);
2107      language = _parseBodyString(self, YES);
2108      if ([language length])
2109	[languages addObject: language];
2110    }
2111    _consume(self,1);
2112  }
2113  else {
2114    language = _parseBodyString(self, YES);
2115    if ([language length])
2116      [languages addObject: language];
2117  }
2118
2119  return languages;
2120}
2121
2122static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self)
2123{
2124  NSMutableDictionary *list;
2125
2126  if (_la(self, 0) == '(') {
2127    _consume(self, 1);
2128
2129    list = [NSMutableDictionary dictionaryWithCapacity:4];
2130
2131    while (_la(self,0) != ')') {
2132      NSString *key, *value;
2133
2134      if (_la(self, 0) == ' ')
2135        _consume(self, 1);
2136
2137      key = _parseBodyString(self, YES);
2138      _consumeIfMatch(self, ' ');
2139      value = _parseBodyDecodeString(self, YES, YES);
2140
2141      if (value) [list setObject:value forKey:[key lowercaseString]];
2142    }
2143    _consumeIfMatch(self, ')');
2144  }
2145  else {
2146    NSString *str;
2147    str = _parseBodyString(self, YES);
2148
2149    if ([str isNotEmpty])
2150      NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str);
2151
2152    list = (id)[NSDictionary dictionary];
2153  }
2154  return list;
2155}
2156
2157static NSDictionary *_parseContentDisposition(NGImap4ResponseParser *self)
2158{
2159  NSMutableDictionary *disposition;
2160  NSString *type;
2161
2162  disposition = [NSMutableDictionary dictionary];
2163
2164  if (_la(self, 0) == '(') {
2165    _consume(self, 1);
2166    type = _parseBodyString(self, YES);
2167    [disposition setObject: type forKey: @"type"];
2168    if (_la(self, 0) != ')') {
2169      _consume(self, 1);
2170      [disposition setObject: _parseBodyParameterList(self)
2171		   forKey: @"parameterList"];
2172    }
2173    _consume(self, 1);
2174  }
2175  else
2176    _parseBodyString(self, YES);
2177
2178  return disposition;
2179}
2180
2181static NSArray *_parseAddressStructure(NGImap4ResponseParser *self) {
2182  NSString *personalName, *sourceRoute, *mailboxName, *hostName;
2183
2184  // Handle broken address list - generally sent by Microsoft Exchange.
2185  if (_la(self, 0) == '(') {
2186    _consume(self, 1);
2187
2188    personalName = _parseBodyString(self, YES);
2189    _consumeIfMatch(self, ' ');
2190    sourceRoute = _parseBodyString(self, YES);
2191    _consumeIfMatch(self, ' ');
2192    mailboxName = _parseBodyString(self, YES);
2193    _consumeIfMatch(self, ' ');
2194    hostName = _parseBodyString(self, YES);
2195    _consumeIfMatch(self, ')');
2196
2197    return [NSDictionary dictionaryWithObjectsAndKeys:
2198                           personalName, @"personalName",
2199                         sourceRoute,  @"sourceRoute",
2200                         mailboxName,  @"mailboxName",
2201                         hostName,     @"hostName", nil];
2202  }
2203
2204  _parseUntil(self, ')');
2205
2206  return [NSDictionary dictionary];
2207}
2208
2209static NSArray *_parseParenthesizedAddressList(NGImap4ResponseParser *self) {
2210  NSMutableArray *result;
2211  result = [NSMutableArray arrayWithCapacity:8];
2212
2213  if (_la(self, 0) == '(') {
2214    _consume(self, 1);
2215    while (_la(self, 0) != ')') {
2216      [result addObject:_parseAddressStructure(self)];
2217    }
2218    _consume(self, 1);
2219  }
2220  else {
2221    NSString *str;
2222    str = _parseBodyString(self, YES);
2223
2224    if ([str isNotEmpty])
2225      NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str);
2226
2227    result = (id)[NSArray array];
2228  }
2229  return result;
2230}
2231
2232static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self,
2233				      BOOL isBodyStructure) {
2234  NSString            *type, *subtype, *bodyId, *description,
2235		      *result, *encoding, *bodysize;
2236  NSDictionary        *parameterList;
2237  NSMutableDictionary *dict;
2238  NSArray	      *languages;
2239
2240  type = [_parseBodyString(self, YES) lowercaseString];
2241  _consumeIfMatch(self, ' ');
2242  subtype = [_parseBodyString(self, YES) lowercaseString];
2243  _consumeIfMatch(self, ' ');
2244  parameterList = _parseBodyParameterList(self);
2245  _consumeIfMatch(self, ' ');
2246  bodyId = _parseBodyString(self, YES);
2247  _consumeIfMatch(self, ' ');
2248  description = _parseBodyString(self, YES);
2249  _consumeIfMatch(self, ' ');
2250  encoding = _parseBodyString(self, YES);
2251  _consumeIfMatch(self, ' ');
2252  bodysize = _parseBodyString(self, YES);
2253
2254  dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2255                              type, @"type",
2256                              subtype, @"subtype",
2257                              parameterList, @"parameterList",
2258                              bodyId,        @"bodyId",
2259                              description,   @"description",
2260                              encoding,      @"encoding",
2261                              bodysize,      @"size", nil];
2262
2263  if ([type isEqualToString:@"text"]) {
2264    _consumeIfMatch(self, ' ');
2265    [dict setObject:_parseBodyString(self, YES) forKey:@"lines"];
2266  }
2267  else if ([type isEqualToString:@"message"]
2268	   && [subtype isEqualToString:@"rfc822"]) {
2269    if (_la(self, 0) != ')') {
2270      _consumeIfMatch(self, ' ');
2271      _consumeIfMatch(self, '(');
2272      result = _parseBodyString(self, YES);
2273      if (result == nil) result = @"";
2274      [dict setObject:result forKey:@"date"];
2275      _consumeIfMatch(self, ' ');
2276      result = _parseBodyString(self, YES);
2277      if (result == nil) result = @"";
2278      [dict setObject:result forKey:@"subject"];
2279      _consumeIfMatch(self, ' ');
2280      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"from"];
2281      if (_la(self, 0) == ' ')
2282        _consume(self, 1);
2283      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"sender"];
2284      if (_la(self, 0) == ' ')
2285        _consume(self, 1);
2286      [dict setObject:_parseParenthesizedAddressList(self)
2287            forKey:@"reply-to"];
2288      if (_la(self, 0) == ' ')
2289        _consume(self, 1);
2290      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"to"];
2291      if (_la(self, 0) == ' ')
2292        _consume(self, 1);
2293      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"cc"];
2294      if (_la(self, 0) == ' ')
2295        _consume(self, 1);
2296      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"bcc"];
2297      if (_la(self, 0) == ' ')
2298        _consume(self, 1);
2299      result = _parseBodyString(self, YES);
2300      if (result == nil) result = @"";
2301      [dict setObject:result forKey:@"in-reply-to"];
2302      if (_la(self, 0) == ' ')
2303        _consume(self, 1);
2304      result = _parseBodyString(self, YES);
2305      if (result == nil) result = @"";
2306      [dict setObject:result forKey:@"messageId"];
2307      _consumeIfMatch(self, ')');
2308      if (_la(self, 0) == ' ')
2309        _consume(self, 1);
2310      [dict setObject:_parseBody(self, isBodyStructure) forKey:@"body"];
2311      if (_la(self, 0) == ' ')
2312        _consume(self, 1);
2313      result = _parseBodyString(self, YES);
2314      if (result == nil) result = @"";
2315      [dict setObject:result forKey:@"bodyLines"];
2316    }
2317  }
2318
2319  if (isBodyStructure) {
2320    if (_la(self, 0) != ')') {
2321      _consume(self,1);
2322      [dict setObject: _parseBodyString(self, YES)
2323	    forKey: @"md5"];
2324      if (_la(self, 0) != ')') {
2325	_consume(self,1);
2326	[dict setObject: _parseContentDisposition(self)
2327	      forKey: @"disposition"];
2328	if (_la(self, 0) != ')') {
2329	  _consume(self,1);
2330	  languages = _parseLanguages(self);
2331	  if ([languages count])
2332	    [dict setObject: languages forKey: @"languages"];
2333	  if (_la(self, 0) != ')') {
2334	    _consume(self,1);
2335	    [dict setObject: _parseBodyString(self, YES)
2336		  forKey: @"location"];
2337	  };
2338	};
2339      };
2340    };
2341  }
2342
2343  return dict;
2344}
2345
2346static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self,
2347					 BOOL isBodyStructure) {
2348  NSMutableArray *parts;
2349  NSArray	 *languages;
2350  NSString       *kind;
2351  NSMutableDictionary *dict;
2352
2353  parts = [NSMutableArray arrayWithCapacity:4];
2354
2355  while (_la(self, 0) == '(') {
2356    [parts addObject:_parseBody(self, isBodyStructure)];
2357  }
2358  _consumeIfMatch(self, ' ');
2359  kind = _parseBodyString(self, YES);
2360  dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2361				parts,        @"parts",
2362			      @"multipart", @"type",
2363			      kind        , @"subtype", nil];
2364  if (isBodyStructure) {
2365    if (_la(self, 0) != ')') {
2366      _consume(self,1);
2367      [dict setObject: _parseBodyParameterList(self)
2368	    forKey: @"parameterList"];
2369      if (_la(self, 0) != ')') {
2370	_consume(self,1);
2371	[dict setObject: _parseContentDisposition(self)
2372	      forKey: @"disposition"];
2373	if (_la(self, 0) != ')') {
2374	  _consume(self,1);
2375	  languages = _parseLanguages(self);
2376	  if ([languages count])
2377	    [dict setObject: languages forKey: @"languages"];
2378	  if (_la(self, 0) != ')') {
2379	    _consume(self,1);
2380	    [dict setObject: _parseBodyString(self, YES)
2381		  forKey: @"location"];
2382	  };
2383	};
2384      };
2385    };
2386  }
2387
2388  return dict;
2389}
2390
2391static NSDictionary *_parseBody(NGImap4ResponseParser *self, BOOL isBodyStructure) {
2392  NSDictionary *result;
2393
2394  _consumeIfMatch(self, '(');
2395
2396  if (_la(self, 0) == '(') {
2397    result = _parseMultipartBody(self, isBodyStructure);
2398  }
2399  else {
2400    result = _parseSingleBody(self, isBodyStructure);
2401  }
2402  if (_la(self,0) != ')') {
2403    NSString *str;
2404
2405    str = _parseUntil(self, ')');
2406    NSLog(@"%s: got noparsed content %@", __PRETTY_FUNCTION__,
2407          str);
2408  }
2409  else
2410    _consume(self, 1);
2411
2412  return result;
2413}
2414
2415- (NSDictionary *)_parseBodyContent {
2416  NSData *data;
2417  unsigned char c0;
2418
2419  c0 = _la(self, 0);
2420  if (c0 == '"') {
2421    NSString *str;
2422    _consume(self,1);
2423
2424    str = _parseUntil(self, '"');
2425    data = [str dataUsingEncoding:defCStringEncoding];
2426  }
2427  else if (c0 == 'N' && _matchesString(self, "NIL")) {
2428    _consume(self, 3);
2429    return nil;
2430  }
2431  else
2432    data = [self _parseData];
2433
2434  if (data == nil) {
2435    [self logWithFormat:@"ERROR(%s): got no data.", __PRETTY_FUNCTION__];
2436    return nil;
2437  }
2438  return [NSDictionary dictionaryWithObject:data forKey:@"data"];
2439}
2440
2441
2442static NSArray *_parseFlagArray(NGImap4ResponseParser *self) {
2443  NSString *flags;
2444
2445  _consumeIfMatch(self, '(');
2446
2447  flags = _parseUntil(self, ')');
2448  if (![flags isNotEmpty]) {
2449    static NSArray *emptyArray = nil;
2450    if (emptyArray == nil) emptyArray = [[NSArray alloc] init];
2451    return emptyArray;
2452  }
2453  else
2454    return [[flags lowercaseString] componentsSeparatedByString:@" "];
2455}
2456
2457static BOOL _parseEnabledUntaggedResponse(NGImap4ResponseParser *self,
2458                                          NGMutableHashMap *result_) {
2459  NSMutableArray *extensions;
2460  NSString *extension;
2461
2462  if ((_la(self, 0) == 'E')
2463      && (_la(self, 1) == 'N')
2464      && (_la(self, 2) == 'A')
2465      && (_la(self, 3) == 'B')
2466      && (_la(self, 4) == 'L')
2467      && (_la(self, 5) == 'E')
2468      && (_la(self, 6) == 'D')) {
2469    extensions = [NSMutableArray new];
2470    [result_ setObject: extensions forKey: @"extensions"];
2471    [extensions release];
2472
2473    _consume(self, 7);
2474    while (_la(self, 0) == ' ') {
2475      _consume(self, 1);
2476      extension = _parseUntil2(self, ' ', '\n');
2477      if ([extension length] > 0) {
2478        [extensions addObject: extension];
2479      }
2480    }
2481    _consumeIfMatch(self, '\n');
2482    return YES;
2483  }
2484  return NO;
2485}
2486
2487static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self,
2488                                        NGMutableHashMap *result_) {
2489  if ((_la(self, 0) == 'F')
2490      && (_la(self, 1) == 'L')
2491      && (_la(self, 2) == 'A')
2492      && (_la(self, 3) == 'G')
2493      && (_la(self, 4) == 'S')
2494      && (_la(self, 5) == ' ')) {
2495    _consume(self, 6);
2496    [result_ addObject:_parseFlagArray(self) forKey:@"flags"];
2497    _consumeIfMatch(self, '\n');
2498    return YES;
2499  }
2500  return NO;
2501}
2502
2503static BOOL _parseBadUntaggedResponse(NGImap4ResponseParser *self,
2504                                     NGMutableHashMap *result_)
2505{
2506  if (!((_la(self, 0)=='B') && (_la(self, 1)=='A') && (_la(self, 2)=='D')
2507	&& (_la(self, 3) == ' ')))
2508    return NO;
2509
2510  _consume(self, 4);
2511  [result_ addObject:_parseUntil(self, '\n') forKey:@"bad"];
2512  return YES;
2513}
2514
2515static BOOL _parseNoOrOkArguments(NGImap4ResponseParser *self,
2516                                  NGMutableHashMap *result_, NSString *_key)
2517{
2518  id obj;
2519
2520  obj = nil;
2521
2522  if (_la(self, 0) == '[') {
2523    NSString *key;
2524
2525    _consume(self, 1);
2526    key = _parseUntil2(self, ']', ' ');
2527
2528    /* possible kinds of untagged OK responses are either
2529     * OK [ALERT] System shutdown in 10 minutes
2530     or
2531     * OK [UNSEEN 14]
2532     or
2533     * OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen \*)]
2534     */
2535    if (_la(self, 0) == ']') {
2536
2537      _consume(self, 1);
2538      if (_la(self, 0) == ' ') {
2539        id value;
2540
2541        _consume(self, 1);
2542        value = _parseUntil(self, '\n');
2543        if ([value isNotEmpty]) {
2544          obj = [[NSDictionary alloc]
2545		  initWithObjects:&value forKeys:&key count:1];
2546	}
2547        else
2548          obj = [key retain];
2549      }
2550      else {
2551        obj = [key retain];
2552        _parseUntil(self, '\n');
2553      }
2554    }
2555    else { /* _la(self, 0) should be ' ' */
2556      id value;
2557
2558      value = nil;
2559
2560      _consume(self, 1);
2561      if (_la(self, 0) == '(') {
2562        value = _parseFlagArray(self);
2563        _consume(self, 1); /* consume ']' */
2564      }
2565      else {
2566        value = _parseUntil(self, ']');
2567      }
2568      {
2569        id tmp;
2570
2571        tmp = _parseUntil(self, '\n');
2572
2573        obj = [[NSDictionary alloc] initWithObjectsAndKeys:
2574                            value, key,
2575                            tmp,   @"comment", nil];
2576      }
2577    }
2578  }
2579  else
2580    obj = [_parseUntil(self, '\n') retain];
2581
2582  [result_ addObject:obj forKey:_key];
2583  [obj release];
2584  return YES;
2585}
2586
2587
2588static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self,
2589                                     NGMutableHashMap *result_)
2590{
2591  if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' ')))
2592    return NO;
2593
2594  _consume(self, 3);
2595  return _parseNoOrOkArguments(self, result_, @"no");
2596}
2597
2598static BOOL _parseOkUntaggedResponse(NGImap4ResponseParser *self,
2599                                     NGMutableHashMap *result_)
2600{
2601  if (!((_la(self, 0)=='O') && (_la(self, 1)=='K') && (_la(self, 2)==' ')))
2602    return NO;
2603
2604  _consume(self, 3);
2605  return _parseNoOrOkArguments(self, result_, @"ok");
2606}
2607
2608static NSNumber *_parseUnsigned(NGImap4ResponseParser *self) {
2609  unsigned      n;
2610  unsigned char c;
2611  BOOL     isNumber;
2612
2613  isNumber = NO;
2614  n        = 0;
2615  c        = _la(self, 0);
2616
2617  while ((c >= '0') && (c <= '9')) {
2618    _consume(self, 1);
2619    isNumber = YES;
2620    n        = 10 * n + (c - 48);
2621    c        = _la(self, 0);
2622  }
2623  if (!isNumber)
2624    return nil;
2625
2626  return [NumClass numberWithUnsignedInt:n];
2627}
2628
2629static NSString *_parseUntil(NGImap4ResponseParser *self, char _c) {
2630  /*
2631    Note: this function consumes the stop char (_c)!
2632    normalize \r\n constructions
2633  */
2634  // TODO: optimize!
2635  char            buf[1024], c;
2636  NSMutableString *str;
2637  unsigned        cnt;
2638
2639  cnt = 0;
2640  str = nil;
2641  while ((c = _la(self, 0)) != _c) {
2642    if (c == '\\') {
2643      _consume(self, 1);
2644      c = _la(self, 0);
2645    }
2646    buf[cnt] = c;
2647    _consume(self, 1);
2648    cnt++;
2649    if (cnt == 1024) {
2650      if (str == nil) {
2651        str = (NSMutableString *)
2652          [NSMutableString stringWithCString:buf length:1024];
2653      }
2654      else {
2655        NSString *s;
2656
2657        s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024];
2658        [str appendString:s];
2659        [s release];
2660      }
2661      cnt = 0;
2662    }
2663  }
2664  _consume(self,1); /* consume known stop char */
2665  if (_c == '\n' && cnt > 0) {
2666    if (buf[cnt-1] == '\r')
2667      cnt--;
2668  }
2669
2670  if (str == nil)
2671    return [StrClass stringWithCString:buf length:cnt];
2672  else {
2673    NSString *s, *s2;
2674
2675    s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt];
2676    s2 = [str stringByAppendingString:s];
2677    [s release];
2678    return s2;
2679  }
2680}
2681
2682static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2){
2683  /* _parseUntil2(self, char, char) doesn`t consume the stop-chars */
2684  char            buf[1024], c;
2685  NSMutableString *str;
2686  unsigned        cnt;
2687
2688  cnt = 0;
2689  c   = _la(self, 0);
2690  str = nil;
2691
2692  while ((c != _c1) && (c != _c2)) {
2693    buf[cnt] = c;
2694    _consume(self, 1);
2695    cnt++;
2696    if (cnt == 1024) {
2697      if (str == nil)
2698        str = (NSMutableString *)
2699                 [NSMutableString stringWithCString:buf length:1024];
2700      else {
2701        NSString *s;
2702
2703        s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024];
2704        [str appendString:s];
2705        [s release];
2706      }
2707
2708      cnt = 0;
2709    }
2710    c = _la(self, 0);
2711  }
2712
2713  if (str == nil)
2714    return [StrClass stringWithCString:buf length:cnt];
2715
2716  {
2717    NSString *s, *s2;
2718
2719    s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt];
2720    s2 = [str stringByAppendingString:s];
2721    [s release];
2722    return s2;
2723  }
2724}
2725
2726- (NSException *)exceptionForFailedMatch:(unsigned char)_match
2727  got:(unsigned char)_avail
2728{
2729  NSException *e;
2730
2731  e = [NGImap4ParserException alloc];
2732  if (self->debug) {
2733    e = [e initWithFormat:@"unexpected char <%c> expected <%c> <%@>",
2734	   _avail, _match, self->serverResponseDebug];
2735  }
2736  else {
2737    e = [e initWithFormat:@"unexpected char <%c> expected <%c>",
2738	     _avail, _match];
2739  }
2740  return [e autorelease];
2741}
2742
2743static __inline__ NSException *_consumeIfMatch(NGImap4ResponseParser *self,
2744					       unsigned char _match)
2745{
2746  NSException *e;
2747
2748  if (_la(self,0) == _match) {
2749    _consume(self, 1);
2750    return nil;
2751  }
2752
2753  e = [self exceptionForFailedMatch:_match got:_la(self, 0)];
2754  [self setLastException:e];
2755  return e;
2756}
2757
2758static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt) {
2759  /* Normalize end of line */
2760
2761  if (_cnt == 0)
2762    return;
2763
2764  _cnt +=  (__la(self, _cnt - 1) == '\r') ? 1 : 0;
2765
2766  if (self->debug) {
2767    unsigned cnt;
2768
2769    for (cnt = 0; cnt < _cnt; cnt++) {
2770      NSString *s;
2771      unichar c = _la(self, cnt);
2772
2773      if (c == '\r')
2774	continue;
2775
2776      s = [[StrClass alloc] initWithCharacters:&c length:1];
2777      [self->serverResponseDebug appendString:s];
2778      [s release];
2779
2780      if (c == '\n') {
2781	if ([self->serverResponseDebug lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding] > 2) {
2782            fprintf(stderr, "S[%p]: %s", self,
2783                    [self->serverResponseDebug cStringUsingEncoding:NSISOLatin1StringEncoding]);
2784          }
2785          [self->serverResponseDebug release];
2786          self->serverResponseDebug =
2787            [[NSMutableString alloc] initWithCapacity:512];
2788      }
2789    }
2790  }
2791  [self->buffer consume:_cnt];
2792}
2793
2794@end /* NGImap4ResponseParser */
2795