2014-01-10 20:12:53 +01:00
|
|
|
/*
|
|
|
|
|
|
|
|
Copyright (c) 2014, Inverse inc.
|
|
|
|
All rights reserved.
|
|
|
|
|
2014-01-13 17:46:32 +01:00
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
|
|
|
|
* Redistributions of source code must retain the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer in the
|
|
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
* Neither the name of the Inverse inc. nor the
|
|
|
|
names of its contributors may be used to endorse or promote products
|
|
|
|
derived from this software without specific prior written permission.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
*/
|
|
|
|
#include "SOGoMailObject+ActiveSync.h"
|
|
|
|
|
|
|
|
#import <Foundation/NSArray.h>
|
2014-02-07 16:45:09 +01:00
|
|
|
#import <Foundation/NSCalendarDate.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <Foundation/NSDictionary.h>
|
2014-02-17 14:46:05 +01:00
|
|
|
#import <Foundation/NSException.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <Foundation/NSString.h>
|
|
|
|
|
2014-01-24 17:09:37 +01:00
|
|
|
#import <NGCards/iCalCalendar.h>
|
|
|
|
#import <NGCards/iCalDateTime.h>
|
|
|
|
#import <NGCards/iCalEvent.h>
|
2014-02-07 16:45:09 +01:00
|
|
|
#import <NGCards/iCalPerson.h>
|
2014-01-24 17:09:37 +01:00
|
|
|
#import <NGCards/iCalTimeZone.h>
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <NGExtensions/NGBase64Coding.h>
|
2014-03-19 16:44:36 +01:00
|
|
|
#import <NGExtensions/NGQuotedPrintableCoding.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <NGExtensions/NSString+misc.h>
|
|
|
|
#import <NGExtensions/NSString+Encoding.h>
|
|
|
|
#import <NGImap4/NGImap4Envelope.h>
|
|
|
|
#import <NGImap4/NGImap4EnvelopeAddress.h>
|
2015-02-05 22:21:27 +01:00
|
|
|
#import <NGImap4/NSString+Imap4.h>
|
2014-01-24 17:09:37 +01:00
|
|
|
#import <NGObjWeb/WOContext+SoObjects.h>
|
2015-05-12 15:59:01 +02:00
|
|
|
#import <NGObjWeb/WOApplication.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-03-19 16:44:36 +01:00
|
|
|
#import <NGMime/NGMimeBodyPart.h>
|
|
|
|
#import <NGMime/NGMimeFileData.h>
|
|
|
|
#import <NGMime/NGMimeMultipartBody.h>
|
|
|
|
#import <NGMime/NGMimeType.h>
|
|
|
|
#import <NGMail/NGMimeMessageParser.h>
|
|
|
|
#import <NGMail/NGMimeMessage.h>
|
|
|
|
#import <NGMail/NGMimeMessageGenerator.h>
|
|
|
|
|
2015-02-05 22:21:27 +01:00
|
|
|
#import <Mailer/SOGoMailLabel.h>
|
|
|
|
|
|
|
|
#import <SOGo/SOGoUserDefaults.h>
|
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
#include "iCalTimeZone+ActiveSync.h"
|
2014-02-17 14:46:05 +01:00
|
|
|
#include "NSData+ActiveSync.h"
|
2014-01-10 20:12:53 +01:00
|
|
|
#include "NSDate+ActiveSync.h"
|
2014-02-03 16:24:33 +01:00
|
|
|
#include "NSString+ActiveSync.h"
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-02-17 22:08:29 +01:00
|
|
|
#include <Appointments/iCalEntityObject+SOGo.h>
|
|
|
|
#include <Appointments/iCalPerson+SOGo.h>
|
|
|
|
#include <Mailer/NSString+Mail.h>
|
|
|
|
#include <Mailer/SOGoMailBodyPart.h>
|
2014-01-24 17:09:37 +01:00
|
|
|
#include <SOGo/SOGoUser.h>
|
2014-10-30 14:20:21 +01:00
|
|
|
#include <SOGo/NSString+Utilities.h>
|
2014-01-24 17:09:37 +01:00
|
|
|
|
2015-05-12 15:59:01 +02:00
|
|
|
#import <Appointments/SOGoAptMailNotification.h>
|
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
typedef struct {
|
|
|
|
uint32_t dwLowDateTime;
|
|
|
|
uint32_t dwHighDateTime;
|
|
|
|
} FILETIME;
|
|
|
|
|
|
|
|
struct GlobalObjectId {
|
|
|
|
uint8_t ByteArrayID[16];
|
|
|
|
uint8_t YH;
|
|
|
|
uint8_t YL;
|
|
|
|
uint8_t Month;
|
|
|
|
uint8_t D;
|
|
|
|
FILETIME CreationTime;
|
|
|
|
uint8_t X[8];
|
|
|
|
uint32_t Size;
|
|
|
|
uint8_t* Data;
|
|
|
|
};
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
@implementation SOGoMailObject (ActiveSync)
|
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (void) _setInstanceDate: (struct GlobalObjectId *) newGlobalId
|
|
|
|
fromDate: (NSCalendarDate *) instanceDate
|
|
|
|
{
|
|
|
|
uint16_t year;
|
|
|
|
|
|
|
|
if (instanceDate)
|
|
|
|
{
|
|
|
|
//[instanceDate setTimeZone: timeZone];
|
|
|
|
year = [instanceDate yearOfCommonEra];
|
|
|
|
newGlobalId->YH = year >> 8;
|
|
|
|
newGlobalId->YL = year & 0xff;
|
|
|
|
newGlobalId->Month = [instanceDate monthOfYear];
|
|
|
|
newGlobalId->D = [instanceDate dayOfMonth];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// The GlobalObjId is documented here: http://msdn.microsoft.com/en-us/library/ee160198(v=EXCHG.80).aspx
|
|
|
|
//
|
|
|
|
- (NSData *) _computeGlobalObjectIdFromEvent: (iCalEvent *) event
|
|
|
|
{
|
2014-02-07 22:17:11 +01:00
|
|
|
NSData *binPrefix, *globalObjectId, *uidAsASCII;
|
2014-02-03 16:24:33 +01:00
|
|
|
NSString *prefix, *uid;
|
|
|
|
|
|
|
|
struct GlobalObjectId newGlobalId;
|
2014-02-07 22:17:11 +01:00
|
|
|
const char *bytes;
|
2014-02-03 16:24:33 +01:00
|
|
|
|
|
|
|
prefix = @"040000008200e00074c5b7101a82e008";
|
|
|
|
|
|
|
|
// dataPrefix is "vCal-Uid %x01 %x00 %x00 %x00"
|
|
|
|
uint8_t dataPrefix[] = { 0x76, 0x43, 0x61, 0x6c, 0x2d, 0x55, 0x69, 0x64, 0x01, 0x00, 0x00, 0x00 };
|
|
|
|
uid = [event uid];
|
|
|
|
|
|
|
|
binPrefix = [prefix convertHexStringToBytes];
|
|
|
|
[binPrefix getBytes: &newGlobalId.ByteArrayID];
|
|
|
|
[self _setInstanceDate: &newGlobalId
|
|
|
|
fromDate: [event recurrenceId]];
|
2014-02-07 22:17:11 +01:00
|
|
|
uidAsASCII = [uid dataUsingEncoding: NSASCIIStringEncoding];
|
|
|
|
bytes = [uidAsASCII bytes];
|
2014-02-03 16:24:33 +01:00
|
|
|
|
|
|
|
// 0x0c is the size of our dataPrefix
|
2014-02-07 22:17:11 +01:00
|
|
|
newGlobalId.Size = 0x0c + [uidAsASCII length];
|
2014-02-03 16:24:33 +01:00
|
|
|
newGlobalId.Data = malloc(newGlobalId.Size * sizeof(uint8_t));
|
|
|
|
memcpy(newGlobalId.Data, dataPrefix, 0x0c);
|
2014-02-07 22:17:11 +01:00
|
|
|
memcpy(newGlobalId.Data + 0x0c, bytes, newGlobalId.Size - 0x0c);
|
2014-02-03 16:24:33 +01:00
|
|
|
|
|
|
|
globalObjectId = [[NSData alloc] initWithBytes: &newGlobalId length: 40 + newGlobalId.Size*sizeof(uint8_t)];
|
|
|
|
free(newGlobalId.Data);
|
|
|
|
|
|
|
|
return [globalObjectId autorelease];
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// For debugging purposes...
|
|
|
|
//
|
2014-02-17 14:46:05 +01:00
|
|
|
- (NSString *) _uidFromGlobalObjectId: (NSData *) objectId
|
|
|
|
{
|
|
|
|
NSString *uid;
|
2014-02-03 16:24:33 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
struct GlobalObjectId *newGlobalId;
|
|
|
|
NSUInteger length;
|
|
|
|
uint8_t *bytes;
|
2014-02-03 16:24:33 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
length = [objectId length];
|
|
|
|
uid = nil;
|
2014-02-03 16:24:33 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
bytes = malloc(length*sizeof(uint8_t));
|
|
|
|
[objectId getBytes: bytes length: length];
|
2014-02-03 16:24:33 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
newGlobalId = (struct GlobalObjectId *)bytes;
|
2014-02-03 16:24:33 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
// We must take the offset (dataPrefix) into account
|
|
|
|
uid = [[NSString alloc] initWithBytes: newGlobalId->Data+12 length: newGlobalId->Size-12 encoding: NSASCIIStringEncoding];
|
|
|
|
free(bytes);
|
2014-02-03 16:24:33 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
return AUTORELEASE(uid);
|
|
|
|
}
|
2014-02-03 16:24:33 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
2014-01-24 17:09:37 +01:00
|
|
|
- (NSString *) _emailAddressesFrom: (NSArray *) enveloppeAddresses
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
|
|
|
NGImap4EnvelopeAddress *address;
|
2014-03-06 20:05:59 +01:00
|
|
|
NSString *email, *rc, *name;
|
2014-02-17 14:46:05 +01:00
|
|
|
NSMutableArray *addresses;
|
2014-01-10 20:12:53 +01:00
|
|
|
int i, max;
|
|
|
|
|
|
|
|
rc = nil;
|
|
|
|
max = [enveloppeAddresses count];
|
|
|
|
|
|
|
|
if (max > 0)
|
|
|
|
{
|
|
|
|
addresses = [NSMutableArray array];
|
|
|
|
for (i = 0; i < max; i++)
|
|
|
|
{
|
|
|
|
address = [enveloppeAddresses objectAtIndex: i];
|
2014-03-06 20:05:59 +01:00
|
|
|
name = [address personalName];
|
|
|
|
email = [NSString stringWithFormat: @"\"%@\" <%@>", (name ? name : [address baseEMail]), [address baseEMail]];
|
2014-01-24 17:09:37 +01:00
|
|
|
|
|
|
|
if (email)
|
|
|
|
[addresses addObject: email];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
rc = [addresses componentsJoinedByString: @", "];
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (NSData *) _preferredBodyDataInMultipartUsingType: (int) theType
|
2015-02-12 14:49:17 +01:00
|
|
|
nativeTypeFound: (int *) theNativeTypeFound
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2014-03-19 16:44:36 +01:00
|
|
|
NSString *encoding, *key, *plainKey, *htmlKey, *type, *subtype;
|
2014-01-10 20:12:53 +01:00
|
|
|
NSDictionary *textParts, *part;
|
|
|
|
NSEnumerator *e;
|
|
|
|
NSData *d;
|
|
|
|
|
2015-05-14 21:58:53 +02:00
|
|
|
BOOL ignore;
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
textParts = [self fetchPlainTextParts];
|
|
|
|
e = [textParts keyEnumerator];
|
|
|
|
plainKey = nil;
|
|
|
|
htmlKey = nil;
|
|
|
|
d = nil;
|
|
|
|
|
|
|
|
while ((key = [e nextObject]))
|
|
|
|
{
|
2015-05-14 21:58:53 +02:00
|
|
|
// Walk the hierarchy up and check whether parents are of type mulipart - i.e. ignore message/rfc822
|
|
|
|
if ([key countOccurrencesOfString: @"."] > 0)
|
2015-03-31 22:17:21 +02:00
|
|
|
{
|
2015-05-14 21:58:53 +02:00
|
|
|
NSMutableArray *a;
|
|
|
|
|
|
|
|
a = [[[key componentsSeparatedByString: @"."] mutableCopy] autorelease];
|
|
|
|
ignore = NO;
|
|
|
|
|
|
|
|
while ([a count] > 0)
|
|
|
|
{
|
|
|
|
[a removeLastObject];
|
|
|
|
part = [self lookupInfoForBodyPart: [a componentsJoinedByString: @"."]];
|
|
|
|
|
|
|
|
if (![[part valueForKey: @"type"] isEqualToString: @"multipart"])
|
|
|
|
ignore = YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ignore)
|
2015-03-31 22:17:21 +02:00
|
|
|
continue;
|
|
|
|
}
|
2015-03-30 15:57:27 +02:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
part = [self lookupInfoForBodyPart: key];
|
|
|
|
type = [part valueForKey: @"type"];
|
|
|
|
subtype = [part valueForKey: @"subtype"];
|
|
|
|
|
2015-02-11 14:31:32 +01:00
|
|
|
// Don't select an attachment as body
|
|
|
|
if ([[[part valueForKey: @"disposition"] valueForKey: @"type"] isEqualToString: @"attachment"])
|
|
|
|
continue;
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
if ([type isEqualToString: @"text"] && [subtype isEqualToString: @"html"])
|
|
|
|
htmlKey = key;
|
|
|
|
else if ([type isEqualToString: @"text"] && [subtype isEqualToString: @"plain"])
|
|
|
|
plainKey = key;
|
|
|
|
}
|
|
|
|
|
2014-03-19 16:44:36 +01:00
|
|
|
key = nil;
|
2015-02-12 14:49:17 +01:00
|
|
|
*theNativeTypeFound = 1;
|
2014-03-19 16:44:36 +01:00
|
|
|
|
2015-02-12 14:49:17 +01:00
|
|
|
if (theType == 2 && htmlKey)
|
|
|
|
{
|
|
|
|
key = htmlKey;
|
|
|
|
*theNativeTypeFound = 2;
|
|
|
|
}
|
|
|
|
else if (theType == 1 && plainKey)
|
2014-03-19 16:44:36 +01:00
|
|
|
key = plainKey;
|
2015-02-12 14:49:17 +01:00
|
|
|
else if (theType == 2 && plainKey)
|
|
|
|
key = plainKey;
|
|
|
|
else if (theType == 1 && htmlKey)
|
|
|
|
{
|
|
|
|
key = htmlKey;
|
|
|
|
*theNativeTypeFound = 2;
|
|
|
|
}
|
2014-03-19 16:44:36 +01:00
|
|
|
|
|
|
|
if (key)
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2014-06-26 17:01:16 +02:00
|
|
|
NSString *s, *charset;
|
|
|
|
|
2014-03-19 16:44:36 +01:00
|
|
|
d = [[self fetchPlainTextParts] objectForKey: key];
|
|
|
|
|
|
|
|
encoding = [[self lookupInfoForBodyPart: key] objectForKey: @"encoding"];
|
|
|
|
|
|
|
|
if ([encoding caseInsensitiveCompare: @"base64"] == NSOrderedSame)
|
|
|
|
d = [d dataByDecodingBase64];
|
|
|
|
else if ([encoding caseInsensitiveCompare: @"quoted-printable"] == NSOrderedSame)
|
|
|
|
d = [d dataByDecodingQuotedPrintableTransferEncoding];
|
2014-06-26 17:01:16 +02:00
|
|
|
|
|
|
|
charset = [[[self lookupInfoForBodyPart: key] objectForKey: @"parameterList"] objectForKey: @"charset"];
|
|
|
|
|
|
|
|
if (![charset length])
|
2015-03-20 13:45:42 +01:00
|
|
|
charset = @"utf-8";
|
2014-06-26 17:01:16 +02:00
|
|
|
|
|
|
|
s = [NSString stringWithData: d usingEncodingNamed: charset];
|
2015-02-12 14:49:17 +01:00
|
|
|
|
2015-03-20 13:45:42 +01:00
|
|
|
// We fallback to ISO-8859-1 string encoding
|
|
|
|
if (!s)
|
|
|
|
s = [[[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding] autorelease];
|
|
|
|
|
2015-02-12 14:49:17 +01:00
|
|
|
if (theType == 1 && *theNativeTypeFound == 2)
|
|
|
|
s = [s htmlToText];
|
|
|
|
|
2014-06-26 17:01:16 +02:00
|
|
|
d = [s dataUsingEncoding: NSUTF8StringEncoding];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
2014-03-19 16:44:36 +01:00
|
|
|
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (void) _sanitizedMIMEPart: (id) thePart
|
|
|
|
performed: (BOOL *) b
|
|
|
|
{
|
|
|
|
if ([thePart isKindOfClass: [NGMimeMultipartBody class]])
|
|
|
|
{
|
|
|
|
NGMimeBodyPart *part;
|
|
|
|
NSArray *parts;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
parts = [thePart parts];
|
|
|
|
|
|
|
|
for (i = 0; i < [parts count]; i++)
|
|
|
|
{
|
|
|
|
part = [parts objectAtIndex: i];
|
|
|
|
|
|
|
|
[self _sanitizedMIMEPart: part
|
|
|
|
performed: b];
|
|
|
|
}
|
|
|
|
}
|
2014-12-17 21:26:02 +01:00
|
|
|
else if ([thePart isKindOfClass: [NGMimeBodyPart class]] || [thePart isKindOfClass: [NGMimeMessage class]])
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2014-03-19 16:44:36 +01:00
|
|
|
NGMimeFileData *fdata;
|
|
|
|
id body;
|
|
|
|
|
|
|
|
body = [thePart body];
|
|
|
|
|
|
|
|
if ([body isKindOfClass: [NGMimeMultipartBody class]])
|
|
|
|
{
|
|
|
|
[self _sanitizedMIMEPart: body
|
|
|
|
performed: b];
|
|
|
|
}
|
2014-06-04 21:56:22 +02:00
|
|
|
else if (([body isKindOfClass: [NSData class]] || [body isKindOfClass: [NSString class]]) &&
|
2014-03-21 14:02:39 +01:00
|
|
|
[[[thePart contentType] type] isEqualToString: @"text"] &&
|
2014-03-21 14:05:52 +01:00
|
|
|
([[[thePart contentType] subType] isEqualToString: @"plain"] || [[[thePart contentType] subType] isEqualToString: @"html"]))
|
2014-03-19 16:44:36 +01:00
|
|
|
{
|
2015-03-20 13:45:42 +01:00
|
|
|
// We make sure everything is encoded in UTF-8.
|
2014-03-19 16:44:36 +01:00
|
|
|
NGMimeType *mimeType;
|
2014-06-04 21:56:22 +02:00
|
|
|
NSString *s;
|
2014-03-19 16:44:36 +01:00
|
|
|
|
2014-06-04 21:56:22 +02:00
|
|
|
if ([body isKindOfClass: [NSData class]])
|
|
|
|
{
|
|
|
|
NSString *charset;
|
2014-06-26 17:01:16 +02:00
|
|
|
|
2014-06-04 21:56:22 +02:00
|
|
|
charset = [[thePart contentType] valueOfParameter: @"charset"];
|
2014-06-26 17:01:16 +02:00
|
|
|
|
|
|
|
if (![charset length])
|
2015-03-20 13:45:42 +01:00
|
|
|
charset = @"utf-8";
|
2014-06-04 21:56:22 +02:00
|
|
|
|
2015-03-20 13:45:42 +01:00
|
|
|
s = [NSString stringWithData: body usingEncodingNamed: charset];
|
|
|
|
|
|
|
|
// We fallback to ISO-8859-1 string encoding. We avoid #3103.
|
|
|
|
if (!s)
|
2015-03-20 13:51:35 +01:00
|
|
|
s = [[[NSString alloc] initWithData: body encoding: NSISOLatin1StringEncoding] autorelease];
|
2014-06-04 21:56:22 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Handle situations when SOPE stupidly returns us a NSString
|
|
|
|
// This can happen for Content-Type: text/plain, Content-Transfer-Encoding: 8bit
|
|
|
|
s = body;
|
|
|
|
}
|
2014-03-19 16:44:36 +01:00
|
|
|
|
|
|
|
if (s)
|
|
|
|
{
|
2015-07-28 12:37:33 +02:00
|
|
|
// We sanitize the content immediately, in case we have non-UNICODE safe
|
|
|
|
// characters that would be re-encoded later in HTML entities and thus,
|
|
|
|
// ignore afterwards.
|
|
|
|
s = [s safeString];
|
|
|
|
|
2014-03-19 16:44:36 +01:00
|
|
|
body = [s dataUsingEncoding: NSUTF8StringEncoding];
|
|
|
|
}
|
|
|
|
|
|
|
|
mimeType = [NGMimeType mimeType: [[thePart contentType] type]
|
|
|
|
subType: [[thePart contentType] subType]
|
|
|
|
parameters: [NSDictionary dictionaryWithObject: @"utf-8" forKey: @"charset"]];
|
|
|
|
[thePart setHeader: mimeType forKey: @"content-type"];
|
|
|
|
|
|
|
|
fdata = [[NGMimeFileData alloc] initWithBytes: [body bytes]
|
|
|
|
length: [body length]];
|
|
|
|
|
|
|
|
[thePart setBody: fdata];
|
|
|
|
RELEASE(fdata);
|
|
|
|
*b = YES;
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
2014-03-19 16:44:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (NSData *) _sanitizedMIMEMessage
|
|
|
|
{
|
|
|
|
NGMimeMessageParser *parser;
|
|
|
|
NGMimeMessage *message;
|
|
|
|
NSData *d;
|
|
|
|
|
|
|
|
BOOL b;
|
|
|
|
|
|
|
|
d = [self content];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-03-19 16:44:36 +01:00
|
|
|
parser = [[NGMimeMessageParser alloc] init];
|
|
|
|
AUTORELEASE(parser);
|
|
|
|
|
|
|
|
message = [parser parsePartFromData: d];
|
|
|
|
b = NO;
|
|
|
|
|
|
|
|
if (message)
|
|
|
|
{
|
2014-12-17 21:26:02 +01:00
|
|
|
[self _sanitizedMIMEPart: message performed: &b];
|
2014-03-19 16:44:36 +01:00
|
|
|
|
|
|
|
if (b)
|
|
|
|
{
|
|
|
|
NGMimeMessageGenerator *generator;
|
|
|
|
|
|
|
|
generator = [[NGMimeMessageGenerator alloc] init];
|
|
|
|
AUTORELEASE(generator);
|
|
|
|
|
|
|
|
d = [generator generateMimeFromPart: message];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (NSData *) _preferredBodyDataUsingType: (int) theType
|
2014-01-14 16:42:15 +01:00
|
|
|
nativeType: (int *) theNativeType
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2014-01-14 16:42:15 +01:00
|
|
|
NSString *type, *subtype, *encoding;
|
2014-01-10 20:12:53 +01:00
|
|
|
NSData *d;
|
|
|
|
|
|
|
|
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
|
|
|
|
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
|
|
|
|
|
|
|
|
d = nil;
|
2014-01-14 16:42:15 +01:00
|
|
|
|
|
|
|
// We determine the native type
|
|
|
|
if ([type isEqualToString: @"text"] && [subtype isEqualToString: @"plain"])
|
|
|
|
*theNativeType = 1;
|
|
|
|
else if ([type isEqualToString: @"text"] && [subtype isEqualToString: @"html"])
|
|
|
|
*theNativeType = 2;
|
|
|
|
else if ([type isEqualToString: @"multipart"])
|
|
|
|
*theNativeType = 4;
|
|
|
|
|
|
|
|
// We get the right part based on the preference
|
2014-01-10 20:12:53 +01:00
|
|
|
if (theType == 1 || theType == 2)
|
|
|
|
{
|
2015-05-12 15:59:01 +02:00
|
|
|
if ([type isEqualToString: @"text"] && ![subtype isEqualToString: @"calendar"])
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2014-06-26 17:01:16 +02:00
|
|
|
NSString *s, *charset;
|
|
|
|
|
|
|
|
charset = [[[self lookupInfoForBodyPart: @""] objectForKey: @"parameterList"] objectForKey: @"charset"];
|
|
|
|
|
|
|
|
if (![charset length])
|
2015-03-20 13:45:42 +01:00
|
|
|
charset = @"utf-8";
|
2014-06-26 17:01:16 +02:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
d = [[self fetchPlainTextParts] objectForKey: @""];
|
|
|
|
|
2014-01-14 16:42:15 +01:00
|
|
|
// We check if we have base64 encoded parts. If so, we just
|
|
|
|
// un-encode them before using them
|
|
|
|
encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"];
|
|
|
|
|
|
|
|
if ([encoding caseInsensitiveCompare: @"base64"] == NSOrderedSame)
|
|
|
|
d = [d dataByDecodingBase64];
|
2014-03-19 16:44:36 +01:00
|
|
|
else if ([encoding caseInsensitiveCompare: @"quoted-printable"] == NSOrderedSame)
|
|
|
|
d = [d dataByDecodingQuotedPrintableTransferEncoding];
|
2014-01-14 16:42:15 +01:00
|
|
|
|
2014-06-26 17:01:16 +02:00
|
|
|
s = [NSString stringWithData: d usingEncodingNamed: charset];
|
2015-03-20 13:45:42 +01:00
|
|
|
|
|
|
|
// We fallback to ISO-8859-1 string encoding. We avoid #3103.
|
|
|
|
if (!s)
|
|
|
|
s = [[[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding] autorelease];
|
2014-06-26 17:01:16 +02:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
// Check if we must convert html->plain
|
|
|
|
if (theType == 1 && [subtype isEqualToString: @"html"])
|
|
|
|
{
|
|
|
|
s = [s htmlToText];
|
|
|
|
}
|
2015-02-11 14:31:32 +01:00
|
|
|
|
2014-06-26 17:01:16 +02:00
|
|
|
d = [s dataUsingEncoding: NSUTF8StringEncoding];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
else if ([type isEqualToString: @"multipart"])
|
|
|
|
{
|
2015-02-12 14:49:17 +01:00
|
|
|
d = [self _preferredBodyDataInMultipartUsingType: theType nativeTypeFound: theNativeType];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (theType == 4)
|
|
|
|
{
|
2015-02-26 22:00:03 +01:00
|
|
|
// We sanitize the content *ONLY* for Outlook clients and if the content-transfer-encoding is 8bit. Outlook has strange issues
|
2014-03-19 16:44:36 +01:00
|
|
|
// with quoted-printable/base64 encoded text parts. It just doesn't decode them.
|
2015-02-26 22:00:03 +01:00
|
|
|
encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"];
|
|
|
|
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"] || ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame))
|
2014-03-19 16:44:36 +01:00
|
|
|
d = [self _sanitizedMIMEMessage];
|
|
|
|
else
|
|
|
|
d = [self content];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (iCalCalendar *) calendarFromIMIPMessage
|
|
|
|
{
|
|
|
|
NSDictionary *part;
|
2015-05-12 15:59:01 +02:00
|
|
|
NSString *type, *subtype;
|
2014-02-03 16:24:33 +01:00
|
|
|
NSArray *parts;
|
|
|
|
int i;
|
|
|
|
|
2015-05-12 15:59:01 +02:00
|
|
|
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
|
|
|
|
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
|
|
|
|
|
|
|
|
// process mail of type text/calendar
|
|
|
|
if ([type isEqualToString: @"text"] && [subtype isEqualToString: @"calendar"])
|
|
|
|
{
|
|
|
|
iCalCalendar *calendar;
|
|
|
|
NSString *encoding;
|
|
|
|
NSData *calendarData;
|
|
|
|
|
|
|
|
encoding = [[[self bodyStructure] valueForKey: @"encoding"] lowercaseString];
|
|
|
|
calendarData = [[self fetchPlainTextParts] objectForKey: @""];
|
|
|
|
|
|
|
|
if ([encoding caseInsensitiveCompare: @"base64"] == NSOrderedSame)
|
|
|
|
calendarData = [calendarData dataByDecodingBase64];
|
|
|
|
else if ([encoding caseInsensitiveCompare: @"quoted-printable"] == NSOrderedSame)
|
|
|
|
calendarData = [calendarData dataByDecodingQuotedPrintableTransferEncoding];
|
|
|
|
|
|
|
|
NS_DURING
|
|
|
|
calendar = [iCalCalendar parseSingleFromSource: calendarData];
|
|
|
|
NS_HANDLER
|
|
|
|
calendar = nil;
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
|
|
|
return calendar;
|
|
|
|
}
|
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
// We check if we have at least 2 parts and if one of them is a text/calendar
|
|
|
|
parts = [[self bodyStructure] objectForKey: @"parts"];
|
|
|
|
|
|
|
|
if ([parts count] > 1)
|
|
|
|
{
|
|
|
|
for (i = 0; i < [parts count]; i++)
|
|
|
|
{
|
|
|
|
part = [parts objectAtIndex: i];
|
|
|
|
|
|
|
|
if ([[part objectForKey: @"type"] isEqualToString: @"text"] &&
|
|
|
|
[[part objectForKey: @"subtype"] isEqualToString: @"calendar"])
|
|
|
|
{
|
|
|
|
id bodyPart;
|
|
|
|
|
|
|
|
bodyPart = [self lookupImap4BodyPartKey: [NSString stringWithFormat: @"%d", i+1]
|
|
|
|
inContext: self->context];
|
|
|
|
|
|
|
|
if (bodyPart)
|
|
|
|
{
|
2014-02-17 14:46:05 +01:00
|
|
|
iCalCalendar *calendar;
|
2014-02-03 16:24:33 +01:00
|
|
|
NSData *calendarData;
|
|
|
|
|
|
|
|
calendarData = [bodyPart fetchBLOB];
|
2014-02-17 14:46:05 +01:00
|
|
|
calendar = nil;
|
|
|
|
|
|
|
|
NS_DURING
|
|
|
|
calendar = [iCalCalendar parseSingleFromSource: calendarData];
|
|
|
|
NS_HANDLER
|
|
|
|
calendar = nil;
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
|
|
|
return calendar;
|
2014-02-03 16:24:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
2014-02-17 16:01:44 +01:00
|
|
|
- (NSString *) activeSyncRepresentationInContext: (WOContext *) _context
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2014-02-03 16:24:33 +01:00
|
|
|
NSData *d, *globalObjId;
|
2014-04-04 22:53:58 +02:00
|
|
|
NSArray *attachmentKeys;
|
2014-01-10 20:12:53 +01:00
|
|
|
NSMutableString *s;
|
2015-03-19 21:08:33 +01:00
|
|
|
|
|
|
|
uint32_t v;
|
|
|
|
NSString *p;
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
id value;
|
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
iCalCalendar *calendar;
|
2014-02-17 22:08:29 +01:00
|
|
|
|
2014-01-14 16:42:15 +01:00
|
|
|
int preferredBodyType, nativeBodyType;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
s = [NSMutableString string];
|
|
|
|
|
|
|
|
// To - "The value of this element contains one or more e-mail addresses.
|
|
|
|
// If there are multiple e-mail addresses, they are separated by commas."
|
2014-01-24 17:09:37 +01:00
|
|
|
value = [self _emailAddressesFrom: [[self envelope] to]];
|
2014-01-10 20:12:53 +01:00
|
|
|
if (value)
|
2014-02-17 16:01:44 +01:00
|
|
|
[s appendFormat: @"<To xmlns=\"Email:\">%@</To>", [value activeSyncRepresentationInContext: context]];
|
2014-01-24 17:09:37 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
// From
|
|
|
|
value = [self _emailAddressesFrom: [[self envelope] from]];
|
2014-01-10 20:12:53 +01:00
|
|
|
if (value)
|
2014-02-17 16:01:44 +01:00
|
|
|
[s appendFormat: @"<From xmlns=\"Email:\">%@</From>", [value activeSyncRepresentationInContext: context]];
|
2014-02-17 14:46:05 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
// Subject
|
|
|
|
value = [self decodedSubject];
|
|
|
|
if (value)
|
2014-01-24 17:09:37 +01:00
|
|
|
{
|
2014-02-17 16:01:44 +01:00
|
|
|
[s appendFormat: @"<Subject xmlns=\"Email:\">%@</Subject>", [value activeSyncRepresentationInContext: context]];
|
|
|
|
[s appendFormat: @"<ThreadTopic xmlns=\"Email:\">%@</ThreadTopic>", [value activeSyncRepresentationInContext: context]];
|
2014-01-24 17:09:37 +01:00
|
|
|
}
|
2014-02-17 14:46:05 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
// DateReceived
|
|
|
|
value = [self date];
|
|
|
|
if (value)
|
2014-03-06 20:05:59 +01:00
|
|
|
[s appendFormat: @"<DateReceived xmlns=\"Email:\">%@</DateReceived>", [value activeSyncRepresentationInContext: context]];
|
2014-02-17 14:46:05 +01:00
|
|
|
|
|
|
|
// DisplayTo
|
|
|
|
[s appendFormat: @"<DisplayTo xmlns=\"Email:\">%@</DisplayTo>", [[context activeUser] login]];
|
|
|
|
|
|
|
|
// Cc - same syntax as the To field
|
|
|
|
value = [self _emailAddressesFrom: [[self envelope] cc]];
|
|
|
|
if (value)
|
2014-02-17 16:01:44 +01:00
|
|
|
[s appendFormat: @"<Cc xmlns=\"Email:\">%@</Cc>", [value activeSyncRepresentationInContext: context]];
|
2014-01-24 17:09:37 +01:00
|
|
|
|
2015-03-19 21:08:33 +01:00
|
|
|
// Importance
|
|
|
|
v = 0x1;
|
2015-03-31 22:13:52 +02:00
|
|
|
value = [[self mailHeaders] objectForKey: @"x-priority"];
|
|
|
|
if ([value isKindOfClass: [NSArray class]])
|
|
|
|
p = [value lastObject];
|
|
|
|
else
|
|
|
|
p = value;
|
2015-03-19 21:08:33 +01:00
|
|
|
|
|
|
|
if (p)
|
|
|
|
{
|
|
|
|
if ([p hasPrefix: @"1"]) v = 0x2;
|
|
|
|
else if ([p hasPrefix: @"2"]) v = 0x2;
|
|
|
|
else if ([p hasPrefix: @"4"]) v = 0x0;
|
|
|
|
else if ([p hasPrefix: @"5"]) v = 0x0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-03-31 22:13:52 +02:00
|
|
|
value = [[self mailHeaders] objectForKey: @"importance"];
|
|
|
|
if ([value isKindOfClass: [NSArray class]])
|
|
|
|
p = [value lastObject];
|
|
|
|
else
|
|
|
|
p = value;
|
|
|
|
|
2015-03-19 21:08:33 +01:00
|
|
|
if ([p hasPrefix: @"High"]) v = 0x2;
|
|
|
|
else if ([p hasPrefix: @"Low"]) v = 0x0;
|
|
|
|
}
|
|
|
|
|
|
|
|
[s appendFormat: @"<Importance xmlns=\"Email:\">%d</Importance>", v];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
// Read
|
|
|
|
[s appendFormat: @"<Read xmlns=\"Email:\">%d</Read>", ([self read] ? 1 : 0)];
|
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
// We handle MeetingRequest
|
|
|
|
calendar = [self calendarFromIMIPMessage];
|
2014-02-17 22:08:29 +01:00
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
if (calendar)
|
|
|
|
{
|
2014-02-17 14:46:05 +01:00
|
|
|
NSString *method, *className;
|
|
|
|
iCalPerson *attendee;
|
2014-02-03 16:24:33 +01:00
|
|
|
iCalTimeZone *tz;
|
|
|
|
iCalEvent *event;
|
2014-02-17 14:46:05 +01:00
|
|
|
|
|
|
|
iCalPersonPartStat partstat;
|
2014-02-07 22:17:11 +01:00
|
|
|
int v;
|
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
event = [[calendar events] lastObject];
|
2014-02-17 14:46:05 +01:00
|
|
|
method = [[event parent] method];
|
|
|
|
|
2014-02-17 22:08:29 +01:00
|
|
|
// If we are the organizer, let's pick the attendee based on the From address
|
|
|
|
if ([event userIsOrganizer: [context activeUser]])
|
|
|
|
attendee = [event findAttendeeWithEmail: [[[[self envelope] from] lastObject] baseEMail]];
|
|
|
|
else
|
|
|
|
attendee = [event findAttendeeWithEmail: [[[context activeUser] allEmails] objectAtIndex: 0]];
|
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
partstat = [attendee participationStatus];
|
|
|
|
|
|
|
|
// We generate the correct MessageClass
|
|
|
|
if ([method isEqualToString: @"REQUEST"])
|
|
|
|
className = @"IPM.Schedule.Meeting.Request";
|
|
|
|
else if ([method isEqualToString: @"REPLY"])
|
|
|
|
{
|
|
|
|
switch (partstat)
|
|
|
|
{
|
|
|
|
case iCalPersonPartStatAccepted:
|
|
|
|
className = @"IPM.Schedule.Meeting.Resp.Pos";
|
|
|
|
break;
|
|
|
|
case iCalPersonPartStatDeclined:
|
|
|
|
className = @"IPM.Schedule.Meeting.Resp.Neg";
|
|
|
|
break;
|
|
|
|
case iCalPersonPartStatTentative:
|
2014-02-17 22:08:29 +01:00
|
|
|
case iCalPersonPartStatNeedsAction:
|
2014-02-17 14:46:05 +01:00
|
|
|
className = @"IPM.Schedule.Meeting.Resp.Tent";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
className = @"IPM.Appointment";
|
|
|
|
NSLog(@"unhandled part stat");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ([method isEqualToString: @"COUNTER"])
|
|
|
|
className = @"IPM.Schedule.Meeting.Resp.Tent";
|
|
|
|
else if ([method isEqualToString: @"CANCEL"])
|
|
|
|
className = @"IPM.Schedule.Meeting.Cancelled";
|
|
|
|
else
|
|
|
|
className = @"IPM.Appointment";
|
|
|
|
|
|
|
|
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", className];
|
2014-02-03 16:24:33 +01:00
|
|
|
|
|
|
|
[s appendString: @"<MeetingRequest xmlns=\"Email:\">"];
|
2014-02-07 22:17:11 +01:00
|
|
|
|
|
|
|
[s appendFormat: @"<AllDayEvent xmlns=\"Email:\">%d</AllDayEvent>", ([event isAllDay] ? 1 : 0)];
|
2014-02-17 14:46:05 +01:00
|
|
|
|
2015-05-12 16:37:26 +02:00
|
|
|
// StartTime -- https://msdn.microsoft.com/en-us/library/ee203365%28v=exchg.80%29.aspx
|
2014-02-17 14:46:05 +01:00
|
|
|
if ([event startDate])
|
2015-05-12 16:37:26 +02:00
|
|
|
[s appendFormat: @"<StartTime xmlns=\"Email:\">%@</StartTime>", [[event startDate] activeSyncRepresentationInContext: context]];
|
2014-02-03 16:24:33 +01:00
|
|
|
|
|
|
|
if ([event timeStampAsDate])
|
2015-05-12 16:37:26 +02:00
|
|
|
[s appendFormat: @"<DTStamp xmlns=\"Email:\">%@</DTStamp>", [[event timeStampAsDate] activeSyncRepresentationInContext: context]];
|
2014-02-03 16:24:33 +01:00
|
|
|
else if ([event created])
|
2015-05-12 16:37:26 +02:00
|
|
|
[s appendFormat: @"<DTStamp xmlns=\"Email:\">%@</DTStamp>", [[event created] activeSyncRepresentationInContext: context]];
|
2014-02-03 16:24:33 +01:00
|
|
|
|
2015-05-12 16:37:26 +02:00
|
|
|
// EndTime -- https://msdn.microsoft.com/en-us/library/ee158628(v=exchg.80).aspx
|
2014-02-03 16:24:33 +01:00
|
|
|
if ([event endDate])
|
2015-05-12 16:37:26 +02:00
|
|
|
[s appendFormat: @"<EndTime xmlns=\"Email:\">%@</EndTime>", [[event endDate] activeSyncRepresentationInContext: context]];
|
2014-02-03 16:24:33 +01:00
|
|
|
|
2014-02-17 22:08:29 +01:00
|
|
|
// FIXME: Single appointment - others are not supported right now
|
2014-02-03 16:24:33 +01:00
|
|
|
[s appendFormat: @"<InstanceType xmlns=\"Email:\">%d</InstanceType>", 0];
|
2014-02-17 14:46:05 +01:00
|
|
|
|
|
|
|
// Location
|
|
|
|
if ([[event location] length])
|
2014-02-17 16:01:44 +01:00
|
|
|
[s appendFormat: @"<Location xmlns=\"Email:\">%@</Location>", [[event location] activeSyncRepresentationInContext: context]];
|
2014-02-17 14:46:05 +01:00
|
|
|
|
2014-02-17 16:01:44 +01:00
|
|
|
[s appendFormat: @"<Organizer xmlns=\"Email:\">%@</Organizer>", [[[event organizer] mailAddress] activeSyncRepresentationInContext: context]];
|
2014-02-17 14:46:05 +01:00
|
|
|
|
|
|
|
// This will trigger the SendMail command. We set it to no for email invitations as
|
|
|
|
// SOGo will send emails when MeetingResponse is called.
|
|
|
|
[s appendFormat: @"<ResponseRequested xmlns=\"Email:\">%d</ResponseRequested>", 0];
|
|
|
|
|
2014-02-07 22:17:11 +01:00
|
|
|
// Sensitivity
|
|
|
|
if ([[event accessClass] isEqualToString: @"PRIVATE"])
|
|
|
|
v = 2;
|
|
|
|
if ([[event accessClass] isEqualToString: @"CONFIDENTIAL"])
|
|
|
|
v = 3;
|
|
|
|
else
|
|
|
|
v = 0;
|
|
|
|
|
|
|
|
[s appendFormat: @"<Sensitivity xmlns=\"Email:\">%d</Sensitivity>", v];
|
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
[s appendFormat: @"<BusyStatus xmlns=\"Email:\">%d</BusyStatus>", 2];
|
2014-02-07 16:45:09 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
// Timezone
|
|
|
|
tz = [(iCalDateTime *)[event firstChildWithTag: @"dtstart"] timeZone];
|
|
|
|
|
|
|
|
if (!tz)
|
|
|
|
tz = [iCalTimeZone timeZoneForName: @"Europe/London"];
|
|
|
|
|
2014-02-17 16:01:44 +01:00
|
|
|
[s appendFormat: @"<TimeZone xmlns=\"Email:\">%@</TimeZone>", [tz activeSyncRepresentationInContext: context]];
|
2014-02-17 14:46:05 +01:00
|
|
|
|
2014-02-07 16:45:09 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
// We disallow new time proposals
|
|
|
|
[s appendFormat: @"<DisallowNewTimeProposal xmlns=\"Email:\">%d</DisallowNewTimeProposal>", 1];
|
|
|
|
|
2014-02-03 16:24:33 +01:00
|
|
|
// From http://blogs.msdn.com/b/exchangedev/archive/2011/07/22/working-with-meeting-requests-in-exchange-activesync.aspx:
|
|
|
|
//
|
|
|
|
// "Clients that need to determine whether the GlobalObjId element for a meeting request corresponds to an existing Calendar
|
|
|
|
// object in the Calendar folder have to convert the GlobalObjId element value to a UID element value to make the comparison."
|
|
|
|
//
|
|
|
|
globalObjId = [self _computeGlobalObjectIdFromEvent: event];
|
2014-02-17 16:01:44 +01:00
|
|
|
[s appendFormat: @"<GlobalObjId xmlns=\"Email:\">%@</GlobalObjId>", [globalObjId activeSyncRepresentationInContext: context]];
|
2014-02-17 14:46:05 +01:00
|
|
|
|
|
|
|
// We set the right message type - we must set AS version to 14.1 for this
|
2015-05-12 16:05:45 +02:00
|
|
|
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.1"])
|
|
|
|
[s appendFormat: @"<MeetingMessageType xmlns=\"Email2:\">%d</MeetingMessageType>", 1];
|
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
[s appendString: @"</MeetingRequest>"];
|
|
|
|
|
|
|
|
// ContentClass
|
2014-02-03 16:24:33 +01:00
|
|
|
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:calendarmessage"];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// MesssageClass and ContentClass
|
|
|
|
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
|
|
|
|
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"];
|
|
|
|
}
|
2014-01-24 17:09:37 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
// Reply-To - FIXME
|
|
|
|
//NSArray *replyTo = [[message objectForKey: @"envelope"] replyTo];
|
|
|
|
//if ([replyTo count])
|
|
|
|
// [s appendFormat: @"<Reply-To xmlns=\"Email:\">%@</Reply-To>", [addressFormatter stringForArray: replyTo]];
|
|
|
|
|
2014-01-24 17:09:37 +01:00
|
|
|
// InternetCPID - 65001 == UTF-8, we use this all the time for now.
|
2014-03-19 16:44:36 +01:00
|
|
|
// - 20127 == US-ASCII
|
2014-01-10 20:12:53 +01:00
|
|
|
[s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"];
|
|
|
|
|
|
|
|
// Body - namespace 17
|
|
|
|
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
|
|
|
|
|
2014-01-14 16:42:15 +01:00
|
|
|
nativeBodyType = 1;
|
|
|
|
d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType];
|
2015-05-12 15:59:01 +02:00
|
|
|
|
|
|
|
if (calendar && !d)
|
|
|
|
{
|
|
|
|
WOApplication *app;
|
|
|
|
SOGoAptMailNotification *p;
|
|
|
|
NSString *pageName;
|
|
|
|
|
|
|
|
nativeBodyType = 2;
|
|
|
|
|
|
|
|
/* get WOApplication instance */
|
|
|
|
app = [WOApplication application];
|
|
|
|
|
|
|
|
/* create page name */
|
|
|
|
pageName = [NSString stringWithFormat: @"SOGoAptMail%@", @"Invitation"];
|
|
|
|
/* construct message content */
|
|
|
|
p = [app pageWithName: pageName inContext: context];
|
|
|
|
[p setApt: (iCalEvent *) [[calendar events] lastObject]];
|
|
|
|
|
|
|
|
if ([[ [[calendar events] lastObject] organizer] cn] && [[[ [[calendar events] lastObject] organizer] cn] length])
|
|
|
|
{
|
|
|
|
[p setOrganizerName: [[ [[calendar events] lastObject] organizer] cn]];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[p setOrganizerName: [[SOGoUser userWithLogin: owner] cn]];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (preferredBodyType == 1 && nativeBodyType == 2)
|
|
|
|
d = [[[p getBody] htmlToText] dataUsingEncoding: NSUTF8StringEncoding];
|
|
|
|
else
|
2015-05-12 16:15:07 +02:00
|
|
|
{
|
|
|
|
preferredBodyType = 2;
|
|
|
|
d = [[p getBody] dataUsingEncoding: NSUTF8StringEncoding];
|
|
|
|
}
|
2015-05-12 15:59:01 +02:00
|
|
|
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
if (d)
|
|
|
|
{
|
|
|
|
NSString *content;
|
2014-02-17 14:46:05 +01:00
|
|
|
int len, truncated;
|
2015-07-28 12:37:33 +02:00
|
|
|
|
|
|
|
content = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding];
|
2014-02-17 14:46:05 +01:00
|
|
|
|
|
|
|
// FIXME: This is a hack. We should normally avoid doing this as we might get
|
|
|
|
// broken encodings. We should rather tell that the data was truncated and expect
|
|
|
|
// a ItemOperations call to download the whole base64 encoding multipart.
|
2014-03-19 16:44:36 +01:00
|
|
|
//
|
|
|
|
// See http://social.msdn.microsoft.com/Forums/en-US/b9944e49-9bc9-4ab8-ba33-a9fc08557c5b/mime-raw-data-in-eas-sync-response?forum=os_exchangeprotocols
|
|
|
|
// for an "interesting" discussion around this.
|
|
|
|
//
|
2014-02-17 14:46:05 +01:00
|
|
|
if (!content)
|
2015-07-28 12:37:33 +02:00
|
|
|
content = [[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding];
|
2014-02-17 14:46:05 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
AUTORELEASE(content);
|
2014-02-17 14:46:05 +01:00
|
|
|
|
2014-02-17 16:01:44 +01:00
|
|
|
content = [content activeSyncRepresentationInContext: context];
|
2014-02-17 14:46:05 +01:00
|
|
|
truncated = 0;
|
2015-07-28 12:37:33 +02:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
len = [content length];
|
2015-07-28 12:37:33 +02:00
|
|
|
|
2015-02-26 23:49:26 +01:00
|
|
|
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
|
|
|
|
{
|
|
|
|
[s appendFormat: @"<Body xmlns=\"Email:\">%@</Body>", content];
|
|
|
|
[s appendFormat: @"<BodyTruncated xmlns=\"Email:\">%d</BodyTruncated>", truncated];
|
|
|
|
}
|
2015-02-11 14:31:32 +01:00
|
|
|
else
|
2015-02-26 23:49:26 +01:00
|
|
|
{
|
|
|
|
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
2015-02-11 14:31:32 +01:00
|
|
|
|
2015-02-26 23:49:26 +01:00
|
|
|
// Set the correct type if client requested text/html but we got text/plain
|
|
|
|
if (preferredBodyType == 2 && nativeBodyType == 1)
|
|
|
|
[s appendString: @"<Type>1</Type>"];
|
|
|
|
else
|
|
|
|
[s appendFormat: @"<Type>%d</Type>", preferredBodyType];
|
2014-03-19 16:44:36 +01:00
|
|
|
|
2015-02-26 23:49:26 +01:00
|
|
|
[s appendFormat: @"<Truncated>%d</Truncated>", truncated];
|
|
|
|
[s appendFormat: @"<Preview></Preview>"];
|
|
|
|
|
|
|
|
if (!truncated)
|
|
|
|
{
|
|
|
|
[s appendFormat: @"<Data>%@</Data>", content];
|
|
|
|
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", len];
|
|
|
|
}
|
|
|
|
[s appendString: @"</Body>"];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
2014-12-04 17:27:10 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
// Attachments -namespace 16
|
2014-04-04 22:53:58 +02:00
|
|
|
attachmentKeys = [self fetchFileAttachmentKeys];
|
2014-01-10 20:12:53 +01:00
|
|
|
if ([attachmentKeys count])
|
|
|
|
{
|
|
|
|
int i;
|
2015-02-26 23:56:42 +01:00
|
|
|
|
|
|
|
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
|
|
|
|
[s appendString: @"<Attachments xmlns=\"Email:\">"];
|
|
|
|
else
|
|
|
|
[s appendString: @"<Attachments xmlns=\"AirSyncBase:\">"];
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
for (i = 0; i < [attachmentKeys count]; i++)
|
|
|
|
{
|
|
|
|
value = [attachmentKeys objectAtIndex: i];
|
|
|
|
|
|
|
|
[s appendString: @"<Attachment>"];
|
2015-03-19 19:53:01 +01:00
|
|
|
[s appendFormat: @"<DisplayName>%@</DisplayName>", [[value objectForKey: @"filename"] activeSyncRepresentationInContext: context]];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
// FileReference must be a unique identifier across the whole store. We use the following structure:
|
|
|
|
// mail/<foldername>/<message UID/<pathofpart>
|
|
|
|
// mail/INBOX/2
|
2015-02-26 23:56:42 +01:00
|
|
|
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
|
|
|
|
[s appendFormat: @"<AttName>mail/%@/%@/%@</AttName>", [[[self container] relativeImap4Name] stringByEscapingURL], [self nameInContainer], [value objectForKey: @"path"]];
|
|
|
|
else
|
|
|
|
[s appendFormat: @"<FileReference>mail/%@/%@/%@</FileReference>", [[[self container] relativeImap4Name] stringByEscapingURL], [self nameInContainer], [value objectForKey: @"path"]];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2015-02-26 23:56:42 +01:00
|
|
|
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"])
|
|
|
|
{
|
|
|
|
[s appendFormat: @"<AttMethod>%d</AttMethod>", 1];
|
|
|
|
[s appendFormat: @"<AttSize>%d</AttSize>", [[value objectForKey: @"size"] intValue]];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[s appendFormat: @"<Method>%d</Method>", 1]; // See: http://msdn.microsoft.com/en-us/library/ee160322(v=exchg.80).aspx
|
|
|
|
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [[value objectForKey: @"size"] intValue]];
|
|
|
|
//[s appendFormat: @"<IsInline>%d</IsInline>", 1];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
[s appendString: @"</Attachment>"];
|
|
|
|
}
|
|
|
|
|
|
|
|
[s appendString: @"</Attachments>"];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Flags
|
|
|
|
[s appendString: @"<Flag xmlns=\"Email:\">"];
|
2014-06-09 15:34:00 +02:00
|
|
|
[s appendFormat: @"<FlagStatus>%d</FlagStatus>", ([self flagged] ? 2 : 0)];
|
2014-01-10 20:12:53 +01:00
|
|
|
[s appendString: @"</Flag>"];
|
2015-02-05 22:21:27 +01:00
|
|
|
|
|
|
|
|
|
|
|
// Categroies/Labels
|
|
|
|
NSEnumerator *categories;
|
|
|
|
categories = [[[self fetchCoreInfos] objectForKey: @"flags"] objectEnumerator];
|
|
|
|
|
|
|
|
if (categories)
|
|
|
|
{
|
|
|
|
NSString *currentFlag;
|
|
|
|
NSDictionary *v;
|
|
|
|
|
|
|
|
v = [[[context activeUser] userDefaults] mailLabelsColors];
|
|
|
|
|
|
|
|
[s appendFormat: @"<Categories xmlns=\"Email:\">"];
|
|
|
|
while ((currentFlag = [categories nextObject]))
|
|
|
|
{
|
|
|
|
if ([[v objectForKey: currentFlag] objectAtIndex:0])
|
|
|
|
[s appendFormat: @"<Category>%@</Category>", [[[v objectForKey: currentFlag] objectAtIndex:0] activeSyncRepresentationInContext: context]];
|
|
|
|
}
|
|
|
|
[s appendFormat: @"</Categories>"];
|
|
|
|
}
|
2014-01-24 17:09:37 +01:00
|
|
|
|
2015-05-14 21:40:03 +02:00
|
|
|
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.0"] ||
|
2015-05-12 16:44:51 +02:00
|
|
|
[[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.1"])
|
|
|
|
{
|
|
|
|
if ([self inReplyTo])
|
|
|
|
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[[self inReplyTo] dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
|
|
|
|
else if ([self messageId])
|
|
|
|
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[[self messageId] dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
|
|
|
|
}
|
|
|
|
|
2014-01-24 17:09:37 +01:00
|
|
|
// FIXME - support these in the future
|
|
|
|
//[s appendString: @"<ConversationIndex xmlns=\"Email2:\">zot=</ConversationIndex>"];
|
2015-05-12 16:44:51 +02:00
|
|
|
|
2014-01-24 17:09:37 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
// NativeBodyType -- http://msdn.microsoft.com/en-us/library/ee218276(v=exchg.80).aspx
|
|
|
|
// This is a required child element.
|
|
|
|
// 1 -> plain/text, 2 -> HTML and 3 -> RTF
|
2014-01-24 17:09:37 +01:00
|
|
|
if (nativeBodyType == 4)
|
|
|
|
nativeBodyType = 1;
|
|
|
|
|
|
|
|
[s appendFormat: @"<NativeBodyType xmlns=\"AirSyncBase:\">%d</NativeBodyType>", nativeBodyType];
|
2014-01-24 20:10:19 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
2014-03-28 19:18:48 +01:00
|
|
|
// Exemple for a message being marked as read:
|
|
|
|
//
|
|
|
|
// <Change>
|
|
|
|
// <ServerId>607</ServerId>
|
|
|
|
// <ApplicationData>
|
|
|
|
// <Read xmlns="Email:">1</Read>
|
|
|
|
// </ApplicationData>
|
|
|
|
// </Change>
|
|
|
|
// </Commands>
|
|
|
|
//
|
2014-01-10 20:12:53 +01:00
|
|
|
- (void) takeActiveSyncValues: (NSDictionary *) theValues
|
2014-02-17 16:01:44 +01:00
|
|
|
inContext: (WOContext *) _context
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
|
|
|
id o;
|
|
|
|
|
|
|
|
if ((o = [theValues objectForKey: @"Flag"]))
|
|
|
|
{
|
2014-06-09 15:34:00 +02:00
|
|
|
// We must handle empty flags -> {Flag = ""; } - some ActiveSync clients, like the HTC Desire
|
|
|
|
// will send an empty Flag message when "unflagging" a mail.
|
|
|
|
if (([o isKindOfClass: [NSMutableDictionary class]]))
|
|
|
|
{
|
|
|
|
if ((o = [o objectForKey: @"FlagStatus"]))
|
|
|
|
{
|
|
|
|
// 0 = The flag is cleared.
|
|
|
|
// 1 = The status is set to complete.
|
|
|
|
// 2 = The status is set to active.
|
|
|
|
if (([o isEqualToString: @"2"]))
|
|
|
|
[self addFlags: @"\\Flagged"];
|
|
|
|
else
|
|
|
|
[self removeFlags: @"\\Flagged"];
|
|
|
|
}
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
else
|
2014-02-17 14:46:05 +01:00
|
|
|
[self removeFlags: @"\\Flagged"];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
2014-03-28 19:18:48 +01:00
|
|
|
|
|
|
|
if ((o = [theValues objectForKey: @"Read"]))
|
|
|
|
{
|
|
|
|
if ([o intValue])
|
|
|
|
[self addFlags: @"seen"];
|
|
|
|
else
|
|
|
|
[self removeFlags: @"seen"];;
|
|
|
|
}
|
2015-02-05 22:21:27 +01:00
|
|
|
|
|
|
|
if ((o = [theValues objectForKey: @"Categories"]))
|
|
|
|
{
|
|
|
|
NSEnumerator *categories;
|
|
|
|
NSString *currentFlag;
|
|
|
|
NSDictionary *v;
|
|
|
|
|
|
|
|
v = [[[context activeUser] userDefaults] mailLabelsColors];
|
|
|
|
|
|
|
|
// add categories/labels sent from client
|
|
|
|
if ([o isKindOfClass: [NSArray class]])
|
|
|
|
{
|
|
|
|
NSEnumerator *enumerator;
|
|
|
|
NSMutableArray *labels;
|
|
|
|
NSEnumerator *flags;
|
|
|
|
id key;
|
|
|
|
|
|
|
|
labels = [NSMutableArray array];
|
|
|
|
|
|
|
|
enumerator = [v keyEnumerator];
|
|
|
|
flags = [o objectEnumerator];
|
|
|
|
|
|
|
|
while ((currentFlag = [flags nextObject]))
|
|
|
|
{
|
|
|
|
while ((key = [enumerator nextObject]))
|
|
|
|
{
|
|
|
|
if (([currentFlag isEqualToString:[[v objectForKey:key] objectAtIndex:0]]))
|
|
|
|
{
|
|
|
|
[labels addObject: key];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[self addFlags: [labels componentsJoinedByString: @" "]];
|
|
|
|
}
|
|
|
|
|
|
|
|
categories = [[[self fetchCoreInfos] objectForKey: @"flags"] objectEnumerator];
|
|
|
|
|
|
|
|
// remove all categories/labels from server which were not it the list sent from client
|
|
|
|
if (categories)
|
|
|
|
{
|
|
|
|
while ((currentFlag = [categories nextObject]))
|
|
|
|
{
|
|
|
|
// only deal with lables and don't touch flags like seen and flagged
|
|
|
|
if (([v objectForKey: currentFlag]))
|
|
|
|
{
|
|
|
|
if (![o isKindOfClass: [NSArray class]])
|
|
|
|
{
|
|
|
|
[self removeFlags: currentFlag];
|
|
|
|
}
|
|
|
|
else if (([o indexOfObject: [[v objectForKey:currentFlag] objectAtIndex:0]] == NSNotFound))
|
|
|
|
{
|
|
|
|
[self removeFlags: currentFlag];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|