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
23 #include "NGMimePartParser.h"
24 #include "NGMimeBodyParser.h"
25 #include "NGMimeType.h"
26 #include "NGMimeUtilities.h"
29 /* this tunes, how big reused data cache objects may get (10MB) */
30 #define MAX_DATA_OBJECT_SIZE_CACHE (10*1024*1024)
32 @implementation NGMimePartParser
34 static Class StringClass = Nil;
35 static Class MStringClass = Nil;
36 static Class DataClass = Nil;
37 static Class NSMutableDataClass = NULL;
39 static NGMimeHeaderNames *HeaderNames = NULL;
45 static int MimeLogEnabled = -1;
48 static BOOL isInitialized = NO;
49 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
50 if (isInitialized) return;
53 MimeLogEnabled = [ud boolForKey:@"MimeLogEnabled"] ? 1 : 0;
54 MStringClass = [NSMutableString class];
55 StringClass = [NSString class];
56 DataClass = [NSData class];
57 NSMutableDataClass = [NSMutableData class];
60 static inline int _la(NGMimePartParser *self, int _la);
61 static inline void _consume(NGMimePartParser *self, int _cnt);
62 static inline BOOL _checkKey(NGMimePartParser *self, NGHashMap *_map,
65 + (NGMimeHeaderNames *)headerFieldNames {
66 if (HeaderNames == NULL) {
67 HeaderNames = malloc(sizeof(NGMimeHeaderNames));
69 HeaderNames->accept = @"accept";
70 HeaderNames->acceptLanguage = @"accept-language";
71 HeaderNames->acceptEncoding = @"accept-encoding";
72 HeaderNames->acceptCharset = @"accept-charset";
73 HeaderNames->cacheControl = @"cache-control";
74 HeaderNames->cc = @"cc";
75 HeaderNames->connection = @"connection";
76 HeaderNames->contentDisposition = @"content-disposition";
77 HeaderNames->contentLength = @"content-length";
78 HeaderNames->contentTransferEncoding = @"content-transfer-encoding";
79 HeaderNames->contentType = @"content-type";
80 HeaderNames->cookie = @"cookie";
81 HeaderNames->date = @"date";
82 HeaderNames->from = @"from";
83 HeaderNames->host = @"host";
84 HeaderNames->keepAlive = @"keep-alive";
85 HeaderNames->messageID = @"message-id";
86 HeaderNames->mimeVersion = @"mime-version";
87 HeaderNames->organization = @"organization";
88 HeaderNames->received = @"received";
89 HeaderNames->returnPath = @"return-path";
90 HeaderNames->referer = @"referer";
91 HeaderNames->replyTo = @"reply-to";
92 HeaderNames->subject = @"subject";
93 HeaderNames->to = @"to";
94 HeaderNames->userAgent = @"user-agent";
95 HeaderNames->xMailer = @"x-mailer";
101 if ((self = [super init])) {
103 self->contentLength = -1;
109 [self->contentTransferEncoding release];
110 [self->source release];
111 [self->sourceData release];
117 - (void)setDelegate:(id)_delegate {
118 self->delegate = _delegate;
120 self->delegateRespondsTo.parserWillParseHeader =
121 [self->delegate respondsToSelector:@selector(parserWillParseHeader:)];
123 self->delegateRespondsTo.parserDidParseHeader =
124 [self->delegate respondsToSelector:@selector(parser:didParseHeader:)];
126 self->delegateRespondsTo.parserKeepHeaderFieldData =
127 [self->delegate respondsToSelector:@selector(parser:keepHeaderField:data:)];
129 self->delegateRespondsTo.parserKeepHeaderFieldValue =
130 [self->delegate respondsToSelector:
131 @selector(parser:keepHeaderField:value:)];
133 self->delegateRespondsTo.parserFoundCommentInHeaderField =
134 [self->delegate respondsToSelector:
135 @selector(parser:foundComment:inHeaderField:)];
137 self->delegateRespondsTo.parserWillParseBodyOfPart =
138 [self->delegate respondsToSelector:@selector(parser:willParseBodyOfPart:)];
140 self->delegateRespondsTo.parserDidParseBodyOfPart =
141 [self->delegate respondsToSelector:@selector(parser:didParseBodyOfPart:)];
143 self->delegateRespondsTo.parserParseRawBodyDataOfPart =
144 [self->delegate respondsToSelector:
145 @selector(parser:parseRawBodyData:ofPart:)];
147 self->delegateRespondsTo.parserBodyParserForPart =
148 [self->delegate respondsToSelector:@selector(parser:bodyParserForPart:)];
150 self->delegateRespondsTo.parserDecodeBodyOfPart =
151 [self->delegate respondsToSelector:@selector(parser:decodeBody:ofPart:)];
153 self->delegateRespondsTo.parserParseHeaderFieldData =
154 [self->delegate respondsToSelector:@selector(parser:parseHeaderField:data:)];
158 return self->delegate;
163 - (id<NGMimeHeaderFieldParser>)parserForHeaderField:(NSString *)_name {
164 static id defParserSet = nil;
165 if (defParserSet == nil) {
167 [[NGMimeHeaderFieldParserSet defaultRfc822HeaderFieldParserSet] retain];
172 + (NSStringEncoding)defaultHeaderFieldEncoding {
173 return NSISOLatin1StringEncoding;
176 - (id)valueOfHeaderField:(NSString *)_name data:(id)_data {
177 // TODO: use iconv (if available, eg not on OSX !!!) to convert
178 // an unknown encoding to UTF-16 and create an NSConcrete*UTF16String
179 id<NGMimeHeaderFieldParser> parser;
183 if (self->delegateRespondsTo.parserParseHeaderFieldData)
184 value = [delegate parser:self parseHeaderField:_name data:_data];
189 if ([_data isKindOfClass:DataClass]) {
190 tmp = [[StringClass alloc]
192 encoding:[NGMimePartParser defaultHeaderFieldEncoding]];
195 tmp = [_data retain];
197 if ((parser = [self parserForHeaderField:_name]))
198 value = [parser parseValue:tmp ofHeaderField:_name];
200 value = [tmp stringByTrimmingSpaces];
202 value = [[value retain] autorelease];
211 NGMime_To = @"to"; : 2
213 NGMime_Date = @"date";
214 NGMime_Host = @"host";
215 NGMime_From = @"from"; : 4
217 NGMime_Cookie = @"cookie"
218 NGMime_Accept = @"accept" : 6
220 NGMime_Referer = @"referer"
221 NGMime_Subject = @"subject"; : 7
223 NGMime_xMailer = @"x-mailer";
224 NGMime_ReplyTo = @"reply-to"
225 NGMime_Received = @"received"; : 8
227 NGMime_Connection = @"connection"
228 NGMime_KeepAlive = @"keep-alive"
229 NGMime_UserAgent = @"user-agent";
230 NGMime_MessageID = @"message-id"; : 10
232 NGMime_ReturnPath = @"return-path"; : 11
234 NGMime_MimeVersion = @"mime-version";
235 NGMime_Organization = @"organization";
236 NGMime_ContentType = @"content-type"; : 12
238 NGMime_CacheControl = @"cache-control" : 13
240 NGMime_AcceptCharset = @"accept-charset"
241 NGMime_ContentLength = @"content-length"; : 14
243 NGMime_AcceptEncoding = @"accept-encoding"
244 NGMime_AcceptLanguage = @"accept-language" : 15
246 NGMime_ContentDisposition = @"content-disposition"; : 19
248 NGMime_ContentTransferEncoding = @"content-transfer-encoding"; : 25
251 static NSString *fieldNameForCString(id self, char *_cstring, int _len) {
252 if (HeaderNames == NULL)
253 [NGMimePartParser headerFieldNames];
259 if (_cstring[0] == 'c' && _cstring[1] == 'c')
260 return HeaderNames->cc;
261 else if (_cstring[0] == 't' && _cstring[1] == 'o')
262 return HeaderNames->to;
265 if (_cstring[3] == 'e') {
266 if (strncmp(_cstring, "date", 4) == 0)
267 return HeaderNames->date;
269 else if (_cstring[3] == 'm') {
270 if (strncmp(_cstring, "from", 4) == 0)
271 return HeaderNames->from;
273 else if (_cstring[3] == 't') {
274 if (strncmp(_cstring, "host", 4) == 0)
275 return HeaderNames->host;
279 if (_cstring[5] == 't') {
280 if (strncmp(_cstring, "accept", 6) == 0)
281 return HeaderNames->accept;
283 if (_cstring[5] == 'e') {
284 if (strncmp(_cstring, "cookie", 6) == 0)
285 return HeaderNames->cookie;
289 if (_cstring[6] == 't') {
290 if (strncmp(_cstring, "subject", 7) == 0)
291 return HeaderNames->subject;
293 if (_cstring[6] == 'r') {
294 if (strncmp(_cstring, "referer", 7) == 0)
295 return HeaderNames->referer;
299 if (_cstring[5] == '-') {
300 if (strncmp(_cstring, "reply-to", 8) == 0)
301 return HeaderNames->replyTo;
303 if (_cstring[7] == 'd') {
304 if (strncmp(_cstring, "received", 8) == 0)
305 return HeaderNames->received;
307 if (_cstring[1] == '-') {
308 if (strncmp(_cstring, "x-mailer", 8) == 0)
309 return HeaderNames->xMailer;
313 if (_cstring[4] == '-') {
314 if (_cstring[6] == 'g') {
315 if (strncmp(_cstring, "user-agent", 10) == 0)
316 return HeaderNames->userAgent;
318 if (_cstring[6] == 'l') {
319 if (strncmp(_cstring, "keep-alive", 10) == 0)
320 return HeaderNames->keepAlive;
323 else if (_cstring[7] == '-') {
324 if (strncmp(_cstring, "message-id", 10) == 0)
325 return HeaderNames->messageID;
327 else if (_cstring[9] == 'n') {
328 if (strncmp(_cstring, "connection", 10) == 0)
329 return HeaderNames->connection;
333 if (_cstring[6] == '-') {
334 if (strncmp(_cstring, "return-path", 11) == 0)
335 return HeaderNames->returnPath;
339 if (_cstring[4] == '-') {
340 if (strncmp(_cstring, "mime-version", 12) == 0)
341 return HeaderNames->mimeVersion;
343 else if (_cstring[11] == 'n') {
344 if (strncmp(_cstring, "organization", 12) == 0)
345 return HeaderNames->organization;
347 else if (_cstring[7] == '-') {
348 if (strncmp(_cstring, "content-type", 12) == 0)
349 return HeaderNames->contentType;
353 if (_cstring[5] == '-') {
354 if (strncmp(_cstring, "cache-control", 13) == 0)
355 return HeaderNames->cacheControl;
359 if (_cstring[7] == '-') {
360 if (strncmp(_cstring, "content-length", 14) == 0) {
361 return HeaderNames->contentLength;
364 else if (_cstring[6] == '-') {
365 if (strncmp(_cstring, "accept-charset", 14) == 0)
366 return HeaderNames->acceptCharset;
370 if (_cstring[6] == '-') {
371 if (_cstring[7] == 'l') {
372 if (strncmp(_cstring, "accept-language", 15) == 0)
373 return HeaderNames->acceptLanguage;
375 else if (_cstring[7] == 'e') {
376 if (strncmp(_cstring, "accept-encoding", 15) == 0)
377 return HeaderNames->acceptEncoding;
382 if (_cstring[7] == '-') {
383 if (strncmp(_cstring, "content-disposition", 19) == 0)
384 return HeaderNames->contentDisposition;
388 if (_cstring[7] == '-') {
389 if (strncmp(_cstring, "content-transfer-encoding", 25) == 0)
390 return HeaderNames->contentTransferEncoding;
397 result = [NSString stringWithCString:_cstring length:_len];
400 [self logWithFormat:@"%s: found no headerfield constant for <%@>, "
401 @"generate new string", __PRETTY_FUNCTION__, result];
408 - (NSString *)fieldNameForCString:(char *)_cstring length:(int)_len {
409 return fieldNameForCString(self, _cstring, _len);
412 - (NGHashMap *)parseHeader {
413 // TODO: split up this huge method!
414 /* parse headers until an empty line is seen */
415 NGMutableHashMap *header = nil;
416 NSMutableData *fieldValue = nil;
417 NSMutableString *fieldName = nil;
418 NSString *realFieldName = nil;
419 BOOL foundEndOfHeaders = NO;
422 NSAutoreleasePool *pool;
424 ASSIGN(self->contentTransferEncoding, (id)nil);
426 if (self->delegateRespondsTo.parserWillParseHeader) {
427 if (![self->delegate parserWillParseHeader:self])
431 pool = [[NSAutoreleasePool alloc] init];
432 fieldValue = [NSMutableData dataWithCapacity:512];
433 header = [NGMutableHashMap hashMapWithCapacity:128];
434 buf = calloc(self->bufLen, 1);
437 while (!foundEndOfHeaders) {
439 BOOL endOfFieldBody = NO;
441 /* reset mutable vars */
448 [fieldValue setLength:0];
450 /* parse fieldName */
459 while ((c = _la(self, 0)) != ':') {
464 /* check for leading '\r\n' or '\n' */
469 else if (c == '\n') {
470 /* finish, found header starting with newline */
471 foundEndOfHeaders = YES;
472 endOfFieldBody = YES;
473 self->useContentLength = NO;
474 _consume(self, 1); // consume newline
475 break; /* leave local loop */
478 else if ((fnlen == 1) && lastWasCR) {
480 /* finish, found \r\n */
481 foundEndOfHeaders = YES;
482 endOfFieldBody = YES;
483 self->useContentLength = NO;
485 _consume(self, 1); // consume newline
486 break; /* leave local loop */
496 if (bufCnt >= self->bufLen) {
499 for (i = 0; i < bufCnt; i++)
500 buf[i] = tolower((int)buf[i]);
503 if (fieldName == nil) {
504 fieldName = [[MStringClass alloc] initWithCString:buf length:bufCnt];
509 s = [[StringClass alloc] initWithCString:buf length:bufCnt];
510 [fieldName appendString:s];
511 [s release]; s = nil;
516 if (foundEndOfHeaders)
517 /* leave main loop */
523 for (i = 0; i < bufCnt; i++)
524 buf[i] = tolower((int)buf[i]);
526 if ([fieldName length] == 0) {
527 /* const headernames are always smaller than bufLen */
528 realFieldName = fieldNameForCString(self, buf, bufCnt);
533 s = [[StringClass alloc] initWithCString:buf length:bufCnt];
534 [fieldName appendString:s];
535 [s release]; s = nil;
536 realFieldName = fieldName;
539 NSLog(@"WARNING(%s:%i): 1 an error occured during header-field "
540 @" parsing (maybe end of stream) fieldName: %@",
541 __PRETTY_FUNCTION__, __LINE__, fieldName);
542 foundEndOfHeaders = YES;
543 endOfFieldBody = YES;
548 realFieldName = fieldName;
550 _consume(self, 1); // consume ':'
552 /* parse fieldBody */
555 while (!endOfFieldBody) {
556 int laC0 = _la(self, 0);
561 if (laC0 == '\r') { // CR
562 int laC1 = _la(self, 1);
564 if (isRfc822_LWSP(laC1)) { // CR LSWSP
565 _consume(self, 2); // folding
567 else if (laC1 == '\n') { // CR LF
568 int laC2 = _la(self, 2);
570 if (isRfc822_LWSP(laC2)) { // CR LF LWSP
571 _consume(self, 3); // folding
573 else if (laC2 == '\r') { // CR LF CR
574 int laC3 = _la(self, 3);
576 if (laC3 == '\n') { // CR LF CR LF
578 foundEndOfHeaders = YES; // end of headers
579 endOfFieldBody = YES;
582 _consume(self, 3); // ignored ??
585 else if (laC2 == '\n') { // CR LF LF
587 foundEndOfHeaders = YES; // end of headers
588 endOfFieldBody = YES;
592 endOfFieldBody = YES; // next header field
597 endOfFieldBody = YES; // next header field
600 else if (laC0 == '\n') { // LF
601 int laC1 = _la(self, 1);
603 if (isRfc822_LWSP(laC1)) { // LF LWSP
604 _consume(self, 2); // folding
606 else if (laC1 == '\n') { // LF LF
608 foundEndOfHeaders = YES; // end of headers
609 endOfFieldBody = YES;
611 else if (laC1 == '\r') { // LF CR
612 int laC2 = _la(self, 2);
614 if (isRfc822_LWSP(laC2)) { // LF CR LWSP
615 _consume(self, 3); // folding
617 else if (laC2 == '\n') { // LF CR LF
618 _consume(self, 3); //
619 foundEndOfHeaders = YES; // end of headers
620 endOfFieldBody = YES;
624 endOfFieldBody = YES; // next header field
629 endOfFieldBody = YES; // next header field
633 if ((bufCnt != 0) || (!isRfc822_LWSP(laC0))) {
634 /* ignore leading white spaces */
635 buf[bufCnt++] = laC0;
638 if (bufCnt >= self->bufLen) {
639 [fieldValue appendBytes:buf length:bufCnt];
645 [fieldValue appendBytes:buf length:bufCnt];
648 if (!endOfFieldBody) {
650 @"WARNING(%s:%i): 2 an error occured during body parsing "
651 @"(maybe end of stream)", __PRETTY_FUNCTION__, __LINE__];
652 foundEndOfHeaders = YES;
654 if (realFieldName != nil) {
655 BOOL keepHeader = YES;
657 if (HeaderNames == NULL)
658 [NGMimePartParser headerFieldNames];
660 if (realFieldName == HeaderNames->contentTransferEncoding) {
662 const unsigned char *cstr;
664 len = [fieldValue length];
665 cstr = [fieldValue bytes];
667 keepHeader = NO; // don't keep content-tranfer-encodings
669 while (isRfc822_LWSP(*cstr) && (len > 0)) { // strip leading spaces
673 if (len > 0) { // len==0 means the value was a string of LWSP
674 [self->contentTransferEncoding release];
675 self->contentTransferEncoding =
676 [[StringClass alloc] initWithCString:cstr length:len];
679 ASSIGN(self->contentTransferEncoding, (id)nil);
682 take a look on content-length headers, since the parser
683 needs to know this for reading in the body ..
685 if (keepHeader && self->useContentLength) {
686 if (realFieldName == HeaderNames->contentLength) {
688 const unsigned char *cstr;
690 len = [fieldValue length];
691 cstr = [fieldValue bytes];
693 while (isRfc822_LWSP(*cstr) && (len > 0)) { // strip leading spaces
697 if (len > 0) { // len==0 means the value was a string of LWSP
698 unsigned char buf[len + 1];
701 while (isdigit(*cstr) && (i < len)) { // extract following digits
705 buf[i] = '\0'; // stop string after last digit (ignore the rest)
706 self->contentLength = atoi(buf);
709 /* header value are only spaces */
710 self->contentLength = -1;
714 /* ask delegate if the header is to be kept */
716 if (self->delegateRespondsTo.parserKeepHeaderFieldData)
717 keepHeader = [self->delegate parser:self
718 keepHeaderField:realFieldName
724 value = [self valueOfHeaderField:realFieldName
728 value = [value retain];
729 /* ask delegate if the header is to be kept */
730 if (self->delegateRespondsTo.parserKeepHeaderFieldValue) {
731 keepHeader = [self->delegate parser:self
732 keepHeaderField:realFieldName
736 NSAssert(realFieldName, @"missing field name ..");
737 NSAssert(value, @"missing field value ..");
740 check whether content-length, content-type,
741 subject already in hashmap
743 if (_checkKey(self, header, realFieldName))
744 [header addObject:value forKey:realFieldName];
756 if (self->delegateRespondsTo.parserDidParseHeader)
757 [self->delegate parser:self didParseHeader:header];
759 header = [header retain];
762 return [header autorelease];
765 - (NSData *)readBodyUnknownLengthStream {
766 static NSMutableData *dataObject = nil;
767 NGIOReadMethodType readBytes = NULL;
772 char buf[self->bufLen];
773 void (*appendBytes)(id,SEL,const void *,unsigned);
776 *(&readBytes) = NULL;
778 if ([self->source respondsToSelector:@selector(methodForSelector:)]) {
779 readBytes = (NGIOReadMethodType)
780 [self->source methodForSelector:@selector(readBytes:count:)];
783 *(&appendBytes) = NULL;
787 /* check whether we can reuse the dataObj ... */
789 *(&body) = [dataObject autorelease];
790 dataObject = nil; /* mark as used ... */
793 *(&body) = [[[NSMutableData alloc] initWithCapacity:100010] autorelease];
797 appendBytes = (void(*)(id,SEL,const void *, unsigned))
798 [body methodForSelector:@selector(appendBytes:length:)];
805 _la(self, self->bufLen - 1);
808 if (![localException isKindOfClass:[NGEndOfStreamException class]])
809 [localException raise];
814 bufCnt = (readBytes != NULL)
815 ? readBytes(self->source, @selector(readBytes:count:),
817 : [self->source readBytes:buf count:self->bufLen];
819 if (bufCnt == NGStreamError) {
820 e = [self->source lastException];
822 if ([e isKindOfClass:[NGEndOfStreamException class]])
829 /* perform any on-the-fly encodings */
831 /* add to body data */
832 appendBytes(body, @selector(appendBytes:length:), buf, bufCnt);
837 if (![localException isKindOfClass:[NGEndOfStreamException class]])
838 [localException raise];
842 appendBytes(body, @selector(appendBytes:length:), buf, bufCnt);
847 ASSIGN(self->contentTransferEncoding, (id)nil);
851 /* remember that object for reuse ... */
852 if (dataObject == nil && [body length] < MAX_DATA_OBJECT_SIZE_CACHE) {
853 dataObject = [body retain];
854 [dataObject setLength:0];
857 return [rbody autorelease];
860 - (NSData *)readBodyUnknownLengthData {
861 return [self->sourceData subdataWithRange:
862 NSMakeRange(self->dataIdx, self->byteLen - self->dataIdx)];
865 - (NSData *)readBodyUnknownLength {
866 return (self->source)
867 ? [self readBodyUnknownLengthStream]
868 : [self readBodyUnknownLengthData];
871 - (NSData *)readBodyWithKnownLengthFromStream:(unsigned)_len {
872 NGIOReadMethodType readBytes = NULL;
874 unsigned char *buf = NULL;
877 *(&readBytes) = NULL;
879 if ([self->source respondsToSelector:@selector(methodForSelector:)]) {
880 readBytes = (NGIOReadMethodType)
881 [self->source methodForSelector:@selector(readBytes:count:)];
888 buf = calloc(_len, sizeof(char));
893 if (self->contentLength > self->bufLen)
894 _la(self, self->bufLen - 1);
896 _la(self, self->contentLength - 1);
899 if ([localException isKindOfClass:[NGEndOfStreamException class]]) {
901 "WARNING(%s): EOF occurred before whole content was read "
902 "(content-length=%i, read=%i)\n", __PRETTY_FUNCTION__,
903 self->contentLength, readB);
907 [localException raise];
913 while (self->contentLength != readB) {
914 int tmp = self->contentLength - readB;
916 readB += (readBytes != NULL)
917 ? readBytes(self->source, @selector(readBytes:count:),
919 : [self->source readBytes:(buf + readB) count:tmp];
921 if (readB == NGStreamError) {
922 [[self->source lastException] raise];
925 tmp = self->contentLength - readB;
927 if (tmp > self->bufLen)
928 _la(self, self->bufLen - 1);
935 if ([localException isKindOfClass:[NGEndOfStreamException class]]) {
937 "WARNING(%s): EOF occurred before whole content was read "
938 "(content-length=%i, read=%i)\n", __PRETTY_FUNCTION__,
939 self->contentLength, readB);
943 [localException raise];
948 rbody = buf ? [NSData dataWithBytes:buf length:readB] : nil;
953 - (NSData *)readBodyWithKnownLengthFromData:(unsigned)_len {
956 data = [self->sourceData subdataWithRange:
957 NSMakeRange(self->dataIdx, self->byteLen - self->dataIdx)];
958 if ([data length] != _len) {
959 NSLog(@"%s[%i]: got wrong data %d _len %d", __PRETTY_FUNCTION__, __LINE__,
960 [data length], _len);
966 - (NSData *)readBodyWithKnownLength:(unsigned)_len {
967 return (self->source)
968 ? [self readBodyWithKnownLengthFromStream:_len]
969 : [self readBodyWithKnownLengthFromData:_len];
972 - (NSData *)applyTransferEncoding:(NSString *)_encoding onData:(NSData *)_data{
973 // TODO: make this an NSData category
977 if ((len = [_encoding length]) == 0)
980 _encoding = [_encoding lowercaseString];
982 c = [_encoding characterAtIndex:0];
985 if ([_encoding hasPrefix:@"quoted"])
986 return [_data dataByDecodingQuotedPrintable];
989 if ([_encoding hasPrefix:@"base64"])
990 return [_data dataByDecodingBase64];
991 else if ([@"binary" isEqualToString:_encoding])
998 if ([@"7bit" isEqualToString:_encoding])
1000 if ([@"8bit" isEqualToString:_encoding])
1004 else if (len == 8) {
1005 if ([@"identity" isEqualToString:_encoding])
1011 if ([@"unknown-8bit" isEqualToString:_encoding])
1021 - (NSData *)readBody {
1022 /* Read data of body and apply content-transfer-encoding if required. */
1023 NSAutoreleasePool *pool;
1024 NSData *rbody = nil;
1026 pool = [[NSAutoreleasePool alloc] init];
1028 if ((self->contentLength == -1) || (self->contentLength == 0)) {
1029 rbody = [self readBodyUnknownLength];
1032 /* note: this is called only, if self->useContentLength is set ! */
1033 rbody = [self readBodyWithKnownLength:self->contentLength];
1036 if ([self->contentTransferEncoding length] > 0) {
1039 new = [self applyTransferEncoding:self->contentTransferEncoding
1042 ASSIGN(self->contentTransferEncoding, (id)nil);
1046 [self logWithFormat:@"WARNING(%s): "
1047 @"encountered unknown content-transfer-encoding: '%@'",
1048 __PRETTY_FUNCTION__,
1049 self->contentTransferEncoding];
1053 rbody = [rbody retain];
1055 return [rbody autorelease];
1058 - (NSData *)decodeBody:(NSData *)_data ofPart:(id<NGMimePart>)_part {
1059 return (self->delegateRespondsTo.parserDecodeBodyOfPart)
1060 ? [self->delegate parser:self decodeBody:_data ofPart:_part]
1064 - (NGMimeType *)defaultContentTypeForPart:(id<NGMimePart>)_part {
1065 static NGMimeType *octetType = nil;
1067 if (octetType == nil)
1068 octetType = [[NGMimeType mimeType:@"application/octet-stream"] retain];
1072 - (id<NGMimeBodyParser>)parserForBodyOfPart:(id<NGMimePart>)_p
1076 NGMimeType *contentType;
1077 id<NGMimeBodyParser> bodyParser = nil;
1079 ctype = [_p contentType];
1081 contentType = ([ctype isKindOfClass:[NGMimeType class]])
1083 : [NGMimeType mimeType:[ctype stringValue]];
1085 if (self->delegateRespondsTo.parserBodyParserForPart) {
1086 if ((bodyParser = [self->delegate parser:self bodyParserForPart:_p]))
1090 if (contentType == nil) {
1091 contentType = [self defaultContentTypeForPart:_p];
1095 if ([[contentType type] isEqualToString:@"multipart"]) {
1096 bodyParser = [[[NGMimeMultipartBodyParser alloc] init] autorelease];
1098 else if ([[contentType type] isEqualToString:@"text"] &&
1099 [[contentType subType] isEqualToString:@"plain"]) {
1100 bodyParser = [[[NGMimeTextBodyParser alloc] init] autorelease];
1106 - (void)parseBodyOfPart:(id<NGMimePart>)_part {
1107 NGMimeBodyParser *parser = nil;
1108 NSData *rawBody = nil;
1111 rawBody = [self readBody];
1113 /* apply content-encoding, transfer-encoding and similiar */
1114 rawBody = [self decodeBody:rawBody ofPart:_part];
1116 if (self->delegateRespondsTo.parserParseRawBodyDataOfPart) {
1120 [self->delegate parser:self parseRawBodyData:rawBody ofPart:_part];
1122 if (didParse) return;
1125 parser = (NGMimeBodyParser *)[self parserForBodyOfPart:_part data:rawBody];
1127 /* make sure delegate keeps being around .. */
1128 self->delegate = [[self->delegate retain] autorelease];
1130 body = [parser parseBodyOfPart:_part
1132 delegate:self->delegate];
1134 else if (rawBody) { /* no parser found for body */
1135 if (body == nil) body = rawBody;
1137 [_part setBody:body];
1142 - (id<NGMimePart>)producePartWithHeader:(NGHashMap *)_header {
1143 [self subclassResponsibility:_cmd];
1147 - (BOOL)prepareForParsingFromData:(NSData *)_data {
1151 ASSIGN(self->sourceData, _data);
1152 self->sourceBytes = [self->sourceData bytes];
1153 self->byteLen = [self->sourceData length];
1155 self->contentLength = -1;
1160 - (BOOL)prepareForParsingFromStream:(id<NGStream>)_stream {
1164 if (self->source != _stream) {
1167 bb = [NGByteBuffer alloc];
1168 bb = [bb initWithSource:_stream la:self->bufLen];
1169 [self->source release];
1172 if ([self->source respondsToSelector:@selector(methodForSelector:)]) {
1173 self->la = (int (*)(id, SEL, unsigned))
1174 [self->source methodForSelector:@selector(la:)];
1175 self->consume = (void (*)(id, SEL))
1176 [self->source methodForSelector:@selector(consume)];
1177 self->consumeCnt = (void (*)(id, SEL, unsigned))
1178 [self->source methodForSelector:@selector(consume:)];
1182 self->consume = NULL;
1183 self->consumeCnt = NULL;
1185 self->contentLength = -1;
1190 - (void)finishParsingOfPart:(id<NGMimePart>)_part {
1191 [self->source release]; self->source = nil;
1192 self->contentLength = -1;
1195 self->consume = NULL;
1196 self->consumeCnt = NULL;
1199 - (void)finishParsingOfPartFromData:(id<NGMimePart>)_part {
1200 [self->sourceData release]; self->sourceData = nil;
1201 self->sourceBytes = NULL;
1204 self->contentLength = -1;
1207 - (BOOL)parsePrefix {
1211 - (void)parseSuffix {
1214 - (id<NGMimePart>)parsePart {
1215 id<NGMimePart> part = nil;
1219 if (![self parsePrefix])
1222 if ((header = [self parseHeader]) == nil)
1225 part = [self producePartWithHeader:header];
1227 doParse = (delegateRespondsTo.parserWillParseBodyOfPart)
1228 ? [delegate parser:self willParseBodyOfPart:part]
1232 NSAutoreleasePool *pool;
1234 pool = [[NSAutoreleasePool alloc] init];
1235 [self parseBodyOfPart:part];
1238 if (delegateRespondsTo.parserDidParseBodyOfPart)
1239 [delegate parser:self didParseBodyOfPart:part];
1246 - (id<NGMimePart>)parsePartFromStream:(id<NGStream>)_stream {
1249 if (![self prepareForParsingFromStream:_stream])
1252 p = [self parsePart];
1253 [self finishParsingOfPart:p];
1257 - (id<NGMimePart>)parsePartFromData:(NSData *)_data {
1258 id<NGMimePart> part;
1260 if ([_data isKindOfClass:NSMutableDataClass]) {
1261 NGDataStream *dataStream;
1263 dataStream = [NGDataStream streamWithData:_data];
1264 part = [self parsePartFromStream:dataStream];
1269 if ([self prepareForParsingFromData:_data]) {
1270 part = [self parsePart];
1271 [self finishParsingOfPartFromData:part];
1280 - (BOOL)doesUseContentLength {
1281 return self->useContentLength;
1283 - (void)setUseContentLength:(BOOL)_use {
1284 self->useContentLength = _use;
1289 static inline int _la(NGMimePartParser *self, int _la) {
1291 return (self->la != NULL) ? self->la(self->source, @selector(la:), _la)
1292 : [self->source la:_la];
1295 if ((self->dataIdx+_la) < self->byteLen)
1296 return self->sourceBytes[self->dataIdx+_la];
1302 static inline void _consume(NGMimePartParser *self, int _cnt) {
1305 if (self->consume != NULL)
1306 self->consume(self->source, @selector(consume));
1308 [self->source consume];
1311 if (self->consumeCnt != NULL)
1312 self->consumeCnt(self->source, @selector(consume:), _cnt);
1314 [self->source consume:_cnt];
1318 if ((self->dataIdx+_cnt) <= self->byteLen) {
1319 self->dataIdx += _cnt;
1322 NSLog(@"%s[%i]: error try to read over buffer len self->dataIdx %d "
1323 @"_cnt %d byteLen %d", __PRETTY_FUNCTION__, __LINE__,
1324 self->dataIdx, _cnt, self->byteLen);
1329 static inline BOOL _checkKey(NGMimePartParser *self, NGHashMap *_map,
1332 if (HeaderNames == NULL)
1333 [NGMimePartParser headerFieldNames];
1335 if ((_key == HeaderNames->contentLength) ||
1336 _key == HeaderNames->contentType) {
1337 if ([_map countObjectsForKey:_key] > 0)
1343 @end /* NGMimePartParser */