2 Copyright (C) 2000-2007 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_;
46 - (BOOL)_parseACLResponseIntoHashMap:(NGMutableHashMap *)result_;
47 - (BOOL)_parseMyRightsResponseIntoHashMap:(NGMutableHashMap *)result_;
48 - (BOOL)_parseListRightsResponseIntoHashMap:(NGMutableHashMap *)result_;
50 - (NSArray *)_parseThread;
54 @implementation NGImap4ResponseParser
56 #define __la(__SELF__, __PEEKPOS) \
57 ((__SELF__->la == NULL) \
58 ? [__SELF__->buffer la:__PEEKPOS]\
59 : __SELF__->la(__SELF__->buffer, @selector(la:), __PEEKPOS))
61 static __inline__ int _la(NGImap4ResponseParser *self, unsigned _laCnt) {
62 register unsigned char c = __la(self, _laCnt);
65 ? _la(self, _laCnt + 1)
68 static __inline__ BOOL _matchesString(NGImap4ResponseParser *self,
71 register unsigned int i;
73 for (i = 0; s[i] != '\0'; i++) {
74 if (_la(self, i) != s[i])
80 static NSDictionary *_parseBody(NGImap4ResponseParser *self,
81 BOOL isBodyStructure);
82 static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self,
83 BOOL isBodyStructure);
84 static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self,
85 BOOL isBodyStructure);
87 static NSString *_parseBodyString(NGImap4ResponseParser *self,
89 static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
92 static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self);
93 static NSDictionary *_parseContentDisposition(NGImap4ResponseParser *self);
94 static NSArray *_parseAddressStructure(NGImap4ResponseParser *self);
95 static NSArray *_parseParenthesizedAddressList(NGImap4ResponseParser *self);
96 static int _parseTaggedResponse(NGImap4ResponseParser *self,
97 NGMutableHashMap *result_);
98 static void _parseUntaggedResponse(NGImap4ResponseParser *self,
99 NGMutableHashMap *result_);
100 static NSArray *_parseFlagArray(NGImap4ResponseParser *self);
101 static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self,
102 NGMutableHashMap *result_);
103 static BOOL _parseOkUntaggedResponse(NGImap4ResponseParser *self,
104 NGMutableHashMap *result_);
105 static BOOL _parseBadUntaggedResponse(NGImap4ResponseParser *self,
106 NGMutableHashMap *result_);
107 static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self,
108 NGMutableHashMap *result_);
109 static BOOL _parseThreadResponse(NGImap4ResponseParser *self,
110 NGMutableHashMap *result_);
111 static NSNumber *_parseUnsigned(NGImap4ResponseParser *self);
112 static NSString *_parseUntil(NGImap4ResponseParser *self, char _c);
113 static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2);
115 static __inline__ NSException *_consumeIfMatch
116 (NGImap4ResponseParser *self, unsigned char _m);
117 static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt);
119 static void _parseSieveRespone(NGImap4ResponseParser *self,
120 NGMutableHashMap *result_);
121 static BOOL _parseGreetingsSieveResponse(NGImap4ResponseParser *self,
122 NGMutableHashMap *result_);
123 static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self,
124 NGMutableHashMap *result_);
125 static BOOL _parseOkSieveResponse(NGImap4ResponseParser *self,
126 NGMutableHashMap *result_);
127 static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self,
128 NGMutableHashMap *result_);
129 static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self);
130 static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self);
132 static unsigned int LaSize = 4097;
133 static unsigned UseMemoryMappedData = 0;
134 static unsigned Imap4MMDataBoundary = 0;
135 static BOOL debugOn = NO;
136 static BOOL debugDataOn = NO;
137 static NSStringEncoding encoding;
138 static Class StrClass = Nil;
139 static Class NumClass = Nil;
140 static Class DataClass = Nil;
141 static NSStringEncoding defCStringEncoding;
142 static NSNumber *YesNum = nil;
143 static NSNumber *NoNum = nil;
144 static NSNull *null = nil;
147 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
148 static BOOL didInit = NO;
152 null = [[NSNull null] retain];
154 encoding = [NGMimePartParser defaultHeaderFieldEncoding];
155 defCStringEncoding = [NSString defaultCStringEncoding];
157 debugOn = [ud boolForKey:@"ImapDebugEnabled"];
158 debugDataOn = [ud boolForKey:@"ImapDebugDataEnabled"];
159 UseMemoryMappedData = [ud boolForKey:@"NoMemoryMappedDataForImapBlobs"]?0:1;
160 Imap4MMDataBoundary = [ud integerForKey:@"Imap4MMDataBoundary"];
162 if (Imap4MMDataBoundary < 10)
163 /* Note: this should be larger than a usual header size! */
164 Imap4MMDataBoundary = 2 * LaSize;
166 StrClass = [NSString class];
167 NumClass = [NSNumber class];
168 DataClass = [NSData class];
169 YesNum = [[NumClass numberWithBool:YES] retain];
170 NoNum = [[NumClass numberWithBool:NO] retain];
173 + (id)parserWithStream:(id<NGActiveSocket>)_stream {
174 NGImap4ResponseParser *parser;
176 parser = [NGImap4ResponseParser alloc]; /* seperate line to keep gcc happy */
177 return [[parser initWithStream:_stream] autorelease];
180 - (id)initWithStream:(id<NGActiveSocket>)_stream {
181 // designated initializer
182 if (_stream == nil) {
183 [self logWithFormat:@"%s: got no stream ...", __PRETTY_FUNCTION__];
188 if ((self = [super init])) {
191 s = [(NGBufferedStream *)[NGBufferedStream alloc] initWithSource:_stream];
192 self->buffer = [NGByteBuffer alloc];
193 self->buffer = [self->buffer initWithSource:s la:LaSize];
196 if ([self->buffer respondsToSelector:@selector(methodForSelector:)])
197 self->la = (int(*)(id, SEL, unsigned))
198 [self->buffer methodForSelector:@selector(la:)];
200 self->debug = debugOn;
207 [NSException raise:@"InvalidUseOfMethodException"
209 @"calling -init on the NGImap4ResponseParser is not allowed"];
214 [self->buffer release];
216 [self->serverResponseDebug release];
220 /* exception handling */
222 - (void)setLastException:(NSException *)_exc {
223 // TODO: support last exception
228 ** Parse Sieve Responses
231 - (NGHashMap *)parseSieveResponse {
232 NGMutableHashMap *result;
235 if (self->serverResponseDebug != nil)
236 [self->serverResponseDebug release];
237 self->serverResponseDebug = [[NSMutableString alloc] initWithCapacity:512];
239 result = [NGMutableHashMap hashMapWithCapacity:64];
241 if (_la(self,0) == -1) {
242 [self setLastException:[self->buffer lastException]];
246 _parseSieveRespone(self, result);
250 - (NGHashMap *)parseResponseForTagId:(int)_tag exception:(NSException **)ex_ {
251 /* parse a response from the server, _tag!=-1 parse until tagged response */
252 // TODO: is NGHashMap really necessary here?
254 NGMutableHashMap *result;
259 [self->serverResponseDebug release]; self->serverResponseDebug = nil;
260 self->serverResponseDebug = [[NSMutableString alloc] initWithCapacity:512];
263 result = [NGMutableHashMap hashMapWithCapacity:64];
265 if (_la(self, 0) == -1) {
266 [self logWithFormat:@"%s: catched: %@", __PRETTY_FUNCTION__,
267 [self->buffer lastException]];
270 *ex_ = [self->buffer lastException];
274 [self setLastException:[self->buffer lastException]];
278 for (endOfCommand = NO; !endOfCommand; ) {
283 if (l0 == '*') { /* those starting with '* ' */
284 _parseUntaggedResponse(self, result);
285 if ([result objectForKey:@"bye"]) {
290 if ([result objectForKey:@"ok"] != nil)
295 else if (l0 == '+') { /* starting with a '+'? */
296 [self _parseContinuationResponseIntoHashMap:result];
299 else if (isdigit(l0)) {
300 /* those starting with a number '24 ', eg '24 OK Completed' */
301 endOfCommand = (_parseTaggedResponse(self, result) == _tag);
306 - (NGHashMap *)parseResponseForTagId:(int)_tag {
308 NSException *e = nil;
311 hm = [self parseResponseForTagId:_tag exception:&e];
313 [self setLastException:e];
319 static void _parseSieveRespone(NGImap4ResponseParser *self,
320 NGMutableHashMap *result_)
322 if (_parseGreetingsSieveResponse(self, result_))
324 if (_parseDataSieveResponse(self, result_)) // la: 1
326 if (_parseOkSieveResponse(self, result_)) // la: 2
328 if (_parseNoSieveResponse(self, result_)) // la: 2
332 - (NSData *)_parseDataToFile:(unsigned)_size {
333 // TODO: move to own method
334 // TODO: do not use NGFileStream but just fopen/fwrite
335 static NSProcessInfo *Pi = nil;
336 NGFileStream *stream;
338 unsigned char buf[LaSize + 2];
339 unsigned char tmpBuf[LaSize + 2];
340 unsigned wasRead = 0;
342 signed char lastChar; // must be signed
344 if (debugDataOn) [self logWithFormat:@" using memory mapped data ..."];
347 Pi = [[NSProcessInfo processInfo] retain];
349 path = [Pi temporaryFileName];
350 stream = [NGFileStream alloc]; /* extra line to keep gcc happy */
351 stream = [stream initWithPath:path];
353 if (![stream openInMode:NGFileWriteOnly]) {
356 e = [[NGImap4ParserException alloc]
357 initWithFormat:@"Could not open temporary file %@", path];
358 [self setLastException:[e autorelease]];
363 while (wasRead < _size) {
364 unsigned readCnt, bufCnt, tmpSize, cnt, tmpBufCnt;
368 if (lastChar != -1) {
369 buf[bufCnt++] = lastChar;
373 [self->buffer la:(_size - wasRead < LaSize)
377 readCnt = [self->buffer readBytes:buf+bufCnt count:_size - wasRead];
382 tmpSize = bufCnt - 1;
386 while (cnt < tmpSize) {
387 if ((buf[cnt] == '\r') && (buf[cnt+1] == '\n')) {
390 tmpBuf[tmpBufCnt++] = buf[cnt++];
395 [stream writeBytes:tmpBuf count:tmpBufCnt];
398 [stream writeBytes:&lastChar count:1];
401 [stream release]; stream = nil;
402 result = [DataClass dataWithContentsOfMappedFile:path];
403 [[NSFileManager defaultManager] removeFileAtPath:path handler:nil];
407 - (NSData *)_parseDataIntoRAM:(unsigned)_size {
408 /* parses data into a RAM buffer (NSData) */
409 unsigned char *buf = NULL;
410 unsigned char *tmpBuf;
411 unsigned wasRead = 0;
412 unsigned cnt, tmpBufCnt, tmpSize;
415 buf = calloc(_size + 10, sizeof(char));
417 while (wasRead < _size) {
418 [self->buffer la:(_size - wasRead < LaSize) ? (_size - wasRead) : LaSize];
420 wasRead += [self->buffer readBytes:(buf + wasRead) count:(_size-wasRead)];
423 /* normalize response \r\n -> \n */
425 tmpBuf = calloc(_size + 10, sizeof(char));
428 tmpSize = _size == 0 ? 0 : _size - 1;
429 while (tmpBufCnt < tmpSize && cnt < _size) {
430 if ((buf[cnt] == '\r') && (buf[cnt + 1] == '\n'))
433 tmpBuf[tmpBufCnt] = buf[cnt];
438 tmpBuf[tmpBufCnt] = buf[cnt];
443 result = [DataClass dataWithBytesNoCopy:tmpBuf length:tmpBufCnt];
445 if (buf != NULL) free(buf); buf = NULL;
448 - (NSData *)_parseData {
453 // TODO: split up method
458 if (_la(self, 0) != '{')
461 if (debugDataOn) [self logWithFormat:@"parse data ..."];
466 _consume(self, 1); // '{'
467 if ((sizeNum = _parseUnsigned(self)) == nil) {
470 e = [[NGImap4ParserException alloc]
471 initWithFormat:@"expect a number between {}"];
472 [self setLastException:[e autorelease]];
475 if (debugDataOn) [self logWithFormat:@" parse data, size: %@", sizeNum];
476 _consumeIfMatch(self, '}');
477 _consumeIfMatch(self, '\n');
479 if ((size = [sizeNum intValue]) == 0) {
480 [self logWithFormat:@"ERROR(%s): got content size '0'!",
481 __PRETTY_FUNCTION__];
485 if (UseMemoryMappedData && (size > Imap4MMDataBoundary))
486 return [self _parseDataToFile:size];
488 return [self _parseDataIntoRAM:size];
491 static int _parseTaggedResponse(NGImap4ResponseParser *self,
492 NGMutableHashMap *result_)
497 NSString *desc = nil;
498 NSString *flag = nil;
500 if ((tag = _parseUnsigned(self)) == nil) {
504 e = [[NGImap4ParserException alloc]
505 initWithFormat:@"expect a number at begin of tagged response <%@>",
506 self->serverResponseDebug];
509 e = [[NGImap4ParserException alloc]
510 initWithFormat:@"expect a number at begin of tagged response"];
513 [self setLastException:e];
517 _consumeIfMatch(self, ' ');
518 res = [_parseUntil(self, ' ') lowercaseString];
519 if (_la(self, 0) == '[') { /* Found flag like [READ-ONLY] */
521 flag = _parseUntil(self, ']');
523 desc = _parseUntil(self, '\n');
525 ATTENTION: if no flag was set, flag == nil, in this case all key-value
526 pairs after flag will be ignored
528 d = [[NSDictionary alloc] initWithObjectsAndKeys:
531 desc, @"description",
533 [result_ addObject:d forKey:@"ResponseResult"];
535 return [tag intValue];
538 static void _parseUntaggedResponse(NGImap4ResponseParser *self,
539 NGMutableHashMap *result_)
541 // TODO: is it really required by IMAP4 that responses are uppercase?
542 // TODO: apparently this code *breaks* with lowercase detection on!
543 unsigned char l0, l1 = 0;
544 _consumeIfMatch(self, '*');
545 _consumeIfMatch(self, ' ');
550 if ([self _parseACLResponseIntoHashMap:result_])
556 if (l1 == 'A' && _parseBadUntaggedResponse(self, result_)) // la: 3
558 if (l1 == 'Y' && [self _parseByeUntaggedResponseIntoHashMap:result_]) // 3
563 if ([self _parseCapabilityResponseIntoHashMap:result_]) // la: 10
568 if (_parseFlagsUntaggedResponse(self, result_)) // la: 5
573 if (_matchesString(self, "LISTRIGHTS")) {
574 if ([self _parseListRightsResponseIntoHashMap:result_])
577 if ([self _parseListOrLSubResponseIntoHashMap:result_]) // la: 4
582 if ([self _parseMyRightsResponseIntoHashMap:result_])
587 if (_parseNoUntaggedResponse(self, result_)) // la: 2
592 if (_parseOkUntaggedResponse(self, result_)) // la: 2
593 /* eg "* OK Completed" */
601 switch (_la(self, 1)) {
603 if ([self _parseSortResponseIntoHashMap:result_]) // la: 4
607 if ([self _parseSearchResponseIntoHashMap:result_]) // la: 5
611 if ([self _parseStatusResponseIntoHashMap:result_]) // la: 6
612 /* eg "* STATUS INBOX (MESSAGES 0 RECENT 0 UNSEEN 0)" */
619 if (_parseThreadResponse(self, result_)) // la: 6
624 if ([self _parseQuotaResponseIntoHashMap:result_]) // la: 6
626 if ([self _parseQuotaRootResponseIntoHashMap:result_]) // la: 10
630 case '0': case '1': case '2': case '3': case '4':
631 case '5': case '6': case '7': case '8': case '9':
632 if ([self _parseNumberUntaggedResponse:result_]) // la: 5
633 /* eg "* 928 FETCH ..." */
638 // TODO: what if none matches?
639 [self logWithFormat:@"%s: no matching tag specifier?", __PRETTY_FUNCTION__];
640 [self logWithFormat:@" line: '%@'", _parseUntil(self, '\n')];
643 - (void)_parseContinuationResponseIntoHashMap:(NGMutableHashMap *)result_ {
644 _consumeIfMatch(self, '+');
645 _consumeIfMatch(self, ' ');
647 [result_ addObject:YesNum forKey:@"ContinuationResponse"];
648 [result_ addObject:_parseUntil(self, '\n') forKey:@"description"];
651 - (NSString *)_parseQuotedString {
652 /* parse a quoted string, eg '"' */
653 if (_la(self, 0) == '"') {
655 return _parseUntil(self, '"');
659 - (void)_consumeOptionalSpace {
660 if (_la(self, 0) == ' ') _consume(self, 1);
663 - (BOOL)_parseListOrLSubResponseIntoHashMap:(NGMutableHashMap *)result_ {
664 NSArray *flags = nil;
665 NSString *delim = nil;
666 NSString *name = nil;
669 if (!_matchesString(self, "LIST ") && !_matchesString(self, "LSUB "))
672 _consume(self, 5); /* consume 'LIST ' or 'LSUB ' */
673 flags = _parseFlagArray(self);
674 _consumeIfMatch(self, ' ');
676 if (_la(self, 0) == '"') {
677 delim = [self _parseQuotedString];
678 _consumeIfMatch(self, ' ');
681 _parseUntil(self, ' ');
684 if (_la(self, 0) == '"') {
685 name = [self _parseQuotedString];
686 _parseUntil(self, '\n');
689 name = _parseUntil(self, '\n');
691 d = [[NSDictionary alloc] initWithObjectsAndKeys:
694 delim, @"delimiter", nil];
695 [result_ addObject:d forKey:@"list"];
700 - (BOOL)_parseCapabilityResponseIntoHashMap:(NGMutableHashMap *)result_ {
702 NSEnumerator *enumerator;
704 NSMutableArray *array;
707 if (!_matchesString(self, "CAPABILITY "))
710 caps = _parseUntil(self, '\n');
712 array = [[NSMutableArray alloc] initWithCapacity:16];
714 enumerator = [[caps componentsSeparatedByString:@" "] objectEnumerator];
715 while ((obj = [enumerator nextObject]) != nil)
716 [array addObject:[obj lowercaseString]];
719 [result_ addObject:tmp forKey:@"capability"];
721 [array release]; array = nil;
722 [tmp release]; tmp = nil;
726 - (BOOL)_parseACLResponseIntoHashMap:(NGMutableHashMap *)result_ {
729 * ACL INBOX test.et.di.cete-lyon lrswipcda helge lrwip
732 NSEnumerator *enumerator;
734 NSMutableArray *uids;
735 NSMutableArray *rights;
736 NSDictionary *result;
738 if (!_matchesString(self, "ACL "))
742 if ((obj = _parseBodyString(self, YES)) != nil)
743 [result_ setObject:obj forKey:@"mailbox"];
744 _consumeIfMatch(self, ' ');
746 acls = _parseUntil(self, '\n');
748 uids = [[NSMutableArray alloc] initWithCapacity:8];
749 rights = [[NSMutableArray alloc] initWithCapacity:8];
751 enumerator = [[acls componentsSeparatedByString:@" "] objectEnumerator];
752 while ((obj = [enumerator nextObject]) != nil) {
753 [uids addObject:obj];
754 obj = [enumerator nextObject];
755 [rights addObject:(obj != nil ? obj : (id)@"")];
758 result = [[NSDictionary alloc] initWithObjects:rights forKeys:uids];
759 [result_ addObject:result forKey:@"acl"];
761 [uids release]; uids = nil;
762 [rights release]; rights = nil;
763 [result release]; result = nil;
767 - (BOOL)_parseMyRightsResponseIntoHashMap:(NGMutableHashMap *)result_ {
771 * MYRIGHTS INBOX lrswipcda
777 if (!_matchesString(self, "MYRIGHTS "))
781 if ((obj = _parseBodyString(self, YES)) != nil)
782 [result_ setObject:obj forKey:@"mailbox"];
783 _consumeIfMatch(self, ' ');
785 rights = _parseUntil(self, '\n');
786 [result_ setObject:rights forKey:@"myrights"];
790 - (BOOL)_parseListRightsResponseIntoHashMap:(NGMutableHashMap *)result_ {
793 22 LISTRIGHTS INBOX helge
794 * LISTRIGHTS INBOX helge "" l r s w i p c d a 0 1 2 3 4 5 6 7 8 9
800 if (!_matchesString(self, "LISTRIGHTS "))
804 if ((obj = _parseBodyString(self, YES)) != nil)
805 [result_ setObject:obj forKey:@"mailbox"];
806 _consumeIfMatch(self, ' ');
808 if ((obj = _parseBodyString(self, YES)) != nil)
809 [result_ setObject:obj forKey:@"uid"];
810 _consumeIfMatch(self, ' ');
812 if ((obj = _parseUntil(self, ' ')) != nil) {
813 if ([obj isEqual:@"\"\""])
815 [result_ setObject:obj forKey:@"requiredRights"];
818 rights = _parseUntil(self, '\n');
819 [result_ setObject:[rights componentsSeparatedByString:@" "]
820 forKey:@"listrights"];
824 - (BOOL)_parseSearchResponseIntoHashMap:(NGMutableHashMap *)result_ {
825 NSMutableArray *msn = nil;
827 if (!_matchesString(self, "SEARCH"))
832 msn = [NSMutableArray arrayWithCapacity:128];
834 while (_la(self, 0) == ' ') {
836 [msn addObject:_parseUnsigned(self)];
838 _parseUntil(self, '\n');
839 [result_ addObject:msn forKey:@"search"];
843 - (BOOL)_parseSortResponseIntoHashMap:(NGMutableHashMap *)result_ {
844 NSMutableArray *msn = nil;
846 if (!_matchesString(self, "SORT"))
851 msn = [NSMutableArray arrayWithCapacity:128];
853 while (_la(self, 0) == ' ') {
855 [msn addObject:_parseUnsigned(self)];
857 _parseUntil(self, '\n');
858 [result_ addObject:msn forKey:@"sort"];
862 - (BOOL)_parseQuotaResponseIntoHashMap:(NGMutableHashMap *)result_ {
864 NSMutableDictionary *parse;
865 NSMutableDictionary *quota;
867 if (!_matchesString(self, "QUOTA "))
872 quota = [result_ objectForKey:@"quota"];
875 quota = [NSMutableDictionary dictionaryWithCapacity:2];
876 [result_ setObject:quota forKey:@"quota"];
879 parse = [NSMutableDictionary dictionaryWithCapacity:3];
880 qRoot = _parseUntil2(self, ' ', '\n');
882 if (_la(self, 0) == ' ') {
885 if (_la(self, 0) == '(') {
887 if (_la(self, 0) == ')') { /* empty quota response */
893 key = _parseUntil(self, ' ');
894 key = [key lowercaseString];
895 if ([key isEqualToString:@"storage"]) {
896 NSString *used, *max;
898 used = _parseUntil(self, ' ');
899 max = _parseUntil(self, ')');
901 [parse setObject:used forKey:@"usedSpace"];
902 [parse setObject:max forKey:@"maxQuota"];
907 v = _parseUntil(self, ')');
909 [parse setObject:v forKey:@"resource"];
913 [quota setObject:parse forKey:qRoot];
915 _parseUntil(self, '\n');
920 - (BOOL)_parseQuotaRootResponseIntoHashMap:(NGMutableHashMap *)result_ {
921 NSString *folderName, *folderRoot;
922 NSMutableDictionary *dict;
924 if (!_matchesString(self, "QUOTAROOT "))
929 dict = [result_ objectForKey:@"quotaRoot"];
932 dict = [NSMutableDictionary dictionaryWithCapacity:2];
933 [result_ setObject:dict forKey:@"quotaRoot"];
935 if (_la(self, 0) == '"') {
937 folderName = _parseUntil(self, '"');
940 folderName = _parseUntil2(self, '\n', ' ');
942 if (_la(self, 0) == ' ') {
944 folderRoot = _parseUntil(self, '\n');
950 if ([folderName isNotEmpty] && [folderRoot isNotEmpty])
951 [dict setObject:folderRoot forKey:folderName];
956 - (NSArray *)_parseThread {
957 NSMutableArray *array;
960 array = [NSMutableArray arrayWithCapacity:64];
962 if (_la(self, 0) == '(')
966 if (_la(self, 0) == '(') {
969 a = [self _parseThread];
970 if (a != nil) [array addObject:a];
972 else if ((msg = _parseUnsigned(self))) {
973 [array addObject:msg];
978 if (_la(self, 0) == ')')
980 else if (_la(self, 0) == ' ')
983 _consumeIfMatch(self, ')');
988 static BOOL _parseThreadResponse(NGImap4ResponseParser *self,
989 NGMutableHashMap *result_) {
990 if ((_la(self, 0) == 'T')
991 && (_la(self, 1) == 'H')
992 && (_la(self, 2) == 'R')
993 && (_la(self, 3) == 'E')
994 && (_la(self, 4) == 'A')
995 && (_la(self, 5) == 'D')) {
1001 if (_la(self, 0) == ' ') {
1005 [result_ addObject:[NSArray array] forKey:@"thread"];
1008 msn = [NSMutableArray arrayWithCapacity:64];
1009 while ((_la(self, 0) == '(')) {
1012 if ((array = [self _parseThread]) != nil)
1013 [msn addObject:array];
1015 _parseUntil(self, '\n');
1016 [result_ addObject:msn forKey:@"thread"];
1022 - (BOOL)_parseStatusResponseIntoHashMap:(NGMutableHashMap *)result_ {
1023 NSString *name = nil;
1024 NSMutableDictionary *flags = nil;
1027 if (!_matchesString(self, "STATUS "))
1032 if (_la(self, 0) == '"') {
1034 name = _parseUntil(self, '"');
1035 _consumeIfMatch(self, ' ');
1038 name = _parseUntil(self, ' ');
1040 _consumeIfMatch(self, '(');
1041 flags = [NSMutableDictionary dictionaryWithCapacity:8];
1043 while (_la(self, 0) != ')') {
1044 NSString *key = _parseUntil(self, ' ');
1045 id value = _parseUntil2(self, ' ', ')');
1047 if (_la(self, 0) == ' ')
1050 [flags setObject:[NumClass numberWithInt:[value intValue]]
1051 forKey:[key lowercaseString]];
1053 _consumeIfMatch(self, ')');
1054 _parseUntil(self, '\n');
1056 d = [[NSDictionary alloc] initWithObjectsAndKeys:
1057 name, @"folderName",
1058 flags, @"flags", nil];
1059 [result_ addObject:d forKey:@"status"];
1064 - (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_ {
1067 if (!_matchesString(self, "BYE "))
1071 reason = _parseUntil(self, '\n');
1072 [result_ addObject:reason forKey:@"bye"];
1076 - (NSString *)_parseQuotedStringOrNIL {
1079 if ((c0 = _la(self, 0)) == '"')
1080 return [self _parseQuotedString];
1083 /* a size indicator, eg '{112}\nkasdjfkja sdj fhj hasdfj hjasdf' */
1087 if ((data = [self _parseData]) == nil)
1089 if (![data isNotEmpty])
1092 s = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
1094 [self logWithFormat:
1095 @"ERROR(%s): could not convert data (%d bytes) into string.",
1096 __PRETTY_FUNCTION__, [data length]];
1097 return @"[ERROR: NGImap4 could not parse IMAP4 data string]";
1099 return [s autorelease];
1102 if (c0 == 'N' && _matchesString(self, "NIL")) {
1108 - (id)_parseQuotedStringOrDataOrNIL {
1109 if (_la(self, 0) == '"')
1110 return [self _parseQuotedString];
1111 if (_la(self, 0) == '{')
1112 return [self _parseData];
1114 if (_matchesString(self, "NIL")) {
1121 - (id)_decodeQP:(id)_string headerField:(NSString *)_field {
1122 if (![_string isNotNull])
1125 if ([_string isKindOfClass:DataClass])
1126 return [_string decodeQuotedPrintableValueOfMIMEHeaderField:_field];
1128 if ([_string isKindOfClass:StrClass]) {
1129 if ([_string length] <= 6 /* minimum size */)
1132 if ([_string rangeOfString:@"=?"].length > 0) {
1136 [self debugWithFormat:@"WARNING: string with quoted printable info!"];
1138 // TODO: this is really expensive ...
1139 data = [_string dataUsingEncoding:NSUTF8StringEncoding];
1143 qpData = [data decodeQuotedPrintableValueOfMIMEHeaderField:_field];
1144 if (qpData != data) return qpData;
1153 - (NGImap4EnvelopeAddress *)_parseEnvelopeAddressStructure {
1155 Note: returns retained object!
1159 SMTP@at-domain-list(source route)
1163 ("Helge Hess" NIL "helge.hess" "opengroupware.org")
1165 NGImap4EnvelopeAddress *address;
1166 NSString *pname, *route, *mailbox, *host;
1168 if (_la(self, 0) != '(') {
1169 if (_matchesString(self, "NIL")) {
1171 return (id)[null retain];
1175 _consume(self, 1); // '('
1177 /* parse personal name, can be with quoted printable encoding! */
1179 pname = [self _parseQuotedStringOrNIL];
1180 if ([pname isNotNull]) // TODO: headerField 'subject'?? explain!
1181 pname = [self _decodeQP:pname headerField:@"subject"];
1182 [self _consumeOptionalSpace];
1184 // TODO: I think those forbid QP encoding?
1185 route = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace];
1186 mailbox = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace];
1187 host = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace];
1189 if (_la(self, 0) != ')') {
1190 [self logWithFormat:@"WARNING: IMAP4 envelope "
1191 @"address not properly closed (c0=%c,c1=%c): %@",
1192 _la(self, 0), _la(self, 1), self->serverResponseDebug];
1197 address = [[NGImap4EnvelopeAddress alloc] initWithPersonalName:pname
1198 sourceRoute:route mailbox:mailbox
1203 - (NSArray *)_parseEnvelopeAddressStructures {
1205 Parses an array of envelopes, most common:
1206 ((NIL NIL "users-admin" "opengroupware.org"))
1207 (just one envelope in the array)
1211 if (_la(self, 0) != '(') {
1212 if (_matchesString(self, "NIL")) {
1214 return (id)[null retain];
1218 _consume(self, 1); // '('
1221 while (_la(self, 0) != ')') {
1222 NGImap4EnvelopeAddress *address;
1224 if ((address = [self _parseEnvelopeAddressStructure]) == nil)
1225 continue; // TODO: should we stop parsing?
1226 if (![address isNotNull])
1229 if (ma == nil) ma = [NSMutableArray arrayWithCapacity:4];
1230 [ma addObject:address];
1231 [address release]; /* the parse returns a retained object! */
1234 if (_la(self, 0) != ')') {
1235 [self logWithFormat:
1236 @"WARNING: IMAP4 envelope address not properly closed!"];
1243 - (id)_parseEnvelope {
1245 http://www.hunnysoft.com/rfc/rfc3501.html
1247 envelope = "(" env-date SP env-subject SP env-from SP env-sender SP
1248 env-reply-to SP env-to SP env-cc SP env-bcc SP
1249 env-in-reply-to SP env-message-id ")"
1251 * 1189 FETCH (UID 1189 ENVELOPE
1252 ("Tue, 22 Jun 2004 08:42:01 -0500" ""
1253 (("Jeff Glaspie" NIL "jeff" "glaspie.org"))
1254 (("Jeff Glaspie" NIL "jeff" "glaspie.org"))
1255 (("Jeff Glaspie" NIL "jeff" "glaspie.org"))
1256 ((NIL NIL "helge.hess" "opengroupware.org"))
1258 "<20040622134354.F11133CEB14@mail.opengroupware.org>"
1262 static NGMimeRFC822DateHeaderFieldParser *dateParser = nil;
1263 NGImap4Envelope *env;
1267 if (dateParser == nil)
1268 dateParser = [[NGMimeRFC822DateHeaderFieldParser alloc] init];
1270 if (_la(self, 0) != '(')
1274 env = [[[NGImap4Envelope alloc] init] autorelease];
1278 dateStr = [self _parseQuotedStringOrNIL];
1279 [self _consumeOptionalSpace];
1280 if ([dateStr isNotNull])
1281 env->date = [[dateParser parseValue:dateStr ofHeaderField:nil] retain];
1285 if ((tmp = [self _parseQuotedStringOrDataOrNIL]) != nil) {
1286 // TODO: that one is an issue, the client does know the requested charset
1287 // but doesn't pass it down to the parser? Requiring the client to
1288 // deal with NSData's is a bit overkill?
1289 env->subject = [tmp isNotNull]
1290 ? [[self _decodeQP:tmp headerField:@"subject"] copy]
1292 [self _consumeOptionalSpace];
1295 [self logWithFormat:@"ERROR(%s): failed on subject(%c): %@",
1296 __PRETTY_FUNCTION__, _la(self, 0), self->serverResponseDebug];
1300 /* parse addresses */
1302 if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1303 env->from = [tmp isNotNull] ? [tmp copy] : nil;
1304 [self _consumeOptionalSpace];
1307 [self logWithFormat:@"ERROR(%s): failed on from.", __PRETTY_FUNCTION__];
1310 if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1311 env->sender = [tmp isNotNull] ? [[tmp lastObject] copy] : nil;
1312 [self _consumeOptionalSpace];
1315 [self logWithFormat:@"ERROR(%s): failed on sender.", __PRETTY_FUNCTION__];
1318 if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1319 env->replyTo = [tmp isNotNull] ? [tmp copy] : nil;
1320 [self _consumeOptionalSpace];
1323 if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1324 env->to = [tmp isNotNull] ? [tmp copy] : nil;
1325 [self _consumeOptionalSpace];
1327 if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1328 env->cc = [tmp isNotNull] ? [tmp copy] : nil;
1329 [self _consumeOptionalSpace];
1331 if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) {
1332 env->bcc = [tmp isNotNull] ? [tmp copy] : nil;
1333 [self _consumeOptionalSpace];
1336 if ((tmp = [self _parseQuotedStringOrNIL])) {
1337 env->inReplyTo = [tmp isNotNull] ? [tmp copy] : nil;
1338 [self _consumeOptionalSpace];
1340 if ((tmp = [self _parseQuotedStringOrNIL])) {
1341 env->msgId = [tmp isNotNull] ? [tmp copy] : nil;
1342 [self _consumeOptionalSpace];
1345 if (_la(self, 0) != ')') {
1346 [self logWithFormat:@"WARNING: IMAP4 envelope not properly closed"
1347 @" (c0=%c,c1=%c): %@",
1348 _la(self, 0), _la(self, 1), self->serverResponseDebug];
1356 - (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_ {
1357 NSMutableDictionary *fetch;
1359 NSString *key = nil;
1361 if ((number = _parseUnsigned(self)) == nil)
1364 _consumeIfMatch(self, ' ');
1366 if (!_matchesString(self, "FETCH ")) {
1367 /* got a number request from select like exists or recent */
1368 key = _parseUntil(self, '\n');
1369 [result_ addObject:number forKey:[key lowercaseString]];
1373 /* eg: "FETCH (FLAGS (\Seen) UID 5 RFC822.HEADER {2903}" */
1374 fetch = [[NSMutableDictionary alloc] initWithCapacity:10];
1376 _consume(self, 6); /* "FETCH " */
1377 _consumeIfMatch(self, '(');
1378 while (_la(self, 0) != ')') { /* until closing parent */
1381 key = [_parseUntil(self, ' ') lowercaseString];
1383 [self logWithFormat:@"PARSE KEY: %@", key];
1385 if ([key hasPrefix:@"body["]) {
1386 NSDictionary *content;
1388 if ((content = [self _parseBodyContent]) != nil)
1389 [fetch setObject:content forKey:key];
1391 [self logWithFormat:@"ERROR: got no body content for key: '%@'",key];
1393 else if ([key isEqualToString:@"body"]) {
1394 [fetch setObject:_parseBody(self, NO) forKey:key];
1396 else if ([key isEqualToString:@"bodystructure"]) {
1397 [fetch setObject:_parseBody(self, YES) forKey:key];
1399 else if ([key isEqualToString:@"flags"]) {
1400 [fetch setObject:_parseFlagArray(self) forKey:key];
1402 else if ([key isEqualToString:@"uid"]) {
1403 [fetch setObject:_parseUnsigned(self) forKey:key];
1405 else if ([key isEqualToString:@"rfc822.size"]) {
1406 [fetch setObject:_parseUnsigned(self) forKey:key];
1408 else if ([key hasPrefix:@"rfc822"]) {
1411 if (_la(self, 0) == '"') {
1415 str = _parseUntil(self, '"');
1416 data = [str dataUsingEncoding:defCStringEncoding];
1419 data = [self _parseData];
1421 if (data != nil) [fetch setObject:data forKey:key];
1423 else if ([key isEqualToString:@"envelope"]) {
1426 if ((envelope = [self _parseEnvelope]) != nil)
1427 [fetch setObject:envelope forKey:key];
1429 [self logWithFormat:@"ERROR: could not parse envelope!"];
1431 // else if ([key isEqualToString:@"bodystructure"]) {
1432 // // TODO: implement!
1435 // e = [[NGImap4ParserException alloc]
1436 // initWithFormat:@"bodystructure fetch result not yet supported!"];
1437 // [self setLastException:[e autorelease]];
1440 else if ([key isEqualToString:@"internaldate"]) {
1444 e = [[NGImap4ParserException alloc]
1445 initWithFormat:@"INTERNALDATE fetch result not yet supported!"];
1446 [self setLastException:[e autorelease]];
1452 e = [[NGImap4ParserException alloc] initWithFormat:
1453 @"unsupported fetch key: %@",
1455 [self setLastException:[e autorelease]];
1459 if (_la(self, 0) == ' ')
1463 [fetch setObject:number forKey:@"msn"];
1464 [result_ addObject:fetch forKey:@"fetch"];
1465 _consume(self, 1); /* consume ')' */
1466 _consumeIfMatch(self, '\n');
1468 else { /* no correct fetch line */
1469 _parseUntil(self, '\n');
1472 [fetch release]; fetch = nil;
1476 static BOOL _parseGreetingsSieveResponse(NGImap4ResponseParser *self,
1477 NGMutableHashMap *result_)
1481 while (!(isOK = _parseOkSieveResponse(self, result_))) {
1482 NSString *key, *value;
1484 if (!(key = _parseStringSieveResponse(self))) {
1487 if (_la(self, 0) == ' ') {
1490 if (!(value = _parseStringSieveResponse(self))) {
1497 _parseUntil(self, '\n');
1498 [result_ addObject:value forKey:[key lowercaseString]];
1503 static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self,
1504 NGMutableHashMap *result_)
1509 if ((data = [self _parseData]) == nil)
1512 str = [[StrClass alloc] initWithData:data encoding:defCStringEncoding];
1513 [result_ setObject:str forKey:@"data"];
1514 [str release]; str = nil;
1518 static BOOL _parseOkSieveResponse(NGImap4ResponseParser *self,
1519 NGMutableHashMap *result_)
1521 if (!((_la(self, 0) == 'O') && (_la(self, 1) == 'K')))
1526 if (_la(self, 0) == ' ') {
1529 if ((reason = _parseContentSieveResponse(self)))
1530 [result_ addObject:reason forKey:@"reason"];
1532 _parseUntil(self, '\n');
1534 [result_ addObject:YesNum forKey:@"ok"];
1538 static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self,
1539 NGMutableHashMap *result_)
1543 if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' ')))
1548 data = _parseContentSieveResponse(self);
1550 [result_ addObject:NoNum forKey:@"ok"];
1551 if (data) [result_ addObject:data forKey:@"reason"];
1555 static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self) {
1559 if ((str = _parseStringSieveResponse(self)))
1562 if ((data = [self _parseData]) == nil)
1565 return [[[StrClass alloc] initWithData:data encoding:defCStringEncoding]
1569 static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self) {
1570 if (_la(self, 0) != '"')
1574 return _parseUntil(self, '"');
1577 static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
1578 BOOL _convertString,
1583 if (_la(self, 0) == '"') {
1584 // TODO: can the " be escaped somehow?
1586 str = _parseUntil(self, '"');
1588 else if (_la(self, 0) == '{') {
1591 if (debugDataOn) [self logWithFormat:@"parse body decode string"];
1592 data = [self _parseData];
1595 data = [data decodeQuotedPrintableValueOfMIMEHeaderField:nil];
1597 return [[[StrClass alloc] initWithData:data encoding:encoding]
1601 str = _parseUntil2(self, ' ', ')');
1603 if (_convertString) {
1604 if ([[str lowercaseString] isEqualToString:@"nil"])
1610 d = [str dataUsingEncoding:defCStringEncoding];
1611 d = [d decodeQuotedPrintableValueOfMIMEHeaderField:nil];
1613 if ([d isKindOfClass:StrClass])
1616 str = [[[StrClass alloc] initWithData:d encoding:encoding]
1624 static NSString *_parseBodyString(NGImap4ResponseParser *self,
1625 BOOL _convertString)
1627 return _parseBodyDecodeString(self, _convertString, NO /* no decode */);
1630 static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self)
1632 NSMutableDictionary *list;
1634 if (_la(self, 0) == '(') {
1637 list = [NSMutableDictionary dictionaryWithCapacity:4];
1639 while (_la(self,0) != ')') {
1640 NSString *key, *value;
1642 if (_la(self, 0) == ' ')
1645 key = _parseBodyString(self, YES);
1646 _consumeIfMatch(self, ' ');
1647 value = _parseBodyDecodeString(self, YES, YES);
1649 [list setObject:value forKey:[key lowercaseString]];
1651 _consumeIfMatch(self, ')');
1655 str = _parseBodyString(self, YES);
1657 if ([str isNotEmpty])
1658 NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str);
1660 list = (id)[NSDictionary dictionary];
1665 static NSDictionary *_parseContentDisposition(NGImap4ResponseParser *self)
1667 NSMutableDictionary *disposition;
1670 disposition = [NSMutableDictionary dictionary];
1672 if (_la(self, 0) == '(') {
1674 type = _parseBodyString(self, YES);
1675 [disposition setObject: type forKey: @"type"];
1676 if (_la(self, 0) != ')') {
1678 [disposition setObject: _parseBodyParameterList(self)
1679 forKey: @"parameterList"];
1684 _parseBodyString(self, YES);
1689 static NSArray *_parseAddressStructure(NGImap4ResponseParser *self) {
1690 NSString *personalName, *sourceRoute, *mailboxName, *hostName;
1692 _consumeIfMatch(self, '(');
1693 personalName = _parseBodyString(self, YES);
1694 _consumeIfMatch(self, ' ');
1695 sourceRoute = _parseBodyString(self, YES);
1696 _consumeIfMatch(self, ' ');
1697 mailboxName = _parseBodyString(self, YES);
1698 _consumeIfMatch(self, ' ');
1699 hostName = _parseBodyString(self, YES);
1700 _consumeIfMatch(self, ')');
1701 return [NSDictionary dictionaryWithObjectsAndKeys:
1702 personalName, @"personalName",
1703 sourceRoute, @"sourceRoute",
1704 mailboxName, @"mailboxName",
1705 hostName, @"hostName", nil];
1708 static NSArray *_parseParenthesizedAddressList(NGImap4ResponseParser *self) {
1709 NSMutableArray *result;
1710 result = [NSMutableArray arrayWithCapacity:8];
1712 if (_la(self, 0) == '(') {
1714 while (_la(self, 0) != ')') {
1715 [result addObject:_parseAddressStructure(self)];
1721 str = _parseBodyString(self, YES);
1723 if ([str isNotEmpty])
1724 NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str);
1726 result = (id)[NSArray array];
1731 static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self,
1732 BOOL isBodyStructure) {
1733 NSString *type, *subtype, *bodyId, *description,
1734 *encoding, *bodysize;
1735 NSDictionary *parameterList;
1736 NSMutableDictionary *dict;
1738 type = [_parseBodyString(self, YES) lowercaseString];
1739 _consumeIfMatch(self, ' ');
1740 subtype = _parseBodyString(self, YES);
1741 _consumeIfMatch(self, ' ');
1742 parameterList = _parseBodyParameterList(self);
1743 _consumeIfMatch(self, ' ');
1744 bodyId = _parseBodyString(self, YES);
1745 _consumeIfMatch(self, ' ');
1746 description = _parseBodyString(self, YES);
1747 _consumeIfMatch(self, ' ');
1748 encoding = _parseBodyString(self, YES);
1749 _consumeIfMatch(self, ' ');
1750 bodysize = _parseBodyString(self, YES);
1752 dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1754 subtype, @"subtype",
1755 parameterList, @"parameterList",
1757 description, @"description",
1758 encoding, @"encoding",
1759 bodysize, @"size", nil];
1761 if ([type isEqualToString:@"text"]) {
1762 _consumeIfMatch(self, ' ');
1763 [dict setObject:_parseBodyString(self, YES) forKey:@"lines"];
1765 else if ([type isEqualToString:@"message"]) {
1766 if (_la(self, 0) != ')') {
1767 _consumeIfMatch(self, ' ');
1768 _consumeIfMatch(self, '(');
1769 [dict setObject:_parseBodyString(self, YES) forKey:@"date"];
1770 _consumeIfMatch(self, ' ');
1771 [dict setObject:_parseBodyString(self, YES) forKey:@"subject"];
1772 _consumeIfMatch(self, ' ');
1773 [dict setObject:_parseParenthesizedAddressList(self) forKey:@"from"];
1774 _consumeIfMatch(self, ' ');
1775 [dict setObject:_parseParenthesizedAddressList(self) forKey:@"sender"];
1776 _consumeIfMatch(self, ' ');
1777 [dict setObject:_parseParenthesizedAddressList(self)
1778 forKey:@"reply-to"];
1779 _consumeIfMatch(self, ' ');
1780 [dict setObject:_parseParenthesizedAddressList(self) forKey:@"to"];
1781 _consumeIfMatch(self, ' ');
1782 [dict setObject:_parseParenthesizedAddressList(self) forKey:@"cc"];
1783 _consumeIfMatch(self, ' ');
1784 [dict setObject:_parseParenthesizedAddressList(self) forKey:@"bcc"];
1785 _consumeIfMatch(self, ' ');
1786 [dict setObject:_parseBodyString(self, YES) forKey:@"in-reply-to"];
1787 _consumeIfMatch(self, ' ');
1788 [dict setObject:_parseBodyString(self, YES) forKey:@"messageId"];
1789 _consumeIfMatch(self, ')');
1790 _consumeIfMatch(self, ' ');
1791 [dict setObject:_parseBody(self, isBodyStructure) forKey:@"body"];
1792 _consumeIfMatch(self, ' ');
1793 [dict setObject:_parseBodyString(self, YES) forKey:@"bodyLines"];
1797 if (isBodyStructure) {
1798 if (_la(self, 0) != ')') {
1800 [dict setObject: _parseBodyString(self, YES)
1802 if (_la(self, 0) != ')') {
1804 [dict setObject: _parseContentDisposition(self)
1805 forKey: @"disposition"];
1806 if (_la(self, 0) != ')') {
1808 if (_la(self, 0) == '(') {
1809 [dict setObject: _parseBodyParameterList(self)
1810 forKey: @"language"];
1813 [dict setObject: _parseBodyString(self, YES)
1814 forKey: @"language"];
1816 if (_la(self, 0) != ')') {
1818 [dict setObject: _parseBodyString(self, YES)
1819 forKey: @"location"];
1829 static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self,
1830 BOOL isBodyStructure) {
1831 NSMutableArray *parts;
1833 NSMutableDictionary *dict;
1835 parts = [NSMutableArray arrayWithCapacity:4];
1837 while (_la(self, 0) == '(') {
1838 [parts addObject:_parseBody(self, isBodyStructure)];
1840 _consumeIfMatch(self, ' ');
1841 kind = _parseBodyString(self, YES);
1842 dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1844 @"multipart", @"type",
1845 kind , @"subtype", nil];
1846 if (isBodyStructure) {
1847 if (_la(self, 0) != ')') {
1849 [dict setObject: _parseBodyParameterList(self)
1850 forKey: @"parameterList"];
1851 if (_la(self, 0) != ')') {
1853 [dict setObject: _parseContentDisposition(self)
1854 forKey: @"disposition"];
1855 if (_la(self, 0) != ')') {
1857 if (_la(self, 0) == '(') {
1858 [dict setObject: _parseBodyParameterList(self)
1859 forKey: @"language"];
1862 [dict setObject: _parseBodyString(self, YES)
1863 forKey: @"language"];
1865 if (_la(self, 0) != ')') {
1867 [dict setObject: _parseBodyString(self, YES)
1868 forKey: @"location"];
1878 static NSDictionary *_parseBody(NGImap4ResponseParser *self, BOOL isBodyStructure) {
1879 NSDictionary *result;
1881 _consumeIfMatch(self, '(');
1883 if (_la(self, 0) == '(') {
1884 result = _parseMultipartBody(self, isBodyStructure);
1887 result = _parseSingleBody(self, isBodyStructure);
1889 if (_la(self,0) != ')') {
1892 str = _parseUntil(self, ')');
1893 NSLog(@"%s: got noparsed content %@", __PRETTY_FUNCTION__,
1902 - (NSDictionary *)_parseBodyContent {
1905 if (_la(self, 0) == '"') {
1909 str = _parseUntil(self, '"');
1910 data = [str dataUsingEncoding:defCStringEncoding];
1913 data = [self _parseData];
1916 [self logWithFormat:@"ERROR(%s): got no data.", __PRETTY_FUNCTION__];
1919 return [NSDictionary dictionaryWithObject:data forKey:@"data"];
1923 static NSArray *_parseFlagArray(NGImap4ResponseParser *self) {
1926 _consumeIfMatch(self, '(');
1928 flags = _parseUntil(self, ')');
1929 if (![flags isNotEmpty]) {
1930 static NSArray *emptyArray = nil;
1931 if (emptyArray == nil) emptyArray = [[NSArray alloc] init];
1935 return [[flags lowercaseString] componentsSeparatedByString:@" "];
1938 static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self,
1939 NGMutableHashMap *result_) {
1940 if ((_la(self, 0) == 'F')
1941 && (_la(self, 1) == 'L')
1942 && (_la(self, 2) == 'A')
1943 && (_la(self, 3) == 'G')
1944 && (_la(self, 4) == 'S')
1945 && (_la(self, 5) == ' ')) {
1947 [result_ addObject:_parseFlagArray(self) forKey:@"flags"];
1948 _consumeIfMatch(self, '\n');
1954 static BOOL _parseBadUntaggedResponse(NGImap4ResponseParser *self,
1955 NGMutableHashMap *result_)
1957 if (!((_la(self, 0)=='B') && (_la(self, 1)=='A') && (_la(self, 2)=='D')
1958 && (_la(self, 3) == ' ')))
1962 [result_ addObject:_parseUntil(self, '\n') forKey:@"bad"];
1966 static BOOL _parseNoOrOkArguments(NGImap4ResponseParser *self,
1967 NGMutableHashMap *result_, NSString *_key)
1973 if (_la(self, 0) == '[') {
1977 key = _parseUntil2(self, ']', ' ');
1979 /* possible kinds of untagged OK responses are either
1980 * OK [ALERT] System shutdown in 10 minutes
1984 * OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen \*)]
1986 if (_la(self, 0) == ']') {
1989 if (_la(self, 0) == ' ') {
1993 value = _parseUntil(self, '\n');
1994 if ([value isNotEmpty]) {
1995 obj = [[NSDictionary alloc]
1996 initWithObjects:&value forKeys:&key count:1];
2003 _parseUntil(self, '\n');
2006 else { /* _la(self, 0) should be ' ' */
2012 if (_la(self, 0) == '(') {
2013 value = _parseFlagArray(self);
2014 _consume(self, 1); /* consume ']' */
2017 value = _parseUntil(self, ']');
2022 tmp = _parseUntil(self, '\n');
2024 obj = [[NSDictionary alloc] initWithObjectsAndKeys:
2026 tmp, @"comment", nil];
2031 obj = [_parseUntil(self, '\n') retain];
2033 [result_ addObject:obj forKey:_key];
2039 static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self,
2040 NGMutableHashMap *result_)
2042 if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' ')))
2046 return _parseNoOrOkArguments(self, result_, @"no");
2049 static BOOL _parseOkUntaggedResponse(NGImap4ResponseParser *self,
2050 NGMutableHashMap *result_)
2052 if (!((_la(self, 0)=='O') && (_la(self, 1)=='K') && (_la(self, 2)==' ')))
2056 return _parseNoOrOkArguments(self, result_, @"ok");
2059 static NSNumber *_parseUnsigned(NGImap4ResponseParser *self) {
2068 while ((c >= '0') && (c <= '9')) {
2071 n = 10 * n + (c - 48);
2077 return [NumClass numberWithUnsignedInt:n];
2080 static NSString *_parseUntil(NGImap4ResponseParser *self, char _c) {
2082 Note: this function consumes the stop char (_c)!
2083 normalize \r\n constructions
2087 NSMutableString *str;
2092 while ((c = _la(self, 0)) != _c) {
2098 str = (NSMutableString *)
2099 [NSMutableString stringWithCString:buf length:1024];
2104 s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024];
2105 [str appendString:s];
2111 _consume(self,1); /* consume known stop char */
2112 if (_c == '\n' && cnt > 0) {
2113 if (buf[cnt-1] == '\r')
2118 return [StrClass stringWithCString:buf length:cnt];
2122 s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt];
2123 s2 = [str stringByAppendingString:s];
2129 static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2){
2130 /* _parseUntil2(self, char, char) doesn`t consume the stop-chars */
2132 NSMutableString *str;
2139 while ((c != _c1) && (c != _c2)) {
2145 str = (NSMutableString *)
2146 [NSMutableString stringWithCString:buf length:1024];
2150 s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024];
2151 [str appendString:s];
2161 return [StrClass stringWithCString:buf length:cnt];
2166 s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt];
2167 s2 = [str stringByAppendingString:s];
2173 - (NSException *)exceptionForFailedMatch:(unsigned char)_match
2174 got:(unsigned char)_avail
2178 e = [NGImap4ParserException alloc];
2180 e = [e initWithFormat:@"unexpected char <%c> expected <%c> <%@>",
2181 _avail, _match, self->serverResponseDebug];
2184 e = [e initWithFormat:@"unexpected char <%c> expected <%c>",
2187 return [e autorelease];
2190 static __inline__ NSException *_consumeIfMatch(NGImap4ResponseParser *self,
2191 unsigned char _match)
2195 if (_la(self,0) == _match) {
2200 e = [self exceptionForFailedMatch:_match got:_la(self, 0)];
2201 [self setLastException:e];
2205 static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt) {
2206 /* Normalize end of line */
2211 _cnt += (__la(self, _cnt - 1) == '\r') ? 1 : 0;
2216 for (cnt = 0; cnt < _cnt; cnt++) {
2218 unichar c = _la(self, cnt);
2223 s = [[StrClass alloc] initWithCharacters:&c length:1];
2224 [self->serverResponseDebug appendString:s];
2228 if ([self->serverResponseDebug cStringLength] > 2) {
2229 fprintf(stderr, "S[%p]: %s", self,
2230 [self->serverResponseDebug cString]);
2232 [self->serverResponseDebug release];
2233 self->serverResponseDebug =
2234 [[NSMutableString alloc] initWithCapacity:512];
2238 [self->buffer consume:_cnt];
2241 @end /* NGImap4ResponseParser */