~ubuntu-branches/ubuntu/edgy/sope/edgy

« back to all changes in this revision

Viewing changes to sope-mime/NGImap4/NGImap4ResponseParser.m

  • Committer: Bazaar Package Importer
  • Author(s): Sebastian Ley
  • Date: 2005-08-19 16:53:31 UTC
  • Revision ID: james.westby@ubuntu.com-20050819165331-hs683wz1osm708pw
Tags: upstream-4.4rc.2
ImportĀ upstreamĀ versionĀ 4.4rc.2

Show diffs side-by-side

added added

removed removed

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