First pass at event invitations support + few bug fixes.

pull/17/merge
Ludovic Marcotte 2014-02-03 10:24:33 -05:00
parent d709a1216b
commit 9e9407cf30
10 changed files with 638 additions and 143 deletions

View File

@ -31,6 +31,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
static NSArray *asElementArray = nil;
@implementation NGDOMElement (ActiveSync)
@ -44,13 +47,40 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// </Flag>
// </ApplicationData>
//
// and stuff like that:
//
// <Attendees xmlns="Calendar:">
// <Attendee>
// <Attendee_Email>sogo1@example.com</Attendee_Email>
// <Attendee_Name>John Doe</Attendee_Name>
// <Attendee_Status>5</Attendee_Status>
// <Attendee_Type>1</Attendee_Type>
// </Attendee>
// <Attendee>
// <Attendee_Email>sogo2@example.com</Attendee_Email>
// <Attendee_Name>Balthazar César</Attendee_Name>
// <Attendee_Status>5</Attendee_Status>
// <Attendee_Type>1</Attendee_Type>
// </Attendee>
// <Attendee>
// <Attendee_Email>sogo3@example.com</Attendee_Email>
// <Attendee_Name>Wolfgang Fritz</Attendee_Name>
// <Attendee_Status>5</Attendee_Status>
// <Attendee_Type>1</Attendee_Type>
// </Attendee>
// </Attendees>
//
- (NSDictionary *) applicationData
{
NSMutableDictionary *data;
id <DOMNodeList> children;
id <DOMElement> element;
int i;
int i, count;
if (!asElementArray)
asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", nil];
data = [NSMutableDictionary dictionary];
children = [self childNodes];
@ -65,10 +95,55 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
id value;
tag = [element tagName];
count = [(NSArray *)[element childNodes] count];
// Handle inner data
if ([(NSArray *)[element childNodes] count] > 2)
value = [(NGDOMElement *)element applicationData];
// Handle inner data - see above for samples
if (count > 2)
{
NSMutableArray *innerElements;
id <DOMElement> innerElement;
NSArray *childNodes;
NSString *innerTag;
BOOL same;
int j;
childNodes = (NSArray *)[element childNodes];
innerElements = [NSMutableArray array];
innerTag = nil;
same = YES;
for (j = 1; j < count; j++)
{
innerElement = [childNodes objectAtIndex: j];
if ([innerElement nodeType] == DOM_ELEMENT_NODE)
{
if (!innerTag)
innerTag = [innerElement tagName];
if ([innerTag isEqualToString: [innerElement tagName]])
{
[innerElements addObject: [(NGDOMElement *)innerElement applicationData]];
}
else
{
same = NO;
break;
}
}
}
if (same && [asElementArray containsObject: innerTag])
value = innerElements;
else
{
value = [(NGDOMElement *)element applicationData];
// Don't set empty values like Foo = {}
if (![value count])
value = nil;
}
}
else
value = [[element firstChild] nodeValue];

View File

@ -35,6 +35,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "SOGoActiveSyncConstants.h"
@class NSCalendarDate;
@class NSData;
@interface NSString (ActiveSync)
@ -43,6 +44,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSCalendarDate *) calendarDate;
- (NSString *) deviceId;
- (NSString *) command;
- (NSData *) convertHexStringToBytes;
@end

View File

