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