From e4aedbac080d85ac4f927acb9e015b9e8839891b Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Wed, 20 Nov 2013 08:56:29 -0500 Subject: [PATCH] conversion to file attachments + CIDs. --- NEWS | 13 ++ SoObjects/Mailer/NSString+Mail.h | 4 + SoObjects/Mailer/NSString+Mail.m | 206 ++++++++++++++++++--- SoObjects/Mailer/SOGoDraftObject.m | 127 ++++++++++--- SoObjects/Mailer/SOGoMailObject+Draft.m | 33 ++++ UI/MailPartViewers/UIxMailPartHTMLViewer.h | 4 +- UI/MailPartViewers/UIxMailPartHTMLViewer.m | 6 +- 7 files changed, 336 insertions(+), 57 deletions(-) diff --git a/NEWS b/NEWS index c5184cf7c..0fb0aada6 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,16 @@ +2.1.2 (2013-11-XX) +------------------ + +New features + - + +Enhancements + - we now automatically convert into file attachments + using CIDs. This prevents Outlook issues. + +Bug fixes + - + 2.1.1 (2013-11-19) ------------------ diff --git a/SoObjects/Mailer/NSString+Mail.h b/SoObjects/Mailer/NSString+Mail.h index e967622b5..5d04fc01b 100644 --- a/SoObjects/Mailer/NSString+Mail.h +++ b/SoObjects/Mailer/NSString+Mail.h @@ -21,12 +21,16 @@ #ifndef NSSTRING_MAIL_H #define NSSTRING_MAIL_H +#import #import @interface NSString (SOGoExtension) - (NSString *) htmlToText; +- (NSString *) htmlByExtractingImages: (NSMutableArray *) theImages; - (NSString *) stringByConvertingCRLNToHTML; +- (int) indexOf: (unichar) _c + fromIndex: (int) start; - (int) indexOf: (unichar) _c; - (NSString *) decodedHeader; diff --git a/SoObjects/Mailer/NSString+Mail.m b/SoObjects/Mailer/NSString+Mail.m index 94f8e9b4f..b9fa535df 100644 --- a/SoObjects/Mailer/NSString+Mail.m +++ b/SoObjects/Mailer/NSString+Mail.m @@ -22,19 +22,25 @@ #import #import #import +#import + #import #import #import #import #import +#import #import #import #import +#import +#import #include #import "NSString+Mail.h" #import "NSData+Mail.h" +#import "../SOGo/SOGoObject.h" #if 0 #define showWhoWeAre() \ @@ -43,11 +49,15 @@ #define showWhoWeAre() {} #endif -@interface _SOGoHTMLToTextContentHandler : NSObject +#define paddingBuffer 8192 + +@interface _SOGoHTMLContentHandler : NSObject { + NSMutableArray *images; + NSArray *ignoreContentTags; NSArray *specialTreatmentTags; - + BOOL ignoreContent; BOOL orderedList; BOOL unorderedList; @@ -57,32 +67,26 @@ } + (id) htmlToTextContentHandler; ++ (id) sanitizerContentHandler; - (NSString *) result; +- (void) setIgnoreContentTags: (NSArray *) theTags; +- (void) setSpecialTreatmentTags: (NSArray *) theTags; +- (void) setImages: (NSMutableArray *) theImages; + @end -@implementation _SOGoHTMLToTextContentHandler - -+ (id) htmlToTextContentHandler -{ - static id htmlToTextContentHandler; - - if (!htmlToTextContentHandler) - htmlToTextContentHandler = [self new]; - - return htmlToTextContentHandler; -} +@implementation _SOGoHTMLContentHandler - (id) init { if ((self = [super init])) { - ignoreContentTags = [NSArray arrayWithObjects: @"head", @"script", - @"style", nil]; - specialTreatmentTags = [NSArray arrayWithObjects: @"body", @"p", @"ul", - @"li", @"table", @"tr", @"td", @"th", - @"br", @"hr", @"dt", @"dd", nil]; + images = nil; + + ignoreContentTags = nil; + specialTreatmentTags = nil; [ignoreContentTags retain]; [specialTreatmentTags retain]; @@ -97,6 +101,32 @@ return self; } ++ (id) htmlToTextContentHandler +{ + static id htmlToTextContentHandler; + + if (!htmlToTextContentHandler) + htmlToTextContentHandler = [self new]; + + [htmlToTextContentHandler setIgnoreContentTags: [NSArray arrayWithObjects: @"head", @"script", + @"style", nil]]; + [htmlToTextContentHandler setSpecialTreatmentTags: [NSArray arrayWithObjects: @"body", @"p", @"ul", + @"li", @"table", @"tr", @"td", @"th", + @"br", @"hr", @"dt", @"dd", nil]]; + + return htmlToTextContentHandler; +} + ++ (id) sanitizerContentHandler +{ + static id sanitizerContentHandler; + + if (!sanitizerContentHandler) + sanitizerContentHandler = [self new]; + + return sanitizerContentHandler; +} + - (xmlCharEncoding) contentEncoding { return XML_CHAR_ENCODING_UTF8; @@ -121,6 +151,25 @@ return newResult; } +- (void) setIgnoreContentTags: (NSArray *) theTags +{ + ASSIGN(ignoreContentTags, theTags); +} + +- (void) setSpecialTreatmentTags: (NSArray *) theTags +{ + ASSIGN(specialTreatmentTags, theTags); +} + +// +// We MUST NOT retain the array here +// +- (void) setImages: (NSMutableArray *) theImages +{ + images = theImages; +} + + /* SaxContentHandler */ - (void) startDocument { @@ -210,14 +259,89 @@ showWhoWeAre(); - if (!ignoreContent) + tagName = [rawName lowercaseString]; + + if (!ignoreContent && ignoreContentTags && specialTreatmentTags) { - tagName = [rawName lowercaseString]; if ([ignoreContentTags containsObject: tagName]) ignoreContent = YES; else if ([specialTreatmentTags containsObject: tagName]) [self _startSpecialTreatment: tagName]; } + else if ([tagName isEqualToString: @"img"]) + { + NSString *value; + + value = [attributes valueForRawName: @"src"]; + + // + // Check for Data URI Scheme + // + // data:[][;charset=][;base64], + // + if ([value length] > 5 && [[value substringToIndex: 5] caseInsensitiveCompare: @"data:"] == NSOrderedSame) + { + NSString *uniqueId, *mimeType, *encoding, *charset; + NGMimeBodyPart *bodyPart; + NGMutableHashMap *map; + NSData *data; + id body; + + int i, j, k; + + i = [value indexOf: ';']; + j = [value indexOf: ';' fromIndex: i+1]; + k = [value indexOf: ',']; + + // We try to get the MIME type + mimeType = nil; + + if (i > 5 && i < k) + { + mimeType = [value substringWithRange: NSMakeRange(5, i-5)]; + } + else + i = 5; + + // We might get a stupid value. We discard anything that doesn't have a / in it + if ([mimeType indexOf: '/'] < 0) + mimeType = @"image/jpeg"; + + // We check and skip the charset + if (j > i) + charset = [value substringWithRange: NSMakeRange(i+1, j-i-1)]; + else + j = i; + + // We check the encoding and we completely ignore it + encoding = [value substringWithRange: NSMakeRange(j+1, k-j-1)]; + + if (![encoding length]) + encoding = @"base64"; + + data = [[value substringFromIndex: k+1] dataUsingEncoding: NSASCIIStringEncoding]; + + uniqueId = [SOGoObject globallyUniqueObjectId]; + + map = [[[NGMutableHashMap alloc] initWithCapacity:5] autorelease]; + [map setObject: encoding forKey: @"content-transfer-encoding"]; + [map setObject:[NSNumber numberWithInt:[data length]] forKey: @"content-length"]; + [map setObject: [NSString stringWithFormat: @"inline; filename=\"%@\"", uniqueId] forKey: @"content-disposition"]; + [map setObject: [NSString stringWithFormat: @"%@; name=\"%@\"", mimeType, uniqueId] forKey: @"content-type"]; + [map setObject: [NSString stringWithFormat: @"<%@>", uniqueId] forKey: @"content-id"]; + + + body = [[NGMimeFileData alloc] initWithBytes: [data bytes] length: [data length]]; + + bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease]; + [bodyPart setBody: body]; + [body release]; + + [images addObject: bodyPart]; + + [result appendFormat: @"", uniqueId, mimeType]; + } + } } - (void) endElement: (NSString *) element @@ -228,7 +352,7 @@ showWhoWeAre(); - if (ignoreContent) + if (ignoreContent && ignoreContentTags && specialTreatmentTags) { tagName = [rawName lowercaseString]; if ([ignoreContentTags containsObject: tagName]) @@ -345,13 +469,13 @@ - (NSString *) htmlToText { - _SOGoHTMLToTextContentHandler *handler; + _SOGoHTMLContentHandler *handler; id parser; NSData *d; parser = [[SaxXMLReaderFactory standardXMLReaderFactory] createXMLReaderForMimeType: @"text/html"]; - handler = [_SOGoHTMLToTextContentHandler htmlToTextContentHandler]; + handler = [_SOGoHTMLContentHandler htmlToTextContentHandler]; [parser setContentHandler: handler]; d = [self dataUsingEncoding: NSUTF8StringEncoding]; @@ -360,7 +484,24 @@ return [handler result]; } -#define paddingBuffer 8192 +- (NSString *) htmlByExtractingImages: (NSMutableArray *) theImages +{ + _SOGoHTMLContentHandler *handler; + id parser; + NSData *d; + + parser = [[SaxXMLReaderFactory standardXMLReaderFactory] + createXMLReaderForMimeType: @"text/html"]; + handler = [_SOGoHTMLContentHandler sanitizerContentHandler]; + [handler setImages: theImages]; + + [parser setContentHandler: handler]; + + d = [self dataUsingEncoding: NSUTF8StringEncoding]; + [parser parseFromSource: d]; + + return [handler result]; +} static inline char * convertChars (const char *oldString, unsigned int oldLength, @@ -434,18 +575,29 @@ convertChars (const char *oldString, unsigned int oldLength, return convertedString; } + - (int) indexOf: (unichar) _c + fromIndex: (int) start { int i, len; - + len = [self length]; - - for (i = 0; i < len; i++) + + if (start < 0 || start >= len) + start = 0; + + for (i = start; i < len; i++) { if ([self characterAtIndex: i] == _c) return i; } return -1; + +} + +- (int) indexOf: (unichar) _c +{ + return [self indexOf: _c fromIndex: 0]; } - (NSString *) decodedHeader diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index 9ce6b4136..4a9ea4ece 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2007-2012 Inverse inc. + Copyright (C) 2007-2013 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG This file is part of SOGo. @@ -395,11 +395,17 @@ static NSString *userAgent = nil; [self setSourceFolder: [paths componentsJoinedByString: @"/"]]; } +// +// +// - (NSString *) sourceFolder { return sourceFolder; } +// +// +// - (NSException *) storeInfo { NSMutableDictionary *infos; @@ -446,6 +452,9 @@ static NSString *userAgent = nil; return error; } +// +// +// - (void) _loadInfosFromDictionary: (NSDictionary *) infoDict { id value; @@ -478,11 +487,17 @@ static NSString *userAgent = nil; [self setInReplyTo: value]; } +// +// +// - (NSString *) relativeImap4Name { return [NSString stringWithFormat: @"%d", IMAP4ID]; } +// +// +// - (void) fetchInfo { NSString *p; @@ -504,16 +519,25 @@ static NSString *userAgent = nil; [self debugWithFormat: @"Note: info object does not yet exist: %@", p]; } +// +// +// - (void) setIMAP4ID: (int) newIMAP4ID { IMAP4ID = newIMAP4ID; } +// +// +// - (int) IMAP4ID { return IMAP4ID; } +// +// +// - (NSException *) save { NGImap4Client *client; @@ -552,6 +576,9 @@ static NSString *userAgent = nil; return error; } +// +// +// - (void) _addEMailsOfAddresses: (NSArray *) _addrs toArray: (NSMutableArray *) _ma { @@ -564,6 +591,9 @@ static NSString *userAgent = nil; [_ma addObject: [currentAddress email]]; } +// +// +// - (void) _addRecipients: (NSArray *) recipients toArray: (NSMutableArray *) array { @@ -576,6 +606,9 @@ static NSString *userAgent = nil; [array addObject: [currentAddress baseEMail]]; } +// +// +// - (void) _purgeRecipients: (NSArray *) recipients fromAddresses: (NSMutableArray *) addresses { @@ -602,6 +635,9 @@ static NSString *userAgent = nil; } } +// +// +// - (void) _fillInReplyAddresses: (NSMutableDictionary *) _info replyToAll: (BOOL) _replyToAll envelope: (NGImap4Envelope *) _envelope @@ -711,6 +747,9 @@ static NSString *userAgent = nil; } } +// +// +// - (NSArray *) _attachmentBodiesFromPaths: (NSArray *) paths fromResponseFetch: (NSDictionary *) fetch; { @@ -731,6 +770,9 @@ static NSString *userAgent = nil; return bodies; } +// +// +// - (void) _fetchAttachments: (NSArray *) parts fromMail: (SOGoMailObject *) sourceMail { @@ -758,6 +800,9 @@ static NSString *userAgent = nil; } } +// +// +// - (void) fetchMailForEditing: (SOGoMailObject *) sourceMail { NSString *subject, *msgid; @@ -804,6 +849,9 @@ static NSString *userAgent = nil; [self storeInfo]; } +// +// +// - (void) fetchMailForReplying: (SOGoMailObject *) sourceMail toAll: (BOOL) toAll { @@ -1255,6 +1303,9 @@ static NSString *userAgent = nil; return bodyPart; } +// +// +// - (NSArray *) bodyPartsForAllAttachments { /* returns nil on error */ @@ -1276,6 +1327,9 @@ static NSString *userAgent = nil; return bodyParts; } +// +// +// - (NGMimeBodyPart *) mimeMultipartAlternative { NGMimeMultipartBody *textParts; @@ -1301,6 +1355,9 @@ static NSString *userAgent = nil; return part; } +// +// +// - (NGMimeMessage *) mimeMultiPartMessageWithHeaderMap: (NGMutableHashMap *) map andBodyParts: (NSArray *) _bodyParts { @@ -1340,6 +1397,9 @@ static NSString *userAgent = nil; return message; } +// +// +// - (void) _addHeaders: (NSDictionary *) _h toHeaderMap: (NGMutableHashMap *) _map { @@ -1511,48 +1571,59 @@ static NSString *userAgent = nil; return map; } +// +// +// - (NGMimeMessage *) mimeMessageWithHeaders: (NSDictionary *) _headers excluding: (NSArray *) _exclude { - NGMutableHashMap *map; - NSArray *bodyParts; - NGMimeMessage *message; + NSMutableArray *bodyParts; + NGMimeMessage *message; + NGMutableHashMap *map; + NSString *newText; message = nil; + bodyParts = [NSMutableArray array]; + newText = [text htmlByExtractingImages: bodyParts]; + + if ([bodyParts count]) + [self setText: newText]; + map = [self mimeHeaderMapWithHeaders: _headers excluding: _exclude]; if (map) { //[self debugWithFormat: @"MIME Envelope: %@", map]; - - bodyParts = [self bodyPartsForAllAttachments]; - if (bodyParts) - { - //[self debugWithFormat: @"attachments: %@", bodyParts]; - - if ([bodyParts count] == 0) - /* no attachments */ - message = [self mimeMessageForContentWithHeaderMap: map]; - else - /* attachments, create multipart/mixed */ - message = [self mimeMultiPartMessageWithHeaderMap: map - andBodyParts: bodyParts]; - //[self debugWithFormat: @"message: %@", message]; - } + + [bodyParts addObjectsFromArray: [self bodyPartsForAllAttachments]]; + + //[self debugWithFormat: @"attachments: %@", bodyParts]; + + if ([bodyParts count] == 0) + /* no attachments */ + message = [self mimeMessageForContentWithHeaderMap: map]; else - [self errorWithFormat: - @"could not create body parts for attachments!"]; + /* attachments, create multipart/mixed */ + message = [self mimeMultiPartMessageWithHeaderMap: map + andBodyParts: bodyParts]; + //[self debugWithFormat: @"message: %@", message]; } - + return message; } +// +// +// - (NGMimeMessage *) mimeMessage { return [self mimeMessageWithHeaders: nil excluding: nil]; } +// +// +// - (NSData *) mimeMessageAsData { NGMimeMessageGenerator *generator; @@ -1565,6 +1636,9 @@ static NSString *userAgent = nil; return message; } +// +// +// - (NSArray *) allRecipients { NSMutableArray *allRecipients; @@ -1584,6 +1658,9 @@ static NSString *userAgent = nil; return allRecipients; } +// +// +// - (NSArray *) allBareRecipients { NSMutableArray *bareRecipients; @@ -1599,11 +1676,17 @@ static NSString *userAgent = nil; return bareRecipients; } +// +// +// - (NSException *) sendMail { return [self sendMailAndCopyToSent: YES]; } +// +// +// - (NSException *) sendMailAndCopyToSent: (BOOL) copyToSent { NSMutableData *cleaned_message; diff --git a/SoObjects/Mailer/SOGoMailObject+Draft.m b/SoObjects/Mailer/SOGoMailObject+Draft.m index 00f34be50..121983c8a 100644 --- a/SoObjects/Mailer/SOGoMailObject+Draft.m +++ b/SoObjects/Mailer/SOGoMailObject+Draft.m @@ -42,6 +42,9 @@ #define maxFilenameLength 64 +// +// +// @implementation SOGoMailObject (SOGoDraftObjectExtensions) - (NSString *) subjectForReply @@ -77,6 +80,9 @@ return newSubject; } +// +// +// - (NSString *) _convertRawContentForEditing: (NSString *) raw rawHtml: (BOOL) html { @@ -96,6 +102,9 @@ return rc; } +// +// +// - (NSString *) _contentForEditingFromKeys: (NSArray *) keys { NSArray *types; @@ -151,6 +160,9 @@ return content; } +// +// +// - (NSString *) contentForEditing { NSMutableArray *keys; @@ -166,6 +178,9 @@ return [self _contentForEditingFromKeys: keys]; } +// +// +// - (NSString *) contentForReply { NSString *pageName; @@ -185,6 +200,9 @@ return [[page generateResponse] contentAsString]; } +// +// +// - (NSString *) filenameForForward { NSString *subject; @@ -218,6 +236,9 @@ return newSubject; } +// +// +// - (NSString *) subjectForForward { NSString *subject, *newSubject; @@ -231,6 +252,9 @@ return newSubject; } +// +// +// - (NSString *) contentForInlineForward { SOGoUserDefaults *ud; @@ -247,6 +271,9 @@ return [[page generateResponse] contentAsString]; } +// +// +// - (void) _fetchFileAttachmentKey: (NSDictionary *) part intoArray: (NSMutableArray *) keys withPath: (NSString *) path @@ -291,6 +318,9 @@ } } +// +// +// - (void) _fetchFileAttachmentKeysInPart: (NSDictionary *) part intoArray: (NSMutableArray *) keys withPath: (NSString *) path @@ -325,6 +355,9 @@ } } +// +// +// #warning we might need to handle parts with a "name" attribute - (NSArray *) fetchFileAttachmentKeys { diff --git a/UI/MailPartViewers/UIxMailPartHTMLViewer.h b/UI/MailPartViewers/UIxMailPartHTMLViewer.h index bbfb0e049..5ba673c31 100644 --- a/UI/MailPartViewers/UIxMailPartHTMLViewer.h +++ b/UI/MailPartViewers/UIxMailPartHTMLViewer.h @@ -1,8 +1,6 @@ /* UIxMailPartHTMLViewer.h - this file is part of SOGo * - * Copyright (C) 2007, 2008 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2007-2013 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/UI/MailPartViewers/UIxMailPartHTMLViewer.m b/UI/MailPartViewers/UIxMailPartHTMLViewer.m index 99d43abdd..e60f97d0f 100644 --- a/UI/MailPartViewers/UIxMailPartHTMLViewer.m +++ b/UI/MailPartViewers/UIxMailPartHTMLViewer.m @@ -1,10 +1,6 @@ /* UIxMailPartHTMLViewer.m - this file is part of SOGo * - * Copyright (C) 2007-2012 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Ludovic Marcotte - * Francis Lachapelle + * Copyright (C) 2007-2013 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by