@ -31,6 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <Foundation/NSArray.h>
#include <Foundation/NSCalendarDate.h>
#include <Foundation/NSData.h>
#include <Foundation/NSDate.h>
#include <NGExtensions/NSString+misc.h>
@ -53,30 +54,34 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSString *) realCollectionIdWithFolderType: (SOGoMicrosoftActiveSyncFolderType *) folderType;
{
NSString *realCollectionId;
NSString *realCollectionId, *v;
*folderType = ActiveSyncGenericFolder;
v = [self stringByUnescapingURL];
if ([self hasPrefix: @"vevent/"])
if ([v hasPrefix: @"vevent/"])
{
realCollectionId = [self substringFromIndex: 7];
realCollectionId = [v substringFromIndex: 7];
*folderType = ActiveSyncEventFolder;
}
else if ([self hasPrefix: @"vtodo/"])
else if ([v hasPrefix: @"vtodo/"])
{
realCollectionId = [self substringFromIndex: 6];
realCollectionId = [v substringFromIndex: 6];
*folderType = ActiveSyncTaskFolder;
}
else if ([self hasPrefix: @"vcard/"])
else if ([v hasPrefix: @"vcard/"])
{
realCollectionId = [self substringFromIndex: 6];
realCollectionId = [v substringFromIndex: 6];
*folderType = ActiveSyncContactFolder;
}
else if ([v hasPrefix: @"mail/"])
{
realCollectionId = [[v stringByUnescapingURL] substringFromIndex: 5];
*folderType = ActiveSyncMailFolder;
}
else
{
// mail/
realCollectionId = [[self stringByUnescapingURL] substringFromIndex: 5];
*folderType = ActiveSyncMailFolder;
realCollectionId = nil;
}
return realCollectionId;
@ -150,4 +155,100 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
return s;
}
//
// FIXME: combine with our OpenChange code.
//
- (char) _decodeHexByte: (char) byteChar
{
char newByte;
if (byteChar >= 48 && byteChar <= 57)
newByte = (uint8_t) byteChar - 48;
else if (byteChar >= 65 && byteChar <= 70)
newByte = (uint8_t) byteChar - 55;
else if (byteChar >= 97 && byteChar <= 102)
newByte = (uint8_t) byteChar - 87;
else
newByte = -1;
return newByte;
}
//
// FIXME: combine with our OpenChange code.
//
- (BOOL) _decodeHexByte: (uint8_t *) byte
atPos: (NSUInteger) pos
{
BOOL error = NO;
char newByte;
unichar byteChar;
byteChar = [self characterAtIndex: pos];
if (byteChar < 256)
{
newByte = [self _decodeHexByte: (char) byteChar];
if (newByte == -1)
error = YES;
else
*byte = newByte;
}
else
error = YES;
return error;
}
//
// FIXME: combine with our OpenChange code.
//
- (BOOL) _decodeHexPair: (uint8_t *) byte
atPos: (NSUInteger) pos
{
BOOL error;
uint8_t lowValue, highValue;
error = [self _decodeHexByte: &highValue atPos: pos];
if (!error)
{
error = [self _decodeHexByte: &lowValue atPos: pos + 1];
if (!error)
*byte = highValue << 4 | lowValue;
}
return error;
}
//
// FIXME: combine with our OpenChange code.
//
- (NSData *) convertHexStringToBytes
{
NSUInteger count, strLen, bytesLen;
uint8_t *bytes, *currentByte;
NSData *decoded = nil;
BOOL error = NO;
strLen = [self length];
if ((strLen % 2) == 0)
{
bytesLen = strLen / 2;
bytes = NSZoneCalloc (NULL, bytesLen, sizeof (uint8_t));
currentByte = bytes;
for (count = 0; !error && count < strLen; count += 2)
{
error = [self _decodeHexPair: currentByte atPos: count];
currentByte++;
}
if (error)
NSZoneFree (NULL, bytes);
else
decoded = [NSData dataWithBytesNoCopy: bytes
length: bytesLen
freeWhenDone: YES];
}
return decoded;
}
@end

View File

@ -46,13 +46,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/WOResponse.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalEntityObject.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalToDo.h>
#import <NGCards/NGVCard.h>
#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NSString+misc.h>
@ -107,46 +106,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation SOGoActiveSyncDispatcher (Sync)
- (id) collectionFromId: (NSString *) theCollectionId
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType
{
id collection;
collection = nil;
switch (theFolderType)
{
case ActiveSyncContactFolder:
{
collection = [[context activeUser] personalContactsFolderInContext: context];
}
break;
case ActiveSyncEventFolder:
case ActiveSyncTaskFolder:
{
collection = [[context activeUser] personalCalendarFolderInContext: context];
}
break;
case ActiveSyncMailFolder:
default:
{
SOGoMailAccounts *accountsFolder;
SOGoMailFolder *currentFolder;
SOGoUserFolder *userFolder;
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
collection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", theCollectionId]
inContext: context
acquire: NO];
}
}
return collection;
}
//
// <?xml version="1.0"?>
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">

View File

@ -29,6 +29,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/NSObject.h>
#include "SOGoActiveSyncConstants.h"
@class NSException;
@interface SOGoActiveSyncDispatcher : NSObject
@ -36,6 +38,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
id context;
}
- (id) collectionFromId: (NSString *) theCollectionId
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;
- (NSException *) dispatchRequest: (id) theRequest
inResponse: (id) theResponse
context: (id) theContext;

View File

