2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
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
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.
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
22 #include "NGMimeBodyParser.h"
23 #include "NGMimeBodyPartParser.h"
24 #include "NGMimeMultipartBody.h"
27 @implementation NGMimeMultipartBodyParser
29 static int MimeLogEnabled = -1;
32 return [super version] + 0 /* v2 */;
35 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
37 NSAssert2([super version] == 2,
38 @"invalid superclass (%@) version %i !",
39 NSStringFromClass([self superclass]), [super version]);
40 MimeLogEnabled = [ud boolForKey:@"MimeLogEnabled"] ? 1 : 0;
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)
48 register unsigned pos = 0;
49 register unsigned blen = _boundaryLen;
52 blen += 3; // -- + at least one EOL char at end of line
53 if (_len < blen) return NULL; // too short to contain boundary
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) {
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
71 CRLF--boundary(--)?CRLF
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] == '-')) {
79 if (strncmp(&(_from[pos + 3]), _boundary, _boundaryLen) == 0)
84 else if (_from[pos] == '\r') { // check for CR.-?
85 if ((_from[pos + 1] == '-') && (_from[pos + 2] == '-')) { // CHECK FOR CR--
87 if (strncmp(&(_from[pos + 3]), _boundary, _boundaryLen) == 0)
91 if ((_from[pos + 1] == '\n') && (_from[pos + 2] == '-')
92 && (_from[pos + 3] == '-')) {
94 if ((_len - pos) <= blen) {
95 // remaining part is too short for boundary starting with CRLF
99 if (strncmp(&(_from[pos + 4]), _boundary, _boundaryLen) == 0)
100 // found LF--boundary
101 return (_from + pos);
104 else if ((_from[pos] == '-') && (_from[pos + 1] == '-')) {
105 if (strncmp(&(_from[pos + 2]), _boundary, _boundaryLen) == 0) {
107 return (_from + pos);
116 _isEndBoundary(const char *_from, unsigned _len,
117 const char *_boundary, unsigned _boundaryLen)
119 // no buffer out-of-bounds check, may cause segfault
121 if (_len < (_boundaryLen + 8)) // + 2x CRLF and 2x '--'
124 while (*_from != '-') _from++; // search first '-'
125 _from += 2; // skip '--'
127 _from += _boundaryLen; // skip boundary;
128 return ((_from[0] == '-') && (_from[1] == '-')) ? YES : NO;
131 static inline const char *
132 _skipBoundary(id self, const char *_from, unsigned _len, BOOL _first)
134 register unsigned pos = 0;
135 register unsigned char c = 0;
137 if (_from == NULL) return NULL;
139 if (_from[0] == '-') { // skip '--'
140 c = 0; // EOL needs to be detected
142 else if (_from[1] == '-') { // skip CR-- or LF--
144 if ((c != '\n') && (c != '\r')) {
146 [self logWithFormat:@"WARNING(%s): invalid char before boundary '--'",
147 __PRETTY_FUNCTION__];
151 else if (_from[2] == '-') { // skip CRLF--
155 [self logWithFormat:@"WARNING(%s): missing CR before boundary 'LF--'",
156 __PRETTY_FUNCTION__];
161 [self logWithFormat:@"WARNING(%s): missing LF before boundary '--' (after"
162 @"CR)", __PRETTY_FUNCTION__];
168 [self logWithFormat:@"ERROR(%s): invalid parser state, skipping 4.",
169 __PRETTY_FUNCTION__];
174 register unsigned char fc = _from[pos];
176 if (c == 0) { // EOL detect (on first line)
177 if (fc == '\n') // LF
180 if (fc == '\r') { // CR *
181 if ((pos + 1) == _len) // CR EOF
183 else if (_from[pos + 1] == '\n') { // CRLF
191 else if (fc == c) // EOL char is known
198 pos++; // skip EOL char
200 return &(_from[pos]); // return pointer to position after char
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
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];
216 NSCAssert(buffer, @"got no buffer");
217 NSCAssert(_boundary, @"got no boundary");
219 result = [NSMutableArray arrayWithCapacity:7];
221 // find first boundary and store prefix
223 begin = _findNextBoundary(buffer, len, _boundary, _boundaryLen, YES);
227 [self logWithFormat:@"WARNING(%s): Found multipart with no 1st boundary",
228 __PRETTY_FUNCTION__];
229 [result addObject:_data];
233 pool = [[NSAutoreleasePool alloc] init];
236 unsigned preLen = begin - buffer;
239 if ([_delegate respondsToSelector:
240 @selector(multipartBodyParser:foundPrefix:inMultipart:)]) {
241 [_delegate multipartBodyParser:self
242 foundPrefix:[NSData dataWithBytes:buffer length:preLen]
246 [_body setPrefix:[NSString stringWithCString:buffer length:preLen]];
250 // skip first boundary
252 begin = _skipBoundary(self, begin, len - (begin - buffer), YES);
253 NSCAssert(begin, @"could not skip 1st boundary ..");
255 // loop over multipart bodies and exit if end-boundary is found
259 /* check for boundary denoting end of current part */
261 end = _findNextBoundary(begin, len - (begin - buffer),
262 _boundary,_boundaryLen, NO);
264 NSRange subDataRange;
265 NSData *rawData = nil;
268 [self logWithFormat:@"WARNING(%s): reached end of body without"
269 @" end-boundary", __PRETTY_FUNCTION__];
271 subDataRange.location = (begin - buffer);
272 subDataRange.length = ([_data length] + buffer - begin);
273 rawData = [_data subdataWithRange:subDataRange];
275 [result addObject:rawData];
280 NSRange subDataRange;
281 NSData *rawData = nil;
283 NSCAssert(end - begin >= 0, @"invalid range ..");
285 subDataRange.location = (begin - buffer);
286 subDataRange.length = (end - begin);
288 rawData = [_data subdataWithRange:subDataRange];
291 [result addObject:rawData];
294 NSLog(@"WARNING(%s): could not create rawdata for "
295 @" bodypart in multipart %@", __PRETTY_FUNCTION__,
299 /* check whether last read boundary was an end boundary */
301 if (_isEndBoundary(end, len - (end - buffer),
302 _boundary, _boundaryLen)) {
307 /* skip non-end boundary */
309 begin = _skipBoundary(self, end, len - (end - buffer), NO);
314 // skip end boundary and store suffix
316 if ((begin = _skipBoundary(self, end, len - (end - buffer), NO))) {
319 sufLen = len - (begin - buffer);
322 if ([_delegate respondsToSelector: @selector(multipartBodyParser:
325 [_delegate multipartBodyParser:self
326 foundSuffix:[NSData dataWithBytes:begin length:sufLen]
329 [_body setSuffix:[NSString stringWithCString:begin length:sufLen]];
334 /* result is not contained in this pool, so no need to retain ... */
340 static NSString *_searchBoundary(NGMimeMultipartBodyParser *self,
342 const char *buffer = [_data bytes];
343 int length = [_data length];
350 if ((buffer[0] == '-') && (buffer[1] == '-')) { // no prefix
355 while (pos + 5 < length) {
356 if (buffer[pos + 2] != '-') {
357 // if third char is not a '-' it cannot be a boundary start
362 if (buffer[pos] == '\n') { // check for LF--
363 if (buffer[pos + 1] == '-') {
370 else if (buffer[pos] == '\r') { // check for CR.-?
371 if ((buffer[pos + 1] == '-') ) { // CHECK FOR CR--
377 if ((buffer[pos + 1] == '\n') && (buffer[pos + 3] == '-')) {
379 if ((length - pos) <= 4) {
380 // remaining part is too short for boundary starting with CRLF
383 // found LF--boundary
397 while (((boundLength + pos) < length) &&
398 (buffer[boundLength] != '\n') &&
399 (buffer[boundLength] != '\r')) {
402 if ((boundLength + pos) < length) {
403 return [NSString stringWithCString:buffer length:boundLength];
412 - (id<NGMimePart>)parseBodyPartWithData:(NSData *)_rawData
413 inMultipart:(id<NGMimePart>)_multipart
414 parser:(NGMimePartParser *)_parser
416 if (![_rawData length])
419 return [_parser parsePartFromData:_rawData];
422 - (BOOL)parseBody:(NGMimeMultipartBody *)_body
423 ofMultipart:(id<NGMimePart>)_part
424 data:(NSData *)_data delegate:(id)_d
426 NGMimeType *contentType = nil;
427 NSString *boundary = nil;
428 NSArray *rawBodyParts = nil;
429 BOOL foundError = NO;
431 contentType = [_part contentType];
432 boundary = [contentType valueOfParameter:@"boundary"];
435 boundary = _searchBoundary(self, _data);
439 *(&rawBodyParts) = [self _parseBody:_body part:_part data:_data
440 boundary:[boundary cString]
441 length:[boundary cStringLength]
445 NGMimeBodyPartParser *bodyPartParser;
447 BOOL askDelegate = NO;
449 *(&count) = [rawBodyParts count];
451 *(&bodyPartParser) = nil;
453 if ([_d respondsToSelector:
454 @selector(multipartBodyParser:parserForEntity:inMultipart:)]) {
455 *(&askDelegate) = YES;
458 for (i = 0; i < count; i++) {
460 @"ERROR: could not parse body part at index %i in multipart %@: %@";
461 NGMimePartParser *parser;
465 rawData = [rawBodyParts objectAtIndex:i];
466 *(&parser) = bodyPartParser;
470 parser = [_d multipartBodyParser:self
471 parserForEntity:rawData
473 [parser setDelegate:_d];
475 else if (bodyPartParser == nil) {
476 bodyPartParser = [[NGMimeBodyPartParser alloc] init];
477 [bodyPartParser setDelegate:_d];
478 parser = bodyPartParser;
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];
494 if ([rawData length])
495 bodyPart = [self parseBodyPartWithData:rawData
500 NSLog(reason, i, _part, localException);
506 [_body addBodyPart:bodyPart];
513 RELEASE(bodyPartParser); bodyPartParser = nil;
518 - (BOOL)parseImmediatlyWithDelegate:(id)_delegate
519 multipart:(id<NGMimePart>)_part data:(NSData *)_data
521 if ([_delegate respondsToSelector:
522 @selector(multipartBodyParser:immediatlyParseBodyOfMultipart:data:)]) {
525 result = [_delegate multipartBodyParser:self
526 immediatlyParseBodyOfMultipart:_part
534 - (id)parseBodyOfPart:(id<NGMimePart>)_part
535 data:(NSData *)_data delegate:(id)_d
537 NGMimeMultipartBody *body;
538 NGMimeType *contentType;
542 contentType = [_part contentType];
543 boundary = [contentType valueOfParameter:@"boundary"];
544 len = [_data length];
549 if (contentType == nil) {
550 NSLog(@"ERROR [%s]: part %@ has no content type, cannot find out "
551 @"the boundary !", __PRETTY_FUNCTION__, _part);
554 if (![contentType isCompositeType]) {
555 NSLog(@"ERROR [%s]: content type %@ of part %@ is not composite !",
556 __PRETTY_FUNCTION__, contentType, _part);
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);
567 if ([boundary length] > 70) {
569 [self logWithFormat:@"WARNING(%s): got boundary longer than 70 chars "
570 @"(not allowed by RFC) in type %@",
571 __PRETTY_FUNCTION__, contentType];
574 if ([self parseImmediatlyWithDelegate:_d multipart:_part data:_data]) {
575 body = [[NGMimeMultipartBody alloc] initWithPart:_part];
576 body = AUTORELEASE(body);
578 if (![self parseBody:body ofMultipart:_part data:_data delegate:_d])
582 body = [[NGMimeMultipartBody alloc]
583 initWithPart:_part data:_data delegate:_d];
584 body = AUTORELEASE(body);
590 @end /* NGMimeMultipartBodyParser */