sogo/OpenChange/MAPIStoreMailVolatileMessage.m
Enrique J. Hernández Blasco 916c04387b oc-mail: Return error when delivery was not successful
For example, if the SMTP is down, then the message is not sent and
an error is returned. We returned back this error code to be managed
by upper layer.
2015-12-18 12:23:49 +01:00

1191 lines
37 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/NSScanner.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
#import <NGExtensions/NGBase64Coding.h>
#import <NGExtensions/NGHashMap.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSObject+Values.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/SOGoCacheObject.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 "MAPIStoreMailVolatileMessage.h"
#undef DEBUG
#include <mapistore/mapistore.h>
#include <mapistore/mapistore_errors.h>
#include <mapistore/mapistore_nameid.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
{
NSString *contentId = [properties
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
return contentId && [contentId length] > 0;
}
- (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 && [contentId length] > 0)
[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
? [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 mapistore_message *msgData;
struct mapistore_message_recipient *recipient;
enum ulRecipClass type;
// [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: @"sAMAccountName"];
recipient->username = [username asUnicodeInMemCtx: msgData];
entryId = MAPIStoreInternalEntryId ([[self context] connectionInfo], 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
FillMessageHeadersFromSharingProperties (NGMutableHashMap *headers, NSDictionary *mailProperties)
{
/* Store the *important* properties related with a sharing object as
MIME eXtension headers. See [MS-OXSHARE] Section 2.2 for details
about the properties */
id value;
NSNumber *sharingFlavourNum = nil;
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingCapabilities)];
if (value)
[headers setObject: value
forKey: @"X-MS-Sharing-Capabilities"];
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingFlavor)];
if (value)
sharingFlavourNum = (NSNumber *)value;
else
{
value = [mailProperties objectForKey: MAPIPropertyKey (PidNameXSharingFlavor)];
if (value)
{
/* Transform the hex string to unsigned int */
NSScanner *scanner;
unsigned int sharingFlavour;
scanner = [NSScanner scannerWithString:value];
if ([scanner scanHexInt:&sharingFlavour])
sharingFlavourNum =[NSNumber numberWithUnsignedInt: sharingFlavour];
}
}
if (sharingFlavourNum)
{
if ([sharingFlavourNum unsignedIntegerValue] == 0x5100)
{
/* 0x5100 sharing flavour is not in standard but it seems to
be a denial of request + invitation message so we store
deny sharing flavour */
sharingFlavourNum = [NSNumber numberWithUnsignedInt: SHARING_DENY_REQUEST];
}
[headers setObject: sharingFlavourNum
forKey: @"X-MS-Sharing-Flavor"];
}
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingInitiatorEntryId)];
if (value)
[headers setObject: [[value stringByEncodingBase64] stringByReplacingOccurrencesOfString: @"\n"
withString: @""]
forKey: @"X-MS-Sharing-InitiatorEntryId"];
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingInitiatorName)];
if (value)
[headers setObject: value
forKey: @"X-MS-Sharing-InitiatorName"];
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingInitiatorSmtp)];
if (value)
[headers setObject: value
forKey: @"X-MS-Sharing-InitiatorSmtp"];
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingLocalType)];
if (value)
[headers setObject: value
forKey: @"X-MS-Sharing-LocalType"];
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingProviderName)];
if (value)
[headers setObject: value
forKey: @"X-MS-Sharing-ProviderName"];
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingRemoteName)];
if (value)
[headers setObject: value
forKey: @"X-MS-Sharing-RemoteName"];
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingRemoteStoreUid)];
if (value)
[headers setObject: value
forKey: @"X-MS-Sharing-RemoteStoreUid"];
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingRemoteUid)];
if (value)
[headers setObject: value
forKey: @"X-MS-Sharing-RemoteUid"];
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingResponseTime)];
if (value)
[headers setObject: value
forKey: @"X-MS-Sharing-ResponseTime"];
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingResponseType)];
if (value)
[headers setObject: value
forKey: @"X-MS-Sharing-ResponseType"];
}
static inline void
FillMessageHeadersFromProperties (NGMutableHashMap *headers,
NSDictionary *mailProperties, BOOL withBcc,
struct mapistore_connection_info *connInfo)
{
BOOL fromResolved = NO;
NSData *senderEntryId;
NSMutableString *subject;
NSString *from, *recId, *messageId, *subjectData, *recipientsStr, *msgClass;
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"];
fromResolved = YES;
}
}
if (!fromResolved)
{
TALLOC_CTX *local_mem_ctx;
local_mem_ctx = talloc_new(NULL);
if (!local_mem_ctx)
{
NSLog (@"%s: Out of memory", __PRETTY_FUNCTION__);
return;
}
NSLog (@"Message without an orig from, try to guess it from PidTagSenderEntryId");
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 (local_mem_ctx, &bin32);
if (addrBookEntryId && [[NSString stringWithGUID: &addrBookEntryId->ProviderUID]
hasSuffix: @"08002b2fe182"])
{
/* TODO: better way to distinguish local and other ones */
username = MAPIStoreSamDBUserAttribute (connInfo, @"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 (local_mem_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"];
}
}
if ([[fromRecipient allKeys] count] > 0)
{
list = MakeRecipientsList ([NSArray arrayWithObjects: fromRecipient, nil]);
if ([list count])
[headers setObjects: list forKey: @"from"];
}
}
/* Free entryId */
talloc_free(local_mem_ctx);
}
if (!recipients)
{
NSLog (@"Message without recipients."
@"Guessing recipients from PidTagOriginalDisplayTo and PidTagOriginalCc");
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];
if ([subject length] == 0)
{
subjectData = [mailProperties objectForKey: MAPIPropertyKey (PR_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"];
}
msgClass = [mailProperties objectForKey: MAPIPropertyKey (PidTagMessageClass)];
if ([msgClass isEqualToString: @"IPM.Sharing"])
{
FillMessageHeadersFromSharingProperties (headers, mailProperties);
}
}
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)
{
[message setHeader: 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
{
enum mapistore_error rc = MAPISTORE_SUCCESS;
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"] || [msgClass isEqualToString: @"IPM.Sharing"]) /* 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];
[[self userContext] activate];
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 errorWithFormat: @"an error occurred: '%@'", error];
rc = MAPISTORE_ERR_MSG_SEND;
}
// 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 rc;
}
- (void) save: (TALLOC_CTX *) memCtx
{
BOOL updatedMetadata;
NSString *folderName, *flag, *newIdString, *messageKey, *changeNumber;
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 predecessor change list
with the change key 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)
{
updatedMetadata = [(MAPIStoreMailFolder *) container updatePredecessorChangeListWith: changeKey
forMessageWithKey: messageKey];
if (!updatedMetadata)
[self warnWithFormat: @"Predecessor change list not updated with client data"];
}
/* Update version property (PR_CHANGE_KEY indeed) as it is
requested once it is saved */
changeNumber = [(MAPIStoreMailFolder *) container changeNumberForMessageUID: newIdString];
if (changeNumber)
[properties setObject: [NSNumber numberWithUnsignedLongLong: [changeNumber unsignedLongLongValue] >> 16]
forKey: @"version"];
}
}
@end