]> err.no Git - sope/blob - sope-mime/NGImap4/NGImap4ResponseParser.m
fixed OGo bug #1899
[sope] / sope-mime / NGImap4 / NGImap4ResponseParser.m
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   License for more details.
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
35 - (NSData *)_parseData;
36
37 - (BOOL)_parseQuotaResponseIntoHashMap:(NGMutableHashMap *)result_;
38 - (void)_parseContinuationResponseIntoHashMap:(NGMutableHashMap *)result_;
39 - (BOOL)_parseListOrLSubResponseIntoHashMap:(NGMutableHashMap *)result_;
40 - (BOOL)_parseCapabilityResponseIntoHashMap:(NGMutableHashMap *)result_;
41 - (BOOL)_parseSearchResponseIntoHashMap:(NGMutableHashMap *)result_;
42 - (BOOL)_parseSortResponseIntoHashMap:(NGMutableHashMap *)result_;
43 - (BOOL)_parseQuotaRootResponseIntoHashMap:(NGMutableHashMap *)result_;
44 - (BOOL)_parseStatusResponseIntoHashMap:(NGMutableHashMap *)result_;
45 - (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_;
46 - (BOOL)_parseACLResponseIntoHashMap:(NGMutableHashMap *)result_;
47 - (BOOL)_parseMyRightsResponseIntoHashMap:(NGMutableHashMap *)result_;
48 - (BOOL)_parseListRightsResponseIntoHashMap:(NGMutableHashMap *)result_;
49
50 - (NSArray *)_parseThread;
51
52 @end
53
54 @implementation NGImap4ResponseParser
55
56 #define __la(__SELF__, __PEEKPOS) \
57   ((__SELF__->la == NULL) \
58     ? [__SELF__->buffer la:__PEEKPOS]\
59     : __SELF__->la(__SELF__->buffer, @selector(la:), __PEEKPOS))
60
61 static __inline__ int _la(NGImap4ResponseParser *self, unsigned _laCnt) {
62   register unsigned char c = __la(self, _laCnt);
63   
64   return (c == '\r')
65     ? _la(self, _laCnt + 1)
66     : c;
67 }
68 static __inline__ BOOL _matchesString(NGImap4ResponseParser *self, 
69                                       const char *s)
70 {
71   register unsigned int  i;
72   
73   for (i = 0; s[i] != '\0'; i++) {
74     if (_la(self, i) != s[i])
75       return NO;
76   }
77   return YES;
78 }
79
80 static NSDictionary *_parseBody(NGImap4ResponseParser *self,
81                                 BOOL isBodyStructure);
82 static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self,
83                                       BOOL isBodyStructure);
84 static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self,
85                                          BOOL isBodyStructure);
86
87 static NSString *_parseBodyString(NGImap4ResponseParser *self,
88                                   BOOL _convertString);
89 static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
90                                         BOOL _convertString,
91                                         BOOL _decode);
92 static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self);
93 static NSDictionary *_parseContentDisposition(NGImap4ResponseParser *self);
94 static NSArray *_parseAddressStructure(NGImap4ResponseParser *self);
95 static NSArray *_parseParenthesizedAddressList(NGImap4ResponseParser *self);
96 static int _parseTaggedResponse(NGImap4ResponseParser *self,
97                                 NGMutableHashMap *result_);
98 static void _parseUntaggedResponse(NGImap4ResponseParser *self,
99                                    NGMutableHashMap *result_);
100 static NSArray *_parseFlagArray(NGImap4ResponseParser *self);
101 static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self,
102                                         NGMutableHashMap *result_);
103 static BOOL _parseOkUntaggedResponse(NGImap4ResponseParser *self,
104                                      NGMutableHashMap *result_);
105 static BOOL _parseBadUntaggedResponse(NGImap4ResponseParser *self,
106                                       NGMutableHashMap *result_);
107 static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self,
108                                      NGMutableHashMap *result_);
109 static BOOL _parseThreadResponse(NGImap4ResponseParser *self,
110                                  NGMutableHashMap *result_);
111 static NSNumber *_parseUnsigned(NGImap4ResponseParser *self);
112 static NSString *_parseUntil(NGImap4ResponseParser *self, char _c);
113 static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2);
114
115 static __inline__ NSException *_consumeIfMatch
116   (NGImap4ResponseParser *self, unsigned char _m);
117 static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt);
118
119 static void _parseSieveRespone(NGImap4ResponseParser *self,
120                                NGMutableHashMap *result_);
121 static BOOL _parseGreetingsSieveResponse(NGImap4ResponseParser *self,
122                                          NGMutableHashMap *result_);
123 static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self,
124                                     NGMutableHashMap *result_);
125 static BOOL _parseOkSieveResponse(NGImap4ResponseParser *self,
126                                   NGMutableHashMap *result_);
127 static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self,
128                                   NGMutableHashMap *result_);
129 static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self);
130 static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self);
131
132 static unsigned int     LaSize              = 4097;
133 static unsigned         UseMemoryMappedData = 0;
134 static unsigned         Imap4MMDataBoundary = 0;
135 static BOOL             debugOn             = NO;
136 static BOOL             debugDataOn         = NO;
137 static NSStringEncoding encoding;
138 static Class            StrClass  = Nil;
139 static Class            NumClass  = Nil;
140 static Class            DataClass = Nil;
141 static NSStringEncoding defCStringEncoding;
142 static NSNumber         *YesNum = nil;
143 static NSNumber         *NoNum  = nil;
144 static NSNull           *null   = nil;
145
146 + (void)initialize {
147   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
148   static BOOL didInit = NO;
149   if (didInit) return;
150   didInit = YES;
151
152   null = [[NSNull null] retain];
153   
154   encoding = [NGMimePartParser defaultHeaderFieldEncoding];
155   defCStringEncoding = [NSString defaultCStringEncoding];
156   
157   debugOn             = [ud boolForKey:@"ImapDebugEnabled"];
158   debugDataOn         = [ud boolForKey:@"ImapDebugDataEnabled"];
159   UseMemoryMappedData = [ud boolForKey:@"NoMemoryMappedDataForImapBlobs"]?0:1;
160   Imap4MMDataBoundary = [ud integerForKey:@"Imap4MMDataBoundary"];
161   
162   if (Imap4MMDataBoundary < 10)
163     /* Note: this should be larger than a usual header size! */
164     Imap4MMDataBoundary = 2 * LaSize;
165   
166   StrClass  = [NSString class];
167   NumClass  = [NSNumber class];
168   DataClass = [NSData class];
169   YesNum    = [[NumClass numberWithBool:YES] retain];
170   NoNum     = [[NumClass numberWithBool:NO]  retain];
171 }
172
173 + (id)parserWithStream:(id<NGActiveSocket>)_stream {
174   NGImap4ResponseParser *parser;
175
176   parser = [NGImap4ResponseParser alloc]; /* seperate line to keep gcc happy */
177   return [[parser initWithStream:_stream] autorelease];
178 }
179
180 - (id)initWithStream:(id<NGActiveSocket>)_stream {
181   // designated initializer
182   if (_stream == nil) {
183     [self logWithFormat:@"%s: got no stream ...", __PRETTY_FUNCTION__];
184     [self release];
185     return nil;
186   }
187   
188   if ((self = [super init])) {
189     id s;
190     
191     s = [(NGBufferedStream *)[NGBufferedStream alloc] initWithSource:_stream];
192     self->buffer = [NGByteBuffer alloc];
193     self->buffer = [self->buffer initWithSource:s la:LaSize];
194     [s release];
195     
196     if ([self->buffer respondsToSelector:@selector(methodForSelector:)])
197       self->la = (int(*)(id, SEL, unsigned))
198         [self->buffer methodForSelector:@selector(la:)];
199     
200     self->debug = debugOn;
201   }
202   return self;
203 }
204
205 - (id)init {
206   [self release];
207   [NSException raise:@"InvalidUseOfMethodException"
208                format:
209                  @"calling -init on the NGImap4ResponseParser is not allowed"];
210   return nil;
211 }
212
213 - (void)dealloc {
214   [self->buffer release];
215   if (self->debug)
216     [self->serverResponseDebug release];
217   [super dealloc];
218 }
219
220 /* exception handling */
221
222 - (void)setLastException:(NSException *)_exc {
223   // TODO: support last exception
224   [_exc raise];
225 }
226
227 /*
228 ** Parse Sieve Responses
229 */
230
231 - (NGHashMap *)parseSieveResponse {
232   NGMutableHashMap *result;
233
234   if (self->debug) {
235     if (self->serverResponseDebug != nil)
236       [self->serverResponseDebug release];
237     self->serverResponseDebug = [[NSMutableString alloc] initWithCapacity:512];
238   }
239   result = [NGMutableHashMap hashMapWithCapacity:64];
240
241   if (_la(self,0) == -1) {
242     [self setLastException:[self->buffer lastException]];
243     return nil;
244   }
245   
246   _parseSieveRespone(self, result);
247   return result;
248 }
249
250 - (NGHashMap *)parseResponseForTagId:(int)_tag exception:(NSException **)ex_ {
251   /* parse a response from the server, _tag!=-1 parse until tagged response */
252   // TODO: is NGHashMap really necessary here?
253   BOOL             endOfCommand;
254   NGMutableHashMap *result;
255   
256   if (ex_) *ex_ = nil;
257   
258   if (self->debug) {
259     [self->serverResponseDebug release]; self->serverResponseDebug = nil;
260     self->serverResponseDebug = [[NSMutableString alloc] initWithCapacity:512];
261   }
262   
263   result = [NGMutableHashMap hashMapWithCapacity:64];
264   
265   if (_la(self, 0) == -1) {
266     [self logWithFormat:@"%s: catched: %@", __PRETTY_FUNCTION__,
267             [self->buffer lastException]];
268     
269     if (ex_) {
270       *ex_ = [self->buffer lastException];
271       return nil;
272     }
273     else {
274       [self setLastException:[self->buffer lastException]];
275       return nil;
276     }
277   }
278   for (endOfCommand = NO; !endOfCommand; ) {
279     unsigned char l0;
280     
281     l0 = _la(self, 0);
282     
283     if (l0 == '*') { /* those starting with '* ' */
284       _parseUntaggedResponse(self, result);
285       if ([result objectForKey:@"bye"]) {
286         endOfCommand = YES;
287       }
288       else {
289         if (_tag == -1) {
290           if ([result objectForKey:@"ok"] != nil)
291             endOfCommand = YES;
292         }
293       }
294     }
295     else if (l0 == '+') { /* starting with a '+'? */
296       [self _parseContinuationResponseIntoHashMap:result];
297       endOfCommand = YES;
298     }
299     else if (isdigit(l0)) {
300       /* those starting with a number '24 ', eg '24 OK Completed' */
301       endOfCommand = (_parseTaggedResponse(self, result) == _tag);
302     }
303   }
304   return result;
305 }
306 - (NGHashMap *)parseResponseForTagId:(int)_tag {
307   // DEPRECATED
308   NSException *e = nil;
309   NGHashMap   *hm;
310
311   hm = [self parseResponseForTagId:_tag exception:&e];
312   if (e) {
313     [self setLastException:e];
314     return nil;
315   }
316   return hm;
317 }
318
319 static void _parseSieveRespone(NGImap4ResponseParser *self,
320                                NGMutableHashMap *result_)
321 {
322   if (_parseGreetingsSieveResponse(self, result_)) 
323     return;
324   if (_parseDataSieveResponse(self, result_))    // la: 1
325     return;
326   if (_parseOkSieveResponse(self, result_))     // la: 2
327     return; 
328   if (_parseNoSieveResponse(self, result_))     // la: 2
329     return;
330 }
331
332 - (NSData *)_parseDataToFile:(unsigned)_size {
333   // TODO: move to own method
334   // TODO: do not use NGFileStream but just fopen/fwrite
335   static NSProcessInfo *Pi = nil;
336   NGFileStream  *stream;
337   NSData        *result;
338   unsigned char buf[LaSize + 2];
339   unsigned char tmpBuf[LaSize + 2];
340   unsigned      wasRead = 0;
341   NSString      *path;
342   signed char   lastChar; // must be signed
343       
344   if (debugDataOn) [self logWithFormat:@"  using memory mapped data  ..."];
345       
346   if (Pi == nil)
347     Pi = [[NSProcessInfo processInfo] retain];
348
349   path   = [Pi temporaryFileName];
350   stream = [NGFileStream alloc]; /* extra line to keep gcc happy */
351   stream = [stream initWithPath:path];
352
353   if (![stream openInMode:NGFileWriteOnly]) {
354     NSException *e;
355
356     e = [[NGImap4ParserException alloc]
357           initWithFormat:@"Could not open temporary file %@", path];
358     [self setLastException:[e autorelease]];
359     return nil;
360   }
361       
362   lastChar = -1;
363   while (wasRead < _size) {
364     unsigned readCnt, bufCnt, tmpSize, cnt, tmpBufCnt;
365
366     bufCnt = 0;
367         
368     if (lastChar != -1) {
369       buf[bufCnt++] = lastChar;
370       lastChar = -1;
371     }
372         
373     [self->buffer la:(_size - wasRead <  LaSize) 
374          ? (_size - wasRead)
375          : LaSize];
376         
377     readCnt = [self->buffer readBytes:buf+bufCnt count:_size - wasRead];
378         
379     wasRead+=readCnt;
380     bufCnt +=readCnt;
381
382     tmpSize   = bufCnt - 1;
383     cnt       = 0;
384     tmpBufCnt = 0;
385         
386     while (cnt < tmpSize) {
387       if ((buf[cnt] == '\r') && (buf[cnt+1] == '\n')) {
388         cnt++;
389       }
390       tmpBuf[tmpBufCnt++] = buf[cnt++];
391     }
392     if (cnt < bufCnt) {
393       lastChar = buf[cnt];
394     }
395     [stream writeBytes:tmpBuf count:tmpBufCnt];
396   }
397   if (lastChar != -1)
398     [stream writeBytes:&lastChar count:1];
399   
400   [stream close];
401   [stream release]; stream = nil;
402   result = [DataClass dataWithContentsOfMappedFile:path];
403   [[NSFileManager defaultManager] removeFileAtPath:path handler:nil];
404
405   return result;
406 }
407 - (NSData *)_parseDataIntoRAM:(unsigned)_size {
408   /* parses data into a RAM buffer (NSData) */
409   unsigned char *buf = NULL;
410   unsigned char *tmpBuf;
411   unsigned      wasRead   = 0;
412   unsigned      cnt, tmpBufCnt, tmpSize;
413   NSData        *result;
414           
415   buf = calloc(_size + 10, sizeof(char));
416     
417   while (wasRead < _size) {
418     [self->buffer la:(_size - wasRead <  LaSize) ? (_size - wasRead) : LaSize];
419             
420     wasRead += [self->buffer readBytes:(buf + wasRead) count:(_size-wasRead)];
421   }
422   
423   /* normalize response  \r\n -> \n */
424         
425   tmpBuf    = calloc(_size + 10, sizeof(char));
426   cnt       = 0;
427   tmpBufCnt = 0;
428   tmpSize   = _size == 0 ? 0 : _size - 1;
429   while (tmpBufCnt < tmpSize && cnt < _size) {
430     if ((buf[cnt] == '\r') && (buf[cnt + 1] == '\n'))
431       cnt++; /* skip \r */
432       
433     tmpBuf[tmpBufCnt] = buf[cnt];
434     tmpBufCnt++;
435     cnt++;
436   }
437   if (cnt < _size) {
438     tmpBuf[tmpBufCnt] = buf[cnt];
439     tmpBufCnt++;
440     cnt++;
441   }
442     
443   result = [DataClass dataWithBytesNoCopy:tmpBuf length:tmpBufCnt];
444     
445   if (buf != NULL) free(buf); buf = NULL;
446   return result;
447 }
448 - (NSData *)_parseData {
449   /*
450     parses:
451       { <uint> } \n
452   */
453   // TODO: split up method
454   NSData   *result;
455   unsigned size;
456   NSNumber *sizeNum;
457
458   if (_la(self, 0) != '{')
459     return nil;
460   
461   if (debugDataOn) [self logWithFormat:@"parse data ..."];
462
463   /* got header */
464   result = nil;  
465   
466   _consume(self, 1); // '{'
467   if ((sizeNum = _parseUnsigned(self)) == nil) {
468     NSException *e;
469
470     e = [[NGImap4ParserException alloc] 
471             initWithFormat:@"expect a number between {}"];
472     [self setLastException:[e autorelease]];
473     return nil;
474   }
475   if (debugDataOn) [self logWithFormat:@"  parse data, size: %@", sizeNum];
476   _consumeIfMatch(self, '}');
477   _consumeIfMatch(self, '\n');
478   
479   if ((size = [sizeNum intValue]) == 0) {
480     [self logWithFormat:@"ERROR(%s): got content size '0'!", 
481             __PRETTY_FUNCTION__];
482     return nil;
483   }
484   
485   if (UseMemoryMappedData && (size > Imap4MMDataBoundary))
486     return [self _parseDataToFile:size];
487   
488   return [self _parseDataIntoRAM:size];
489 }
490
491 static int _parseTaggedResponse(NGImap4ResponseParser *self,
492                                 NGMutableHashMap *result_) 
493 {
494   NSDictionary *d;
495   NSNumber *tag  = nil;
496   NSString *res  = nil;
497   NSString *desc = nil;
498   NSString *flag = nil;
499   
500   if ((tag  = _parseUnsigned(self)) == nil) {
501     NSException *e;
502     
503     if (self->debug) {
504       e = [[NGImap4ParserException alloc]
505             initWithFormat:@"expect a number at begin of tagged response <%@>",
506             self->serverResponseDebug];
507     }
508     else {
509       e = [[NGImap4ParserException alloc]
510             initWithFormat:@"expect a number at begin of tagged response"];
511     }
512     e = [e autorelease];
513     [self setLastException:e];
514     return -1;
515   }
516   
517   _consumeIfMatch(self, ' ');
518   res  = [_parseUntil(self, ' ') lowercaseString];
519   if (_la(self, 0) == '[') { /* Found flag like [READ-ONLY] */
520     _consume(self, 1);
521     flag = _parseUntil(self, ']');
522   }
523   desc = _parseUntil(self, '\n');
524   /*
525     ATTENTION: if no flag was set, flag == nil, in this case all key-value 
526                pairs after flag will be ignored
527   */
528   d = [[NSDictionary alloc] initWithObjectsAndKeys:
529                               tag,  @"tagId",
530                               res,  @"result",
531                               desc, @"description",
532                               flag, @"flag", nil];
533   [result_ addObject:d forKey:@"ResponseResult"];
534   [d release];
535   return [tag intValue];
536 }
537
538 static void _parseUntaggedResponse(NGImap4ResponseParser *self,
539                                    NGMutableHashMap *result_) 
540 {
541   // TODO: is it really required by IMAP4 that responses are uppercase?
542   // TODO: apparently this code *breaks* with lowercase detection on!
543   unsigned char l0, l1 = 0;
544   _consumeIfMatch(self, '*');
545   _consumeIfMatch(self, ' ');
546   
547   l0 = _la(self, 0);
548   switch (l0) {
549   case 'A':
550     if ([self _parseACLResponseIntoHashMap:result_])
551       return;
552     break;
553     
554   case 'B':
555     l1 = _la(self, 1);
556     if (l1 == 'A' && _parseBadUntaggedResponse(self, result_))    // la: 3
557       return;
558     if (l1 == 'Y' && [self _parseByeUntaggedResponseIntoHashMap:result_]) // 3
559       return;
560     break;
561
562   case 'C':
563     if ([self _parseCapabilityResponseIntoHashMap:result_])       // la: 10
564       return;
565     break;
566     
567   case 'F':
568     if (_parseFlagsUntaggedResponse(self, result_))  // la: 5
569       return;
570     break;
571     
572   case 'L':
573     if (_matchesString(self, "LISTRIGHTS")) {
574       if ([self _parseListRightsResponseIntoHashMap:result_])
575         return;
576     }
577     if ([self _parseListOrLSubResponseIntoHashMap:result_])       // la: 4
578       return;
579     break;
580
581   case 'M':
582     if ([self _parseMyRightsResponseIntoHashMap:result_])
583       return;
584     break;
585
586   case 'N':
587     if (_parseNoUntaggedResponse(self, result_))     // la: 2
588       return;
589     break;
590
591   case 'O':
592     if (_parseOkUntaggedResponse(self, result_))     // la: 2
593       /* eg "* OK Completed" */
594       return;
595     break;
596
597   case 'R':
598     break;
599
600   case 'S':
601     switch (_la(self, 1)) {
602     case 'O': // SORT
603       if ([self _parseSortResponseIntoHashMap:result_])   // la: 4
604         return;
605       break;
606     case 'E': // SEARCH
607       if ([self _parseSearchResponseIntoHashMap:result_]) // la: 5
608         return;
609       break;
610     case 'T': // STATUS
611       if ([self _parseStatusResponseIntoHashMap:result_]) // la: 6
612         /* eg "* STATUS INBOX (MESSAGES 0 RECENT 0 UNSEEN 0)" */
613         return;
614       break;
615     }
616     break;
617
618   case 'T':
619     if (_parseThreadResponse(self, result_))         // la: 6
620       return;
621     break;
622     
623   case 'Q':
624     if ([self _parseQuotaResponseIntoHashMap:result_])     // la: 6
625       return;
626     if ([self _parseQuotaRootResponseIntoHashMap:result_]) // la: 10
627       return;
628     break;
629
630   case '0': case '1': case '2': case '3': case '4':
631   case '5': case '6': case '7': case '8': case '9':
632     if ([self _parseNumberUntaggedResponse:result_]) // la: 5
633       /* eg "* 928 FETCH ..." */
634       return;
635     break;
636   }
637   
638   // TODO: what if none matches?
639   [self logWithFormat:@"%s: no matching tag specifier?", __PRETTY_FUNCTION__];
640   [self logWithFormat:@"  line: '%@'", _parseUntil(self, '\n')];
641 }
642
643 - (void)_parseContinuationResponseIntoHashMap:(NGMutableHashMap *)result_ {
644   _consumeIfMatch(self, '+');
645   _consumeIfMatch(self, ' ');
646   
647   [result_ addObject:YesNum forKey:@"ContinuationResponse"];
648   [result_ addObject:_parseUntil(self, '\n') forKey:@"description"];
649 }
650
651 - (NSString *)_parseQuotedString {
652   /* parse a quoted string, eg '"' */
653   if (_la(self, 0) == '"') {
654     _consume(self, 1);
655     return _parseUntil(self, '"');
656   }
657   return nil;
658 }
659 - (void)_consumeOptionalSpace {
660   if (_la(self, 0) == ' ') _consume(self, 1);
661 }
662
663 - (BOOL)_parseListOrLSubResponseIntoHashMap:(NGMutableHashMap *)result_ {
664   NSArray  *flags = nil;
665   NSString *delim = nil;
666   NSString *name  = nil;
667   NSDictionary *d;
668   
669   if (!_matchesString(self, "LIST ") && !_matchesString(self, "LSUB "))
670     return NO;
671   
672   _consume(self, 5); /* consume 'LIST ' or 'LSUB ' */
673   flags = _parseFlagArray(self);
674   _consumeIfMatch(self, ' ');
675   
676   if (_la(self, 0) == '"') {
677     delim = [self _parseQuotedString];
678     _consumeIfMatch(self, ' ');
679   }
680   else {
681     _parseUntil(self, ' ');
682     delim = nil;
683   }
684   if (_la(self, 0) == '"') {
685     name = [self _parseQuotedString];
686     _parseUntil(self, '\n');
687   }
688   else
689     name = _parseUntil(self, '\n');
690   
691   d = [[NSDictionary alloc] initWithObjectsAndKeys:
692                               name,  @"folderName",
693                               flags, @"flags",
694                               delim, @"delimiter", nil];
695   [result_ addObject:d forKey:@"list"];
696   [d release];
697   return YES;
698 }
699
700 - (BOOL)_parseCapabilityResponseIntoHashMap:(NGMutableHashMap *)result_ {
701   NSString *caps;
702   NSEnumerator   *enumerator;
703   id             obj;
704   NSMutableArray *array;
705   NSArray        *tmp;
706   
707   if (!_matchesString(self, "CAPABILITY "))
708     return NO;
709
710   caps = _parseUntil(self, '\n');
711
712   array = [[NSMutableArray alloc] initWithCapacity:16];
713
714   enumerator = [[caps componentsSeparatedByString:@" "] objectEnumerator];
715   while ((obj = [enumerator nextObject]) != nil)
716     [array addObject:[obj lowercaseString]];
717   
718   tmp = [array copy];
719   [result_ addObject:tmp forKey:@"capability"];
720   
721   [array release]; array = nil;
722   [tmp   release]; tmp   = nil;
723   return YES;
724 }
725
726 - (BOOL)_parseACLResponseIntoHashMap:(NGMutableHashMap *)result_ {
727   /*
728     21 GETACL INBOX
729     * ACL INBOX test.et.di.cete-lyon lrswipcda helge lrwip
730   */
731   NSString       *acls;
732   NSEnumerator   *enumerator;
733   id             obj;
734   NSMutableArray *uids;
735   NSMutableArray *rights;
736   NSDictionary   *result;
737   
738   if (!_matchesString(self, "ACL "))
739     return NO;
740   _consume(self, 4);
741   
742   if ((obj = _parseBodyString(self, YES)) != nil)
743     [result_ setObject:obj forKey:@"mailbox"];
744   _consumeIfMatch(self, ' ');
745   
746   acls = _parseUntil(self, '\n');
747   
748   uids   = [[NSMutableArray alloc] initWithCapacity:8];
749   rights = [[NSMutableArray alloc] initWithCapacity:8];
750   
751   enumerator = [[acls componentsSeparatedByString:@" "] objectEnumerator];
752   while ((obj = [enumerator nextObject]) != nil) {
753     [uids  addObject:obj];
754     obj = [enumerator nextObject];
755     [rights addObject:(obj != nil ? obj : (id)@"")];
756   }
757   
758   result = [[NSDictionary alloc] initWithObjects:rights forKeys:uids];
759   [result_ addObject:result forKey:@"acl"];
760   
761   [uids   release]; uids   = nil;
762   [rights release]; rights = nil;
763   [result release]; result = nil;
764   return YES;
765 }
766
767 - (BOOL)_parseMyRightsResponseIntoHashMap:(NGMutableHashMap *)result_ {
768   /*
769     Raw Sample (Cyrus):
770       18 myrights INBOX
771       * MYRIGHTS INBOX lrswipcda
772       18 OK Completed
773   */
774   NSString *rights;
775   id obj;
776   
777   if (!_matchesString(self, "MYRIGHTS "))
778     return NO;
779   _consume(self, 9);
780   
781   if ((obj = _parseBodyString(self, YES)) != nil)
782     [result_ setObject:obj forKey:@"mailbox"];
783   _consumeIfMatch(self, ' ');
784   
785   rights = _parseUntil(self, '\n');
786   [result_ setObject:rights forKey:@"myrights"];
787   return YES;
788 }
789
790 - (BOOL)_parseListRightsResponseIntoHashMap:(NGMutableHashMap *)result_ {
791   /*
792     Raw Sample (Cyrus):
793       22 LISTRIGHTS INBOX helge
794       * LISTRIGHTS INBOX helge "" l r s w i p c d a 0 1 2 3 4 5 6 7 8 9
795       22 OK Completed
796  */
797   NSString *rights;
798   id obj;
799   
800   if (!_matchesString(self, "LISTRIGHTS "))
801     return NO;
802   _consume(self, 11);
803   
804   if ((obj = _parseBodyString(self, YES)) != nil)
805     [result_ setObject:obj forKey:@"mailbox"];
806   _consumeIfMatch(self, ' ');
807
808   if ((obj = _parseBodyString(self, YES)) != nil)
809     [result_ setObject:obj forKey:@"uid"];
810   _consumeIfMatch(self, ' ');
811   
812   if ((obj = _parseUntil(self, ' ')) != nil) {
813     if ([obj isEqual:@"\"\""])
814       obj = @"";
815     [result_ setObject:obj forKey:@"requiredRights"];
816   }
817   
818   rights = _parseUntil(self, '\n');
819   [result_ setObject:[rights componentsSeparatedByString:@" "]
820            forKey:@"listrights"];
821   return YES;
822 }
823
824 - (BOOL)_parseSearchResponseIntoHashMap:(NGMutableHashMap *)result_ {
825   NSMutableArray *msn = nil;
826   
827   if (!_matchesString(self, "SEARCH"))
828     return NO;
829
830   _consume(self, 6);
831
832   msn = [NSMutableArray arrayWithCapacity:128];
833
834   while (_la(self, 0) == ' ') {
835       _consume(self, 1);
836       [msn addObject:_parseUnsigned(self)];
837   }
838   _parseUntil(self, '\n');
839   [result_ addObject:msn forKey:@"search"];
840   return YES;
841 }
842
843 - (BOOL)_parseSortResponseIntoHashMap:(NGMutableHashMap *)result_ {
844   NSMutableArray *msn = nil;
845   
846   if (!_matchesString(self, "SORT"))
847     return NO;
848   
849   _consume(self, 4);
850
851   msn = [NSMutableArray arrayWithCapacity:128];
852
853   while (_la(self, 0) == ' ') {
854     _consume(self, 1);
855     [msn addObject:_parseUnsigned(self)];
856   }
857   _parseUntil(self, '\n');
858   [result_ addObject:msn forKey:@"sort"];
859   return YES;
860 }
861
862 - (BOOL)_parseQuotaResponseIntoHashMap:(NGMutableHashMap *)result_ {
863   NSString            *qRoot;
864   NSMutableDictionary *parse;
865   NSMutableDictionary *quota;
866
867   if (!_matchesString(self, "QUOTA "))
868     return NO;
869
870   _consume(self, 6);
871
872   quota = [result_ objectForKey:@"quota"];
873   
874   if (quota == nil) {
875       quota = [NSMutableDictionary dictionaryWithCapacity:2];
876       [result_ setObject:quota forKey:@"quota"];
877   }
878     
879   parse = [NSMutableDictionary dictionaryWithCapacity:3];
880   qRoot = _parseUntil2(self, ' ', '\n');
881
882   if (_la(self, 0) == ' ') {
883       _consume(self, 1);
884
885       if (_la(self, 0) == '(') {
886         _consume(self,1);
887         if (_la(self, 0) == ')') { /* empty quota response */
888           _consume(self,1);
889         }
890         else {
891           NSString *key;
892
893           key = _parseUntil(self, ' ');
894           key = [key lowercaseString];
895           if ([key isEqualToString:@"storage"]) {
896             NSString *used, *max;
897
898             used = _parseUntil(self, ' ');
899             max  = _parseUntil(self, ')');
900
901             [parse setObject:used forKey:@"usedSpace"];
902             [parse setObject:max  forKey:@"maxQuota"];
903           }
904           else {
905             NSString *v;
906
907             v = _parseUntil(self, ')');
908
909             [parse setObject:v forKey:@"resource"];
910           }
911         }
912       }
913       [quota setObject:parse forKey:qRoot];
914   }
915   _parseUntil(self, '\n');
916     
917   return YES;
918 }
919
920 - (BOOL)_parseQuotaRootResponseIntoHashMap:(NGMutableHashMap *)result_ {
921   NSString *folderName, *folderRoot;
922   NSMutableDictionary *dict;
923   
924   if (!_matchesString(self, "QUOTAROOT "))
925     return NO;
926
927   _consume(self, 10);
928
929   dict = [result_ objectForKey:@"quotaRoot"];
930
931   if (!dict) {
932     dict = [NSMutableDictionary dictionaryWithCapacity:2];
933     [result_ setObject:dict forKey:@"quotaRoot"];
934   }
935   if (_la(self, 0) == '"') {
936     _consume(self , 1);
937     folderName = _parseUntil(self, '"');
938   }
939   else {
940     folderName = _parseUntil2(self, '\n', ' ');
941   }
942   if (_la(self, 0) == ' ') {
943     _consume(self, 1);
944     folderRoot = _parseUntil(self, '\n');
945   }
946   else {
947     _consume(self, 1);
948     folderRoot = nil;
949   }
950   if ([folderName isNotEmpty] && [folderRoot isNotEmpty])
951     [dict setObject:folderRoot forKey:folderName];
952   
953   return YES;
954 }
955
956 - (NSArray *)_parseThread {
957   NSMutableArray *array;
958   NSNumber       *msg;
959     
960   array = [NSMutableArray arrayWithCapacity:64];
961
962   if (_la(self, 0) == '(')
963     _consume(self, 1);
964   
965   while (1) {
966     if (_la(self, 0) == '(') {
967       NSArray *a;
968       
969       a = [self _parseThread];
970       if (a != nil) [array addObject:a];
971     }
972     else if ((msg = _parseUnsigned(self))) {
973       [array addObject:msg];
974     }
975     else {
976       return nil;
977     }
978     if (_la(self, 0) == ')')
979       break;
980     else if (_la(self, 0) == ' ')
981       _consume(self, 1);
982   }
983   _consumeIfMatch(self, ')');
984   return array;
985 }
986
987
988 static BOOL _parseThreadResponse(NGImap4ResponseParser *self,
989                                  NGMutableHashMap *result_) {
990   if ((_la(self, 0) == 'T')
991       && (_la(self, 1) == 'H')
992       && (_la(self, 2) == 'R')
993       && (_la(self, 3) == 'E')
994       && (_la(self, 4) == 'A')
995       && (_la(self, 5) == 'D')) {
996
997     NSMutableArray *msn;
998
999     _consume(self, 6);
1000
1001     if (_la(self, 0) == ' ') {
1002       _consume(self, 1);
1003     }
1004     else {
1005       [result_ addObject:[NSArray array] forKey:@"thread"];
1006       return YES;
1007     }
1008     msn = [NSMutableArray arrayWithCapacity:64];
1009     while ((_la(self, 0) == '(')) {
1010       NSArray *array;
1011       
1012       if ((array = [self _parseThread]) != nil)
1013         [msn addObject:array];
1014     }
1015     _parseUntil(self, '\n');
1016     [result_ addObject:msn forKey:@"thread"];
1017     return YES;
1018   }
1019   return NO;
1020 }
1021
1022 - (BOOL)_parseStatusResponseIntoHashMap:(NGMutableHashMap *)result_ {
1023   NSString            *name  = nil;
1024   NSMutableDictionary *flags = nil;
1025   NSDictionary *d;
1026     
1027   if (!_matchesString(self, "STATUS "))
1028     return NO;
1029
1030   _consume(self, 7);
1031
1032   if (_la(self, 0) == '"') {
1033     _consume(self, 1);
1034     name = _parseUntil(self, '"');
1035     _consumeIfMatch(self, ' ');
1036   }
1037   else {
1038     name = _parseUntil(self, ' ');
1039   }
1040   _consumeIfMatch(self, '(');
1041   flags = [NSMutableDictionary dictionaryWithCapacity:8];
1042     
1043   while (_la(self, 0) != ')') {
1044     NSString *key   = _parseUntil(self, ' ');
1045     id       value = _parseUntil2(self, ' ', ')');
1046
1047     if (_la(self, 0) == ' ')
1048       _consume(self, 1);
1049       
1050     [flags setObject:[NumClass numberWithInt:[value intValue]]
1051            forKey:[key lowercaseString]];
1052   }
1053   _consumeIfMatch(self, ')');
1054   _parseUntil(self, '\n');
1055   
1056   d = [[NSDictionary alloc] initWithObjectsAndKeys:
1057                               name,  @"folderName",
1058                               flags, @"flags", nil];
1059   [result_ addObject:d forKey:@"status"];
1060   [d release];
1061   return YES;
1062 }
1063
1064 - (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_ {
1065   NSString *reason;
1066   
1067   if (!_matchesString(self, "BYE "))
1068     return NO;
1069
1070   _consume(self, 4);
1071   reason = _parseUntil(self, '\n');
1072   [result_ addObject:reason forKey:@"bye"];
1073   return YES;
1074 }
1075
1076 - (NSString *)_parseQuotedStringOrNIL {
1077   unsigned char c0;
1078   
1079   if ((c0 = _la(self, 0)) == '"')
1080     return [self _parseQuotedString];
1081   
1082   if (c0 == '{') {
1083     /* a size indicator, eg '{112}\nkasdjfkja sdj fhj hasdfj hjasdf' */
1084     NSData   *data;
1085     NSString *s;
1086     
1087     if ((data = [self _parseData]) == nil)
1088       return nil;
1089     if (![data isNotEmpty])
1090       return @"";
1091     
1092     s = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
1093     if (s == nil) {
1094       [self logWithFormat:
1095               @"ERROR(%s): could not convert data (%d bytes) into string.",
1096               __PRETTY_FUNCTION__, [data length]];
1097       return @"[ERROR: NGImap4 could not parse IMAP4 data string]";
1098     }
1099     return [s autorelease];
1100   }
1101   
1102   if (c0 == 'N' && _matchesString(self, "NIL")) {
1103     _consume(self, 3);
1104     return (id)null;
1105   }
1106   return nil;
1107 }
1108 - (id)_parseQuotedStringOrDataOrNIL {
1109   if (_la(self, 0) == '"')
1110     return [self _parseQuotedString];
1111   if (_la(self, 0) == '{')
1112     return [self _parseData];
1113   
1114   if (_matchesString(self, "NIL")) {
1115     _consume(self, 3);
1116     return null;
1117   }
1118   return nil;
1119 }
1120
1121 - (id)_decodeQP:(id)_string headerField:(NSString *)_field {
1122   if (![_string isNotNull])
1123     return _string;
1124   
1125   if ([_string isKindOfClass:DataClass])
1126     return [_string decodeQuotedPrintableValueOfMIMEHeaderField:_field];
1127   
1128   if ([_string isKindOfClass:StrClass]) {
1129     if ([_string length] <= 6 /* minimum size */)
1130       return _string;
1131     
1132     if ([_string rangeOfString:@"=?"].length > 0) {
1133       NSData *data;
1134       
1135       if (debugOn)
1136         [self debugWithFormat:@"WARNING: string with quoted printable info!"];
1137       
1138       // TODO: this is really expensive ...
1139       data = [_string dataUsingEncoding:NSUTF8StringEncoding];
1140       if (data != nil) {
1141         NSData *qpData;
1142         
1143         qpData = [data decodeQuotedPrintableValueOfMIMEHeaderField:_field];
1144         if (qpData != data) return qpData;
1145       }
1146     }
1147     return _string;
1148   }
1149   
1150   return _string;
1151 }
1152
1153 - (NGImap4EnvelopeAddress *)_parseEnvelopeAddressStructure {
1154   /* 
1155      Note: returns retained object!
1156      
1157      Order:
1158        personal name
1159        SMTP@at-domain-list(source route)
1160        mailbox name
1161        hostname
1162      eg: 
1163        ("Helge Hess" NIL "helge.hess" "opengroupware.org")
1164   */
1165   NGImap4EnvelopeAddress *address;
1166   NSString *pname, *route, *mailbox, *host;
1167   
1168   if (_la(self, 0) != '(') {
1169     if (_matchesString(self, "NIL")) {
1170       _consume(self, 3);
1171       return (id)[null retain];
1172     }
1173     return nil;
1174   }
1175   _consume(self, 1); // '('
1176
1177   /* parse personal name, can be with quoted printable encoding! */
1178   
1179   pname = [self _parseQuotedStringOrNIL];
1180   if ([pname isNotNull]) // TODO: headerField 'subject'?? explain!
1181     pname = [self _decodeQP:pname headerField:@"subject"];
1182   [self _consumeOptionalSpace];
1183   
1184   // TODO: I think those forbid QP encoding?
1185   route   = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace];
1186   mailbox = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace];
1187   host    = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace];
1188   
1189   if (_la(self, 0) != ')') {
1190     [self logWithFormat:@"WARNING: IMAP4 envelope "
1191             @"address not properly closed (c0=%c,c1=%c): %@",
1192             _la(self, 0), _la(self, 1), self->serverResponseDebug];
1193   }
1194   else
1195     _consume(self, 1);
1196   
1197   address = [[NGImap4EnvelopeAddress alloc] initWithPersonalName:pname
1198                                             sourceRoute:route mailbox:mailbox
1199                                             host:host];
1200   return address;
1201 }
1202
1203 - (NSArray *)_parseEnvelopeAddressStructures {
1204   /*
1205     Parses an array of envelopes, most common:
1206       ((NIL NIL "users-admin" "opengroupware.org"))
1207     (just one envelope in the array)
1208   */
1209   NSMutableArray *ma;
1210   
1211   if (_la(self, 0) != '(') {
1212     if (_matchesString(self, "NIL")) {
1213       _consume(self, 3);
1214       return (id)[null retain];
1215     }
1216     return nil;
1217   }
1218   _consume(self, 1); // '('
1219   
1220   ma = nil;
1221   while (_la(self, 0) != ')') {
1222     NGImap4EnvelopeAddress *address;
1223     
1224     if ((address = [self _parseEnvelopeAddressStructure]) == nil)
1225       continue; // TODO: should we stop parsing?
1226     if (![address isNotNull])
1227       continue;
1228     
1229     if (ma == nil) ma = [NSMutableArray arrayWithCapacity:4];
1230     [ma addObject:address];
1231     [address release]; /* the parse returns a retained object! */
1232   }
1233   
1234   if (_la(self, 0) != ')') {
1235     [self logWithFormat:
1236             @"WARNING: IMAP4 envelope address not properly closed!"];
1237   }
1238   else
1239     _consume(self, 1);
1240   return ma;
1241 }
1242
1243 - (id)_parseEnvelope {
1244   /*
1245     http://www.hunnysoft.com/rfc/rfc3501.html
1246           
1247     envelope = "(" env-date SP env-subject SP env-from SP env-sender SP
1248                    env-reply-to SP env-to SP env-cc SP env-bcc SP
1249                    env-in-reply-to SP env-message-id ")" 
1250                    
1251     * 1189 FETCH (UID 1189 ENVELOPE 
1252        ("Tue, 22 Jun 2004 08:42:01 -0500" "" 
1253         (("Jeff Glaspie" NIL "jeff" "glaspie.org")) 
1254         (("Jeff Glaspie" NIL "jeff" "glaspie.org")) 
1255         (("Jeff Glaspie" NIL "jeff" "glaspie.org")) 
1256         ((NIL NIL "helge.hess" "opengroupware.org")) 
1257         NIL NIL NIL 
1258         "<20040622134354.F11133CEB14@mail.opengroupware.org>"
1259        )
1260       )
1261   */
1262   static NGMimeRFC822DateHeaderFieldParser *dateParser = nil;
1263   NGImap4Envelope *env;
1264   NSString        *dateStr;
1265   id tmp;
1266   
1267   if (dateParser == nil)
1268     dateParser = [[NGMimeRFC822DateHeaderFieldParser alloc] init];
1269   
1270   if (_la(self, 0) != '(')
1271     return nil;
1272   _consume(self, 1);
1273   
1274   env = [[[NGImap4Envelope alloc] init] autorelease];
1275   
1276   /* parse date */
1277   
1278   dateStr = [self _parseQuotedStringOrNIL]; 
1279   [self _consumeOptionalSpace];
1280   if ([dateStr isNotNull])
1281     env->date = [[dateParser parseValue:dateStr ofHeaderField:nil] retain];
1282   
1283   /* parse subject */
1284   
1285   if ((tmp = [self _parseQuotedStringOrDataOrNIL]) != nil) {
1286     // TODO: that one is an issue, the client does know the requested charset
1287     //       but doesn't pass it down to the parser? Requiring the client to
1288     //       deal with NSData's is a bit overkill?
1289     env->subject = [tmp isNotNull] 
1290       ? [[self _decodeQP:tmp headerField:@"subject"] copy]
1291       : nil;
1292     [self _consumeOptionalSpace];
1293   }
1294   else {
1295     [self logWithFormat:@"ERROR(%s): failed on subject(%c): %@",
1296           __PRETTY_FUNCTION__, _la(self, 0), self->serverResponseDebug];
1297     return nil;
1298   }
1299   
1300   /* parse addresses */
1301   
1302   if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1303     env->from = [tmp isNotNull] ? [tmp copy] : nil;
1304     [self _consumeOptionalSpace];
1305   }
1306   else {
1307     [self logWithFormat:@"ERROR(%s): failed on from.", __PRETTY_FUNCTION__];
1308     return nil;
1309   }
1310   if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1311     env->sender = [tmp isNotNull] ? [[tmp lastObject] copy] : nil;
1312     [self _consumeOptionalSpace];
1313   }
1314   else {
1315     [self logWithFormat:@"ERROR(%s): failed on sender.", __PRETTY_FUNCTION__];
1316     return nil;
1317   }
1318   if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1319     env->replyTo = [tmp isNotNull] ? [tmp copy] : nil;
1320     [self _consumeOptionalSpace];
1321   }
1322   
1323   if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1324     env->to = [tmp isNotNull] ? [tmp copy] : nil;
1325     [self _consumeOptionalSpace];
1326   }
1327   if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1328     env->cc = [tmp isNotNull] ? [tmp copy] : nil;
1329     [self _consumeOptionalSpace];
1330   }
1331   if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1332     env->bcc = [tmp isNotNull] ? [tmp copy] : nil;
1333     [self _consumeOptionalSpace];
1334   }
1335
1336   if ((tmp = [self _parseQuotedStringOrNIL])) {
1337     env->inReplyTo = [tmp isNotNull] ? [tmp copy] : nil;
1338     [self _consumeOptionalSpace];
1339   }
1340   if ((tmp = [self _parseQuotedStringOrNIL])) {
1341     env->msgId = [tmp isNotNull] ? [tmp copy] : nil;
1342     [self _consumeOptionalSpace];
1343   }
1344   
1345   if (_la(self, 0) != ')') {
1346     [self logWithFormat:@"WARNING: IMAP4 envelope not properly closed"
1347             @" (c0=%c,c1=%c): %@",
1348             _la(self, 0), _la(self, 1), self->serverResponseDebug];
1349   }
1350   else
1351     _consume(self, 1);
1352   
1353   return env;
1354 }
1355
1356 - (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_ {
1357   NSMutableDictionary *fetch;
1358   NSNumber *number;
1359   NSString *key    = nil;
1360
1361   if ((number = _parseUnsigned(self)) == nil)
1362     return NO;
1363   
1364   _consumeIfMatch(self, ' ');
1365
1366   if (!_matchesString(self, "FETCH ")) {
1367     /* got a number request from select like  exists or recent */
1368     key = _parseUntil(self, '\n');
1369     [result_ addObject:number forKey:[key lowercaseString]];
1370     return YES;
1371   }
1372   
1373   /* eg: "FETCH (FLAGS (\Seen) UID 5 RFC822.HEADER {2903}" */
1374   fetch = [[NSMutableDictionary alloc] initWithCapacity:10];
1375     
1376   _consume(self, 6); /* "FETCH " */
1377   _consumeIfMatch(self, '(');
1378   while (_la(self, 0) != ')') { /* until closing parent */
1379     NSString *key;
1380       
1381     key = [_parseUntil(self, ' ') lowercaseString];
1382 #if 0
1383     [self logWithFormat:@"PARSE KEY: %@", key];
1384 #endif
1385     if ([key hasPrefix:@"body["]) {
1386       NSDictionary *content;
1387         
1388       if ((content = [self _parseBodyContent]) != nil)
1389         [fetch setObject:content forKey:key];
1390       else
1391         [self logWithFormat:@"ERROR: got no body content for key: '%@'",key];
1392     }
1393     else if ([key isEqualToString:@"body"]) {
1394       [fetch setObject:_parseBody(self, NO) forKey:key];
1395     }
1396     else if ([key isEqualToString:@"bodystructure"]) {
1397       [fetch setObject:_parseBody(self, YES) forKey:key];
1398     }
1399     else if ([key isEqualToString:@"flags"]) {
1400       [fetch setObject:_parseFlagArray(self) forKey:key];
1401     }
1402     else if ([key isEqualToString:@"uid"]) {
1403       [fetch setObject:_parseUnsigned(self) forKey:key];
1404     }
1405     else if ([key isEqualToString:@"rfc822.size"]) {
1406       [fetch setObject:_parseUnsigned(self) forKey:key];
1407     }
1408     else if ([key hasPrefix:@"rfc822"]) {
1409       NSData *data;
1410       
1411       if (_la(self, 0) == '"') {
1412         NSString *str;
1413         _consume(self,1);
1414
1415         str = _parseUntil(self, '"');
1416         data = [str dataUsingEncoding:defCStringEncoding];
1417       }
1418       else 
1419         data = [self _parseData];
1420
1421       if (data != nil) [fetch setObject:data forKey:key];
1422     }
1423     else if ([key isEqualToString:@"envelope"]) {
1424       id envelope;
1425
1426       if ((envelope = [self _parseEnvelope]) != nil)
1427         [fetch setObject:envelope forKey:key];
1428       else
1429         [self logWithFormat:@"ERROR: could not parse envelope!"];
1430     }
1431 //     else if ([key isEqualToString:@"bodystructure"]) {
1432 //       // TODO: implement!
1433 //       NSException *e;
1434       
1435 //       e = [[NGImap4ParserException alloc] 
1436 //          initWithFormat:@"bodystructure fetch result not yet supported!"];
1437 //       [self setLastException:[e autorelease]];
1438 //       return NO;
1439 //     }
1440     else if ([key isEqualToString:@"internaldate"]) {
1441       // TODO: implement!
1442       NSException *e;
1443       
1444       e = [[NGImap4ParserException alloc] 
1445             initWithFormat:@"INTERNALDATE fetch result not yet supported!"];
1446       [self setLastException:[e autorelease]];
1447       return NO;
1448     }
1449     else {
1450       NSException *e;
1451         
1452       e = [[NGImap4ParserException alloc] initWithFormat:
1453                                             @"unsupported fetch key: %@", 
1454                                           key];
1455       [self setLastException:[e autorelease]];
1456       return NO;
1457     }
1458     
1459     if (_la(self, 0) == ' ')
1460       _consume(self, 1);
1461   }
1462   if (fetch != nil) {
1463     [fetch setObject:number  forKey:@"msn"];
1464     [result_ addObject:fetch forKey:@"fetch"];
1465     _consume(self, 1); /* consume ')' */
1466     _consumeIfMatch(self, '\n');
1467   }
1468   else { /* no correct fetch line */
1469     _parseUntil(self, '\n');
1470   }
1471     
1472   [fetch release]; fetch = nil;
1473   return YES;
1474   }
1475
1476 static BOOL _parseGreetingsSieveResponse(NGImap4ResponseParser *self,
1477                                          NGMutableHashMap *result_) 
1478 {
1479   BOOL isOK;
1480   
1481   while (!(isOK = _parseOkSieveResponse(self, result_))) {
1482     NSString *key, *value;
1483
1484     if (!(key = _parseStringSieveResponse(self))) {
1485       break;
1486     }
1487     if (_la(self, 0) == ' ') {
1488       _consume(self, 1);
1489
1490       if (!(value = _parseStringSieveResponse(self))) {
1491         break;
1492       }
1493     }
1494     else {
1495       value = @"";
1496     }
1497     _parseUntil(self, '\n');
1498     [result_ addObject:value forKey:[key lowercaseString]];
1499   }
1500   return isOK;
1501 }
1502
1503 static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self,
1504                                     NGMutableHashMap *result_) 
1505 {
1506   NSString *str;
1507   NSData   *data;
1508
1509   if ((data = [self _parseData]) == nil)
1510     return NO;
1511   
1512   str = [[StrClass alloc] initWithData:data encoding:defCStringEncoding];
1513   [result_ setObject:str forKey:@"data"];
1514   [str release]; str = nil;
1515   return YES;
1516 }
1517
1518 static BOOL _parseOkSieveResponse(NGImap4ResponseParser *self,
1519                                   NGMutableHashMap *result_) 
1520 {
1521   if (!((_la(self, 0) == 'O') && (_la(self, 1) == 'K')))
1522     return NO;
1523     
1524   _consume(self, 2);
1525
1526   if (_la(self, 0) == ' ') {
1527       NSString *reason;
1528
1529       if ((reason = _parseContentSieveResponse(self)))
1530         [result_ addObject:reason forKey:@"reason"];
1531   }
1532   _parseUntil(self, '\n');
1533
1534   [result_ addObject:YesNum forKey:@"ok"];
1535   return YES;
1536 }
1537
1538 static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self,
1539                                   NGMutableHashMap *result_) 
1540 {
1541   NSString *data;
1542   
1543   if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' ')))
1544     return NO;
1545     
1546   _consume(self, 3);
1547
1548   data = _parseContentSieveResponse(self);
1549     
1550   [result_ addObject:NoNum forKey:@"ok"];
1551   if (data) [result_ addObject:data forKey:@"reason"];
1552   return YES;
1553 }
1554
1555 static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self) {
1556   NSString *str;
1557   NSData *data;
1558   
1559   if ((str = _parseStringSieveResponse(self)))
1560     return str;
1561   
1562   if ((data = [self _parseData]) == nil)
1563     return nil;
1564   
1565   return [[[StrClass alloc] initWithData:data encoding:defCStringEncoding]
1566                           autorelease];
1567 }
1568
1569 static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self) {
1570   if (_la(self, 0) != '"')
1571     return nil;
1572   
1573   _consume(self, 1);
1574   return _parseUntil(self, '"');
1575 }
1576
1577 static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
1578                                         BOOL _convertString,
1579                                         BOOL _decode)
1580 {
1581   NSString *str;
1582   
1583   if (_la(self, 0) == '"') {
1584     // TODO: can the " be escaped somehow?
1585     _consume(self, 1);
1586     str = _parseUntil(self, '"');
1587   }
1588   else if (_la(self, 0) == '{') {
1589     NSData *data;
1590     
1591     if (debugDataOn) [self logWithFormat:@"parse body decode string"];
1592     data = [self _parseData];
1593
1594     if (_decode)
1595       data = [data decodeQuotedPrintableValueOfMIMEHeaderField:nil];
1596     
1597     return [[[StrClass alloc] initWithData:data encoding:encoding]
1598                             autorelease];
1599   }
1600   else {
1601     str = _parseUntil2(self, ' ', ')');
1602   }
1603   if (_convertString) {
1604     if ([[str lowercaseString] isEqualToString:@"nil"])
1605       str = @"";
1606   }
1607   if (_decode) {
1608     id  d;
1609     
1610     d = [str dataUsingEncoding:defCStringEncoding];
1611     d = [d decodeQuotedPrintableValueOfMIMEHeaderField:nil];
1612     
1613     if ([d isKindOfClass:StrClass])
1614       str = d;
1615     else {
1616       str = [[[StrClass alloc] initWithData:d encoding:encoding]
1617                                autorelease];
1618     }
1619   }
1620   return str;
1621 }
1622
1623
1624 static NSString *_parseBodyString(NGImap4ResponseParser *self,
1625                                   BOOL _convertString)
1626 {
1627   return _parseBodyDecodeString(self, _convertString, NO /* no decode */);
1628 }
1629
1630 static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self)
1631 {
1632   NSMutableDictionary *list;
1633
1634   if (_la(self, 0) == '(') {
1635     _consume(self, 1);
1636
1637     list = [NSMutableDictionary dictionaryWithCapacity:4];
1638   
1639     while (_la(self,0) != ')') {
1640       NSString *key, *value;
1641
1642       if (_la(self, 0) == ' ')
1643         _consume(self, 1);
1644       
1645       key = _parseBodyString(self, YES);
1646       _consumeIfMatch(self, ' ');
1647       value = _parseBodyDecodeString(self, YES, YES);
1648
1649       [list setObject:value forKey:[key lowercaseString]];
1650     }
1651     _consumeIfMatch(self, ')');
1652   }
1653   else {
1654     NSString *str;
1655     str = _parseBodyString(self, YES);
1656
1657     if ([str isNotEmpty])
1658       NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str);
1659     
1660     list = (id)[NSDictionary dictionary];
1661   }
1662   return list;
1663 }
1664
1665 static NSDictionary *_parseContentDisposition(NGImap4ResponseParser *self)
1666 {
1667   NSMutableDictionary *disposition;
1668   NSString *type;
1669
1670   disposition = [NSMutableDictionary dictionary];
1671
1672   if (_la(self, 0) == '(') {
1673     _consume(self, 1);
1674     type = _parseBodyString(self, YES);
1675     [disposition setObject: type forKey: @"type"];
1676     if (_la(self, 0) != ')') {
1677       _consume(self, 1);
1678       [disposition setObject: _parseBodyParameterList(self)
1679                    forKey: @"parameterList"];
1680     }
1681     _consume(self, 1);
1682   }
1683   else
1684     _parseBodyString(self, YES);
1685
1686   return disposition;
1687 }
1688
1689 static NSArray *_parseAddressStructure(NGImap4ResponseParser *self) {
1690   NSString *personalName, *sourceRoute, *mailboxName, *hostName;
1691   
1692   _consumeIfMatch(self, '(');
1693   personalName = _parseBodyString(self, YES);
1694   _consumeIfMatch(self, ' ');
1695   sourceRoute = _parseBodyString(self, YES);
1696   _consumeIfMatch(self, ' ');
1697   mailboxName = _parseBodyString(self, YES);
1698   _consumeIfMatch(self, ' ');
1699   hostName = _parseBodyString(self, YES);
1700   _consumeIfMatch(self, ')');
1701   return [NSDictionary dictionaryWithObjectsAndKeys:
1702                        personalName, @"personalName",
1703                        sourceRoute,  @"sourceRoute",
1704                        mailboxName,  @"mailboxName",
1705                        hostName,     @"hostName", nil];
1706 }
1707
1708 static NSArray *_parseParenthesizedAddressList(NGImap4ResponseParser *self) {
1709   NSMutableArray *result;
1710   result = [NSMutableArray arrayWithCapacity:8];
1711
1712   if (_la(self, 0) == '(') {
1713     _consume(self, 1);
1714     while (_la(self, 0) != ')') {
1715       [result addObject:_parseAddressStructure(self)];
1716     }
1717     _consume(self, 1);
1718   }
1719   else {
1720     NSString *str;
1721     str = _parseBodyString(self, YES);
1722
1723     if ([str isNotEmpty])
1724       NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str);
1725     
1726     result = (id)[NSArray array];
1727   }
1728   return result;
1729 }
1730
1731 static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self,
1732                                       BOOL isBodyStructure) {
1733   NSString            *type, *subtype, *bodyId, *description,
1734                       *encoding, *bodysize;
1735   NSDictionary        *parameterList;
1736   NSMutableDictionary *dict;
1737
1738   type = [_parseBodyString(self, YES) lowercaseString];
1739   _consumeIfMatch(self, ' ');
1740   subtype = _parseBodyString(self, YES);
1741   _consumeIfMatch(self, ' ');
1742   parameterList = _parseBodyParameterList(self);
1743   _consumeIfMatch(self, ' ');
1744   bodyId = _parseBodyString(self, YES);
1745   _consumeIfMatch(self, ' ');
1746   description = _parseBodyString(self, YES);
1747   _consumeIfMatch(self, ' ');
1748   encoding = _parseBodyString(self, YES);
1749   _consumeIfMatch(self, ' ');
1750   bodysize = _parseBodyString(self, YES);
1751
1752   dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1753                               type, @"type",
1754                               subtype, @"subtype",
1755                               parameterList, @"parameterList",
1756                               bodyId,        @"bodyId",
1757                               description,   @"description",
1758                               encoding,      @"encoding",
1759                               bodysize,      @"size", nil];
1760   
1761   if ([type isEqualToString:@"text"]) {
1762     _consumeIfMatch(self, ' ');
1763     [dict setObject:_parseBodyString(self, YES) forKey:@"lines"];
1764   }
1765   else if ([type isEqualToString:@"message"]) {
1766     if (_la(self, 0) != ')') {
1767       _consumeIfMatch(self, ' ');
1768       _consumeIfMatch(self, '(');
1769       [dict setObject:_parseBodyString(self, YES) forKey:@"date"];
1770       _consumeIfMatch(self, ' ');
1771       [dict setObject:_parseBodyString(self, YES) forKey:@"subject"];
1772       _consumeIfMatch(self, ' ');
1773       [dict setObject:_parseParenthesizedAddressList(self) forKey:@"from"];
1774       _consumeIfMatch(self, ' ');
1775       [dict setObject:_parseParenthesizedAddressList(self) forKey:@"sender"];
1776       _consumeIfMatch(self, ' ');
1777       [dict setObject:_parseParenthesizedAddressList(self)
1778             forKey:@"reply-to"];
1779       _consumeIfMatch(self, ' ');
1780       [dict setObject:_parseParenthesizedAddressList(self) forKey:@"to"];
1781       _consumeIfMatch(self, ' ');
1782       [dict setObject:_parseParenthesizedAddressList(self) forKey:@"cc"];
1783       _consumeIfMatch(self, ' ');
1784       [dict setObject:_parseParenthesizedAddressList(self) forKey:@"bcc"];
1785       _consumeIfMatch(self, ' ');
1786       [dict setObject:_parseBodyString(self, YES) forKey:@"in-reply-to"];
1787       _consumeIfMatch(self, ' ');
1788       [dict setObject:_parseBodyString(self, YES) forKey:@"messageId"];
1789       _consumeIfMatch(self, ')');
1790       _consumeIfMatch(self, ' ');
1791       [dict setObject:_parseBody(self, isBodyStructure) forKey:@"body"];
1792       _consumeIfMatch(self, ' ');
1793       [dict setObject:_parseBodyString(self, YES) forKey:@"bodyLines"];
1794     }
1795   }
1796
1797   if (isBodyStructure) {
1798     if (_la(self, 0) != ')') {
1799       _consume(self,1);
1800       [dict setObject: _parseBodyString(self, YES)
1801             forKey: @"md5"];
1802       if (_la(self, 0) != ')') {
1803         _consume(self,1);
1804         [dict setObject: _parseContentDisposition(self)
1805               forKey: @"disposition"];
1806         if (_la(self, 0) != ')') {
1807           _consume(self,1);
1808           if (_la(self, 0) == '(') {
1809             [dict setObject: _parseBodyParameterList(self)
1810                   forKey: @"language"];
1811           }
1812           else {
1813             [dict setObject: _parseBodyString(self, YES)
1814                   forKey: @"language"];
1815           }
1816           if (_la(self, 0) != ')') {
1817             _consume(self,1);
1818             [dict setObject: _parseBodyString(self, YES)
1819                   forKey: @"location"];
1820           };
1821         };
1822       };
1823     };
1824   }
1825
1826   return dict;
1827 }
1828
1829 static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self,
1830                                          BOOL isBodyStructure) {
1831   NSMutableArray *parts;
1832   NSString       *kind;
1833   NSMutableDictionary *dict;
1834
1835   parts = [NSMutableArray arrayWithCapacity:4];
1836
1837   while (_la(self, 0) == '(') {
1838     [parts addObject:_parseBody(self, isBodyStructure)];
1839   }
1840   _consumeIfMatch(self, ' ');
1841   kind = _parseBodyString(self, YES);
1842   dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1843                                 parts,        @"parts",
1844                               @"multipart", @"type",
1845                               kind        , @"subtype", nil];
1846   if (isBodyStructure) {
1847     if (_la(self, 0) != ')') {
1848       _consume(self,1);
1849       [dict setObject: _parseBodyParameterList(self)
1850             forKey: @"parameterList"];
1851       if (_la(self, 0) != ')') {
1852         _consume(self,1);
1853         [dict setObject: _parseContentDisposition(self)
1854               forKey: @"disposition"];
1855         if (_la(self, 0) != ')') {
1856           _consume(self,1);
1857           if (_la(self, 0) == '(') {
1858             [dict setObject: _parseBodyParameterList(self)
1859                   forKey: @"language"];
1860           }
1861           else {
1862             [dict setObject: _parseBodyString(self, YES)
1863                   forKey: @"language"];
1864           }
1865           if (_la(self, 0) != ')') {
1866             _consume(self,1);
1867             [dict setObject: _parseBodyString(self, YES)
1868                   forKey: @"location"];
1869           };
1870         };
1871       };
1872     };
1873   }
1874
1875   return dict;
1876 }
1877
1878 static NSDictionary *_parseBody(NGImap4ResponseParser *self, BOOL isBodyStructure) {
1879   NSDictionary *result;
1880
1881   _consumeIfMatch(self, '(');
1882
1883   if (_la(self, 0) == '(') {
1884     result = _parseMultipartBody(self, isBodyStructure);
1885   }
1886   else {
1887     result = _parseSingleBody(self, isBodyStructure);
1888   }
1889   if (_la(self,0) != ')') {
1890     NSString *str;
1891
1892     str = _parseUntil(self, ')');
1893     NSLog(@"%s: got noparsed content %@", __PRETTY_FUNCTION__,
1894           str);
1895   }
1896   else 
1897     _consume(self, 1);
1898
1899   return result;
1900 }
1901
1902 - (NSDictionary *)_parseBodyContent {
1903   NSData *data;
1904   
1905   if (_la(self, 0) == '"') {
1906     NSString *str;
1907     _consume(self,1);
1908     
1909     str = _parseUntil(self, '"');
1910     data = [str dataUsingEncoding:defCStringEncoding];
1911   }
1912   else 
1913     data = [self _parseData];
1914   
1915   if (data == nil) {
1916     [self logWithFormat:@"ERROR(%s): got no data.", __PRETTY_FUNCTION__];
1917     return nil;
1918   }
1919   return [NSDictionary dictionaryWithObject:data forKey:@"data"];
1920 }
1921
1922
1923 static NSArray *_parseFlagArray(NGImap4ResponseParser *self) {
1924   NSString *flags;
1925   
1926   _consumeIfMatch(self, '(');
1927   
1928   flags = _parseUntil(self, ')');
1929   if (![flags isNotEmpty]) {
1930     static NSArray *emptyArray = nil;
1931     if (emptyArray == nil) emptyArray = [[NSArray alloc] init];
1932     return emptyArray;
1933   }
1934   else
1935     return [[flags lowercaseString] componentsSeparatedByString:@" "];
1936 }
1937
1938 static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self,
1939                                         NGMutableHashMap *result_) {
1940   if ((_la(self, 0) == 'F')
1941       && (_la(self, 1) == 'L')
1942       && (_la(self, 2) == 'A')
1943       && (_la(self, 3) == 'G')
1944       && (_la(self, 4) == 'S')      
1945       && (_la(self, 5) == ' ')) {
1946     _consume(self, 6);
1947     [result_ addObject:_parseFlagArray(self) forKey:@"flags"];
1948     _consumeIfMatch(self, '\n');
1949     return YES;
1950   }
1951   return NO;
1952 }
1953
1954 static BOOL _parseBadUntaggedResponse(NGImap4ResponseParser *self,
1955                                      NGMutableHashMap *result_) 
1956 {
1957   if (!((_la(self, 0)=='B') && (_la(self, 1)=='A') && (_la(self, 2)=='D')      
1958         && (_la(self, 3) == ' ')))
1959     return NO;
1960
1961   _consume(self, 4);
1962   [result_ addObject:_parseUntil(self, '\n') forKey:@"bad"];
1963   return YES;
1964 }
1965
1966 static BOOL _parseNoOrOkArguments(NGImap4ResponseParser *self,
1967                                   NGMutableHashMap *result_, NSString *_key) 
1968 {
1969   NSString *obj;
1970
1971   obj = nil;
1972   
1973   if (_la(self, 0) == '[') {
1974     NSString *key;
1975       
1976     _consume(self, 1);
1977     key = _parseUntil2(self, ']', ' ');
1978
1979     /* possible kinds of untagged OK responses are either
1980      * OK [ALERT] System shutdown in 10 minutes
1981      or               
1982      * OK [UNSEEN 14]
1983      or
1984      * OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen \*)]
1985      */
1986     if (_la(self, 0) == ']') {
1987
1988       _consume(self, 1);
1989       if (_la(self, 0) == ' ') {
1990         id value;
1991         
1992         _consume(self, 1);
1993         value = _parseUntil(self, '\n');
1994         if ([value isNotEmpty]) {
1995           obj = [[NSDictionary alloc] 
1996                   initWithObjects:&value forKeys:&key count:1];
1997         }
1998         else
1999           obj = [key retain];
2000       }
2001       else {
2002         obj = [key retain];
2003         _parseUntil(self, '\n');
2004       }
2005     }
2006     else { /* _la(self, 0) should be ' ' */
2007       id value;
2008
2009       value = nil;
2010       
2011       _consume(self, 1);
2012       if (_la(self, 0) == '(') {
2013         value = _parseFlagArray(self);
2014         _consume(self, 1); /* consume ']' */
2015       }
2016       else {
2017         value = _parseUntil(self, ']');
2018       }
2019       {
2020         id tmp;
2021
2022         tmp = _parseUntil(self, '\n');
2023         
2024         obj = [[NSDictionary alloc] initWithObjectsAndKeys:
2025                             value, key,
2026                             tmp,   @"comment", nil];
2027       }
2028     }
2029   }
2030   else
2031     obj = [_parseUntil(self, '\n') retain];
2032
2033   [result_ addObject:obj forKey:_key];
2034   [obj release];
2035   return YES;
2036 }
2037
2038
2039 static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self,
2040                                      NGMutableHashMap *result_) 
2041 {
2042   if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' ')))
2043     return NO;
2044
2045   _consume(self, 3);
2046   return _parseNoOrOkArguments(self, result_, @"no");
2047 }
2048
2049 static BOOL _parseOkUntaggedResponse(NGImap4ResponseParser *self,
2050                                      NGMutableHashMap *result_) 
2051 {
2052   if (!((_la(self, 0)=='O') && (_la(self, 1)=='K') && (_la(self, 2)==' ')))
2053     return NO;
2054   
2055   _consume(self, 3);
2056   return _parseNoOrOkArguments(self, result_, @"ok");
2057 }
2058
2059 static NSNumber *_parseUnsigned(NGImap4ResponseParser *self) {
2060   unsigned      n;
2061   unsigned char c;
2062   BOOL     isNumber;
2063
2064   isNumber = NO;  
2065   n        = 0;  
2066   c        = _la(self, 0);
2067   
2068   while ((c >= '0') && (c <= '9')) {
2069     _consume(self, 1);
2070     isNumber = YES;
2071     n        = 10 * n + (c - 48);
2072     c        = _la(self, 0);
2073   }
2074   if (!isNumber)
2075     return nil;
2076   
2077   return [NumClass numberWithUnsignedInt:n];
2078 }
2079
2080 static NSString *_parseUntil(NGImap4ResponseParser *self, char _c) {
2081   /*
2082     Note: this function consumes the stop char (_c)!
2083     normalize \r\n constructions
2084   */
2085   // TODO: optimize!
2086   char            buf[1024], c;
2087   NSMutableString *str;
2088   unsigned        cnt;
2089
2090   cnt = 0;
2091   str = nil;  
2092   while ((c = _la(self, 0)) != _c) {
2093     buf[cnt] = c;
2094     _consume(self, 1);
2095     cnt++;
2096     if (cnt == 1024) {
2097       if (str == nil) {
2098         str = (NSMutableString *)
2099           [NSMutableString stringWithCString:buf length:1024];
2100       }
2101       else {
2102         NSString *s;
2103         
2104         s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024];
2105         [str appendString:s];
2106         [s release];
2107       }
2108       cnt = 0;
2109     }
2110   }
2111   _consume(self,1); /* consume known stop char */
2112   if (_c == '\n' && cnt > 0) {
2113     if (buf[cnt-1] == '\r')
2114       cnt--;
2115   }
2116   
2117   if (str == nil)
2118     return [StrClass stringWithCString:buf length:cnt];
2119   else {
2120     NSString *s, *s2;
2121     
2122     s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt];
2123     s2 = [str stringByAppendingString:s];
2124     [s release];
2125     return s2;
2126   }
2127 }
2128
2129 static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2){
2130   /* _parseUntil2(self, char, char) doesn`t consume the stop-chars */
2131   char            buf[1024], c;
2132   NSMutableString *str;
2133   unsigned        cnt;
2134
2135   cnt = 0;
2136   c   = _la(self, 0);
2137   str = nil;
2138   
2139   while ((c != _c1) && (c != _c2)) {
2140     buf[cnt] = c;
2141     _consume(self, 1);
2142     cnt++;
2143     if (cnt == 1024) {
2144       if (str == nil)
2145         str = (NSMutableString *)
2146                  [NSMutableString stringWithCString:buf length:1024];
2147       else {
2148         NSString *s;
2149         
2150         s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024];
2151         [str appendString:s];
2152         [s release];
2153       }
2154       
2155       cnt = 0;
2156     }
2157     c = _la(self, 0);    
2158   }
2159
2160   if (str == nil)
2161     return [StrClass stringWithCString:buf length:cnt];
2162   
2163   {
2164     NSString *s, *s2;
2165     
2166     s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt];
2167     s2 = [str stringByAppendingString:s];
2168     [s release];
2169     return s2;
2170   }
2171 }
2172
2173 - (NSException *)exceptionForFailedMatch:(unsigned char)_match
2174   got:(unsigned char)_avail
2175 {
2176   NSException *e;
2177   
2178   e = [NGImap4ParserException alloc];
2179   if (self->debug) {
2180     e = [e initWithFormat:@"unexpected char <%c> expected <%c> <%@>",
2181            _avail, _match, self->serverResponseDebug];
2182   }
2183   else {
2184     e = [e initWithFormat:@"unexpected char <%c> expected <%c>",
2185              _avail, _match];
2186   }
2187   return [e autorelease];
2188 }
2189
2190 static __inline__ NSException *_consumeIfMatch(NGImap4ResponseParser *self, 
2191                                                unsigned char _match) 
2192 {
2193   NSException *e;
2194   
2195   if (_la(self,0) == _match) {
2196     _consume(self, 1);
2197     return nil;
2198   }
2199   
2200   e = [self exceptionForFailedMatch:_match got:_la(self, 0)];
2201   [self setLastException:e];
2202   return e;
2203 }
2204
2205 static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt) {
2206   /* Normalize end of line */
2207
2208   if (_cnt == 0)
2209     return;
2210   
2211   _cnt +=  (__la(self, _cnt - 1) == '\r') ? 1 : 0;
2212   
2213   if (self->debug) {
2214     unsigned cnt;
2215     
2216     for (cnt = 0; cnt < _cnt; cnt++) {
2217       NSString *s;
2218       unichar c = _la(self, cnt);
2219       
2220       if (c == '\r')
2221         continue;
2222         
2223       s = [[StrClass alloc] initWithCharacters:&c length:1];
2224       [self->serverResponseDebug appendString:s];
2225       [s release];
2226       
2227       if (c == '\n') {
2228           if ([self->serverResponseDebug cStringLength] > 2) {
2229             fprintf(stderr, "S[%p]: %s", self,
2230                     [self->serverResponseDebug cString]);
2231           }
2232           [self->serverResponseDebug release];
2233           self->serverResponseDebug = 
2234             [[NSMutableString alloc] initWithCapacity:512];
2235       }
2236     }
2237   }
2238   [self->buffer consume:_cnt];
2239 }
2240
2241 @end /* NGImap4ResponseParser */