2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo 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 OGo 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 OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "NGMimePartParser.h"
23 #include "NGMimeBodyParser.h"
24 #include "NGMimeType.h"
25 #include "NGMimeUtilities.h"
28 /* this tunes, how big reused data cache objects may get (10MB) */
29 #define MAX_DATA_OBJECT_SIZE_CACHE (10*1024*1024)
31 @implementation NGMimePartParser
33 static Class StringClass = Nil;
34 static Class MStringClass = Nil;
35 static Class DataClass = Nil;
36 static Class NSMutableDataClass = NULL;
38 static NGMimeHeaderNames *HeaderNames = NULL;
44 static int MimeLogEnabled = -1;
47 static BOOL isInitialized = NO;
48 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
49 if (isInitialized) return;
52 MimeLogEnabled = [ud boolForKey:@"MimeLogEnabled"] ? 1 : 0;
53 MStringClass = [NSMutableString class];
54 StringClass = [NSString class];
55 DataClass = [NSData class];
56 NSMutableDataClass = [NSMutableData class];
59 static inline int _la(NGMimePartParser *self, int _la);
60 static inline void _consume(NGMimePartParser *self, int _cnt);
61 static inline BOOL _checkKey(NGMimePartParser *self, NGHashMap *_map,
64 + (NGMimeHeaderNames *)headerFieldNames {
65 if (HeaderNames == NULL) {
66 HeaderNames = malloc(sizeof(NGMimeHeaderNames));
68 HeaderNames->accept = @"accept";
69 HeaderNames->acceptLanguage = @"accept-language";
70 HeaderNames->acceptEncoding = @"accept-encoding";
71 HeaderNames->acceptCharset = @"accept-charset";
72 HeaderNames->cacheControl = @"cache-control";
73 HeaderNames->cc = @"cc";
74 HeaderNames->connection = @"connection";
75 HeaderNames->contentDisposition = @"content-disposition";
76 HeaderNames->contentLength = @"content-length";
77 HeaderNames->contentTransferEncoding = @"content-transfer-encoding";
78 HeaderNames->contentType = @"content-type";
79 HeaderNames->cookie = @"cookie";
80 HeaderNames->date = @"date";
81 HeaderNames->from = @"from";
82 HeaderNames->host = @"host";
83 HeaderNames->keepAlive = @"keep-alive";
84 HeaderNames->messageID = @"message-id";
85 HeaderNames->mimeVersion = @"mime-version";
86 HeaderNames->organization = @"organization";
87 HeaderNames->received = @"received";
88 HeaderNames->returnPath = @"return-path";
89 HeaderNames->referer = @"referer";
90 HeaderNames->replyTo = @"reply-to";
91 HeaderNames->subject = @"subject";
92 HeaderNames->to = @"to";
93 HeaderNames->userAgent = @"user-agent";
94 HeaderNames->xMailer = @"x-mailer";
100 if ((self = [super init])) {
102 self->contentLength = -1;
108 [self->contentTransferEncoding release];
109 [self->source release];
110 [self->sourceData release];
116 - (void)setDelegate:(id)_delegate {
117 self->delegate = _delegate;
119 self->delegateRespondsTo.parserWillParseHeader =
120 [self->delegate respondsToSelector:@selector(parserWillParseHeader:)];
122 self->delegateRespondsTo.parserDidParseHeader =
123 [self->delegate respondsToSelector:@selector(parser:didParseHeader:)];
125 self->delegateRespondsTo.parserKeepHeaderFieldData =
126 [self->delegate respondsToSelector:@selector(parser:keepHeaderField:data:)];
128 self->delegateRespondsTo.parserKeepHeaderFieldValue =
129 [self->delegate respondsToSelector:
130 @selector(parser:keepHeaderField:value:)];
132 self->delegateRespondsTo.parserFoundCommentInHeaderField =
133 [self->delegate respondsToSelector:
134 @selector(parser:foundComment:inHeaderField:)];
136 self->delegateRespondsTo.parserWillParseBodyOfPart =
137 [self->delegate respondsToSelector:@selector(parser:willParseBodyOfPart:)];
139 self->delegateRespondsTo.parserDidParseBodyOfPart =
140 [self->delegate respondsToSelector:@selector(parser:didParseBodyOfPart:)];
142 self->delegateRespondsTo.parserParseRawBodyDataOfPart =
143 [self->delegate respondsToSelector:
144 @selector(parser:parseRawBodyData:ofPart:)];
146 self->delegateRespondsTo.parserBodyParserForPart =
147 [self->delegate respondsToSelector:@selector(parser:bodyParserForPart:)];
149 self->delegateRespondsTo.parserDecodeBodyOfPart =
150 [self->delegate respondsToSelector:@selector(parser:decodeBody:ofPart:)];
152 self->delegateRespondsTo.parserParseHeaderFieldData =
153 [self->delegate respondsToSelector:@selector(parser:parseHeaderField:data:)];
157 return self->delegate;
162 - (id<NGMimeHeaderFieldParser>)parserForHeaderField:(NSString *)_name {
163 static id defParserSet = nil;
164 if (defParserSet == nil) {
166 [[NGMimeHeaderFieldParserSet defaultRfc822HeaderFieldParserSet] retain];
171 + (NSStringEncoding)defaultHeaderFieldEncoding {
172 return NSISOLatin1StringEncoding;
175 - (id)valueOfHeaderField:(NSString *)_name data:(id)_data {
176 // TODO: use iconv (if available, eg not on OSX !!!) to convert
177 // an unknown encoding to UTF-16 and create an NSConcrete*UTF16String
178 id<NGMimeHeaderFieldParser> parser;
182 if (self->delegateRespondsTo.parserParseHeaderFieldData)
183 value = [delegate parser:self parseHeaderField:_name data:_data];
188 if ([_data isKindOfClass:DataClass]) {
189 tmp = [[StringClass alloc]
191 encoding:[NGMimePartParser defaultHeaderFieldEncoding]];
194 tmp = [_data retain];
196 if ((parser = [self parserForHeaderField:_name]))
197 value = [parser parseValue:tmp ofHeaderField:_name];
199 value = [tmp stringByTrimmingSpaces];
201 value = [[value retain] autorelease];
210 NGMime_To = @"to"; : 2
212 NGMime_Date = @"date";
213 NGMime_Host = @"host";
214 NGMime_From = @"from"; : 4
216 NGMime_Cookie = @"cookie"
217 NGMime_Accept = @"accept" : 6
219 NGMime_Referer = @"referer"
220 NGMime_Subject = @"subject"; : 7
222 NGMime_xMailer = @"x-mailer";
223 NGMime_ReplyTo = @"reply-to"
224 NGMime_Received = @"received"; : 8
226 NGMime_Connection = @"connection"
227 NGMime_KeepAlive = @"keep-alive"
228 NGMime_UserAgent = @"user-agent";
229 NGMime_MessageID = @"message-id"; : 10
231 NGMime_ReturnPath = @"return-path"; : 11
233 NGMime_MimeVersion = @"mime-version";
234 NGMime_Organization = @"organization";
235 NGMime_ContentType = @"content-type"; : 12
237 NGMime_CacheControl = @"cache-control" : 13
239 NGMime_AcceptCharset = @"accept-charset"
240 NGMime_ContentLength = @"content-length"; : 14
242 NGMime_AcceptEncoding = @"accept-encoding"
243 NGMime_AcceptLanguage = @"accept-language" : 15
245 NGMime_ContentDisposition = @"content-disposition"; : 19
247 NGMime_ContentTransferEncoding = @"content-transfer-encoding"; : 25
250 static NSString *fieldNameForCString(id self, char *_cstring, int _len) {
251 if (HeaderNames == NULL)
252 [NGMimePartParser headerFieldNames];
258 if (_cstring[0] == 'c' && _cstring[1] == 'c')
259 return HeaderNames->cc;
260 else if (_cstring[0] == 't' && _cstring[1] == 'o')
261 return HeaderNames->to;
264 if (_cstring[3] == 'e') {
265 if (strncmp(_cstring, "date", 4) == 0)
266 return HeaderNames->date;
268 else if (_cstring[3] == 'm') {
269 if (strncmp(_cstring, "from", 4) == 0)
270 return HeaderNames->from;
272 else if (_cstring[3] == 't') {
273 if (strncmp(_cstring, "host", 4) == 0)
274 return HeaderNames->host;
278 if (_cstring[5] == 't') {
279 if (strncmp(_cstring, "accept", 6) == 0)
280 return HeaderNames->accept;
282 if (_cstring[5] == 'e') {
283 if (strncmp(_cstring, "cookie", 6) == 0)
284 return HeaderNames->cookie;
288 if (_cstring[6] == 't') {
289 if (strncmp(_cstring, "subject", 7) == 0)
290 return HeaderNames->subject;
292 if (_cstring[6] == 'r') {
293 if (strncmp(_cstring, "referer", 7) == 0)
294 return HeaderNames->referer;
298 if (_cstring[5] == '-') {
299 if (strncmp(_cstring, "reply-to", 8) == 0)
300 return HeaderNames->replyTo;
302 if (_cstring[7] == 'd') {
303 if (strncmp(_cstring, "received", 8) == 0)
304 return HeaderNames->received;
306 if (_cstring[1] == '-') {
307 if (strncmp(_cstring, "x-mailer", 8) == 0)
308 return HeaderNames->xMailer;
312 if (_cstring[4] == '-') {
313 if (_cstring[6] == 'g') {
314 if (strncmp(_cstring, "user-agent", 10) == 0)
315 return HeaderNames->userAgent;
317 if (_cstring[6] == 'l') {
318 if (strncmp(_cstring, "keep-alive", 10) == 0)
319 return HeaderNames->keepAlive;
322 else if (_cstring[7] == '-') {
323 if (strncmp(_cstring, "message-id", 10) == 0)
324 return HeaderNames->messageID;
326 else if (_cstring[9] == 'n') {
327 if (strncmp(_cstring, "connection", 10) == 0)
328 return HeaderNames->connection;
332 if (_cstring[6] == '-') {
333 if (strncmp(_cstring, "return-path", 11) == 0)
334 return HeaderNames->returnPath;
338 if (_cstring[4] == '-') {
339 if (strncmp(_cstring, "mime-version", 12) == 0)
340 return HeaderNames->mimeVersion;
342 else if (_cstring[11] == 'n') {
343 if (strncmp(_cstring, "organization", 12) == 0)
344 return HeaderNames->organization;
346 else if (_cstring[7] == '-') {
347 if (strncmp(_cstring, "content-type", 12) == 0)
348 return HeaderNames->contentType;
352 if (_cstring[5] == '-') {
353 if (strncmp(_cstring, "cache-control", 13) == 0)
354 return HeaderNames->cacheControl;
358 if (_cstring[7] == '-') {
359 if (strncmp(_cstring, "content-length", 14) == 0) {
360 return HeaderNames->contentLength;
363 else if (_cstring[6] == '-') {
364 if (strncmp(_cstring, "accept-charset", 14) == 0)
365 return HeaderNames->acceptCharset;
369 if (_cstring[6] == '-') {
370 if (_cstring[7] == 'l') {
371 if (strncmp(_cstring, "accept-language", 15) == 0)
372 return HeaderNames->acceptLanguage;
374 else if (_cstring[7] == 'e') {
375 if (strncmp(_cstring, "accept-encoding", 15) == 0)
376 return HeaderNames->acceptEncoding;
381 if (_cstring[7] == '-') {
382 if (strncmp(_cstring, "content-disposition", 19) == 0)
383 return HeaderNames->contentDisposition;
387 if (_cstring[7] == '-') {
388 if (strncmp(_cstring, "content-transfer-encoding", 25) == 0)
389 return HeaderNames->contentTransferEncoding;
396 result = [NSString stringWithCString:_cstring length:_len];
399 [self logWithFormat:@"%s: found no headerfield constant for <%@>, "
400 @"generate new string", __PRETTY_FUNCTION__, result];
407 - (NSString *)fieldNameForCString:(char *)_cstring length:(int)_len {
408 return fieldNameForCString(self, _cstring, _len);
411 - (NGHashMap *)parseHeader {
412 // TODO: split up this huge method!
413 /* parse headers until an empty line is seen */
414 NGMutableHashMap *header = nil;
415 NSMutableData *fieldValue = nil;
416 NSMutableString *fieldName = nil;
417 NSString *realFieldName = nil;
418 BOOL foundEndOfHeaders = NO;
421 NSAutoreleasePool *pool;
423 ASSIGN(self->contentTransferEncoding, (id)nil);
425 if (self->delegateRespondsTo.parserWillParseHeader) {
426 if (![self->delegate parserWillParseHeader:self])
430 pool = [[NSAutoreleasePool alloc] init];
431 fieldValue = [NSMutableData dataWithCapacity:512];
432 header = [NGMutableHashMap hashMapWithCapacity:128];
433 buf = calloc(self->bufLen, 1);
436 while (!foundEndOfHeaders) {
438 BOOL endOfFieldBody = NO;
440 /* reset mutable vars */
447 [fieldValue setLength:0];
449 /* parse fieldName */
458 while ((c = _la(self, 0)) != ':') {
463 /* check for leading '\r\n' or '\n' */
468 else if (c == '\n') {
469 /* finish, found header starting with newline */
470 foundEndOfHeaders = YES;
471 endOfFieldBody = YES;
472 self->useContentLength = NO;
473 _consume(self, 1); // consume newline
474 break; /* leave local loop */
477 else if ((fnlen == 1) && lastWasCR) {
479 /* finish, found \r\n */
480 foundEndOfHeaders = YES;
481 endOfFieldBody = YES;
482 self->useContentLength = NO;
484 _consume(self, 1); // consume newline
485 break; /* leave local loop */
495 if (bufCnt >= self->bufLen) {
498 for (i = 0; i < bufCnt; i++)
499 buf[i] = tolower((int)buf[i]);
502 if (fieldName == nil) {
503 fieldName = [[MStringClass alloc] initWithCString:buf length:bufCnt];
508 s = [[StringClass alloc] initWithCString:buf length:bufCnt];
509 [fieldName appendString:s];
510 [s release]; s = nil;
515 if (foundEndOfHeaders)
516 /* leave main loop */
522 for (i = 0; i < bufCnt; i++)
523 buf[i] = tolower((int)buf[i]);
525 if ([fieldName length] == 0) {
526 /* const headernames are always smaller than bufLen */
527 realFieldName = fieldNameForCString(self, buf, bufCnt);
532 s = [[StringClass alloc] initWithCString:buf length:bufCnt];
533 [fieldName appendString:s];
534 [s release]; s = nil;
535 realFieldName = fieldName;
538 NSLog(@"WARNING(%s:%i): 1 an error occured during header-field "
539 @" parsing (maybe end of stream) fieldName: %@",
540 __PRETTY_FUNCTION__, __LINE__, fieldName);
541 foundEndOfHeaders = YES;
542 endOfFieldBody = YES;
547 realFieldName = fieldName;
549 _consume(self, 1); // consume ':'
551 /* parse fieldBody */
554 while (!endOfFieldBody) {
555 int laC0 = _la(self, 0);
560 if (laC0 == '\r') { // CR
561 int laC1 = _la(self, 1);
563 if (isRfc822_LWSP(laC1)) { // CR LSWSP
564 _consume(self, 2); // folding
566 else if (laC1 == '\n') { // CR LF
567 int laC2 = _la(self, 2);
569 if (isRfc822_LWSP(laC2)) { // CR LF LWSP
570 _consume(self, 3); // folding
572 else if (laC2 == '\r') { // CR LF CR
573 int laC3 = _la(self, 3);
575 if (laC3 == '\n') { // CR LF CR LF
577 foundEndOfHeaders = YES; // end of headers
578 endOfFieldBody = YES;
581 _consume(self, 3); // ignored ??
584 else if (laC2 == '\n') { // CR LF LF
586 foundEndOfHeaders = YES; // end of headers
587 endOfFieldBody = YES;
591 endOfFieldBody = YES; // next header field
596 endOfFieldBody = YES; // next header field
599 else if (laC0 == '\n') { // LF
600 int laC1 = _la(self, 1);
602 if (isRfc822_LWSP(laC1)) { // LF LWSP
603 _consume(self, 2); // folding
605 else if (laC1 == '\n') { // LF LF
607 foundEndOfHeaders = YES; // end of headers
608 endOfFieldBody = YES;
610 else if (laC1 == '\r') { // LF CR
611 int laC2 = _la(self, 2);
613 if (isRfc822_LWSP(laC2)) { // LF CR LWSP
614 _consume(self, 3); // folding
616 else if (laC2 == '\n') { // LF CR LF
617 _consume(self, 3); //
618 foundEndOfHeaders = YES; // end of headers
619 endOfFieldBody = YES;
623 endOfFieldBody = YES; // next header field
628 endOfFieldBody = YES; // next header field
632 if ((bufCnt != 0) || (!isRfc822_LWSP(laC0))) {
633 /* ignore leading white spaces */
634 buf[bufCnt++] = laC0;
637 if (bufCnt >= self->bufLen) {
638 [fieldValue appendBytes:buf length:bufCnt];
644 [fieldValue appendBytes:buf length:bufCnt];
647 if (!endOfFieldBody) {
649 @"WARNING(%s:%i): 2 an error occured during body parsing "
650 @"(maybe end of stream)", __PRETTY_FUNCTION__, __LINE__];
651 foundEndOfHeaders = YES;
653 if (realFieldName != nil) {
654 BOOL keepHeader = YES;
656 if (HeaderNames == NULL)
657 [NGMimePartParser headerFieldNames];
659 if (realFieldName == HeaderNames->contentTransferEncoding) {
661 const unsigned char *cstr;
663 len = [fieldValue length];
664 cstr = [fieldValue bytes];
666 keepHeader = NO; // don't keep content-tranfer-encodings
668 while (isRfc822_LWSP(*cstr) && (len > 0)) { // strip leading spaces
672 if (len > 0) { // len==0 means the value was a string of LWSP
673 [self->contentTransferEncoding release];
674 self->contentTransferEncoding =
675 [[StringClass alloc] initWithCString:cstr length:len];
678 ASSIGN(self->contentTransferEncoding, (id)nil);
681 take a look on content-length headers, since the parser
682 needs to know this for reading in the body ..
684 if (keepHeader && self->useContentLength) {
685 if (realFieldName == HeaderNames->contentLength) {
687 const unsigned char *cstr;
689 len = [fieldValue length];
690 cstr = [fieldValue bytes];
692 while (isRfc822_LWSP(*cstr) && (len > 0)) { // strip leading spaces
696 if (len > 0) { // len==0 means the value was a string of LWSP
697 unsigned char buf[len + 1];
700 while (isdigit(*cstr) && (i < len)) { // extract following digits
704 buf[i] = '\0'; // stop string after last digit (ignore the rest)
705 self->contentLength = atoi(buf);
708 /* header value are only spaces */
709 self->contentLength = -1;
713 /* ask delegate if the header is to be kept */
715 if (self->delegateRespondsTo.parserKeepHeaderFieldData)
716 keepHeader = [self->delegate parser:self
717 keepHeaderField:realFieldName
723 value = [self valueOfHeaderField:realFieldName
727 value = [value retain];
728 /* ask delegate if the header is to be kept */
729 if (self->delegateRespondsTo.parserKeepHeaderFieldValue) {
730 keepHeader = [self->delegate parser:self
731 keepHeaderField:realFieldName
735 NSAssert(realFieldName, @"missing field name ..");
736 NSAssert(value, @"missing field value ..");
739 check whether content-length, content-type,
740 subject already in hashmap
742 if (_checkKey(self, header, realFieldName))
743 [header addObject:value forKey:realFieldName];
755 if (self->delegateRespondsTo.parserDidParseHeader)
756 [self->delegate parser:self didParseHeader:header];
758 header = [header retain];
761 return [header autorelease];
764 - (NSData *)readBodyUnknownLengthStream {
765 static NSMutableData *dataObject = nil;
766 NGIOReadMethodType readBytes = NULL;
770 char buf[self->bufLen];
771 void (*appendBytes)(id,SEL,const void *,unsigned);
774 *(&readBytes) = NULL;
776 if ([self->source respondsToSelector:@selector(methodForSelector:)]) {
777 readBytes = (NGIOReadMethodType)
778 [self->source methodForSelector:@selector(readBytes:count:)];
781 *(&appendBytes) = NULL;
785 /* check whether we can reuse the dataObj ... */
787 *(&body) = [dataObject autorelease];
788 dataObject = nil; /* mark as used ... */
791 *(&body) = [[[NSMutableData alloc] initWithCapacity:100010] autorelease];
795 appendBytes = (void(*)(id,SEL,const void *, unsigned))
796 [body methodForSelector:@selector(appendBytes:length:)];
803 _la(self, self->bufLen - 1);
806 if (![localException isKindOfClass:[NGEndOfStreamException class]])
807 [localException raise];
812 bufCnt = (readBytes != NULL)
813 ? readBytes(self->source, @selector(readBytes:count:),
815 : [self->source readBytes:buf count:self->bufLen];
817 if (bufCnt == NGStreamError) {
818 e = [self->source lastException];
820 if ([e isKindOfClass:[NGEndOfStreamException class]])
827 /* perform any on-the-fly encodings */
829 /* add to body data */
830 appendBytes(body, @selector(appendBytes:length:), buf, bufCnt);
835 if (![localException isKindOfClass:[NGEndOfStreamException class]])
836 [localException raise];
839 if (bufCnt > 0 && bufCnt != NGStreamError) {
840 appendBytes(body, @selector(appendBytes:length:), buf, bufCnt);
845 ASSIGN(self->contentTransferEncoding, (id)nil);
849 /* remember that object for reuse ... */
850 if (dataObject == nil && [body length] < MAX_DATA_OBJECT_SIZE_CACHE) {
851 dataObject = [body retain];
852 [dataObject setLength:0];
855 return [rbody autorelease];
858 - (NSData *)readBodyUnknownLengthData {
859 return [self->sourceData subdataWithRange:
860 NSMakeRange(self->dataIdx, self->byteLen - self->dataIdx)];
863 - (NSData *)readBodyUnknownLength {
864 return (self->source)
865 ? [self readBodyUnknownLengthStream]
866 : [self readBodyUnknownLengthData];
869 - (NSData *)readBodyWithKnownLengthFromStream:(unsigned)_len {
870 NGIOReadMethodType readBytes = NULL;
872 unsigned char *buf = NULL;
875 *(&readBytes) = NULL;
877 if ([self->source respondsToSelector:@selector(methodForSelector:)]) {
878 readBytes = (NGIOReadMethodType)
879 [self->source methodForSelector:@selector(readBytes:count:)];
886 buf = calloc(_len, sizeof(char));
891 if (self->contentLength > self->bufLen)
892 _la(self, self->bufLen - 1);
894 _la(self, self->contentLength - 1);
897 if ([localException isKindOfClass:[NGEndOfStreamException class]]) {
899 "WARNING(%s): EOF occurred before whole content was read "
900 "(content-length=%i, read=%i)\n", __PRETTY_FUNCTION__,
901 self->contentLength, readB);
905 [localException raise];
911 while (self->contentLength != readB) {
912 int tmp = self->contentLength - readB;
914 readB += (readBytes != NULL)
915 ? readBytes(self->source, @selector(readBytes:count:),
917 : [self->source readBytes:(buf + readB) count:tmp];
919 if (readB == NGStreamError) {
920 [[self->source lastException] raise];
923 tmp = self->contentLength - readB;
925 if (tmp > self->bufLen)
926 _la(self, self->bufLen - 1);
933 if ([localException isKindOfClass:[NGEndOfStreamException class]]) {
935 "WARNING(%s): EOF occurred before whole content was read "
936 "(content-length=%i, read=%i)\n", __PRETTY_FUNCTION__,
937 self->contentLength, readB);
941 [localException raise];
946 rbody = buf ? [NSData dataWithBytes:buf length:readB] : nil;
951 - (NSData *)readBodyWithKnownLengthFromData:(unsigned)_len {
954 data = [self->sourceData subdataWithRange:
955 NSMakeRange(self->dataIdx, self->byteLen - self->dataIdx)];
956 if ([data length] != _len) {
957 NSLog(@"%s[%i]: got wrong data %d _len %d", __PRETTY_FUNCTION__, __LINE__,
958 [data length], _len);
964 - (NSData *)readBodyWithKnownLength:(unsigned)_len {
965 return (self->source)
966 ? [self readBodyWithKnownLengthFromStream:_len]
967 : [self readBodyWithKnownLengthFromData:_len];
970 - (NSData *)applyTransferEncoding:(NSString *)_encoding onData:(NSData *)_data{
971 // TODO: make this an NSData category
975 if ((len = [_encoding length]) == 0)
978 _encoding = [_encoding lowercaseString];
980 c = [_encoding characterAtIndex:0];
983 if ([_encoding hasPrefix:@"quoted"])
984 return [_data dataByDecodingQuotedPrintable];
987 if ([_encoding hasPrefix:@"base64"])
988 return [_data dataByDecodingBase64];
989 else if ([@"binary" isEqualToString:_encoding])
996 if ([@"7bit" isEqualToString:_encoding])
998 if ([@"8bit" isEqualToString:_encoding])
1002 else if (len == 8) {
1003 if ([@"identity" isEqualToString:_encoding])
1009 if ([@"unknown-8bit" isEqualToString:_encoding])
1019 - (NSData *)readBody {
1020 /* Read data of body and apply content-transfer-encoding if required. */
1021 NSAutoreleasePool *pool;
1022 NSData *rbody = nil;
1024 pool = [[NSAutoreleasePool alloc] init];
1026 if ((self->contentLength == -1) || (self->contentLength == 0)) {
1027 rbody = [self readBodyUnknownLength];
1030 /* note: this is called only, if self->useContentLength is set ! */
1031 rbody = [self readBodyWithKnownLength:self->contentLength];
1034 if ([self->contentTransferEncoding length] > 0) {
1037 new = [self applyTransferEncoding:self->contentTransferEncoding
1040 ASSIGN(self->contentTransferEncoding, (id)nil);
1044 [self logWithFormat:@"WARNING(%s): "
1045 @"encountered unknown content-transfer-encoding: '%@'",
1046 __PRETTY_FUNCTION__,
1047 self->contentTransferEncoding];
1051 rbody = [rbody retain];
1053 return [rbody autorelease];
1056 - (NSData *)decodeBody:(NSData *)_data ofPart:(id<NGMimePart>)_part {
1057 return (self->delegateRespondsTo.parserDecodeBodyOfPart)
1058 ? [self->delegate parser:self decodeBody:_data ofPart:_part]
1062 - (NGMimeType *)defaultContentTypeForPart:(id<NGMimePart>)_part {
1063 static NGMimeType *octetType = nil;
1065 if (octetType == nil)
1066 octetType = [[NGMimeType mimeType:@"application/octet-stream"] retain];
1070 - (id<NGMimeBodyParser>)parserForBodyOfPart:(id<NGMimePart>)_p
1074 NGMimeType *contentType;
1075 id<NGMimeBodyParser> bodyParser = nil;
1077 ctype = [_p contentType];
1079 contentType = ([ctype isKindOfClass:[NGMimeType class]])
1081 : [NGMimeType mimeType:[ctype stringValue]];
1083 if (self->delegateRespondsTo.parserBodyParserForPart) {
1084 if ((bodyParser = [self->delegate parser:self bodyParserForPart:_p]))
1088 if (contentType == nil) {
1089 contentType = [self defaultContentTypeForPart:_p];
1093 if ([[contentType type] isEqualToString:@"multipart"]) {
1094 bodyParser = [[[NGMimeMultipartBodyParser alloc] init] autorelease];
1096 else if ([[contentType type] isEqualToString:@"text"] &&
1097 [[contentType subType] isEqualToString:@"plain"]) {
1098 bodyParser = [[[NGMimeTextBodyParser alloc] init] autorelease];
1104 - (void)parseBodyOfPart:(id<NGMimePart>)_part {
1105 NGMimeBodyParser *parser = nil;
1106 NSData *rawBody = nil;
1109 rawBody = [self readBody];
1111 /* apply content-encoding, transfer-encoding and similiar */
1112 rawBody = [self decodeBody:rawBody ofPart:_part];
1114 if (self->delegateRespondsTo.parserParseRawBodyDataOfPart) {
1118 [self->delegate parser:self parseRawBodyData:rawBody ofPart:_part];
1120 if (didParse) return;
1123 parser = (NGMimeBodyParser *)[self parserForBodyOfPart:_part data:rawBody];
1125 /* make sure delegate keeps being around .. */
1126 self->delegate = [[self->delegate retain] autorelease];
1128 body = [parser parseBodyOfPart:_part
1130 delegate:self->delegate];
1132 else if (rawBody) { /* no parser found for body */
1133 if (body == nil) body = rawBody;
1135 [_part setBody:body];
1140 - (id<NGMimePart>)producePartWithHeader:(NGHashMap *)_header {
1141 [self subclassResponsibility:_cmd];
1145 - (BOOL)prepareForParsingFromData:(NSData *)_data {
1149 ASSIGN(self->sourceData, _data);
1150 self->sourceBytes = [self->sourceData bytes];
1151 self->byteLen = [self->sourceData length];
1153 self->contentLength = -1;
1158 - (BOOL)prepareForParsingFromStream:(id<NGStream>)_stream {
1162 if (self->source != _stream) {
1165 bb = [NGByteBuffer alloc];
1166 bb = [bb initWithSource:_stream la:self->bufLen];
1167 [self->source release];
1170 if ([self->source respondsToSelector:@selector(methodForSelector:)]) {
1171 self->la = (int (*)(id, SEL, unsigned))
1172 [self->source methodForSelector:@selector(la:)];
1173 self->consume = (void (*)(id, SEL))
1174 [self->source methodForSelector:@selector(consume)];
1175 self->consumeCnt = (void (*)(id, SEL, unsigned))
1176 [self->source methodForSelector:@selector(consume:)];
1180 self->consume = NULL;
1181 self->consumeCnt = NULL;
1183 self->contentLength = -1;
1188 - (void)finishParsingOfPart:(id<NGMimePart>)_part {
1189 [self->source release]; self->source = nil;
1190 self->contentLength = -1;
1193 self->consume = NULL;
1194 self->consumeCnt = NULL;
1197 - (void)finishParsingOfPartFromData:(id<NGMimePart>)_part {
1198 [self->sourceData release]; self->sourceData = nil;
1199 self->sourceBytes = NULL;
1202 self->contentLength = -1;
1205 - (BOOL)parsePrefix {
1209 - (void)parseSuffix {
1212 - (id<NGMimePart>)parsePart {
1213 id<NGMimePart> part = nil;
1217 if (![self parsePrefix])
1220 if ((header = [self parseHeader]) == nil)
1223 part = [self producePartWithHeader:header];
1225 doParse = (delegateRespondsTo.parserWillParseBodyOfPart)
1226 ? [delegate parser:self willParseBodyOfPart:part]
1230 NSAutoreleasePool *pool;
1232 pool = [[NSAutoreleasePool alloc] init];
1233 [self parseBodyOfPart:part];
1236 if (delegateRespondsTo.parserDidParseBodyOfPart)
1237 [delegate parser:self didParseBodyOfPart:part];
1244 - (id<NGMimePart>)parsePartFromStream:(id<NGStream>)_stream {
1247 if (![self prepareForParsingFromStream:_stream])
1250 p = [self parsePart];
1251 [self finishParsingOfPart:p];
1255 - (id<NGMimePart>)parsePartFromData:(NSData *)_data {
1256 id<NGMimePart> part;
1258 if ([_data isKindOfClass:NSMutableDataClass]) {
1259 NGDataStream *dataStream;
1261 dataStream = [NGDataStream streamWithData:_data];
1262 part = [self parsePartFromStream:dataStream];
1267 if ([self prepareForParsingFromData:_data]) {
1268 part = [self parsePart];
1269 [self finishParsingOfPartFromData:part];
1278 - (BOOL)doesUseContentLength {
1279 return self->useContentLength;
1281 - (void)setUseContentLength:(BOOL)_use {
1282 self->useContentLength = _use;
1287 static inline int _la(NGMimePartParser *self, int _la) {
1289 return (self->la != NULL) ? self->la(self->source, @selector(la:), _la)
1290 : [self->source la:_la];
1293 if ((self->dataIdx+_la) < self->byteLen)
1294 return self->sourceBytes[self->dataIdx+_la];
1300 static inline void _consume(NGMimePartParser *self, int _cnt) {
1303 if (self->consume != NULL)
1304 self->consume(self->source, @selector(consume));
1306 [self->source consume];
1309 if (self->consumeCnt != NULL)
1310 self->consumeCnt(self->source, @selector(consume:), _cnt);
1312 [self->source consume:_cnt];
1316 if ((self->dataIdx+_cnt) <= self->byteLen) {
1317 self->dataIdx += _cnt;
1320 NSLog(@"%s[%i]: error try to read over buffer len self->dataIdx %d "
1321 @"_cnt %d byteLen %d", __PRETTY_FUNCTION__, __LINE__,
1322 self->dataIdx, _cnt, self->byteLen);
1327 static inline BOOL _checkKey(NGMimePartParser *self, NGHashMap *_map,
1330 if (HeaderNames == NULL)
1331 [NGMimePartParser headerFieldNames];
1333 if ((_key == HeaderNames->contentLength) ||
1334 _key == HeaderNames->contentType) {
1335 if ([_map countObjectsForKey:_key] > 0)
1341 @end /* NGMimePartParser */