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