2
Copyright (C) 2000-2005 SKYRIX Software AG
4
This file is part of SOPE.
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
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.
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
22
#include "NGImap4ResponseParser.h"
23
#include "NGImap4Support.h"
24
#include "NGImap4Envelope.h"
25
#include "NGImap4EnvelopeAddress.h"
28
// TODO(hh): code is now prepared for last-exception, but currently it just
29
// raises and may leak the exception object
31
@interface NGImap4ResponseParser(ParsingPrivates)
32
- (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_;
33
- (NSDictionary *)_parseBodyContent;
35
- (NSData *)_parseData;
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_;
47
- (NSArray *)_parseThread;
51
@implementation NGImap4ResponseParser
53
#define __la(__SELF__, __PEEKPOS) \
54
((__SELF__->la == NULL) \
55
? [__SELF__->buffer la:__PEEKPOS]\
56
: __SELF__->la(__SELF__->buffer, @selector(la:), __PEEKPOS))
58
static __inline__ int _la(NGImap4ResponseParser *self, unsigned _laCnt) {
59
register unsigned char c = __la(self, _laCnt);
62
? _la(self, _laCnt + 1)
65
static __inline__ BOOL _matchesString(NGImap4ResponseParser *self,
68
register unsigned int i;
70
for (i = 0; s[i] != '\0'; i++) {
71
if (_la(self, i) != s[i])
77
static NSDictionary *_parseBody(NGImap4ResponseParser *self);
78
static NSString *_parseBodyString(NGImap4ResponseParser *self,
80
static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
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);
107
static __inline__ NSException *_consumeIfMatch
108
(NGImap4ResponseParser *self, unsigned char _m);
109
static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt);
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);
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;
139
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
140
static BOOL didInit = NO;
144
null = [[NSNull null] retain];
146
encoding = [NGMimePartParser defaultHeaderFieldEncoding];
147
defCStringEncoding = [NSString defaultCStringEncoding];
149
debugOn = [ud boolForKey:@"ImapDebugEnabled"];
150
debugDataOn = [ud boolForKey:@"ImapDebugDataEnabled"];
151
UseMemoryMappedData = [ud boolForKey:@"NoMemoryMappedDataForImapBlobs"]?0:1;
152
Imap4MMDataBoundary = [ud integerForKey:@"Imap4MMDataBoundary"];
154
if (Imap4MMDataBoundary < 10)
155
/* Note: this should be larger than a usual header size! */
156
Imap4MMDataBoundary = 2 * LaSize;
158
StrClass = [NSString class];
159
NumClass = [NSNumber class];
160
DataClass = [NSData class];
161
YesNum = [[NumClass numberWithBool:YES] retain];
162
NoNum = [[NumClass numberWithBool:NO] retain];
165
+ (id)parserWithStream:(id<NGActiveSocket>)_stream {
166
NGImap4ResponseParser *parser;
168
parser = [NGImap4ResponseParser alloc]; /* seperate line to keep gcc happy */
169
return [[parser initWithStream:_stream] autorelease];
172
- (id)initWithStream:(id<NGActiveSocket>)_stream {
173
// designated initializer
174
if (_stream == nil) {
175
[self logWithFormat:@"%s: got no stream ...", __PRETTY_FUNCTION__];
180
if ((self = [super init])) {
183
s = [(NGBufferedStream *)[NGBufferedStream alloc] initWithSource:_stream];
184
self->buffer = [NGByteBuffer alloc];
185
self->buffer = [self->buffer initWithSource:s la:LaSize];
188
if ([self->buffer respondsToSelector:@selector(methodForSelector:)])
189
self->la = (int(*)(id, SEL, unsigned))
190
[self->buffer methodForSelector:@selector(la:)];
192
self->debug = debugOn;
199
[NSException raise:@"InvalidUseOfMethodException"
201
@"calling -init on the NGImap4ResponseParser is not allowed"];
206
[self->buffer release];
208
[self->serverResponseDebug release];
212
/* exception handling */
214
- (void)setLastException:(NSException *)_exc {
215
// TODO: support last exception
220
** Parse Sieve Responses
223
- (NGHashMap *)parseSieveResponse {
224
NGMutableHashMap *result;
227
if (self->serverResponseDebug != nil)
228
[self->serverResponseDebug release];
229
self->serverResponseDebug = [[NSMutableString alloc] initWithCapacity:512];
231
result = [NGMutableHashMap hashMapWithCapacity:64];
233
if (_la(self,0) == -1) {
234
[self setLastException:[self->buffer lastException]];
238
_parseSieveRespone(self, result);
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?
246
NGMutableHashMap *result;
251
[self->serverResponseDebug release]; self->serverResponseDebug = nil;
252
self->serverResponseDebug = [[NSMutableString alloc] initWithCapacity:512];
255
result = [NGMutableHashMap hashMapWithCapacity:64];
257
if (_la(self, 0) == -1) {
258
[self logWithFormat:@"%s: catched: %@", __PRETTY_FUNCTION__,
259
[self->buffer lastException]];
262
*ex_ = [self->buffer lastException];
266
[self setLastException:[self->buffer lastException]];
270
for (endOfCommand = NO; !endOfCommand; ) {
275
if (l0 == '*') { /* those starting with '* ' */
276
_parseUntaggedResponse(self, result);
277
if ([result objectForKey:@"bye"]) {
282
if ([result objectForKey:@"ok"] != nil)
287
else if (l0 == '+') { /* starting with a '+'? */
288
[self _parseContinuationResponseIntoHashMap:result];
291
else if (isdigit(l0)) {
292
/* those starting with a number '24 ', eg '24 OK Completed' */
293
endOfCommand = (_parseTaggedResponse(self, result) == _tag);
298
- (NGHashMap *)parseResponseForTagId:(int)_tag {
300
NSException *e = nil;
303
hm = [self parseResponseForTagId:_tag exception:&e];
305
[self setLastException:e];
311
static void _parseSieveRespone(NGImap4ResponseParser *self,
312
NGMutableHashMap *result_)
314
if (_parseGreetingsSieveResponse(self, result_))
316
if (_parseDataSieveResponse(self, result_)) // la: 1
318
if (_parseOkSieveResponse(self, result_)) // la: 2
320
if (_parseNoSieveResponse(self, result_)) // la: 2
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;
330
unsigned char buf[LaSize + 2];
331
unsigned char tmpBuf[LaSize + 2];
332
unsigned wasRead = 0;
334
signed char lastChar; // must be signed
336
if (debugDataOn) [self logWithFormat:@" using memory mapped data ..."];
339
Pi = [[NSProcessInfo processInfo] retain];
341
path = [Pi temporaryFileName];
342
stream = [NGFileStream alloc]; /* extra line to keep gcc happy */
343
stream = [stream initWithPath:path];
345
if (![stream openInMode:NGFileWriteOnly]) {
348
e = [[NGImap4ParserException alloc]
349
initWithFormat:@"Could not open temporary file %@", path];
350
[self setLastException:[e autorelease]];
355
while (wasRead < _size) {
356
unsigned readCnt, bufCnt, tmpSize, cnt, tmpBufCnt;
360
if (lastChar != -1) {
361
buf[bufCnt++] = lastChar;
365
[self->buffer la:(_size - wasRead < LaSize)
369
readCnt = [self->buffer readBytes:buf+bufCnt count:_size - wasRead];
374
tmpSize = bufCnt - 1;
378
while (cnt < tmpSize) {
379
if ((buf[cnt] == '\r') && (buf[cnt+1] == '\n')) {
382
tmpBuf[tmpBufCnt++] = buf[cnt++];
387
[stream writeBytes:tmpBuf count:tmpBufCnt];
390
[stream writeBytes:&lastChar count:1];
393
[stream release]; stream = nil;
394
result = [DataClass dataWithContentsOfMappedFile:path];
395
[[NSFileManager defaultManager] removeFileAtPath:path handler:nil];
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;
407
buf = calloc(_size + 10, sizeof(char));
409
while (wasRead < _size) {
410
[self->buffer la:(_size - wasRead < LaSize) ? (_size - wasRead) : LaSize];
412
wasRead += [self->buffer readBytes:(buf + wasRead) count:(_size-wasRead)];
415
/* normalize response \r\n -> \n */
417
tmpBuf = calloc(_size + 10, sizeof(char));
420
tmpSize = _size == 0 ? 0 : _size - 1;
421
while (tmpBufCnt < tmpSize && cnt < _size) {
422
if ((buf[cnt] == '\r') && (buf[cnt + 1] == '\n'))
425
tmpBuf[tmpBufCnt] = buf[cnt];
430
tmpBuf[tmpBufCnt] = buf[cnt];
435
result = [DataClass dataWithBytesNoCopy:tmpBuf length:tmpBufCnt];
437
if (buf != NULL) free(buf); buf = NULL;
440
- (NSData *)_parseData {
445
// TODO: split up method
450
if (_la(self, 0) != '{')
453
if (debugDataOn) [self logWithFormat:@"parse data ..."];
458
_consume(self, 1); // '{'
459
if ((sizeNum = _parseUnsigned(self)) == nil) {
462
e = [[NGImap4ParserException alloc]
463
initWithFormat:@"expect a number between {}"];
464
[self setLastException:[e autorelease]];
467
if (debugDataOn) [self logWithFormat:@" parse data, size: %@", sizeNum];
468
_consumeIfMatch(self, '}');
469
_consumeIfMatch(self, '\n');
471
if ((size = [sizeNum intValue]) == 0) {
472
[self logWithFormat:@"ERROR(%s): got content size '0'!",
473
__PRETTY_FUNCTION__];
477
if (UseMemoryMappedData && (size > Imap4MMDataBoundary))
478
return [self _parseDataToFile:size];
480
return [self _parseDataIntoRAM:size];
483
static int _parseTaggedResponse(NGImap4ResponseParser *self,
484
NGMutableHashMap *result_)
489
NSString *desc = nil;
490
NSString *flag = nil;
492
if ((tag = _parseUnsigned(self)) == nil) {
496
e = [[NGImap4ParserException alloc]
497
initWithFormat:@"expect a number at begin of tagged response <%@>",
498
self->serverResponseDebug];
501
e = [[NGImap4ParserException alloc]
502
initWithFormat:@"expect a number at begin of tagged response"];
505
[self setLastException:e];
509
_consumeIfMatch(self, ' ');
510
res = [_parseUntil(self, ' ') lowercaseString];
511
if (_la(self, 0) == '[') { /* Found flag like [READ-ONLY] */
513
flag = _parseUntil(self, ']');
515
desc = _parseUntil(self, '\n');
517
ATTENTION: if no flag was set, flag == nil, in this case all key-value
518
pairs after flag will be ignored
520
d = [[NSDictionary alloc] initWithObjectsAndKeys:
523
desc, @"description",
525
[result_ addObject:d forKey:@"ResponseResult"];
527
return [tag intValue];
530
static void _parseUntaggedResponse(NGImap4ResponseParser *self,
531
NGMutableHashMap *result_)
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, ' ');
543
if (l1 == 'A' && _parseBadUntaggedResponse(self, result_)) // la: 3
545
if (l1 == 'Y' && [self _parseByeUntaggedResponseIntoHashMap:result_]) // 3
550
if ([self _parseCapabilityResponseIntoHashMap:result_]) // la: 10
555
if (_parseFlagsUntaggedResponse(self, result_)) // la: 5
560
if ([self _parseListOrLSubResponseIntoHashMap:result_]) // la: 4
565
if (_parseNoUntaggedResponse(self, result_)) // la: 2
570
if (_parseOkUntaggedResponse(self, result_)) // la: 2
571
/* eg "* OK Completed" */
579
switch (_la(self, 1)) {
581
if ([self _parseSortResponseIntoHashMap:result_]) // la: 4
585
if ([self _parseSearchResponseIntoHashMap:result_]) // la: 5
589
if ([self _parseStatusResponseIntoHashMap:result_]) // la: 6
590
/* eg "* STATUS INBOX (MESSAGES 0 RECENT 0 UNSEEN 0)" */
597
if (_parseThreadResponse(self, result_)) // la: 6
602
if ([self _parseQuotaResponseIntoHashMap:result_]) // la: 6
604
if ([self _parseQuotaRootResponseIntoHashMap:result_]) // la: 10
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 ..." */
616
// TODO: what if none matches?
617
[self logWithFormat:@"%s: no matching tag specifier?", __PRETTY_FUNCTION__];
620
- (void)_parseContinuationResponseIntoHashMap:(NGMutableHashMap *)result_ {
621
_consumeIfMatch(self, '+');
622
_consumeIfMatch(self, ' ');
624
[result_ addObject:YesNum forKey:@"ContinuationResponse"];
625
[result_ addObject:_parseUntil(self, '\n') forKey:@"description"];
628
- (NSString *)_parseQuotedString {
629
/* parse a quoted string, eg '"' */
630
if (_la(self, 0) == '"') {
632
return _parseUntil(self, '"');
636
- (void)_consumeOptionalSpace {
637
if (_la(self, 0) == ' ') _consume(self, 1);
640
- (BOOL)_parseListOrLSubResponseIntoHashMap:(NGMutableHashMap *)result_ {
641
NSArray *flags = nil;
642
NSString *delim = nil;
643
NSString *name = nil;
646
if (!_matchesString(self, "LIST ") && !_matchesString(self, "LSUB "))
649
_consume(self, 5); /* consume 'LIST ' or 'LSUB ' */
650
flags = _parseFlagArray(self);
651
_consumeIfMatch(self, ' ');
653
if (_la(self, 0) == '"') {
654
delim = [self _parseQuotedString];
655
_consumeIfMatch(self, ' ');
658
_parseUntil(self, ' ');
661
if (_la(self, 0) == '"') {
662
name = [self _parseQuotedString];
663
_parseUntil(self, '\n');
666
name = _parseUntil(self, '\n');
668
d = [[NSDictionary alloc] initWithObjectsAndKeys:
671
delim, @"delimiter", nil];
672
[result_ addObject:d forKey:@"list"];
677
- (BOOL)_parseCapabilityResponseIntoHashMap:(NGMutableHashMap *)result_ {
679
NSEnumerator *enumerator;
681
NSMutableArray *array;
684
if (!_matchesString(self, "CAPABILITY "))
687
caps = _parseUntil(self, '\n');
689
array = [[NSMutableArray alloc] initWithCapacity:16];
691
enumerator = [[caps componentsSeparatedByString:@" "] objectEnumerator];
692
while ((obj = [enumerator nextObject]) != nil)
693
[array addObject:[obj lowercaseString]];
696
[result_ addObject:tmp forKey:@"capability"];
698
[array release]; array = nil;
699
[tmp release]; tmp = nil;
703
- (BOOL)_parseSearchResponseIntoHashMap:(NGMutableHashMap *)result_ {
704
NSMutableArray *msn = nil;
706
if (!_matchesString(self, "SEARCH"))
711
msn = [NSMutableArray arrayWithCapacity:128];
713
while (_la(self, 0) == ' ') {
715
[msn addObject:_parseUnsigned(self)];
717
_parseUntil(self, '\n');
718
[result_ addObject:msn forKey:@"search"];
722
- (BOOL)_parseSortResponseIntoHashMap:(NGMutableHashMap *)result_ {
723
NSMutableArray *msn = nil;
725
if (!_matchesString(self, "SORT"))
730
msn = [NSMutableArray arrayWithCapacity:128];
732
while (_la(self, 0) == ' ') {
734
[msn addObject:_parseUnsigned(self)];
736
_parseUntil(self, '\n');
737
[result_ addObject:msn forKey:@"sort"];
741
- (BOOL)_parseQuotaResponseIntoHashMap:(NGMutableHashMap *)result_ {
743
NSMutableDictionary *parse;
744
NSMutableDictionary *quota;
746
if (!_matchesString(self, "QUOTA "))
751
quota = [result_ objectForKey:@"quota"];
754
quota = [NSMutableDictionary dictionaryWithCapacity:2];
755
[result_ setObject:quota forKey:@"quota"];
758
parse = [NSMutableDictionary dictionaryWithCapacity:3];
759
qRoot = _parseUntil2(self, ' ', '\n');
761
if (_la(self, 0) == ' ') {
764
if (_la(self, 0) == '(') {
766
if (_la(self, 0) == ')') { /* empty quota response */
772
key = _parseUntil(self, ' ');
773
key = [key lowercaseString];
774
if ([key isEqualToString:@"storage"]) {
775
NSString *used, *max;
777
used = _parseUntil(self, ' ');
778
max = _parseUntil(self, ')');
780
[parse setObject:used forKey:@"usedSpace"];
781
[parse setObject:max forKey:@"maxQuota"];
786
v = _parseUntil(self, ')');
788
[parse setObject:v forKey:@"resource"];
792
[quota setObject:parse forKey:qRoot];
794
_parseUntil(self, '\n');
799
- (BOOL)_parseQuotaRootResponseIntoHashMap:(NGMutableHashMap *)result_ {
800
NSString *folderName, *folderRoot;
801
NSMutableDictionary *dict;
803
if (!_matchesString(self, "QUOTAROOT "))
808
dict = [result_ objectForKey:@"quotaRoot"];
811
dict = [NSMutableDictionary dictionaryWithCapacity:2];
812
[result_ setObject:dict forKey:@"quotaRoot"];
814
if (_la(self, 0) == '"') {
816
folderName = _parseUntil(self, '"');
819
folderName = _parseUntil2(self, '\n', ' ');
821
if (_la(self, 0) == ' ') {
823
folderRoot = _parseUntil(self, '\n');
829
if ([folderName length] && [folderRoot length]) {
830
[dict setObject:folderRoot forKey:folderName];
835
- (NSArray *)_parseThread {
836
NSMutableArray *array;
839
array = [NSMutableArray arrayWithCapacity:64];
841
if (_la(self, 0) == '(')
845
if (_la(self, 0) == '(') {
848
a = [self _parseThread];
849
if (a != nil) [array addObject:a];
851
else if ((msg = _parseUnsigned(self))) {
852
[array addObject:msg];
857
if (_la(self, 0) == ')')
859
else if (_la(self, 0) == ' ')
862
_consumeIfMatch(self, ')');
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')) {
880
if (_la(self, 0) == ' ') {
884
[result_ addObject:[NSArray array] forKey:@"thread"];
887
msn = [NSMutableArray arrayWithCapacity:64];
888
while ((_la(self, 0) == '(')) {
891
if ((array = [self _parseThread]) != nil)
892
[msn addObject:array];
894
_parseUntil(self, '\n');
895
[result_ addObject:msn forKey:@"thread"];
901
- (BOOL)_parseStatusResponseIntoHashMap:(NGMutableHashMap *)result_ {
902
NSString *name = nil;
903
NSMutableDictionary *flags = nil;
906
if (!_matchesString(self, "STATUS "))
911
if (_la(self, 0) == '"') {
913
name = _parseUntil(self, '"');
914
_consumeIfMatch(self, ' ');
917
name = _parseUntil(self, ' ');
919
_consumeIfMatch(self, '(');
920
flags = [NSMutableDictionary dictionaryWithCapacity:8];
922
while (_la(self, 0) != ')') {
923
NSString *key = _parseUntil(self, ' ');
924
id value = _parseUntil2(self, ' ', ')');
926
if (_la(self, 0) == ' ')
929
[flags setObject:[NumClass numberWithInt:[value intValue]]
930
forKey:[key lowercaseString]];
932
_consumeIfMatch(self, ')');
933
_parseUntil(self, '\n');
935
d = [[NSDictionary alloc] initWithObjectsAndKeys:
937
flags, @"flags", nil];
938
[result_ addObject:d forKey:@"status"];
943
- (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_ {
946
if (!_matchesString(self, "BYE "))
950
reason = _parseUntil(self, '\n');
951
[result_ addObject:reason forKey:@"bye"];
955
- (NSString *)_parseQuotedStringOrNIL {
956
if (_la(self, 0) == '"')
957
return [self _parseQuotedString];
958
if (_matchesString(self, "NIL")) {
964
- (id)_parseQuotedStringOrDataOrNIL {
965
if (_la(self, 0) == '"')
966
return [self _parseQuotedString];
967
if (_la(self, 0) == '{')
968
return [self _parseData];
970
if (_matchesString(self, "NIL")) {
977
- (id)_decodeQP:(id)_string headerField:(NSString *)_field {
978
if (![_string isNotNull])
981
if ([_string isKindOfClass:DataClass])
982
return [_string decodeQuotedPrintableValueOfMIMEHeaderField:_field];
984
if ([_string isKindOfClass:StrClass]) {
985
if ([_string length] <= 6 /* minimum size */)
987
if ([_string characterAtIndex:0] == '=' &&
988
[_string characterAtIndex:1] == '?') {
992
[self debugWithFormat:@"WARNING: string with quoted printable info!"];
994
// TODO: this is really expensive ...
995
data = [_string dataUsingEncoding:NSUTF8StringEncoding];
999
qpData = [data decodeQuotedPrintableValueOfMIMEHeaderField:_field];
1000
if (qpData != data) return qpData;
1009
- (NGImap4EnvelopeAddress *)_parseEnvelopeAddressStructure {
1011
Note: returns retained object!
1015
SMTP@at-domain-list(source route)
1019
(NIL NIL "helge.hess" "opengroupware.org")
1021
NGImap4EnvelopeAddress *address;
1022
NSString *pname, *route, *mailbox, *host;
1024
if (_la(self, 0) != '(') {
1025
if (_matchesString(self, "NIL")) {
1027
return (id)[null retain];
1031
_consume(self, 1); // '('
1033
/* parse personal name, can be with quoted printable encoding! */
1035
pname = [self _parseQuotedStringOrNIL];
1036
if ([pname isNotNull])
1037
pname = [self _decodeQP:pname headerField:@"subject"];
1038
[self _consumeOptionalSpace];
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];
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];
1053
address = [[NGImap4EnvelopeAddress alloc] initWithPersonalName:pname
1054
sourceRoute:route mailbox:mailbox
1059
- (NSArray *)_parseEnvelopeAddressStructures {
1062
if (_la(self, 0) != '(') {
1063
if (_matchesString(self, "NIL")) {
1065
return (id)[null retain];
1069
_consume(self, 1); // '('
1072
while (_la(self, 0) != ')') {
1073
NGImap4EnvelopeAddress *address;
1075
if ((address = [self _parseEnvelopeAddressStructure]) == nil)
1076
continue; // TODO: should we stop parsing?
1077
if (![address isNotNull])
1080
if (ma == nil) ma = [NSMutableArray arrayWithCapacity:4];
1081
[ma addObject:address];
1082
[address release]; /* the parse returns a retained object! */
1085
if (_la(self, 0) != ')') {
1086
[self logWithFormat:
1087
@"WARNING: IMAP4 envelope address not properly closed!"];
1094
- (id)_parseEnvelope {
1096
http://www.hunnysoft.com/rfc/rfc3501.html
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 ")"
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"))
1109
"<20040622134354.F11133CEB14@mail.opengroupware.org>"
1113
static NGMimeRFC822DateHeaderFieldParser *dateParser = nil;
1114
NGImap4Envelope *env;
1118
if (dateParser == nil)
1119
dateParser = [[NGMimeRFC822DateHeaderFieldParser alloc] init];
1121
if (_la(self, 0) != '(')
1125
env = [[[NGImap4Envelope alloc] init] autorelease];
1129
dateStr = [self _parseQuotedStringOrNIL];
1130
[self _consumeOptionalSpace];
1131
if ([dateStr isNotNull])
1132
env->date = [[dateParser parseValue:dateStr ofHeaderField:nil] retain];
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]
1143
[self _consumeOptionalSpace];
1146
[self logWithFormat:@"ERROR(%s): failed on subject(%c): %@",
1147
__PRETTY_FUNCTION__, _la(self, 0), self->serverResponseDebug];
1151
/* parse addresses */
1153
if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1154
env->from = [tmp isNotNull] ? [[tmp lastObject] copy] : nil;
1155
[self _consumeOptionalSpace];
1158
[self logWithFormat:@"ERROR(%s): failed on from.", __PRETTY_FUNCTION__];
1161
if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1162
env->sender = [tmp isNotNull] ? [[tmp lastObject] copy] : nil;
1163
[self _consumeOptionalSpace];
1166
[self logWithFormat:@"ERROR(%s): failed on sender.", __PRETTY_FUNCTION__];
1169
if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1170
env->replyTo = [tmp isNotNull] ? [[tmp lastObject] copy] : nil;
1171
[self _consumeOptionalSpace];
1174
if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1175
env->to = [tmp isNotNull] ? [tmp copy] : nil;
1176
[self _consumeOptionalSpace];
1178
if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1179
env->cc = [tmp isNotNull] ? [tmp copy] : nil;
1180
[self _consumeOptionalSpace];
1182
if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1183
env->bcc = [tmp isNotNull] ? [tmp copy] : nil;
1184
[self _consumeOptionalSpace];
1187
if ((tmp = [self _parseQuotedStringOrNIL])) {
1188
env->inReplyTo = [tmp isNotNull] ? [tmp copy] : nil;
1189
[self _consumeOptionalSpace];
1191
if ((tmp = [self _parseQuotedStringOrNIL])) {
1192
env->msgId = [tmp isNotNull] ? [tmp copy] : nil;
1193
[self _consumeOptionalSpace];
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];
1207
- (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_ {
1208
NSMutableDictionary *fetch;
1210
NSString *key = nil;
1212
if ((number = _parseUnsigned(self)) == nil)
1215
_consumeIfMatch(self, ' ');
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]];
1224
/* eg: "FETCH (FLAGS (\Seen) UID 5 RFC822.HEADER {2903}" */
1225
fetch = [[NSMutableDictionary alloc] initWithCapacity:10];
1227
_consume(self, 6); /* "FETCH " */
1228
_consumeIfMatch(self, '(');
1229
while (_la(self, 0) != ')') { /* until closing parent */
1232
key = [_parseUntil(self, ' ') lowercaseString];
1234
[self logWithFormat:@"PARSE KEY: %@", key];
1236
if ([key hasPrefix:@"body["]) {
1237
NSDictionary *content;
1239
if ((content = [self _parseBodyContent]) != nil)
1240
[fetch setObject:content forKey:key];
1242
[self logWithFormat:@"ERROR: got no body content for key: '%@'",key];
1244
else if ([key isEqualToString:@"body"]) {
1245
[fetch setObject:_parseBody(self) forKey:key];
1247
else if ([key isEqualToString:@"flags"]) {
1248
[fetch setObject:_parseFlagArray(self) forKey:key];
1250
else if ([key isEqualToString:@"uid"]) {
1251
[fetch setObject:_parseUnsigned(self) forKey:key];
1253
else if ([key isEqualToString:@"rfc822.size"]) {
1254
[fetch setObject:_parseUnsigned(self) forKey:key];
1256
else if ([key hasPrefix:@"rfc822"]) {
1259
if (_la(self, 0) == '"') {
1263
str = _parseUntil(self, '"');
1264
data = [str dataUsingEncoding:defCStringEncoding];
1267
data = [self _parseData];
1269
if (data != nil) [fetch setObject:data forKey:key];
1271
else if ([key isEqualToString:@"envelope"]) {
1274
if ((envelope = [self _parseEnvelope]) != nil)
1275
[fetch setObject:envelope forKey:key];
1277
[self logWithFormat:@"ERROR: could not parse envelope!"];
1279
else if ([key isEqualToString:@"bodystructure"]) {
1283
e = [[NGImap4ParserException alloc]
1284
initWithFormat:@"bodystructure fetch result not yet supported!"];
1285
[self setLastException:[e autorelease]];
1288
else if ([key isEqualToString:@"internaldate"]) {
1292
e = [[NGImap4ParserException alloc]
1293
initWithFormat:@"INTERNALDATE fetch result not yet supported!"];
1294
[self setLastException:[e autorelease]];
1300
e = [[NGImap4ParserException alloc] initWithFormat:
1301
@"unsupported fetch key: %@",
1303
[self setLastException:[e autorelease]];
1307
if (_la(self, 0) == ' ')
1311
[fetch setObject:number forKey:@"msn"];
1312
[result_ addObject:fetch forKey:@"fetch"];
1313
_consume(self, 1); /* consume ')' */
1314
_consumeIfMatch(self, '\n');
1316
else { /* no correct fetch line */
1317
_parseUntil(self, '\n');
1320
[fetch release]; fetch = nil;
1324
static BOOL _parseGreetingsSieveResponse(NGImap4ResponseParser *self,
1325
NGMutableHashMap *result_)
1329
while (!(isOK = _parseOkSieveResponse(self, result_))) {
1330
NSString *key, *value;
1332
if (!(key = _parseStringSieveResponse(self))) {
1335
if (_la(self, 0) == ' ') {
1338
if (!(value = _parseStringSieveResponse(self))) {
1345
_parseUntil(self, '\n');
1346
[result_ addObject:value forKey:[key lowercaseString]];
1351
static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self,
1352
NGMutableHashMap *result_)
1357
if ((data = [self _parseData]) == nil)
1360
str = [[StrClass alloc] initWithData:data encoding:defCStringEncoding];
1361
[result_ setObject:str forKey:@"data"];
1362
[str release]; str = nil;
1366
static BOOL _parseOkSieveResponse(NGImap4ResponseParser *self,
1367
NGMutableHashMap *result_)
1369
if (!((_la(self, 0) == 'O') && (_la(self, 1) == 'K')))
1374
if (_la(self, 0) == ' ') {
1377
if ((reason = _parseContentSieveResponse(self)))
1378
[result_ addObject:reason forKey:@"reason"];
1380
_parseUntil(self, '\n');
1382
[result_ addObject:YesNum forKey:@"ok"];
1386
static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self,
1387
NGMutableHashMap *result_)
1391
if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' ')))
1396
data = _parseContentSieveResponse(self);
1398
[result_ addObject:NoNum forKey:@"ok"];
1399
if (data) [result_ addObject:data forKey:@"reason"];
1403
static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self) {
1407
if ((str = _parseStringSieveResponse(self)))
1410
if ((data = [self _parseData]) == nil)
1413
return [[[StrClass alloc] initWithData:data encoding:defCStringEncoding]
1417
static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self) {
1418
if (_la(self, 0) != '"')
1422
return _parseUntil(self, '"');
1425
static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
1426
BOOL _convertString,
1432
if (_la(self, 0) == '"') {
1434
str = _parseUntil(self, '"');
1436
else if (_la(self, 0) == '{') {
1439
if (debugDataOn) [self logWithFormat:@"parse body decode string"];
1440
data = [self _parseData];
1443
data = [data decodeQuotedPrintableValueOfMIMEHeaderField:nil];
1445
return [[[StrClass alloc] initWithData:data encoding:encoding]
1449
str = _parseUntil2(self, ' ', ')');
1451
if (_convertString) {
1452
if ([[str lowercaseString] isEqualToString:@"nil"])
1458
d = [str dataUsingEncoding:defCStringEncoding];
1459
d = [d decodeQuotedPrintableValueOfMIMEHeaderField:nil];
1461
if ([d isKindOfClass:StrClass])
1464
str = [[[StrClass alloc] initWithData:d encoding:encoding]
1472
static NSString *_parseBodyString(NGImap4ResponseParser *self,
1473
BOOL _convertString)
1475
return _parseBodyDecodeString(self, _convertString, NO /* no decode */);
1478
static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self)
1480
NSMutableDictionary *list;
1482
if (_la(self, 0) == '(') {
1485
list = [NSMutableDictionary dictionaryWithCapacity:4];
1487
while (_la(self,0) != ')') {
1488
NSString *key, *value;
1490
if (_la(self, 0) == ' ')
1493
key = _parseBodyString(self, YES);
1494
_consumeIfMatch(self, ' ');
1495
value = _parseBodyDecodeString(self, YES, YES);
1497
[list setObject:value forKey:[key lowercaseString]];
1499
_consumeIfMatch(self, ')');
1503
str = _parseBodyString(self, YES);
1505
if ([str length] > 0) {
1506
NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str);
1508
list = (id)[NSDictionary dictionary];
1513
static NSArray *_parseAddressStructure(NGImap4ResponseParser *self) {
1514
NSString *personalName, *sourceRoute, *mailboxName, *hostName;
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];
1532
static NSArray *_parseParenthesizedAddressList(NGImap4ResponseParser *self) {
1533
NSMutableArray *result;
1534
result = [NSMutableArray arrayWithCapacity:8];
1536
if (_la(self, 0) == '(') {
1538
while (_la(self, 0) != ')') {
1539
[result addObject:_parseAddressStructure(self)];
1545
str = _parseBodyString(self, YES);
1547
if ([str length] > 0) {
1548
NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str);
1550
result = (id)[NSArray array];
1555
static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self) {
1556
NSString *type, *subtype, *bodyId, *description,
1557
*encoding, *bodysize;
1558
NSDictionary *parameterList;
1559
NSMutableDictionary *dict;
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);
1575
dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1577
subtype, @"subtype",
1578
parameterList, @"parameterList",
1580
description, @"description",
1581
encoding, @"encoding",
1582
bodysize, @"size", nil];
1584
if ([type isEqualToString:@"text"]) {
1585
_consumeIfMatch(self, ' ');
1586
[dict setObject:_parseBodyString(self, YES) forKey:@"lines"];
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"];
1622
static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self) {
1623
NSMutableArray *parts;
1626
parts = [NSMutableArray arrayWithCapacity:4];
1628
while (_la(self, 0) == '(') {
1629
[parts addObject:_parseBody(self)];
1631
_consumeIfMatch(self, ' ');
1632
kind = _parseBodyString(self, YES);
1633
return [NSDictionary dictionaryWithObjectsAndKeys:
1635
@"multipart", @"type",
1636
kind , @"subtype", nil];
1639
static NSDictionary *_parseBody(NGImap4ResponseParser *self) {
1640
NSDictionary *result;
1642
_consumeIfMatch(self, '(');
1644
if (_la(self, 0) == '(') {
1645
result = _parseMultipartBody(self);
1648
result = _parseSingleBody(self);
1650
if (_la(self,0) != ')') {
1653
str = _parseUntil(self, ')');
1654
NSLog(@"%s: got noparsed content %@", __PRETTY_FUNCTION__,
1663
- (NSDictionary *)_parseBodyContent {
1666
if (_la(self, 0) == '"') {
1670
str = _parseUntil(self, '"');
1671
data = [str dataUsingEncoding:defCStringEncoding];
1674
data = [self _parseData];
1677
[self logWithFormat:@"ERROR(%s): got no data.", __PRETTY_FUNCTION__];
1680
return [NSDictionary dictionaryWithObject:data forKey:@"data"];
1684
static NSArray *_parseFlagArray(NGImap4ResponseParser *self) {
1687
_consumeIfMatch(self, '(');
1689
flags = _parseUntil(self, ')');
1690
if ([flags length] == 0) {
1691
static NSArray *emptyArray = nil;
1692
if (emptyArray == nil) emptyArray = [[NSArray alloc] init];
1696
return [[flags lowercaseString] componentsSeparatedByString:@" "];
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) == ' ')) {
1708
[result_ addObject:_parseFlagArray(self) forKey:@"flags"];
1709
_consumeIfMatch(self, '\n');
1715
static BOOL _parseBadUntaggedResponse(NGImap4ResponseParser *self,
1716
NGMutableHashMap *result_)
1718
if (!((_la(self, 0)=='B') && (_la(self, 1)=='A') && (_la(self, 2)=='D')
1719
&& (_la(self, 3) == ' ')))
1723
[result_ addObject:_parseUntil(self, '\n') forKey:@"bad"];
1727
static BOOL _parseNoOrOkArguments(NGImap4ResponseParser *self,
1728
NGMutableHashMap *result_, NSString *_key)
1734
if (_la(self, 0) == '[') {
1738
key = _parseUntil2(self, ']', ' ');
1740
/* possible kinds of untagged OK responses are either
1741
* OK [ALERT] System shutdown in 10 minutes
1745
* OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen \*)]
1747
if (_la(self, 0) == ']') {
1750
if (_la(self, 0) == ' ') {
1754
value = _parseUntil(self, '\n');
1755
if ([value length] > 0) {
1756
obj = [[NSDictionary alloc]
1757
initWithObjects:&value forKeys:&key count:1];
1764
_parseUntil(self, '\n');
1767
else { /* _la(self, 0) should be ' ' */
1773
if (_la(self, 0) == '(') {
1774
value = _parseFlagArray(self);
1775
_consume(self, 1); /* consume ']' */
1778
value = _parseUntil(self, ']');
1783
tmp = _parseUntil(self, '\n');
1785
obj = [[NSDictionary alloc] initWithObjectsAndKeys:
1787
tmp, @"comment", nil];
1792
obj = [_parseUntil(self, '\n') retain];
1794
[result_ addObject:obj forKey:_key];
1800
static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self,
1801
NGMutableHashMap *result_)
1803
if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' ')))
1807
return _parseNoOrOkArguments(self, result_, @"no");
1810
static BOOL _parseOkUntaggedResponse(NGImap4ResponseParser *self,
1811
NGMutableHashMap *result_)
1813
if (!((_la(self, 0)=='O') && (_la(self, 1)=='K') && (_la(self, 2)==' ')))
1817
return _parseNoOrOkArguments(self, result_, @"ok");
1820
static NSNumber *_parseUnsigned(NGImap4ResponseParser *self) {
1829
while ((c >= '0') && (c <= '9')) {
1832
n = 10 * n + (c - 48);
1838
return [NumClass numberWithUnsignedInt:n];
1841
static NSString *_parseUntil(NGImap4ResponseParser *self, char _c) {
1843
Note: this function consumes the stop char (_c)!
1844
normalize \r\n constructions
1848
NSMutableString *str;
1853
while ((c = _la(self, 0)) != _c) {
1859
str = (NSMutableString *)
1860
[NSMutableString stringWithCString:buf length:1024];
1865
s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024];
1866
[str appendString:s];
1872
_consume(self,1); /* consume known stop char */
1873
if (_c == '\n' && cnt > 0) {
1874
if (buf[cnt-1] == '\r')
1879
return [StrClass stringWithCString:buf length:cnt];
1883
s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt];
1884
s2 = [str stringByAppendingString:s];
1890
static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2){
1891
/* _parseUntil2(self, char, char) doesn`t consume the stop-chars */
1893
NSMutableString *str;
1900
while ((c != _c1) && (c != _c2)) {
1906
str = (NSMutableString *)
1907
[NSMutableString stringWithCString:buf length:1024];
1911
s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024];
1912
[str appendString:s];
1922
return [StrClass stringWithCString:buf length:cnt];
1927
s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt];
1928
s2 = [str stringByAppendingString:s];
1934
- (NSException *)exceptionForFailedMatch:(unsigned char)_match
1935
got:(unsigned char)_avail
1939
e = [NGImap4ParserException alloc];
1941
e = [e initWithFormat:@"unexpected char <%c> expected <%c> <%@>",
1942
_avail, _match, self->serverResponseDebug];
1945
e = [e initWithFormat:@"unexpected char <%c> expected <%c>",
1948
return [e autorelease];
1951
static __inline__ NSException *_consumeIfMatch(NGImap4ResponseParser *self,
1952
unsigned char _match)
1956
if (_la(self,0) == _match) {
1961
e = [self exceptionForFailedMatch:_match got:_la(self, 0)];
1962
[self setLastException:e];
1966
static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt) {
1967
/* Normalize end of line */
1972
_cnt += (__la(self, _cnt - 1) == '\r') ? 1 : 0;
1977
for (cnt = 0; cnt < _cnt; cnt++) {
1979
unichar c = _la(self, cnt);
1984
s = [[StrClass alloc] initWithCharacters:&c length:1];
1985
[self->serverResponseDebug appendString:s];
1989
if ([self->serverResponseDebug cStringLength] > 2) {
1990
fprintf(stderr, "S[%p]: %s", self,
1991
[self->serverResponseDebug cString]);
1993
[self->serverResponseDebug release];
1994
self->serverResponseDebug =
1995
[[NSMutableString alloc] initWithCapacity:512];
1999
[self->buffer consume:_cnt];
2002
@end /* NGImap4ResponseParser */