Instead of treating all the message either as alternative or mixed with this changeset the MIME type of the parent part is used. This allows a correct disposition of the message in the cases when nested multiparts elements are used. Also in mixed parts we convert between plain text and HTML as needed.
2020 lines
59 KiB
Objective-C
2020 lines
59 KiB
Objective-C
/* MAPIStoreMailMessage.m - this file is part of SOGo
|
|
*
|
|
* Copyright (C) 2011-2012 Inverse inc
|
|
*
|
|
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
|
* Ludovic Marcotte <lmarcotte@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.
|
|
*/
|
|
|
|
#import <Foundation/NSArray.h>
|
|
#import <Foundation/NSCalendarDate.h>
|
|
#import <Foundation/NSDictionary.h>
|
|
#import <Foundation/NSException.h>
|
|
#import <Foundation/NSValue.h>
|
|
#import <NGExtensions/NSObject+Logs.h>
|
|
#import <NGExtensions/NSObject+Values.h>
|
|
#import <NGExtensions/NSString+Encoding.h>
|
|
#import <NGImap4/NGImap4Client.h>
|
|
#import <NGImap4/NGImap4Connection.h>
|
|
#import <NGImap4/NGImap4EnvelopeAddress.h>
|
|
#import <NGMail/NGMailAddress.h>
|
|
#import <NGMail/NGMailAddressParser.h>
|
|
#import <NGMail/NGMimeMessageGenerator.h>
|
|
#import <NGCards/iCalCalendar.h>
|
|
#import <SOGo/NSArray+Utilities.h>
|
|
#import <SOGo/NSString+Utilities.h>
|
|
#import <SOGo/SOGoUserManager.h>
|
|
#import <Mailer/NSData+Mail.h>
|
|
#import <Mailer/SOGoMailBodyPart.h>
|
|
#import <Mailer/SOGoMailObject.h>
|
|
#import <Mailer/NSDictionary+Mail.h>
|
|
#import <Mailer/NSString+Mail.h>
|
|
|
|
#import "Codepages.h"
|
|
#import "NSData+MAPIStore.h"
|
|
#import "NSObject+MAPIStore.h"
|
|
#import "NSString+MAPIStore.h"
|
|
#import "MAPIStoreAppointmentWrapper.h"
|
|
#import "MAPIStoreContext.h"
|
|
#import "MAPIStoreFolder.h"
|
|
#import "MAPIStoreMailAttachment.h"
|
|
#import "MAPIStoreMailFolder.h"
|
|
#import "MAPIStoreMapping.h"
|
|
#import "MAPIStoreSamDBUtils.h"
|
|
#import "MAPIStoreSharingMessage.h"
|
|
#import "MAPIStoreTypes.h"
|
|
#import "MAPIStoreUserContext.h"
|
|
|
|
#import "MAPIStoreMailMessage.h"
|
|
|
|
#undef DEBUG
|
|
#include <stdbool.h>
|
|
#include <gen_ndr/exchange.h>
|
|
#include <mapistore/mapistore.h>
|
|
#include <mapistore/mapistore_errors.h>
|
|
|
|
#define BODY_CONTENT_TEXT 0
|
|
#define BODY_CONTENT_HTML 1
|
|
|
|
@class iCalCalendar, iCalEvent;
|
|
|
|
static Class NSExceptionK, MAPIStoreSharingMessageK;
|
|
static NSArray *acceptedMimeTypes;
|
|
|
|
@interface NSString (MAPIStoreMIME)
|
|
|
|
- (NSString *) _strippedBodyKey;
|
|
|
|
@end
|
|
|
|
@implementation NSString (MAPIStoreMIME)
|
|
|
|
- (NSString *) _strippedBodyKey
|
|
{
|
|
NSRange bodyRange;
|
|
NSString *strippedKey;
|
|
|
|
bodyRange = [self rangeOfString: @"body.peek["];
|
|
if (bodyRange.length > 0)
|
|
{
|
|
strippedKey = [self substringFromIndex: NSMaxRange (bodyRange)];
|
|
strippedKey = [strippedKey substringToIndex: [strippedKey length] - 1];
|
|
}
|
|
else
|
|
strippedKey = nil;
|
|
|
|
return strippedKey;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation SOGoMailObject (MAPIStoreExtension)
|
|
|
|
- (Class) mapistoreMessageClass
|
|
{
|
|
return [MAPIStoreMailMessage class];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation MAPIStoreMailMessage
|
|
|
|
+ (void) initialize
|
|
{
|
|
NSExceptionK = [NSException class];
|
|
MAPIStoreSharingMessageK = [MAPIStoreSharingMessage class];
|
|
acceptedMimeTypes = [[NSArray alloc] initWithObjects: @"text/calendar",
|
|
@"application/ics",
|
|
@"text/html",
|
|
@"text/plain",
|
|
nil];
|
|
}
|
|
|
|
- (id) init
|
|
{
|
|
if ((self = [super init]))
|
|
{
|
|
bodyContentKeys = nil;
|
|
bodyPartsEncodings = nil;
|
|
bodyPartsCharsets = nil;
|
|
bodyPartsMimeTypes = nil;
|
|
bodyPartsMixed = nil;
|
|
|
|
headerSetup = NO;
|
|
bodySetup = NO;
|
|
bodyContent = nil;
|
|
|
|
mailIsEvent = NO;
|
|
mailIsMeetingRequest = NO;
|
|
mailIsSharingObject = NO;
|
|
headerCharset = nil;
|
|
headerMimeType = nil;
|
|
|
|
appointmentWrapper = nil;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[bodyContentKeys release];
|
|
[bodyPartsEncodings release];
|
|
[bodyPartsCharsets release];
|
|
[bodyPartsMimeTypes release];
|
|
[bodyPartsMixed release];
|
|
|
|
[bodyContent release];
|
|
|
|
[headerMimeType release];
|
|
[headerCharset release];
|
|
[appointmentWrapper release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSString *) subject
|
|
{
|
|
return [sogoObject decodedSubject];
|
|
}
|
|
|
|
- (NSDate *) creationTime
|
|
{
|
|
return [sogoObject date];
|
|
}
|
|
|
|
- (NSDate *) lastModificationTime
|
|
{
|
|
return [sogoObject date];
|
|
}
|
|
|
|
- (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
BOOL listedProperties[65536];
|
|
NSUInteger count;
|
|
uint16_t propId;
|
|
|
|
if (mailIsSharingObject)
|
|
{
|
|
memset (listedProperties, NO, 65536 * sizeof (BOOL));
|
|
[super getAvailableProperties: propertiesP inMemCtx: memCtx];
|
|
for (count = 0; count < (*propertiesP)->cValues; count++)
|
|
{
|
|
propId = ((*propertiesP)->aulPropTag[count] >> 16) & 0xffff;
|
|
listedProperties[propId] = YES;
|
|
}
|
|
[MAPIStoreSharingMessage fillAvailableProperties: *propertiesP
|
|
withExclusions: listedProperties];
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
else
|
|
return [super getAvailableProperties: propertiesP inMemCtx: memCtx];
|
|
}
|
|
|
|
static NSComparisonResult
|
|
_compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
|
{
|
|
NSComparisonResult result;
|
|
NSArray *keys;
|
|
NSString *data1, *data2;
|
|
NSUInteger count1, count2;
|
|
|
|
keys = data;
|
|
|
|
data1 = [entry1 objectForKey: @"mimeType"];
|
|
count1 = [keys indexOfObject: data1];
|
|
data2 = [entry2 objectForKey: @"mimeType"];
|
|
count2 = [keys indexOfObject: data2];
|
|
|
|
if (count1 == count2)
|
|
{
|
|
data1 = [entry1 objectForKey: @"key"];
|
|
count1 = [data1 countOccurrencesOfString: @"."];
|
|
data2 = [entry2 objectForKey: @"key"];
|
|
count2 = [data2 countOccurrencesOfString: @"."];
|
|
if (count1 == count2)
|
|
{
|
|
data1 = [data1 _strippedBodyKey];
|
|
count1 = [data1 intValue];
|
|
data2 = [data2 _strippedBodyKey];
|
|
count2 = [data2 intValue];
|
|
if (count1 == count2)
|
|
result = NSOrderedSame;
|
|
else if (count1 < count2)
|
|
result = NSOrderedAscending;
|
|
else
|
|
result = NSOrderedDescending;
|
|
}
|
|
else if (count1 < count2)
|
|
result = NSOrderedAscending;
|
|
else
|
|
result = NSOrderedDescending;
|
|
}
|
|
else if (count1 < count2)
|
|
result = NSOrderedAscending;
|
|
else
|
|
result = NSOrderedDescending;
|
|
|
|
return result;
|
|
}
|
|
|
|
- (void) _fetchHeaderData
|
|
{
|
|
MAPIStoreSharingMessage *sharingMessage;
|
|
NSMutableArray *keys;
|
|
NSUInteger keysCount;
|
|
NSDictionary *partHeaderData, *parameters;
|
|
NSString *sharingHeader;
|
|
|
|
keys = [NSMutableArray array];
|
|
[sogoObject addRequiredKeysOfStructure: [sogoObject bodyStructure]
|
|
path: @""
|
|
toArray: keys
|
|
acceptedTypes: acceptedMimeTypes
|
|
withPeek: YES];
|
|
[keys sortUsingFunction: _compareBodyKeysByPriority context: acceptedMimeTypes];
|
|
keysCount = [keys count];
|
|
if (keysCount > 0)
|
|
{
|
|
NSUInteger i;
|
|
BOOL hasHtml = NO;
|
|
BOOL hasText = NO;
|
|
|
|
bodyContentKeys = [[NSMutableArray alloc] initWithCapacity: keysCount];
|
|
bodyPartsEncodings = [[NSMutableDictionary alloc] initWithCapacity: keysCount];
|
|
bodyPartsCharsets = [[NSMutableDictionary alloc] initWithCapacity: keysCount];
|
|
bodyPartsMimeTypes = [[NSMutableDictionary alloc] initWithCapacity: keysCount];
|
|
bodyPartsMixed = [[NSMutableDictionary alloc] initWithCapacity: keysCount];
|
|
|
|
for (i = 0; i < keysCount; i++)
|
|
{
|
|
NSDictionary *bodyStructureKey;
|
|
NSString *key;
|
|
NSString *mimeType;
|
|
BOOL mixedPart;
|
|
NSString *strippedKey;
|
|
NSString *encoding;
|
|
NSString *charset;
|
|
NSDictionary *partParameters;
|
|
NSString *multipart;
|
|
|
|
bodyStructureKey = [keys objectAtIndex: i];
|
|
key = [bodyStructureKey objectForKey: @"key"];
|
|
if (key == nil)
|
|
continue;
|
|
|
|
[bodyContentKeys addObject: key];
|
|
|
|
strippedKey = [key _strippedBodyKey];
|
|
partHeaderData = [sogoObject lookupInfoForBodyPart: strippedKey];
|
|
|
|
partParameters = [partHeaderData objectForKey: @"parameterList"];
|
|
encoding = [partHeaderData objectForKey: @"encoding"];
|
|
charset = [partParameters objectForKey: @"charset"];
|
|
mimeType = [bodyStructureKey objectForKey: @"mimeType"];
|
|
|
|
/* multipart/mixed is the default type.
|
|
multipart/alternative is the only other type of multipart supported now.
|
|
*/
|
|
multipart = [bodyStructureKey objectForKey: @"multipart"];
|
|
if ([multipart isEqualToString: @""])
|
|
{
|
|
mixedPart = NO;
|
|
}
|
|
else
|
|
{
|
|
mixedPart = !([multipart isEqualToString: @"multipart/alternative"] ||
|
|
[multipart isEqualToString: @"multipart/related"]);
|
|
}
|
|
|
|
if (encoding)
|
|
[bodyPartsEncodings setObject: encoding forKey: key];
|
|
if (charset)
|
|
[bodyPartsCharsets setObject: charset forKey: key];
|
|
if (mimeType)
|
|
{
|
|
[bodyPartsMimeTypes setObject: mimeType forKey: key];
|
|
if ([mimeType isEqualToString: @"text/plain"])
|
|
hasText = YES;
|
|
else if ([mimeType isEqualToString: @"text/html"])
|
|
hasHtml = YES;
|
|
}
|
|
[bodyPartsMixed setObject: [NSNumber numberWithBool: mixedPart] forKey: key];
|
|
|
|
if (i == 0)
|
|
{
|
|
ASSIGN (headerMimeType, mimeType);
|
|
parameters = partParameters;
|
|
}
|
|
|
|
if (charset)
|
|
{
|
|
if (headerCharset == nil)
|
|
{
|
|
ASSIGN (headerCharset, charset);
|
|
}
|
|
else if (![headerCharset isEqualToString: charset])
|
|
{
|
|
/* Because we have different charsets we will encode all in UTF-8 */
|
|
ASSIGN (headerCharset, @"utf-8");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (!hasHtml || !hasText)
|
|
{
|
|
NSArray *bodyPartsMixedKeys = [bodyPartsMixed allKeys];
|
|
for (i = 0; i < [keys count]; i++)
|
|
{
|
|
NSString *key = [bodyPartsMixedKeys objectAtIndex: i];
|
|
[bodyPartsMixed setObject: [NSNumber numberWithBool: NO] forKey: key];
|
|
}
|
|
}
|
|
|
|
|
|
if ([headerMimeType isEqualToString: @"text/calendar"]
|
|
|| [headerMimeType isEqualToString: @"application/ics"])
|
|
{
|
|
mailIsEvent = YES;
|
|
if ([[parameters objectForKey: @"method"] isEqualToString: @"REQUEST"])
|
|
mailIsMeetingRequest = YES;
|
|
}
|
|
else
|
|
{
|
|
sharingHeader = [[sogoObject mailHeaders] objectForKey: @"x-ms-sharing-localtype"];
|
|
if (sharingHeader)
|
|
{
|
|
mailIsSharingObject = YES;
|
|
/* It is difficult to subclass this in folder class, that's why
|
|
a sharing object is a proxy in a mail message */
|
|
sharingMessage = [[MAPIStoreSharingMessage alloc]
|
|
initWithMailHeaders: [sogoObject mailHeaders]
|
|
andConnectionInfo: [[self context] connectionInfo]
|
|
fromMessage: self];
|
|
[self addProxy: sharingMessage];
|
|
[sharingMessage release];
|
|
}
|
|
}
|
|
}
|
|
|
|
headerSetup = YES;
|
|
}
|
|
|
|
- (void) _fetchBodyData
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
if (!bodyContent && bodyContentKeys)
|
|
{
|
|
id result;
|
|
NSString *key;
|
|
NSEnumerator *enumerator;
|
|
NSMutableData *htmlContent;
|
|
NSMutableData *textContent;
|
|
NSStringEncoding headerEncoding;
|
|
|
|
result = [sogoObject fetchParts: bodyContentKeys];
|
|
result = [[result valueForKey: @"RawResponse"] objectForKey: @"fetch"];
|
|
|
|
htmlContent = [[NSMutableData alloc] initWithCapacity: 0];
|
|
textContent = [[NSMutableData alloc] initWithCapacity: 0];
|
|
|
|
headerEncoding = [NSString stringEncodingForEncodingNamed: headerCharset];
|
|
|
|
enumerator = [bodyContentKeys objectEnumerator];
|
|
while ((key = [enumerator nextObject]))
|
|
{
|
|
NSString *noPeekKey = [key stringByReplacingOccurrencesOfString: @"body.peek"
|
|
withString: @"body"];
|
|
|
|
NSData *content = [[result objectForKey: noPeekKey] objectForKey: @"data"];
|
|
if (content == nil)
|
|
continue;
|
|
NSString *mimeType = [bodyPartsMimeTypes objectForKey: key];
|
|
if (mimeType == nil)
|
|
continue;
|
|
NSString *contentEncoding = [bodyPartsEncodings objectForKey: key];
|
|
if (contentEncoding == nil)
|
|
contentEncoding = @"7-bit";
|
|
|
|
/* We should provide a case for each of the types in acceptedMimeTypes */
|
|
if (!mailIsEvent)
|
|
{
|
|
NSString *charset;
|
|
NSStringEncoding charsetEncoding;
|
|
NSString *stringValue;
|
|
BOOL html;
|
|
BOOL mixed = [[bodyPartsMixed objectForKey: key] boolValue];
|
|
if ([mimeType isEqualToString: @"text/html"])
|
|
{
|
|
html = YES;
|
|
}
|
|
else if ([mimeType isEqualToString: @"text/plain"])
|
|
{
|
|
html = NO;
|
|
}
|
|
else
|
|
{
|
|
[self warnWithFormat: @"Unsupported MIME type for non-event body part: %@.",
|
|
mimeType];
|
|
continue;
|
|
}
|
|
|
|
content = [content bodyDataFromEncoding: contentEncoding];
|
|
charset = [bodyPartsCharsets objectForKey: key];
|
|
|
|
stringValue = nil;
|
|
if (charset)
|
|
{
|
|
charsetEncoding = [NSString stringEncodingForEncodingNamed: charset];
|
|
if ((charsetEncoding == headerEncoding) || !headerEncoding)
|
|
{
|
|
if (html)
|
|
[htmlContent appendData: content];
|
|
else
|
|
[textContent appendData: content];
|
|
}
|
|
else
|
|
{
|
|
stringValue = [content bodyStringFromCharset: charset];
|
|
if (html)
|
|
[htmlContent appendData: [stringValue dataUsingEncoding: headerEncoding]];
|
|
else
|
|
[textContent appendData: [stringValue dataUsingEncoding: headerEncoding]];
|
|
}
|
|
|
|
if (mixed)
|
|
{
|
|
// We must add it also to the other mail representation
|
|
if (html)
|
|
{
|
|
// TODO: html conversion to text
|
|
if (stringValue && headerEncoding)
|
|
[textContent appendData: [stringValue dataUsingEncoding: headerEncoding]];
|
|
else
|
|
[textContent appendData: content];
|
|
}
|
|
else
|
|
{
|
|
if (headerEncoding)
|
|
{
|
|
if (stringValue == nil)
|
|
stringValue = [content bodyStringFromCharset: charset];
|
|
|
|
stringValue = [stringValue stringByReplacingOccurrencesOfString: @"\n"
|
|
withString: @"<br/>"];
|
|
[htmlContent appendData: [stringValue dataUsingEncoding: headerEncoding]];
|
|
}
|
|
else
|
|
{
|
|
[htmlContent appendData: content];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Without charset we cannot mangle the text, so we add as it stands */
|
|
if (html || mixed)
|
|
[htmlContent appendData: content];
|
|
if (!html || mixed)
|
|
[textContent appendData: content];
|
|
}
|
|
|
|
}
|
|
else if ([mimeType isEqualToString: @"text/calendar"] ||
|
|
[mimeType isEqualToString: @"application/ics"])
|
|
{
|
|
content = [content bodyDataFromEncoding: contentEncoding];
|
|
[textContent appendData: content];
|
|
}
|
|
else
|
|
{
|
|
[self warnWithFormat: @"Unsupported combination for event body part. MIME type: %@",
|
|
mimeType];
|
|
}
|
|
}
|
|
|
|
NSArray *newBodyContent = [[NSArray alloc] initWithObjects: textContent, htmlContent, nil];
|
|
ASSIGN (bodyContent, newBodyContent);
|
|
}
|
|
|
|
bodySetup = YES;
|
|
}
|
|
|
|
- (NSArray*) getBodyContent
|
|
{
|
|
if (!bodySetup)
|
|
[self _fetchBodyData];
|
|
return bodyContent;
|
|
}
|
|
|
|
- (MAPIStoreAppointmentWrapper *) _appointmentWrapper
|
|
{
|
|
NSData *textContent;
|
|
NSArray *events, *from;
|
|
iCalCalendar *calendar;
|
|
iCalEvent *event;
|
|
NSString *stringValue, *senderEmail;
|
|
MAPIStoreContext *context;
|
|
|
|
if (!appointmentWrapper)
|
|
{
|
|
if (!bodySetup)
|
|
[self _fetchBodyData];
|
|
|
|
textContent = [bodyContent objectAtIndex: BODY_CONTENT_TEXT];
|
|
stringValue = [textContent bodyStringFromCharset: headerCharset];
|
|
calendar = [iCalCalendar parseSingleFromSource: stringValue];
|
|
events = [calendar events];
|
|
if ([events count] > 0)
|
|
{
|
|
event = [events objectAtIndex: 0];
|
|
from = [sogoObject fromEnvelopeAddresses];
|
|
if ([from count] > 0)
|
|
senderEmail = [[from objectAtIndex: 0] email];
|
|
else
|
|
senderEmail = nil;
|
|
context = [self context];
|
|
appointmentWrapper = [MAPIStoreAppointmentWrapper
|
|
wrapperWithICalEvent: event
|
|
andUser: [context activeUser]
|
|
andSenderEmail: senderEmail
|
|
withConnectionInfo: [context connectionInfo]];
|
|
[appointmentWrapper retain];
|
|
}
|
|
}
|
|
|
|
return appointmentWrapper;
|
|
}
|
|
|
|
- (int) getPidTagChangeKey: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
int rc = MAPISTORE_SUCCESS;
|
|
NSData *changeKey;
|
|
MAPIStoreMailFolder *parentFolder;
|
|
NSString *nameInContainer;
|
|
|
|
if (isNew)
|
|
rc = MAPISTORE_ERR_NOT_FOUND;
|
|
else
|
|
{
|
|
parentFolder = (MAPIStoreMailFolder *)[self container];
|
|
nameInContainer = [self nameInContainer];
|
|
changeKey = [parentFolder changeKeyForMessageWithKey: nameInContainer];
|
|
if (!changeKey)
|
|
{
|
|
[self warnWithFormat: @"attempting to get change key"
|
|
@" by synchronising folder..."];
|
|
[(MAPIStoreMailFolder *) container synchroniseCache];
|
|
[parentFolder synchroniseCache];
|
|
changeKey = [parentFolder changeKeyForMessageWithKey: nameInContainer];
|
|
if (changeKey)
|
|
[self logWithFormat: @"got one"];
|
|
else
|
|
{
|
|
[self errorWithFormat: @"still nothing. We crash!"];
|
|
abort ();
|
|
}
|
|
}
|
|
*data = [changeKey asBinaryInMemCtx: memCtx];
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
- (int) getPidTagPredecessorChangeList: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
int rc = MAPISTORE_SUCCESS;
|
|
NSData *changeList;
|
|
|
|
if (isNew)
|
|
rc = MAPISTORE_ERR_NOT_FOUND;
|
|
else
|
|
{
|
|
changeList = [(MAPIStoreMailFolder *)[self container]
|
|
predecessorChangeListForMessageWithKey: [self nameInContainer]];
|
|
if (!changeList)
|
|
{
|
|
[self warnWithFormat: @"attempting to get predecessor change list"
|
|
@" by synchronising folder..."];
|
|
[(MAPIStoreMailFolder *) container synchroniseCache];
|
|
changeList = [(MAPIStoreMailFolder *)[self container]
|
|
predecessorChangeListForMessageWithKey: [self nameInContainer]];
|
|
if (changeList)
|
|
[self logWithFormat: @"got one"];
|
|
else
|
|
{
|
|
[self errorWithFormat: @"still nothing. We crash!"];
|
|
abort ();
|
|
}
|
|
}
|
|
*data = [changeList asBinaryInMemCtx: memCtx];
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
- (uint64_t) objectVersion
|
|
{
|
|
uint64_t version = ULLONG_MAX;
|
|
NSString *uid, *changeNumber;
|
|
BOOL synced;
|
|
|
|
uid = [(MAPIStoreMailFolder *)
|
|
container messageUIDFromMessageKey: [self nameInContainer]];
|
|
if (uid)
|
|
{
|
|
changeNumber = [(MAPIStoreMailFolder *) container
|
|
changeNumberForMessageUID: uid];
|
|
if (!changeNumber)
|
|
{
|
|
[self warnWithFormat: @"attempting to get change number"
|
|
@" by synchronising folder..."];
|
|
[(MAPIStoreMailFolder *) container synchroniseCache];
|
|
changeNumber = [(MAPIStoreMailFolder *) container
|
|
changeNumberForMessageUID: uid];
|
|
if (changeNumber)
|
|
[self logWithFormat: @"got one"];
|
|
else
|
|
{
|
|
[self warnWithFormat: @"attempting to get change number"
|
|
@" by synchronising this specific message..."];
|
|
synced = [(MAPIStoreMailFolder *) container synchroniseCacheForUID: uid];
|
|
if (synced)
|
|
{
|
|
changeNumber = [(MAPIStoreMailFolder *) container
|
|
changeNumberForMessageUID: uid];
|
|
}
|
|
else
|
|
{
|
|
[self errorWithFormat: @"still nothing. We crash!"];
|
|
abort();
|
|
}
|
|
}
|
|
}
|
|
version = [changeNumber unsignedLongLongValue] >> 16;
|
|
}
|
|
else
|
|
abort();
|
|
|
|
return version;
|
|
}
|
|
|
|
- (int) getPidTagIconIndex: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
uint32_t longValue;
|
|
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
if (mailIsEvent)
|
|
[[self _appointmentWrapper] getPidTagIconIndex: data inMemCtx: memCtx];
|
|
else
|
|
{
|
|
/* see http://msdn.microsoft.com/en-us/library/cc815472.aspx */
|
|
if ([sogoObject isNewMail])
|
|
longValue = 0xffffffff;
|
|
else if ([sogoObject replied])
|
|
longValue = 0x105;
|
|
else if ([sogoObject forwarded])
|
|
longValue = 0x106;
|
|
else if ([sogoObject read])
|
|
longValue = 0x100;
|
|
else
|
|
longValue = 0x101;
|
|
*data = MAPILongValue (memCtx, longValue);
|
|
}
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidLidResponseStatus: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
*data = MAPILongValue (memCtx, 0);
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidLidImapDeleted: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
uint32_t longValue;
|
|
|
|
if ([sogoObject deleted])
|
|
longValue = 1;
|
|
else
|
|
longValue = 0;
|
|
*data = MAPILongValue (memCtx, longValue);
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagSubjectPrefix: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSString *subject;
|
|
NSUInteger colIdx;
|
|
NSString *stringValue;
|
|
|
|
/* As specified in [MS-OXCMAIL] 2.2.3.2.6.1, if there are three
|
|
or less characters followed by a colon at the beginning of
|
|
the subject, we can assume that's the subject prefix */
|
|
subject = [self subject];
|
|
colIdx = [subject rangeOfString: @":"].location;
|
|
if (colIdx != NSNotFound && colIdx < 4)
|
|
stringValue = [NSString stringWithFormat: @"%@: ",
|
|
[subject substringToIndex: colIdx]];
|
|
else
|
|
stringValue = @"";
|
|
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagNormalizedSubject: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSString *stringValue, *subject;
|
|
NSUInteger quoteStartIdx, quoteEndIdx, colIdx;
|
|
NSRange quoteRange;
|
|
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
subject = [self subject];
|
|
if (mailIsMeetingRequest)
|
|
{
|
|
|
|
/* SOGo "spices up" the invitation/update mail's subject, but
|
|
the client uses it to name the attendee's event, so we keep
|
|
only what's inside the quotes */
|
|
quoteStartIdx = [subject rangeOfString: @"\""].location;
|
|
quoteEndIdx = [subject rangeOfString: @"\""
|
|
options: NSBackwardsSearch].location;
|
|
if (quoteStartIdx != NSNotFound
|
|
&& quoteEndIdx != NSNotFound
|
|
&& quoteStartIdx != quoteEndIdx)
|
|
{
|
|
quoteRange = NSMakeRange(quoteStartIdx + 1, quoteEndIdx - quoteStartIdx - 1);
|
|
stringValue = [subject substringWithRange: quoteRange];
|
|
}
|
|
else stringValue = subject;
|
|
}
|
|
else
|
|
{
|
|
|
|
/* As specified in [MS-OXCMAIL] 2.2.3.2.6.1, if there are three
|
|
or less characters followed by a colon at the beginning of
|
|
the subject, we can assume that's the subject prefix */
|
|
colIdx = [subject rangeOfString: @":"].location;
|
|
if (colIdx != NSNotFound && colIdx < 4)
|
|
stringValue = [[subject substringFromIndex: colIdx + 1]
|
|
stringByTrimmingLeadSpaces];
|
|
else
|
|
stringValue = subject;
|
|
}
|
|
if (!stringValue)
|
|
stringValue = @"";
|
|
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidLidFInvited: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getYes: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagMessageClass: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
if (mailIsEvent)
|
|
[[self _appointmentWrapper] getPidTagMessageClass: data
|
|
inMemCtx: memCtx];
|
|
else if (mailIsSharingObject)
|
|
*data = talloc_strdup (memCtx, "IPM.Sharing");
|
|
else
|
|
*data = talloc_strdup (memCtx, "IPM.Note");
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagReplyRequested: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsMeetingRequest
|
|
? [self getYes: data inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidTagResponseRequested: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagReplyRequested: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagLatestDeliveryTime: (void **) data // DOUBT
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagCreationTime: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagOriginalSubmitTime: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagCreationTime: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagClientSubmitTime: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagCreationTime: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagMessageDeliveryTime: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagCreationTime: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagMessageFlags: (void **) data // TODO
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSDictionary *coreInfos;
|
|
NSArray *flags;
|
|
unsigned int v = 0;
|
|
|
|
coreInfos = [sogoObject fetchCoreInfos];
|
|
flags = [coreInfos objectForKey: @"flags"];
|
|
|
|
// if ([container isKindOfClass: MAPIStoreSentItemsFolderK]
|
|
// || [container isKindOfClass: MAPIStoreDraftsFolderK])
|
|
// v |= MSGFLAG_FROMME;
|
|
if ([flags containsObject: @"seen"])
|
|
v |= MSGFLAG_READ;
|
|
if ([[self attachmentKeys]
|
|
count] > 0)
|
|
v |= MSGFLAG_HASATTACH;
|
|
|
|
*data = MAPILongValue (memCtx, v);
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagFlagStatus: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSDictionary *coreInfos;
|
|
NSArray *flags;
|
|
unsigned int v;
|
|
|
|
coreInfos = [sogoObject fetchCoreInfos];
|
|
|
|
flags = [coreInfos objectForKey: @"flags"];
|
|
if ([flags containsObject: @"flagged"])
|
|
v = 2;
|
|
else
|
|
v = 0;
|
|
|
|
*data = MAPILongValue (memCtx, v);
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagFollowupIcon: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSDictionary *coreInfos;
|
|
NSArray *flags;
|
|
unsigned int v;
|
|
|
|
coreInfos = [sogoObject fetchCoreInfos];
|
|
|
|
flags = [coreInfos objectForKey: @"flags"];
|
|
if ([flags containsObject: @"flagged"])
|
|
v = 6;
|
|
else
|
|
v = 0;
|
|
|
|
*data = MAPILongValue (memCtx, v);
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagSensitivity: (void **) data // TODO
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getLongZero: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagOriginalSensitivity: (void **) data // TODO
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagSensitivity: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagSentRepresentingAddressType: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getSMTPAddrType: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagReceivedRepresentingAddressType: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getSMTPAddrType: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagReceivedByAddressType: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getSMTPAddrType: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagSenderAddressType: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getSMTPAddrType: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) _getEmailAddressFromEmail: (NSString *) fullMail
|
|
inData: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NGMailAddress *ngAddress;
|
|
NSString *email;
|
|
|
|
if (!fullMail)
|
|
fullMail = @"";
|
|
|
|
ngAddress = [[NGMailAddressParser mailAddressParserWithString: fullMail]
|
|
parse];
|
|
if ([ngAddress isKindOfClass: [NGMailAddress class]])
|
|
email = [ngAddress address];
|
|
else
|
|
email = @"";
|
|
|
|
*data = [email asUnicodeInMemCtx: memCtx];
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) _getCNFromEmail: (NSString *) fullMail
|
|
inData: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NGMailAddress *ngAddress;
|
|
NSString *cn;
|
|
|
|
if (!fullMail)
|
|
fullMail = @"";
|
|
|
|
ngAddress = [[NGMailAddressParser mailAddressParserWithString: fullMail]
|
|
parse];
|
|
if ([ngAddress isKindOfClass: [NGMailAddress class]])
|
|
{
|
|
cn = [ngAddress displayName];
|
|
|
|
// If we don't have a displayName, we use the email address instead. This
|
|
// avoid bug #2119 - where Outlook won't display anything in the "From" field,
|
|
// nor in the recipient field if we reply to the email.
|
|
if (![cn length])
|
|
cn = [ngAddress address];
|
|
}
|
|
else
|
|
cn = @"";
|
|
|
|
*data = [cn asUnicodeInMemCtx: memCtx];
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) _getEntryIdFromEmail: (NSString *) fullMail
|
|
inData: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSString *username, *cn, *email;
|
|
SOGoUserManager *mgr;
|
|
NSDictionary *contactInfos;
|
|
NGMailAddress *ngAddress;
|
|
NSData *entryId;
|
|
int rc;
|
|
|
|
if (fullMail)
|
|
{
|
|
ngAddress = [[NGMailAddressParser mailAddressParserWithString: fullMail]
|
|
parse];
|
|
if ([ngAddress isKindOfClass: [NGMailAddress class]])
|
|
{
|
|
email = [ngAddress address];
|
|
cn = [ngAddress displayName];
|
|
}
|
|
else
|
|
{
|
|
email = fullMail;
|
|
cn = @"";
|
|
}
|
|
|
|
mgr = [SOGoUserManager sharedUserManager];
|
|
contactInfos = [mgr contactInfosForUserWithUIDorEmail: email];
|
|
if (contactInfos)
|
|
{
|
|
username = [contactInfos objectForKey: @"sAMAccountName"];
|
|
entryId = MAPIStoreInternalEntryId([[self context] connectionInfo], username);
|
|
}
|
|
else
|
|
entryId = MAPIStoreExternalEntryId (cn, email);
|
|
|
|
*data = [entryId asBinaryInMemCtx: memCtx];
|
|
|
|
rc = MAPISTORE_SUCCESS;
|
|
}
|
|
else
|
|
rc = MAPISTORE_ERR_NOT_FOUND;
|
|
|
|
return rc;
|
|
}
|
|
|
|
- (int) getPidTagSenderEmailAddress: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self _getEmailAddressFromEmail: [sogoObject from]
|
|
inData: data
|
|
inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagSenderName: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self _getCNFromEmail: [sogoObject from]
|
|
inData: data
|
|
inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagSenderEntryId: (void **) data inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self _getEntryIdFromEmail: [sogoObject from]
|
|
inData: data
|
|
inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagOriginalAuthorName: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagSenderEmailAddress: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagSentRepresentingEmailAddress: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagSenderEmailAddress: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagSentRepresentingName: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagSenderName: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagSentRepresentingEntryId: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagSenderEntryId: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagReceivedByEmailAddress: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self _getEmailAddressFromEmail: [sogoObject to]
|
|
inData: data
|
|
inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagReceivedByName: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self _getCNFromEmail: [sogoObject to]
|
|
inData: data
|
|
inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagReceivedByEntryId: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self _getEntryIdFromEmail: [sogoObject to]
|
|
inData: data
|
|
inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagReceivedRepresentingName: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagReceivedByName: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagReceivedRepresentingEmailAddress: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagReceivedByEmailAddress: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagReceivedRepresentingEntryId: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagReceivedByEntryId: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagDisplayTo: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
*data = [[sogoObject to] asUnicodeInMemCtx: memCtx];
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagOriginalDisplayTo: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagDisplayTo: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagDisplayCc: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSString *stringValue;
|
|
|
|
stringValue = [sogoObject cc];
|
|
if (!stringValue)
|
|
stringValue = @"";
|
|
|
|
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagOriginalDisplayCc: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagDisplayCc: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagDisplayBcc: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getEmptyString: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagOriginalDisplayBcc: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getPidTagDisplayBcc: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidNameContentType: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
*data = [@"message/rfc822" asUnicodeInMemCtx: memCtx];
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagImportance: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
uint32_t v;
|
|
NSString *s;
|
|
|
|
s = [[sogoObject mailHeaders] 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);
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagInternetCodepage: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSNumber *codepage;
|
|
|
|
codepage = [Codepages getCodepageFromName: headerCharset];
|
|
if (!codepage)
|
|
{
|
|
[self warnWithFormat: @"Couldn't find codepage from `%@`. "
|
|
@"Using UTF-8 by default", headerCharset];
|
|
codepage = [Codepages getCodepageFromName: @"utf-8"];
|
|
}
|
|
|
|
*data = MAPILongValue(memCtx, [codepage intValue]);
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagBody: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSData *textContent;
|
|
int rc;
|
|
|
|
if (!bodySetup)
|
|
[self _fetchBodyData];
|
|
|
|
if (!bodyContent)
|
|
{
|
|
*data = NULL;
|
|
return MAPISTORE_ERR_NOT_FOUND;
|
|
}
|
|
|
|
if (mailIsEvent)
|
|
{
|
|
rc = [[self _appointmentWrapper] getPidTagBody: data
|
|
inMemCtx: memCtx];
|
|
}
|
|
else
|
|
{
|
|
textContent = [bodyContent objectAtIndex: BODY_CONTENT_TEXT];
|
|
if ([textContent length])
|
|
{
|
|
NSString *stringValue = [textContent bodyStringFromCharset: headerCharset];
|
|
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
|
rc = MAPISTORE_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
*data = NULL;
|
|
rc = MAPISTORE_ERR_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
- (int) getPidTagHtml: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSData *htmlContent;
|
|
int rc;
|
|
|
|
if (!bodySetup)
|
|
[self _fetchBodyData];
|
|
|
|
if (!bodyContent || mailIsEvent)
|
|
{
|
|
*data = NULL;
|
|
return MAPISTORE_ERR_NOT_FOUND;
|
|
}
|
|
|
|
htmlContent = [bodyContent objectAtIndex: BODY_CONTENT_HTML] ;
|
|
|
|
if ([htmlContent length])
|
|
{
|
|
*data = [htmlContent asBinaryInMemCtx: memCtx];
|
|
rc = MAPISTORE_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
*data = NULL;
|
|
rc = MAPISTORE_ERR_NOT_FOUND;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
- (int) getPidTagRtfCompressed: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
*data = NULL;
|
|
|
|
return MAPISTORE_ERR_NOT_FOUND;
|
|
}
|
|
|
|
- (int) getPidTagRtfInSync: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getNo: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidTagInternetMessageId: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
*data = [[sogoObject messageId] asUnicodeInMemCtx: memCtx];
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagReadReceiptRequested: (void **) data // TODO
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getNo: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidLidGlobalObjectId: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidGlobalObjectId: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidCleanGlobalObjectId: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidCleanGlobalObjectId: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidServerProcessed: (void **) data inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidServerProcessed: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidServerProcessingActions: (void **) data inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidServerProcessingActions: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidTagProcessed: (void **) data inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
int rc;
|
|
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
if (mailIsEvent)
|
|
rc = [self getYes: data inMemCtx: memCtx];
|
|
else
|
|
rc = MAPISTORE_ERR_NOT_FOUND;
|
|
|
|
return rc;
|
|
}
|
|
|
|
// - (int) getPidLidServerProcessed: (void **) data
|
|
// inMemCtx: (TALLOC_CTX *) memCtx
|
|
// {
|
|
// if (!headerSetup)
|
|
// [self _fetchHeaderData];
|
|
|
|
// return (mailIsEvent
|
|
// ? [[self _appointmentWrapper] getPidLidServerProcessed: data
|
|
// inMemCtx: memCtx]
|
|
// : MAPISTORE_ERR_NOT_FOUND);
|
|
// }
|
|
|
|
- (int) getPidLidPrivate: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidPrivate: data
|
|
inMemCtx: memCtx]
|
|
: [self getNo: data inMemCtx: memCtx]);
|
|
}
|
|
|
|
- (int) getPidTagMessageEditorFormat: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
uint32_t format;
|
|
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
if ([headerMimeType isEqualToString: @"text/plain"])
|
|
format = EDITOR_FORMAT_PLAINTEXT;
|
|
else if ([headerMimeType isEqualToString: @"text/html"])
|
|
format = EDITOR_FORMAT_HTML;
|
|
else
|
|
format = 0; /* EDITOR_FORMAT_DONTKNOW */
|
|
|
|
*data = MAPILongValue (memCtx, format);
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidLidReminderSet: (void **) data // TODO
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getNo: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidLidUseTnef: (void **) data // TODO
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getNo: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidLidRemoteStatus: (void **) data // TODO
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getLongZero: data inMemCtx: memCtx];
|
|
}
|
|
|
|
- (int) getPidLidAgingDontAgeMe: (void **) data // TODO
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
return [self getYes: data inMemCtx: memCtx];
|
|
}
|
|
|
|
/* event getters */
|
|
- (int) getPidTagStartDate: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidTagStartDate: data inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidAppointmentMessageClass: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
int rc = MAPISTORE_SUCCESS;
|
|
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
if (mailIsEvent)
|
|
*data = talloc_strdup (memCtx, "IPM.Appointment");
|
|
else
|
|
rc = MAPISTORE_ERR_NOT_FOUND;
|
|
|
|
return rc;
|
|
}
|
|
|
|
- (int) getPidLidAppointmentStartWhole: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidAppointmentStartWhole: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidCommonStart: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidCommonStart: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidTagEndDate: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidTagEndDate: data inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidAppointmentEndWhole: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidAppointmentEndWhole: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidCommonEnd: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidCommonEnd: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidAppointmentDuration: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidAppointmentDuration: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidAppointmentSubType: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidAppointmentSubType: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidBusyStatus: (void **) data // TODO
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidBusyStatus: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidLocation: (void **) data // LOCATION
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidLocation: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidIsRecurring: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidIsRecurring: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidRecurring: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidRecurring: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidAppointmentRecur: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidAppointmentRecur: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidTagOwnerAppointmentId: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidTagOwnerAppointmentId: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidLidMeetingType: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
return (mailIsEvent
|
|
? [[self _appointmentWrapper] getPidLidMeetingType: data
|
|
inMemCtx: memCtx]
|
|
: MAPISTORE_ERR_NOT_FOUND);
|
|
}
|
|
|
|
- (int) getPidTagTransportMessageHeaders: (void **) data
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSDictionary *mailHeaders;
|
|
NSEnumerator *keyEnumerator;
|
|
NSMutableArray *headers;
|
|
NGMimeMessageGenerator *g;
|
|
NSString *headerKey, *fullHeader, *headerGenerated;
|
|
id headerValue;
|
|
NSData *headerData;
|
|
|
|
/* Let's encode each mail header and put them on 'headers' array */
|
|
mailHeaders = [sogoObject mailHeaders];
|
|
headers = [NSMutableArray arrayWithCapacity: [mailHeaders count]];
|
|
|
|
g = [[NGMimeMessageGenerator alloc] init];
|
|
keyEnumerator = [mailHeaders keyEnumerator];
|
|
while ((headerKey = [keyEnumerator nextObject]))
|
|
{
|
|
headerValue = [mailHeaders objectForKey: headerKey];
|
|
|
|
headerData = [g generateDataForHeaderField: headerKey value: headerValue];
|
|
headerGenerated = [[NSString alloc] initWithData: headerData encoding:NSUTF8StringEncoding];
|
|
fullHeader = [NSString stringWithFormat:@"%@: %@", headerKey, headerGenerated];
|
|
[headerGenerated release];
|
|
|
|
[headers addObject: fullHeader];
|
|
}
|
|
[g release];
|
|
|
|
*data = [[headers componentsJoinedByString:@"\n"] asUnicodeInMemCtx: memCtx];
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (void) getMessageData: (struct mapistore_message **) dataPtr
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSArray *addresses;
|
|
NSString *addressMethods[] = { @"fromEnvelopeAddresses",
|
|
@"toEnvelopeAddresses",
|
|
@"ccEnvelopeAddresses",
|
|
@"bccEnvelopeAddresses" };
|
|
enum ulRecipClass addressTypes[] = { MAPI_ORIG, MAPI_TO,
|
|
MAPI_CC, MAPI_BCC };
|
|
NSUInteger arrayCount, count, recipientStart, max, p;
|
|
NGImap4EnvelopeAddress *currentAddress;
|
|
NSString *username, *cn, *email;
|
|
NSData *entryId;
|
|
NSDictionary *contactInfos;
|
|
SOGoUserManager *mgr;
|
|
struct mapistore_message *msgData;
|
|
struct mapistore_message_recipient *recipient;
|
|
|
|
[super getMessageData: &msgData inMemCtx: memCtx];
|
|
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
if (mailIsEvent)
|
|
[[self _appointmentWrapper] fillMessageData: msgData
|
|
inMemCtx: memCtx];
|
|
else
|
|
{
|
|
mgr = [SOGoUserManager sharedUserManager];
|
|
|
|
/* Retrieve recipients from the message */
|
|
|
|
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);
|
|
|
|
msgData->recipients_count = 0;
|
|
msgData->recipients = NULL;
|
|
|
|
recipientStart = 0;
|
|
|
|
for (arrayCount = 0; arrayCount < 4; arrayCount++)
|
|
{
|
|
addresses = [sogoObject performSelector: NSSelectorFromString (addressMethods[arrayCount])];
|
|
max = [addresses count];
|
|
if (max > 0)
|
|
{
|
|
msgData->recipients_count += max;
|
|
msgData->recipients = talloc_realloc (msgData, msgData->recipients, struct mapistore_message_recipient, msgData->recipients_count);
|
|
|
|
for (count = 0; count < max; count++)
|
|
{
|
|
recipient = msgData->recipients + recipientStart;
|
|
currentAddress = [addresses objectAtIndex: count];
|
|
cn = [currentAddress personalName];
|
|
email = [currentAddress baseEMail];
|
|
if ([cn length] == 0)
|
|
cn = 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);
|
|
}
|
|
recipient->type = addressTypes[arrayCount];
|
|
|
|
/* 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++;
|
|
|
|
recipientStart++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*dataPtr = msgData;
|
|
}
|
|
|
|
- (void) _fetchAttachmentPartsInBodyInfo: (NSDictionary *) bodyInfo
|
|
withPrefix: (NSString *) keyPrefix
|
|
{
|
|
NSArray *parts;
|
|
NSUInteger count, max;
|
|
|
|
if ([[bodyInfo filename] length] > 0)
|
|
{
|
|
if ([keyPrefix length] == 0)
|
|
keyPrefix = @"0";
|
|
[attachmentParts setObject: bodyInfo
|
|
forKey: keyPrefix];
|
|
}
|
|
else
|
|
{
|
|
if ([keyPrefix length] > 0)
|
|
keyPrefix = [NSString stringWithFormat: @"%@/", keyPrefix];
|
|
parts = [bodyInfo objectForKey: @"parts"];
|
|
max = [parts count];
|
|
for (count = 0; count < max; count++)
|
|
[self _fetchAttachmentPartsInBodyInfo: [parts objectAtIndex: count]
|
|
withPrefix: [NSString stringWithFormat: @"%@%d",
|
|
keyPrefix, count + 1]];
|
|
}
|
|
}
|
|
|
|
- (NSArray *) attachmentKeysMatchingQualifier: (EOQualifier *) qualifier
|
|
andSortOrderings: (NSArray *) sortOrderings
|
|
{
|
|
[self _fetchAttachmentPartsInBodyInfo: [sogoObject bodyStructure]
|
|
withPrefix: @""];
|
|
|
|
return [super attachmentKeysMatchingQualifier: qualifier
|
|
andSortOrderings: sortOrderings];
|
|
}
|
|
|
|
- (id) lookupAttachment: (NSString *) childKey
|
|
{
|
|
MAPIStoreMailAttachment *attachment;
|
|
SOGoMailBodyPart *currentPart;
|
|
NSArray *keyParts;
|
|
NSUInteger count, max;
|
|
|
|
attachment = nil;
|
|
|
|
keyParts = [childKey componentsSeparatedByString: @"/"];
|
|
max = [keyParts count];
|
|
if (max > 0)
|
|
{
|
|
[[self userContext] activate];
|
|
|
|
currentPart = [sogoObject lookupName: [keyParts objectAtIndex: 0]
|
|
inContext: nil
|
|
acquire: NO];
|
|
if ([currentPart isKindOfClass: NSExceptionK])
|
|
currentPart = nil;
|
|
|
|
for (count = 1; currentPart && count < max; count++)
|
|
{
|
|
[parentContainersBag addObject: currentPart];
|
|
currentPart = [currentPart lookupName: [keyParts objectAtIndex: count]
|
|
inContext: nil
|
|
acquire: NO];
|
|
if ([currentPart isKindOfClass: NSExceptionK])
|
|
currentPart = nil;
|
|
}
|
|
|
|
if (currentPart)
|
|
{
|
|
attachment = [MAPIStoreMailAttachment
|
|
mapiStoreObjectInContainer: self];
|
|
[attachment setBodyPart: currentPart];
|
|
[attachment setBodyInfo: [attachmentParts objectForKey: childKey]];
|
|
[attachment setAID: [[self attachmentKeys] indexOfObject: childKey]];
|
|
}
|
|
}
|
|
|
|
return attachment;
|
|
}
|
|
|
|
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
|
{
|
|
/* TODO: notifications should probably be emitted from here */
|
|
if (flag & CLEAR_READ_FLAG)
|
|
[properties setObject: [NSNumber numberWithBool: NO] forKey: @"read_flag_set"];
|
|
else
|
|
[properties setObject: [NSNumber numberWithBool: YES] forKey: @"read_flag_set"];
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (void) setBodyContentFromRawData: (NSArray *) rawContent
|
|
{
|
|
if (!headerSetup)
|
|
[self _fetchHeaderData];
|
|
|
|
ASSIGN (bodyContent, rawContent);
|
|
bodySetup = YES;
|
|
}
|
|
|
|
- (MAPIStoreSharingMessage *) _sharingObject
|
|
{
|
|
/* Get the sharing object if available */
|
|
NSUInteger i, max;
|
|
id proxy;
|
|
|
|
max = [proxies count];
|
|
for (i = 0; i < max; i++) {
|
|
proxy = [proxies objectAtIndex: i];
|
|
if ([proxy isKindOfClass: MAPIStoreSharingMessageK])
|
|
return proxy;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void) save: (TALLOC_CTX *) memCtx
|
|
{
|
|
BOOL modified = NO;
|
|
BOOL seen, storedSeenFlag;
|
|
NSNumber *value;
|
|
NSString *imapFlag = @"\\Seen";
|
|
|
|
value = [properties objectForKey: MAPIPropertyKey (PR_FLAG_STATUS)];
|
|
if (value)
|
|
{
|
|
/* We don't handle the concept of "Follow Up" */
|
|
if ([value intValue] == 2)
|
|
[sogoObject addFlags: @"\\Flagged"];
|
|
else /* 0: unflagged, 1: follow up complete */
|
|
[sogoObject removeFlags: @"\\Flagged"];
|
|
|
|
modified = YES;
|
|
}
|
|
|
|
/* Manage seen flag on save */
|
|
value = [properties objectForKey: @"read_flag_set"];
|
|
if (value)
|
|
{
|
|
seen = [value boolValue];
|
|
storedSeenFlag = [[[sogoObject fetchCoreInfos] objectForKey: @"flags"] containsObject: @"seen"];
|
|
/* We modify the flags anyway to generate a new change number */
|
|
if (seen)
|
|
{
|
|
if (storedSeenFlag)
|
|
[sogoObject removeFlags: imapFlag];
|
|
[sogoObject addFlags: imapFlag];
|
|
}
|
|
else
|
|
{
|
|
if (!storedSeenFlag)
|
|
[sogoObject addFlags: imapFlag];
|
|
[sogoObject removeFlags: imapFlag];
|
|
}
|
|
modified = YES;
|
|
}
|
|
|
|
if (modified)
|
|
[(MAPIStoreMailFolder *)[self container] synchroniseCache];
|
|
|
|
if (mailIsSharingObject)
|
|
[[self _sharingObject] saveWithMessage: self
|
|
andSOGoObject: sogoObject];
|
|
|
|
}
|
|
|
|
@end
|