diff --git a/ChangeLog b/ChangeLog index 84a38fd61..8692462b7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,6 +8,14 @@ 2011-10-26 Wolfgang Sourdeau + * OpenChange/MAPIStoreFSMessage.m, + OpenChange/MAPIStoreMailVolatileMessage.m: new descendants of + MAPIStoreMemMessage. + + * OpenChange/MAPIStoreDraftsMessage: replaced class with + MAPIStoreMemMessage, enabling the creation of mails from any mail + folder. + * OpenChange/MAPIStoreMessage.m (-modifyRecipientsWithRows:andCount:andColumns:): take BCC recipients into account. diff --git a/OpenChange/GNUmakefile b/OpenChange/GNUmakefile index 9a62a65cd..ece0ee44e 100644 --- a/OpenChange/GNUmakefile +++ b/OpenChange/GNUmakefile @@ -51,6 +51,8 @@ $(SOGOBACKEND)_OBJC_FILES += \ MAPIStoreFolderTable.m \ MAPIStorePermissionsTable.m \ \ + MAPIStoreMemMessage.m \ + \ MAPIStoreFSBaseContext.m \ MAPIStoreFSFolder.m \ MAPIStoreFSFolderTable.m \ @@ -86,8 +88,8 @@ $(SOGOBACKEND)_OBJC_FILES += \ MAPIStoreMailAttachment.m \ MAPIStoreMailContext.m \ MAPIStoreMailFolder.m \ - MAPIStoreDraftsMessage.m \ MAPIStoreMailMessage.m \ + MAPIStoreMailVolatileMessage.m \ MAPIStoreMailMessageTable.m \ \ MAPIStoreNotesContext.m \ @@ -115,8 +117,6 @@ $(SOGOBACKEND)_OBJC_FILES += \ \ EOBitmaskQualifier.m \ EOQualifier+MAPIMem.m \ - \ - MAPIStoreMemMailMessage.m \ $(SOGOBACKEND)_RESOURCE_FILES += \ diff --git a/OpenChange/MAPIStoreContactsMessage.m b/OpenChange/MAPIStoreContactsMessage.m index 915eba161..764e1ea90 100644 --- a/OpenChange/MAPIStoreContactsMessage.m +++ b/OpenChange/MAPIStoreContactsMessage.m @@ -30,7 +30,6 @@ #import #import #import -#import #import #import "MAPIStoreContactsAttachment.h" diff --git a/OpenChange/MAPIStoreDraftsMessage.h b/OpenChange/MAPIStoreDraftsMessage.h deleted file mode 100644 index 5e446d490..000000000 --- a/OpenChange/MAPIStoreDraftsMessage.h +++ /dev/null @@ -1,36 +0,0 @@ -/* MAPIStoreDraftsMessage.h - this file is part of SOGo - * - * Copyright (C) 2011 Inverse inc - * - * Author: Wolfgang Sourdeau - * - * 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 2, 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. - */ - -#ifndef MAPISTOREDRAFTSMESSAGE_H -#define MAPISTOREDRAFTSMESSAGE_H - -#import "MAPIStoreMailMessage.h" - -@class NSMutableArray; - -@interface MAPIStoreDraftsMessage : MAPIStoreMailMessage - -- (int) submitWithFlags: (enum SubmitFlags) flags; - -@end - -#endif /* MAPISTOREDRAFTSMESSAGE_H */ diff --git a/OpenChange/MAPIStoreDraftsMessage.m b/OpenChange/MAPIStoreDraftsMessage.m deleted file mode 100644 index d4402cf46..000000000 --- a/OpenChange/MAPIStoreDraftsMessage.m +++ /dev/null @@ -1,711 +0,0 @@ -/* MAPIStoreDraftsMessage.m - this file is part of SOGo - * - * Copyright (C) 2011 Inverse inc - * - * Author: Wolfgang Sourdeau - * - * 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. - */ - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import "MAPIStoreContext.h" -#import "MAPIStoreMapping.h" -#import "MAPIStoreMIME.h" -#import "MAPIStoreTypes.h" -#import "NSData+MAPIStore.h" -#import "NSObject+MAPIStore.h" -#import "NSString+MAPIStore.h" - -#import "MAPIStoreDraftsMessage.h" - -#undef DEBUG -#include -#include - -static Class NGMailAddressK, NSArrayK, SOGoDraftObjectK; - -typedef void (*getMessageData_inMemCtx_) (MAPIStoreMessage *, SEL, - struct mapistore_message **, - TALLOC_CTX *); - -@implementation SOGoDraftObject (MAPIStoreExtension) - -- (Class) mapistoreMessageClass -{ - return [MAPIStoreDraftsMessage class]; -} - -@end - -@implementation MAPIStoreDraftsMessage - -+ (void) initialize -{ - NGMailAddressK = [NGMailAddress class]; - NSArrayK = [NSArray class]; - SOGoDraftObjectK = [SOGoDraftObject class]; -} - -- (uint64_t) objectVersion -{ - return ([sogoObject isKindOfClass: SOGoDraftObjectK] - ? ULLONG_MAX - : [super objectVersion]); -} - -- (void) _fetchHeaderData -{ - [sogoObject fetchInfo]; - ASSIGN (headerMimeType, ([sogoObject isHTML] ? @"text/html" : @"text/plain")); - ASSIGN (headerEncoding, @"8bit"); - ASSIGN (headerCharset, @"utf-8"); - headerSetup = YES; -} - -- (void) _fetchBodyData -{ - ASSIGN (bodyContent, - [[sogoObject text] dataUsingEncoding: NSUTF8StringEncoding]); - bodySetup = YES; -} - -- (void) getMessageData: (struct mapistore_message **) dataPtr - inMemCtx: (TALLOC_CTX *) memCtx -{ - NSArray *to; - NSInteger count, max, p; - NSString *username, *cn, *email; - NSData *entryId; - NSDictionary *contactInfos; - SOGoUserManager *mgr; - NSDictionary *headers; - NGMailAddress *currentAddress; - NGMailAddressParser *parser; - struct mapistore_message *msgData; - struct mapistore_message_recipient *recipient; - getMessageData_inMemCtx_ superMethod; - - if ([sogoObject isKindOfClass: SOGoDraftObjectK]) - { - /* FIXME: this is a hack designed to work-around a hierarchy issue between - SOGoMailObject and SOGoDraftObject */ - superMethod = (getMessageData_inMemCtx_) - [MAPIStoreMessage instanceMethodForSelector: _cmd]; - superMethod (self, _cmd, &msgData, memCtx); - - /* Retrieve recipients from the message */ - if (!headerSetup) - [self _fetchHeaderData]; - headers = [sogoObject headers]; - - to = [headers objectForKey: @"to"]; - max = [to count]; - - 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); - - if (max > 0) - { - mgr = [SOGoUserManager sharedUserManager]; - msgData->recipients_count = max; - msgData->recipients = talloc_array (msgData, struct mapistore_message_recipient *, max); - for (count = 0; count < max; count++) - { - msgData->recipients[count] - = talloc_zero (msgData, struct mapistore_message_recipient); - recipient = msgData->recipients[count]; - recipient->data = talloc_array (msgData, void *, msgData->columns->cValues); - memset (recipient->data, 0, msgData->columns->cValues * sizeof (void *)); - - email = nil; - cn = nil; - - parser = [NGMailAddressParser - mailAddressParserWithString: [to objectAtIndex: count]]; - currentAddress = [parser parse]; - if ([currentAddress isKindOfClass: NGMailAddressK]) - { - email = [currentAddress address]; - cn = [currentAddress displayName]; - contactInfos = [mgr contactInfosForUserWithUIDorEmail: email]; - - // PR_ACCOUNT_UNICODE - if (contactInfos) - { - username = [contactInfos objectForKey: @"c_uid"]; - recipient->username = [username asUnicodeInMemCtx: msgData]; - entryId = MAPIStoreInternalEntryId (username); - } - else - entryId = MAPIStoreExternalEntryId (cn, email); - } - else - { - entryId = nil; - [self warnWithFormat: @"address could not be parsed" - @" properly (ignored)"]; - } - recipient->type = MAPI_TO; - - /* 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] = NULL; - p++; - - // PR_RECIPIENT_ENTRYID - recipient->data[p] = [entryId asShortBinaryInMemCtx: msgData]; - p++; - } - } - *dataPtr = msgData; - } - else - [super getMessageData: dataPtr inMemCtx: memCtx]; -} - -- (int) getPrIconIndex: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - int rc; - - if ([sogoObject isKindOfClass: SOGoDraftObjectK]) - { - *data = MAPILongValue (memCtx, 0xffffffff); - rc = MAPISTORE_SUCCESS; - } - else - rc = [super getPrIconIndex: data inMemCtx: memCtx]; - - return rc; -} - -- (int) getPrImportance: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - int rc; - uint32_t v; - NSString *s; - - if ([sogoObject isKindOfClass: SOGoDraftObjectK]) - { - if (!headerSetup) - [self _fetchHeaderData]; - s = [[sogoObject headers] objectForKey: @"X-Priority"]; - v = 0x1; - - if ([s hasPrefix: @"1"]) v = 0x2; - else if ([s hasPrefix: @"2"]) v = 0x2; - else if ([s hasPrefix: @"4"]) v = 0x0; - else if ([s hasPrefix: @"5"]) v = 0x0; - - *data = MAPILongValue (memCtx, v); - - rc = MAPISTORE_SUCCESS; - } - else - rc = [super getPrImportance: data inMemCtx: memCtx]; - - return rc; -} - -- (int) getPrMessageFlags: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - unsigned int v = MSGFLAG_FROMME; - int rc; - - if ([sogoObject isKindOfClass: SOGoDraftObjectK]) - { - if ([[self attachmentKeys] count] > 0) - v |= MSGFLAG_HASATTACH; - - *data = MAPILongValue (memCtx, v); - rc = MAPISTORE_SUCCESS; - } - else - rc = [super getPrMessageFlags: data inMemCtx: memCtx]; - - return rc; -} - -- (int) getPrFlagStatus: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - return ([sogoObject isKindOfClass: SOGoDraftObjectK] - ? [self getLongZero: data inMemCtx: memCtx] - : [super getPrFlagStatus: data inMemCtx: memCtx]); -} - -- (int) getPrFollowupIcon: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - return ([sogoObject isKindOfClass: SOGoDraftObjectK] - ? [self getLongZero: data inMemCtx: memCtx] - : [super getPrFollowupIcon: data inMemCtx: memCtx]); -} - -- (int) getPrChangeKey: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - return MAPISTORE_ERR_NOT_FOUND; -} - -- (int) getPrPredecessorChangeList: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - return MAPISTORE_ERR_NOT_FOUND; -} - -- (int) getPidLidImapDeleted: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - return MAPISTORE_ERR_NOT_FOUND; -} - -- (int) getPrInternetMessageId: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - return MAPISTORE_ERR_NOT_FOUND; -} - -- (void) _saveAttachment: (NSString *) attachmentKey -{ - NSDictionary *attProperties, *metadata; - NSString *filename, *mimeType; - NSData *content; - MAPIStoreAttachment *attachment; - - attachment = [attachmentParts objectForKey: attachmentKey]; - attProperties = [attachment properties]; - filename - = [attProperties - objectForKey: MAPIPropertyKey (PR_ATTACH_LONG_FILENAME_UNICODE)]; - if (![filename length]) - { - filename - = [attProperties - objectForKey: MAPIPropertyKey (PR_ATTACH_FILENAME_UNICODE)]; - if (![filename length]) - filename = @"untitled.bin"; - } - - mimeType = [attProperties - objectForKey: MAPIPropertyKey (PR_ATTACH_MIME_TAG_UNICODE)]; - if (!mimeType) - mimeType = [[MAPIStoreMIME sharedMAPIStoreMIME] - mimeTypeForExtension: [filename pathExtension]]; - if (!mimeType) - mimeType = @"application/octet-stream"; - - content = [attProperties objectForKey: MAPIPropertyKey (PR_ATTACH_DATA_BIN)]; - if (content) - { - metadata = [NSDictionary dictionaryWithObjectsAndKeys: - filename, @"filename", - mimeType, @"mimetype", nil]; - [sogoObject saveAttachment: content withMetadata: metadata]; - } - else - [self errorWithFormat: @"no content for attachment"]; -} - -- (void) _commitProperties -{ - static NSString *recIds[] = { @"to", @"cc", @"bcc" }; - NSArray *list; - NSDictionary *recipients, *identity; - NSMutableDictionary *newHeaders; - NSString *recId, *body; - NSMutableString *subject; - NSUInteger count, max; - WOContext *woContext; - id value; - - newHeaders = [NSMutableDictionary dictionaryWithCapacity: 7]; - - /* save the recipients */ - recipients = [properties objectForKey: @"recipients"]; - if (recipients) - { - for (count = 0; count < 3; count++) - { - recId = recIds[count]; - list = [recipients objectForKey: recId]; - if ([list count] > 0) - [newHeaders setObject: [list objectsForKey: @"email" - notFoundMarker: nil] - forKey: recId]; - } - } - else - [self errorWithFormat: @"message without recipients"]; - - /* - message properties (20): - recipients: {to = ({email = "wsourdeau@inverse.ca"; fullName = "wsourdeau@inverse.ca"; }); } - 0x1000001f (PR_BODY_UNICODE): text body (GSCBufferString) - 0x0037001f (PR_SUBJECT_UNICODE): Test without (GSCBufferString) - 0x30070040 (PR_CREATION_TIME): 2010-11-24 13:45:38 -0500 (NSCalendarDate) -e) - 2010-11-24 13:45:38.715 samba[25685] 0x0e62000b (PR_URL_COMP_NAME_SET): - 0 (NSIntNumber) */ - - /* save the subject */ - subject = [NSMutableString stringWithCapacity: 128]; - value = [properties objectForKey: MAPIPropertyKey (PR_SUBJECT_UNICODE)]; - if (value) - [subject appendString: value]; - else - { - value = [properties - objectForKey: MAPIPropertyKey (PR_SUBJECT_PREFIX_UNICODE)]; - if (value) - [subject appendString: value]; - value = [properties - objectForKey: MAPIPropertyKey (PR_NORMALIZED_SUBJECT_UNICODE)]; - if (value) - [subject appendString: value]; - } - if (subject) - [newHeaders setObject: subject forKey: @"subject"]; - - /* generate a valid from */ - woContext = [[self context] woContext]; - identity = [[woContext activeUser] primaryIdentity]; - [newHeaders setObject: [identity keysWithFormat: @"%{fullName} <%{email}>"] - forKey: @"from"]; - - /* set the proper priority: - 0x00000000 == Low importance - 0x00000001 == Normal importance - 0x00000002 == High importance */ - value = [properties objectForKey: MAPIPropertyKey (PR_IMPORTANCE)]; - if (value && [value intValue] == 0x0) - { - [newHeaders setObject: @"LOW" forKey: @"priority"]; - } - else if (value && [value intValue] == 0x2) - { - [newHeaders setObject: @"HIGH" forKey: @"priority"]; - } - - /* save the newly generated headers */ - [sogoObject setHeaders: newHeaders]; - - value = [properties objectForKey: MAPIPropertyKey (PR_HTML)]; - if (value) - { - [sogoObject setIsHTML: YES]; - // TODO: encoding - body = [[NSString alloc] initWithData: value - encoding: NSUTF8StringEncoding]; - [sogoObject setText: body]; - [body release]; - } - else - { - value = [properties objectForKey: MAPIPropertyKey (PR_BODY_UNICODE)]; - if (value) - { - [sogoObject setIsHTML: NO]; - [sogoObject setText: value]; - } - } - - /* save attachments */ - max = [[self attachmentKeys] count]; - for (count = 0; count < max; count++) - [self _saveAttachment: [attachmentKeys objectAtIndex: count]]; -} - -- (int) _getAddressHeader: (void **) data - addressKey: (NSString *) key - inMemCtx: (TALLOC_CTX *) memCtx -{ - NSString *stringValue, *address; - NGMailAddress *currentAddress; - NGMailAddressParser *parser; - id to; - - if (!headerSetup) - [self _fetchHeaderData]; - - stringValue = @""; - - to = [[sogoObject headers] objectForKey: key]; - if ([to isKindOfClass: NSArrayK]) - { - if ([to count] > 0) - address = [to objectAtIndex: 0]; - else - address = @""; - } - else - address = to; - - parser = [NGMailAddressParser mailAddressParserWithString: address]; - currentAddress = [parser parse]; - if ([currentAddress isKindOfClass: NGMailAddressK]) - { - stringValue = [currentAddress address]; - if (!stringValue) - stringValue = @""; - } - *data = [stringValue asUnicodeInMemCtx: memCtx]; - - return MAPISTORE_SUCCESS; -} - -- (int) getPrSenderEmailAddress: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - return ([sogoObject isKindOfClass: SOGoDraftObjectK] - ? [self _getAddressHeader: data - addressKey: @"from" - inMemCtx: memCtx] - : [super getPrSenderEmailAddress: data inMemCtx: memCtx]); -} - -- (int) getPrSenderName: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - return MAPISTORE_ERR_NOT_FOUND; -} - -- (int) getPrSenderEntryid: (void **) data inMemCtx: (TALLOC_CTX *) memCtx -{ - return MAPISTORE_ERR_NOT_FOUND; -} - -- (int) getPrReceivedByEmailAddress: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - return ([sogoObject isKindOfClass: SOGoDraftObjectK] - ? [self _getAddressHeader: data - addressKey: @"to" - inMemCtx: memCtx] - : [super getPrReceivedByEmailAddress: data inMemCtx: memCtx]); -} - -- (int) getPrDisplayTo: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - return ([sogoObject isKindOfClass: SOGoDraftObjectK] - ? [self _getAddressHeader: data - addressKey: @"to" - inMemCtx: memCtx] - : [super getPrDisplayTo: data inMemCtx: memCtx]); -} - -- (int) getPrDisplayCc: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - return ([sogoObject isKindOfClass: SOGoDraftObjectK] - ? [self _getAddressHeader: data - addressKey: @"cc" - inMemCtx: memCtx] - : [super getPrDisplayCc: data inMemCtx: memCtx]); -} - -- (int) getPrDisplayBcc: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - return ([sogoObject isKindOfClass: SOGoDraftObjectK] - ? [self _getAddressHeader: data - addressKey: @"cc" - inMemCtx: memCtx] - : [super getPrDisplayBcc: data inMemCtx: memCtx]); -} - -- (NSArray *) attachmentKeysMatchingQualifier: (EOQualifier *) qualifier - andSortOrderings: (NSArray *) sortOrderings -{ - NSArray *keys; - - if ([sogoObject isKindOfClass: SOGoDraftObjectK]) - { - if (qualifier) - [self errorWithFormat: @"qualifier is not used for attachments"]; - if (sortOrderings) - [self errorWithFormat: @"sort orderings are not used for attachments"]; - - keys = [attachmentParts allKeys]; - } - else - keys = [super attachmentKeysMatchingQualifier: qualifier - andSortOrderings: sortOrderings]; - - return keys; -} - -- (id) lookupAttachment: (NSString *) childKey -{ - return ([sogoObject isKindOfClass: SOGoDraftObjectK] - ? [attachmentParts objectForKey: childKey] - : [super lookupAttachment: childKey]); -} - -- (void) submit -{ - NSString *msgClass; - NSException *error; - MAPIStoreMapping *mapping; - - msgClass = [properties - objectForKey: MAPIPropertyKey (PR_MESSAGE_CLASS_UNICODE)]; - if (![msgClass isEqualToString: @"IPM.Schedule.Meeting.Request"]) - { - [self logWithFormat: @"sending message"]; - [self _commitProperties]; - error = [(SOGoDraftObject *) sogoObject sendMailAndCopyToSent: NO]; - if (error) - [self errorWithFormat: @"an exception occurred: %@", error]; - } - else - [self logWithFormat: @"ignored scheduling message"]; - - mapping = [[self context] mapping]; - [mapping unregisterURLWithID: [self objectId]]; -} - -- (int) submitWithFlags: (enum SubmitFlags) flags -{ - int rc; - - if ([sogoObject isKindOfClass: SOGoDraftObjectK]) - { - [self submit]; - [self setIsNew: NO]; - [self resetProperties]; - [[self container] cleanupCaches]; - rc = MAPISTORE_SUCCESS; - } - else - { - [self errorWithFormat: @"'submit' cannot be invoked on instances of '%@'", - NSStringFromClass ([sogoObject class])]; - rc = MAPISTORE_ERROR; - } - - return rc; -} - -- (void) save -{ - NSString *msgClass; - - if ([sogoObject isKindOfClass: SOGoDraftObjectK]) - { - msgClass = [properties - objectForKey: MAPIPropertyKey (PR_MESSAGE_CLASS_UNICODE)]; - if (![msgClass isEqualToString: @"IPM.Schedule.Meeting.Request"]) - { - [self logWithFormat: @"saving message"]; - [self _commitProperties]; - [(SOGoDraftObject *) sogoObject save]; - } - else - [self logWithFormat: @"ignored scheduling message"]; - } - else - [self errorWithFormat: @"'save' cannot be invoked on instances of '%@'", - NSStringFromClass ([sogoObject class])]; -} - -- (NSString *) subject -{ - NSString *subject; - - if ([sogoObject isKindOfClass: SOGoDraftObjectK]) - { - subject = [properties objectForKey: MAPIPropertyKey (PR_SUBJECT_UNICODE)]; - if (!subject) - { - if (!headerSetup) - [self _fetchHeaderData]; - subject = [[sogoObject headers] objectForKey: @"subject"]; - } - } - else - subject = [super subject]; - - return subject; -} - -- (NSDate *) creationTime -{ - return ([sogoObject isKindOfClass: SOGoDraftObjectK] - ? (NSDate *) [properties objectForKey: MAPIPropertyKey (PR_CREATION_TIME)] - : [super creationTime]); -} - -- (NSDate *) lastModificationTime -{ - return ([sogoObject isKindOfClass: SOGoDraftObjectK] - ? (NSDate *) [properties - objectForKey: MAPIPropertyKey (PR_LAST_MODIFICATION_TIME)] - : [super lastModificationTime]); -} - -@end diff --git a/OpenChange/MAPIStoreFSMessage.h b/OpenChange/MAPIStoreFSMessage.h index d15892fc4..009b71392 100644 --- a/OpenChange/MAPIStoreFSMessage.h +++ b/OpenChange/MAPIStoreFSMessage.h @@ -23,13 +23,9 @@ #ifndef MAPISTOREFSMESSAGE_H #define MAPISTOREFSMESSAGE_H -#import "MAPIStoreMessage.h" - -@interface MAPIStoreFSMessage : MAPIStoreMessage -{ - BOOL fetchedAttachments; -} +#import "MAPIStoreMemMessage.h" +@interface MAPIStoreFSMessage : MAPIStoreMemMessage @end #endif /* MAPISTOREFSMESSAGE_H */ diff --git a/OpenChange/MAPIStoreFSMessage.m b/OpenChange/MAPIStoreFSMessage.m index cf1b49818..6d2d3a3ef 100644 --- a/OpenChange/MAPIStoreFSMessage.m +++ b/OpenChange/MAPIStoreFSMessage.m @@ -20,36 +20,24 @@ * Boston, MA 02111-1307, USA. */ -#import -#import #import -#import #import #import #import "MAPIStoreContext.h" #import "MAPIStorePropertySelectors.h" -#import "MAPIStoreTypes.h" -#import "NSData+MAPIStore.h" -#import "NSObject+MAPIStore.h" -#import "NSString+MAPIStore.h" #import "SOGoMAPIFSMessage.h" #import "MAPIStoreFSMessage.h" +#import "MAPIStoreTypes.h" +#import "NSData+MAPIStore.h" #undef DEBUG #include #include -Class NSNumberK; - @implementation MAPIStoreFSMessage -+ (void) initialize -{ - NSNumberK = [NSNumber class]; -} - + (int) getAvailableProperties: (struct SPropTagArray **) propertiesP inMemCtx: (TALLOC_CTX *) memCtx { @@ -77,145 +65,6 @@ Class NSNumberK; return MAPISTORE_SUCCESS; } -- (id) init -{ - if ((self = [super init])) - fetchedAttachments = NO; - - return self; -} - -- (uint64_t) objectVersion -{ - NSNumber *version; - - version = [[sogoObject properties] objectForKey: @"version"]; - - return (version - ? exchange_globcnt ([version unsignedLongLongValue]) - : ULLONG_MAX); -} - -- (int) getProperty: (void **) data - withTag: (enum MAPITAGS) propTag - inMemCtx: (TALLOC_CTX *) memCtx -{ - id value; - int rc; - - value = [[sogoObject properties] objectForKey: MAPIPropertyKey (propTag)]; - if (value) - rc = [value getMAPIValue: data forTag: propTag inMemCtx: memCtx]; - else - rc = [super getProperty: data withTag: propTag inMemCtx: memCtx]; - - return rc; -} - -- (int) getPrSubject: (void **) data inMemCtx: (TALLOC_CTX *) memCtx -{ - /* if we get here, it means that the properties file didn't contain a - relevant value */ - return [self getEmptyString: data inMemCtx: memCtx]; -} - -- (int) getPrMessageClass: (void **) data inMemCtx: (TALLOC_CTX *) memCtx -{ - /* if we get here, it means that the properties file didn't contain a - relevant value */ - - *data = [@"IPM.Note" asUnicodeInMemCtx: memCtx]; - - return MAPISTORE_SUCCESS; -} - -- (int) getPrChangeKey: (void **) data inMemCtx: (TALLOC_CTX *) memCtx -{ - NSData *changeKey; - int rc; - - changeKey = [[sogoObject properties] - objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)]; - if (changeKey) - { - *data = [changeKey asBinaryInMemCtx: memCtx]; - rc = MAPISTORE_SUCCESS; - } - else - rc = [super getPrChangeKey: data inMemCtx: memCtx]; - - return rc; -} - -- (int) getAvailableProperties: (struct SPropTagArray **) propertiesP - inMemCtx: (TALLOC_CTX *) memCtx -{ - NSArray *keys; - NSUInteger count, max; - NSString *key; - struct SPropTagArray *availableProps; - - keys = [[sogoObject properties] allKeys]; - max = [keys count]; - - availableProps = talloc_zero (NULL, struct SPropTagArray); - availableProps->cValues = max; - availableProps->aulPropTag = talloc_array (availableProps, enum MAPITAGS, max); - for (count = 0; count < max; count++) - { -// #if (GS_SIZEOF_LONG == 4) -// return [NSNumber numberWithUnsignedLong: propTag]; -// #elif (GS_SIZEOF_INT == 4) -// return [NSNumber numberWithUnsignedInt: propTag]; -// #else - key = [keys objectAtIndex: count]; - if ([key isKindOfClass: NSNumberK]) - { -#if (GS_SIZEOF_LONG == 4) - availableProps->aulPropTag[count] = [[keys objectAtIndex: count] unsignedLongValue]; -#elif (GS_SIZEOF_INT == 4) - availableProps->aulPropTag[count] = [[keys objectAtIndex: count] unsignedIntValue]; -#endif - } - } - - *propertiesP = availableProps; - - return MAPISTORE_SUCCESS; -} - -- (NSArray *) attachmentsKeysMatchingQualifier: (EOQualifier *) qualifier - andSortOrderings: (NSArray *) sortOrderings -{ - NSDictionary *attachments; - NSArray *keys; - NSString *key, *newKey; - NSUInteger count, max, aid; - MAPIStoreAttachment *attachment; - - if (!fetchedAttachments) - { - attachments = [[sogoObject properties] objectForKey: @"attachments"]; - keys = [attachments allKeys]; - max = [keys count]; - if (max > 0) - { - aid = [keys count]; - for (count = 0; count < max; count++) - { - key = [keys objectAtIndex: count]; - attachment = [attachments objectForKey: key]; - newKey = [NSString stringWithFormat: @"%ul", (aid + count)]; - [attachmentParts setObject: attachment forKey: newKey]; - } - } - fetchedAttachments = YES; - } - - return [super attachmentKeysMatchingQualifier: qualifier - andSortOrderings: sortOrderings]; -} - - (void) save { uint64_t newVersion; diff --git a/OpenChange/MAPIStoreMailFolder.m b/OpenChange/MAPIStoreMailFolder.m index bfce3f5e1..ace7513b7 100644 --- a/OpenChange/MAPIStoreMailFolder.m +++ b/OpenChange/MAPIStoreMailFolder.m @@ -47,7 +47,7 @@ #import "MAPIApplication.h" #import "MAPIStoreAppointmentWrapper.h" #import "MAPIStoreContext.h" -#import "MAPIStoreDraftsMessage.h" +// #import "MAPIStoreDraftsMessage.h" #import "MAPIStoreFAIMessage.h" #import "MAPIStoreMailMessageTable.h" #import "MAPIStoreMapping.h" @@ -59,7 +59,7 @@ /* Those are parts of a hack that enables creating mails to IMAP folders from Exchange properties */ #import "SOGoMAPIMemMessage.h" -#import "MAPIStoreMemMailMessage.h" +#import "MAPIStoreMailVolatileMessage.h" #import "MAPIStoreMailFolder.h" @@ -991,18 +991,17 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP) return MAPISTORE_SUCCESS; } -/* FIXME: this method makes use of the hacky MAPIStoreMemMailMessage */ - (MAPIStoreMessage *) createMessage { - MAPIStoreDraftsMessage *newMessage; + MAPIStoreMailVolatileMessage *newMessage; SOGoMAPIMemMessage *newObject; newObject = [SOGoMAPIMemMessage objectWithName: [SOGoObject globallyUniqueObjectId] inContainer: sogoObject]; newMessage - = [MAPIStoreMemMailMessage mapiStoreObjectWithSOGoObject: newObject - inContainer: self]; + = [MAPIStoreMailVolatileMessage mapiStoreObjectWithSOGoObject: newObject + inContainer: self]; return newMessage; } @@ -1145,19 +1144,6 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP) return [accountFolder draftsFolderInContext: woContext]; } -- (MAPIStoreMessage *) createMessage -{ - MAPIStoreDraftsMessage *newMessage; - SOGoDraftObject *newDraft; - - newDraft = [sogoObject newDraft]; - newMessage - = [MAPIStoreDraftsMessage mapiStoreObjectWithSOGoObject: newDraft - inContainer: self]; - - return newMessage; -} - @end // @implementation MAPIStoreDeletedItemsFolder : MAPIStoreMailFolder @@ -1182,17 +1168,4 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP) return [accountFolder draftsFolderInContext: woContext]; } -- (MAPIStoreMessage *) createMessage -{ - MAPIStoreDraftsMessage *newMessage; - SOGoDraftObject *newDraft; - - newDraft = [sogoObject newDraft]; - newMessage - = [MAPIStoreDraftsMessage mapiStoreObjectWithSOGoObject: newDraft - inContainer: self]; - - return newMessage; -} - @end diff --git a/OpenChange/MAPIStoreMemMailMessage.h b/OpenChange/MAPIStoreMailVolatileMessage.h similarity index 71% rename from OpenChange/MAPIStoreMemMailMessage.h rename to OpenChange/MAPIStoreMailVolatileMessage.h index 452959aa8..436d9ad7e 100644 --- a/OpenChange/MAPIStoreMemMailMessage.h +++ b/OpenChange/MAPIStoreMailVolatileMessage.h @@ -1,4 +1,4 @@ -/* MAPIStoreMemMailMessage.h - this file is part of SOGo +/* MAPIStoreMailVolatileMessage.h - this file is part of SOGo * * Copyright (C) 2011 Inverse inc * @@ -20,13 +20,15 @@ * Boston, MA 02111-1307, USA. */ -#ifndef MAPISTOREMEMMAILMESSAGE_H -#define MAPISTOREMEMMAILMESSAGE_H +#ifndef MAPISTOREMAILVOLATILEMESSAGE_H +#define MAPISTOREMAILVOLATILEMESSAGE_H -#import "MAPIStoreMessage.h" +#import "MAPIStoreMemMessage.h" -@interface MAPIStoreMemMailMessage : MAPIStoreMessage +@interface MAPIStoreMailVolatileMessage : MAPIStoreMemMessage + +- (int) submitWithFlags: (enum SubmitFlags) flags; @end -#endif /* MAPISTOREMEMMAILMESSAGE_H */ +#endif /* MAPISTOREMAILVOLATILEMESSAGE_H */ diff --git a/OpenChange/MAPIStoreMailVolatileMessage.m b/OpenChange/MAPIStoreMailVolatileMessage.m new file mode 100644 index 000000000..8e1c27bef --- /dev/null +++ b/OpenChange/MAPIStoreMailVolatileMessage.m @@ -0,0 +1,562 @@ +/* MAPIStoreMailVolatileMessage.m - this file is part of SOGo + * + * Copyright (C) 2011 Inverse inc + * + * Author: Wolfgang Sourdeau + * + * 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: + - proper handling of multipart/alternative and multipart/related mime + body types + - calendar invitations +*/ + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import "MAPIStoreAttachment.h" +#import "MAPIStoreContext.h" +#import "MAPIStoreMailFolder.h" +#import "MAPIStoreMIME.h" +#import "MAPIStoreMapping.h" +#import "MAPIStoreTypes.h" +#import "NSObject+MAPIStore.h" +#import "NSString+MAPIStore.h" +#import "SOGoMAPIMemMessage.h" + +#import "MAPIStoreMailVolatileMessage.h" + +#undef DEBUG +#include +#include + +static NSString *recIds[] = { @"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) + +- (NGMimeBodyPart *) asMIMEBodyPart; + +@end + +@implementation MAPIStoreAttachment (MAPIStoreMIME) + +- (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 + +/* FIXME: copied from SOGoDraftMessage... */ +- (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) + // bar, foo + 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; +} + +- (NSArray *) _attachmentBodyParts +{ + NSMutableArray *attachmentBodyParts; + NSArray *keys; + MAPIStoreAttachment *attachment; + NSUInteger count, max; + NGMimeBodyPart *mimePart; + + keys = [attachmentParts allKeys]; + max = [keys count]; + attachmentBodyParts = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) + { + attachment = [attachmentParts + objectForKey: [keys objectAtIndex: count]]; + mimePart = [attachment asMIMEBodyPart]; + if (mimePart) + [attachmentBodyParts addObject: mimePart]; + } + + return attachmentBodyParts; +} + +- (NSData *) _generateMailData +{ + NSDictionary *mailProperties; + NSMutableString *subject; + NSString *from, *recId, *messageId, *subjectData, *charset, + *mailContentType, *textContentType; + NSData *textData, *messageData; + NSArray *list, *attParts; + NSNumber *codePage; + NSCalendarDate *date; + NSDictionary *recipients; + NGMimeMessage *message; + NGMutableHashMap *map, *textMap; + NGMimeBodyPart *textBodyPart; + NGMimeMultipartBody *multiPart; + NGMimeMessageGenerator *generator; + NSUInteger count, max; + struct mapistore_connection_info *connInfo; + SOGoUser *activeUser; + + mailProperties = [sogoObject properties]; + + /* headers */ + map = [[NGMutableHashMap alloc] initWithCapacity: 16]; + + connInfo = [[self context] connectionInfo]; + activeUser + = [SOGoUser + userWithLogin: [NSString stringWithUTF8String: connInfo->username]]; + + from = [NSString stringWithFormat: @"%@ <%@>", + [activeUser cn], [[activeUser allEmails] objectAtIndex: 0]]; + [map setObject: [self _quoteSpecials: from] forKey: @"from"]; + + /* save the recipients */ + recipients = [mailProperties objectForKey: @"recipients"]; + if (recipients) + { + for (count = 0; count < 3; count++) + { + recId = recIds[count]; + list = [recipients objectForKey: recId]; + if ([list count] > 0) + [map setObjects: [list keysWithFormat: @"%{fullName} <%{email}>"] + forKey: recId]; + } + } + else + [self errorWithFormat: @"message without recipients"]; + + 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]; + [map setObject: [subject asQPSubjectString: @"utf-8"] forKey: @"subject"]; + + messageId = [mailProperties objectForKey: MAPIPropertyKey (PR_INTERNET_MESSAGE_ID_UNICODE)]; + if ([messageId length]) + [map setObject: messageId forKey: @"message-id"]; + + date = [mailProperties objectForKey: MAPIPropertyKey (PR_CLIENT_SUBMIT_TIME)]; + if (date) + [map addObject: [date rfc822DateString] forKey: @"date"]; + [map addObject: @"1.0" forKey: @"MIME-Version"]; + + message = [[[NGMimeMessage alloc] initWithHeader: map] autorelease]; + [map release]; + + textData = [mailProperties objectForKey: MAPIPropertyKey (PR_HTML)]; + if (textData) + { + /* charset */ + codePage = [mailProperties objectForKey: MAPIPropertyKey (PR_INTERNET_CPID)]; + switch ([codePage intValue]) + { + case 20127: + charset = @"us-ascii"; + break; + case 28605: + charset = @"iso-8859-15"; + break; + case 65001: + charset = @"utf-8"; + break; + case 28591: + default: + charset = @"iso-8859-1"; + } + textContentType = [NSString stringWithFormat: @"text/html; charset=%@", + charset]; + } + else + { + textContentType = @"text/plain; charset=utf-8"; + textData = [[mailProperties + objectForKey: MAPIPropertyKey (PR_BODY_UNICODE)] + dataUsingEncoding: NSUTF8StringEncoding]; + } + + attParts = [self _attachmentBodyParts]; + max = [attParts count]; + if (max > 0) + { + mailContentType = @"multipart/mixed"; + multiPart = [[NGMimeMultipartBody alloc] initWithPart: message]; + + /* text part */ + textMap = [[NGMutableHashMap alloc] initWithCapacity: 1]; + [textMap setObject: textContentType forKey: @"content-type"]; + textBodyPart = [NGMimeBodyPart bodyPartWithHeader: textMap]; + [textBodyPart setBody: textData]; + [textMap release]; + + [multiPart addBodyPart: textBodyPart]; + for (count = 0; count < max; count++) + [multiPart addBodyPart: [attParts objectAtIndex: count]]; + + [message setBody: multiPart]; + [multiPart release]; + } + else + { + mailContentType = textContentType; + [message setBody: textData]; + } + [map setObject: mailContentType forKey: @"content-type"]; + + /* mime message generation */ + generator = [NGMimeMessageGenerator new]; + messageData = [generator generateMimeFromPart: message]; + [generator release]; + [messageData writeToFile: @"/tmp/mimegen.eml" atomically: NO]; + + return messageData; +} + +- (int) submitWithFlags: (enum SubmitFlags) flags +{ + NSDictionary *mailProperties, *recipients; + NSData *message; + NSMutableData *cleanedMessage; + NSMutableArray *recipientEmails; + NSArray *list; + NSString *recId; + NSRange r1, r2; + NSUInteger count; + struct mapistore_connection_info *connInfo; + SOGoUser *activeUser; + NSString *from; + // SOGoMailFolder *sentFolder; + SOGoDomainDefaults *dd; + NSException *error; + MAPIStoreMapping *mapping; + + /* send mail */ + + message = [self _generateMailData]; + cleanedMessage = [message mutableCopy]; + r1 = [cleanedMessage rangeOfCString: "\r\n\r\n"]; + r1 = [cleanedMessage rangeOfCString: "\r\nbcc: " + options: 0 + range: NSMakeRange(0,r1.location-1)]; + if (r1.location != NSNotFound) + { + // We search for the first \r\n AFTER the Bcc: header and + // replace the whole thing with \r\n. + r2 = [cleanedMessage rangeOfCString: "\r\n" + options: 0 + range: NSMakeRange(NSMaxRange(r1)+1,[cleanedMessage length]-NSMaxRange(r1)-1)]; + [cleanedMessage replaceBytesInRange: NSMakeRange(r1.location, NSMaxRange(r2)-r1.location) + withBytes: "\r\n" + length: 2]; + } + + mailProperties = [sogoObject properties]; + recipientEmails = [NSMutableArray arrayWithCapacity: 32]; + recipients = [mailProperties objectForKey: @"recipients"]; + for (count = 0; count < 3; count++) + { + recId = recIds[count]; + list = [recipients objectForKey: recId]; + [recipientEmails + addObjectsFromArray: [list objectsForKey: @"email" + notFoundMarker: nil]]; + } + + connInfo = [[self context] connectionInfo]; + activeUser = [SOGoUser userWithLogin: [NSString stringWithUTF8String: connInfo->username]]; + + [self logWithFormat: @"recipients: %@", recipientEmails]; + dd = [activeUser domainDefaults]; + from = [[activeUser allEmails] objectAtIndex: 0]; + error = [[SOGoMailer mailerWithDomainDefaults: dd] + sendMailData: cleanedMessage + toRecipients: recipientEmails + sender: from]; + if (error) + [self logWithFormat: @"an error occurred: '%@'", error]; + + mapping = [[self context] mapping]; + [mapping unregisterURLWithID: [self objectId]]; + [self setIsNew: NO]; + [self resetProperties]; + [[self container] cleanupCaches]; + + return MAPISTORE_SUCCESS; +} + +- (void) save +{ + NSString *folderName, *flag, *newIdString; + NSData *changeKey, *messageData; + NGImap4Connection *connection; + NGImap4Client *client; + SOGoMailFolder *containerFolder; + NSDictionary *result, *responseResult; + MAPIStoreMapping *mapping; + uint64_t mid; + + messageData = [self _generateMailData]; + + /* 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]; + mid = [self objectId]; + mapping = [[self context] mapping]; + [mapping unregisterURLWithID: mid]; + [sogoObject setNameInContainer: [NSString stringWithFormat: @"%@.eml", newIdString]]; + [mapping registerURL: [self url] withID: mid]; + } + + /* synchronise the cache and update the change key with the one provided by + the client */ + [(MAPIStoreMailFolder *) container synchroniseCache]; + changeKey = [[sogoObject properties] + objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)]; + if (changeKey) + [(MAPIStoreMailFolder *) container + setChangeKey: changeKey forMessageWithKey: [self nameInContainer]]; +} + +@end diff --git a/OpenChange/MAPIStoreMemMailMessage.m b/OpenChange/MAPIStoreMemMailMessage.m deleted file mode 100644 index 9311c76b7..000000000 --- a/OpenChange/MAPIStoreMemMailMessage.m +++ /dev/null @@ -1,356 +0,0 @@ -/* MAPIStoreMemMailMessage.m - this file is part of SOGo - * - * Copyright (C) 2011 Inverse inc - * - * Author: Wolfgang Sourdeau - * - * 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. - */ - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import "MAPIStoreContext.h" -#import "MAPIStoreMailFolder.h" -#import "MAPIStoreMapping.h" -#import "MAPIStoreTypes.h" -#import "NSObject+MAPIStore.h" -#import "NSString+MAPIStore.h" -#import "SOGoMAPIMemMessage.h" - -#import "MAPIStoreMemMailMessage.h" - -#undef DEBUG -#include -#include - -Class NSNumberK; - -@implementation MAPIStoreMemMailMessage - -+ (void) initialize -{ - NSNumberK = [NSNumber class]; -} - -- (int) addPropertiesFromRow: (struct SRow *) aRow -{ - int rc; - - rc = [super addPropertiesFromRow: aRow]; - if (rc == MAPISTORE_SUCCESS) - { - [sogoObject appendProperties: properties]; - [properties removeAllObjects]; - } - - return rc; -} - -- (int) getProperty: (void **) data - withTag: (enum MAPITAGS) propTag - inMemCtx: (TALLOC_CTX *) memCtx -{ - id value; - int rc; - - value = [[sogoObject properties] objectForKey: MAPIPropertyKey (propTag)]; - if (value) - rc = [value getMAPIValue: data forTag: propTag inMemCtx: memCtx]; - else - rc = [super getProperty: data withTag: propTag inMemCtx: memCtx]; - - return rc; -} - -- (int) getPrMessageClass: (void **) data inMemCtx: (TALLOC_CTX *) memCtx -{ - *data = [@"IPM.Note" asUnicodeInMemCtx: memCtx]; - - return MAPISTORE_SUCCESS; -} - -- (int) getAvailableProperties: (struct SPropTagArray **) propertiesP - inMemCtx: (TALLOC_CTX *) memCtx -{ - NSArray *keys; - NSUInteger count, max; - NSString *key; - struct SPropTagArray *availableProps; - - keys = [[sogoObject properties] allKeys]; - max = [keys count]; - - availableProps = talloc_zero (NULL, struct SPropTagArray); - availableProps->cValues = max; - availableProps->aulPropTag = talloc_array (availableProps, enum MAPITAGS, max); - for (count = 0; count < max; count++) - { - key = [keys objectAtIndex: count]; - if ([key isKindOfClass: NSNumberK]) - { -#if (GS_SIZEOF_LONG == 4) - availableProps->aulPropTag[count] = [[keys objectAtIndex: count] unsignedLongValue]; -#elif (GS_SIZEOF_INT == 4) - availableProps->aulPropTag[count] = [[keys objectAtIndex: count] unsignedIntValue]; -#endif - } - } - - *propertiesP = availableProps; - - return MAPISTORE_SUCCESS; -} - -- (NSArray *) attachmentsKeysMatchingQualifier: (EOQualifier *) qualifier - andSortOrderings: (NSArray *) sortOrderings -{ - NSDictionary *attachments; - NSArray *keys; - NSString *key, *newKey; - NSUInteger count, max, aid; - MAPIStoreAttachment *attachment; - - attachments = [[sogoObject properties] objectForKey: @"attachments"]; - keys = [attachments allKeys]; - max = [keys count]; - if (max > 0) - { - aid = [keys count]; - for (count = 0; count < max; count++) - { - key = [keys objectAtIndex: count]; - attachment = [attachments objectForKey: key]; - newKey = [NSString stringWithFormat: @"%ul", (aid + count)]; - [attachmentParts setObject: attachment forKey: newKey]; - } - } - - return [super attachmentKeysMatchingQualifier: qualifier - andSortOrderings: sortOrderings]; -} - -/* FIXME: copied from SOGoDraftMessage... */ -- (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) - // bar, foo - 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; -} - -- (void) save -{ - static NSString *recIds[] = { @"to", @"cc", @"bcc" }; - NSDictionary *mailProperties; - NSMutableString *subject; - NSString *from, *recId, *messageId, *subjectData, *body, *folderName, *flag, - *newIdString, *charset; - NSData *changeKey, *htmlData, *messageData; - NSArray *list; - NSNumber *codePage; - NSCalendarDate *date; - NSDictionary *recipients; - NGMimeMessage *message; - NGMutableHashMap *map; - NGMimeMessageGenerator *generator; - NGImap4Connection *connection; - NGImap4Client *client; - SOGoMailFolder *containerFolder; - NSDictionary *result, *responseResult; - MAPIStoreMapping *mapping; - uint64_t mid; - NSUInteger count; - - mailProperties = [sogoObject properties]; - - /* headers */ - map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease]; - - from = [self _quoteSpecials: [mailProperties objectForKey: MAPIPropertyKey (PR_ORIGINAL_AUTHOR_NAME_UNICODE)]]; - if ([from length]) - [map setObject: from forKey: @"from"]; - - /* save the recipients */ - recipients = [mailProperties objectForKey: @"recipients"]; - if (recipients) - { - for (count = 0; count < 3; count++) - { - recId = recIds[count]; - list = [recipients objectForKey: recId]; - if ([list count] > 0) - [map setObjects: [list keysWithFormat: @"%{fullName} <%{email}>"] - forKey: recId]; - } - } - else - [self errorWithFormat: @"message without recipients"]; - - 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]; - [map setObject: [subject asQPSubjectString: @"utf-8"] forKey: @"subject"]; - - messageId = [mailProperties objectForKey: MAPIPropertyKey (PR_INTERNET_MESSAGE_ID_UNICODE)]; - if ([messageId length]) - [map setObject: messageId forKey: @"message-id"]; - - date = [mailProperties objectForKey: MAPIPropertyKey (PR_CLIENT_SUBMIT_TIME)]; - if (date) - [map addObject: [date rfc822DateString] forKey: @"date"]; - [map addObject: @"1.0" forKey: @"MIME-Version"]; - - htmlData = [mailProperties objectForKey: MAPIPropertyKey (PR_HTML)]; - if (htmlData) - { - /* charset */ - charset = @"us-ascii"; - codePage = [mailProperties objectForKey: MAPIPropertyKey (PR_INTERNET_CPID)]; - switch ([codePage intValue]) - { - case 20127: - charset = @"us-ascii"; - break; - case 28605: - charset = @"iso-8859-15"; - break; - case 65001: - charset = @"utf-8"; - break; - case 28591: - default: - charset = @"iso-8859-1"; - } - [map setObject: [NSString stringWithFormat: @"text/html; charset=%@", - charset] - forKey: @"content-type"]; - } - else - [map setObject: @"text/plain; charset=utf-8" - forKey: @"content-type"]; - - message = [[[NGMimeMessage alloc] initWithHeader: map] autorelease]; - - /* body */ - if (htmlData) - { - body = [NSString stringWithData: htmlData - usingEncodingNamed: charset]; - [message setBody: body]; - } - else - { - body = [mailProperties objectForKey: MAPIPropertyKey (PR_BODY_UNICODE)]; - if (body) - [message setBody: body]; - } - - /* mime message generation */ - generator = [NGMimeMessageGenerator new]; - messageData = [generator generateMimeFromPart: message]; - [generator release]; - - /* 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]; - mid = [self objectId]; - mapping = [[self context] mapping]; - [mapping unregisterURLWithID: mid]; - [sogoObject setNameInContainer: [NSString stringWithFormat: @"%@.eml", newIdString]]; - [mapping registerURL: [self url] withID: mid]; - } - - /* synchronise the cache and update the change key with the one provided by - the client */ - [(MAPIStoreMailFolder *) container synchroniseCache]; - changeKey = [mailProperties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)]; - if (changeKey) - [(MAPIStoreMailFolder *) container - setChangeKey: changeKey forMessageWithKey: [self nameInContainer]]; -} - -@end diff --git a/OpenChange/MAPIStoreSOGo.m b/OpenChange/MAPIStoreSOGo.m index a3a9d5edd..62dca57ce 100644 --- a/OpenChange/MAPIStoreSOGo.m +++ b/OpenChange/MAPIStoreSOGo.m @@ -36,9 +36,9 @@ #import "MAPIApplication.h" #import "MAPIStoreAttachment.h" #import "MAPIStoreContext.h" -#import "MAPIStoreDraftsMessage.h" #import "MAPIStoreFolder.h" #import "MAPIStoreMessage.h" +#import "MAPIStoreMailVolatileMessage.h" #import "MAPIStoreObject.h" #import "MAPIStoreTable.h" #import "NSObject+MAPIStore.h" @@ -740,7 +740,7 @@ sogo_message_submit (void *message_object, enum SubmitFlags flags) { struct MAPIStoreTallocWrapper *wrapper; NSAutoreleasePool *pool; - MAPIStoreDraftsMessage *message; + MAPIStoreMailVolatileMessage *message; int rc; DEBUG (5, ("[SOGo: %s:%d]\n", __FUNCTION__, __LINE__));