@ -45,7 +45,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/WOResponse.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalEntityObject.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalToDo.h>
@ -85,6 +85,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Appointments/SOGoAppointmentFolder.h>
#import <Appointments/SOGoAppointmentFolders.h>
#import <Appointments/SOGoAppointmentObject.h>
#import <Contacts/SOGoContactGCSFolder.h>
#import <Contacts/SOGoContactFolders.h>
@ -127,6 +128,49 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[[[context activeUser] userSettings] synchronize];
}
//
//
//
- (id) collectionFromId: (NSString *) theCollectionId
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType
{
id collection;
collection = nil;
switch (theFolderType)
{
case ActiveSyncContactFolder:
{
collection = [[context activeUser] personalContactsFolderInContext: context];
}
break;
case ActiveSyncEventFolder:
case ActiveSyncTaskFolder:
{
collection = [[context activeUser] personalCalendarFolderInContext: context];
}
break;
case ActiveSyncMailFolder:
default:
{
SOGoMailAccounts *accountsFolder;
SOGoMailFolder *currentFolder;
SOGoUserFolder *userFolder;
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
collection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", theCollectionId]
inContext: context
acquire: NO];
}
}
return collection;
}
//
//
//
@ -524,11 +568,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
EOQualifier *notDeletedQualifier, *sinceDateQualifier;
NSString *collectionId, *realCollectionId;
id currentFolder, currentCollection;
SOGoMailAccounts *accountsFolder;
SOGoUserFolder *userFolder;
EOAndQualifier *qualifier;
NSCalendarDate *filter;
id currentCollection;
NSMutableString *s;
NSArray *uids;
NSData *d;
@ -541,15 +583,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue];
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
currentCollection = [self collectionFromId: realCollectionId type: folderType];
currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", realCollectionId]
inContext: context
acquire: NO];
//
// For IMAP, we simply build a request like this:
//
@ -677,12 +712,91 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//
// <?xml version="1.0"?>
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
// <MeetingResponse xmlns="MeetingResponse:">
// <Request>
// <UserResponse>1</UserResponse>
// <CollectionId>mail%2FINBOX</CollectionId>
// <RequestId>283</RequestId>
// </Request>
// </MeetingResponse>
//
- (void) processMeetingResponse: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSString *realCollectionId, *requestId, *participationStatus;
NSMutableString *s;
NSData *d;
id collection;
SOGoMicrosoftActiveSyncFolderType folderType;
int userResponse;
int status;
s = [NSMutableString string];
status = 1;
realCollectionId = [[[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType];
collection = [self collectionFromId: realCollectionId type: ActiveSyncMailFolder];
// 1 -> accepted, 2 -> tentative, 3 -> declined
userResponse = [[[(id)[theDocumentElement getElementsByTagName: @"UserResponse"] lastObject] textValue] intValue];
requestId = [[(id)[theDocumentElement getElementsByTagName: @"RequestId"] lastObject] textValue];
//
// We fetch the calendar information based on the email (requestId) in the user's INBOX (or elsewhere)
//
// FIXME: that won't work too well for external invitations...
SOGoMailObject *mailObject;
mailObject = [collection lookupName: requestId
inContext: context
acquire: 0];
if (![mailObject isKindOfClass: [NSException class]])
{
SOGoAppointmentObject *appointmentObject;
iCalCalendar *calendar;
iCalEvent *event;
calendar = [mailObject calendarFromIMIPMessage];
event = [[calendar events] lastObject];
// Fetch the SOGoAppointmentObject
collection = [[context activeUser] personalCalendarFolderInContext: context];
appointmentObject = [collection lookupName: [NSString stringWithFormat: @"%@.ics", [event uid]]
inContext: context
acquire: NO];
if (userResponse == 1)
participationStatus = @"ACCEPTED";
else if (userResponse == 2)
participationStatus = @"TENTATIVE";
else
participationStatus = @"DECLINED";
[appointmentObject changeParticipationStatus: participationStatus
withDelegate: nil];
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[s appendString: @"<MeetingResponse xmlns=\"MeetingResponse:\">"];
[s appendString: @"<Result>"];
[s appendFormat: @"<RequestId>%@</RequestId>", requestId];
[s appendFormat: @"<CalendarId>%@</CalendarId>", [event uid]];
[s appendFormat: @"<Status>%d</Status>", status];
[s appendString: @"</Result>"];
[s appendString: @"</MeetingResponse>"];
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d];
}
else
{
[theResponse setStatus: 500];
}
}
@ -710,22 +824,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// FIXME
if (srcFolderType == ActiveSyncMailFolder && dstFolderType == ActiveSyncMailFolder)
{
SOGoMailAccounts *accountsFolder;
SOGoMailFolder *currentFolder;
SOGoUserFolder *userFolder;
NGImap4Client *client;
id currentCollection;
NSDictionary *response;
NSString *v;
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
// userFolder = [[context activeUser] homeFolderInContext: context];
// accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
// currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", srcFolderId]
inContext: context
acquire: NO];
currentCollection = [self collectionFromId: srcFolderId type: srcFolderType];
// [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", srcFolderId]
// inContext: context
// acquire: NO];
client = [[currentCollection imap4Connection] client];
[client select: srcFolderId];
@ -747,7 +860,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
addOrRemove: YES];
if ([[response valueForKey: @"result"] boolValue])
[currentCollection expunge];
[(SOGoMailFolder *)currentCollection expunge];
}

