From f388d180ae4fbb8c46c082e6020c9aa60dce0287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Amor=20Garc=C3=ADa?= Date: Mon, 8 Feb 2016 18:17:35 +0100 Subject: [PATCH] oc-mail: Better management of nested multipart types Instead of treating all the message either as alternative or mixed with this changeset the MIME type of the parent part is used. This allows a correct disposition of the message in the cases when nested multiparts elements are used. Also in mixed parts we convert between plain text and HTML as needed. --- OpenChange/MAPIStoreMailMessage.h | 2 +- OpenChange/MAPIStoreMailMessage.m | 79 +++++++++++++++++++------------ SoObjects/Mailer/SOGoMailObject.h | 6 +-- SoObjects/Mailer/SOGoMailObject.m | 49 +++++++++++++++---- 4 files changed, 93 insertions(+), 43 deletions(-) diff --git a/OpenChange/MAPIStoreMailMessage.h b/OpenChange/MAPIStoreMailMessage.h index 735861ff3..ed2fc2fd7 100644 --- a/OpenChange/MAPIStoreMailMessage.h +++ b/OpenChange/MAPIStoreMailMessage.h @@ -44,11 +44,11 @@ NSMutableDictionary *bodyPartsEncodings; NSMutableDictionary *bodyPartsCharsets; NSMutableDictionary *bodyPartsMimeTypes; + NSMutableDictionary *bodyPartsMixed; NSString *headerCharset; NSString *headerMimeType; BOOL bodySetup; - BOOL multipartMixed; NSArray *bodyContent; BOOL fetchedAttachments; diff --git a/OpenChange/MAPIStoreMailMessage.m b/OpenChange/MAPIStoreMailMessage.m index 2b3588ef5..65eb0f2c7 100644 --- a/OpenChange/MAPIStoreMailMessage.m +++ b/OpenChange/MAPIStoreMailMessage.m @@ -25,6 +25,7 @@ #import #import #import +#import #import #import #import @@ -42,6 +43,7 @@ #import #import #import +#import #import "Codepages.h" #import "NSData+MAPIStore.h" @@ -131,11 +133,11 @@ static NSArray *acceptedMimeTypes; bodyPartsEncodings = nil; bodyPartsCharsets = nil; bodyPartsMimeTypes = nil; + bodyPartsMixed = nil; headerSetup = NO; bodySetup = NO; bodyContent = nil; - multipartMixed = NO; mailIsEvent = NO; mailIsMeetingRequest = NO; @@ -155,6 +157,7 @@ static NSArray *acceptedMimeTypes; [bodyPartsEncodings release]; [bodyPartsCharsets release]; [bodyPartsMimeTypes release]; + [bodyPartsMixed release]; [bodyContent release]; @@ -260,7 +263,8 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) keys = [NSMutableArray array]; [sogoObject addRequiredKeysOfStructure: [sogoObject bodyStructure] - path: @"" toArray: keys + path: @"" + toArray: keys acceptedTypes: acceptedMimeTypes withPeek: YES]; [keys sortUsingFunction: _compareBodyKeysByPriority context: acceptedMimeTypes]; @@ -268,39 +272,29 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) if (keysCount > 0) { NSUInteger i; - id bodyStructure; BOOL hasHtml = NO; BOOL hasText = NO; - - bodyStructure = [sogoObject bodyStructure]; - /* multipart/mixed is the default type. - multipart/alternative and multipart/related are the only other type of multipart supported for the - message body - */ - if ([[bodyStructure objectForKey: @"type"] isEqualToString: @"multipart"]) - { - NSString *subtype = [bodyStructure objectForKey: @"subtype"]; - multipartMixed = !([subtype isEqualToString: @"alternative"] || - [subtype isEqualToString: @"related"]); - } - else - multipartMixed = NO; bodyContentKeys = [[NSMutableArray alloc] initWithCapacity: keysCount]; bodyPartsEncodings = [[NSMutableDictionary alloc] initWithCapacity: keysCount]; bodyPartsCharsets = [[NSMutableDictionary alloc] initWithCapacity: keysCount]; bodyPartsMimeTypes = [[NSMutableDictionary alloc] initWithCapacity: keysCount]; + bodyPartsMixed = [[NSMutableDictionary alloc] initWithCapacity: keysCount]; for (i = 0; i < keysCount; i++) { + NSDictionary *bodyStructureKey; NSString *key; NSString *mimeType; + BOOL mixedPart; NSString *strippedKey; NSString *encoding; NSString *charset; NSDictionary *partParameters; + NSString *multipart; - key = [[keys objectAtIndex: i] objectForKey: @"key"]; + bodyStructureKey = [keys objectAtIndex: i]; + key = [bodyStructureKey objectForKey: @"key"]; if (key == nil) continue; @@ -312,7 +306,21 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) partParameters = [partHeaderData objectForKey: @"parameterList"]; encoding = [partHeaderData objectForKey: @"encoding"]; charset = [partParameters objectForKey: @"charset"]; - mimeType = [[keys objectAtIndex: i] objectForKey: @"mimeType"]; + mimeType = [bodyStructureKey objectForKey: @"mimeType"]; + + /* multipart/mixed is the default type. + multipart/alternative is the only other type of multipart supported now. + */ + multipart = [bodyStructureKey objectForKey: @"multipart"]; + if ([multipart isEqualToString: @""]) + { + mixedPart = NO; + } + else + { + mixedPart = !([multipart isEqualToString: @"multipart/alternative"] || + [multipart isEqualToString: @"multipart/related"]); + } if (encoding) [bodyPartsEncodings setObject: encoding forKey: key]; @@ -326,6 +334,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) else if ([mimeType isEqualToString: @"text/html"]) hasHtml = YES; } + [bodyPartsMixed setObject: [NSNumber numberWithBool: mixedPart] forKey: key]; if (i == 0) { @@ -336,17 +345,28 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) if (charset) { if (headerCharset == nil) - ASSIGN (headerCharset, charset); + { + ASSIGN (headerCharset, charset); + } else if (![headerCharset isEqualToString: charset]) { /* Because we have different charsets we will encode all in UTF-8 */ ASSIGN (headerCharset, @"utf-8"); } } + } if (!hasHtml || !hasText) - multipartMixed = NO; + { + NSArray *bodyPartsMixedKeys = [bodyPartsMixed allKeys]; + for (i = 0; i < [keys count]; i++) + { + NSString *key = [bodyPartsMixedKeys objectAtIndex: i]; + [bodyPartsMixed setObject: [NSNumber numberWithBool: NO] forKey: key]; + } + } + if ([headerMimeType isEqualToString: @"text/calendar"] || [headerMimeType isEqualToString: @"application/ics"]) @@ -410,9 +430,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) NSString *mimeType = [bodyPartsMimeTypes objectForKey: key]; if (mimeType == nil) continue; - NSString *encoding = [bodyPartsEncodings objectForKey: key]; - if (encoding == nil) - encoding = @"7-bit"; + NSString *contentEncoding = [bodyPartsEncodings objectForKey: key]; + if (contentEncoding == nil) + contentEncoding = @"7-bit"; /* We should provide a case for each of the types in acceptedMimeTypes */ if (!mailIsEvent) @@ -421,6 +441,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) NSStringEncoding charsetEncoding; NSString *stringValue; BOOL html; + BOOL mixed = [[bodyPartsMixed objectForKey: key] boolValue]; if ([mimeType isEqualToString: @"text/html"]) { html = YES; @@ -436,7 +457,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) continue; } - content = [content bodyDataFromEncoding: encoding]; + content = [content bodyDataFromEncoding: contentEncoding]; charset = [bodyPartsCharsets objectForKey: key]; stringValue = nil; @@ -459,7 +480,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) [textContent appendData: [stringValue dataUsingEncoding: headerEncoding]]; } - if (multipartMixed) + if (mixed) { // We must add it also to the other mail representation if (html) @@ -491,9 +512,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) else { /* Without charset we cannot mangle the text, so we add as it stands */ - if (html || multipartMixed) + if (html || mixed) [htmlContent appendData: content]; - if (!html || multipartMixed) + if (!html || mixed) [textContent appendData: content]; } @@ -501,7 +522,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) else if ([mimeType isEqualToString: @"text/calendar"] || [mimeType isEqualToString: @"application/ics"]) { - content = [content bodyDataFromEncoding: encoding]; + content = [content bodyDataFromEncoding: contentEncoding]; [textContent appendData: content]; } else diff --git a/SoObjects/Mailer/SOGoMailObject.h b/SoObjects/Mailer/SOGoMailObject.h index af52334ee..8089bcf0b 100644 --- a/SoObjects/Mailer/SOGoMailObject.h +++ b/SoObjects/Mailer/SOGoMailObject.h @@ -129,9 +129,9 @@ NSArray *SOGoMailCoreInfoKeys; inContext: (id)_ctx; - (void) addRequiredKeysOfStructure: (NSDictionary *) info - path: (NSString *) p - toArray: (NSMutableArray *) keys - acceptedTypes: (NSArray *) types + path: (NSString *) p + toArray: (NSMutableArray *) keys + acceptedTypes: (NSArray *) types withPeek: (BOOL) withPeek; @end diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index dd4ee0358..2c4738061 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -516,12 +516,15 @@ static BOOL debugSoParts = NO; return s; } +/* This is defined before the public version without parentMimeType + argument to be able to call it recursively */ /* bulk fetching of plain/text content */ - (void) addRequiredKeysOfStructure: (NSDictionary *) info - path: (NSString *) p - toArray: (NSMutableArray *) keys - acceptedTypes: (NSArray *) types + path: (NSString *) p + toArray: (NSMutableArray *) keys + acceptedTypes: (NSArray *) types withPeek: (BOOL) withPeek + parentMultipart: (NSString *) parentMPart { /* This is used to collect the set of IMAP4 fetch-keys required to fetch @@ -536,6 +539,7 @@ static BOOL debugSoParts = NO; id body; NSString *bodyToken, *sp, *mimeType; id childInfo; + NSString *multipart; bodyToken = (withPeek ? @"body.peek" : @"body"); @@ -543,6 +547,12 @@ static BOOL debugSoParts = NO; [info valueForKey: @"type"], [info valueForKey: @"subtype"]] lowercaseString]; + + if ([[info valueForKey: @"type"] isEqualToString: @"multipart"]) + multipart = mimeType; + else + multipart = parentMPart; + if ([types containsObject: mimeType]) { if ([p length] > 0) @@ -557,7 +567,8 @@ static BOOL debugSoParts = NO; k = [NSString stringWithFormat: @"%@[text]", bodyToken]; } [keys addObject: [NSDictionary dictionaryWithObjectsAndKeys: k, @"key", - mimeType, @"mimeType", nil]]; + mimeType, @"mimeType", + multipart, @"multipart", nil]]; } parts = [info objectForKey: @"parts"]; @@ -571,9 +582,11 @@ static BOOL debugSoParts = NO; childInfo = [parts objectAtIndex: i]; [self addRequiredKeysOfStructure: childInfo - path: sp toArray: keys - acceptedTypes: types - withPeek: withPeek]; + path: sp + toArray: keys + acceptedTypes: types + withPeek: withPeek + parentMultipart: multipart]; } /* check body */ @@ -597,12 +610,28 @@ static BOOL debugSoParts = NO; else sp = [p length] > 0 ? (id)[p stringByAppendingString: @".1"] : (id)@"1"; [self addRequiredKeysOfStructure: body - path: sp toArray: keys - acceptedTypes: types - withPeek: withPeek]; + path: sp + toArray: keys + acceptedTypes: types + withPeek: withPeek + parentMultipart: multipart]; } } +- (void) addRequiredKeysOfStructure: (NSDictionary *) info + path: (NSString *) p + toArray: (NSMutableArray *) keys + acceptedTypes: (NSArray *) types + withPeek: (BOOL) withPeek +{ + [self addRequiredKeysOfStructure: (NSDictionary *) info + path: (NSString *) p + toArray: (NSMutableArray *) keys + acceptedTypes: (NSArray *) types + withPeek: (BOOL) withPeek + parentMultipart: @""]; +} + - (NSArray *) plainTextContentFetchKeys { /*