]> err.no Git - sope/blob - sope-mime/NGMime/NGMimeMultipartBodyParser.m
bumped Xcode dyld versions
[sope] / sope-mime / NGMime / NGMimeMultipartBodyParser.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 "NGMimeBodyParser.h"
23 #include "NGMimeBodyPartParser.h"
24 #include "NGMimeMultipartBody.h"
25 #include "common.h"
26
27 @implementation NGMimeMultipartBodyParser
28
29 static int MimeLogEnabled = -1;
30
31 + (int)version {
32   return [super version] + 0 /* v2 */;
33 }
34 + (void)initialize {
35   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
36   
37   NSAssert2([super version] == 2,
38             @"invalid superclass (%@) version %i !",
39             NSStringFromClass([self superclass]), [super version]);
40   MimeLogEnabled = [ud boolForKey:@"MimeLogEnabled"] ? 1 : 0;
41 }
42
43 // returns the postion of the '\r' that starts the boundary
44 static inline const char
45 *_findNextBoundary(const char *_from, unsigned _len,
46                    const char *_boundary, unsigned _boundaryLen, BOOL _isFirst)
47 {
48   register unsigned pos  = 0;
49   register unsigned blen = _boundaryLen;
50
51   if (_isFirst) {
52     blen += 3; // -- + at least one EOL char at end of line
53     if (_len < blen) return NULL;  // too short to contain boundary
54     
55     for (pos = 0; (pos < _len) && (_len - pos > blen); pos++) {
56       if ((_from[pos] == '-') && (_from[pos + 1] == '-')) {
57         if (strncmp(&(_from[pos + 2]), _boundary, _boundaryLen) == 0) {
58           // found boundary;
59           return _from + pos;
60         }
61       }
62     }
63   }
64   else {
65     blen += 4; // -- + at least two EOL chars at start and end of line
66     if (_len < blen) return NULL; // too short to contain boundary
67
68     /* detect:
69          --boundary(--)?CR
70          CR--boundary(--)?CR
71          CRLF--boundary(--)?CRLF
72          LF--boundary(--)?LF
73     */
74     
75     for (pos = 0; (pos < _len) && ((_len - pos) > blen); pos++) {
76       if (_from[pos] == '\n') { // check for LF--
77         if ((_from[pos + 1] == '-') &&(_from[pos + 2] == '-')) {
78           // found LF--
79           if (strncmp(&(_from[pos + 3]), _boundary, _boundaryLen) == 0)
80             // found LF--boundary
81             return (_from + pos);
82         }
83       }
84       else if (_from[pos] == '\r') { // check for CR.-?
85         if ((_from[pos + 1] == '-') && (_from[pos + 2] == '-')) { // CHECK FOR CR--
86           // found CR--
87           if (strncmp(&(_from[pos + 3]), _boundary, _boundaryLen) == 0)
88             // found LF--boundary
89             return (_from + pos);
90         }
91         if ((_from[pos + 1] == '\n') && (_from[pos + 2] == '-')
92             && (_from[pos + 3] == '-')) {
93           // found CRLF--
94           if ((_len - pos) <= blen) {
95             // remaining part is too short for boundary starting with CRLF
96             break;
97           }
98           
99           if (strncmp(&(_from[pos + 4]), _boundary, _boundaryLen) == 0)
100             // found LF--boundary
101             return (_from + pos);
102         }
103       }
104       else if ((_from[pos] == '-') && (_from[pos + 1] == '-')) {
105         if (strncmp(&(_from[pos + 2]), _boundary, _boundaryLen) == 0) {
106             // found --boundary
107           return (_from + pos);
108         }
109       }
110     }
111   }
112   return NULL;
113 }
114
115 static inline BOOL
116 _isEndBoundary(const char *_from, unsigned _len,
117                const char *_boundary, unsigned _boundaryLen)
118 {
119   // no buffer out-of-bounds check, may cause segfault
120   
121   if (_len < (_boundaryLen + 8)) // + 2x CRLF and 2x '--'
122     return YES;
123
124   while (*_from != '-') _from++; // search first '-'
125   _from += 2; // skip '--'
126   
127   _from += _boundaryLen; // skip boundary;
128   return ((_from[0] == '-') && (_from[1] == '-')) ? YES : NO;
129 }
130
131 static inline const char *
132 _skipBoundary(id self, const char *_from, unsigned _len, BOOL _first)
133 {
134   register unsigned pos = 0;
135   register unsigned char c = 0;
136
137   if (_from == NULL) return NULL;
138
139   if (_from[0] == '-') { // skip '--'
140     c = 0; // EOL needs to be detected
141   }
142   else if (_from[1] == '-') { // skip CR-- or LF--
143     c = _from[0];
144     if ((c != '\n') && (c != '\r')) {
145       if (MimeLogEnabled) 
146         [self logWithFormat:@"WARNING(%s): invalid char before boundary '--'",
147               __PRETTY_FUNCTION__];
148     }
149     pos = 3;
150   }
151   else if (_from[2] == '-') { // skip CRLF--
152     c = _from[0];
153     if (c != '\r') {
154       if (MimeLogEnabled)
155         [self logWithFormat:@"WARNING(%s): missing CR before boundary 'LF--'",
156               __PRETTY_FUNCTION__];
157     }
158     c = _from[1];
159     if (c != '\n') {
160       if (MimeLogEnabled)
161         [self logWithFormat:@"WARNING(%s): missing LF before boundary '--' (after"
162               @"CR)", __PRETTY_FUNCTION__];
163     }
164     pos = 4;
165   }
166   else {
167     if (MimeLogEnabled)
168       [self logWithFormat:@"ERROR(%s): invalid parser state, skipping 4.",
169             __PRETTY_FUNCTION__];
170     pos = 4;
171   }
172
173   while (pos < _len) {
174     register unsigned char fc = _from[pos];
175     
176     if (c == 0) { // EOL detect (on first line)
177       if (fc == '\n') // LF
178         break;
179
180       if (fc == '\r') { // CR *
181         if ((pos + 1) == _len) // CR EOF
182           break;
183         else if (_from[pos + 1] == '\n') { // CRLF
184           pos++; // skip LF
185           break;
186         }
187         else // CR
188           break;
189       }
190     }
191     else if (fc == c) // EOL char is known
192       break;
193     
194     pos++;
195   }
196
197   if (pos < _len)
198     pos++; // skip EOL char
199   
200   return &(_from[pos]); // return pointer to position after char
201 }
202
203 - (NSArray *)_parseBody:(NGMimeMultipartBody *)_body
204   part:(id<NGMimePart>)_part data:(NSData *)_data
205   boundary:(const char *)_boundary length:(unsigned)_boundaryLen
206   delegate:(id)_delegate
207 {
208   NSMutableArray    *result;
209   NSAutoreleasePool *pool;
210   const char *begin   = NULL;
211   const char *end     = NULL;
212   const char *buffer  = [_data bytes];
213   unsigned   len      = [_data length];
214   BOOL       isEOF    = NO;    
215
216   NSCAssert(buffer,                @"got no buffer");
217   NSCAssert(_boundary,             @"got no boundary");
218
219   result = [NSMutableArray arrayWithCapacity:7];
220   
221   // find first boundary and store prefix
222   
223   begin = _findNextBoundary(buffer, len, _boundary, _boundaryLen, YES);
224   
225   if (begin == NULL) {
226     if (MimeLogEnabled)
227       [self logWithFormat:@"WARNING(%s): Found multipart with no 1st boundary",
228             __PRETTY_FUNCTION__];
229     [result addObject:_data];
230     return result;
231   }
232
233   pool = [[NSAutoreleasePool alloc] init];
234   
235   {
236     unsigned preLen = begin - buffer;
237     
238     if (preLen > 0) {
239       if ([_delegate respondsToSelector:
240                      @selector(multipartBodyParser:foundPrefix:inMultipart:)]) {
241         [_delegate multipartBodyParser:self
242                    foundPrefix:[NSData dataWithBytes:buffer length:preLen]
243                    inMultipart:_part];
244       }
245
246       [_body setPrefix:[NSString stringWithCString:buffer length:preLen]];
247     }
248   }
249
250   // skip first boundary
251   
252   begin = _skipBoundary(self, begin, len - (begin - buffer), YES);
253   NSCAssert(begin, @"could not skip 1st boundary ..");
254
255   // loop over multipart bodies and exit if end-boundary is found
256
257   do {
258
259     /* check for boundary denoting end of current part */
260     
261     end = _findNextBoundary(begin, len - (begin - buffer),
262                             _boundary,_boundaryLen, NO);
263     if (end == NULL) {
264       NSRange subDataRange;
265       NSData  *rawData = nil;
266
267       if (MimeLogEnabled)
268         [self logWithFormat:@"WARNING(%s): reached end of body without"
269               @" end-boundary", __PRETTY_FUNCTION__];
270       
271       subDataRange.location = (begin - buffer);
272       subDataRange.length   = ([_data length] + buffer - begin);
273       rawData = [_data subdataWithRange:subDataRange];
274       if (rawData)
275         [result addObject:rawData];
276       isEOF = YES;      
277       break;
278     }
279     else {
280       NSRange subDataRange;
281       NSData *rawData = nil;
282       
283       NSCAssert(end - begin >= 0, @"invalid range ..");
284
285       subDataRange.location = (begin - buffer);
286       subDataRange.length   = (end - begin);
287
288       rawData = [_data subdataWithRange:subDataRange];
289
290       if (rawData) {
291         [result addObject:rawData];
292       }
293       else {
294         NSLog(@"WARNING(%s): could not create rawdata for "
295               @" bodypart in multipart %@", __PRETTY_FUNCTION__,
296               _part);
297       }
298
299       /* check whether last read boundary was an end boundary */
300
301       if (_isEndBoundary(end, len - (end - buffer),
302                          _boundary, _boundaryLen)) {
303         isEOF = NO;
304         break;        
305       }
306       
307       /* skip non-end boundary */
308     
309       begin = _skipBoundary(self, end, len - (end - buffer), NO);
310     }
311   }
312   while (begin);
313
314   // skip end boundary and store suffix
315   if (!isEOF) {
316     if ((begin = _skipBoundary(self, end, len - (end - buffer), NO))) {   
317       unsigned sufLen;
318
319       sufLen = len - (begin - buffer);
320       
321       if (sufLen > 0) {
322         if ([_delegate respondsToSelector: @selector(multipartBodyParser:
323                                                      foundSuffix:
324                                                      inMultipart:)])
325             [_delegate multipartBodyParser:self
326                        foundSuffix:[NSData dataWithBytes:begin length:sufLen]
327                        inMultipart:_part];
328         
329         [_body setSuffix:[NSString stringWithCString:begin length:sufLen]];
330       }
331     }
332   }
333   
334   /* result is not contained in this pool, so no need to retain ... */
335   RELEASE(pool);
336
337   return result;
338 }
339
340 static NSString *_searchBoundary(NGMimeMultipartBodyParser *self,
341                                  NSData *_data) {
342   const char *buffer = [_data bytes];
343   int        length  = [_data length];  
344   int        pos     = 0;
345   BOOL       found   = NO;
346
347   if (length < 3)
348     return nil;
349
350   if ((buffer[0] == '-') && (buffer[1] == '-')) {   // no prefix
351     found = YES;
352     pos = 2;
353   }
354   else {
355     while (pos + 5 < length) {
356       if (buffer[pos + 2] != '-') {
357         // if third char is not a '-' it cannot be a boundary start
358         pos++;
359         continue;
360       }
361       
362       if (buffer[pos] == '\n') { // check for LF--
363         if (buffer[pos + 1] == '-') {
364           // found LF--
365           pos  += 3;
366           found = YES;
367           break;
368         }
369       }
370       else if (buffer[pos] == '\r') { // check for CR.-?
371         if ((buffer[pos + 1] == '-') ) { // CHECK FOR CR--
372           // found CR--
373           pos  += 3;
374           found = YES;
375           break;
376         }
377         if ((buffer[pos + 1] == '\n') && (buffer[pos + 3] == '-')) {
378           // found CRLF--
379           if ((length - pos) <= 4) {
380             // remaining part is too short for boundary starting with CRLF
381             break;
382           }
383           // found LF--boundary
384           pos  += 4;
385           found = YES;
386           break;
387         }
388       }
389       pos++;
390     }
391   }
392   if (found) {
393     int boundLength = 0;
394     
395     buffer += pos;
396     
397     while (((boundLength + pos) < length) &&
398            (buffer[boundLength] != '\n')  &&
399            (buffer[boundLength] != '\r')) {
400       boundLength++;
401     }
402     if ((boundLength + pos) < length) {
403       return [NSString stringWithCString:buffer length:boundLength]; 
404     }
405     else 
406       return nil;
407   }
408   else 
409     return nil;
410 }
411
412 - (id<NGMimePart>)parseBodyPartWithData:(NSData *)_rawData
413   inMultipart:(id<NGMimePart>)_multipart
414   parser:(NGMimePartParser *)_parser
415 {
416   if (![_rawData length])
417     return nil;
418
419   return [_parser parsePartFromData:_rawData];
420 }
421
422 - (BOOL)parseBody:(NGMimeMultipartBody *)_body
423   ofMultipart:(id<NGMimePart>)_part
424   data:(NSData *)_data delegate:(id)_d
425 {
426   NGMimeType *contentType  = nil;
427   NSString   *boundary     = nil;
428   NSArray    *rawBodyParts = nil;
429   BOOL       foundError    = NO;
430
431   contentType = [_part contentType];
432   boundary    = [contentType valueOfParameter:@"boundary"];
433   
434   if (boundary == nil)
435     boundary = _searchBoundary(self, _data);
436   
437   *(&foundError) = NO;
438   
439   *(&rawBodyParts) = [self _parseBody:_body part:_part data:_data
440                            boundary:[boundary cString]
441                            length:[boundary cStringLength]
442                            delegate:_d];
443
444   if (rawBodyParts) {
445     NGMimeBodyPartParser *bodyPartParser;
446     unsigned i, count;
447     BOOL     askDelegate = NO;
448
449     *(&count)          = [rawBodyParts count];    
450     *(&i)              = 0;
451     *(&bodyPartParser) = nil;
452
453     if ([_d respondsToSelector:
454               @selector(multipartBodyParser:parserForEntity:inMultipart:)]) {
455       *(&askDelegate) = YES;
456     }
457
458     for (i = 0; i < count; i++) {
459       NSString     *reason  =
460         @"ERROR: could not parse body part at index %i in multipart %@: %@";
461       NGMimePartParser *parser;
462       id               rawData;
463       id               bodyPart;
464
465       rawData      = [rawBodyParts objectAtIndex:i];      
466       *(&parser)   = bodyPartParser;
467       *(&bodyPart) = nil;
468
469       if (askDelegate) {
470         parser = [_d multipartBodyParser:self
471                      parserForEntity:rawData
472                      inMultipart:_part];
473         [parser setDelegate:_d];
474       }
475       else if (bodyPartParser == nil) {
476         bodyPartParser = [[NGMimeBodyPartParser alloc] init];
477         [bodyPartParser setDelegate:_d];
478         parser = bodyPartParser;
479       }
480
481       if (parser == nil) {
482         if (rawData) {
483           NSData *d;
484           
485           /* ensure that we have a copy, not a range on the full data */
486           d = [[NSData alloc] initWithBytes:[rawData bytes] 
487                               length:[rawData length]];
488           [_body addBodyPart:d];
489           RELEASE(d);
490         }
491       }
492       else {
493         NS_DURING {
494           if ([rawData length])
495             bodyPart = [self parseBodyPartWithData:rawData
496                              inMultipart:_part
497                              parser:parser];
498         }
499         NS_HANDLER {
500           NSLog(reason, i, _part, localException);
501           foundError = YES;
502         }
503         NS_ENDHANDLER;
504         
505         if (bodyPart) {
506           [_body addBodyPart:bodyPart];
507         }
508
509         parser = nil;
510       }
511     }
512     
513     RELEASE(bodyPartParser); bodyPartParser = nil;
514   }
515   return foundError;
516 }
517
518 - (BOOL)parseImmediatlyWithDelegate:(id)_delegate
519   multipart:(id<NGMimePart>)_part data:(NSData *)_data
520 {
521   if ([_delegate respondsToSelector:
522           @selector(multipartBodyParser:immediatlyParseBodyOfMultipart:data:)]) {
523     BOOL result;
524
525     result = [_delegate multipartBodyParser:self
526                         immediatlyParseBodyOfMultipart:_part
527                         data:_data];
528     return result;
529   }
530   else
531     return YES;
532 }
533
534 - (id)parseBodyOfPart:(id<NGMimePart>)_part
535   data:(NSData *)_data delegate:(id)_d
536 {
537   NGMimeMultipartBody *body;
538   NGMimeType *contentType;
539   NSString   *boundary;
540   unsigned   len;
541   
542   contentType = [_part contentType];
543   boundary    = [contentType valueOfParameter:@"boundary"];
544   len         = [_data length];
545   
546   if (len == 0)
547     return nil;
548   
549   if (contentType == nil) {
550     NSLog(@"ERROR [%s]: part %@ has no content type, cannot find out "
551           @"the boundary !", __PRETTY_FUNCTION__, _part);
552     return _data;
553   }
554   if (![contentType isCompositeType]) {
555     NSLog(@"ERROR [%s]: content type %@ of part %@ is not composite !",
556           __PRETTY_FUNCTION__, contentType, _part);
557     return _data;
558   }
559   if (boundary == nil) {
560     if (!(boundary = _searchBoundary(self, _data))) {
561       NSLog(@"ERROR [%s]: no boundary parameter in content "
562             @"type %@ of part %@ !",
563             __PRETTY_FUNCTION__, contentType, _part);
564       return _data;
565     }
566   }
567   if ([boundary length] > 70) {
568     if (MimeLogEnabled) 
569       [self logWithFormat:@"WARNING(%s): got boundary longer than 70 chars "
570             @"(not allowed by RFC) in type %@",
571             __PRETTY_FUNCTION__, contentType];
572   }
573   
574   if ([self parseImmediatlyWithDelegate:_d multipart:_part data:_data]) {
575     body = [[NGMimeMultipartBody alloc] initWithPart:_part];
576     body = AUTORELEASE(body);
577     
578     if (![self parseBody:body ofMultipart:_part data:_data delegate:_d])
579       ; // error
580   }
581   else {
582     body = [[NGMimeMultipartBody alloc]
583                                  initWithPart:_part data:_data delegate:_d];
584     body = AUTORELEASE(body);
585   }
586   
587   return body;
588 }
589
590 @end /* NGMimeMultipartBodyParser */