From: helge Date: Fri, 1 Oct 2004 21:16:52 +0000 (+0000) Subject: added support for envelope responses X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e4e31807463a037e3b4a4416a9a480084befe247;p=sope added support for envelope responses git-svn-id: http://svn.opengroupware.org/SOPE/trunk@211 e4a50df8-12e2-0310-a44c-efbce7f8a7e3 --- diff --git a/sope-mime/NGImap4/ChangeLog b/sope-mime/NGImap4/ChangeLog index 5e87e179..430f479c 100644 --- a/sope-mime/NGImap4/ChangeLog +++ b/sope-mime/NGImap4/ChangeLog @@ -1,21 +1,26 @@ +2004-10-01 Helge Hess + + * NGImap4ResponseParser.m: added support for envelope responses, major + code cleanups (v4.3.184) + 2004-09-30 Helge Hess - + * v4.3.183 - + * NGImap4Folder.m: use new sort API - + * NGImap4Client.m: deprecated -sort:qualifier: in favor of -sort:qualifier:encoding:, code cleanups - + 2004-09-29 Helge Hess - + * NGImap4Client.m: improved -description (v4.3.181) - + 2004-09-21 Marcus Mueller - + * imCommon.h: Fixed duplicate interface declarations by renaming them. I believe this was an Xcode only issue. (v4.3.180) - + 2004-09-07 Helge Hess * NGImap4ResponseParser.m: added some sanity checks for unexpected diff --git a/sope-mime/NGImap4/GNUmakefile b/sope-mime/NGImap4/GNUmakefile index e628ab98..67bd0fa5 100644 --- a/sope-mime/NGImap4/GNUmakefile +++ b/sope-mime/NGImap4/GNUmakefile @@ -18,6 +18,8 @@ NGImap4_HEADER_FILES = \ NGImap4DataSource.h \ NSString+Imap4.h \ NGSieveClient.h \ + NGImap4Envelope.h \ + NGImap4EnvelopeAddress.h\ NGImap4_OBJC_FILES = \ NGImap4ResponseParser.m \ @@ -41,6 +43,8 @@ NGImap4_OBJC_FILES = \ NGImap4FolderMailRegistry.m \ NGImap4FolderFlags.m \ NGImap4ResponseNormalizer.m \ + NGImap4Envelope.m \ + NGImap4EnvelopeAddress.m \ -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/subproject.make diff --git a/sope-mime/NGImap4/NGImap4Client.m b/sope-mime/NGImap4/NGImap4Client.m index 0511a259..4006e48f 100644 --- a/sope-mime/NGImap4/NGImap4Client.m +++ b/sope-mime/NGImap4/NGImap4Client.m @@ -694,15 +694,20 @@ static BOOL ImapDebugEnabled = NO; } - (NSDictionary *)fetchUids:(NSArray *)_uids parts:(NSArray *)_parts { + /* + eg: 'UID FETCH 1189,1325,1326 ([TODO])' + */ NSAutoreleasePool *pool; NSString *cmd; NSDictionary *result; + NSString *uidsStr, *partsStr; id fetchres; pool = [[NSAutoreleasePool alloc] init]; - cmd = [NSString stringWithFormat:@"uid fetch %@ (%@)", - [self _uidsJoinedForFetchCmd:_uids], - [self _partsJoinedForFetchCmd:_parts]]; + + uidsStr = [self _uidsJoinedForFetchCmd:_uids]; + partsStr = [self _partsJoinedForFetchCmd:_parts]; + cmd = [NSString stringWithFormat:@"uid fetch %@ (%@)", uidsStr, partsStr]; fetchres = [self processCommand:cmd]; result = [[self->normer normalizeFetchResponse:fetchres] retain]; diff --git a/sope-mime/NGImap4/NGImap4Envelope.h b/sope-mime/NGImap4/NGImap4Envelope.h new file mode 100644 index 00000000..e44bf914 --- /dev/null +++ b/sope-mime/NGImap4/NGImap4Envelope.h @@ -0,0 +1,72 @@ +/* + Copyright (C) 2004 SKYRIX Software AG + + This file is part of OpenGroupware.org. + + OGo is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + OGo is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with OGo; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#ifndef __NGImap4_NGImap4Envelope_H__ +#define __NGImap4_NGImap4Envelope_H__ + +#import + +/* + NGImap4Envelope + + Wraps the raw envelope as parsed from an IMAP4 fetch response. +*/ + +@class NSString, NSArray, NSCalendarDate; +@class NGImap4EnvelopeAddress; + +@interface NGImap4Envelope : NSObject +{ +@public + NSCalendarDate *date; + NSString *subject; + NSString *inReplyTo; + NSString *msgId; + NGImap4EnvelopeAddress *from; + NGImap4EnvelopeAddress *sender; + NGImap4EnvelopeAddress *replyTo; + NSArray *to; + NSArray *cc; + NSArray *bcc; +} + +/* accessors */ + +- (NSCalendarDate *)date; +- (NSString *)subject; +- (NSString *)inReplyTo; +- (NSString *)messageID; +- (NGImap4EnvelopeAddress *)from; +- (NGImap4EnvelopeAddress *)sender; +- (NGImap4EnvelopeAddress *)replyTo; +- (NSArray *)to; +- (NSArray *)cc; +- (NSArray *)bcc; + +/* derived accessors */ + +- (BOOL)hasTo; +- (BOOL)hasCC; +- (BOOL)hasBCC; + +@end + +#endif /* __NGImap4_NGImap4Envelope_H__ */ diff --git a/sope-mime/NGImap4/NGImap4Envelope.m b/sope-mime/NGImap4/NGImap4Envelope.m new file mode 100644 index 00000000..8e2bb082 --- /dev/null +++ b/sope-mime/NGImap4/NGImap4Envelope.m @@ -0,0 +1,111 @@ +/* + Copyright (C) 2004 SKYRIX Software AG + + This file is part of OpenGroupware.org. + + OGo is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + OGo is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with OGo; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#include "NGImap4Envelope.h" +#include "NGImap4EnvelopeAddress.h" +#include "imCommon.h" + +@implementation NGImap4Envelope + +- (void)dealloc { + [self->date release]; + [self->subject release]; + [self->inReplyTo release]; + [self->msgId release]; + [self->from release]; + [self->sender release]; + [self->replyTo release]; + [self->to release]; + [self->cc release]; + [self->bcc release]; + [super dealloc]; +} + +/* accessors */ + +- (NSCalendarDate *)date { + return self->date; +} +- (NSString *)subject { + return self->subject; +} +- (NSString *)inReplyTo { + return self->inReplyTo; +} +- (NSString *)messageID { + return self->msgId; +} +- (NGImap4EnvelopeAddress *)from { + return self->from; +} +- (NGImap4EnvelopeAddress *)sender { + return self->sender; +} +- (NGImap4EnvelopeAddress *)replyTo { + return self->replyTo; +} +- (NSArray *)to { + return self->to; +} +- (NSArray *)cc { + return self->cc; +} +- (NSArray *)bcc { + return self->bcc; +} + +/* derived accessors */ + +- (BOOL)hasTo { + return [self->to count] > 0 ? YES : NO; +} +- (BOOL)hasCC { + return [self->cc count] > 0 ? YES : NO; +} +- (BOOL)hasBCC { + return [self->bcc count] > 0 ? YES : NO; +} + +/* description */ + +- (NSString *)description { + NSMutableString *ms; + + ms = [NSMutableString stringWithCapacity:128]; + [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])]; + + if (self->date) [ms appendFormat:@" date='%@'", self->date]; + if (self->subject) [ms appendFormat:@" subject='%@'", self->subject]; + if (self->msgId) [ms appendFormat:@" msgid='%@'", self->msgId]; + if (self->inReplyTo) [ms appendFormat:@" inreplyto='%@'", self->inReplyTo]; + + if (self->from) [ms appendFormat:@" from=%@", [self->from email]]; + if (self->sender) [ms appendFormat:@" sender=%@", [self->sender email]]; + + if (self->to) [ms appendFormat:@" to=%@", self->to]; + if (self->cc) [ms appendFormat:@" cc=%@", self->cc]; + if (self->bcc) [ms appendFormat:@" bcc=%@", self->bcc]; + + [ms appendString:@">"]; + return ms; +} + +@end /* NGImap4Envelope */ diff --git a/sope-mime/NGImap4/NGImap4EnvelopeAddress.h b/sope-mime/NGImap4/NGImap4EnvelopeAddress.h new file mode 100644 index 00000000..923faf9a --- /dev/null +++ b/sope-mime/NGImap4/NGImap4EnvelopeAddress.h @@ -0,0 +1,62 @@ +/* + Copyright (C) 2004 SKYRIX Software AG + + This file is part of OpenGroupware.org. + + OGo is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + OGo is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with OGo; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#ifndef __NGImap4_NGImap4EnvelopeAddress_H__ +#define __NGImap4_NGImap4EnvelopeAddress_H__ + +#import + +/* + NGImap4EnvelopeAddress + + Wraps the raw mail address in the envelope as parsed from an IMAP4 fetch + response. +*/ + +@class NSString; + +@interface NGImap4EnvelopeAddress : NSObject < NSCopying > +{ +@public + NSString *personalName; + NSString *sourceRoute; + NSString *mailbox; + NSString *host; +} + +- (id)initWithPersonalName:(NSString *)_pname sourceRoute:(NSString *)_route + mailbox:(NSString *)_mbox host:(NSString *)_host; + +/* accessors */ + +- (NSString *)personalName; +- (NSString *)sourceRoute; +- (NSString *)mailbox; +- (NSString *)host; + +/* derived accessors */ + +- (NSString *)baseEMail; +- (NSString *)email; + +@end + +#endif /* __NGImap4_NGImap4EnvelopeAddress_H__ */ diff --git a/sope-mime/NGImap4/NGImap4EnvelopeAddress.m b/sope-mime/NGImap4/NGImap4EnvelopeAddress.m new file mode 100644 index 00000000..bfb4d4f5 --- /dev/null +++ b/sope-mime/NGImap4/NGImap4EnvelopeAddress.m @@ -0,0 +1,121 @@ +/* + Copyright (C) 2004 SKYRIX Software AG + + This file is part of OpenGroupware.org. + + OGo is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + OGo is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with OGo; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#include "NGImap4EnvelopeAddress.h" +#include "imCommon.h" + +@implementation NGImap4EnvelopeAddress + +- (id)initWithPersonalName:(NSString *)_pname sourceRoute:(NSString *)_route + mailbox:(NSString *)_mbox host:(NSString *)_host +{ + /* Note: we expect NSNull for unset values! */ + if (_pname == nil || _route == nil || _mbox == nil || _host == nil) { + [self release]; + return nil; + } + + if ((self = [super init])) { + if ([_pname isNotNull]) self->personalName = [_pname copy]; + if ([_route isNotNull]) self->sourceRoute = [_route copy]; + if ([_mbox isNotNull]) self->mailbox = [_mbox copy]; + if ([_host isNotNull]) self->host = [_host copy]; + } + return self; +} +- (id)init { + return [self initWithPersonalName:nil sourceRoute:nil mailbox:nil host:nil]; +} + +- (void)dealloc { + [self->personalName release]; + [self->sourceRoute release]; + [self->mailbox release]; + [self->host release]; + [super dealloc]; +} + +/* accessors */ + +- (NSString *)personalName { + return self->personalName; +} +- (NSString *)sourceRoute { + return self->sourceRoute; +} +- (NSString *)mailbox { + return self->mailbox; +} +- (NSString *)host { + return self->host; +} + +/* NSCopying */ + +- (id)copyWithZone:(NSZone *)_zone { + /* we are immutable */ + return [self retain]; +} + +/* derived accessors */ + +- (NSString *)baseEMail { + NSString *t; + + if ([self->mailbox length] == 0) + return nil; + if ([self->host length] == 0) + return self->mailbox; + + t = [self->mailbox stringByAppendingString:@"@"]; + return [t stringByAppendingString:self->host]; +} +- (NSString *)email { + NSString *t; + + if ([self->personalName length] == 0) + return [self baseEMail]; + if ((t = [self baseEMail]) == nil) + return self->personalName; + + t = [[self->personalName + stringByAppendingString:@" <"] stringByAppendingString:t]; + return [t stringByAppendingString:@">"]; +} + +/* description */ + +- (NSString *)description { + NSMutableString *ms; + + ms = [NSMutableString stringWithCapacity:128]; + [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])]; + + if (self->personalName) [ms appendFormat:@" name='%@'", self->personalName]; + if (self->sourceRoute) [ms appendFormat:@" source='%@'", self->sourceRoute]; + if (self->mailbox) [ms appendFormat:@" mailbox='%@'", self->mailbox]; + if (self->host) [ms appendFormat:@" host='%@'", self->host]; + + [ms appendString:@">"]; + return ms; +} + +@end /* NGImap4EnvelopeAddress */ diff --git a/sope-mime/NGImap4/NGImap4ResponseParser.m b/sope-mime/NGImap4/NGImap4ResponseParser.m index 046a70a1..c03db7bd 100644 --- a/sope-mime/NGImap4/NGImap4ResponseParser.m +++ b/sope-mime/NGImap4/NGImap4ResponseParser.m @@ -21,6 +21,8 @@ #include "NGImap4ResponseParser.h" #include "NGImap4Support.h" +#include "NGImap4Envelope.h" +#include "NGImap4EnvelopeAddress.h" #include "imCommon.h" // TODO(hh): code is now prepared for last-exception, but currently it just @@ -29,22 +31,47 @@ @interface NGImap4ResponseParser(ParsingPrivates) - (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_; - (NSDictionary *)_parseBodyContent; + +- (NSData *)_parseData; + +- (BOOL)_parseQuotaResponseIntoHashMap:(NGMutableHashMap *)result_; +- (void)_parseContinuationResponseIntoHashMap:(NGMutableHashMap *)result_; +- (BOOL)_parseListOrLSubResponseIntoHashMap:(NGMutableHashMap *)result_; +- (BOOL)_parseCapabilityResponseIntoHashMap:(NGMutableHashMap *)result_; +- (BOOL)_parseSearchResponseIntoHashMap:(NGMutableHashMap *)result_; +- (BOOL)_parseSortResponseIntoHashMap:(NGMutableHashMap *)result_; +- (BOOL)_parseQuotaRootResponseIntoHashMap:(NGMutableHashMap *)result_; +- (BOOL)_parseStatusResponseIntoHashMap:(NGMutableHashMap *)result_; +- (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_; + +- (NSArray *)_parseThread; + @end @implementation NGImap4ResponseParser -#define __la(__SELF__, __LACNT) \ +#define __la(__SELF__, __PEEKPOS) \ ((__SELF__->la == NULL) \ - ? [__SELF__->buffer la:__LACNT]\ - : __SELF__->la(__SELF__->buffer, @selector(la:), __LACNT)) + ? [__SELF__->buffer la:__PEEKPOS]\ + : __SELF__->la(__SELF__->buffer, @selector(la:), __PEEKPOS)) static __inline__ int _la(NGImap4ResponseParser *self, unsigned _laCnt) { - char c = __la(self, _laCnt); + register unsigned char c = __la(self, _laCnt); - if (c == '\r') - return _la(self, _laCnt + 1); + return (c == '\r') + ? _la(self, _laCnt + 1) + : c; +} +static __inline__ BOOL _matchesString(NGImap4ResponseParser *self, + unsigned char *s) +{ + register unsigned int i; - return c; + for (i = 0; s[i] != '\0'; i++) { + if (_la(self, i) != s[i]) + return NO; + } + return YES; } static NSDictionary *_parseBody(NGImap4ResponseParser *self); @@ -62,8 +89,6 @@ static int _parseTaggedResponse(NGImap4ResponseParser *self, NGMutableHashMap *result_); static void _parseUntaggedResponse(NGImap4ResponseParser *self, NGMutableHashMap *result_); -static BOOL _parseByeUntaggedResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_); static NSArray *_parseFlagArray(NGImap4ResponseParser *self); static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self, NGMutableHashMap *result_); @@ -75,25 +100,13 @@ static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self, NGMutableHashMap *result_); static BOOL _parseThreadResponse(NGImap4ResponseParser *self, NGMutableHashMap *result_); -static BOOL _parseStatusResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_); -static BOOL _parseListOrLSubResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_); -static BOOL _parseCapabilityResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_); -static BOOL _parseSearchResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_); -static NSArray *_parseThread(NGImap4ResponseParser *self); -static BOOL _parseSortResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_); static NSNumber *_parseUnsigned(NGImap4ResponseParser *self); static NSString *_parseUntil(NGImap4ResponseParser *self, char _c); static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2); -static __inline__ void _match(NGImap4ResponseParser *self, char _match); + +static __inline__ NSException *_consumeIfMatch + (NGImap4ResponseParser *self, unsigned char _m); static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt); -static void _parseContinuationResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_); -static NSData *_parseData(NGImap4ResponseParser *self); static void _parseSieveRespone(NGImap4ResponseParser *self, NGMutableHashMap *result_); @@ -108,11 +121,6 @@ static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self, static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self); static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self); -static BOOL _parseQuotaResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_); -static BOOL _parseQuotaRootResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_); - static unsigned int LaSize = 4097; static unsigned UseMemoryMappedData = 0; static unsigned Imap4MMDataBoundary = 0; @@ -124,6 +132,7 @@ static Class NumClass = Nil; static NSStringEncoding defCStringEncoding; static NSNumber *YesNum = nil; static NSNumber *NoNum = nil; +static NSNull *null = nil; + (void)initialize { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; @@ -131,6 +140,8 @@ static NSNumber *NoNum = nil; if (didInit) return; didInit = YES; + null = [[NSNull null] retain]; + encoding = [NGMimePartParser defaultHeaderFieldEncoding]; defCStringEncoding = [NSString defaultCStringEncoding]; @@ -271,7 +282,7 @@ static NSNumber *NoNum = nil; } } else if (l0 == '+') { /* starting with a '+'? */ - _parseContinuationResponse(self, result); + [self _parseContinuationResponseIntoHashMap:result]; endOfCommand = YES; } else if (isdigit(l0)) { @@ -307,154 +318,163 @@ static void _parseSieveRespone(NGImap4ResponseParser *self, return; } -static NSData *_parseData(NGImap4ResponseParser *self) { - // TODO: split up method - NSData *result; - unsigned size; - NSNumber *n; - - if (_la(self, 0) != '{') - return nil; - - if (debugDataOn) [self logWithFormat:@"parse data ..."]; - - /* got header */ - result = nil; - - _consume(self, 1); - if ((n = _parseUnsigned(self)) == nil) { - NSException *e; - - e = [[NGImap4ParserException alloc] - initWithFormat:@"expect a number between {}"]; - [self setLastException:[e autorelease]]; - return nil; - } - if (debugDataOn) [self logWithFormat:@" parse data: %@", n]; - _match(self, '}'); - _match(self, '\n'); - - size = [n intValue]; - - if (UseMemoryMappedData && (size > Imap4MMDataBoundary)) { - static NSProcessInfo *Pi = nil; - NGFileStream *stream; - - unsigned char buf[LaSize + 2]; - unsigned char tmpBuf[LaSize + 2]; - unsigned wasRead = 0; - NSString *path; - signed char lastChar; // must be signed +- (NSData *)_parseDataToFile:(unsigned)_size { + // TODO: move to own method + // TODO: do not use NGFileStream but just fopen/fwrite + static NSProcessInfo *Pi = nil; + NGFileStream *stream; + NSData *result; + unsigned char buf[LaSize + 2]; + unsigned char tmpBuf[LaSize + 2]; + unsigned wasRead = 0; + NSString *path; + signed char lastChar; // must be signed - if (debugDataOn) [self logWithFormat:@" using memory mapped data ..."]; + if (debugDataOn) [self logWithFormat:@" using memory mapped data ..."]; - if (Pi == nil) - Pi = [[NSProcessInfo processInfo] retain]; + if (Pi == nil) + Pi = [[NSProcessInfo processInfo] retain]; - path = [Pi temporaryFileName]; - stream = [NGFileStream alloc]; /* extra line to keep gcc happy */ - stream = [stream initWithPath:path]; + path = [Pi temporaryFileName]; + stream = [NGFileStream alloc]; /* extra line to keep gcc happy */ + stream = [stream initWithPath:path]; - if (![stream openInMode:NGFileWriteOnly]) { - NSException *e; + if (![stream openInMode:NGFileWriteOnly]) { + NSException *e; - e = [[NGImap4ParserException alloc] - initWithFormat:@"Could not open temporary file %@", path]; - [self setLastException:[e autorelease]]; - return nil; - } + e = [[NGImap4ParserException alloc] + initWithFormat:@"Could not open temporary file %@", path]; + [self setLastException:[e autorelease]]; + return nil; + } - lastChar = -1; - while (wasRead < size) { - unsigned readCnt, bufCnt, tmpSize, cnt, tmpBufCnt; + lastChar = -1; + while (wasRead < _size) { + unsigned readCnt, bufCnt, tmpSize, cnt, tmpBufCnt; - bufCnt = 0; + bufCnt = 0; - if (lastChar != -1) { - buf[bufCnt++] = lastChar; - lastChar = -1; - } + if (lastChar != -1) { + buf[bufCnt++] = lastChar; + lastChar = -1; + } - [self->buffer la:(size - wasRead < LaSize) - ? (size - wasRead) - : LaSize]; + [self->buffer la:(_size - wasRead < LaSize) + ? (_size - wasRead) + : LaSize]; - readCnt = [self->buffer readBytes:buf+bufCnt count:size - wasRead]; + readCnt = [self->buffer readBytes:buf+bufCnt count:_size - wasRead]; - wasRead+=readCnt; - bufCnt +=readCnt; + wasRead+=readCnt; + bufCnt +=readCnt; - tmpSize = bufCnt - 1; - cnt = 0; - tmpBufCnt = 0; + tmpSize = bufCnt - 1; + cnt = 0; + tmpBufCnt = 0; - while (cnt < tmpSize) { - if ((buf[cnt] == '\r') && (buf[cnt+1] == '\n')) { - cnt++; - } - tmpBuf[tmpBufCnt++] = buf[cnt++]; - } - if (cnt < bufCnt) { - lastChar = buf[cnt]; - } - [stream writeBytes:tmpBuf count:tmpBufCnt]; + while (cnt < tmpSize) { + if ((buf[cnt] == '\r') && (buf[cnt+1] == '\n')) { + cnt++; } - if (lastChar != -1) - [stream writeBytes:&lastChar count:1]; - - [stream close]; - [stream release]; stream = nil; - result = [NSData dataWithContentsOfMappedFile:path]; - [[NSFileManager defaultManager] removeFileAtPath:path handler:nil]; - - return result; + tmpBuf[tmpBufCnt++] = buf[cnt++]; + } + if (cnt < bufCnt) { + lastChar = buf[cnt]; + } + [stream writeBytes:tmpBuf count:tmpBufCnt]; } + if (lastChar != -1) + [stream writeBytes:&lastChar count:1]; + + [stream close]; + [stream release]; stream = nil; + result = [NSData dataWithContentsOfMappedFile:path]; + [[NSFileManager defaultManager] removeFileAtPath:path handler:nil]; - if (size == 0) { - [self logWithFormat:@"ERROR(%s): got content size '0'!", - __PRETTY_FUNCTION__]; - return nil; - } - else { - unsigned char *buf = NULL; - unsigned wasRead = 0; - unsigned char *tmpBuf; - unsigned cnt, tmpBufCnt, tmpSize; + return result; +} +- (NSData *)_parseDataIntoRAM:(unsigned)_size { + /* parses data into a RAM buffer (NSData) */ + unsigned char *buf = NULL; + unsigned char *tmpBuf; + unsigned wasRead = 0; + unsigned cnt, tmpBufCnt, tmpSize; + NSData *result; - buf = calloc(size + 10, sizeof(char)); + buf = calloc(_size + 10, sizeof(char)); - while (wasRead < size) { - [self->buffer la:(size - wasRead < LaSize) ? (size - wasRead) : LaSize]; + while (wasRead < _size) { + [self->buffer la:(_size - wasRead < LaSize) ? (_size - wasRead) : LaSize]; - wasRead += [self->buffer readBytes:(buf + wasRead) - count:(size - wasRead)]; - } - - /* normalize response \r\n -> \n */ + wasRead += [self->buffer readBytes:(buf + wasRead) count:(_size-wasRead)]; + } + + /* normalize response \r\n -> \n */ - tmpBuf = calloc(size + 10, sizeof(char)); - cnt = 0; - tmpBufCnt = 0; - tmpSize = size == 0 ? 0 : size - 1; - while (tmpBufCnt < tmpSize && cnt < size) { - if ((buf[cnt] == '\r') && (buf[cnt + 1] == '\n')) - cnt++; /* skip \r */ + tmpBuf = calloc(_size + 10, sizeof(char)); + cnt = 0; + tmpBufCnt = 0; + tmpSize = _size == 0 ? 0 : _size - 1; + while (tmpBufCnt < tmpSize && cnt < _size) { + if ((buf[cnt] == '\r') && (buf[cnt + 1] == '\n')) + cnt++; /* skip \r */ - tmpBuf[tmpBufCnt] = buf[cnt]; - tmpBufCnt++; - cnt++; - } - if (cnt < size) { - tmpBuf[tmpBufCnt] = buf[cnt]; - tmpBufCnt++; - cnt++; - } + tmpBuf[tmpBufCnt] = buf[cnt]; + tmpBufCnt++; + cnt++; + } + if (cnt < _size) { + tmpBuf[tmpBufCnt] = buf[cnt]; + tmpBufCnt++; + cnt++; + } - result = [NSData dataWithBytesNoCopy:tmpBuf length:tmpBufCnt]; + result = [NSData dataWithBytesNoCopy:tmpBuf length:tmpBufCnt]; - if (buf != NULL) free(buf); buf = NULL; - return result; + if (buf != NULL) free(buf); buf = NULL; + return result; +} +- (NSData *)_parseData { + /* + parses: + { } \n + */ + // TODO: split up method + NSData *result; + unsigned size; + NSNumber *sizeNum; + + if (_la(self, 0) != '{') + return nil; + + if (debugDataOn) [self logWithFormat:@"parse data ..."]; + + /* got header */ + result = nil; + + _consume(self, 1); + if ((sizeNum = _parseUnsigned(self)) == nil) { + NSException *e; + + e = [[NGImap4ParserException alloc] + initWithFormat:@"expect a number between {}"]; + [self setLastException:[e autorelease]]; + return nil; } + if (debugDataOn) [self logWithFormat:@" parse data, size: %@", sizeNum]; + _consumeIfMatch(self, '}'); + _consumeIfMatch(self, '\n'); + + if ((size = [sizeNum intValue]) == 0) { + [self logWithFormat:@"ERROR(%s): got content size '0'!", + __PRETTY_FUNCTION__]; + return nil; + } + + if (UseMemoryMappedData && (size > Imap4MMDataBoundary)) + return [self _parseDataToFile:size]; + + return [self _parseDataIntoRAM:size]; } static int _parseTaggedResponse(NGImap4ResponseParser *self, @@ -483,7 +503,7 @@ static int _parseTaggedResponse(NGImap4ResponseParser *self, return -1; } - _match(self, ' '); + _consumeIfMatch(self, ' '); res = [_parseUntil(self, ' ') lowercaseString]; if (_la(self, 0) == '[') { /* Found flag like [READ-ONLY] */ _consume(self, 1); @@ -510,8 +530,8 @@ static void _parseUntaggedResponse(NGImap4ResponseParser *self, // TODO: is it really required by IMAP4 that responses are uppercase? // TODO: apparently this code *breaks* with lowercase detection on! unsigned char l0, l1 = 0; - _match(self, '*'); - _match(self, ' '); + _consumeIfMatch(self, '*'); + _consumeIfMatch(self, ' '); l0 = _la(self, 0); switch (l0) { @@ -519,22 +539,22 @@ static void _parseUntaggedResponse(NGImap4ResponseParser *self, l1 = _la(self, 1); if (l1 == 'A' && _parseBadUntaggedResponse(self, result_)) // la: 3 return; - if (l1 == 'Y' && _parseByeUntaggedResponse(self, result_)) // la: 3 + if (l1 == 'Y' && [self _parseByeUntaggedResponseIntoHashMap:result_]) // 3 return; break; case 'C': - if (_parseCapabilityResponse(self, result_)) // la: 10 + if ([self _parseCapabilityResponseIntoHashMap:result_]) // la: 10 return; break; - + case 'F': if (_parseFlagsUntaggedResponse(self, result_)) // la: 5 return; break; case 'L': - if (_parseListOrLSubResponse(self, result_)) // la: 4 + if ([self _parseListOrLSubResponseIntoHashMap:result_]) // la: 4 return; break; @@ -555,15 +575,15 @@ static void _parseUntaggedResponse(NGImap4ResponseParser *self, case 'S': switch (_la(self, 1)) { case 'O': // SORT - if (_parseSortResponse(self, result_)) // la: 4 + if ([self _parseSortResponseIntoHashMap:result_]) // la: 4 return; break; case 'E': // SEARCH - if (_parseSearchResponse(self, result_)) // la: 5 + if ([self _parseSearchResponseIntoHashMap:result_]) // la: 5 return; break; case 'T': // STATUS - if (_parseStatusResponse(self, result_)) // la: 6 + if ([self _parseStatusResponseIntoHashMap:result_]) // la: 6 /* eg "* STATUS INBOX (MESSAGES 0 RECENT 0 UNSEEN 0)" */ return; break; @@ -576,9 +596,9 @@ static void _parseUntaggedResponse(NGImap4ResponseParser *self, break; case 'Q': - if (_parseQuotaResponse(self, result_)) // la: 6 + if ([self _parseQuotaResponseIntoHashMap:result_]) // la: 6 return; - if (_parseQuotaRootResponse(self, result_)) // la: 10 + if ([self _parseQuotaRootResponseIntoHashMap:result_]) // la: 10 return; break; @@ -594,182 +614,147 @@ static void _parseUntaggedResponse(NGImap4ResponseParser *self, [self logWithFormat:@"%s: no matching tag specifier?", __PRETTY_FUNCTION__]; } -static void _parseContinuationResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_) { - _match(self, '+'); - _match(self, ' '); - +- (void)_parseContinuationResponseIntoHashMap:(NGMutableHashMap *)result_ { + _consumeIfMatch(self, '+'); + _consumeIfMatch(self, ' '); + [result_ addObject:YesNum forKey:@"ContinuationResponse"]; [result_ addObject:_parseUntil(self, '\n') forKey:@"description"]; } -static BOOL _parseListOrLSubResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_) { - if (((_la(self, 0) == 'L') - && (_la(self, 1) == 'I') - && (_la(self, 2) == 'S') - && (_la(self, 3) == 'T') - && (_la(self, 4) == ' ')) || - ((_la(self, 0) == 'L') - && (_la(self, 1) == 'S') - && (_la(self, 2) == 'U') - && (_la(self, 3) == 'B') - && (_la(self, 4) == ' '))) { - NSArray *flags = nil; - NSString *delim = nil; - NSString *name = nil; - NSDictionary *d; - - _consume(self, 5); - - flags = _parseFlagArray(self); - - _match(self, ' '); - - if (_la(self, 0) == '"') { - _match(self, '"'); - delim = _parseUntil(self, '"'); - _match(self, ' '); - } - else { - _parseUntil(self, ' '); - delim = nil; - } - if (_la(self, 0) == '"') { - _consume(self, 1); - name = _parseUntil(self, '"'); - _parseUntil(self, '\n'); - } - else { - name = _parseUntil(self, '\n'); - } +- (NSString *)_parseQuotedString { + /* parse a quoted string, eg '"' */ + if (_la(self, 0) != '"') + return nil; + _consume(self, 1); + return _parseUntil(self, '"'); +} +- (void)_consumeOptionalSpace { + if (_la(self, 0) == ' ') _consume(self, 1); +} - d = [[NSDictionary alloc] initWithObjectsAndKeys: - name, @"folderName", - flags, @"flags", - delim, @"delimiter", nil]; - [result_ addObject:d forKey:@"list"]; - [d release]; - return YES; +- (BOOL)_parseListOrLSubResponseIntoHashMap:(NGMutableHashMap *)result_ { + NSArray *flags = nil; + NSString *delim = nil; + NSString *name = nil; + NSDictionary *d; + + if (!_matchesString(self, "LIST ") && !_matchesString(self, "LSUB ")) + return NO; + + _consume(self, 5); /* consume 'LIST ' or 'LSUB ' */ + flags = _parseFlagArray(self); + _consumeIfMatch(self, ' '); + + if (_la(self, 0) == '"') { + delim = [self _parseQuotedString]; + _consumeIfMatch(self, ' '); } - return NO; + else { + _parseUntil(self, ' '); + delim = nil; + } + if (_la(self, 0) == '"') { + name = [self _parseQuotedString]; + _parseUntil(self, '\n'); + } + else + name = _parseUntil(self, '\n'); + + d = [[NSDictionary alloc] initWithObjectsAndKeys: + name, @"folderName", + flags, @"flags", + delim, @"delimiter", nil]; + [result_ addObject:d forKey:@"list"]; + [d release]; + return YES; } -static BOOL _parseCapabilityResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_) { - if ((_la(self, 0) == 'C') - && (_la(self, 1) == 'A') - && (_la(self, 2) == 'P') - && (_la(self, 3) == 'A') - && (_la(self, 4) == 'B') - && (_la(self, 5) == 'I') - && (_la(self, 6) == 'L') - && (_la(self, 7) == 'I') - && (_la(self, 8) == 'T') - && (_la(self, 9) == 'Y') - && (_la(self, 10) == ' ')) { - NSString *caps; - - caps = _parseUntil(self, '\n'); - { - NSEnumerator *enumerator; - id obj; - NSMutableArray *array; - NSArray *tmp; - - array = [[NSMutableArray alloc] initWithCapacity:16]; - - enumerator = [[caps componentsSeparatedByString:@" "] objectEnumerator]; - while ((obj = [enumerator nextObject])) { - [array addObject:[obj lowercaseString]]; - } - tmp = [array shallowCopy]; - [result_ addObject:tmp forKey:@"capability"]; +- (BOOL)_parseCapabilityResponseIntoHashMap:(NGMutableHashMap *)result_ { + NSString *caps; + NSEnumerator *enumerator; + id obj; + NSMutableArray *array; + NSArray *tmp; + + if (!_matchesString(self, "CAPABILITY ")) + return NO; - [array release]; array = nil; - [tmp release]; tmp = nil; - } - return YES; - } - return NO; -} + caps = _parseUntil(self, '\n'); -static BOOL _parseSearchResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_) { - if ((_la(self, 0) == 'S') - && (_la(self, 1) == 'E') - && (_la(self, 2) == 'A') - && (_la(self, 3) == 'R') - && (_la(self, 4) == 'C') - && (_la(self, 5) == 'H')) { + array = [[NSMutableArray alloc] initWithCapacity:16]; - NSMutableArray *msn = nil; + enumerator = [[caps componentsSeparatedByString:@" "] objectEnumerator]; + while ((obj = [enumerator nextObject]) != nil) + [array addObject:[obj lowercaseString]]; + + tmp = [array copy]; + [result_ addObject:tmp forKey:@"capability"]; + + [array release]; array = nil; + [tmp release]; tmp = nil; + return YES; +} - _consume(self, 6); +- (BOOL)_parseSearchResponseIntoHashMap:(NGMutableHashMap *)result_ { + NSMutableArray *msn = nil; + + if (!_matchesString(self, "SEARCH ")) + return NO; - msn = [NSMutableArray arrayWithCapacity:128]; + _consume(self, 6); - while (_la(self, 0) == ' ') { + msn = [NSMutableArray arrayWithCapacity:128]; + + while (_la(self, 0) == ' ') { _consume(self, 1); [msn addObject:_parseUnsigned(self)]; - } - _parseUntil(self, '\n'); - [result_ addObject:msn forKey:@"search"]; - return YES; } - return NO; + _parseUntil(self, '\n'); + [result_ addObject:msn forKey:@"search"]; + return YES; } -static BOOL _parseSortResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_) { - if ((_la(self, 0) == 'S') - && (_la(self, 1) == 'O') - && (_la(self, 2) == 'R') - && (_la(self, 3) == 'T')) { - - NSMutableArray *msn = nil; - - _consume(self, 4); +- (BOOL)_parseSortResponseIntoHashMap:(NGMutableHashMap *)result_ { + NSMutableArray *msn = nil; + + if (!_matchesString(self, "SORT")) + return NO; + + _consume(self, 4); - msn = [NSMutableArray arrayWithCapacity:128]; + msn = [NSMutableArray arrayWithCapacity:128]; - while (_la(self, 0) == ' ') { - _consume(self, 1); - [msn addObject:_parseUnsigned(self)]; - } - _parseUntil(self, '\n'); - [result_ addObject:msn forKey:@"sort"]; - return YES; + while (_la(self, 0) == ' ') { + _consume(self, 1); + [msn addObject:_parseUnsigned(self)]; } - return NO; + _parseUntil(self, '\n'); + [result_ addObject:msn forKey:@"sort"]; + return YES; } -static BOOL _parseQuotaResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_) { - if ((_la(self, 0) == 'Q') - && (_la(self, 1) == 'U') - && (_la(self, 2) == 'O') - && (_la(self, 3) == 'T') - && (_la(self, 4) == 'A') - && (_la(self, 5) == ' ')) { +- (BOOL)_parseQuotaResponseIntoHashMap:(NGMutableHashMap *)result_ { + NSString *qRoot; + NSMutableDictionary *parse; + NSMutableDictionary *quota; - NSString *qRoot; - NSMutableDictionary *parse; - NSMutableDictionary *quota; + if (!_matchesString(self, "QUOTA ")) + return NO; - _consume(self, 6); + _consume(self, 6); - quota = [result_ objectForKey:@"quota"]; + quota = [result_ objectForKey:@"quota"]; - if (!quota) { + if (!quota) { quota = [NSMutableDictionary dictionaryWithCapacity:2]; [result_ setObject:quota forKey:@"quota"]; - } + } - parse = [NSMutableDictionary dictionaryWithCapacity:3]; - qRoot = _parseUntil2(self, ' ', '\n'); + parse = [NSMutableDictionary dictionaryWithCapacity:3]; + qRoot = _parseUntil2(self, ' ', '\n'); - if (_la(self, 0) == ' ') { + if (_la(self, 0) == ' ') { _consume(self, 1); if (_la(self, 0) == '(') { @@ -801,75 +786,63 @@ static BOOL _parseQuotaResponse(NGImap4ResponseParser *self, } } [quota setObject:parse forKey:qRoot]; - } - _parseUntil(self, '\n'); - - return YES; } - return NO; + _parseUntil(self, '\n'); + + return YES; } -static BOOL _parseQuotaRootResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_) -{ - if ((_la(self, 0) == 'Q') - && (_la(self, 1) == 'U') - && (_la(self, 2) == 'O') - && (_la(self, 3) == 'T') - && (_la(self, 4) == 'A') - && (_la(self, 5) == 'R') - && (_la(self, 6) == 'O') - && (_la(self, 7) == 'O') - && (_la(self, 8) == 'T') - && (_la(self, 9) == ' ')) { - NSString *folderName, *folderRoot; - NSMutableDictionary *dict; +- (BOOL)_parseQuotaRootResponseIntoHashMap:(NGMutableHashMap *)result_ { + NSString *folderName, *folderRoot; + NSMutableDictionary *dict; + + if (!_matchesString(self, "QUOTAROOT ")) + return NO; - _consume(self, 10); + _consume(self, 10); - dict = [result_ objectForKey:@"quotaRoot"]; + dict = [result_ objectForKey:@"quotaRoot"]; - if (!dict) { - dict = [NSMutableDictionary dictionaryWithCapacity:2]; - [result_ setObject:dict forKey:@"quotaRoot"]; - } - if (_la(self, 0) == '"') { - _consume(self , 1); - folderName = _parseUntil(self, '"'); - } - else { - folderName = _parseUntil2(self, '\n', ' '); - } - if (_la(self, 0) == ' ') { - _consume(self, 1); - folderRoot = _parseUntil(self, '\n'); - } - else { - _consume(self, 1); - folderRoot = nil; - } - if ([folderName length] && [folderRoot length]) { - [dict setObject:folderRoot forKey:folderName]; - } - return YES; + if (!dict) { + dict = [NSMutableDictionary dictionaryWithCapacity:2]; + [result_ setObject:dict forKey:@"quotaRoot"]; } - return NO; + if (_la(self, 0) == '"') { + _consume(self , 1); + folderName = _parseUntil(self, '"'); + } + else { + folderName = _parseUntil2(self, '\n', ' '); + } + if (_la(self, 0) == ' ') { + _consume(self, 1); + folderRoot = _parseUntil(self, '\n'); + } + else { + _consume(self, 1); + folderRoot = nil; + } + if ([folderName length] && [folderRoot length]) { + [dict setObject:folderRoot forKey:folderName]; + } + return YES; } -static NSArray *_parseThread(NGImap4ResponseParser *self) { +- (NSArray *)_parseThread { NSMutableArray *array; NSNumber *msg; array = [NSMutableArray arrayWithCapacity:64]; - if (_la(self, 0) == '(') { + if (_la(self, 0) == '(') _consume(self, 1); - } + while (1) { if (_la(self, 0) == '(') { - id a; - a = _parseThread(self); - [array addObject:a]; + NSArray *a; + + a = [self _parseThread]; + if (a != nil) [array addObject:a]; } else if ((msg = _parseUnsigned(self))) { [array addObject:msg]; @@ -882,7 +855,7 @@ static NSArray *_parseThread(NGImap4ResponseParser *self) { else if (_la(self, 0) == ' ') _consume(self, 1); } - _match(self, ')'); + _consumeIfMatch(self, ')'); return array; } @@ -910,7 +883,8 @@ static BOOL _parseThreadResponse(NGImap4ResponseParser *self, msn = [NSMutableArray arrayWithCapacity:64]; while ((_la(self, 0) == '(')) { NSArray *array; - if ((array = _parseThread(self))) + + if ((array = [self _parseThread]) != nil) [msn addObject:array]; } _parseUntil(self, '\n'); @@ -920,94 +894,249 @@ static BOOL _parseThreadResponse(NGImap4ResponseParser *self, return NO; } -static BOOL _parseStatusResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_) { - if ((_la(self, 0) == 'S') - && (_la(self, 1) == 'T') - && (_la(self, 2) == 'A') - && (_la(self, 3) == 'T') - && (_la(self, 4) == 'U') - && (_la(self, 5) == 'S') - && (_la(self, 6) == ' ')) { - NSString *name = nil; - NSMutableDictionary *flags = nil; - NSDictionary *d; +- (BOOL)_parseStatusResponseIntoHashMap:(NGMutableHashMap *)result_ { + NSString *name = nil; + NSMutableDictionary *flags = nil; + NSDictionary *d; - _consume(self, 7); + if (!_matchesString(self, "STATUS ")) + return NO; - if (_la(self, 0) == '"') { - _consume(self, 1); - name = _parseUntil(self, '"'); - _match(self, ' '); - } - else { - name = _parseUntil(self, ' '); - } - _match(self, '('); - flags = [NSMutableDictionary dictionaryWithCapacity:8]; + _consume(self, 7); + + if (_la(self, 0) == '"') { + _consume(self, 1); + name = _parseUntil(self, '"'); + _consumeIfMatch(self, ' '); + } + else { + name = _parseUntil(self, ' '); + } + _consumeIfMatch(self, '('); + flags = [NSMutableDictionary dictionaryWithCapacity:8]; - while (_la(self, 0) != ')') { - NSString *key = _parseUntil(self, ' '); - id value = _parseUntil2(self, ' ', ')'); + while (_la(self, 0) != ')') { + NSString *key = _parseUntil(self, ' '); + id value = _parseUntil2(self, ' ', ')'); - if (_la(self, 0) == ' ') - _consume(self, 1); + if (_la(self, 0) == ' ') + _consume(self, 1); - [flags setObject:[NumClass numberWithInt:[value intValue]] - forKey:[key lowercaseString]]; - } - _match(self, ')'); - _parseUntil(self, '\n'); + [flags setObject:[NumClass numberWithInt:[value intValue]] + forKey:[key lowercaseString]]; + } + _consumeIfMatch(self, ')'); + _parseUntil(self, '\n'); + + d = [[NSDictionary alloc] initWithObjectsAndKeys: + name, @"folderName", + flags, @"flags", nil]; + [result_ addObject:d forKey:@"status"]; + [d release]; + return YES; +} - d = [[NSDictionary alloc] initWithObjectsAndKeys: - name, @"folderName", - flags, @"flags", nil]; - [result_ addObject:d forKey:@"status"]; - [d release]; - return YES; +- (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_ { + NSString *reason; + + if (!_matchesString(self, "BYE ")) + return NO; + + _consume(self, 4); + reason = _parseUntil(self, '\n'); + [result_ addObject:reason forKey:@"bye"]; + return YES; +} + +- (id)_parseQuotedStringOrNIL { + if (_la(self, 0) == '"') + return [self _parseQuotedString]; + if (_matchesString(self, "NIL")) { + _consume(self, 3); + return null; } - return NO; + return nil; } -static BOOL _parseByeUntaggedResponse(NGImap4ResponseParser *self, - NGMutableHashMap *result_) { - if ((_la(self, 0) == 'B') - && (_la(self, 1) == 'Y') - && (_la(self, 2) == 'E') - && (_la(self, 3) == ' ')) { - NSString *reason = nil; - - _consume(self, 4); - reason = _parseUntil(self, '\n'); - [result_ addObject:reason forKey:@"bye"]; - return YES; +- (NGImap4EnvelopeAddress *)_parseEnvelopeAddressStructure { + /* + Note: returns retained object! + + Order: + personal name + SMTP@at-domain-list(source route) + mailbox name + hostname + eg: + (NIL NIL "helge.hess" "opengroupware.org") + */ + NGImap4EnvelopeAddress *address; + NSString *pname, *route, *mailbox, *host; + + if (_la(self, 0) != '(') { + if (_matchesString(self, "NIL")) { + _consume(self, 3); + return (id)[null retain]; + } + return nil; } - return NO; + _consume(self, 1); // '(' + + pname = [[self _parseQuotedStringOrNIL] copy]; [self _consumeOptionalSpace]; + route = [[self _parseQuotedStringOrNIL] copy]; [self _consumeOptionalSpace]; + mailbox = [[self _parseQuotedStringOrNIL] copy];[self _consumeOptionalSpace]; + host = [[self _parseQuotedStringOrNIL] copy]; [self _consumeOptionalSpace]; + + if (_la(self, 0) != ')') { + [self logWithFormat:@"WARNING: IMAP4 envelope " + @"address not properly closed (c0=%c,c1=%c): %@", + _la(self, 0), _la(self, 1), self->serverResponseDebug]; + } + else + _consume(self, 1); + + address = [[NGImap4EnvelopeAddress alloc] initWithPersonalName:pname + sourceRoute:route mailbox:mailbox + host:host]; + return address; +} + +- (NSArray *)_parseEnvelopeAddressStructures { + NSMutableArray *ma; + + if (_la(self, 0) != '(') { + if (_matchesString(self, "NIL")) { + _consume(self, 3); + return (id)[null retain]; + } + return nil; + } + _consume(self, 1); // '(' + + ma = nil; + while (_la(self, 0) != ')') { + NGImap4EnvelopeAddress *address; + + if ((address = [self _parseEnvelopeAddressStructure]) == nil) + continue; // TODO: should we stop parsing? + if (![address isNotNull]) + continue; + + if (ma == nil) ma = [NSMutableArray arrayWithCapacity:4]; + [ma addObject:address]; + [address release]; /* the parse returns a retained object! */ + } + + if (_la(self, 0) != ')') { + [self logWithFormat: + @"WARNING: IMAP4 envelope address not properly closed!"]; + } + else + _consume(self, 1); + return ma; +} + +- (id)_parseEnvelope { + /* + http://www.hunnysoft.com/rfc/rfc3501.html + + envelope = "(" env-date SP env-subject SP env-from SP env-sender SP + env-reply-to SP env-to SP env-cc SP env-bcc SP + env-in-reply-to SP env-message-id ")" + + * 1189 FETCH (UID 1189 ENVELOPE + ("Tue, 22 Jun 2004 08:42:01 -0500" "" + (("Jeff Glaspie" NIL "jeff" "glaspie.org")) + (("Jeff Glaspie" NIL "jeff" "glaspie.org")) + (("Jeff Glaspie" NIL "jeff" "glaspie.org")) + ((NIL NIL "helge.hess" "opengroupware.org")) + NIL NIL NIL + "<20040622134354.F11133CEB14@mail.opengroupware.org>" + ) + ) + */ + static NGMimeRFC822DateHeaderFieldParser *dateParser = nil; + NGImap4Envelope *env; + NSString *dateStr; + id tmp; + + if (dateParser == nil) + dateParser = [[NGMimeRFC822DateHeaderFieldParser alloc] init]; + + if (_la(self, 0) != '(') + return nil; + _consume(self, 1); + + env = [[[NGImap4Envelope alloc] init] autorelease]; + + /* parse date */ + + dateStr = [self _parseQuotedStringOrNIL]; + [self _consumeOptionalSpace]; + if ([dateStr isNotNull]) + env->date = [[dateParser parseValue:dateStr ofHeaderField:nil] retain]; + + /* parse subject */ + + if ((tmp = [self _parseQuotedStringOrNIL])) + env->subject = [tmp isNotNull] ? [tmp copy] : nil; + [self _consumeOptionalSpace]; + + /* parse addresses */ + + if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) + env->from = [tmp isNotNull] ? [[tmp lastObject] copy] : nil; + [self _consumeOptionalSpace]; + if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) + env->sender = [tmp isNotNull] ? [[tmp lastObject] copy] : nil; + [self _consumeOptionalSpace]; + if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) + env->replyTo = [tmp isNotNull] ? [[tmp lastObject] copy] : nil; + [self _consumeOptionalSpace]; + + if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) + env->to = [tmp isNotNull] ? [tmp copy] : nil; + [self _consumeOptionalSpace]; + if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) + env->cc = [tmp isNotNull] ? [tmp copy] : nil; + [self _consumeOptionalSpace]; + if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) + env->bcc = [tmp isNotNull] ? [tmp copy] : nil; + [self _consumeOptionalSpace]; + + if ((tmp = [self _parseQuotedStringOrNIL])) + env->inReplyTo = [tmp isNotNull] ? [tmp copy] : nil; + [self _consumeOptionalSpace]; + if ((tmp = [self _parseQuotedStringOrNIL])) + env->msgId = [tmp isNotNull] ? [tmp copy] : nil; + [self _consumeOptionalSpace]; + + if (_la(self, 0) != ')') + [self logWithFormat:@"WARNING: IMAP4 envelope not properly closed!"]; + else + _consume(self, 1); + + return env; } - (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_ { - NSNumber *number = nil; + NSNumber *number; NSString *key = nil; if ((number = _parseUnsigned(self)) == nil) return NO; - _match(self, ' '); + _consumeIfMatch(self, ' '); - if ((_la(self, 0) == 'F') - && (_la(self, 1) == 'E') - && (_la(self, 2) == 'T') - && (_la(self, 3) == 'C') - && (_la(self, 4) == 'H') - && (_la(self, 5) == ' ')) { /* got a fetch response (fetch request) */ + if (_matchesString(self, "FETCH ")) { /* eg: "FETCH (FLAGS (\Seen) UID 5 RFC822.HEADER {2903}" */ - NSMutableDictionary *fetch = nil; - + NSMutableDictionary *fetch; + fetch = [[NSMutableDictionary alloc] initWithCapacity:10]; - _consume(self, 6); - _match(self, '('); - while (_la(self, 0) != ')') { + _consume(self, 6); /* "FETCH " */ + _consumeIfMatch(self, '('); + while (_la(self, 0) != ')') { /* until closing parent */ NSString *key = nil; key = [_parseUntil(self, ' ') lowercaseString]; @@ -1042,33 +1171,41 @@ static BOOL _parseByeUntaggedResponse(NGImap4ResponseParser *self, data = [str dataUsingEncoding:defCStringEncoding]; } else - data = _parseData(self); + data = [self _parseData]; if (data != nil) [fetch setObject:data forKey:key]; } + else if ([key isEqualToString:@"envelope"]) { + id envelope; + + if ((envelope = [self _parseEnvelope]) != nil) + [fetch setObject:envelope forKey:key]; + else + [self logWithFormat:@"ERROR: could not parse envelope!"]; + } else { NSException *e; - + e = [[NGImap4ParserException alloc] initWithFormat: - @"unsupported fetch %@", key]; + @"unsupported fetch key: %@", + key]; [self setLastException:[e autorelease]]; return NO; } if (_la(self, 0) == ' ') _consume(self, 1); } - if (fetch) { - [fetch setObject:number forKey:@"msn"]; + if (fetch != nil) { + [fetch setObject:number forKey:@"msn"]; [result_ addObject:fetch forKey:@"fetch"]; _consume(self, 1); /* consume ')' */ - _match(self, '\n'); + _consumeIfMatch(self, '\n'); } else { /* no correct fetch line */ _parseUntil(self, '\n'); } - + [fetch release]; fetch = nil; - return YES; } @@ -1112,7 +1249,7 @@ static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self, NSString *str; NSData *data; - if ((data = _parseData(self)) == nil) + if ((data = [self _parseData]) == nil) return NO; str = [[StrClass alloc] initWithData:data encoding:defCStringEncoding]; @@ -1165,7 +1302,7 @@ static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self) { if ((str = _parseStringSieveResponse(self))) return str; - if ((data = _parseData(self)) == nil) + if ((data = [self _parseData]) == nil) return nil; return [[[StrClass alloc] initWithData:data encoding:defCStringEncoding] @@ -1195,7 +1332,7 @@ static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self, NSData *data; if (debugDataOn) [self logWithFormat:@"parse body decode string"]; - data = _parseData(self); + data = [self _parseData]; if (_decode) data = [data decodeQuotedPrintableValueOfMIMEHeaderField:nil]; @@ -1249,12 +1386,12 @@ static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self) _consume(self, 1); key = _parseBodyString(self, YES); - _match(self, ' '); + _consumeIfMatch(self, ' '); value = _parseBodyDecodeString(self, YES, YES); [list setObject:value forKey:[key lowercaseString]]; } - _match(self, ')'); + _consumeIfMatch(self, ')'); } else { NSString *str; @@ -1271,15 +1408,15 @@ static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self) static NSArray *_parseAddressStructure(NGImap4ResponseParser *self) { NSString *personalName, *sourceRoute, *mailboxName, *hostName; - _match(self, '('); + _consumeIfMatch(self, '('); personalName = _parseBodyString(self, YES); - _match(self, ' '); + _consumeIfMatch(self, ' '); sourceRoute = _parseBodyString(self, YES); - _match(self, ' '); + _consumeIfMatch(self, ' '); mailboxName = _parseBodyString(self, YES); - _match(self, ' '); + _consumeIfMatch(self, ' '); hostName = _parseBodyString(self, YES); - _match(self, ')'); + _consumeIfMatch(self, ')'); return [NSDictionary dictionaryWithObjectsAndKeys: personalName, @"personalName", sourceRoute, @"sourceRoute", @@ -1317,17 +1454,17 @@ static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self) { NSMutableDictionary *dict; type = [_parseBodyString(self, YES) lowercaseString]; - _match(self, ' '); + _consumeIfMatch(self, ' '); subtype = _parseBodyString(self, YES); - _match(self, ' '); + _consumeIfMatch(self, ' '); parameterList = _parseBodyParameterList(self); - _match(self, ' '); + _consumeIfMatch(self, ' '); bodyId = _parseBodyString(self, YES); - _match(self, ' '); + _consumeIfMatch(self, ' '); description = _parseBodyString(self, YES); - _match(self, ' '); + _consumeIfMatch(self, ' '); encoding = _parseBodyString(self, YES); - _match(self, ' '); + _consumeIfMatch(self, ' '); bodysize = _parseBodyString(self, YES); dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: @@ -1340,37 +1477,37 @@ static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self) { bodysize, @"size", nil]; if ([type isEqualToString:@"text"]) { - _match(self, ' '); + _consumeIfMatch(self, ' '); [dict setObject:_parseBodyString(self, YES) forKey:@"lines"]; } else if ([type isEqualToString:@"message"]) { if (_la(self, 0) != ')') { - _match(self, ' '); - _match(self, '('); + _consumeIfMatch(self, ' '); + _consumeIfMatch(self, '('); [dict setObject:_parseBodyString(self, YES) forKey:@"date"]; - _match(self, ' '); + _consumeIfMatch(self, ' '); [dict setObject:_parseBodyString(self, YES) forKey:@"subject"]; - _match(self, ' '); + _consumeIfMatch(self, ' '); [dict setObject:_parseParenthesizedAddressList(self) forKey:@"from"]; - _match(self, ' '); + _consumeIfMatch(self, ' '); [dict setObject:_parseParenthesizedAddressList(self) forKey:@"sender"]; - _match(self, ' '); + _consumeIfMatch(self, ' '); [dict setObject:_parseParenthesizedAddressList(self) forKey:@"reply-to"]; - _match(self, ' '); + _consumeIfMatch(self, ' '); [dict setObject:_parseParenthesizedAddressList(self) forKey:@"to"]; - _match(self, ' '); + _consumeIfMatch(self, ' '); [dict setObject:_parseParenthesizedAddressList(self) forKey:@"cc"]; - _match(self, ' '); + _consumeIfMatch(self, ' '); [dict setObject:_parseParenthesizedAddressList(self) forKey:@"bcc"]; - _match(self, ' '); + _consumeIfMatch(self, ' '); [dict setObject:_parseBodyString(self, YES) forKey:@"in-reply-to"]; - _match(self, ' '); + _consumeIfMatch(self, ' '); [dict setObject:_parseBodyString(self, YES) forKey:@"messageId"]; - _match(self, ')'); - _match(self, ' '); + _consumeIfMatch(self, ')'); + _consumeIfMatch(self, ' '); [dict setObject:_parseBody(self) forKey:@"body"]; - _match(self, ' '); + _consumeIfMatch(self, ' '); [dict setObject:_parseBodyString(self, YES) forKey:@"bodyLines"]; } } @@ -1386,7 +1523,7 @@ static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self) { while (_la(self, 0) == '(') { [parts addObject:_parseBody(self)]; } - _match(self, ' '); + _consumeIfMatch(self, ' '); kind = _parseBodyString(self, YES); return [NSDictionary dictionaryWithObjectsAndKeys: parts, @"parts", @@ -1397,7 +1534,7 @@ static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self) { static NSDictionary *_parseBody(NGImap4ResponseParser *self) { NSDictionary *result; - _match(self, '('); + _consumeIfMatch(self, '('); if (_la(self, 0) == '(') { result = _parseMultipartBody(self); @@ -1429,7 +1566,7 @@ static NSDictionary *_parseBody(NGImap4ResponseParser *self) { data = [str dataUsingEncoding:defCStringEncoding]; } else - data = _parseData(self); + data = [self _parseData]; if (data == nil) { [self logWithFormat:@"ERROR(%s): got no data.", __PRETTY_FUNCTION__]; @@ -1442,7 +1579,7 @@ static NSDictionary *_parseBody(NGImap4ResponseParser *self) { static NSArray *_parseFlagArray(NGImap4ResponseParser *self) { NSString *flags; - _match(self, '('); + _consumeIfMatch(self, '('); flags = _parseUntil(self, ')'); if ([flags length] == 0) { @@ -1464,7 +1601,7 @@ static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self, && (_la(self, 5) == ' ')) { _consume(self, 6); [result_ addObject:_parseFlagArray(self) forKey:@"flags"]; - _match(self, '\n'); + _consumeIfMatch(self, '\n'); return YES; } return NO; @@ -1664,6 +1801,7 @@ static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2){ [NSMutableString stringWithCString:buf length:1024]; else { NSString *s; + s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024]; [str appendString:s]; [s release]; @@ -1687,30 +1825,36 @@ static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2){ } } +- (NSException *)exceptionForFailedMatch:(unsigned char)_match + got:(unsigned char)_avail +{ + NSException *e; + + e = [NGImap4ParserException alloc]; + if (self->debug) { + e = [e initWithFormat:@"unexpected char <%c> expected <%c> <%@>", + _avail, _match, self->serverResponseDebug]; + } + else { + e = [e initWithFormat:@"unexpected char <%c> expected <%c>", + _avail, _match]; + } + return [e autorelease]; +} -static __inline__ void _match(NGImap4ResponseParser *self, char _match) { +static __inline__ NSException *_consumeIfMatch(NGImap4ResponseParser *self, + unsigned char _match) +{ + NSException *e; + if (_la(self,0) == _match) { _consume(self, 1); - return; - } - { - NSException *e; - if (self->debug) { - e = [[NGImap4ParserException alloc] - initWithFormat:@"unexpected char <%c> " - @"expected <%c> <%@>", - _la(self, 0), _match, - self->serverResponseDebug]; - } - else { - e = [[NGImap4ParserException alloc] - initWithFormat:@"unexpected char <%c> " - @"expected <%c>", - _la(self, 0), _match]; - } - e = [e autorelease]; - [self setLastException:e]; + return nil; } + + e = [self exceptionForFailedMatch:_match got:_la(self, 0)]; + [self setLastException:e]; + return e; } static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt) { diff --git a/sope-mime/NGMime/NGMimeHeaderFieldParser.h b/sope-mime/NGMime/NGMimeHeaderFieldParser.h index e4b191ec..d4c9a2f3 100644 --- a/sope-mime/NGMime/NGMimeHeaderFieldParser.h +++ b/sope-mime/NGMime/NGMimeHeaderFieldParser.h @@ -18,7 +18,6 @@ Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -// $Id$ #ifndef __NGMime_NGHeaderFieldParser_H__ #define __NGMime_NGHeaderFieldParser_H__ diff --git a/sope-mime/Version b/sope-mime/Version index 5f3604ff..5fc17adf 100644 --- a/sope-mime/Version +++ b/sope-mime/Version @@ -2,6 +2,6 @@ MAJOR_VERSION:=4 MINOR_VERSION:=3 -SUBMINOR_VERSION:=183 +SUBMINOR_VERSION:=184 # v4.2.149 requires libNGStreams v4.2.34