sogo/OpenChange/MAPIStoreMailVolatileMessage.m
Enrique J. Hernández Blasco c6b976a830 oc-mail: Remove unnecessary time shift in date for new mails
The MIME type can store the date in UTC and the clients are
responsible on showing correctly to the client as it does.
2015-01-20 14:35:32 +01:00

1046 lines
32 KiB
Objective-C

/* MAPIStoreMailVolatileMessage.m - this file is part of SOGo
*
* Copyright (C) 2011-2012 Inverse inc
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* 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
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/* TODO:
- calendar invitations
- merge some code in a common module with SOGoDraftObject
*/
#import <Foundation/NSArray.h>
#import <Foundation/NSData.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
#import <NGExtensions/NGHashMap.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+Encoding.h>
#import <NGMime/NGMimeBodyPart.h>
#import <NGMime/NGMimeMultipartBody.h>
#import <NGMail/NGMimeMessage.h>
#import <NGMail/NGMimeMessageGenerator.h>
#import <NGImap4/NGImap4Client.h>
#import <NGImap4/NGImap4Connection.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSCalendarDate+SOGo.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoMailer.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserManager.h>
#import <Mailer/SOGoMailFolder.h>
#import <Mailer/NSString+Mail.h>
#import "Codepages.h"
#import "MAPIStoreAttachment.h"
#import "MAPIStoreAttachmentTable.h"
#import "MAPIStoreContext.h"
#import "MAPIStoreMailFolder.h"
#import "MAPIStoreMIME.h"
#import "MAPIStoreMapping.h"
#import "MAPIStoreSamDBUtils.h"
#import "MAPIStoreTypes.h"
#import "MAPIStoreUserContext.h"
#import "NSData+MAPIStore.h"
#import "NSObject+MAPIStore.h"
#import "NSString+MAPIStore.h"
#import <SOGo/SOGoCacheObject.h>
#import "MAPIStoreMailVolatileMessage.h"
#undef DEBUG
#include <mapistore/mapistore.h>
#include <mapistore/mapistore_errors.h>
static Class NSNumberK = Nil;
static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
//
// Useful extension that comes from Pantomime which is also
// released under the LGPL. We should eventually merge
// this with the same category found in SOPE's NGSmtpClient.m
// or simply drop sope-mime in favor of Pantomime
//
@interface NSMutableData (DataCleanupExtension)
- (NSRange) rangeOfCString: (const char *) theCString;
- (NSRange) rangeOfCString: (const char *) theCString
options: (unsigned int) theOptions
range: (NSRange) theRange;
@end
@implementation NSMutableData (DataCleanupExtension)
- (NSRange) rangeOfCString: (const char *) theCString
{
return [self rangeOfCString: theCString
options: 0
range: NSMakeRange(0,[self length])];
}
-(NSRange) rangeOfCString: (const char *) theCString
options: (unsigned int) theOptions
range: (NSRange) theRange
{
const char *b, *bytes;
int i, len, slen;
if (!theCString)
{
return NSMakeRange(NSNotFound,0);
}
bytes = [self bytes];
len = [self length];
slen = strlen(theCString);
b = bytes;
if (len > theRange.location + theRange.length)
{
len = theRange.location + theRange.length;
}
if (theOptions == NSCaseInsensitiveSearch)
{
i = theRange.location;
b += i;
for (; i <= len-slen; i++, b++)
{
if (!strncasecmp(theCString,b,slen))
{
return NSMakeRange(i,slen);
}
}
}
else
{
i = theRange.location;
b += i;
for (; i <= len-slen; i++, b++)
{
if (!memcmp(theCString,b,slen))
{
return NSMakeRange(i,slen);
}
}
}
return NSMakeRange(NSNotFound,0);
}
@end
@interface MAPIStoreAttachment (MAPIStoreMIME)
- (BOOL) hasContentId;
- (NGMimeBodyPart *) asMIMEBodyPart;
@end
@implementation MAPIStoreAttachment (MAPIStoreMIME)
- (BOOL) hasContentId
{
return ([properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)]
!= nil);
}
- (NGMimeBodyPart *) asMIMEBodyPart
{
NGMimeBodyPart *bodyPart = nil;
NSString *filename, *mimeType, *baseDisposition, *contentType,
*contentDisposition, *contentId;
NSData *content;
struct mapistore_connection_info *connInfo;
SOGoDomainDefaults *dd;
SOGoUser *activeUser;
NGMutableHashMap *map;
content = [properties objectForKey: MAPIPropertyKey (PR_ATTACH_DATA_BIN)];
if (content)
{
filename
= [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_LONG_FILENAME_UNICODE)];
if (![filename length])
filename
= [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_FILENAME_UNICODE)];
mimeType = [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_MIME_TAG_UNICODE)];
if (!mimeType && [filename length])
mimeType = [[MAPIStoreMIME sharedMAPIStoreMIME]
mimeTypeForExtension: [filename pathExtension]];
if (!mimeType)
mimeType = @"application/octet-stream";
if ([mimeType hasPrefix: @"text/"])
{
connInfo = [[self context] connectionInfo];
activeUser = [SOGoUser userWithLogin: [NSString stringWithUTF8String: connInfo->username]];
dd = [activeUser domainDefaults];
baseDisposition = ([dd mailAttachTextDocumentsInline]
? @"inline" : @"attachment");
}
else if ([mimeType hasPrefix: @"image/"] || [mimeType hasPrefix: @"message"])
baseDisposition = @"inline";
else
baseDisposition = @"attachment";
if ([filename length] > 0)
{
contentType = [NSString stringWithFormat: @"%@; name=\"%@\"",
mimeType, filename];
contentDisposition = [NSString stringWithFormat: @"%@; filename=\"%@\"",
baseDisposition, filename];
}
else
{
contentType = mimeType;
contentDisposition = baseDisposition;
}
map = [[NGMutableHashMap alloc] initWithCapacity: 16];
[map addObject: contentType forKey: @"content-type"];
[map addObject: contentDisposition forKey: @"content-disposition"];
contentId = [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
if (contentId)
[map setObject: [NSString stringWithFormat: @"<%@>", contentId]
forKey: @"content-id"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: map];
[bodyPart setBody: content];
[map release];
}
else
[self errorWithFormat: @"no content for attachment"];
return bodyPart;
}
@end
@implementation MAPIStoreMailVolatileMessage
+ (void) initialize
{
NSNumberK = [NSNumber class];
}
- (id) initWithSOGoObject: (id) newSOGoObject
inContainer: (MAPIStoreObject *) newContainer
{
if ((self = [super initWithSOGoObject: newSOGoObject
inContainer: newContainer]))
{
ASSIGN (properties, [sogoObject properties]);
}
return self;
}
- (void) addProperties: (NSDictionary *) newProperties
{
[super addProperties: newProperties];
[sogoObject adjustLastModified];
}
- (BOOL) canGetProperty: (enum MAPITAGS) propTag
{
return ([super canGetProperty: propTag]
|| [properties objectForKey: MAPIPropertyKey (propTag)] != nil);
}
- (uint64_t) objectVersion
{
NSNumber *version;
version = [properties objectForKey: @"version"];
return (version
? exchange_globcnt ([version unsignedLongLongValue])
: ULLONG_MAX);
}
- (int) getPidTagMessageClass: (void **) data inMemCtx: (TALLOC_CTX *) memCtx
{
*data = [@"IPM.Note" asUnicodeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
- (int) getPidTagChangeKey: (void **) data inMemCtx: (TALLOC_CTX *) memCtx
{
NSData *changeKey;
int rc;
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
if (changeKey)
{
*data = [changeKey asBinaryInMemCtx: memCtx];
rc = MAPISTORE_SUCCESS;
}
else
rc = [super getPidTagChangeKey: data inMemCtx: memCtx];
return rc;
}
- (NSDate *) creationTime
{
return [sogoObject creationDate];
}
- (NSDate *) lastModificationTime
{
return [sogoObject lastModified];
}
- (id) lookupAttachment: (NSString *) childKey
{
return [attachmentParts objectForKey: childKey];
}
- (void) getMessageData: (struct mapistore_message **) dataPtr
inMemCtx: (TALLOC_CTX *) memCtx
{
NSArray *recipients;
NSUInteger count, max, recipientsMax, p, current;
NSString *username, *cn, *email;
NSData *entryId;
NSDictionary *allRecipients, *dict, *contactInfos;
SOGoUserManager *mgr;
struct ldb_context *samCtx;
struct mapistore_message *msgData;
struct mapistore_message_recipient *recipient;
enum ulRecipClass type;
samCtx = [[self context] connectionInfo]->sam_ctx;
// [super getMessageData: &msgData inMemCtx: memCtx];
msgData = talloc_zero (memCtx, struct mapistore_message);
allRecipients = [properties objectForKey: @"recipients"];
msgData->columns = set_SPropTagArray (msgData, 9,
PR_OBJECT_TYPE,
PR_DISPLAY_TYPE,
PR_7BIT_DISPLAY_NAME_UNICODE,
PR_SMTP_ADDRESS_UNICODE,
PR_SEND_INTERNET_ENCODING,
PR_RECIPIENT_DISPLAY_NAME_UNICODE,
PR_RECIPIENT_FLAGS,
PR_RECIPIENT_ENTRYID,
PR_RECIPIENT_TRACKSTATUS);
/* Retrieve recipients from the message */
max = ([[allRecipients objectForKey: @"orig"] count]
+ [[allRecipients objectForKey: @"to"] count]
+ [[allRecipients objectForKey: @"cc"] count]
+ [[allRecipients objectForKey: @"bcc"] count]);
mgr = [SOGoUserManager sharedUserManager];
msgData->recipients_count = max;
msgData->recipients = talloc_array (msgData, struct mapistore_message_recipient, max);
current = 0;
for (type = MAPI_ORIG; type <= MAPI_BCC; type++)
{
recipients = [allRecipients objectForKey: recTypes[type]];
recipientsMax = [recipients count];
for (count = 0; count < recipientsMax; count++)
{
recipient = msgData->recipients + current;
recipient->type = type;
dict = [recipients objectAtIndex: count];
cn = [dict objectForKey: @"fullName"];
email = [dict objectForKey: @"email"];
contactInfos = [mgr contactInfosForUserWithUIDorEmail: email];
if (contactInfos)
{
username = [contactInfos objectForKey: @"c_uid"];
recipient->username = [username asUnicodeInMemCtx: msgData];
entryId = MAPIStoreInternalEntryId (samCtx, username);
}
else
{
recipient->username = NULL;
entryId = MAPIStoreExternalEntryId (cn, email);
}
/* properties */
p = 0;
recipient->data = talloc_array (msgData, void *, msgData->columns->cValues);
memset (recipient->data, 0, msgData->columns->cValues * sizeof (void *));
// PR_OBJECT_TYPE = MAPI_MAILUSER (see MAPI_OBJTYPE)
recipient->data[p] = MAPILongValue (msgData, MAPI_MAILUSER);
p++;
// PR_DISPLAY_TYPE = DT_MAILUSER (see MS-NSPI)
recipient->data[p] = MAPILongValue (msgData, 0);
p++;
// PR_7BIT_DISPLAY_NAME_UNICODE
recipient->data[p] = [cn asUnicodeInMemCtx: msgData];
p++;
// PR_SMTP_ADDRESS_UNICODE
recipient->data[p] = [email asUnicodeInMemCtx: msgData];
p++;
// PR_SEND_INTERNET_ENCODING = 0x00060000 (plain text, see OXCMAIL)
recipient->data[p] = MAPILongValue (msgData, 0x00060000);
p++;
// PR_RECIPIENT_DISPLAY_NAME_UNICODE
recipient->data[p] = [cn asUnicodeInMemCtx: msgData];
p++;
// PR_RECIPIENT_FLAGS
recipient->data[p] = MAPILongValue (msgData, 0x01);
p++;
// PR_RECIPIENT_ENTRYID
recipient->data[p] = [entryId asBinaryInMemCtx: msgData];
p++;
// PR_RECIPIENT_TRACKSTATUS
recipient->data[p] = MAPILongValue (msgData, 0x00);
p++;
current++;
}
}
*dataPtr = msgData;
}
static inline NSString *
MakeRecipientString (NSDictionary *recipient)
{
NSString *fullName, *email, *fullEmail;
fullName = [recipient objectForKey: @"fullName"];
email = [recipient objectForKey: @"email"];
if ([email length] > 0)
{
if ([fullName length] > 0)
fullEmail = [NSString stringWithFormat: @"%@ <%@>", fullName, email];
else
fullEmail = email;
}
else
{
NSLog (@"recipient not generated from record: %@", recipient);
fullEmail = nil;
}
return fullEmail;
}
static inline NSArray *
MakeRecipientsList (NSArray *recipients)
{
NSMutableArray *list;
NSUInteger count, max;
NSString *recipient;
max = [recipients count];
list = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
recipient = MakeRecipientString ([recipients objectAtIndex: count]);
if (recipient)
[list addObject: recipient];
}
return list;
}
static NSString *
QuoteSpecials (NSString *address)
{
NSString *result, *part, *s2;
int i, len;
// We want to correctly send mails to recipients such as :
// foo.bar
// foo (bar) <foo@zot.com>
// bar, foo <foo@zot.com>
if ([address indexOf: '('] >= 0 || [address indexOf: ')'] >= 0
|| [address indexOf: '<'] >= 0 || [address indexOf: '>'] >= 0
|| [address indexOf: '@'] >= 0 || [address indexOf: ','] >= 0
|| [address indexOf: ';'] >= 0 || [address indexOf: ':'] >= 0
|| [address indexOf: '\\'] >= 0 || [address indexOf: '"'] >= 0
|| [address indexOf: '.'] >= 0
|| [address indexOf: '['] >= 0 || [address indexOf: ']'] >= 0)
{
// We search for the first instance of < from the end
// and we quote what was before if we need to
len = [address length];
i = -1;
while (len--)
if ([address characterAtIndex: len] == '<')
{
i = len;
break;
}
if (i > 0)
{
part = [address substringToIndex: i - 1];
s2 = [[part stringByReplacingString: @"\\" withString: @"\\\\"]
stringByReplacingString: @"\"" withString: @"\\\""];
result = [NSString stringWithFormat: @"\"%@\" %@", s2, [address substringFromIndex: i]];
}
else
{
s2 = [[address stringByReplacingString: @"\\" withString: @"\\\\"]
stringByReplacingString: @"\"" withString: @"\\\""];
result = [NSString stringWithFormat: @"\"%@\"", s2];
}
}
else
result = address;
return result;
}
static inline void
FillMessageHeadersFromProperties (NGMutableHashMap *headers,
NSDictionary *mailProperties, BOOL withBcc,
struct mapistore_connection_info *connInfo)
{
NSData *senderEntryId;
NSMutableString *subject;
NSString *from, *recId, *messageId, *subjectData, *recipientsStr;
NSArray *list;
NSCalendarDate *date;
NSDictionary *recipients;
enum ulRecipClass type, bccLimit;
SOGoUser *activeUser;
NSNumber *priority;
activeUser
= [SOGoUser
userWithLogin: [NSString stringWithUTF8String: connInfo->username]];
from = [NSString stringWithFormat: @"%@ <%@>",
[activeUser cn], [[activeUser allEmails] objectAtIndex: 0]];
[headers setObject: QuoteSpecials (from) forKey: @"from"];
/* save the recipients */
recipients = [mailProperties objectForKey: @"recipients"];
if (recipients)
{
if (withBcc)
bccLimit = MAPI_BCC;
else
bccLimit = MAPI_CC;
for (type = MAPI_TO; type <= bccLimit; type++)
{
recId = recTypes[type];
list = MakeRecipientsList ([recipients objectForKey: recId]);
[headers setObjects: list forKey: recId];
}
list = MakeRecipientsList ([recipients objectForKey: @"orig"]);
if ([list count])
[headers setObjects: list forKey: @"from"];
}
else
{
NSLog (@"Message without recipients."
@"Guessing recipients from PidTagSenderEntryId, PidTagOriginalDisplayTo"
@"and PidTagOriginalCc");
senderEntryId = [mailProperties objectForKey: MAPIPropertyKey (PR_SENDER_ENTRYID)];
if (senderEntryId)
{
struct Binary_r bin32;
struct AddressBookEntryId *addrBookEntryId;
NSString *username;
NSMutableDictionary *fromRecipient;
fromRecipient = [NSMutableDictionary dictionaryWithCapacity: 2];
bin32.cb = [senderEntryId length];
bin32.lpb = (uint8_t *) [senderEntryId bytes];
addrBookEntryId = get_AddressBookEntryId (connInfo->sam_ctx, &bin32);
if (addrBookEntryId && [[NSString stringWithGUID: &addrBookEntryId->ProviderUID]
hasSuffix: @"08002b2fe182"])
{
/* TODO: better way to distinguish local and other ones */
username = MAPIStoreSamDBUserAttribute (connInfo->sam_ctx, @"legacyExchangeDN",
[NSString stringWithUTF8String: addrBookEntryId->X500DN], @"sAMAccountName");
if (username)
{
SOGoUser *fromUser;
fromUser = [SOGoUser userWithLogin: [username lowercaseString]];
[fromRecipient setObject: [fromUser cn] forKey: @"fullName"];
[fromRecipient setObject: [[fromUser allEmails] objectAtIndex: 0]
forKey: @"email"];
}
else
[fromRecipient setObject: [NSString stringWithUTF8String: addrBookEntryId->X500DN]
forKey: @"email"];
}
else
{
/* Try with One-Off EntryId */
struct OneOffEntryId *oneOffEntryId;
oneOffEntryId = get_OneOffEntryId (connInfo->sam_ctx, &bin32);
if (oneOffEntryId && [[NSString stringWithGUID: &oneOffEntryId->ProviderUID]
hasSuffix: @"00dd010f5402"])
{
[fromRecipient setObject: [NSString stringWithUTF8String: oneOffEntryId->DisplayName.lpszW]
forKey: @"fullName"];
[fromRecipient setObject: [NSString stringWithUTF8String: oneOffEntryId->EmailAddress.lpszW]
forKey: @"email"];
}
talloc_free (oneOffEntryId);
}
talloc_free (addrBookEntryId);
if ([[fromRecipient allKeys] count] > 0)
{
list = MakeRecipientsList ([NSArray arrayWithObjects: fromRecipient, nil]);
if ([list count])
[headers setObjects: list forKey: @"from"];
}
}
recipientsStr = [mailProperties objectForKey: MAPIPropertyKey (PidTagOriginalDisplayTo)];
if (recipientsStr)
{
list = [recipientsStr componentsSeparatedByString:@", "];
if ([list count])
[headers setObjects: list forKey: @"to"];
}
recipientsStr = [mailProperties objectForKey: MAPIPropertyKey (PidTagOriginalDisplayCc)];
if (recipientsStr)
{
list = [recipientsStr componentsSeparatedByString:@", "];
if ([list count])
[headers setObjects: list forKey: @"cc"];
}
}
subject = [NSMutableString stringWithCapacity: 128];
subjectData = [mailProperties objectForKey: MAPIPropertyKey (PR_SUBJECT_PREFIX_UNICODE)];
if (subjectData)
[subject appendString: subjectData];
subjectData = [mailProperties objectForKey: MAPIPropertyKey (PR_NORMALIZED_SUBJECT_UNICODE)];
if (subjectData)
[subject appendString: subjectData];
[headers setObject: [subject asQPSubjectString: @"utf-8"] forKey: @"subject"];
messageId = [mailProperties objectForKey: MAPIPropertyKey (PR_INTERNET_MESSAGE_ID_UNICODE)];
if ([messageId length])
[headers setObject: messageId forKey: @"message-id"];
date = [mailProperties objectForKey: MAPIPropertyKey (PR_CLIENT_SUBMIT_TIME)];
if (date)
{
[headers addObject: [date rfc822DateString] forKey: @"date"];
}
[headers addObject: @"1.0" forKey: @"MIME-Version"];
priority = [mailProperties objectForKey: MAPIPropertyKey (PidTagImportance)];
if ([priority intValue] == 2)
{
[headers addObject: @"1 (Highest)" forKey: @"X-Priority"];
}
else if ([priority intValue] == 1)
{
[headers removeAllObjectsForKey: @"X-Priority"];
}
else
{
[headers addObject: @"5 (Lowest)" forKey: @"X-Priority"];
}
}
static NSArray *
MakeAttachmentParts (NSDictionary *attachmentParts, BOOL withContentId)
{
NSMutableArray *attachmentMimeParts;
NSArray *keys;
MAPIStoreAttachment *attachment;
NSUInteger count, max;
NGMimeBodyPart *mimePart;
keys = [attachmentParts allKeys];
max = [keys count];
attachmentMimeParts = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
attachment = [attachmentParts
objectForKey: [keys objectAtIndex: count]];
if ([attachment hasContentId] == withContentId)
{
mimePart = [attachment asMIMEBodyPart];
if (mimePart)
[attachmentMimeParts addObject: mimePart];
}
}
return attachmentMimeParts;
}
static inline id
MakeTextPlainBody (NSDictionary *mailProperties, NSString **contentType)
{
id textPlainBody;
textPlainBody = [[mailProperties
objectForKey: MAPIPropertyKey (PR_BODY_UNICODE)]
dataUsingEncoding: NSUTF8StringEncoding];
*contentType = @"text/plain; charset=utf-8";
return textPlainBody;
}
static inline id
MakeTextHtmlBody (NSDictionary *mailProperties, NSDictionary *attachmentParts,
NSString **contentType)
{
id textHtmlBody;
NSData *htmlBody;
NSString *charset, *htmlContentType;
NSArray *parts;
NSNumber *codePage;
NGMimeBodyPart *htmlBodyPart;
NGMutableHashMap *headers;
NSUInteger count, max;
htmlBody = [mailProperties objectForKey: MAPIPropertyKey (PR_HTML)];
if (htmlBody)
{
/* charset */
codePage = [mailProperties objectForKey: MAPIPropertyKey (PR_INTERNET_CPID)];
charset = [Codepages getNameFromCodepage: codePage];
if (!charset)
charset = @"utf-8";
htmlContentType = [NSString stringWithFormat: @"text/html; charset=%@",
charset];
parts = MakeAttachmentParts (attachmentParts, YES);
max = [parts count];
if (max > 0)
{
textHtmlBody = [NGMimeMultipartBody new];
[textHtmlBody autorelease];
headers = [[NGMutableHashMap alloc] initWithCapacity: 1];
[headers setObject: htmlContentType forKey: @"content-type"];
htmlBodyPart = [NGMimeBodyPart bodyPartWithHeader: headers];
[htmlBodyPart setBody: htmlBody];
[headers release];
[textHtmlBody addBodyPart: htmlBodyPart];
for (count = 0; count < max; count++)
[textHtmlBody addBodyPart: [parts objectAtIndex: count]];
*contentType = @"multipart/related";
}
else
{
textHtmlBody = htmlBody;
*contentType = htmlContentType;
}
}
else
textHtmlBody = nil;
return textHtmlBody;
}
static inline id
MakeTextPartBody (NSDictionary *mailProperties, NSDictionary *attachmentParts,
NSString **contentType)
{
id textBody, textPlainBody, textHtmlBody;
NSString *textPlainContentType, *textHtmlContentType;
NGMutableHashMap *headers;
NGMimeBodyPart *bodyPart;
textPlainBody = MakeTextPlainBody (mailProperties, &textPlainContentType);
textHtmlBody = MakeTextHtmlBody (mailProperties, attachmentParts, &textHtmlContentType);
if (textPlainBody)
{
if (textHtmlBody)
{
textBody = [NGMimeMultipartBody new];
[textBody autorelease];
headers = [[NGMutableHashMap alloc] initWithCapacity: 1];
[headers setObject: textHtmlContentType forKey: @"content-type"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: headers];
[bodyPart setBody: textHtmlBody];
[headers release];
[textBody addBodyPart: bodyPart];
headers = [[NGMutableHashMap alloc] initWithCapacity: 1];
[headers setObject: textPlainContentType forKey: @"content-type"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: headers];
[bodyPart setBody: textPlainBody];
[headers release];
[textBody addBodyPart: bodyPart];
*contentType = @"multipart/alternative";
}
else
{
textBody = textPlainBody;
*contentType = textPlainContentType;
}
}
else
{
textBody = textHtmlBody;
*contentType = textHtmlContentType;
}
return textBody;
}
// static id
// MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts,
// NSString **contentType)
static id
MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NSString **contentType)
{
id messageBody, textBody;
NSString *textContentType;
NSArray *parts;
NGMimeBodyPart *textBodyPart;
NGMutableHashMap *headers;
NSUInteger count, max;
textBody = MakeTextPartBody (mailProperties, attachmentParts,
&textContentType);
parts = MakeAttachmentParts (attachmentParts, NO);
max = [parts count];
if (max > 0)
{
messageBody = [NGMimeMultipartBody new];
[messageBody autorelease];
if (textBody)
{
headers = [[NGMutableHashMap alloc] initWithCapacity: 1];
[headers setObject: textContentType forKey: @"content-type"];
textBodyPart = [NGMimeBodyPart bodyPartWithHeader: headers];
[textBodyPart setBody: textBody];
[headers release];
[messageBody addBodyPart: textBodyPart];
}
for (count = 0; count < max; count++)
[messageBody addBodyPart: [parts objectAtIndex: count]];
*contentType = @"multipart/mixed";
}
else
{
messageBody = textBody;
*contentType = textContentType;
}
return messageBody;
}
- (NGMimeMessage *) _generateMessageWithBcc: (BOOL) withBcc
{
NSString *contentType;
NGMimeMessage *message;
NGMutableHashMap *headers;
id messageBody;
headers = [[NGMutableHashMap alloc] initWithCapacity: 16];
FillMessageHeadersFromProperties (headers, properties, withBcc,
[[self context] connectionInfo]);
message = [[NGMimeMessage alloc] initWithHeader: headers];
[message autorelease];
[headers release];
messageBody = MakeMessageBody (properties, attachmentParts, &contentType);
if (messageBody)
{
[headers setObject: contentType forKey: @"content-type"];
[message setBody: messageBody];
}
return message;
}
- (NSData *) _generateMailDataWithBcc: (BOOL) withBcc
{
NGMimeMessage *message;
NGMimeMessageGenerator *generator;
NSData *messageData;
/* mime message generation */
generator = [NGMimeMessageGenerator new];
message = [self _generateMessageWithBcc: withBcc];
messageData = [generator generateMimeFromPart: message];
[generator release];
// [messageData writeToFile: @"/tmp/mimegen.eml" atomically: NO];
return messageData;
}
- (int) submitWithFlags: (enum SubmitFlags) flags
{
NSDictionary *recipients;
NSData *messageData;
NSMutableArray *recipientEmails;
NSArray *list;
NSString *recId, *from, *msgClass;
enum ulRecipClass type;
SOGoUser *activeUser;
SOGoDomainDefaults *dd;
NSException *error;
WOContext *woContext;
id <SOGoAuthenticator> authenticator;
msgClass = [properties objectForKey: MAPIPropertyKey (PidTagMessageClass)];
if ([msgClass isEqualToString: @"IPM.Note"]) /* we skip invitation replies */
{
/* send mail */
messageData = [self _generateMailDataWithBcc: NO];
recipientEmails = [NSMutableArray arrayWithCapacity: 32];
recipients = [properties objectForKey: @"recipients"];
for (type = MAPI_ORIG; type <= MAPI_BCC; type++)
{
recId = recTypes[type];
list = [recipients objectForKey: recId];
[recipientEmails
addObjectsFromArray: [list objectsForKey: @"email"
notFoundMarker: nil]];
}
activeUser = [[self context] activeUser];
[self logWithFormat: @"recipients: %@", recipientEmails];
dd = [activeUser domainDefaults];
from = [[activeUser allEmails] objectAtIndex: 0];
woContext = [[self userContext] woContext];
authenticator = [sogoObject authenticatorInContext: woContext];
error = [[SOGoMailer mailerWithDomainDefaults: dd]
sendMailData: messageData
toRecipients: recipientEmails
sender: from
withAuthenticator: authenticator
inContext: woContext];
if (error)
[self logWithFormat: @"an error occurred: '%@'", error];
// mapping = [self mapping];
// [mapping unregisterURLWithID: [self objectId]];
// [self setIsNew: NO];
// [properties removeAllObjects];
[(MAPIStoreMailFolder *) [self container] cleanupCaches];
}
else
[self logWithFormat: @"skipping submit of message with class '%@'",
msgClass];
return MAPISTORE_SUCCESS;
}
- (void) save: (TALLOC_CTX *) memCtx
{
NSString *folderName, *flag, *newIdString, *messageKey;
NSData *changeKey, *messageData;
NGImap4Connection *connection;
NGImap4Client *client;
SOGoMailFolder *containerFolder;
NSDictionary *result, *responseResult;
MAPIStoreMapping *mapping;
uint64_t mid;
messageData = [self _generateMailDataWithBcc: YES];
/* appending to imap folder */
containerFolder = [container sogoObject];
connection = [containerFolder imap4Connection];
client = [connection client];
folderName = [connection imap4FolderNameForURL: [containerFolder imap4URL]];
result = [client append: messageData toFolder: folderName
withFlags: [NSArray arrayWithObjects: @"seen", nil]];
if ([[result objectForKey: @"result"] boolValue])
{
/* we reregister the new message URL with the id mapper */
responseResult = [[result objectForKey: @"RawResponse"]
objectForKey: @"ResponseResult"];
flag = [responseResult objectForKey: @"flag"];
newIdString = [[flag componentsSeparatedByString: @" "]
objectAtIndex: 2];
mapping = [self mapping];
mid = [self objectId];
[mapping unregisterURLWithID: mid];
// [sogoObject setNameInContainer: ];
messageKey = [NSString stringWithFormat: @"%@.eml", newIdString];
[sogoObject setNameInContainer: messageKey];
[mapping registerURL: [self url] withID: mid];
/* synchronise the cache and update the change key with the one provided
by the client. Before doing this, lets issue a unselect/select combo
because of timing issues with Dovecot in obtaining the latest modseq.
Sometimes, Dovecot doesn't return the newly appended UID if we do
a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)" command (where
XYZ is the HIGHESTMODSEQ+1) immediately after IMAP APPEND */
[client unselect];
[client select: folderName];
[(MAPIStoreMailFolder *) container synchroniseCache];
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
if (changeKey)
[(MAPIStoreMailFolder *) container
setChangeKey: changeKey
forMessageWithKey: messageKey];
}
}
@end