View File

@ -32,10 +32,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Mailer/SOGoMailObject.h>
@class iCalCalendar;
@class NSDictionary;
@interface SOGoMailObject (ActiveSync)
- (iCalCalendar *) calendarFromIMIPMessage;
- (NSString *) activeSyncRepresentation;
- (void) takeActiveSyncValues: (NSDictionary *) theValues;

View File

@ -38,7 +38,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalTimeZone.h>
#import <NGExtensions/NGBase64Coding.h>
#import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSString+Encoding.h>
@ -46,14 +45,118 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGImap4/NGImap4EnvelopeAddress.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#include "iCalTimeZone+ActiveSync.h"
#include "NSDate+ActiveSync.h"
#include "NSString+ActiveSync.h"
#include "../SoObjects/Mailer/NSString+Mail.h"
#include "../SoObjects/Mailer/SOGoMailBodyPart.h"
#include <SOGo/SOGoUser.h>
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;
};
@implementation SOGoMailObject (ActiveSync)
//
//
//
- (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
{
NSData *binPrefix, *globalObjectId;
NSString *prefix, *uid;
struct GlobalObjectId newGlobalId;
const char *uidAsUTF8;
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]];
uidAsUTF8 = [uid UTF8String];
// 0x0c is the size of our dataPrefix
newGlobalId.Size = 0x0c + strlen(uidAsUTF8);
newGlobalId.Data = malloc(newGlobalId.Size * sizeof(uint8_t));
memcpy(newGlobalId.Data, dataPrefix, 0x0c);
memcpy(newGlobalId.Data + 0x0c, uidAsUTF8, newGlobalId.Size - 0x0c);
globalObjectId = [[NSData alloc] initWithBytes: &newGlobalId length: 40 + newGlobalId.Size*sizeof(uint8_t)];
free(newGlobalId.Data);
return [globalObjectId autorelease];
}
//
// For debugging purposes...
//
// - (NSString *) _uidFromGlobalObjectId: (NSData *) objectId
// {
// NSString *uid;
// struct GlobalObjectId *newGlobalId;
// NSUInteger length;
// uint8_t *bytes;
// length = [objectId length];
// uid = nil;
// bytes = malloc(length*sizeof(uint8_t));
// [objectId getBytes: bytes length: length];
// newGlobalId = bytes;
// // We must take the offset (dataPrefix) into account
// uid = [[NSString alloc] initWithBytes: newGlobalId->Data+12 length: newGlobalId->Size-12 encoding: NSUTF8StringEncoding];
// free(bytes);
// return AUTORELEASE(uid);
// }
//
//
//
- (NSString *) _emailAddressesFrom: (NSArray *) enveloppeAddresses
{
NSMutableArray *addresses;
@ -183,15 +286,57 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
return d;
}
//
//
//
- (iCalCalendar *) calendarFromIMIPMessage
{
NSDictionary *part;
NSArray *parts;
int i;
// 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)
{
NSData *calendarData;
calendarData = [bodyPart fetchBLOB];
return [iCalCalendar parseSingleFromSource: calendarData];
}
}
}
}
return nil;
}
//
//
//
- (NSString *) activeSyncRepresentation
{
NSData *d, *globalObjId;
NSMutableString *s;
NSData *d;
id value;
iCalCalendar *calendar;
int preferredBodyType, nativeBodyType;
s = [NSMutableString string];
@ -234,58 +379,63 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Read
[s appendFormat: @"<Read xmlns=\"Email:\">%d</Read>", ([self read] ? 1 : 0)];
// MesssageClass
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
//[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Schedule.Meeting.Request"];
//
// TEST
//
#if 0
id foo = [self lookupImap4BodyPartKey: @"2" inContext: self->context];
NSData *calendarData;
iCalCalendar *calendar;
iCalEvent *event;
calendarData = [foo fetchBLOB];
calendar = [iCalCalendar parseSingleFromSource: calendarData];
event = [[calendar events] lastObject];
if ([event timeStampAsDate])
[s appendFormat: @"<DTStamp xmlns=\"Calendar:\">%@</DTStamp>", [[event timeStampAsDate] activeSyncRepresentation]];
else if ([event created])
[s appendFormat: @"<DTStamp xmlns=\"Calendar:\">%@</DTStamp>", [[event created] activeSyncRepresentation]];
[s appendString: @"<MeetingRequest xmlns=\"Email:\">"];
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
if ([event startDate])
[s appendFormat: @"<StartTime xmlns=\"Email:\">%@</StartTime>", [[event startDate] activeSyncRepresentation]];
// EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx
if ([event endDate])
[s appendFormat: @"<EndTime xmlns=\"Email:\">%@</EndTime>", [[event endDate] activeSyncRepresentation]];
// Timezone
iCalTimeZone *tz;
tz = [(iCalDateTime *)[event firstChildWithTag: @"dtstart"] timeZone];
if (!tz)
tz = [iCalTimeZone timeZoneForName: @"Europe/London"];
[s appendFormat: @"<TimeZone xmlns=\"Email:\">%@</TimeZone>", [[tz activeSyncRepresentation] stringByReplacingString: @"\n" withString: @""]];;
[s appendFormat: @"<InstanceType xmlns=\"Email:\">%d</InstanceType>", 0];
[s appendFormat: @"<Organizer xmlns=\"Email:\">%@</Organizer>", @"sogo3@example.com"];
[s appendFormat: @"<ResponseRequested xmlns=\"Email:\">%d</ResponseRequested>", 1];
[s appendString: @"</MeetingRequest>"];
#endif
//
// TEST
//
// We handle MeetingRequest
calendar = [self calendarFromIMIPMessage];
if (calendar)
{
iCalTimeZone *tz;
iCalEvent *event;
event = [[calendar events] lastObject];
[s appendString: @"<MeetingRequest xmlns=\"Email:\">"];
if ([event timeStampAsDate])
[s appendFormat: @"<DTStamp xmlns=\"Email:\">%@</DTStamp>", [[event timeStampAsDate] activeSyncRepresentationWithoutSeparators]];
else if ([event created])
[s appendFormat: @"<DTStamp xmlns=\"Email:\">%@</DTStamp>", [[event created] activeSyncRepresentationWithoutSeparators]];
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
if ([event startDate])
[s appendFormat: @"<StartTime xmlns=\"Email:\">%@</StartTime>", [[event startDate] activeSyncRepresentationWithoutSeparators]];
// EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx
if ([event endDate])
[s appendFormat: @"<EndTime xmlns=\"Email:\">%@</EndTime>", [[event endDate] activeSyncRepresentationWithoutSeparators]];
// Timezone
tz = [(iCalDateTime *)[event firstChildWithTag: @"dtstart"] timeZone];
if (!tz)
tz = [iCalTimeZone timeZoneForName: @"Europe/London"];
[s appendFormat: @"<TimeZone xmlns=\"Email:\">%@</TimeZone>", [[tz activeSyncRepresentation] stringByReplacingString: @"\n" withString: @""]];;
[s appendFormat: @"<InstanceType xmlns=\"Email:\">%d</InstanceType>", 0];
[s appendFormat: @"<Organizer xmlns=\"Email:\">%@</Organizer>", [[event organizer] rfc822Email]];
[s appendFormat: @"<ResponseRequested xmlns=\"Email:\">%d</ResponseRequested>", 1];
// 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];
[s appendFormat: @"<GlobalObjId xmlns=\"Email:\">%@</GlobalObjId>", [globalObjId stringByEncodingBase64]];
[s appendString: @"</MeetingRequest>"];
// MesssageClass and ContentClass
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Schedule.Meeting.Request"];
[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"];
}
// Reply-To - FIXME
//NSArray *replyTo = [[message objectForKey: @"envelope"] replyTo];
@ -349,10 +499,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendString: @"</Attachments>"];
}
// ContentClass
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"];
//[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:calendarmessage"];
// Flags
[s appendString: @"<Flag xmlns=\"Email:\">"];
[s appendFormat: @"<FlagStatus>%d</FlagStatus>", 0];

