/* MAPIStoreCalendarMessage.m - this file is part of SOGo * * Copyright (C) 2011-2012 Inverse inc * * Author: Wolfgang Sourdeau * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* TODO: - merge common code with tasks - take the tz definitions from Outlook */ #include #include #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import "iCalEvent+MAPIStore.h" #import "MAPIStoreAppointmentWrapper.h" #import "MAPIStoreCalendarAttachment.h" #import "MAPIStoreCalendarFolder.h" #import "MAPIStoreContext.h" #import "MAPIStoreMapping.h" #import "MAPIStoreRecurrenceUtils.h" #import "MAPIStoreTypes.h" #import "MAPIStoreUserContext.h" #import "NSDate+MAPIStore.h" #import "NSData+MAPIStore.h" #import "NSObject+MAPIStore.h" #import "NSString+MAPIStore.h" #import "NSValue+MAPIStore.h" #import "MAPIStoreCalendarMessage.h" #undef DEBUG #include #include #include #include #include #include #include // extern void ndr_print_AppointmentRecurrencePattern(struct ndr_print *ndr, const char *name, const struct AppointmentRecurrencePattern *r); static Class NSArrayK, MAPIStoreAppointmentWrapperK; @implementation SOGoAppointmentObject (MAPIStoreExtension) - (Class) mapistoreMessageClass { return [MAPIStoreCalendarMessage class]; } @end @implementation MAPIStoreCalendarMessage + (void) initialize { NSArrayK = [NSArray class]; MAPIStoreAppointmentWrapperK = [MAPIStoreAppointmentWrapper class]; } + (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP inMemCtx: (TALLOC_CTX *) memCtx { BOOL listedProperties[65536]; NSUInteger count; uint16_t propId; memset (listedProperties, NO, 65536 * sizeof (BOOL)); [super getAvailableProperties: propertiesP inMemCtx: memCtx]; for (count = 0; count < (*propertiesP)->cValues; count++) { propId = ((*propertiesP)->aulPropTag[count] >> 16) & 0xffff; listedProperties[propId] = YES; } [MAPIStoreAppointmentWrapper fillAvailableProperties: *propertiesP withExclusions: listedProperties]; return MAPISTORE_SUCCESS; } - (id) init { if ((self = [super init])) { calendar = nil; masterEvent = nil; } return self; } - (void) _setupAttachmentParts { NSUInteger count, max; NSArray *events; NSString *newKey; MAPIStoreCalendarAttachment *attachment; NSUInteger aid; iCalEvent *event; events = [calendar events]; max = [events count]; for (count = 1; count < max; count++) { attachment = [MAPIStoreCalendarAttachment mapiStoreObjectInContainer: self]; /* we now that there are no attachments yet, so we can assume that the right AID is 0 from the start */ aid = count - 1; [attachment setAID: aid]; event = [events objectAtIndex: count]; [attachment setEvent: event]; newKey = [[event uniqueChildWithTag: @"recurrence-id"] flattenedValuesForKey: @""]; [attachmentParts setObject: attachment forKey: newKey]; } } - (id) initWithSOGoObject: (id) newSOGoObject inContainer: (MAPIStoreObject *) newFolder { MAPIStoreContext *context; MAPIStoreUserContext *userContext; iCalCalendar *origCalendar; MAPIStoreAppointmentWrapper *appointmentWrapper; if ((self = [super initWithSOGoObject: newSOGoObject inContainer: newFolder])) { if ([newSOGoObject isNew]) { ASSIGN (calendar, [iCalCalendar groupWithTag: @"vcalendar"]); [calendar setVersion: @"2.0"]; [calendar setProdID: @"-//Inverse inc.//OpenChange+SOGo//EN"]; masterEvent = [iCalEvent groupWithTag: @"vevent"]; [calendar addChild: masterEvent]; [masterEvent setCreated: [NSCalendarDate date]]; } else { origCalendar = [sogoObject calendar: YES secure: YES]; if (!origCalendar) { [self errorWithFormat: @"Incorrect calendar event %@. Empty message is created", [self url]]; return self; } calendar = [origCalendar mutableCopy]; masterEvent = [[calendar events] objectAtIndex: 0]; [self _setupAttachmentParts]; } context = [self context]; userContext = [self userContext]; appointmentWrapper = [MAPIStoreAppointmentWrapper wrapperWithICalEvent: masterEvent andUser: [userContext sogoUser] andSenderEmail: nil withConnectionInfo: [context connectionInfo]]; [self addProxy: appointmentWrapper]; } return self; } - (void) dealloc { //NSLog(@"MAPIStoreCalendarMessage: -dealloc (%p)", self); [calendar release]; [super dealloc]; } - (MAPIStoreAppointmentWrapper *) _appointmentWrapper { NSUInteger i, max; id proxy; max = [proxies count]; for (i = 0; i < max; i++) { proxy = [proxies objectAtIndex: i]; if ([proxy isKindOfClass: MAPIStoreAppointmentWrapperK]) { return proxy; } } return nil; } /* getters */ - (int) getPidTagMessageClass: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { SOGoUser *owner; owner = [[self userContext] sogoUser]; if ([masterEvent userAsAttendee: owner]) *data = talloc_strdup (memCtx, "IPM.Schedule.Meeting.Request"); else *data = talloc_strdup (memCtx, "IPM.Appointment"); return MAPISTORE_SUCCESS; } - (int) getPidLidSideEffects: (void **) data // TODO inMemCtx: (TALLOC_CTX *) memCtx { *data = MAPILongValue (memCtx, seOpenToDelete | seOpenToCopy | seOpenToMove | seCoerceToInbox | seOpenForCtxMenu); return MAPISTORE_SUCCESS; } - (int) getPidTagProcessed: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { return [self getYes: data inMemCtx: memCtx]; } - (void) getMessageData: (struct mapistore_message **) dataPtr inMemCtx: (TALLOC_CTX *) memCtx { static NSString *recTypes[] = {@"orig", @"to", @"cc", @"bcc"}; static NSInteger recTypesValue[] = {MAPI_ORIG, MAPI_TO, MAPI_CC, MAPI_BCC}; struct mapistore_message *msgData; NSDictionary *recipients, *contactInfos; NSString *email; NSArray *attendees; NSDictionary *attendee; SOGoUserManager *mgr; struct mapistore_message_recipient *recipient; NSUInteger nRecType, maxRecTypes, count, max, propCount; NSObject *currentValue; [super getMessageData: &msgData inMemCtx: memCtx]; /* The presence of the "recipients" meta-property means that our most recent list of recipients has not been saved yet and that we must return that one instead of the one stored in the event. */ recipients = [properties objectForKey: @"recipients"]; if (recipients) { mgr = [SOGoUserManager sharedUserManager]; /* TODO: this code might need to be moved into MAPIStoreMessage */ msgData->columns = set_SPropTagArray (msgData, 9, PR_OBJECT_TYPE, PR_DISPLAY_TYPE, PR_7BIT_DISPLAY_NAME_UNICODE, PR_SMTP_ADDRESS_UNICODE, PR_SEND_INTERNET_ENCODING, PR_RECIPIENT_DISPLAY_NAME_UNICODE, PR_RECIPIENT_FLAGS, PR_RECIPIENT_ENTRYID, PR_RECIPIENT_TRACKSTATUS); maxRecTypes = sizeof(recTypes) / sizeof(recTypes[0]); for (nRecType = 0; nRecType < maxRecTypes; nRecType++) { attendees = [recipients objectForKey: recTypes[nRecType]]; max = [attendees count]; if (max > 0) { msgData->recipients_count += max; msgData->recipients = talloc_realloc(msgData, msgData->recipients, struct mapistore_message_recipient, msgData->recipients_count); recipient = msgData->recipients; for (count = 0; count < max; count++) { attendee = [attendees objectAtIndex: count]; recipient->type = recTypesValue[nRecType]; email = [attendee objectForKey: @"email"]; if (email) { contactInfos = [mgr contactInfosForUserWithUIDorEmail: email]; recipient->username = [[contactInfos objectForKey: @"c_uid"] asUnicodeInMemCtx: msgData]; } else recipient->username = NULL; recipient->data = talloc_array(msgData, void *, msgData->columns->cValues); for (propCount = 0; propCount < msgData->columns->cValues; propCount++) { currentValue = [attendee objectForKey: MAPIPropertyKey (msgData->columns->aulPropTag[propCount])]; [currentValue getValue: recipient->data + propCount forTag: msgData->columns->aulPropTag[propCount] inMemCtx: msgData]; } recipient++; } } } } else { [[self _appointmentWrapper] fillMessageData: msgData inMemCtx: memCtx]; } *dataPtr = msgData; } - (int) getPidTagResponseRequested: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { return [self getYes: data inMemCtx: memCtx]; } /* This three methods: getPidTagNormalizedSubject, getPidTagSensitivity and getPidTagImportance are implemented in MAPIStoreMessage base class, then the proxy method is not reached (see MAPIStoreObject). */ - (int) getPidTagNormalizedSubject: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { MAPIStoreAppointmentWrapper *appointmentWrapper; appointmentWrapper = [self _appointmentWrapper]; if (appointmentWrapper) return [appointmentWrapper getPidTagNormalizedSubject: data inMemCtx: memCtx]; return MAPISTORE_ERR_NOT_FOUND; } - (int) getPidTagSensitivity: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { MAPIStoreAppointmentWrapper *appointmentWrapper; appointmentWrapper = [self _appointmentWrapper]; if (appointmentWrapper) return [appointmentWrapper getPidTagSensitivity: data inMemCtx: memCtx]; return [self getLongZero: data inMemCtx: memCtx]; } - (int) getPidTagImportance: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { MAPIStoreAppointmentWrapper *appointmentWrapper; appointmentWrapper = [self _appointmentWrapper]; if (appointmentWrapper) return [appointmentWrapper getPidTagImportance: data inMemCtx: memCtx]; *data = MAPILongValue (memCtx, 1); return MAPISTORE_SUCCESS; } - (NSString *) _uidFromGlobalObjectId: (TALLOC_CTX *) memCtx { NSData *objectId; NSString *uid = nil; char *bytesDup, *uidStart; NSUInteger length; /* NOTE: we only handle the generic case at the moment, see MAPIStoreAppointmentWrapper */ objectId = [properties objectForKey: MAPIPropertyKey (PidLidGlobalObjectId)]; if (objectId) { length = [objectId length]; bytesDup = talloc_array (memCtx, char, length + 1); memcpy (bytesDup, [objectId bytes], length); bytesDup[length] = 0; uidStart = bytesDup + length - 1; while (uidStart != bytesDup && *(uidStart - 1)) uidStart--; if (uidStart > bytesDup && *uidStart) uid = [NSString stringWithUTF8String: uidStart]; talloc_free (bytesDup); } return uid; } - (SOGoAppointmentObject *) _resurrectRecord: (NSString *) cname fromFolder: (SOGoAppointmentFolder *) folder { NSArray *records; EOQualifier *qualifier; EOFetchSpecification *fs; GCSFolder *ocsFolder; SOGoAppointmentObject *newObject; NSMutableDictionary *newRecord; static NSArray *childRecordFields = nil; if (!childRecordFields) { childRecordFields = [NSArray arrayWithObjects: @"c_name", @"c_creationdate", @"c_lastmodified", @"c_content", nil]; [childRecordFields retain]; } ocsFolder = [folder ocsFolder]; qualifier = [EOQualifier qualifierWithQualifierFormat: [NSString stringWithFormat: @"c_name='%@'", cname]]; fs = [EOFetchSpecification fetchSpecificationWithEntityName: [ocsFolder folderName] qualifier: qualifier sortOrderings: nil]; records = [ocsFolder fetchFields: childRecordFields fetchSpecification: fs ignoreDeleted: NO]; if ([records isKindOfClass: NSArrayK] && [records count]) { newRecord = [[records objectAtIndex: 0] mutableCopy]; [newRecord setObject: [NSNumber numberWithInt: 0] forKey: @"c_version"]; newObject = [SOGoAppointmentObject objectWithRecord: newRecord inContainer: folder]; [newRecord autorelease]; [newObject setIsNew: NO]; } else newObject = nil; return newObject; } - (void) _fixupAppointmentObjectWithUID: (NSString *) uid { NSString *cname, *url; MAPIStoreMapping *mapping; uint64_t objectId; WOContext *woContext; SOGoAppointmentFolder *folder; SOGoAppointmentObject *newObject; cname = [[container sogoObject] resourceNameForEventUID: uid]; if (cname) isNew = NO; else cname = [NSString stringWithFormat: @"%@.ics", uid]; mapping = [self mapping]; url = [NSString stringWithFormat: @"%@%@", [container url], cname]; folder = [sogoObject container]; /* reinstantiate the old sogo object and attach it to self */ [[self userContext] activate]; woContext = [[self userContext] woContext]; if (isNew) { /* event could have been deleted, let's try to resurrect it */ newObject = [self _resurrectRecord: cname fromFolder: folder]; if (!newObject) { newObject = [SOGoAppointmentObject objectWithName: cname inContainer: folder]; [newObject setIsNew: YES]; } } else { /* dissociate the object url from the old object's id */ objectId = [mapping idFromURL: url]; [mapping unregisterURLWithID: objectId]; newObject = [folder lookupName: cname inContext: woContext acquire: NO]; } /* dissociate the object url associated with this object, as we want to discard it */ objectId = [self objectId]; [mapping unregisterURLWithID: objectId]; /* associate the new object url with this object id */ [mapping registerURL: url withID: objectId]; [newObject setContext: woContext]; ASSIGN (sogoObject, newObject); } - (BOOL) subscriberCanReadMessage { NSArray *roles; roles = [self activeUserRoles]; return ([roles containsObject: SOGoCalendarRole_ComponentViewer] || [roles containsObject: SOGoCalendarRole_ComponentDAndTViewer] || [self subscriberCanModifyMessage]); } - (BOOL) subscriberCanModifyMessage { BOOL rc; NSArray *roles = [self activeUserRoles]; if (isNew) rc = [roles containsObject: SOGoRole_ObjectCreator]; else rc = ([roles containsObject: SOGoCalendarRole_ComponentModifier] || [roles containsObject: SOGoCalendarRole_ComponentResponder]); return rc; } - (void) _updateAttachedEvents { NSArray *allAttachments; NSUInteger count, max; NSString *uid, *summary; iCalEvent *event; MAPIStoreCalendarAttachment *attachment; /* ensure that all exception events have the same UID as the master */ uid = [masterEvent uid]; summary = [masterEvent summary]; allAttachments = [attachmentParts allValues]; max = [allAttachments count]; for (count = 0; count < max; count++) { attachment = [allAttachments objectAtIndex: count]; event = [attachment event]; if ([[event summary] length] == 0) [event setSummary: summary]; [event setUid: uid]; } } - (void) save: (TALLOC_CTX *) memCtx { // iCalCalendar *vCalendar; // NSCalendarDate *now; NSString *uid, *nameInContainer; // iCalEvent *newEvent; // iCalPerson *userPerson; SOGoUser *activeUser; NSRange rangeOfDot; if (isNew) { uid = [self _uidFromGlobalObjectId: memCtx]; if (uid) { /* Hack required because of what's explained in oxocal 3.1.4.7.1: basically, Outlook creates a copy of the event and then removes the old instance. We perform a trickery to avoid performing those operations in the backend, in a way that enables us to recover the initial instance and act solely on it. */ [self _fixupAppointmentObjectWithUID: uid]; } else { /* We create a UID from the nameInContainer, or the reverse if the latter is already set... */ nameInContainer = [sogoObject nameInContainer]; if (nameInContainer) { rangeOfDot = [nameInContainer rangeOfString: @"." options: NSBackwardsSearch]; if (rangeOfDot.location == NSNotFound) uid = nameInContainer; else uid = [nameInContainer substringToIndex: rangeOfDot.location]; } else { uid = [SOGoObject globallyUniqueObjectId]; nameInContainer = [NSString stringWithFormat: @"%@.ics", uid]; [sogoObject setNameInContainer: nameInContainer]; } } [masterEvent setUid: uid]; } // [self logWithFormat: @"-save, event props:"]; // MAPIStoreDumpMessageProperties (newProperties); // now = [NSCalendarDate date]; activeUser = [[self context] activeUser]; [masterEvent updateFromMAPIProperties: properties inUserContext: [self userContext] withActiveUser: activeUser inMemCtx: memCtx]; [self _updateAttachedEvents]; [[self userContext] activate]; [sogoObject updateContentWithCalendar: calendar fromRequest: nil]; [self updateVersions]; } - (id) lookupAttachment: (NSString *) childKey { return [attachmentParts objectForKey: childKey]; } - (MAPIStoreAttachment *) createAttachment { MAPIStoreCalendarAttachment *newAttachment; uint32_t newAid; NSString *newKey; iCalEvent *newEvent; newAid = [[self attachmentKeys] count]; newAttachment = [MAPIStoreCalendarAttachment mapiStoreObjectInContainer: self]; [newAttachment setAID: newAid]; newEvent = [iCalEvent groupWithTag: @"vevent"]; [newAttachment setEvent: newEvent]; [calendar addToEvents: newEvent]; newKey = [NSString stringWithFormat: @"%ul", newAid]; [attachmentParts setObject: newAttachment forKey: newKey]; [attachmentKeys release]; attachmentKeys = nil; return newAttachment; } - (enum mapistore_error) setReadFlag: (uint8_t) flag { return MAPISTORE_SUCCESS; } @end