]> err.no Git - sope/blob - sope-mime/NGMail/NGMimeMessageParser.m
synced with latest additions and bumped framework versions
[sope] / sope-mime / NGMail / NGMimeMessageParser.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "NGMimeMessageParser.h"
23 #include "NGMimeMessage.h"
24 #include "common.h"
25
26
27
28 @interface NGMimeMessageParserDelegate : NSObject
29 @end
30
31 @implementation NGMimeMessageParserDelegate
32
33 static int   UseFoundationStringEncodingForMimeHeader = -1;
34 static Class NGMimeMessageParserClass                 = NULL; 
35
36 + (void)initialize {
37   if (UseFoundationStringEncodingForMimeHeader == -1) {
38     UseFoundationStringEncodingForMimeHeader
39       = [[NSUserDefaults standardUserDefaults]
40           boolForKey:@"UseFoundationStringEncodingForMimeHeader"]
41       ? 1 : 0;
42   }
43   if (NGMimeMessageParserClass == NULL) {
44     NGMimeMessageParserClass = [NGMimeMessageParser class];
45   }
46 }
47
48 - (id)parser:(NGMimePartParser *)_p parseHeaderField:(NSString *)_field
49   data:(NSData *)_data
50 {
51   NGMimeMessageParser *parser = nil;
52   id v;
53
54   if ([_p isKindOfClass:NGMimeMessageParserClass])
55     return nil;
56   
57   parser = [[NGMimeMessageParserClass alloc] init];
58   v = [parser valueOfHeaderField:_field data:_data];
59   [parser release]; parser = nil;
60   return v;
61 }
62
63 - (id<NGMimeBodyParser>)parser:(NGMimePartParser *)_parser
64   bodyParserForPart:(id<NGMimePart>)_part
65 {
66   id         ctype;
67   NGMimeType *contentType;
68
69   ctype = [_part contentType];
70   
71   contentType = ([ctype isKindOfClass:[NGMimeType class]])
72     ? ctype
73     : [NGMimeType mimeType:[ctype stringValue]];
74   
75   if ([[contentType type] isEqualToString:@"message"] &&
76       [[contentType subType] isEqualToString:@"rfc822"]) {
77     return [[[NGMimeRfc822BodyParser alloc] init] autorelease];
78   }
79   return nil;
80 }
81
82
83 @end /* NGMimeMessageParserDelegate */
84
85 @implementation NGMimeMessageParser
86
87 static Class NSStringClass = Nil;
88
89 + (int)version {
90   return 3;
91 }
92 + (void)initialize {
93   NSAssert2([super version] == 3,
94             @"invalid superclass (%@) version %i !",
95             NSStringFromClass([self superclass]), [super version]);
96   if (NSStringClass == Nil)
97     NSStringClass = [NSString class];
98 }
99
100 - (id)init {
101   if ((self = [super init])) {
102     [self setDelegate:[NGMimeMessageParserDelegate new]];
103   }
104   return self;
105 }
106
107 /* factory */
108
109 - (id<NGMimePart>)producePartWithHeader:(NGHashMap *)_header {
110   return [NGMimeMessage messageWithHeader:_header];
111 }
112
113 /* header field specifics */
114
115 - (id)valueOfHeaderField:(NSString *)_name data:(id)_data {
116   // check data for 8-bit headerfields (RFC 2047 (MIME PART III))
117   
118   /* check whether we got passed a string ... */
119   if ([_data isKindOfClass:NSStringClass]) {
120     NSLog(@"%s: WARNING unexpected class for headerfield %@ (value %@)",
121           __PRETTY_FUNCTION__, _name, _data);
122     return [super valueOfHeaderField:_name data:_data];
123   }
124   _data = [_data decodeQuotedPrintableValueOfMIMEHeaderField:_name];
125   return [super valueOfHeaderField:_name data:_data];
126 }
127
128 @end /* NGMimeMessageParser */
129
130 @implementation NSData(MimeQPHeaderFieldDecoding)
131
132 - (id)decodeQuotedPrintableValueOfMIMEHeaderField:(NSString *)_name {
133   // check data for 8-bit headerfields (RFC 2047 (MIME PART III))
134   static Class NGMimeTypeClass = Nil;
135   enum {
136     NGMimeMessageParser_quoted_start   = 1,
137     NGMimeMessageParser_quoted_charSet = 2,
138     NGMimeMessageParser_quoted_qpData  = 3,
139     NGMimeMessageParser_quoted_end     = 4
140   } status = NGMimeMessageParser_quoted_start;
141   unsigned int        length;
142   const unsigned char *bytes, *firstEq;
143   BOOL foundQP = NO;
144
145   if (NSStringClass   == Nil) NSStringClass   = [NSString class];
146   if (NGMimeTypeClass == Nil) NGMimeTypeClass = [NGMimeType class];
147   
148   length = [self length];
149   
150   /* check whether the string is long enough to be quoted etc */
151   if (length <= 6)
152     return self;
153   
154   /* check whether the string contains QP tokens ... */
155   bytes = [self bytes];
156   
157   if ((firstEq = memchr(bytes, '=', length)) == NULL)
158     return self;
159   
160   /* process data ... (quoting etc) */
161   {
162     unichar       *buffer;
163     unsigned int  bufLen, maxBufLen;
164     NSString      *charset;
165     BOOL          appendLC;
166     int           cnt, tmp;
167     unsigned char encoding;
168     
169     buffer = calloc(length + 13, sizeof(unichar));
170     
171     maxBufLen             = length + 3;
172     buffer[maxBufLen - 1] = '\0';
173     bufLen                = 0;
174     
175     encoding = 0;
176     tmp      = -1;
177     appendLC = YES;      
178     charset  = nil;
179     status   = NGMimeMessageParser_quoted_start;
180
181     /* copy data up to first '=' sign */
182     if ((cnt = (firstEq - bytes)) > 0) {
183       for (; bufLen < cnt; bufLen++) 
184         buffer[bufLen] = bytes[bufLen];
185     }
186     
187     for (; cnt < (length-1); cnt++) {
188       appendLC = YES;      
189       
190       if (status == NGMimeMessageParser_quoted_start) {
191         if ((bytes[cnt] == '=') && (bytes[cnt + 1] == '?')) { // found begin
192           cnt++;
193           status = NGMimeMessageParser_quoted_charSet;
194         }
195         else { // other char
196           if (bytes[cnt + 1] != '=') {
197             buffer[bufLen++] = bytes[cnt];
198             buffer[bufLen++] = bytes[cnt+1];
199             cnt++;
200             if (cnt >= length - 1)
201               appendLC = NO;
202           }
203           else {
204             buffer[bufLen++] = bytes[cnt];
205           }
206         }
207       }
208       else if (status == NGMimeMessageParser_quoted_charSet) {
209         if (tmp == -1)
210           tmp = cnt;
211         
212         if (bytes[cnt] == '?') {
213           charset = 
214             [NSStringClass stringWithCString:(char *)(bytes + tmp) 
215                            length:(cnt - tmp)];
216           tmp = -1;
217           
218           if ((length - cnt) > 2) { 
219             // set encoding (eg 'q' for quoted printable)
220             cnt++; // skip '?'
221             encoding = bytes[cnt];
222             cnt++; // skip encoding
223             status = NGMimeMessageParser_quoted_qpData;
224           }
225           else { // unexpected end
226             NSLog(@"WARNING: unexpected end of header");
227             appendLC = NO;
228             break;
229           }
230         }
231       }
232       else if (status == NGMimeMessageParser_quoted_qpData) {
233         if (tmp == -1)
234           tmp = cnt;
235         
236         if ((bytes[cnt] == '?') && (bytes[cnt + 1] == '=')) {
237           NSData           *tmpData;
238           NSString         *tmpStr;
239           unsigned int     tmpLen;
240           
241           tmpData = _rfc2047Decoding(encoding, (char *)bytes + tmp, cnt - tmp);
242           foundQP = YES;
243
244           /* 
245              create a temporary string for charset conversion ... 
246              Note: the headerfield is currently held in ISO Latin 1
247           */
248           tmpStr = nil;
249           
250           if (!UseFoundationStringEncodingForMimeHeader) {
251             tmpStr = [NSStringClass stringWithData:tmpData
252                                     usingEncodingNamed:charset];
253           }
254           if (tmpStr == nil) {
255             NSStringEncoding enc;
256             
257             enc    = [NGMimeTypeClass stringEncodingForCharset:charset];
258             tmpStr = [[[NSStringClass alloc] initWithData:tmpData encoding:enc]
259                                       autorelease];
260           }
261           tmpLen = [tmpStr length];
262           
263           if ((tmpLen + bufLen) < maxBufLen) {
264             [tmpStr getCharacters:(buffer + bufLen)];
265             bufLen += tmpLen;
266           }
267           else {
268             NSLog(@"ERROR[%s]: quoted data to large --> ignored %@",
269                   __PRETTY_FUNCTION__, tmpStr);
270           }
271           tmp = -1;
272           cnt++;
273           appendLC = YES;
274           status   = NGMimeMessageParser_quoted_start;
275         }
276       }
277     }
278     if (appendLC) {
279       if (cnt < length) {
280         buffer[bufLen] = bytes[cnt];
281         bufLen++;
282       }
283     }
284     buffer[bufLen] = '\0';
285     {
286       id data;
287
288       data = nil;
289       
290       if (buffer && foundQP) {
291         data = [[[NSString alloc] initWithCharacters:buffer length:bufLen]
292                            autorelease];
293         if (data == nil) {
294           NSLog(@"%s: got no string for buffer '%s', length '%i' !", 
295                 __PRETTY_FUNCTION__,
296                 buffer, bufLen);
297         }
298       }
299       if (!data) {
300         data = self;
301       }
302       free(buffer); buffer = NULL;
303       return data;
304     }
305   }
306   return self;
307 }
308
309 @end /* NSData(MimeQPHeaderFieldDecoding) */
310
311 @implementation NGMimeRfc822BodyParser
312
313 + (int)version {
314   return 2;
315 }
316 + (void)initialize {
317   NSAssert2([super version] == 2,
318             @"invalid superclass (%@) version %i !",
319             NSStringFromClass([self superclass]), [super version]);
320 }
321
322 - (id)parseBodyOfPart:(id<NGMimePart>)_part data:(NSData *)_data
323   delegate:(id)_d
324 {
325   id<NGMimePart> body;
326   id             parser; // NGMimeMessageParser
327
328   parser = [[NGMimeMessageParser alloc] init];
329   body = [parser parsePartFromData:_data];
330   [parser release]; parser = nil;
331   
332   return body;
333 }
334
335 @end /* NGMimeRfc822BodyParser */