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.
This commit is contained in:
Javier Amor García
2016-02-08 18:17:35 +01:00
parent e1b1812364
commit f388d180ae
4 changed files with 93 additions and 43 deletions
+1 -1
View File
@@ -44,11 +44,11 @@
NSMutableDictionary *bodyPartsEncodings;
NSMutableDictionary *bodyPartsCharsets;
NSMutableDictionary *bodyPartsMimeTypes;
NSMutableDictionary *bodyPartsMixed;
NSString *headerCharset;
NSString *headerMimeType;
BOOL bodySetup;
BOOL multipartMixed;
NSArray *bodyContent;
BOOL fetchedAttachments;
+50 -29
View File
@@ -25,6 +25,7 @@
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSException.h>
#import <Foundation/NSValue.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSObject+Values.h>
#import <NGExtensions/NSString+Encoding.h>
@@ -42,6 +43,7 @@
#import <Mailer/SOGoMailBodyPart.h>
#import <Mailer/SOGoMailObject.h>
#import <Mailer/NSDictionary+Mail.h>
#import <Mailer/NSString+Mail.h>
#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
+3 -3
View File
@@ -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
+39 -10
View File
@@ -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
{
/*