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 if ([key isEqualToString:@"message"]) {
1259            NSString *used, *max;
1260
1261            used = _parseUntil(self, ' ');
1262            max  = _parseUntil(self, ')');
1263
1264            [parse setObject:used forKey:@"messagesCount"];
1265            [parse setObject:max  forKey:@"maxMessages"];
1266          }
1267          else {
1268            NSString *v;
1269
1270            v = _parseUntil(self, ')');
1271
1272            [parse setObject:v forKey:@"resource"];
1273          }
1274        }
1275      }
1276      [quota setObject:parse forKey:qRoot];
1277  }
1278  _parseUntil(self, '\n');
1279
1280  return YES;
1281}
1282
1283- (BOOL)_parseQuotaRootResponseIntoHashMap:(NGMutableHashMap *)result_ {
1284  NSString *folderName, *folderRoot;
1285  NSMutableDictionary *dict;
1286
1287  if (!_matchesString(self, "QUOTAROOT "))
1288    return NO;
1289
1290  _consume(self, 10);
1291
1292  dict = [result_ objectForKey:@"quotaRoot"];
1293
1294  if (!dict) {
1295    dict = [NSMutableDictionary dictionaryWithCapacity:2];
1296    [result_ setObject:dict forKey:@"quotaRoot"];
1297  }
1298  if (_la(self, 0) == '"') {
1299    _consume(self , 1);
1300    folderName = _parseUntil(self, '"');
1301  }
1302  else {
1303    folderName = _parseUntil2(self, '\n', ' ');
1304  }
1305  if (_la(self, 0) == ' ') {
1306    _consume(self, 1);
1307    folderRoot = _parseUntil(self, '\n');
1308  }
1309  else {
1310    _consume(self, 1);
1311    folderRoot = nil;
1312  }
1313  if ([folderName isNotEmpty] && [folderRoot isNotEmpty])
1314    [dict setObject:folderRoot forKey:folderName];
1315
1316  return YES;
1317}
1318
1319- (NSArray *)_parseThread {
1320  NSMutableArray *array;
1321  NSNumber       *msg;
1322
1323  array = [NSMutableArray arrayWithCapacity:64];
1324
1325  if (_la(self, 0) == '(')
1326    _consume(self, 1);
1327
1328  while (1) {
1329    if (_la(self, 0) == '(') {
1330      NSArray *a;
1331
1332      a = [self _parseThread];
1333      if (a != nil) [array addObject:a];
1334    }
1335    else if ((msg = _parseUnsigned(self))) {
1336      [array addObject:msg];
1337    }
1338    else {
1339      return nil;
1340    }
1341    if (_la(self, 0) == ')')
1342      break;
1343    else if (_la(self, 0) == ' ')
1344      _consume(self, 1);
1345  }
1346  _consumeIfMatch(self, ')');
1347  return array;
1348}
1349
1350
1351- (BOOL)_parseThreadResponseIntoHashMap:(NGMutableHashMap *)result_ {
1352  NSMutableArray *msn;
1353
1354  if (!_matchesString(self, "THREAD"))
1355    return NO;
1356
1357  _consume(self, 6);
1358
1359  if (_la(self, 0) == ' ') {
1360    _consume(self, 1);
1361  }
1362
1363  msn = [NSMutableArray arrayWithCapacity:64];
1364
1365  while ((_la(self, 0) == '(')) {
1366    NSArray *array;
1367
1368    if ((array = [self _parseThread]) != nil)
1369      [msn addObject:array];
1370  }
1371  _parseUntil(self, '\n');
1372  [result_ addObject:msn forKey:@"thread"];
1373  return YES;
1374}
1375
1376- (BOOL)_parseVanishedResponseIntoHashMap:(NGMutableHashMap *)result_ {
1377  // VANISHED (EARLIER) 1:53,55:56,58:113,115,120,126,128'
1378  NSMutableArray *uids;
1379  NSNumber *uid;
1380  NSUInteger count, max;
1381
1382  if (!_matchesString(self, "VANISHED"))
1383    return NO;
1384
1385  _consume(self, 8);
1386
1387  if (_la(self, 0) == ' ') {
1388    _consume(self, 1);
1389  }
1390
1391  if (_la(self, 0) == '(') {
1392    _consume(self, 1);
1393    if (!_matchesString(self, "EARLIER"))
1394      return NO;
1395    _consume(self, 7); /* EARLIER */
1396    _consumeIfMatch(self, ')');
1397    if (_la(self, 0) == ' ') {
1398      _consume(self, 1);
1399    }
1400  }
1401
1402  uids = [NSMutableArray new];
1403
1404  while ((_la(self, 0) != '\n')) {
1405    uid = _parseUnsigned(self);
1406    [uids addObject:uid];
1407    if (_la(self, 0) == ':') {
1408      _consume(self, 1);
1409      count = [uid unsignedIntValue] + 1;
1410      uid = _parseUnsigned(self);
1411      max = [uid unsignedIntValue];
1412      while (count < max) {
1413        [uids addObject: [NSNumber numberWithUnsignedInt: count]];
1414        count++;
1415      }
1416      [uids addObject: uid];
1417    }
1418    if (_la(self, 0) == ',') {
1419      _consume(self, 1);
1420    }
1421  }
1422  _consume(self, 1);
1423  [result_ addObject:uids forKey:@"vanished"];
1424  [uids release];
1425
1426  return YES;
1427}
1428
1429- (BOOL)_parseStatusResponseIntoHashMap:(NGMutableHashMap *)result_ {
1430  NSString            *name  = nil;
1431  NSMutableDictionary *flags = nil;
1432  NSDictionary *d;
1433
1434  if (!_matchesString(self, "STATUS "))
1435    return NO;
1436
1437  _consume(self, 7);
1438
1439  if (_la(self, 0) == '"') {
1440    name = [self _parseQuotedString];
1441//     _consume(self, 1);
1442//     name = _parseUntil(self, '"');
1443    _consumeIfMatch(self, ' ');
1444  }
1445  else if (_la(self, 0) == '{') {
1446    name = [self _parseQuotedStringOrNIL];
1447    _consumeIfMatch(self, ' ');
1448  }
1449  else {
1450    name = _parseUntil(self, ' ');
1451  }
1452  _consumeIfMatch(self, '(');
1453  flags = [NSMutableDictionary dictionaryWithCapacity:8];
1454
1455  while (_la(self, 0) != ')') {
1456    NSString *key   = _parseUntil(self, ' ');
1457    id       value = _parseUntil2(self, ' ', ')');
1458
1459    if (_la(self, 0) == ' ')
1460      _consume(self, 1);
1461
1462    if ([[key lowercaseString] isEqualToString:@"x-guid"])
1463    [flags setObject:value
1464          forKey:[key lowercaseString]];
1465    else
1466    [flags setObject:[NumClass numberWithInt:[value intValue]]
1467          forKey:[key lowercaseString]];
1468  }
1469  _consumeIfMatch(self, ')');
1470  _parseUntil(self, '\n');
1471
1472  d = [[NSDictionary alloc] initWithObjectsAndKeys:
1473			      name,  @"folderName",
1474			      flags, @"flags", nil];
1475  [result_ addObject:d forKey:@"status"];
1476  [d release];
1477  return YES;
1478}
1479
1480- (BOOL)_parseAnnotationResponseIntoHashMap:(NGMutableHashMap *)result_ {
1481  NSString            *name  = nil;
1482  NSString            *entry  = nil;
1483  NSMutableDictionary *attributes = nil;
1484  NSMutableDictionary *d, *f;
1485
1486  if (!_matchesString(self, "ANNOTATION "))
1487    return NO;
1488
1489  _consume(self, 11);
1490
1491  if (_la(self, 0) == '"') {
1492    name = [self _parseQuotedString];
1493    _consumeIfMatch(self, ' ');
1494  }
1495  else if (_la(self, 0) == '{') {
1496    name = [self _parseQuotedStringOrNIL];
1497    _consumeIfMatch(self, ' ');
1498  }
1499  else {
1500    name = _parseUntil(self, ' ');
1501  }
1502
1503  if (_la(self, 0) == '"') {
1504    entry = [self _parseQuotedString];
1505    _consumeIfMatch(self, ' ');
1506  }
1507  else if (_la(self, 0) == '{') {
1508    entry = [self _parseQuotedStringOrNIL];
1509    _consumeIfMatch(self, ' ');
1510  }
1511  else {
1512    entry = _parseUntil(self, ' ');
1513  }
1514
1515  _consumeIfMatch(self, '(');
1516
1517  attributes = [NSMutableDictionary dictionaryWithCapacity:2];
1518  d = [NSMutableDictionary dictionaryWithCapacity:2];
1519  f = [NSMutableDictionary dictionaryWithCapacity:2];
1520
1521  while (_la(self, 0) != ')') {
1522    NSString *key   = [self _parseQuotedString];
1523    _consume(self, 1); // ' '
1524    NSString *value = [self _parseQuotedStringOrNIL];
1525
1526    if (_la(self, 0) == ' ')
1527      _consume(self, 1);
1528
1529    if (value)
1530      {
1531	[attributes setObject:value
1532		       forKey:[key lowercaseString]];
1533
1534	[d setObject:[NSMutableDictionary dictionaryWithDictionary:attributes]
1535	      forKey:entry];
1536      }
1537  }
1538  _consumeIfMatch(self, ')');
1539  _parseUntil(self, '\n');
1540
1541  [f setObject:d forKey:name];
1542  [result_ addObject:f forKey:@"FolderList"];
1543
1544  return YES;
1545}
1546
1547
1548- (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_ {
1549  NSString *reason;
1550
1551  if (!_matchesString(self, "BYE "))
1552    return NO;
1553
1554  _consume(self, 4);
1555  reason = _parseUntil(self, '\n');
1556  [result_ addObject:reason forKey:@"bye"];
1557  return YES;
1558}
1559
1560- (id)_decodeQP:(id)_string headerField:(NSString *)_field {
1561  if (![_string isNotNull])
1562    return _string;
1563
1564  if ([_string isKindOfClass:DataClass])
1565    return [_string decodeQuotedPrintableValueOfMIMEHeaderField:_field];
1566
1567  if ([_string isKindOfClass:StrClass]) {
1568    if ([_string length] <= 6 /* minimum size */)
1569      return _string;
1570
1571    if ([_string rangeOfString:@"=?"].length > 0) {
1572      NSData *data;
1573
1574      if (debugOn)
1575	[self debugWithFormat:@"WARNING: string with quoted printable info!"];
1576
1577      // TODO: this is really expensive ...
1578      data = [_string dataUsingEncoding:NSUTF8StringEncoding];
1579      if (data != nil) {
1580	NSData *qpData;
1581
1582	qpData = [data decodeQuotedPrintableValueOfMIMEHeaderField:_field];
1583	if (qpData != data) return qpData;
1584      }
1585    }
1586    return _string;
1587  }
1588
1589  return _string;
1590}
1591
1592- (NGImap4EnvelopeAddress *)_parseEnvelopeAddressStructure {
1593  /*
1594     Note: returns retained object!
1595
1596     Order:
1597       personal name
1598       SMTP@at-domain-list(source route)
1599       mailbox name
1600       hostname
1601     eg:
1602       ("Helge Hess" NIL "helge.hess" "opengroupware.org")
1603  */
1604  NGImap4EnvelopeAddress *address = nil;
1605  NSString *pname, *route, *mailbox, *host;
1606
1607  if (_la(self, 0) != '(') {
1608    if (_matchesString(self, "NIL")) {
1609      _consume(self, 3);
1610      return (id)[null retain];
1611    }
1612    return nil;
1613  }
1614  _consume(self, 1); // '('
1615
1616  /* parse personal name, can be with quoted printable encoding! */
1617
1618  pname = [self _parseQuotedStringOrNIL];
1619  if ([pname isNotNull]) // TODO: headerField 'subject'?? explain!
1620    pname = [self _decodeQP:pname headerField:@"subject"];
1621  [self _consumeOptionalSpace];
1622
1623  // TODO: I think those forbid QP encoding?
1624  route   = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace];
1625  mailbox = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace];
1626  host    = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace];
1627
1628  if (_la(self, 0) != ')') {
1629    [self logWithFormat:@"WARNING: IMAP4 envelope "
1630	    @"address not properly closed (c0=%c,c1=%c): %@",
1631	    _la(self, 0), _la(self, 1), self->serverResponseDebug];
1632  }
1633  else
1634    _consume(self, 1);
1635
1636  if ([pname isNotNull] || [route isNotNull] || [mailbox isNotNull] || [host isNotNull])
1637    address = [[NGImap4EnvelopeAddress alloc] initWithPersonalName:pname
1638                                                       sourceRoute:route
1639                                                           mailbox:mailbox
1640                                                              host:host];
1641
1642  return address;
1643}
1644
1645- (NSArray *)_parseEnvelopeAddressStructures {
1646  /*
1647    Parses an array of envelopes, most common:
1648      ((NIL NIL "users-admin" "opengroupware.org"))
1649    (just one envelope in the array)
1650  */
1651  NSMutableArray *ma;
1652
1653  if (_la(self, 0) != '(') {
1654    if (_matchesString(self, "NIL")) {
1655      _consume(self, 3);
1656      return (id)[null retain];
1657    }
1658    return nil;
1659  }
1660  _consume(self, 1); // '('
1661
1662  ma = nil;
1663  while (_la(self, 0) != ')') {
1664    NGImap4EnvelopeAddress *address;
1665
1666    if ((address = [self _parseEnvelopeAddressStructure]) == nil) {
1667      [self _consumeOptionalSpace];
1668      continue; // TODO: should we stop parsing?
1669    }
1670    if (![address isNotNull])
1671      continue;
1672
1673    if (ma == nil) ma = [NSMutableArray arrayWithCapacity:4];
1674    [ma addObject:address];
1675    [address release]; /* the parse returns a retained object! */
1676  }
1677
1678  if (_la(self, 0) != ')') {
1679    [self logWithFormat:
1680	    @"WARNING: IMAP4 envelope address not properly closed!"];
1681  }
1682  else
1683    _consume(self, 1);
1684  return ma;
1685}
1686
1687- (id)_parseEnvelope {
1688  /*
1689    http://www.hunnysoft.com/rfc/rfc3501.html
1690
1691    envelope = "(" env-date SP env-subject SP env-from SP env-sender SP
1692	           env-reply-to SP env-to SP env-cc SP env-bcc SP
1693		   env-in-reply-to SP env-message-id ")"
1694
1695    * 1189 FETCH (UID 1189 ENVELOPE
1696       ("Tue, 22 Jun 2004 08:42:01 -0500" ""
1697        (("Jeff Glaspie" NIL "jeff" "glaspie.org"))
1698        (("Jeff Glaspie" NIL "jeff" "glaspie.org"))
1699        (("Jeff Glaspie" NIL "jeff" "glaspie.org"))
1700        ((NIL NIL "helge.hess" "opengroupware.org"))
1701        NIL NIL NIL
1702        "<20040622134354.F11133CEB14@mail.opengroupware.org>"
1703       )
1704      )
1705  */
1706  static NGMimeRFC822DateHeaderFieldParser *dateParser = nil;
1707  NGImap4Envelope *env;
1708  NSString        *dateStr;
1709  id tmp;
1710
1711  if (dateParser == nil)
1712    dateParser = [[NGMimeRFC822DateHeaderFieldParser alloc] init];
1713
1714  if (_la(self, 0) != '(')
1715    return nil;
1716  _consume(self, 1);
1717
1718  env = [[[NGImap4Envelope alloc] init] autorelease];
1719
1720  /* parse date */
1721
1722  dateStr = [self _parseQuotedStringOrNIL];
1723  [self _consumeOptionalSpace];
1724  if ([dateStr isNotNull])
1725    env->date = [[dateParser parseValue:dateStr ofHeaderField:nil] retain];
1726
1727  /* parse subject */
1728
1729  if ((tmp = [self _parseQuotedStringOrDataOrNIL]) != nil) {
1730    // TODO: that one is an issue, the client does know the requested charset
1731    //       but doesn't pass it down to the parser? Requiring the client to
1732    //       deal with NSData's is a bit overkill?
1733    env->subject = [tmp isNotNull]
1734      ? [[self _decodeQP:tmp headerField:@"subject"] copy]
1735      : nil;
1736    if (![env->subject isKindOfClass: [NSString class]])
1737      env->subject = [[NSString alloc] initWithData: env->subject encoding: NSUTF8StringEncoding];
1738    [self _consumeOptionalSpace];
1739  }
1740  else {
1741    [self logWithFormat:@"ERROR(%s): failed on subject(%c): %@",
1742	  __PRETTY_FUNCTION__, _la(self, 0), self->serverResponseDebug];
1743    return nil;
1744  }
1745
1746  /* parse addresses */
1747
1748  if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1749    env->from = [tmp isNotNull] ? [tmp copy] : nil;
1750    [self _consumeOptionalSpace];
1751  }
1752  else {
1753    [self logWithFormat:@"ERROR(%s): failed on from.", __PRETTY_FUNCTION__];
1754    return nil;
1755  }
1756  if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1757    env->sender = [tmp isNotNull] ? [[tmp lastObject] copy] : nil;
1758    [self _consumeOptionalSpace];
1759  }
1760  else {
1761    [self logWithFormat:@"ERROR(%s): failed on sender.", __PRETTY_FUNCTION__];
1762    return nil;
1763  }
1764  if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1765    env->replyTo = [tmp isNotNull] ? [tmp copy] : nil;
1766    [self _consumeOptionalSpace];
1767  }
1768
1769  if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1770    env->to = [tmp isNotNull] ? [tmp copy] : nil;
1771    [self _consumeOptionalSpace];
1772  }
1773  if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1774    env->cc = [tmp isNotNull] ? [tmp copy] : nil;
1775    [self _consumeOptionalSpace];
1776  }
1777  if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1778    env->bcc = [tmp isNotNull] ? [tmp copy] : nil;
1779    [self _consumeOptionalSpace];
1780  }
1781
1782  if ((tmp = [self _parseQuotedStringOrNIL])) {
1783    env->inReplyTo = [tmp isNotNull] ? [tmp copy] : nil;
1784    [self _consumeOptionalSpace];
1785  }
1786  if ((tmp = [self _parseQuotedStringOrNIL])) {
1787    env->msgId = [tmp isNotNull] ? [tmp copy] : nil;
1788    [self _consumeOptionalSpace];
1789  }
1790
1791  if (_la(self, 0) != ')') {
1792    [self logWithFormat:@"WARNING: IMAP4 envelope not properly closed"
1793	    @" (c0=%c,c1=%c): %@",
1794	    _la(self, 0), _la(self, 1), self->serverResponseDebug];
1795  }
1796  else
1797    _consume(self, 1);
1798
1799  return env;
1800}
1801
1802- (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_ {
1803  NSMutableDictionary *fetch;
1804  NSNumber *number;
1805  NSString *key    = nil;
1806
1807  if ((number = _parseUnsigned(self)) == nil)
1808    return NO;
1809
1810  _consumeIfMatch(self, ' ');
1811
1812  if (!_matchesString(self, "FETCH ")) {
1813    /* got a number request from select like  exists or recent */
1814    key = _parseUntil(self, '\n');
1815    [result_ addObject:number forKey:[key lowercaseString]];
1816    return YES;
1817  }
1818
1819  /* eg: "FETCH (FLAGS (\Seen) UID 5 RFC822.HEADER {2903}" */
1820  fetch = [[NSMutableDictionary alloc] initWithCapacity:10];
1821
1822  _consume(self, 6); /* "FETCH " */
1823  _consumeIfMatch(self, '(');
1824  while (_la(self, 0) != ')') { /* until closing parent */
1825    NSString *key;
1826
1827    key = [_parseUntil(self, ' ') lowercaseString];
1828#if 0
1829    [self logWithFormat:@"PARSE KEY: %@", key];
1830#endif
1831    if ([key hasPrefix:@"body[header.fields"]) {
1832      NSData *content;
1833
1834      if ((content = [self _parseBodyHeaderFields]) != nil)
1835	[fetch setObject:content forKey:key];
1836      else
1837	[self logWithFormat:@"ERROR: got no body content for key: '%@'",key];
1838    }
1839    else if ([key hasPrefix:@"body["]) {
1840      NSDictionary *content;
1841
1842      if ((content = [self _parseBodyContent]) != nil)
1843	[fetch setObject:content forKey:key];
1844      else
1845	[self logWithFormat:@"ERROR: got no body content for key: '%@'",key];
1846    }
1847    else if ([key isEqualToString:@"body"]) {
1848      [fetch setObject:_parseBody(self, NO) forKey:key];
1849    }
1850    else if ([key isEqualToString:@"bodystructure"]) {
1851      [fetch setObject:_parseBody(self, YES) forKey:key];
1852    }
1853    else if ([key isEqualToString:@"flags"]) {
1854      [fetch setObject:_parseFlagArray(self) forKey:key];
1855    }
1856    else if ([key isEqualToString:@"uid"]) {
1857      [fetch setObject:_parseUnsigned(self) forKey:key];
1858    }
1859    else if ([key isEqualToString:@"modseq"]) {
1860      _consumeIfMatch(self, '(');
1861      [fetch setObject:_parseUnsigned(self) forKey:key];
1862      _consumeIfMatch(self, ')');
1863    }
1864    else if ([key isEqualToString:@"rfc822.size"]) {
1865      [fetch setObject:_parseUnsigned(self) forKey:key];
1866    }
1867    else if ([key hasPrefix:@"rfc822"]) {
1868      NSData *data;
1869
1870      if (_la(self, 0) == '"') {
1871	NSString *str;
1872	_consume(self,1);
1873
1874	str = _parseUntil(self, '"');
1875	data = [str dataUsingEncoding:defCStringEncoding];
1876      }
1877      else
1878	data = [self _parseData];
1879
1880      if (data != nil) [fetch setObject:data forKey:key];
1881    }
1882    else if ([key isEqualToString:@"envelope"]) {
1883      id envelope;
1884
1885      if ((envelope = [self _parseEnvelope]) != nil)
1886	[fetch setObject:envelope forKey:key];
1887      else
1888	[self logWithFormat:@"ERROR: could not parse envelope!"];
1889    }
1890    else if ([key isEqualToString:@"internaldate"]) {
1891      // TODO: implement!
1892      NSException *e;
1893
1894      e = [[NGImap4ParserException alloc]
1895	    initWithFormat:@"INTERNALDATE fetch result not yet supported!"];
1896      [self setLastException:[e autorelease]];
1897      return NO;
1898    }
1899    else {
1900      NSException *e;
1901
1902      e = [[NGImap4ParserException alloc] initWithFormat:
1903					    @"unsupported fetch key: %@",
1904					  key];
1905      [self setLastException:[e autorelease]];
1906      return NO;
1907    }
1908
1909    if (_la(self, 0) == ' ')
1910      _consume(self, 1);
1911  }
1912  if (fetch != nil) {
1913    [fetch setObject:number  forKey:@"msn"];
1914    [result_ addObject:fetch forKey:@"fetch"];
1915    _consume(self, 1); /* consume ')' */
1916    _consumeIfMatch(self, '\n');
1917  }
1918  else { /* no correct fetch line */
1919    _parseUntil(self, '\n');
1920  }
1921
1922  [fetch release]; fetch = nil;
1923  return YES;
1924  }
1925
1926static BOOL _parseGreetingsSieveResponse(NGImap4ResponseParser *self,
1927                                         NGMutableHashMap *result_)
1928{
1929  BOOL isOK;
1930
1931  while (!(isOK = _parseOkSieveResponse(self, result_))) {
1932    NSString *key, *value;
1933
1934    if (!(key = _parseStringSieveResponse(self))) {
1935      break;
1936    }
1937    if (_la(self, 0) == ' ') {
1938      _consume(self, 1);
1939
1940      if (!(value = _parseStringSieveResponse(self))) {
1941        break;
1942      }
1943    }
1944    else {
1945      value = @"";
1946    }
1947    _parseUntil(self, '\n');
1948    [result_ addObject:value forKey:[key lowercaseString]];
1949  }
1950
1951  return isOK;
1952}
1953
1954static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self,
1955				    NGMutableHashMap *result_)
1956{
1957  NSString *str;
1958  NSData   *data;
1959
1960  if ((data = [self _parseData]) == nil)
1961    return NO;
1962
1963  str = [[StrClass alloc] initWithData:data encoding:NSUTF8StringEncoding];
1964  [result_ setObject:str forKey:@"data"];
1965  [str release]; str = nil;
1966
1967  _parseUntil(self, '\n');
1968
1969  return YES;
1970}
1971
1972static BOOL _parseOkSieveResponse(NGImap4ResponseParser *self,
1973                                  NGMutableHashMap *result_)
1974{
1975  if (!((_la(self, 0) == 'O') && (_la(self, 1) == 'K')))
1976    return NO;
1977
1978  _consume(self, 2);
1979
1980  if (_la(self, 0) == ' ') {
1981      NSString *reason;
1982
1983      if ((reason = _parseContentSieveResponse(self)))
1984        [result_ addObject:reason forKey:@"reason"];
1985  }
1986  _parseUntil(self, '\n');
1987
1988  [result_ addObject:YesNum forKey:@"ok"];
1989  return YES;
1990}
1991
1992static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self,
1993                                  NGMutableHashMap *result_)
1994{
1995  NSString *code, *data;
1996
1997  if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' ')))
1998    return NO;
1999
2000  _consume(self, 3);
2001
2002  if (_la(self, 0) == '(')
2003    {
2004      _consume(self, 1);
2005      code = _parseUntil(self, ')');
2006      if (_la(self, 0) == ' ') _consume(self, 1);
2007      if (code) [result_ addObject:code forKey:@"code"];
2008    }
2009
2010  data = _parseContentSieveResponse(self);
2011
2012  [result_ addObject:NoNum forKey:@"ok"];
2013  if (data) [result_ addObject:data forKey:@"reason"];
2014
2015  _parseUntil(self, '\n');
2016
2017  return YES;
2018}
2019
2020static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self) {
2021  NSString *str;
2022  NSData *data;
2023
2024  if ((str = _parseStringSieveResponse(self)))
2025    return str;
2026
2027  if ((data = [self _parseData]) == nil)
2028    return nil;
2029
2030  return [[[StrClass alloc] initWithData:data encoding:NSUTF8StringEncoding]
2031                          autorelease];
2032}
2033
2034static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self) {
2035  if (_la(self, 0) != '"')
2036    return nil;
2037
2038  _consume(self, 1);
2039  return _parseUntil(self, '"');
2040}
2041
2042static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
2043                                        BOOL _convertString,
2044                                        BOOL _decode)
2045{
2046  NSString *str;
2047
2048  if (_la(self, 0) == '"') {
2049    // TODO: can the " be escaped somehow?
2050    _consume(self, 1);
2051    str = _parseUntil(self, '"');
2052  }
2053  else if (_la(self, 0) == '{') {
2054    NSData *data;
2055
2056    if (debugDataOn) [self logWithFormat:@"parse body decode string"];
2057    data = [self _parseData];
2058
2059    if (_decode)
2060      data = [data decodeQuotedPrintableValueOfMIMEHeaderField:nil];
2061
2062    if ([data isKindOfClass: [NSString class]])
2063      return (NSString *) data;
2064    else
2065      {
2066	NSString *s;
2067
2068	// Let's try with the supplied encoding. If it doesn't work,
2069	// we'll then try UTF-8 or fallback to ISO-8859-1
2070	s = [[StrClass alloc] initWithData:data encoding:encoding];
2071
2072	if (!s && encoding != NSUTF8StringEncoding)
2073	  s = [[StrClass alloc] initWithData:data encoding:NSUTF8StringEncoding];
2074
2075	if (!s)
2076	  s = [[StrClass alloc] initWithData:data encoding:NSISOLatin1StringEncoding];
2077
2078	return [s autorelease];
2079      }
2080  }
2081  else {
2082    str = _parseUntil2(self, ' ', ')');
2083  }
2084  if (_convertString) {
2085    if ([[str lowercaseString] isEqualToString:@"nil"])
2086      str = @"";
2087  }
2088  if (_decode) {
2089    id  d;
2090
2091    d = [str dataUsingEncoding:defCStringEncoding];
2092    d = [d decodeQuotedPrintableValueOfMIMEHeaderField:nil];
2093
2094    if ([d isKindOfClass:StrClass])
2095      str = d;
2096    else {
2097      str = [[[StrClass alloc] initWithData:d encoding:encoding]
2098	                       autorelease];
2099    }
2100  }
2101  return str;
2102}
2103
2104static NSString *_parseBodyString(NGImap4ResponseParser *self,
2105                                  BOOL _convertString)
2106{
2107  return _parseBodyDecodeString(self, _convertString, NO /* no decode */);
2108}
2109
2110static NSArray *_parseLanguages(NGImap4ResponseParser *self) {
2111  NSMutableArray *languages;
2112  NSString *language;
2113
2114  languages = [NSMutableArray array];
2115  if (_la(self, 0) == '(') {
2116    while (_la(self, 0) != ')') {
2117      _consume(self,1);
2118      language = _parseBodyString(self, YES);
2119      if ([language length])
2120	[languages addObject: language];
2121    }
2122    _consume(self,1);
2123  }
2124  else {
2125    language = _parseBodyString(self, YES);
2126    if ([language length])
2127      [languages addObject: language];
2128  }
2129
2130  return languages;
2131}
2132
2133static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self)
2134{
2135  NSMutableDictionary *list;
2136
2137  if (_la(self, 0) == '(') {
2138    _consume(self, 1);
2139
2140    list = [NSMutableDictionary dictionaryWithCapacity:4];
2141
2142    while (_la(self,0) != ')') {
2143      NSString *key, *value;
2144
2145      if (_la(self, 0) == ' ')
2146        _consume(self, 1);
2147
2148      key = _parseBodyString(self, YES);
2149      _consumeIfMatch(self, ' ');
2150      value = _parseBodyDecodeString(self, YES, YES);
2151
2152      if (value) [list setObject:value forKey:[key lowercaseString]];
2153    }
2154    _consumeIfMatch(self, ')');
2155  }
2156  else {
2157    NSString *str;
2158    str = _parseBodyString(self, YES);
2159
2160    if ([str isNotEmpty])
2161      NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str);
2162
2163    list = (id)[NSDictionary dictionary];
2164  }
2165  return list;
2166}
2167
2168static NSDictionary *_parseContentDisposition(NGImap4ResponseParser *self)
2169{
2170  NSMutableDictionary *disposition;
2171  NSString *type;
2172
2173  disposition = [NSMutableDictionary dictionary];
2174
2175  if (_la(self, 0) == '(') {
2176    _consume(self, 1);
2177    type = _parseBodyString(self, YES);
2178    [disposition setObject: type forKey: @"type"];
2179    if (_la(self, 0) != ')') {
2180      _consume(self, 1);
2181      [disposition setObject: _parseBodyParameterList(self)
2182		   forKey: @"parameterList"];
2183    }
2184    _consume(self, 1);
2185  }
2186  else
2187    _parseBodyString(self, YES);
2188
2189  return disposition;
2190}
2191
2192static NSArray *_parseAddressStructure(NGImap4ResponseParser *self) {
2193  NSString *personalName, *sourceRoute, *mailboxName, *hostName;
2194
2195  // Handle broken address list - generally sent by Microsoft Exchange.
2196  if (_la(self, 0) == '(') {
2197    _consume(self, 1);
2198
2199    personalName = _parseBodyString(self, YES);
2200    _consumeIfMatch(self, ' ');
2201    sourceRoute = _parseBodyString(self, YES);
2202    _consumeIfMatch(self, ' ');
2203    mailboxName = _parseBodyString(self, YES);
2204    _consumeIfMatch(self, ' ');
2205    hostName = _parseBodyString(self, YES);
2206    _consumeIfMatch(self, ')');
2207
2208    return [NSDictionary dictionaryWithObjectsAndKeys:
2209                           personalName, @"personalName",
2210                         sourceRoute,  @"sourceRoute",
2211                         mailboxName,  @"mailboxName",
2212                         hostName,     @"hostName", nil];
2213  }
2214
2215  _parseUntil(self, ')');
2216
2217  return [NSDictionary dictionary];
2218}
2219
2220static NSArray *_parseParenthesizedAddressList(NGImap4ResponseParser *self) {
2221  NSMutableArray *result;
2222  result = [NSMutableArray arrayWithCapacity:8];
2223
2224  if (_la(self, 0) == '(') {
2225    _consume(self, 1);
2226    while (_la(self, 0) != ')') {
2227      [result addObject:_parseAddressStructure(self)];
2228    }
2229    _consume(self, 1);
2230  }
2231  else {
2232    NSString *str;
2233    str = _parseBodyString(self, YES);
2234
2235    if ([str isNotEmpty])
2236      NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str);
2237
2238    result = (id)[NSArray array];
2239  }
2240  return result;
2241}
2242
2243static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self,
2244				      BOOL isBodyStructure) {
2245  NSString            *type, *subtype, *bodyId, *description,
2246		      *result, *encoding, *bodysize;
2247  NSDictionary        *parameterList;
2248  NSMutableDictionary *dict;
2249  NSArray	      *languages;
2250
2251  type = [_parseBodyString(self, YES) lowercaseString];
2252  _consumeIfMatch(self, ' ');
2253  subtype = [_parseBodyString(self, YES) lowercaseString];
2254  _consumeIfMatch(self, ' ');
2255  parameterList = _parseBodyParameterList(self);
2256  _consumeIfMatch(self, ' ');
2257  bodyId = _parseBodyString(self, YES);
2258  _consumeIfMatch(self, ' ');
2259  description = _parseBodyString(self, YES);
2260  _consumeIfMatch(self, ' ');
2261  encoding = _parseBodyString(self, YES);
2262  _consumeIfMatch(self, ' ');
2263  bodysize = _parseBodyString(self, YES);
2264
2265  dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2266                              type, @"type",
2267                              subtype, @"subtype",
2268                              parameterList, @"parameterList",
2269                              bodyId,        @"bodyId",
2270                              description,   @"description",
2271                              encoding,      @"encoding",
2272                              bodysize,      @"size", nil];
2273
2274  if ([type isEqualToString:@"text"]) {
2275    _consumeIfMatch(self, ' ');
2276    [dict setObject:_parseBodyString(self, YES) forKey:@"lines"];
2277  }
2278  else if ([type isEqualToString:@"message"]
2279	   && ([subtype isEqualToString:@"rfc822"] || [subtype isEqualToString:@"global"])) {
2280    if (_la(self, 0) != ')') {
2281      _consumeIfMatch(self, ' ');
2282      _consumeIfMatch(self, '(');
2283      result = _parseBodyString(self, YES);
2284      if (result == nil) result = @"";
2285      [dict setObject:result forKey:@"date"];
2286      _consumeIfMatch(self, ' ');
2287      result = _parseBodyString(self, YES);
2288      if (result == nil) result = @"";
2289      [dict setObject:result forKey:@"subject"];
2290      _consumeIfMatch(self, ' ');
2291      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"from"];
2292      if (_la(self, 0) == ' ')
2293        _consume(self, 1);
2294      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"sender"];
2295      if (_la(self, 0) == ' ')
2296        _consume(self, 1);
2297      [dict setObject:_parseParenthesizedAddressList(self)
2298            forKey:@"reply-to"];
2299      if (_la(self, 0) == ' ')
2300        _consume(self, 1);
2301      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"to"];
2302      if (_la(self, 0) == ' ')
2303        _consume(self, 1);
2304      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"cc"];
2305      if (_la(self, 0) == ' ')
2306        _consume(self, 1);
2307      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"bcc"];
2308      if (_la(self, 0) == ' ')
2309        _consume(self, 1);
2310      result = _parseBodyString(self, YES);
2311      if (result == nil) result = @"";
2312      [dict setObject:result forKey:@"in-reply-to"];
2313      if (_la(self, 0) == ' ')
2314        _consume(self, 1);
2315      result = _parseBodyString(self, YES);
2316      if (result == nil) result = @"";
2317      [dict setObject:result forKey:@"messageId"];
2318      _consumeIfMatch(self, ')');
2319      if (_la(self, 0) == ' ')
2320        _consume(self, 1);
2321      [dict setObject:_parseBody(self, isBodyStructure) forKey:@"body"];
2322      if (_la(self, 0) == ' ')
2323        _consume(self, 1);
2324      result = _parseBodyString(self, YES);
2325      if (result == nil) result = @"";
2326      [dict setObject:result forKey:@"bodyLines"];
2327    }
2328  }
2329
2330  if (isBodyStructure) {
2331    if (_la(self, 0) != ')') {
2332      _consume(self,1);
2333      [dict setObject: _parseBodyString(self, YES)
2334	    forKey: @"md5"];
2335      if (_la(self, 0) != ')') {
2336	_consume(self,1);
2337	[dict setObject: _parseContentDisposition(self)
2338	      forKey: @"disposition"];
2339	if (_la(self, 0) != ')') {
2340	  _consume(self,1);
2341	  languages = _parseLanguages(self);
2342	  if ([languages count])
2343	    [dict setObject: languages forKey: @"languages"];
2344	  if (_la(self, 0) != ')') {
2345	    _consume(self,1);
2346	    [dict setObject: _parseBodyString(self, YES)
2347		  forKey: @"location"];
2348	  };
2349	};
2350      };
2351    };
2352  }
2353
2354  return dict;
2355}
2356
2357static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self,
2358					 BOOL isBodyStructure) {
2359  NSMutableArray *parts;
2360  NSArray	 *languages;
2361  NSString       *kind;
2362  NSMutableDictionary *dict;
2363
2364  parts = [NSMutableArray arrayWithCapacity:4];
2365
2366  while (_la(self, 0) == '(') {
2367    [parts addObject:_parseBody(self, isBodyStructure)];
2368  }
2369  _consumeIfMatch(self, ' ');
2370  kind = _parseBodyString(self, YES);
2371  dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2372				parts,        @"parts",
2373			      @"multipart", @"type",
2374			      kind        , @"subtype", nil];
2375  if (isBodyStructure) {
2376    if (_la(self, 0) != ')') {
2377      _consume(self,1);
2378      [dict setObject: _parseBodyParameterList(self)
2379	    forKey: @"parameterList"];
2380      if (_la(self, 0) != ')') {
2381	_consume(self,1);
2382	[dict setObject: _parseContentDisposition(self)
2383	      forKey: @"disposition"];
2384	if (_la(self, 0) != ')') {
2385	  _consume(self,1);
2386	  languages = _parseLanguages(self);
2387	  if ([languages count])
2388	    [dict setObject: languages forKey: @"languages"];
2389	  if (_la(self, 0) != ')') {
2390	    _consume(self,1);
2391	    [dict setObject: _parseBodyString(self, YES)
2392		  forKey: @"location"];
2393	  };
2394	};
2395      };
2396    };
2397  }
2398
2399  return dict;
2400}
2401
2402static NSDictionary *_parseBody(NGImap4ResponseParser *self, BOOL isBodyStructure) {
2403  NSDictionary *result;
2404
2405  _consumeIfMatch(self, '(');
2406
2407  if (_la(self, 0) == '(') {
2408    result = _parseMultipartBody(self, isBodyStructure);
2409  }
2410  else {
2411    result = _parseSingleBody(self, isBodyStructure);
2412  }
2413  if (_la(self,0) != ')') {
2414    NSString *str;
2415
2416    str = _parseUntil(self, ')');
2417    NSLog(@"%s: got noparsed content %@", __PRETTY_FUNCTION__,
2418          str);
2419  }
2420  else
2421    _consume(self, 1);
2422
2423  return result;
2424}
2425
2426- (NSDictionary *)_parseBodyContent {
2427  NSData *data;
2428  unsigned char c0;
2429
2430  c0 = _la(self, 0);
2431  if (c0 == '"') {
2432    NSString *str;
2433    _consume(self,1);
2434
2435    str = _parseUntil(self, '"');
2436    data = [str dataUsingEncoding:defCStringEncoding];
2437  }
2438  else if (c0 == 'N' && _matchesString(self, "NIL")) {
2439    _consume(self, 3);
2440    return nil;
2441  }
2442  else
2443    data = [self _parseData];
2444
2445  if (data == nil) {
2446    [self logWithFormat:@"ERROR(%s): got no data.", __PRETTY_FUNCTION__];
2447    return nil;
2448  }
2449  return [NSDictionary dictionaryWithObject:data forKey:@"data"];
2450}
2451
2452
2453static NSArray *_parseFlagArray(NGImap4ResponseParser *self) {
2454  NSString *flags;
2455
2456  _consumeIfMatch(self, '(');
2457
2458  flags = _parseUntil(self, ')');
2459  if (![flags isNotEmpty]) {
2460    static NSArray *emptyArray = nil;
2461    if (emptyArray == nil) emptyArray = [[NSArray alloc] init];
2462    return emptyArray;
2463  }
2464  else
2465    return [[flags lowercaseString] componentsSeparatedByString:@" "];
2466}
2467
2468static BOOL _parseEnabledUntaggedResponse(NGImap4ResponseParser *self,
2469                                          NGMutableHashMap *result_) {
2470  NSMutableArray *extensions;
2471  NSString *extension;
2472
2473  if ((_la(self, 0) == 'E')
2474      && (_la(self, 1) == 'N')
2475      && (_la(self, 2) == 'A')
2476      && (_la(self, 3) == 'B')
2477      && (_la(self, 4) == 'L')
2478      && (_la(self, 5) == 'E')
2479      && (_la(self, 6) == 'D')) {
2480    extensions = [NSMutableArray new];
2481    [result_ setObject: extensions forKey: @"extensions"];
2482    [extensions release];
2483
2484    _consume(self, 7);
2485    while (_la(self, 0) == ' ') {
2486      _consume(self, 1);
2487      extension = _parseUntil2(self, ' ', '\n');
2488      if ([extension length] > 0) {
2489        [extensions addObject: extension];
2490      }
2491    }
2492    _consumeIfMatch(self, '\n');
2493    return YES;
2494  }
2495  return NO;
2496}
2497
2498static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self,
2499                                        NGMutableHashMap *result_) {
2500  if ((_la(self, 0) == 'F')
2501      && (_la(self, 1) == 'L')
2502      && (_la(self, 2) == 'A')
2503      && (_la(self, 3) == 'G')
2504      && (_la(self, 4) == 'S')
2505      && (_la(self, 5) == ' ')) {
2506    _consume(self, 6);
2507    [result_ addObject:_parseFlagArray(self) forKey:@"flags"];
2508    _consumeIfMatch(self, '\n');
2509    return YES;
2510  }
2511  return NO;
2512}
2513
2514static BOOL _parseBadUntaggedResponse(NGImap4ResponseParser *self,
2515                                     NGMutableHashMap *result_)
2516{
2517  if (!((_la(self, 0)=='B') && (_la(self, 1)=='A') && (_la(self, 2)=='D')
2518	&& (_la(self, 3) == ' ')))
2519    return NO;
2520
2521  _consume(self, 4);
2522  [result_ addObject:_parseUntil(self, '\n') forKey:@"bad"];
2523  return YES;
2524}
2525
2526static BOOL _parseNoOrOkArguments(NGImap4ResponseParser *self,
2527                                  NGMutableHashMap *result_, NSString *_key)
2528{
2529  id obj;
2530
2531  obj = nil;
2532
2533  if (_la(self, 0) == '[') {
2534    NSString *key;
2535
2536    _consume(self, 1);
2537    key = _parseUntil2(self, ']', ' ');
2538
2539    /* possible kinds of untagged OK responses are either
2540     * OK [ALERT] System shutdown in 10 minutes
2541     or
2542     * OK [UNSEEN 14]
2543     or
2544     * OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen \*)]
2545     */
2546    if (_la(self, 0) == ']') {
2547
2548      _consume(self, 1);
2549      if (_la(self, 0) == ' ') {
2550        id value;
2551
2552        _consume(self, 1);
2553        value = _parseUntil(self, '\n');
2554        if ([value isNotEmpty]) {
2555          obj = [[NSDictionary alloc]
2556		  initWithObjects:&value forKeys:&key count:1];
2557	}
2558        else
2559          obj = [key retain];
2560      }
2561      else {
2562        obj = [key retain];
2563        _parseUntil(self, '\n');
2564      }
2565    }
2566    else { /* _la(self, 0) should be ' ' */
2567      id value;
2568
2569      value = nil;
2570
2571      _consume(self, 1);
2572      if (_la(self, 0) == '(') {
2573        value = _parseFlagArray(self);
2574        _consume(self, 1); /* consume ']' */
2575      }
2576      else {
2577        value = _parseUntil(self, ']');
2578      }
2579      {
2580        id tmp;
2581
2582        tmp = _parseUntil(self, '\n');
2583
2584        obj = [[NSDictionary alloc] initWithObjectsAndKeys:
2585                            value, key,
2586                            tmp,   @"comment", nil];
2587      }
2588    }
2589  }
2590  else
2591    obj = [_parseUntil(self, '\n') retain];
2592
2593  [result_ addObject:obj forKey:_key];
2594  [obj release];
2595  return YES;
2596}
2597
2598
2599static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self,
2600                                     NGMutableHashMap *result_)
2601{
2602  if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' ')))
2603    return NO;
2604
2605  _consume(self, 3);
2606  return _parseNoOrOkArguments(self, result_, @"no");
2607}
2608
2609static BOOL _parseOkUntaggedResponse(NGImap4ResponseParser *self,
2610                                     NGMutableHashMap *result_)
2611{
2612  if (!((_la(self, 0)=='O') && (_la(self, 1)=='K') && (_la(self, 2)==' ')))
2613    return NO;
2614
2615  _consume(self, 3);
2616  return _parseNoOrOkArguments(self, result_, @"ok");
2617}
2618
2619static NSNumber *_parseUnsigned(NGImap4ResponseParser *self) {
2620  unsigned      n;
2621  unsigned char c;
2622  BOOL     isNumber;
2623
2624  isNumber = NO;
2625  n        = 0;
2626  c        = _la(self, 0);
2627
2628  while ((c >= '0') && (c <= '9')) {
2629    _consume(self, 1);
2630    isNumber = YES;
2631    n        = 10 * n + (c - 48);
2632    c        = _la(self, 0);
2633  }
2634  if (!isNumber)
2635    return nil;
2636
2637  return [NumClass numberWithUnsignedInt:n];
2638}
2639
2640static NSString *_parseUntil(NGImap4ResponseParser *self, char _c) {
2641  /*
2642    Note: this function consumes the stop char (_c)!
2643    normalize \r\n constructions
2644  */
2645  // TODO: optimize!
2646  char            buf[1024], c;
2647  NSMutableString *str;
2648  unsigned        cnt;
2649
2650  cnt = 0;
2651  str = nil;
2652  while ((c = _la(self, 0)) != _c) {
2653    if (c == '\\') {
2654      _consume(self, 1);
2655      c = _la(self, 0);
2656    }
2657    buf[cnt] = c;
2658    _consume(self, 1);
2659    cnt++;
2660    if (cnt == 1024) {
2661      if (str == nil) {
2662        str = (NSMutableString *)
2663          [NSMutableString stringWithCString:buf length:1024];
2664      }
2665      else {
2666        NSString *s;
2667
2668        s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024];
2669        [str appendString:s];
2670        [s release];
2671      }
2672      cnt = 0;
2673    }
2674  }
2675  _consume(self,1); /* consume known stop char */
2676  if (_c == '\n' && cnt > 0) {
2677    if (buf[cnt-1] == '\r')
2678      cnt--;
2679  }
2680
2681  if (str == nil)
2682    return [StrClass stringWithCString:buf length:cnt];
2683  else {
2684    NSString *s, *s2;
2685
2686    s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt];
2687    s2 = [str stringByAppendingString:s];
2688    [s release];
2689    return s2;
2690  }
2691}
2692
2693static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2){
2694  /* _parseUntil2(self, char, char) doesn`t consume the stop-chars */
2695  char            buf[1024], c;
2696  NSMutableString *str;
2697  unsigned        cnt;
2698
2699  cnt = 0;
2700  c   = _la(self, 0);
2701  str = nil;
2702
2703  while ((c != _c1) && (c != _c2)) {
2704    buf[cnt] = c;
2705    _consume(self, 1);
2706    cnt++;
2707    if (cnt == 1024) {
2708      if (str == nil)
2709        str = (NSMutableString *)
2710                 [NSMutableString stringWithCString:buf length:1024];
2711      else {
2712        NSString *s;
2713
2714        s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024];
2715        [str appendString:s];
2716        [s release];
2717      }
2718
2719      cnt = 0;
2720    }
2721    c = _la(self, 0);
2722  }
2723
2724  if (str == nil)
2725    return [StrClass stringWithCString:buf length:cnt];
2726
2727  {
2728    NSString *s, *s2;
2729
2730    s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt];
2731    s2 = [str stringByAppendingString:s];
2732    [s release];
2733    return s2;
2734  }
2735}
2736
2737- (NSException *)exceptionForFailedMatch:(unsigned char)_match
2738  got:(unsigned char)_avail
2739{
2740  NSException *e;
2741
2742  e = [NGImap4ParserException alloc];
2743  if (self->debug) {
2744    e = [e initWithFormat:@"unexpected char <%c> expected <%c> <%@>",
2745	   _avail, _match, self->serverResponseDebug];
2746  }
2747  else {
2748    e = [e initWithFormat:@"unexpected char <%c> expected <%c>",
2749	     _avail, _match];
2750  }
2751  return [e autorelease];
2752}
2753
2754static __inline__ NSException *_consumeIfMatch(NGImap4ResponseParser *self,
2755					       unsigned char _match)
2756{
2757  NSException *e;
2758
2759  if (_la(self,0) == _match) {
2760    _consume(self, 1);
2761    return nil;
2762  }
2763
2764  e = [self exceptionForFailedMatch:_match got:_la(self, 0)];
2765  [self setLastException:e];
2766  return e;
2767}
2768
2769static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt) {
2770  /* Normalize end of line */
2771
2772  if (_cnt == 0)
2773    return;
2774
2775  _cnt +=  (__la(self, _cnt - 1) == '\r') ? 1 : 0;
2776
2777  if (self->debug) {
2778    unsigned cnt;
2779
2780    for (cnt = 0; cnt < _cnt; cnt++) {
2781      NSString *s;
2782      unichar c = _la(self, cnt);
2783
2784      if (c == '\r')
2785	continue;
2786
2787      s = [[StrClass alloc] initWithCharacters:&c length:1];
2788      [self->serverResponseDebug appendString:s];
2789      [s release];
2790
2791      if (c == '\n') {
2792	if ([self->serverResponseDebug lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding] > 2) {
2793            fprintf(stderr, "S[%p]: %s", self,
2794                    [self->serverResponseDebug cStringUsingEncoding:NSISOLatin1StringEncoding]);
2795          }
2796          [self->serverResponseDebug release];
2797          self->serverResponseDebug =
2798            [[NSMutableString alloc] initWithCapacity:512];
2799      }
2800    }
2801  }
2802  [self->buffer consume:_cnt];
2803}
2804
2805@end /* NGImap4ResponseParser */
2806