View File

@ -204,6 +204,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// http://blogs.msdn.com/b/exchangedev/archive/2011/07/22/working-with-meeting-requests-in-exchange-activesync.aspx
// http://blogs.msdn.com/b/exchangedev/archive/2011/07/29/working-with-meeting-responses-in-exchange-activesync.aspx
//
//
// Here is an example of a Sync call when sogo10 accepts an invitation from sogo3:
//
// <Change>
// <ServerId>2978-52EA9D00-1-A253E70.ics</ServerId>
// <ApplicationData>
// <TimeZone xmlns="Calendar:">LAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==</TimeZone>
// <AllDayEvent xmlns="Calendar:">0</AllDayEvent>
// <StartTime xmlns="Calendar:">20140207T130000Z</StartTime>
// <EndTime xmlns="Calendar:">20140207T140000Z</EndTime>
// <DTStamp xmlns="Calendar:">20140130T185245Z</DTStamp>
// <Subject xmlns="Calendar:">test 8</Subject>
// <Sensitivity xmlns="Calendar:">0</Sensitivity>
// <Body xmlns="AirSyncBase:">
// <Type>1</Type>
// <Data/>
// </Body>
// <Organizer_Email xmlns="Calendar:">sogo3@example.com</Organizer_Email>
// <UID xmlns="Calendar:">2978-52EA9D00-1-A253E70</UID>
// <Attendees xmlns="Calendar:">
// <Attendee>
// <Attendee_Name>sogo10</Attendee_Name>
// <Attendee_Email>sogo10@example.com</Attendee_Email>
// <Attendee_Type>1</Attendee_Type>
// </Attendee>
// </Attendees>
// <BusyStatus xmlns="Calendar:">2</BusyStatus>
// <MeetingStatus xmlns="Calendar:">3</MeetingStatus>
// <Organizer_Name xmlns="Calendar:">Wolfgang Fritz</Organizer_Name>
// </ApplicationData>
// </Change>
//
- (void) takeActiveSyncValues: (NSDictionary *) theValues
{
iCalDateTime *start, *end;
@ -331,6 +363,66 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[rule takeActiveSyncValues: o];
}
// Organizer
if ((o = [theValues objectForKey: @"Organizer_Email"]))
{
iCalPerson *person;
person = [iCalPerson elementWithTag: @"organizer"];
[person setEmail: o];
[person setCn: [theValues objectForKey: @"Organizer_Name"]];
[person setPartStat: @"ACCEPTED"];
[self setOrganizer: person];
}
// Attendees
if ((o = [theValues objectForKey: @"Attendees"]))
{
NSMutableArray *attendees;
NSDictionary *attendee;
iCalPerson *person;
int status, i;
attendees = [NSMutableArray array];
for (i = 0; i < [o count]; i++)
{
// Each attendee has is a dictionary similar to this:
// { "Attendee_Email" = "sogo3@example.com"; "Attendee_Name" = "Wolfgang Fritz"; "Attendee_Status" = 5; "Attendee_Type" = 1; }
attendee = [o objectAtIndex: i];
person = [iCalPerson elementWithTag: @"attendee"];
[person setCn: [attendee objectForKey: @"Attendee_Name"]];
[person setEmail: [attendee objectForKey: @"Attendee_Email"]];
status = [[attendee objectForKey: @"Attendee_Status"] intValue];
switch (status)
{
case 2:
[person setPartStat: @"TENTATIVE"];
break;
case 3:
[person setPartStat: @"ACCEPTED"];
break;
case 4:
[person setPartStat: @"DECLINED"];
break;
case 0:
case 5:
default:
[person setPartStat: @"NEEDS-ACTION"];
break;
}
// FIXME: handle Attendee_Type
[attendees addObject: person];
}
[self setAttendees: attendees];
}
}
@end

View File

@ -42,14 +42,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGExtensions/NGBase64Coding.h>
struct SYSTEMTIME {
uint16_t wYear;
uint16_t wMonth;
uint16_t wDayOfWeek;
uint16_t wDay;
uint16_t wHour;
uint16_t wMinute;
uint16_t wSecond;
uint16_t wMilliseconds;
uint16_t wYear;
uint16_t wMonth;
uint16_t wDayOfWeek;
uint16_t wDay;
uint16_t wHour;
uint16_t wMinute;
uint16_t wSecond;
uint16_t wMilliseconds;
};
@interface iCalTimeZonePeriod (ActiveSync)