oc-mail: Support for multipart/mixed and multipart/alternative
With multipart messages only one of the parts was displayed as message body. This changeset supports both mixed and alternative multipart types.
This commit is contained in:
parent
9bb2473c8e
commit
dee7b4be1a
|
@ -1657,7 +1657,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
|||
- (id) lookupMessage: (NSString *) messageKey
|
||||
{
|
||||
MAPIStoreMailMessage *message;
|
||||
NSData *rawBodyData;
|
||||
NSArray *rawBodyData;
|
||||
|
||||
message = [super lookupMessage: messageKey];
|
||||
if (message)
|
||||
|
@ -1731,73 +1731,31 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
|||
- (enum mapistore_error) preloadMessageBodiesWithKeys: (NSArray *) keys
|
||||
ofTableType: (enum mapistore_table_type) tableType
|
||||
{
|
||||
NSEnumerator *enumerator;
|
||||
NSUInteger max;
|
||||
NSString *messageKey;
|
||||
MAPIStoreMailMessage *message;
|
||||
NSMutableSet *bodyPartKeys;
|
||||
NSMutableDictionary *keyAssoc;
|
||||
NSDictionary *response;
|
||||
NSUInteger count, max;
|
||||
NSString *messageKey, *messageUid, *bodyPartKey;
|
||||
NGImap4Client *client;
|
||||
NSArray *fetch;
|
||||
NSData *bodyContent;
|
||||
NSArray* bodyContent;
|
||||
|
||||
if (tableType == MAPISTORE_MESSAGE_TABLE)
|
||||
if (tableType != MAPISTORE_MESSAGE_TABLE)
|
||||
return MAPISTORE_SUCCESS;
|
||||
|
||||
[bodyData removeAllObjects];
|
||||
|
||||
max = [keys count];
|
||||
if (max == 0)
|
||||
return MAPISTORE_SUCCESS;
|
||||
|
||||
enumerator = [keys objectEnumerator];
|
||||
while ((messageKey = [enumerator nextObject]))
|
||||
{
|
||||
[bodyData removeAllObjects];
|
||||
max = [keys count];
|
||||
|
||||
if (max > 0)
|
||||
message = [self lookupMessage: messageKey];
|
||||
if (message)
|
||||
{
|
||||
bodyPartKeys = [NSMutableSet setWithCapacity: max];
|
||||
|
||||
keyAssoc = [NSMutableDictionary dictionaryWithCapacity: max];
|
||||
for (count = 0; count < max; count++)
|
||||
bodyContent = [message getBodyContent];
|
||||
if (bodyContent)
|
||||
{
|
||||
messageKey = [keys objectAtIndex: count];
|
||||
message = [self lookupMessage: messageKey];
|
||||
if (message)
|
||||
{
|
||||
bodyPartKey = [message bodyContentPartKey];
|
||||
if (bodyPartKey)
|
||||
{
|
||||
[bodyPartKeys addObject: bodyPartKey];
|
||||
messageUid = [self messageUIDFromMessageKey: messageKey];
|
||||
/* If the bodyPartKey include peek, remove it as it is not returned
|
||||
as key in the IMAP server response.
|
||||
|
||||
IMAP conversation example:
|
||||
a4 UID FETCH 1 (UID BODY.PEEK[text])
|
||||
* 1 FETCH (UID 1 BODY[TEXT] {1677}
|
||||
*/
|
||||
bodyPartKey = [bodyPartKey stringByReplacingOccurrencesOfString: @"body.peek"
|
||||
withString: @"body"];
|
||||
[keyAssoc setObject: bodyPartKey forKey: messageUid];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client = [[(SOGoMailFolder *) sogoObject imap4Connection] client];
|
||||
[client select: [sogoObject absoluteImap4Name]];
|
||||
response = [client fetchUids: [keyAssoc allKeys]
|
||||
parts: [bodyPartKeys allObjects]];
|
||||
fetch = [response objectForKey: @"fetch"];
|
||||
max = [fetch count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
response = [fetch objectAtIndex: count];
|
||||
messageUid = [[response objectForKey: @"uid"] stringValue];
|
||||
bodyPartKey = [keyAssoc objectForKey: messageUid];
|
||||
if (bodyPartKey)
|
||||
{
|
||||
bodyContent = [[response objectForKey: bodyPartKey]
|
||||
objectForKey: @"data"];
|
||||
if (bodyContent)
|
||||
{
|
||||
messageKey = [NSString stringWithFormat: @"%@.eml",
|
||||
messageUid];
|
||||
[bodyData setObject: bodyContent forKey: messageKey];
|
||||
}
|
||||
}
|
||||
[bodyData setObject: bodyContent forKey: messageKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,8 +25,10 @@
|
|||
|
||||
#import "MAPIStoreMessage.h"
|
||||
|
||||
@class NSData;
|
||||
@class NSString;
|
||||
@class NSArray;
|
||||
@class NSMutableArray;
|
||||
@class NSMutableDictionary;
|
||||
|
||||
@class MAPIStoreAppointmentWrapper;
|
||||
@class MAPIStoreMailFolder;
|
||||
|
@ -37,12 +39,17 @@
|
|||
BOOL mailIsEvent;
|
||||
BOOL mailIsMeetingRequest;
|
||||
BOOL mailIsSharingObject;
|
||||
NSString *mimeKey;
|
||||
|
||||
NSMutableArray *bodyContentKeys;
|
||||
NSMutableDictionary *bodyPartsEncodings;
|
||||
NSMutableDictionary *bodyPartsCharsets;
|
||||
NSMutableDictionary *bodyPartsMimeTypes;
|
||||
|
||||
NSString *headerCharset;
|
||||
NSString *headerEncoding;
|
||||
NSString *headerMimeType;
|
||||
BOOL bodySetup;
|
||||
NSData *bodyContent;
|
||||
BOOL multipartMixed;
|
||||
NSArray *bodyContent;
|
||||
BOOL fetchedAttachments;
|
||||
|
||||
MAPIStoreAppointmentWrapper *appointmentWrapper;
|
||||
|
@ -73,8 +80,8 @@
|
|||
inMemCtx: (TALLOC_CTX *) memCtx;
|
||||
|
||||
/* batch-mode helpers */
|
||||
- (NSString *) bodyContentPartKey;
|
||||
- (void) setBodyContentFromRawData: (NSData *) rawContent;
|
||||
- (void) setBodyContentFromRawData: (NSArray *) rawContent;
|
||||
- (NSArray *) getBodyContent;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -65,9 +65,13 @@
|
|||
#include <mapistore/mapistore.h>
|
||||
#include <mapistore/mapistore_errors.h>
|
||||
|
||||
#define BODY_CONTENT_TEXT 0
|
||||
#define BODY_CONTENT_HTML 1
|
||||
|
||||
@class iCalCalendar, iCalEvent;
|
||||
|
||||
static Class NSExceptionK, MAPIStoreSharingMessageK;
|
||||
static NSArray *acceptedMimeTypes;
|
||||
|
||||
@interface NSString (MAPIStoreMIME)
|
||||
|
||||
|
@ -111,22 +115,33 @@ static Class NSExceptionK, MAPIStoreSharingMessageK;
|
|||
{
|
||||
NSExceptionK = [NSException class];
|
||||
MAPIStoreSharingMessageK = [MAPIStoreSharingMessage class];
|
||||
acceptedMimeTypes = [[NSArray alloc] initWithObjects: @"text/calendar",
|
||||
@"application/ics",
|
||||
@"text/html",
|
||||
@"text/plain",
|
||||
nil];
|
||||
}
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
mimeKey = nil;
|
||||
bodyContentKeys = nil;
|
||||
bodyPartsEncodings = nil;
|
||||
bodyPartsCharsets = nil;
|
||||
bodyPartsMimeTypes = nil;
|
||||
|
||||
headerSetup = NO;
|
||||
bodySetup = NO;
|
||||
bodyContent = nil;
|
||||
multipartMixed = NO;
|
||||
|
||||
mailIsEvent = NO;
|
||||
mailIsMeetingRequest = NO;
|
||||
mailIsSharingObject = NO;
|
||||
headerCharset = nil;
|
||||
headerEncoding = nil;
|
||||
headerMimeType = nil;
|
||||
headerSetup = NO;
|
||||
bodyContent = nil;
|
||||
bodySetup = NO;
|
||||
|
||||
appointmentWrapper = nil;
|
||||
}
|
||||
|
||||
|
@ -135,11 +150,15 @@ static Class NSExceptionK, MAPIStoreSharingMessageK;
|
|||
|
||||
- (void) dealloc
|
||||
{
|
||||
[mimeKey release];
|
||||
[bodyContentKeys release];
|
||||
[bodyPartsEncodings release];
|
||||
[bodyPartsCharsets release];
|
||||
[bodyPartsMimeTypes release];
|
||||
|
||||
[bodyContent release];
|
||||
|
||||
[headerMimeType release];
|
||||
[headerCharset release];
|
||||
[headerEncoding release];
|
||||
[appointmentWrapper release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
@ -234,30 +253,82 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
|||
{
|
||||
MAPIStoreSharingMessage *sharingMessage;
|
||||
NSMutableArray *keys;
|
||||
NSArray *acceptedTypes;
|
||||
NSDictionary *messageData, *partHeaderData, *parameters;
|
||||
NSUInteger keysCount;
|
||||
NSDictionary *partHeaderData, *parameters;
|
||||
NSString *sharingHeader;
|
||||
|
||||
acceptedTypes = [NSArray arrayWithObjects: @"text/calendar",
|
||||
@"application/ics",
|
||||
@"text/html",
|
||||
@"text/plain", nil];
|
||||
keys = [NSMutableArray array];
|
||||
[sogoObject addRequiredKeysOfStructure: [sogoObject bodyStructure]
|
||||
path: @"" toArray: keys
|
||||
acceptedTypes: acceptedTypes
|
||||
acceptedTypes: acceptedMimeTypes
|
||||
withPeek: YES];
|
||||
[keys sortUsingFunction: _compareBodyKeysByPriority context: acceptedTypes];
|
||||
if ([keys count] > 0)
|
||||
[keys sortUsingFunction: _compareBodyKeysByPriority context: acceptedMimeTypes];
|
||||
keysCount = [keys count];
|
||||
if (keysCount > 0)
|
||||
{
|
||||
messageData = [keys objectAtIndex: 0];
|
||||
ASSIGN (mimeKey, [messageData objectForKey: @"key"]);
|
||||
ASSIGN (headerMimeType, [messageData objectForKey: @"mimeType"]);
|
||||
partHeaderData
|
||||
= [sogoObject lookupInfoForBodyPart: [mimeKey _strippedBodyKey]];
|
||||
ASSIGN (headerEncoding, [partHeaderData objectForKey: @"encoding"]);
|
||||
parameters = [partHeaderData objectForKey: @"parameterList"];
|
||||
ASSIGN (headerCharset, [parameters objectForKey: @"charset"]);
|
||||
NSUInteger i;
|
||||
id bodyStructure;
|
||||
|
||||
bodyStructure = [sogoObject bodyStructure];
|
||||
/* multipart/mixed is the default type.
|
||||
multipart/alternative is the only other type of multipart supported here.
|
||||
*/
|
||||
if ([[bodyStructure objectForKey: @"type"] isEqualToString: @"multipart"])
|
||||
multipartMixed = ! [[bodyStructure objectForKey: @"subtype"] isEqualToString: @"alternative"];
|
||||
else
|
||||
multipartMixed = NO;
|
||||
|
||||
bodyContentKeys = [[NSMutableArray alloc] initWithCapacity: keysCount];
|
||||
bodyPartsEncodings = [[NSMutableDictionary alloc] initWithCapacity: keysCount];
|
||||
bodyPartsCharsets = [[NSMutableDictionary alloc] initWithCapacity: keysCount];
|
||||
bodyPartsMimeTypes = [[NSMutableDictionary alloc] initWithCapacity: keysCount];
|
||||
|
||||
for (i = 0; i < keysCount; i++)
|
||||
{
|
||||
NSString *key;
|
||||
NSString *mimeType;
|
||||
NSString *strippedKey;
|
||||
NSString *encoding;
|
||||
NSString *charset;
|
||||
NSDictionary *partParameters;
|
||||
|
||||
key = [[keys objectAtIndex: i] objectForKey: @"key"];
|
||||
if (key == nil)
|
||||
continue;
|
||||
|
||||
[bodyContentKeys addObject: key];
|
||||
|
||||
strippedKey = [key _strippedBodyKey];
|
||||
partHeaderData = [sogoObject lookupInfoForBodyPart: strippedKey];
|
||||
|
||||
partParameters = [partHeaderData objectForKey: @"parameterList"];
|
||||
encoding = [partHeaderData objectForKey: @"encoding"];
|
||||
charset = [partParameters objectForKey: @"charset"];
|
||||
mimeType = [[keys objectAtIndex: i] objectForKey: @"mimeType"];
|
||||
|
||||
if (encoding)
|
||||
[bodyPartsEncodings setObject: encoding forKey: key];
|
||||
if (charset)
|
||||
[bodyPartsCharsets setObject: charset forKey: key];
|
||||
if (mimeType)
|
||||
[bodyPartsMimeTypes setObject: mimeType forKey: key];
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
ASSIGN (headerMimeType, mimeType);
|
||||
ASSIGN (headerCharset, charset);
|
||||
parameters = partParameters;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We can only put one header charset so we will use utf-8
|
||||
when we have different charsets in the parts */
|
||||
if (headerCharset != charset)
|
||||
ASSIGN (headerCharset, @"utf-8");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ([headerMimeType isEqualToString: @"text/calendar"]
|
||||
|| [headerMimeType isEqualToString: @"application/ics"])
|
||||
{
|
||||
|
@ -288,31 +359,93 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
|||
|
||||
- (void) _fetchBodyData
|
||||
{
|
||||
NSData *rawContent;
|
||||
NSString *resultKey;
|
||||
id result;
|
||||
|
||||
if (!headerSetup)
|
||||
[self _fetchHeaderData];
|
||||
|
||||
if (!bodyContent && mimeKey)
|
||||
if (!bodyContent && bodyContentKeys)
|
||||
{
|
||||
result = [sogoObject fetchParts: [NSArray arrayWithObject: mimeKey]];
|
||||
id result;
|
||||
NSString *key;
|
||||
NSEnumerator *enumerator;
|
||||
NSMutableData *htmlContent = nil;
|
||||
NSMutableData *textContent = nil;
|
||||
|
||||
result = [sogoObject fetchParts: bodyContentKeys];
|
||||
result = [[result valueForKey: @"RawResponse"] objectForKey: @"fetch"];
|
||||
if ([mimeKey hasPrefix: @"body.peek"])
|
||||
resultKey = [NSString stringWithFormat: @"body[%@]",
|
||||
[mimeKey _strippedBodyKey]];
|
||||
|
||||
htmlContent = [[NSMutableData alloc] initWithCapacity: 0];
|
||||
if (multipartMixed || mailIsEvent)
|
||||
textContent = htmlContent;
|
||||
else
|
||||
resultKey = mimeKey;
|
||||
rawContent = [[result objectForKey: resultKey] objectForKey: @"data"];
|
||||
ASSIGN (bodyContent, [rawContent bodyDataFromEncoding: headerEncoding]);
|
||||
textContent = [[NSMutableData alloc] initWithCapacity: 0];
|
||||
|
||||
enumerator = [bodyContentKeys objectEnumerator];
|
||||
while ((key = [enumerator nextObject]))
|
||||
{
|
||||
NSString *noPeekKey = [key stringByReplacingOccurrencesOfString: @"body.peek"
|
||||
withString: @"body"];
|
||||
|
||||
NSData *content = [[result objectForKey: noPeekKey] objectForKey: @"data"];
|
||||
if (content == nil)
|
||||
continue;
|
||||
NSString *mimeType = [bodyPartsMimeTypes objectForKey: key];
|
||||
if (mimeType == nil)
|
||||
continue;
|
||||
NSString *encoding = [bodyPartsEncodings objectForKey: key];
|
||||
if (encoding == nil)
|
||||
encoding = @"7-bit";
|
||||
|
||||
/* We should provide a case for each of the types in acceptedMimeTypes */
|
||||
if ([mimeType isEqualToString: @"text/html"] && !mailIsEvent)
|
||||
{
|
||||
content = [content bodyDataFromEncoding: encoding];
|
||||
[htmlContent appendData: content];
|
||||
}
|
||||
else if ([mimeType isEqualToString: @"text/plain"] && !mailIsEvent)
|
||||
{
|
||||
content = [content bodyDataFromEncoding: encoding];
|
||||
NSString *charset = [bodyPartsCharsets objectForKey: key];
|
||||
if (charset)
|
||||
{
|
||||
NSString *stringValue = [content bodyStringFromCharset: charset];
|
||||
[textContent appendData: [stringValue dataUsingEncoding: NSUTF8StringEncoding]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[textContent appendData: content];
|
||||
}
|
||||
}
|
||||
else if (mailIsEvent &&
|
||||
([mimeType isEqualToString: @"text/calendar"] ||
|
||||
[mimeType isEqualToString: @"application/ics"]))
|
||||
{
|
||||
content = [content bodyDataFromEncoding: encoding];
|
||||
[textContent appendData: content];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self warnWithFormat: @"Unsupported combination for body part. MIME type: %@. Event: %i",
|
||||
mimeType, mailIsEvent];
|
||||
}
|
||||
}
|
||||
|
||||
NSArray *newBodyContent = [[NSArray alloc] initWithObjects: textContent, htmlContent, nil];
|
||||
ASSIGN (bodyContent, newBodyContent);
|
||||
}
|
||||
|
||||
bodySetup = YES;
|
||||
}
|
||||
|
||||
- (NSArray*) getBodyContent
|
||||
{
|
||||
if (!bodySetup)
|
||||
[self _fetchBodyData];
|
||||
return bodyContent;
|
||||
}
|
||||
|
||||
- (MAPIStoreAppointmentWrapper *) _appointmentWrapper
|
||||
{
|
||||
NSData *textContent;
|
||||
NSArray *events, *from;
|
||||
iCalCalendar *calendar;
|
||||
iCalEvent *event;
|
||||
|
@ -324,7 +457,8 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
|||
if (!bodySetup)
|
||||
[self _fetchBodyData];
|
||||
|
||||
stringValue = [bodyContent bodyStringFromCharset: headerCharset];
|
||||
textContent = [bodyContent objectAtIndex: BODY_CONTENT_TEXT];
|
||||
stringValue = [textContent bodyStringFromCharset: headerCharset];
|
||||
calendar = [iCalCalendar parseSingleFromSource: stringValue];
|
||||
events = [calendar events];
|
||||
if ([events count] > 0)
|
||||
|
@ -1030,24 +1164,37 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
|||
- (int) getPidTagBody: (void **) data
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
NSString *stringValue;
|
||||
int rc = MAPISTORE_SUCCESS;
|
||||
NSData *textContent;
|
||||
int rc;
|
||||
|
||||
if (!bodySetup)
|
||||
[self _fetchBodyData];
|
||||
|
||||
if ([headerMimeType isEqualToString: @"text/plain"])
|
||||
{
|
||||
stringValue = [bodyContent bodyStringFromCharset: headerCharset];
|
||||
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
||||
}
|
||||
else if (mailIsEvent)
|
||||
rc = [[self _appointmentWrapper] getPidTagBody: data
|
||||
inMemCtx: memCtx];
|
||||
else
|
||||
if (!bodyContent)
|
||||
{
|
||||
*data = NULL;
|
||||
rc = MAPISTORE_ERR_NOT_FOUND;
|
||||
return MAPISTORE_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (mailIsEvent)
|
||||
{
|
||||
rc = [[self _appointmentWrapper] getPidTagBody: data
|
||||
inMemCtx: memCtx];
|
||||
}
|
||||
else
|
||||
{
|
||||
textContent = [bodyContent objectAtIndex: BODY_CONTENT_TEXT];
|
||||
if ([textContent length])
|
||||
{
|
||||
NSString *stringValue = [textContent bodyStringFromCharset: headerCharset];
|
||||
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
*data = NULL;
|
||||
rc = MAPISTORE_ERR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
|
@ -1056,13 +1203,25 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
|||
- (int) getPidTagHtml: (void **) data
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
int rc = MAPISTORE_SUCCESS;
|
||||
NSData *htmlContent;
|
||||
int rc;
|
||||
|
||||
if (!bodySetup)
|
||||
[self _fetchBodyData];
|
||||
|
||||
if ([headerMimeType isEqualToString: @"text/html"])
|
||||
*data = [bodyContent asBinaryInMemCtx: memCtx];
|
||||
if (!bodyContent || mailIsEvent)
|
||||
{
|
||||
*data = NULL;
|
||||
return MAPISTORE_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
htmlContent = [bodyContent objectAtIndex: BODY_CONTENT_HTML] ;
|
||||
|
||||
if ([htmlContent length])
|
||||
{
|
||||
*data = [htmlContent asBinaryInMemCtx: memCtx];
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
*data = NULL;
|
||||
|
@ -1680,24 +1839,12 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
|||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
- (NSString *) bodyContentPartKey
|
||||
{
|
||||
NSString *bodyPartKey;
|
||||
|
||||
if (!headerSetup)
|
||||
[self _fetchHeaderData];
|
||||
|
||||
bodyPartKey = mimeKey;
|
||||
|
||||
return bodyPartKey;
|
||||
}
|
||||
|
||||
- (void) setBodyContentFromRawData: (NSData *) rawContent
|
||||
- (void) setBodyContentFromRawData: (NSArray *) rawContent
|
||||
{
|
||||
if (!headerSetup)
|
||||
[self _fetchHeaderData];
|
||||
|
||||
ASSIGN (bodyContent, [rawContent bodyDataFromEncoding: headerEncoding]);
|
||||
ASSIGN (bodyContent, rawContent);
|
||||
bodySetup = YES;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue