/* Copyright (C) 2000-2005 SKYRIX Software AG This file is part of SOPE. SOPE 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. SOPE 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 SOPE; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "NGMimeMessageParser.h" #include "NGMimeMessage.h" #include "common.h" @interface NGMimeMessageParserDelegate : NSObject @end @implementation NGMimeMessageParserDelegate static int UseFoundationStringEncodingForMimeHeader = -1; static Class NGMimeMessageParserClass = NULL; + (void)initialize { if (UseFoundationStringEncodingForMimeHeader == -1) { UseFoundationStringEncodingForMimeHeader = [[NSUserDefaults standardUserDefaults] boolForKey:@"UseFoundationStringEncodingForMimeHeader"] ? 1 : 0; } if (NGMimeMessageParserClass == NULL) { NGMimeMessageParserClass = [NGMimeMessageParser class]; } } - (id)parser:(NGMimePartParser *)_p parseHeaderField:(NSString *)_field data:(NSData *)_data { NGMimeMessageParser *parser = nil; id v; if ([_p isKindOfClass:NGMimeMessageParserClass]) return nil; parser = [[NGMimeMessageParserClass alloc] init]; v = [parser valueOfHeaderField:_field data:_data]; [parser release]; parser = nil; return v; } - (id)parser:(NGMimePartParser *)_parser bodyParserForPart:(id)_part { id ctype; NGMimeType *contentType; ctype = [_part contentType]; contentType = ([ctype isKindOfClass:[NGMimeType class]]) ? ctype : [NGMimeType mimeType:[ctype stringValue]]; if ([[contentType type] isEqualToString:@"message"] && [[contentType subType] isEqualToString:@"rfc822"]) { return [[[NGMimeRfc822BodyParser alloc] init] autorelease]; } return nil; } @end /* NGMimeMessageParserDelegate */ @implementation NGMimeMessageParser static Class NSStringClass = Nil; + (int)version { return 3; } + (void)initialize { NSAssert2([super version] == 3, @"invalid superclass (%@) version %i !", NSStringFromClass([self superclass]), [super version]); if (NSStringClass == Nil) NSStringClass = [NSString class]; } - (id)init { if ((self = [super init])) { [self setDelegate:[NGMimeMessageParserDelegate new]]; } return self; } /* factory */ - (id)producePartWithHeader:(NGHashMap *)_header { return [NGMimeMessage messageWithHeader:_header]; } /* header field specifics */ - (id)valueOfHeaderField:(NSString *)_name data:(id)_data { // check data for 8-bit headerfields (RFC 2047 (MIME PART III)) /* check whether we got passed a string ... */ if ([_data isKindOfClass:NSStringClass]) { NSLog(@"%s: WARNING unexpected class for headerfield %@ (value %@)", __PRETTY_FUNCTION__, _name, _data); return [super valueOfHeaderField:_name data:_data]; } _data = [_data decodeQuotedPrintableValueOfMIMEHeaderField:_name]; return [super valueOfHeaderField:_name data:_data]; } @end /* NGMimeMessageParser */ @implementation NSData(MimeQPHeaderFieldDecoding) - (id)decodeQuotedPrintableValueOfMIMEHeaderField:(NSString *)_name { // check data for 8-bit headerfields (RFC 2047 (MIME PART III)) static Class NGMimeTypeClass = Nil; enum { NGMimeMessageParser_quoted_start = 1, NGMimeMessageParser_quoted_charSet = 2, NGMimeMessageParser_quoted_qpData = 3, NGMimeMessageParser_quoted_end = 4 } status = NGMimeMessageParser_quoted_start; unsigned int length; const unsigned char *bytes, *firstEq; BOOL foundQP = NO; if (NSStringClass == Nil) NSStringClass = [NSString class]; if (NGMimeTypeClass == Nil) NGMimeTypeClass = [NGMimeType class]; length = [self length]; /* check whether the string is long enough to be quoted etc */ if (length <= 6) return self; /* check whether the string contains QP tokens ... */ bytes = [self bytes]; if ((firstEq = memchr(bytes, '=', length)) == NULL) return self; /* process data ... (quoting etc) */ { unichar *buffer; unsigned int bufLen, maxBufLen; NSString *charset; BOOL appendLC; int cnt, tmp; unsigned char encoding; buffer = calloc(length + 13, sizeof(unichar)); maxBufLen = length + 3; buffer[maxBufLen - 1] = '\0'; bufLen = 0; encoding = 0; tmp = -1; appendLC = YES; charset = nil; status = NGMimeMessageParser_quoted_start; /* copy data up to first '=' sign */ if ((cnt = (firstEq - bytes)) > 0) { for (; bufLen < cnt; bufLen++) buffer[bufLen] = bytes[bufLen]; } for (; cnt < (length-1); cnt++) { appendLC = YES; if (status == NGMimeMessageParser_quoted_start) { if ((bytes[cnt] == '=') && (bytes[cnt + 1] == '?')) { // found begin cnt++; status = NGMimeMessageParser_quoted_charSet; } else { // other char if (bytes[cnt + 1] != '=') { buffer[bufLen++] = bytes[cnt]; buffer[bufLen++] = bytes[cnt+1]; cnt++; if (cnt >= length - 1) appendLC = NO; } else { buffer[bufLen++] = bytes[cnt]; } } } else if (status == NGMimeMessageParser_quoted_charSet) { if (tmp == -1) tmp = cnt; if (bytes[cnt] == '?') { charset = [NSStringClass stringWithCString:(char *)(bytes + tmp) length:(cnt - tmp)]; tmp = -1; if ((length - cnt) > 2) { // set encoding (eg 'q' for quoted printable) cnt++; // skip '?' encoding = bytes[cnt]; cnt++; // skip encoding status = NGMimeMessageParser_quoted_qpData; } else { // unexpected end NSLog(@"WARNING: unexpected end of header"); appendLC = NO; break; } } } else if (status == NGMimeMessageParser_quoted_qpData) { if (tmp == -1) tmp = cnt; if ((bytes[cnt] == '?') && (bytes[cnt + 1] == '=')) { NSData *tmpData; NSString *tmpStr; unsigned int tmpLen; tmpData = _rfc2047Decoding(encoding, (char *)bytes + tmp, cnt - tmp); foundQP = YES; /* create a temporary string for charset conversion ... Note: the headerfield is currently held in ISO Latin 1 */ tmpStr = nil; if (!UseFoundationStringEncodingForMimeHeader) { tmpStr = [NSStringClass stringWithData:tmpData usingEncodingNamed:charset]; } if (tmpStr == nil) { NSStringEncoding enc; enc = [NGMimeTypeClass stringEncodingForCharset:charset]; tmpStr = [[[NSStringClass alloc] initWithData:tmpData encoding:enc] autorelease]; } tmpLen = [tmpStr length]; if ((tmpLen + bufLen) < maxBufLen) { [tmpStr getCharacters:(buffer + bufLen)]; bufLen += tmpLen; } else { NSLog(@"ERROR[%s]: quoted data to large --> ignored %@", __PRETTY_FUNCTION__, tmpStr); } tmp = -1; cnt++; appendLC = YES; status = NGMimeMessageParser_quoted_start; } } } if (appendLC) { if (cnt < length) { buffer[bufLen] = bytes[cnt]; bufLen++; } } buffer[bufLen] = '\0'; { id data; data = nil; if (buffer && foundQP) { data = [[[NSString alloc] initWithCharacters:buffer length:bufLen] autorelease]; if (data == nil) { NSLog(@"%s: got no string for buffer '%s', length '%i' !", __PRETTY_FUNCTION__, buffer, bufLen); } } if (!data) { data = self; } free(buffer); buffer = NULL; return data; } } return self; } @end /* NSData(MimeQPHeaderFieldDecoding) */ @implementation NGMimeRfc822BodyParser + (int)version { return 2; } + (void)initialize { NSAssert2([super version] == 2, @"invalid superclass (%@) version %i !", NSStringFromClass([self superclass]), [super version]); } - (id)parseBodyOfPart:(id)_part data:(NSData *)_data delegate:(id)_d { id body; id parser; // NGMimeMessageParser parser = [[NGMimeMessageParser alloc] init]; body = [parser parsePartFromData:_data]; [parser release]; parser = nil; return body; } @end /* NGMimeRfc822BodyParser */