diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index 1fe766088..060a6ee8c 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -49,6 +49,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import @@ -89,6 +90,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import +#import #import #import @@ -187,20 +189,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // invitation email and choses "Add to calendar" BEFORE actually syncing the calendar. That would // create a duplicate on the server. if ([allValues objectForKey: @"UID"]) - serverId = [NSString stringWithFormat: @"%@.ics", [allValues objectForKey: @"UID"]]; + serverId = [allValues objectForKey: @"UID"]; else - serverId = [NSString stringWithFormat: @"%@.ics", [theCollection globallyUniqueObjectId]]; + serverId = [theCollection globallyUniqueObjectId]; [allValues setObject: [[[context activeUser] userDefaults] timeZone] forKey: @"SOGoUserTimeZone"]; - sogoObject = [theCollection lookupName: serverId + sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType] inContext: context acquire: NO]; // If object isn't found, we 'create' a new one if ([sogoObject isKindOfClass: [NSException class]]) { - sogoObject = [[SOGoAppointmentObject alloc] initWithName: serverId + sogoObject = [[SOGoAppointmentObject alloc] initWithName: [serverId sanitizedServerIdWithType: theFolderType] inContainer: theCollection]; o = [sogoObject component: YES secure: NO]; } @@ -224,7 +226,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. default: { // FIXME - continue; + //continue; + NSLog(@"BLARG!"); + abort(); } } @@ -302,7 +306,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. allChanges = [[(id)[aChange getElementsByTagName: @"ApplicationData"] lastObject] applicationData]; // Fetch the object and apply the changes - sogoObject = [theCollection lookupName: serverId + sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType] inContext: context acquire: NO]; @@ -387,7 +391,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. serverId = [[(id)[aDelete getElementsByTagName: @"ServerId"] lastObject] textValue]; - sogoObject = [theCollection lookupName: serverId + sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType] inContext: context acquire: NO]; @@ -412,7 +416,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue]; - o = [theCollection lookupName: serverId + o = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType] inContext: context acquire: NO]; @@ -432,13 +436,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // - (void) processSyncGetChanges: (id ) theDocumentElement inCollection: (id) theCollection + withWindowSize: (unsigned int) theWindowSize withSyncKey: (NSString *) theSyncKey withFolderType: (SOGoMicrosoftActiveSyncFolderType) theFolderType withFilterType: (NSCalendarDate *) theFilterType inBuffer: (NSMutableString *) theBuffer + lastServerKey: (NSString **) theLastServerKey + { NSMutableString *s; - int i; + + BOOL more_available; + int i, max; // // No changes in the collection - 2.2.2.19.1.1 Empty Sync Request. @@ -448,7 +457,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. return; s = [NSMutableString string]; - + more_available = NO; + switch (theFolderType) { // Handle all the GCS components @@ -473,7 +483,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType]; - for (i = 0; i < [allComponents count]; i++) + // Check for the WindowSize + max = [allComponents count]; + + // Disabled for now for GCS folders. + // if (max > theWindowSize) + // { + // max = theWindowSize; + // more_available = YES; + // } + + for (i = 0; i < max; i++) { component = [allComponents objectAtIndex: i]; deleted = [[component objectForKey: @"c_deleted"] intValue]; @@ -481,7 +501,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (!deleted && ![[component objectForKey: @"c_component"] isEqualToString: component_name]) continue; - uid = [component objectForKey: @"c_name"]; + uid = [[component objectForKey: @"c_name"] sanitizedServerIdWithType: theFolderType]; if (deleted) { @@ -496,6 +516,41 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if ([[component objectForKey: @"c_creationdate"] intValue] > [theSyncKey intValue]) updated = NO; + sogoObject = [theCollection lookupName: [uid sanitizedServerIdWithType: theFolderType] + inContext: context + acquire: 0]; + + if (theFolderType == ActiveSyncContactFolder) + componentObject = [sogoObject vCard]; + else + componentObject = [sogoObject component: NO secure: NO]; + + + // + // We do NOT synchronize NEW events that are in fact, invitations + // to events. This is due to the fact that Outlook 2013 creates + // "phantom" events in the calendar that are mapped to invitations mails. + // If we synchronize these events too, it'll interfere with the whole thing + // and prevent Outlook from properly calling MeetingResponse. + // + if (!updated && theFolderType == ActiveSyncEventFolder) + { + iCalPersonPartStat partstat; + iCalPerson *attendee; + NSString *email; + + email = [[[context activeUser] allEmails] objectAtIndex: 0]; + attendee = [componentObject findAttendeeWithEmail: email]; + + if (attendee) + { + partstat = [attendee participationStatus]; + + if (partstat == iCalPersonPartStatNeedsAction) + continue; + } + } + if (updated) [s appendString: @""]; else @@ -504,15 +559,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [s appendFormat: @"%@", uid]; [s appendString: @""]; - sogoObject = [theCollection lookupName: uid - inContext: context - acquire: 0]; - - if (theFolderType == ActiveSyncContactFolder) - componentObject = [sogoObject vCard]; - else - componentObject = [sogoObject component: NO secure: NO]; - [s appendString: [componentObject activeSyncRepresentation]]; [s appendString: @""]; @@ -535,7 +581,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType]; - for (i = 0; i < [allMessages count]; i++) + // Check for the WindowSize. + // FIXME: we should eventually check for modseq and slice the maximum + // amount of messages returned to ensure we don't have the same + // modseq accross contiguous boundaries + max = [allMessages count]; + if (max > theWindowSize) + { + max = theWindowSize; + more_available = YES; + } + + for (i = 0; i < max; i++) { aMessage = [allMessages objectAtIndex: i]; @@ -571,6 +628,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } } + + // + if (more_available) + { + *theLastServerKey = uid; + } } break; } // switch (folderType) ... @@ -580,6 +643,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [theBuffer appendString: @""]; [theBuffer appendString: s]; [theBuffer appendString: @""]; + + if (more_available) + [theBuffer appendString: @""]; } } @@ -663,12 +729,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. inBuffer: (NSMutableString *) theBuffer changeDetected: (BOOL *) changeDetected { - NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType; + NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *lastServerKey; SOGoMicrosoftActiveSyncFolderType folderType; id collection, value; NSMutableString *changeBuffer, *commandsBuffer; BOOL getChanges, first_sync; + unsigned int windowSize; changeBuffer = [NSMutableString string]; commandsBuffer = [NSMutableString string]; @@ -679,6 +746,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syncKey = davCollectionTag = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue]; + // We check for a window size, default to 100 if not specfied or out of bounds + windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue]; + + if (windowSize == 0 || windowSize > 512) + windowSize = 100; + + lastServerKey = nil; + // From the documention, if GetChanges is missing, we must assume it's a YES. // See http://msdn.microsoft.com/en-us/library/gg675447(v=exchg.80).aspx for all details. value = [theDocumentElement getElementsByTagName: @"GetChanges"]; @@ -712,10 +787,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { [self processSyncGetChanges: theDocumentElement inCollection: collection + withWindowSize: windowSize withSyncKey: syncKey withFolderType: folderType withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]] - inBuffer: changeBuffer]; + inBuffer: changeBuffer + lastServerKey: &lastServerKey]; } // @@ -745,7 +822,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // let's regenerate our SyncKey based on the collection tag. if ([changeBuffer length] || [commandsBuffer length]) { - davCollectionTag = [collection davCollectionTag]; + if (lastServerKey) + davCollectionTag = [collection davCollectionTagFromId: lastServerKey]; + else + davCollectionTag = [collection davCollectionTag]; + *changeDetected = YES; } diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index 0b4e7bfa2..991ff1327 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -702,7 +702,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [s appendString: @""]; [s appendFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]]; - [s appendFormat: @"%@", [[[currentBodyPart fetchBLOB] stringByEncodingBase64] stringByReplacingString: @"\n" withString: @""]]; + [s appendFormat: @"%@", [[currentBodyPart fetchBLOB] activeSyncRepresentation]]; [s appendString: @""]; [s appendString: @""]; @@ -735,7 +735,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - (void) processMeetingResponse: (id ) theDocumentElement inResponse: (WOResponse *) theResponse { - NSString *realCollectionId, *requestId, *participationStatus; + NSString *realCollectionId, *requestId, *participationStatus, *calendarId; + SOGoAppointmentObject *appointmentObject; SOGoMailObject *mailObject; NSMutableString *s; NSData *d; @@ -750,34 +751,75 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 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]; + requestId = [[(id)[theDocumentElement getElementsByTagName: @"RequestId"] lastObject] textValue]; + appointmentObject = nil; + calendarId = nil; + // Outlook 2013 calls MeetingResponse on the calendar folder! We have + // no way of handling as we can't retrieve the email (using the id found + // in requestId) in any mail folder! If that happens, let's simply + // assume it comes from the INBOX. This should be generally safe as people + // will answer email invitations as they receive them on their INBOX. + // Note that the mail should also still be there as MeetingResponse is + // called *before* MoveItems. // - // 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... - mailObject = [collection lookupName: requestId - inContext: context - acquire: 0]; - - if (![mailObject isKindOfClass: [NSException class]]) + // Apple iOS will also call MeetingResponse on the calendar folder when the + // user accepts/declines the meeting from the Calendar application. Before + // falling back on INBOX, we first check if we can find the event in the + // personal calendar. + if (folderType == ActiveSyncEventFolder) { - 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]] + appointmentObject = [collection lookupName: [requestId sanitizedServerIdWithType: ActiveSyncEventFolder] inContext: context acquire: NO]; + calendarId = requestId; + + // Object not found, let's fallback on the INBOX folder + if ([appointmentObject isKindOfClass: [NSException class]]) + { + folderType = ActiveSyncMailFolder; + realCollectionId = @"INBOX"; + appointmentObject = nil; + } + } + + // Fetch the appointment object from the mail message + if (!appointmentObject) + { + collection = [self collectionFromId: realCollectionId type: folderType]; + + // + // 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... + mailObject = [collection lookupName: requestId + inContext: context + acquire: 0]; + + if (![mailObject isKindOfClass: [NSException class]]) + { + iCalCalendar *calendar; + iCalEvent *event; + + calendar = [mailObject calendarFromIMIPMessage]; + event = [[calendar events] lastObject]; + calendarId = [event uid]; + + // Fetch the SOGoAppointmentObject + collection = [[context activeUser] personalCalendarFolderInContext: context]; + appointmentObject = [collection lookupName: [NSString stringWithFormat: @"%@.ics", [event uid]] + inContext: context + acquire: NO]; + } + } + + if (appointmentObject && + calendarId && + (![appointmentObject isKindOfClass: [NSException class]])) + { + // 1 -> accepted, 2 -> tentative, 3 -> declined if (userResponse == 1) participationStatus = @"ACCEPTED"; else if (userResponse == 2) @@ -785,29 +827,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. else participationStatus = @"DECLINED"; - if (![appointmentObject isKindOfClass: [NSException class]]) - { - [appointmentObject changeParticipationStatus: participationStatus - withDelegate: nil]; - - [s appendString: @""]; - [s appendString: @""]; - [s appendString: @""]; - [s appendString: @""]; - [s appendFormat: @"%@", requestId]; - [s appendFormat: @"%@", [event uid]]; - [s appendFormat: @"%d", status]; - [s appendString: @""]; - [s appendString: @""]; - - d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; - - [theResponse setContent: d]; - } - else - { - [theResponse setStatus: 500]; - } + [appointmentObject changeParticipationStatus: participationStatus + withDelegate: nil]; + + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @"%@", requestId]; + [s appendFormat: @"%@", calendarId]; + [s appendFormat: @"%d", status]; + [s appendString: @""]; + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; } else { @@ -1042,7 +1077,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [s appendString: @""]; [s appendFormat: @"%d", 1]; [s appendFormat: @"%@", [user cn]]; - [s appendFormat: @"%@", [user systemEmail]]; + [s appendFormat: @"%@", [[user allEmails] objectAtIndex: 0]]; // Freebusy structure: http://msdn.microsoft.com/en-us/library/gg663493(v=exchg.80).aspx [s appendString: @""]; diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index 5e1c70209..aaed77ea5 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import @@ -48,9 +49,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #include "iCalTimeZone+ActiveSync.h" +#include "NSData+ActiveSync.h" #include "NSDate+ActiveSync.h" #include "NSString+ActiveSync.h" +#include "../SoObjects/Appointments/iCalPerson+SOGo.h" #include "../SoObjects/Mailer/NSString+Mail.h" #include "../SoObjects/Mailer/SOGoMailBodyPart.h" @@ -133,39 +136,37 @@ struct GlobalObjectId { // // For debugging purposes... // -// - (NSString *) _uidFromGlobalObjectId: (NSData *) objectId -// { -// NSString *uid; +- (NSString *) _uidFromGlobalObjectId: (NSData *) objectId +{ + NSString *uid; -// struct GlobalObjectId *newGlobalId; -// NSUInteger length; -// uint8_t *bytes; + struct GlobalObjectId *newGlobalId; + NSUInteger length; + uint8_t *bytes; -// length = [objectId length]; -// uid = nil; + length = [objectId length]; + uid = nil; -// bytes = malloc(length*sizeof(uint8_t)); -// [objectId getBytes: bytes length: length]; + bytes = malloc(length*sizeof(uint8_t)); + [objectId getBytes: bytes length: length]; -// newGlobalId = (struct GlobalObjectId *)bytes; + newGlobalId = (struct GlobalObjectId *)bytes; -// // We must take the offset (dataPrefix) into account -// uid = [[NSString alloc] initWithBytes: newGlobalId->Data+12 length: newGlobalId->Size-12 encoding: NSASCIIStringEncoding]; -// free(bytes); - -// return AUTORELEASE(uid); -// } + // We must take the offset (dataPrefix) into account + uid = [[NSString alloc] initWithBytes: newGlobalId->Data+12 length: newGlobalId->Size-12 encoding: NSASCIIStringEncoding]; + free(bytes); + return AUTORELEASE(uid); +} // // // - (NSString *) _emailAddressesFrom: (NSArray *) enveloppeAddresses { - NSMutableArray *addresses; - NSString *rc; NGImap4EnvelopeAddress *address; - NSString *email; + NSMutableArray *addresses; + NSString *email, *rc; int i, max; rc = nil; @@ -177,7 +178,7 @@ struct GlobalObjectId { for (i = 0; i < max; i++) { address = [enveloppeAddresses objectAtIndex: i]; - email = [address email]; + email = [NSString stringWithFormat: @"\"%@\" <%@>", [address personalName], [address baseEMail]]; if (email) [addresses addObject: email]; @@ -317,10 +318,19 @@ struct GlobalObjectId { if (bodyPart) { + iCalCalendar *calendar; NSData *calendarData; calendarData = [bodyPart fetchBLOB]; - return [iCalCalendar parseSingleFromSource: calendarData]; + calendar = nil; + + NS_DURING + calendar = [iCalCalendar parseSingleFromSource: calendarData]; + NS_HANDLER + calendar = nil; + NS_ENDHANDLER + + return calendar; } } } @@ -344,25 +354,17 @@ struct GlobalObjectId { s = [NSMutableString string]; - // From - value = [self _emailAddressesFrom: [[self envelope] from]]; - if (value) - [s appendFormat: @"%@", [value activeSyncRepresentation]]; - // 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." value = [self _emailAddressesFrom: [[self envelope] to]]; if (value) [s appendFormat: @"%@", [value activeSyncRepresentation]]; - - // DisplayTo - [s appendFormat: @"\"%@\"", [[context activeUser] login]]; - // Cc - same syntax as the To field - value = [self _emailAddressesFrom: [[self envelope] cc]]; + // From + value = [self _emailAddressesFrom: [[self envelope] from]]; if (value) - [s appendFormat: @"%@", [value activeSyncRepresentation]]; - + [s appendFormat: @"%@", [value activeSyncRepresentation]]; + // Subject value = [self decodedSubject]; if (value) @@ -370,11 +372,19 @@ struct GlobalObjectId { [s appendFormat: @"%@", [value activeSyncRepresentation]]; [s appendFormat: @"%@", [value activeSyncRepresentation]]; } - + // DateReceived value = [self date]; if (value) [s appendFormat: @"%@", [value activeSyncRepresentationWithoutSeparators]]; + + // DisplayTo + [s appendFormat: @"%@", [[context activeUser] login]]; + + // Cc - same syntax as the To field + value = [self _emailAddressesFrom: [[self envelope] cc]]; + if (value) + [s appendFormat: @"%@", [value activeSyncRepresentation]]; // Importance - FIXME [s appendFormat: @"%@", @"1"]; @@ -382,47 +392,84 @@ struct GlobalObjectId { // Read [s appendFormat: @"%d", ([self read] ? 1 : 0)]; - // We handle MeetingRequest calendar = [self calendarFromIMIPMessage]; if (calendar) { + NSString *method, *className; + iCalPerson *attendee; iCalTimeZone *tz; iCalEvent *event; + + iCalPersonPartStat partstat; int v; event = [[calendar events] lastObject]; + method = [[event parent] method]; + + attendee = [event findAttendeeWithEmail: [[[context activeUser] allEmails] objectAtIndex: 0]]; + 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: + 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: @"%@", className]; [s appendString: @""]; [s appendFormat: @"%d", ([event isAllDay] ? 1 : 0)]; + + // StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx + if ([event startDate]) + [s appendFormat: @"%@", [[event startDate] activeSyncRepresentationWithoutSeparators]]; if ([event timeStampAsDate]) [s appendFormat: @"%@", [[event timeStampAsDate] activeSyncRepresentationWithoutSeparators]]; else if ([event created]) [s appendFormat: @"%@", [[event created] activeSyncRepresentationWithoutSeparators]]; - // StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx - if ([event startDate]) - [s appendFormat: @"%@", [[event startDate] activeSyncRepresentationWithoutSeparators]]; - // EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx if ([event endDate]) [s appendFormat: @"%@", [[event endDate] activeSyncRepresentationWithoutSeparators]]; - // Timezone - tz = [(iCalDateTime *)[event firstChildWithTag: @"dtstart"] timeZone]; - - if (!tz) - tz = [iCalTimeZone timeZoneForName: @"Europe/London"]; - - [s appendFormat: @"%@", [[tz activeSyncRepresentation] stringByReplacingString: @"\n" withString: @""]];; - [s appendFormat: @"%d", 0]; - [s appendFormat: @"%@", [[event organizer] rfc822Email]]; - [s appendFormat: @"%d", 1]; - + + // Location + if ([[event location] length]) + [s appendFormat: @"%@", [[event location] activeSyncRepresentation]]; + + [s appendFormat: @"%@", [[[event organizer] mailAddress] activeSyncRepresentation]]; + + // 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: @"%d", 0]; + // Sensitivity if ([[event accessClass] isEqualToString: @"PRIVATE"]) v = 2; @@ -433,23 +480,33 @@ struct GlobalObjectId { [s appendFormat: @"%d", v]; + [s appendFormat: @"%d", 2]; + + // Timezone + tz = [(iCalDateTime *)[event firstChildWithTag: @"dtstart"] timeZone]; + + if (!tz) + tz = [iCalTimeZone timeZoneForName: @"Europe/London"]; + + [s appendFormat: @"%@", [tz activeSyncRepresentation]]; + + // We disallow new time proposals [s appendFormat: @"%d", 1]; - - // We set the right message type - we must set AS version to 14.1 for this - //[s appendFormat: @"%d", 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 stringByEncodingBase64] stringByReplacingString: @"\n" withString: @""]]; - [s appendString: @""]; - - // MesssageClass and ContentClass - [s appendFormat: @"%@", @"IPM.Schedule.Meeting.Request"]; + [s appendFormat: @"%@", [globalObjId activeSyncRepresentation]]; + + // We set the right message type - we must set AS version to 14.1 for this + [s appendFormat: @"%d", 1]; + [s appendString: @""]; + + // ContentClass [s appendFormat: @"%@", @"urn:content-classes:calendarmessage"]; } else @@ -476,19 +533,29 @@ struct GlobalObjectId { if (d) { NSString *content; - int len; - + int len, truncated; + content = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding]; + + // 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. + if (!content) + content = [[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding]; + AUTORELEASE(content); - + content = [content activeSyncRepresentation]; + truncated = 0; + len = [content length]; [s appendString: @""]; [s appendFormat: @"%d", preferredBodyType]; [s appendFormat: @"%d", len]; [s appendFormat: @"%d", 0]; - [s appendFormat: @"%@", content]; + if (!truncated) + [s appendFormat: @"%@", content]; [s appendString: @""]; } @@ -555,8 +622,7 @@ struct GlobalObjectId { if ([o intValue]) [self addFlags: @"\\Flagged"]; else - [self removeFlags: @"\\Flagged"]; - + [self removeFlags: @"\\Flagged"]; } } diff --git a/ActiveSync/iCalEvent+ActiveSync.m b/ActiveSync/iCalEvent+ActiveSync.m index f9fc367b2..fbe6c564a 100644 --- a/ActiveSync/iCalEvent+ActiveSync.m +++ b/ActiveSync/iCalEvent+ActiveSync.m @@ -63,6 +63,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. s = [NSMutableString string]; + [s appendFormat: @"%d", ([self isAllDay] ? 1 : 0)]; + // DTStamp -- http://msdn.microsoft.com/en-us/library/ee219470(v=exchg.80).aspx if ([self timeStampAsDate]) [s appendFormat: @"%@", [[self timeStampAsDate] activeSyncRepresentationWithoutSeparators]]; @@ -83,18 +85,31 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (!tz) tz = [iCalTimeZone timeZoneForName: @"Europe/London"]; - [s appendFormat: @"%@", [[tz activeSyncRepresentation] stringByReplacingString: @"\n" withString: @""]];; + [s appendFormat: @"%@", [tz activeSyncRepresentation]]; - // Organizer + // Organizer and other invitations related properties if ((organizer = [self organizer])) { o = [organizer rfc822Email]; if ([o length]) - [s appendFormat: @"%@", o]; - - o = [organizer cn]; - if ([o length]) - [s appendFormat: @"%@", o]; + { + [s appendFormat: @"%@", o]; + + o = [organizer cn]; + if ([o length]) + [s appendFormat: @"%@", o]; + + + // This depends on the 'NEEDS-ACTION' parameter. + // This will trigger the SendMail command + [s appendFormat: @"%d", 1]; + [s appendFormat: @"%d", 5]; + [s appendFormat: @"%d", 3]; + [s appendFormat: @"%d", 1]; + + // BusyStatus -- http://msdn.microsoft.com/en-us/library/ee202290(v=exchg.80).aspx + [s appendFormat: @"%d", 2]; + } } // Attendees @@ -168,9 +183,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [s appendFormat: @"%d", v]; - // BusyStatus -- http://msdn.microsoft.com/en-us/library/ee202290(v=exchg.80).aspx - [s appendFormat: @"%d", 0]; - // Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx // TODO diff --git a/ActiveSync/iCalRecurrenceRule+ActiveSync.m b/ActiveSync/iCalRecurrenceRule+ActiveSync.m index f610360be..002a9d0cb 100644 --- a/ActiveSync/iCalRecurrenceRule+ActiveSync.m +++ b/ActiveSync/iCalRecurrenceRule+ActiveSync.m @@ -189,6 +189,55 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. return s; } +// +// +// +- (void) _setByDayFromValues: (NSDictionary *) theValues +{ + NSString *day; + id o; + + unsigned int day_of_week; + int i, week_of_month; + + o = [theValues objectForKey: @"Recurrence_DayOfWeek"]; + + // The documentation says WeekOfMonth must be between 1 and 5. The value + // 5 means "last week of month" + week_of_month = [[theValues objectForKey: @"Recurrence_WeekOfMonth"] intValue]; + + if (week_of_month > 4) + week_of_month = -1; + + // We find the correct day of the week + day_of_week = [o intValue]; + + for (i = 0; i < 7; i++) + { + if ((1< +#include "NSData+ActiveSync.h" + struct SYSTEMTIME { uint16_t wYear; uint16_t wMonth; @@ -157,7 +159,7 @@ struct SYSTEMTIME { [bytes appendBytes: &stDaylightDate length: 16]; [bytes appendBytes: &lDaylightBias length: 4]; - return [bytes stringByEncodingBase64]; + return [bytes activeSyncRepresentation]; } @end