diff --git a/ChangeLog b/ChangeLog index b4f8adfdf..3da10f966 100644 --- a/ChangeLog +++ b/ChangeLog @@ -45,6 +45,28 @@ list element, we now use the title attribute. This fixes an issue with IE that would append an extra space to the filename. +2012-02-03 Wolfgang Sourdeau + + * OpenChange/MAPIStoreFolderTable.m + (-evaluatePropertyRestriction:intoQualifier:): overriden method to + always return true for restrictions on PR_CHANGE_NUM, in order to + help the fxics mechanism. + + * OpenChange/MAPIStoreFolder.m (-deleteFolder): cleanup the caches + in order to let the folder disappear. + + * OpenChange/MAPIStoreSOGo.m (sogo_backend_create_root_folder): + new backend method. + + * OpenChange/MAPIStoreFolder.m (-createFolder:withFID:andKey:): + modified method to return a enum mapistore_error, in order to + better determine of the failures that occur. + + * OpenChange/MAPIStoreMailFolder.m (-deleteFolder): overriden method. + + * SoObjects/Mailer/SOGoMailFolder.m (-exists): new method that + returns whether an IMAP folder exists or not yet. + 2012-02-02 Francis Lachapelle * UI/MailerUI/UIxMailMainFrame.m (-composeAction): recipient @@ -54,6 +76,32 @@ received "mailto" parameter is now a JSON-formatted string; we parse it accordingly. +2012-02-01 Wolfgang Sourdeau + + * OpenChange/MAPIStoreUserContext.m (-destroy): dont't release + "username" as it is not initially retained. + (+userContextWithUsername:andTDBIndexing:): the resulting instance + is cached in the table from here instead. + + * OpenChange/MAPIStoreTable.m (-destroyHandle:): skih operation if + the handle parameter is 0. + + * OpenChange/SOGoMAPIFSMessage.m (-delete): returns the exception + rather than raising it. + + * OpenChange/SOGoMAPIFSFolder.m (-delete): new method that removes + the directory from the filesystem. + + * OpenChange/MAPIStoreGCSFolder.m (-deleteFolder): overriden method. + + * OpenChange/MAPIStoreFolder.m (-deleteFolder): implemented method. + + * SoObjects/SOGo/SOGoParentFolder.m (-removeSubFolder): new method + that removes a subfolder entry from the folder cache. + + * OpenChange/MAPIApplication.m (-setUserContext:): do not retain + the user context. + 2012-02-01 Francis Lachapelle * UI/WebServerResources/UIxCalUserRightsEditor.js (onUpdateACL): @@ -75,6 +123,40 @@ (-pathToLocaleForLanguageNamed:): if the language has a CamelCase form, add the first part to the lookup languages. +2012-01-31 Wolfgang Sourdeau + + * OpenChange/MAPIStoreMailContext.m + (+listContextsForUser:withTDBIndexing:inMemCtx:): now returns + secondary folders. + (+[MAPIStoreOutboxContext + listContextsForUser:withTDBIndexing:inMemCtx:]): overridden method + in outbox-specific class. + +2012-01-30 Wolfgang Sourdeau + + * OpenChange/MAPIStoreMailFolder.m (-addProperties): overriden + method in order to intercept rename operations. + (-initWithSOGoObject:inContainer:): removed obsolete method. + + * OpenChange/MAPIStoreGCSFolder.m (-addProperties): overriden + method in order to intercept rename operations. + + * SoObjects/Mailer/SOGoMailFolder.m (-renameTo:): new method, + implementing most of the code from [UIxMailFolderActions + renameFolderAction]. + + * OpenChange/MAPIStoreFallbackContext.m + (+listContextsForUser:withTDBIndexing:inMemCtx:): now returns the + fallback subfolders as secondary contexts (tmp hack). + + * OpenChange/MAPIStoreUserContext.m (-rootFolders): new method + replacing the "...root" methods in way that can match the + MAPIModuleName. + + * OpenChange/MAPIStoreGCSBaseContext.m + (+listContextsForUser:inMemCtx:): centralized code for all GCS + classes. + 2012-01-30 Francis Lachapelle * UI/WebServerResources/UIxCalUserRightsEditor.js (onUpdateACL): @@ -92,6 +174,47 @@ function now accepts two additional arguments to change the default "yes" and "no" buttons. +2012-01-29 Wolfgang Sourdeau + + * OpenChange/MAPIStoreMailContext.[hm]: removed + "MAPIStoreInboxContext", "MAPIStoreSentItemsContext", + and child folders."MAPIStoreDraftsContext" and + "MAPIStoreOutboxContext" which have made obsolete by the new + provisioning and instantiation mechanisms. + + * OpenChange/MAPIStoreContext.m (-woContext:, -mapping) + (-authenticator): those methods are now part of + MAPIStoreUserContext. + (-setupBaseFolder:): removed method, obsoleted by those below. + (-getRootFolder:withFID:): the "root folder" is now instantiated + by lookups from the root folder provided by the user context and + the chain of folders listed in the context url path. + (-MAPIStoreFolderClass): new method returning the Class object + representing the context's class of objects. + (-rootSOGoFolder): new method that returns the proper root folder + depending on the context's class of objects. + + * OpenChange/MAPIStoreFolder.m (-initWithURL:inContext:): all + folders are now instantiated the same way since root objects are + now stored in the MAPIStoreUserContext instancesB. + (-setContext:): new setter to provide a reference to the folder's + mapistore context instance from the topmost parent. + (-setupVersionsMessage:): new helper method invoked during the + folder instantiations, from the moment its parent context has been + made available, which does not occur at the same moment for parent + and child folders... + + * OpenChange/MAPIApplication.m (-init): removed the "mapiContext" + but added the "userContext" ivars. + + * SoObjects/SOGo/SOGoFolder.m (-outlookFolderClass): removed + useless method. + + * OpenChange/MAPIStoreUserContext.[hm]: new class for accessing + user data common to all "mapistore contexts" as a singleton: the + same instance is used across all requests until all related + objects have been freed. + 2012-01-27 Ludovic Marcotte * SoObjects/Appointments/SOGoAppointmentFolder.m @@ -102,6 +225,34 @@ (_folderCalenars) - we now ask for the c_content and use a local autorelease pool to avoid consuming too much memory +2012-01-26 Wolfgang Sourdeau + + * OpenChange/MAPIStoreCommonViewsContext.[hm], + OpenChange/MAPIStoreDeferredActionsContext.[hm], + OpenChange/MAPIStoreFreebusyContext.[hm], + OpenChange/MAPIStoreJournalContext.[hm], + OpenChange/MAPIStoreRemindersContext.[hm], + OpenChange/MAPIStoreScheduleContext.[hm], + OpenChange/MAPIStoreSearchContext.[hm], + OpenChange/MAPIStoreShortcutsContext.[hm], + OpenChange/MAPIStoreSpoolerContext.[hm], + OpenChange/MAPIStoreViewsContext.[hm]: deleted obsolete classes. + + * OpenChange/MAPIStoreFolder.m (supportsSubFolders): new + overridable method that returns whether the current folder can + contain subfolders, nowithstanding the right of the current user + to create or access them. + + * OpenChange/MAPIStoreSOGo.m (sogo_backend_list_contexts): new + backend method. + + * OpenChange/MAPIStoreContext.m + (+listAllContextsForUser:inMemCtx:): centralized method for + returning all contexts available from all context classes for one + user. + (+listContextsForUser:inMemCtx:): new individual method invoked by + the above. Overridden by concrete subclasses. + 2012-01-26 Ludovic Marcotte * SoObjects/SOGo/LDAPSource.{h,m} - now honor @@ -137,6 +288,12 @@ menu is disabled. When switching to text-based message, the popup menu is now correctly re-enabled. +2012-01-16 Wolfgang Sourdeau + + * SoObjects/SOGo/SQLSource.m (_lookupContactEntry:considerEmail:) + (fetchContactsMatching:): assigned self to the "source" key of the + returned dictionaries. + 2012-01-13 Francis Lachapelle * SoObjects/Appointments/SOGoAppointmentFolder.m @@ -198,16 +355,115 @@ when a contact uid is specified. Otherwise, perform the query on the user instance as usual (/SOGo/so//freebusy.ifb/ajaxRead). +2012-01-12 Wolfgang Sourdeau + + * OpenChange/MAPIStoreCalendarMessage.m (-save): same as below + + ensure that nil fields are removed from non-new instances. + + * OpenChange/MAPIStoreAppointmentWrapper.m + (-getPrStartDate:inMemCtx:, -getPrEndDate:inMemCtx:): remove the + tz offset from dates in all-day events. + + * OpenChange/MAPIStoreTasksMessage.m (-save): the dates provided + by Outlook for start, due and completed are all-day dates, we thus + need to remove the timezone offset from those dates. + +2012-01-11 Wolfgang Sourdeau + + * OpenChange/MAPIStoreGCSMessageTable.m + (_fixedDatePropertyRestriction:inMemCtx:): attached the result to + the memCtx passed as parameter to avoid a leak. + 2012-01-10 Wolfgang Sourdeau * SoObjects/Appointments/SOGoAppointmentObject.m (_requireResponseFromAttendees:): initialize listHasChanged to NO. + * OpenChange/MAPIStoreMailFolder.m (_parseCOPYUID): the uniString + buffer was allocated one byte too short. + +2012-01-09 Wolfgang Sourdeau + + * OpenChange/NSObject+MAPIStore.m (-getValue:forTag:inMemCtx:): + handle PT_SVREID just as PT_BINARY. + + * OpenChange/MAPIStoreCalendarFolder.m (-exchangeRightsForRoles): + add the freebusy read rights when the user has read permission on + calendar objects. + 2012-01-05 Francis Lachapelle * SoObjects/SOGo/SOGoUserManager.m (-_registerSource:inDomain::): log error when duplicated IDs are found. +2012-01-04 Wolfgang Sourdeau + + * UI/WebServerResources/ContactsUI.js + (onAddressBooksMenuPrepareVisibility): the "new list", "sharing" and "import" + options are now greyed out properly, depending on the object type + and the new attributes below. + + * UI/Contacts/UIxContactFoldersView.m + (-currentContactFolderAclEditing) + (-currentContactFolderListEditing): new attribute accessors. + + * SoObjects/SOGo/SOGoFolder.m (-sendFolderAdvisoryTemplate:): + moved method from SOGoGCSFolder in order to make it available to + other folder classes. + + * SoObjects/SOGo/LDAPSource.m (-initFromUDSource:inDomain:): use + the new setters for certain ivars + take the new "abOU" key into + account. + (-setListRequiresDot:, -listRequiresDot:, -setSourceID:) + (-setDisplayName, -displayName, -setModifiers:): new accessors. + (-_convertRecordToLDAPAttributes): we now strip object classes that + are not supported by the server prior to remove the related fields. + (-hasUserAddressBooks): new method that returns whether user + addressbooks are supported, i.e. when "abOU" is set. + (-addressBookSourcesForUser:): when "abOU" is set, returns an + array of LDAPSource instances representing the personal + addressbooks of the specified user. + (-addAddressBookSource:withDisplayName:forUser:) + (-renameAddressBookSource:withDisplayName:forUser:) + (-removeAddressBookSource:forUser:): new methods with a + self-explicit name. + + * SoObjects/Contacts/SOGoContactSourceFolder.m + (-setIsPersonalSource, -isPersonalSource): new accessors for the + "isPersonalSource ivar". + (-lookupName:inContext:acquire:): setup the object classes of the + new entries to "inetorgperson" and "mozillaabpersonalpha". + (-lookupContactsWithFilter:onCriteria:sortBy:ordering:): check + whether "listRequiresDot" is set on the current source and return + the full listing if not required. + (-compare:): enhanced to treat personal sources as if they were + regular GCS folders, in order to sort them properly. + (-delete, -renameTo:): implemented method, required for the + corresponding web methods. + (-ownerInContext:) adapted method to personal sources. + + * SoObjects/Contacts/SOGoContactFolders.m (-appendPersonalSource): + overriden method for returning LDAP-based user addresbook sources. + (-newFolderWithName:andNameInContainer:): idem + (-renameLDAPAddressBook:withDisplayName:): new method that enables + the renaming of LDAP-based user addresbook sources. + (-removeLDAPAddressBook:): new method that enables + the removal of LDAP-based user addresbook sources. + +2012-01-03 Wolfgang Sourdeau + + * SoObjects/SOGo/SOGoParentFolder.m (-appendPersonalSources): made + method public so that it can be easily overriden in subclasses. + + * SoObjects/SOGo/SOGoUser.m (-authenticationSource): new method + that returned the SOGoSource instance that successfully recognized + the user represented by the current instance. + + * SoObjects/SOGo/SOGoUserManager.m + (_fillContactInfosForUser:withUIDorEmail:inDomain:): we now set + the identifier of the source that authenticated the specified user + as the "SOGoSource" entry of the returned dictionary. + 2012-01-03 Francis Lachapelle * SoObjects/Appointments/SOGoAptMailNotification.m (-setupValues): @@ -231,6 +487,109 @@ * UI/WebServerResources/UIxFilterEditor.js (ensureFieldValidity): a field value is always considered invalid when empty. +2011-12-30 Wolfgang Sourdeau + + * SoObjects/SOGo/LDAPSourceSchema.[hm]: new class module enabling + schema auto-discovery. + + * UI/WebServerResources/UIxContactEditor.js + (validateContactEditor): the birth date is validated slightly + differently, by enabling empty and 2-digit years as well as single + digits months and days. + + * UI/Contacts/UIxContactView.m (-defaultAction, -dealloc): retain + and release the "card" ivar. + (_formattedURL:): the url should be displayed only if it is + non-nil AND non-empty. + (-vcardAction): removed useless method. + + * UI/Contacts/UIxContactEditor.m (init): removed the "snapshot", + "preferredEmail", "card", "photosURL" and "contactCategories" ivars. + (-ldifRecord): new getter that proxy the invocation to the client + object, but by taking the contactEmail and contactFN url + parameters. Replaces the "-snapshot" getter, since the LDIF record + is now directly edited. + (-addressBooksList): the client object container class is no + longer taken into account when fetching the current user's permissions. + (-supportCategories, -supportPhotos): new getters that enables the + categories and photo tabs. + (-setJsonContactCategories:, -jsonContactCategories): now make + use of the special "vcardcategories" parameter found in the + contact LDIF record. + + * SoObjects/SOGo/SQLSource.m (_lookupContactEntry:considerEmail:): + enhanced to copy the "c_XX" fields to unprefixed equivalents. + + * SoObjects/Contacts/NSString+LDIF.m (mustEncodeLDIFValue): new + method, replacing "_isLDIFSafe" in a clearly public manner. + + * SoObjects/SOGo/LDAPSource.m (-[NGLdapEntry _asDictionary]): new + utility method to convert an entry into a "SOGo LDIF record". + (-[NGLdapAttribute _asArrayOrString]): new utility method to + convert an LDAP attribute into a string or an array of strings + when the attribute has one or more values. + (-initFromUDSource:inDomain:): handle the new "modifiers", + "mapping" and "objectClasses" configuration keys, used when the + source instance is used as an addressbook. All LDAP fields are now + converted to lowercase. + (_searchAttributes): removed method as the special "*" attribute + is not costly enough to justify its existence, thereby reducing + code complexity. + (-lookupContactEntry:, -lookupContactEntryWithUIDorEmail:) + (-lookupLoginByDN:): merged common code in the new + -_lookupLDAPEntry: method, that accepts an EOQualifier as argument. + (--addContactEntry:withID:, -updateContactEntry: and + removeContactEntryWithID:): new methods for editing addressbook + sources. + + * SoObjects/Contacts/SOGoContactSourceFolder.m (-source): new + getter for the "source" ivar. + (-lookupName:inContext:acquire:): accept the creation of new LDIF + entries via web methods ending with "AsContact". + (-saveLDIFEntry:, -deleteLDIFEntry:): new proxy methods for the + new SOGoSource -addContactEntry:withID:, -updateContactEntry: and + removeContactEntryWithID: methods, enabling the creation, + modification and deletion of LDAP contacts. + (-aclsForUser:): implemented method based on the array returned by + -[ modifiers]. + + * SoObjects/Contacts/SOGoContactLDIFEntry.m (-vCard): the vcard is + now generated automatically from the LDIF record of the entry, + using the new method provided by NGVCard+SOGo. + (-aclsForUser:): new overriden method similar to the + implementation from SOGoContentObject. + + * SoObjects/Contacts/SOGoContactGCSEntry.m (-setLDIFRecord) + (-ldifRecord, -hasPhoto): new accessors required by the + SOGoContactObject protocol. + (-lookupName:inContext:acquire:): return the only photo element + when the "photo" key is requested, if present in the card. + (-save): now returns an NSException, instead of void. + + * SoObjects/Contacts/SOGoContactEntryPhoto.m + (+entryPhotoWithID:inContainer:, -setPhotoID:): removed methods, + since there can only be one PHOTO element per contact. + (-photo): simplified thanks to the constraint mentionned above. + + * SoObjects/Contacts/NSDictionary+LDIF.m (-ldifRecordAsString): + new method implementing the code previously found in + SOGo/NSDictionary+Utilities.m, in order to produce a textual + representation of an LDIF record. + + * SoObjects/Contacts/NSDictionary+LDIF.[hm]: new category module. + + * SoObjects/Contacts/NGVCard+SOGo.m (-asLDIFRecord): new method + implementing the conversion code previously found in + UIxContactEditor, in order to produce an "LDIF record" in the form + of an NSDictionary matching the inetOrgPerson and + mozillaAbPersonAlpha object classes. + (-updateFromLDIFRecord:) reciprocal method to "-asLDIFRecord", + with conversion code moved from UIxContactEditor. + + * SoObjects/Contacts/SOGoFolder+CardDAV.m (_isValidFilter:): make + use of the lowercase instance of the string, which was erroneously + ignored previously. + 2011-12-29 Ludovic Marcotte * SoObjects/SOGo/SOGoSQLUserProfile.m (_sqlJsonRepresentation:): diff --git a/OpenChange/GNUmakefile b/OpenChange/GNUmakefile index 7452c39fc..0f3fb1a39 100644 --- a/OpenChange/GNUmakefile +++ b/OpenChange/GNUmakefile @@ -39,6 +39,7 @@ $(SOGOBACKEND)_OBJC_FILES += \ MAPIStoreTypes.m \ MAPIStorePropertySelectors.m \ MAPIStoreSamDBUtils.m \ + MAPIStoreUserContext.m \ \ SOGoMAPIVolatileMessage.m \ SOGoMAPIFSFolder.m \ @@ -102,17 +103,7 @@ $(SOGOBACKEND)_OBJC_FILES += \ MAPIStoreNotesFolder.m \ MAPIStoreNotesMessage.m \ \ - MAPIStoreCommonViewsContext.m \ - MAPIStoreDeferredActionsContext.m \ MAPIStoreFallbackContext.m \ - MAPIStoreFreebusyContext.m \ - MAPIStoreJournalContext.m \ - MAPIStoreRemindersContext.m \ - MAPIStoreScheduleContext.m \ - MAPIStoreSearchContext.m \ - MAPIStoreShortcutsContext.m \ - MAPIStoreSpoolerContext.m \ - MAPIStoreViewsContext.m \ \ NSArray+MAPIStore.m \ NSData+MAPIStore.m \ @@ -172,12 +163,12 @@ LIBMAPISTORE_CFLAGS = $(shell pkg-config libmapistore --cflags) -DSAMBA_PREFIX=" LIBMAPISTORE_LIBS = $(shell pkg-config libmapistore --libs) -lmapiproxy $(MAPISTORESOGO)_INSTALL_DIR = $(DESTDIR)/$(SAMBA_LIB_DIR)/mapistore_backends -$(MAPISTORESOGO)_LDFLAGS += \ +$(MAPISTORESOGO)_LIB_DIRS += \ -L../SoObjects/SOGo/SOGo.framework/ -lSOGo \ $(LIBMAPI_LIBS) \ $(LIBMAPISTORE_LIBS) -$(SOGOBACKEND)_LDFLAGS += \ +$(SOGOBACKEND)_LIB_DIRS += \ -L../OGoContentStore/$(GNUSTEP_OBJ_DIR)/ -lOGoContentStore \ -L../SoObjects/SOGo/SOGo.framework/ -lSOGo \ $(LIBMAPI_LIBS) \ diff --git a/OpenChange/MAPIApplication.h b/OpenChange/MAPIApplication.h index 83e02fe6b..2b30e692c 100644 --- a/OpenChange/MAPIApplication.h +++ b/OpenChange/MAPIApplication.h @@ -25,16 +25,16 @@ #import -@class MAPIStoreContext; +@class MAPIStoreUserContext; @interface MAPIApplication : SoApplication { - MAPIStoreContext *mapiContext; + MAPIStoreUserContext *userContext; } - (id) authenticatorInContext: (id) context; -- (void) setMAPIStoreContext: (MAPIStoreContext *) newMAPIStoreContext; +- (void) setUserContext: (MAPIStoreUserContext *) newContext; @end diff --git a/OpenChange/MAPIApplication.m b/OpenChange/MAPIApplication.m index d3002940e..8cdce056d 100644 --- a/OpenChange/MAPIApplication.m +++ b/OpenChange/MAPIApplication.m @@ -27,7 +27,7 @@ #import -#import "MAPIStoreContext.h" +#import "MAPIStoreUserContext.h" #import "MAPIApplication.h" @@ -67,20 +67,16 @@ MAPIApplication *MAPIApp = nil; return MAPIApp; } -- (void) dealloc +- (void) setUserContext: (MAPIStoreUserContext *) newContext { - [mapiContext release]; - [super dealloc]; -} - -- (void) setMAPIStoreContext: (MAPIStoreContext *) newMAPIStoreContext -{ - ASSIGN (mapiContext, newMAPIStoreContext); + /* user contexts must not be retained here ad their holder (mapistore) + contexts must be active when any operation occurs. */ + userContext = newContext; } - (id) authenticatorInContext: (id) context { - return [mapiContext authenticator]; + return [userContext authenticator]; } @end diff --git a/OpenChange/MAPIStoreAppointmentWrapper.m b/OpenChange/MAPIStoreAppointmentWrapper.m index c1f0e8b90..947e2119b 100644 --- a/OpenChange/MAPIStoreAppointmentWrapper.m +++ b/OpenChange/MAPIStoreAppointmentWrapper.m @@ -638,11 +638,19 @@ static NSCharacterSet *hexCharacterSet = nil; inMemCtx: (TALLOC_CTX *) memCtx { NSCalendarDate *dateValue; + NSInteger offset; if ([event isRecurrent]) dateValue = [event firstRecurrenceStartDate]; else dateValue = [event startDate]; + if ([event isAllDay]) + { + offset = -[timeZone secondsFromGMTForDate: dateValue]; + dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: offset]; + } [dateValue setTimeZone: utcTZ]; *data = [dateValue asFileTimeInMemCtx: memCtx]; @@ -882,16 +890,18 @@ static NSCharacterSet *hexCharacterSet = nil; inMemCtx: (TALLOC_CTX *) memCtx { NSCalendarDate *dateValue; + NSInteger offset; if ([event isRecurrent]) dateValue = [event firstRecurrenceStartDate]; else dateValue = [event startDate]; - dateValue - = [dateValue dateByAddingYears: 0 months: 0 days: 0 - hours: 0 minutes: 0 - seconds: (NSInteger) [event - durationAsTimeInterval]]; + offset = [event durationAsTimeInterval]; + if ([event isAllDay]) + offset -= [timeZone secondsFromGMTForDate: dateValue]; + dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: offset]; [dateValue setTimeZone: utcTZ]; *data = [dateValue asFileTimeInMemCtx: memCtx]; diff --git a/OpenChange/MAPIStoreAttachment.m b/OpenChange/MAPIStoreAttachment.m index 483944a1c..ca9076ad0 100644 --- a/OpenChange/MAPIStoreAttachment.m +++ b/OpenChange/MAPIStoreAttachment.m @@ -100,7 +100,7 @@ mapistoreMsg = talloc_zero (memCtx, struct mapistore_message); - mapping = [[self context] mapping]; + mapping = [self mapping]; attMessage = [self openEmbeddedMessage]; if (attMessage) diff --git a/OpenChange/MAPIStoreCalendarContext.m b/OpenChange/MAPIStoreCalendarContext.m index b3cbb2899..16018ce85 100644 --- a/OpenChange/MAPIStoreCalendarContext.m +++ b/OpenChange/MAPIStoreCalendarContext.m @@ -21,24 +21,39 @@ */ #import +#import #import "MAPIStoreMapping.h" #import "MAPIStoreCalendarFolder.h" +#import "MAPIStoreUserContext.h" #import "MAPIStoreCalendarContext.h" +#undef DEBUG +#include + +static Class MAPIStoreCalendarFolderK; + @implementation MAPIStoreCalendarContext ++ (void) initialize +{ + MAPIStoreCalendarFolderK = [MAPIStoreCalendarFolder class]; +} + + (NSString *) MAPIModuleName { return @"calendar"; } -- (void) setupBaseFolder: (NSURL *) newURL ++ (enum mapistore_context_role) MAPIContextRole { - baseFolder = [MAPIStoreCalendarFolder baseFolderWithURL: newURL - inContext: self]; - [baseFolder retain]; + return MAPISTORE_CALENDAR_ROLE; +} + +- (Class) MAPIStoreFolderClass +{ + return MAPIStoreCalendarFolderK; } @end diff --git a/OpenChange/MAPIStoreCalendarFolder.m b/OpenChange/MAPIStoreCalendarFolder.m index 8ed3f19ed..f2ec0a4dd 100644 --- a/OpenChange/MAPIStoreCalendarFolder.m +++ b/OpenChange/MAPIStoreCalendarFolder.m @@ -24,6 +24,7 @@ #import #import #import +#import #import #import #import @@ -42,37 +43,6 @@ @implementation MAPIStoreCalendarFolder -- (id) initWithURL: (NSURL *) newURL - inContext: (MAPIStoreContext *) newContext -{ - SOGoUserFolder *userFolder; - SOGoAppointmentFolders *parentFolder; - WOContext *woContext; - - if ((self = [super initWithURL: newURL - inContext: newContext])) - { - woContext = [newContext woContext]; - userFolder = [SOGoUserFolder objectWithName: [newURL user] - inContainer: MAPIApp]; - [parentContainersBag addObject: userFolder]; - [woContext setClientObject: userFolder]; - - parentFolder = [userFolder lookupName: @"Calendar" - inContext: woContext - acquire: NO]; - [parentContainersBag addObject: parentFolder]; - [woContext setClientObject: parentFolder]; - - sogoObject = [parentFolder lookupName: @"personal" - inContext: woContext - acquire: NO]; - [sogoObject retain]; - } - - return self; -} - - (MAPIStoreMessageTable *) messageTable { [self synchroniseCache]; @@ -132,6 +102,8 @@ [roles addObject: SOGoCalendarRole_ConfidentialViewer]; } + // [self logWithFormat: @"roles for rights %.8x = (%@)", rights, roles]; + return roles; } @@ -150,9 +122,11 @@ else if ([roles containsObject: SOGoCalendarRole_PublicViewer] && [roles containsObject: SOGoCalendarRole_PrivateViewer] && [roles containsObject: SOGoCalendarRole_ConfidentialViewer]) - rights |= RightsReadItems; + rights |= RightsReadItems | 0x1800; if (rights != 0) rights |= RoleNone; /* actually "folder visible" */ + + // [self logWithFormat: @"rights for roles (%@) = %.8x", roles, rights]; return rights; } diff --git a/OpenChange/MAPIStoreCalendarMessage.m b/OpenChange/MAPIStoreCalendarMessage.m index 263e60d30..a27ae244c 100644 --- a/OpenChange/MAPIStoreCalendarMessage.m +++ b/OpenChange/MAPIStoreCalendarMessage.m @@ -56,6 +56,7 @@ #import "MAPIStoreMapping.h" #import "MAPIStoreRecurrenceUtils.h" #import "MAPIStoreTypes.h" +#import "MAPIStoreUserContext.h" #import "NSDate+MAPIStore.h" #import "NSData+MAPIStore.h" #import "NSObject+MAPIStore.h" @@ -106,14 +107,16 @@ { iCalEvent *event; MAPIStoreContext *context; + MAPIStoreUserContext *userContext; if (!appointmentWrapper) { event = [sogoObject component: NO secure: YES]; context = [self context]; + userContext = [self userContext]; ASSIGN (appointmentWrapper, [MAPIStoreAppointmentWrapper wrapperWithICalEvent: event - andUser: [context ownerUser] + andUser: [userContext sogoUser] andSenderEmail: nil inTimeZone: [self ownerTimeZone] withConnectionInfo: [context connectionInfo]]); @@ -207,7 +210,8 @@ - (int) getPidLidAppointmentSubType: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { - return [[self appointmentWrapper] getPidLidAppointmentSubType: data inMemCtx: memCtx]; + return [[self appointmentWrapper] getPidLidAppointmentSubType: data + inMemCtx: memCtx]; } - (int) getPidLidBusyStatus: (void **) data // TODO @@ -550,7 +554,7 @@ existingCName = [[container sogoObject] resourceNameForEventUID: uid]; if (existingCName) { - mapping = [[self context] mapping]; + mapping = [self mapping]; /* dissociate the object url from the old object's id */ existingURL = [NSString stringWithFormat: @"%@%@", @@ -567,7 +571,7 @@ [mapping registerURL: existingURL withID: objectId]; /* reinstantiate the old sogo object and attach it to self */ - woContext = [[self context] woContext]; + woContext = [[self userContext] woContext]; existingObject = [[container sogoObject] lookupName: existingCName inContext: woContext acquire: NO]; @@ -653,6 +657,7 @@ iCalEvent *newEvent; iCalPerson *userPerson; NSUInteger responseStatus = 0; + NSInteger tzOffset; SOGoUser *activeUser, *ownerUser; id value; @@ -684,7 +689,7 @@ vCalendar = [iCalCalendar parseSingleFromSource: content]; newEvent = [[vCalendar events] objectAtIndex: 0]; - ownerUser = [[self context] ownerUser]; + ownerUser = [[self userContext] sogoUser]; userPerson = [newEvent userAsAttendee: ownerUser]; [newEvent setTimeStampAsDate: now]; @@ -747,10 +752,11 @@ if (value) [newEvent setLocation: value]; - isAllDay = [[properties - objectForKey: MAPIPropertyKey (PidLidAppointmentSubType)] - boolValue]; - + isAllDay = [newEvent isAllDay]; + value = [properties + objectForKey: MAPIPropertyKey (PidLidAppointmentSubType)]; + if (value) + isAllDay = [value boolValue]; if (!isAllDay) { tzName = [[self ownerTimeZone] name]; @@ -767,7 +773,14 @@ { start = (iCalDateTime *) [newEvent uniqueChildWithTag: @"dtstart"]; if (isAllDay) - [start setDate: value]; + { + tzOffset = [[value timeZone] secondsFromGMTForDate: value]; + value = [value dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: -tzOffset]; + [start setTimeZone: nil]; + [start setDate: value]; + } else { [start setTimeZone: tz]; @@ -776,14 +789,21 @@ } /* end */ - value = [properties objectForKey: MAPIPropertyKey(PR_END_DATE)]; + value = [properties objectForKey: MAPIPropertyKey (PR_END_DATE)]; if (!value) - value = [properties objectForKey: MAPIPropertyKey(PidLidAppointmentEndWhole)]; + value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentEndWhole)]; if (value) { end = (iCalDateTime *) [newEvent uniqueChildWithTag: @"dtend"]; if (isAllDay) - [end setDate: value]; + { + tzOffset = [[value timeZone] secondsFromGMTForDate: value]; + value = [value dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: -tzOffset]; + [end setTimeZone: nil]; + [end setDate: value]; + } else { [end setTimeZone: tz]; @@ -840,13 +860,14 @@ if (value) { value = [[NSString alloc] initWithData: value - encoding: NSUTF8StringEncoding]; + encoding: NSUTF8StringEncoding]; [value autorelease]; value = [value htmlToText]; } } - if (value) - [newEvent setComment: value]; + if (value && [value length] == 0) + value = nil; + [newEvent setComment: value]; /* recurrence */ value = [properties diff --git a/OpenChange/MAPIStoreCommonViewsContext.h b/OpenChange/MAPIStoreCommonViewsContext.h deleted file mode 100644 index 45dc82085..000000000 --- a/OpenChange/MAPIStoreCommonViewsContext.h +++ /dev/null @@ -1,32 +0,0 @@ -/* MAPIStoreCommonViewsContext.h - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#ifndef MAPISTORECOMMONVIEWSCONTEXT_H -#define MAPISTORECOMMONVIEWSCONTEXT_H - -#import "MAPIStoreFSBaseContext.h" - -@interface MAPIStoreCommonViewsContext : MAPIStoreFSBaseContext - -@end - -#endif /* MAPISTORECOMMONVIEWSCONTEXT_H */ diff --git a/OpenChange/MAPIStoreCommonViewsContext.m b/OpenChange/MAPIStoreCommonViewsContext.m deleted file mode 100644 index 210cbead7..000000000 --- a/OpenChange/MAPIStoreCommonViewsContext.m +++ /dev/null @@ -1,34 +0,0 @@ -/* MAPIStoreCommonViewsContext.m - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#import - -#import "MAPIStoreCommonViewsContext.h" - -@implementation MAPIStoreCommonViewsContext - -+ (NSString *) MAPIModuleName -{ - return @"common-views"; -} - -@end diff --git a/OpenChange/MAPIStoreContactsContext.m b/OpenChange/MAPIStoreContactsContext.m index 4a266ac30..967b1ccd3 100644 --- a/OpenChange/MAPIStoreContactsContext.m +++ b/OpenChange/MAPIStoreContactsContext.m @@ -22,23 +22,38 @@ #import +#import + #import "MAPIStoreContactsFolder.h" -#import "MAPIStoreMapping.h" +#import "MAPIStoreUserContext.h" #import "MAPIStoreContactsContext.h" +#undef DEBUG +#include + +static Class MAPIStoreContactsFolderK; + @implementation MAPIStoreContactsContext ++ (void) initialize +{ + MAPIStoreContactsFolderK = [MAPIStoreContactsFolder class]; +} + + (NSString *) MAPIModuleName { return @"contacts"; } -- (void) setupBaseFolder: (NSURL *) newURL ++ (enum mapistore_context_role) MAPIContextRole { - baseFolder = [MAPIStoreContactsFolder baseFolderWithURL: newURL - inContext: self]; - [baseFolder retain]; + return MAPISTORE_CONTACTS_ROLE; +} + +- (Class) MAPIStoreFolderClass +{ + return MAPIStoreContactsFolderK; } @end diff --git a/OpenChange/MAPIStoreContactsFolder.m b/OpenChange/MAPIStoreContactsFolder.m index ba3d36c6d..6485a3af8 100644 --- a/OpenChange/MAPIStoreContactsFolder.m +++ b/OpenChange/MAPIStoreContactsFolder.m @@ -39,37 +39,6 @@ @implementation MAPIStoreContactsFolder -- (id) initWithURL: (NSURL *) newURL - inContext: (MAPIStoreContext *) newContext -{ - SOGoUserFolder *userFolder; - SOGoContactFolders *parentFolder; - WOContext *woContext; - - if ((self = [super initWithURL: newURL - inContext: newContext])) - { - woContext = [newContext woContext]; - userFolder = [SOGoUserFolder objectWithName: [newURL user] - inContainer: MAPIApp]; - [parentContainersBag addObject: userFolder]; - [woContext setClientObject: userFolder]; - - parentFolder = [userFolder lookupName: @"Contacts" - inContext: woContext - acquire: NO]; - [parentContainersBag addObject: parentFolder]; - [woContext setClientObject: parentFolder]; - - sogoObject = [parentFolder lookupName: @"personal" - inContext: woContext - acquire: NO]; - [sogoObject retain]; - } - - return self; -} - - (MAPIStoreMessageTable *) messageTable { [self synchroniseCache]; diff --git a/OpenChange/MAPIStoreContext.h b/OpenChange/MAPIStoreContext.h index 09774238d..3d41985ad 100644 --- a/OpenChange/MAPIStoreContext.h +++ b/OpenChange/MAPIStoreContext.h @@ -47,28 +47,34 @@ @class MAPIStoreAttachment; @class MAPIStoreAttachmentTable; @class MAPIStoreFolder; -@class MAPIStoreMapping; @class MAPIStoreMessage; @class MAPIStoreTable; +@class MAPIStoreUserContext; @interface MAPIStoreContext : NSObject { - struct mapistore_context *mstoreCtx; struct mapistore_connection_info *connInfo; SOGoUser *activeUser; - SOGoUser *ownerUser; + + MAPIStoreUserContext *userContext; NSURL *contextUrl; - - MAPIStoreMapping *mapping; - - MAPIStoreAuthenticator *authenticator; - WOContext *woContext; - - MAPIStoreFolder *baseFolder; } ++ (struct mapistore_contexts_list *) listAllContextsForUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb + inMemCtx: (TALLOC_CTX *) memCtx; ++ (struct mapistore_contexts_list *) listContextsForUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb + inMemCtx: (TALLOC_CTX *) memCtx; ++ (enum mapistore_error) createRootFolder: (NSString **) mapistoreUriP + withFID: (uint64_t ) fid + andName: (NSString *) folderName + forUser: (NSString *) username + withRole: (enum mapistore_context_role) role + andTDBIndexing: (struct tdb_wrap *) indexingTdb; + + (int) openContext: (MAPIStoreContext **) contextPtr withURI: (const char *) newUri connectionInfo: (struct mapistore_connection_info *) newConnInfo @@ -78,20 +84,12 @@ withConnectionInfo: (struct mapistore_connection_info *) newConnInfo andTDBIndexing: (struct tdb_wrap *) indexingTdb; -- (void) setAuthenticator: (MAPIStoreAuthenticator *) newAuthenticator; -- (MAPIStoreAuthenticator *) authenticator; - - (NSURL *) url; - (struct mapistore_connection_info *) connectionInfo; -- (WOContext *) woContext; -- (MAPIStoreMapping *) mapping; - -- (void) setupRequest; -- (void) tearDownRequest; +- (MAPIStoreUserContext *) userContext; - (SOGoUser *) activeUser; -- (SOGoUser *) ownerUser; // - (id) lookupObject: (NSString *) objectURLString; @@ -112,7 +110,17 @@ /* subclass methods */ + (NSString *) MAPIModuleName; -- (void) setupBaseFolder: (NSURL *) newURL; ++ (enum mapistore_context_role) MAPIContextRole; ++ (NSString *) + createRootSecondaryFolderWithFID: (uint64_t) fid + andName: (NSString *) folderName + forUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb; +- (Class) MAPIStoreFolderClass; + +/* the top-most parent of the context folder: SOGoMailAccount, + SOGoCalendarFolders, ... */ +- (id) rootSOGoFolder; @end diff --git a/OpenChange/MAPIStoreContext.m b/OpenChange/MAPIStoreContext.m index 07ac7116a..376e5e63b 100644 --- a/OpenChange/MAPIStoreContext.m +++ b/OpenChange/MAPIStoreContext.m @@ -25,9 +25,7 @@ #import #import -#import #import - #import #import @@ -35,10 +33,9 @@ #import "SOGoMAPIFSFolder.h" #import "SOGoMAPIFSMessage.h" -#import "MAPIApplication.h" #import "MAPIStoreAttachment.h" // #import "MAPIStoreAttachmentTable.h" -#import "MAPIStoreAuthenticator.h" +#import "MAPIStoreFallbackContext.h" #import "MAPIStoreFolder.h" #import "MAPIStoreFolderTable.h" #import "MAPIStoreMapping.h" @@ -47,6 +44,7 @@ #import "MAPIStoreFAIMessage.h" #import "MAPIStoreFAIMessageTable.h" #import "MAPIStoreTypes.h" +#import "MAPIStoreUserContext.h" #import "NSArray+MAPIStore.h" #import "NSObject+MAPIStore.h" #import "NSString+MAPIStore.h" @@ -69,7 +67,7 @@ /* sogo://username:password@{contacts,calendar,tasks,journal,notes,mail}/dossier/id */ -static Class NSDataK, NSStringK, MAPIStoreFAIMessageK; +static Class NSExceptionK, MAPIStoreFallbackContextK; static NSMutableDictionary *contextClassMapping; @@ -80,9 +78,7 @@ static NSMutableDictionary *contextClassMapping; NSUInteger count, max; NSString *moduleName; - NSDataK = [NSData class]; - NSStringK = [NSString class]; - MAPIStoreFAIMessageK = [MAPIStoreFAIMessage class]; + NSExceptionK = [NSException class]; contextClassMapping = [NSMutableDictionary new]; classes = GSObjCAllSubclassesOfClass (self); @@ -94,53 +90,127 @@ static NSMutableDictionary *contextClassMapping; if (moduleName) { [contextClassMapping setObject: currentClass - forKey: moduleName]; + forKey: moduleName]; NSLog (@" registered class '%@' as handler of '%@' contexts", NSStringFromClass (currentClass), moduleName); } } + + MAPIStoreFallbackContextK = [MAPIStoreFallbackContext class]; } -static inline enum mapistore_error -_prepareContextClass (Class contextClass, - struct mapistore_connection_info *connInfo, - struct tdb_wrap *indexingTdb, NSURL *url, - MAPIStoreContext **contextP) ++ (struct mapistore_contexts_list *) listAllContextsForUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb + inMemCtx: (TALLOC_CTX *) memCtx { - MAPIStoreContext *context; - MAPIStoreAuthenticator *authenticator; - enum mapistore_error rc; + struct mapistore_contexts_list *list, *current; + NSArray *classes; + Class currentClass; + NSUInteger count, max; + MAPIStoreUserContext *userContext; - context = [[contextClass alloc] initFromURL: url - withConnectionInfo: connInfo - andTDBIndexing: indexingTdb]; - if (context) + list = NULL; + + userContext = [MAPIStoreUserContext userContextWithUsername: userName + andTDBIndexing: indexingTdb]; + [userContext activateWithUser: [userContext sogoUser]]; + + classes = GSObjCAllSubclassesOfClass (self); + max = [classes count]; + for (count = 0; count < max; count++) { - [context autorelease]; - - authenticator = [MAPIStoreAuthenticator new]; - [authenticator setUsername: [url user]]; - [authenticator setPassword: [url password]]; - [context setAuthenticator: authenticator]; - [authenticator release]; - - [context setupRequest]; - [context setupBaseFolder: url]; - [context tearDownRequest]; - if (context->baseFolder && [context->baseFolder sogoObject]) - { - *contextP = context; - rc = MAPISTORE_SUCCESS; - } - else - rc = MAPISTORE_ERR_DENIED; + currentClass = [classes objectAtIndex: count]; + current = [currentClass listContextsForUser: userName + withTDBIndexing: indexingTdb + inMemCtx: memCtx]; + if (current) + DLIST_CONCATENATE(list, current, void); } + + return list; +} + ++ (struct mapistore_contexts_list *) listContextsForUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb + inMemCtx: (TALLOC_CTX *) memCtx +{ + return NULL; +} + +static Class +MAPIStoreLookupContextClassByRole (Class self, enum mapistore_context_role role) +{ + static NSMutableDictionary *classMapping = nil; + Class currentClass; + enum mapistore_context_role classRole; + NSNumber *roleNbr; + NSArray *classes; + NSUInteger count, max; + + if (!classMapping) + { + classMapping = [NSMutableDictionary new]; + classes = GSObjCAllSubclassesOfClass (self); + max = [classes count]; + for (count = 0; count < max; count++) + { + currentClass = [classes objectAtIndex: count]; + classRole = [currentClass MAPIContextRole]; + if (classRole != -1) + { + roleNbr = [NSNumber numberWithUnsignedInt: classRole]; + [classMapping setObject: currentClass + forKey: roleNbr]; + } + } + } + + roleNbr = [NSNumber numberWithUnsignedInt: role]; + + return [classMapping objectForKey: roleNbr]; +} + ++ (enum mapistore_error) createRootFolder: (NSString **) mapistoreUriP + withFID: (uint64_t) fid + andName: (NSString *) folderName + forUser: (NSString *) userName + withRole: (enum mapistore_context_role) role + andTDBIndexing: (struct tdb_wrap *) indexingTdb +{ + Class contextClass; + NSString *mapistoreURI; + enum mapistore_error rc = MAPISTORE_SUCCESS; + + contextClass = MAPIStoreLookupContextClassByRole (self, role); + if (!contextClass) + contextClass = MAPIStoreFallbackContextK; + + mapistoreURI = [contextClass createRootSecondaryFolderWithFID: fid + andName: (NSString *) folderName + forUser: userName + withTDBIndexing: indexingTdb]; + if (mapistoreURI) + *mapistoreUriP = mapistoreURI; else rc = MAPISTORE_ERROR; return rc; } +static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) +{ + NSString *urlString; + NSURL *completeURL; + + urlString = [NSString stringWithFormat: @"sogo://%@", + [NSString stringWithUTF8String: uri]]; + if (![urlString hasSuffix: @"/"]) + urlString = [urlString stringByAppendingString: @"/"]; + completeURL = [NSURL URLWithString: [urlString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]; + + return completeURL; +} + + (int) openContext: (MAPIStoreContext **) contextPtr withURI: (const char *) newUri connectionInfo: (struct mapistore_connection_info *) newConnInfo @@ -148,7 +218,7 @@ _prepareContextClass (Class contextClass, { MAPIStoreContext *context; Class contextClass; - NSString *module, *completeURLString, *urlString; + NSString *module; NSURL *baseURL; int rc = MAPISTORE_ERR_NOT_FOUND; @@ -156,41 +226,31 @@ _prepareContextClass (Class contextClass, context = nil; - urlString = [NSString stringWithUTF8String: newUri]; - if (urlString) + baseURL = CompleteURLFromMapistoreURI (newUri); + if (baseURL) { - completeURLString = [@"sogo://" stringByAppendingString: urlString]; - if (![completeURLString hasSuffix: @"/"]) - completeURLString = [completeURLString stringByAppendingString: @"/"]; - baseURL = [NSURL URLWithString: [completeURLString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]; - if (baseURL) + module = [baseURL host]; + if (module) { - module = [baseURL host]; - if (module) + contextClass = [contextClassMapping objectForKey: module]; + if (contextClass) { - contextClass = [contextClassMapping objectForKey: module]; - if (contextClass) + context = [[contextClass alloc] initFromURL: baseURL + withConnectionInfo: newConnInfo + andTDBIndexing: indexingTdb]; + if (context) { - rc = _prepareContextClass (contextClass, - newConnInfo, indexingTdb, - baseURL, &context); - if (rc == MAPISTORE_SUCCESS) - { - *contextPtr = context; - mapistore_mgmt_backend_register_user (newConnInfo, - "SOGo", - [[[context authenticator] username] UTF8String]); - } + [context autorelease]; + rc = MAPISTORE_SUCCESS; + *contextPtr = context; } - else - NSLog (@"ERROR: unrecognized module name '%@'", module); } + else + NSLog (@"ERROR: unrecognized module name '%@'", module); } - else - NSLog (@"ERROR: url could not be parsed"); } else - NSLog (@"ERROR: url is an invalid UTF-8 string"); + NSLog (@"ERROR: url could not be parsed"); return rc; } @@ -199,9 +259,8 @@ _prepareContextClass (Class contextClass, { if ((self = [super init])) { - woContext = [WOContext contextWithRequest: nil]; - [woContext retain]; - baseFolder = nil; + activeUser = nil; + userContext = nil; contextUrl = nil; } @@ -216,6 +275,26 @@ _prepareContextClass (Class contextClass, if ((self = [self init])) { + ASSIGN (contextUrl, newUrl); + + username = [newUrl user]; + if ([username length] == 0) + { + [self errorWithFormat: + @"attempt to instantiate a context with an empty owner"]; + [self release]; + return nil; + } + + ASSIGN (userContext, + [MAPIStoreUserContext userContextWithUsername: username + andTDBIndexing: indexingTdb]); + + mapistore_mgmt_backend_register_user (newConnInfo, + "SOGo", + [username UTF8String]); + + connInfo = newConnInfo; username = [NSString stringWithUTF8String: newConnInfo->username]; ASSIGN (activeUser, [SOGoUser userWithLogin: username]); if (!activeUser) @@ -225,29 +304,6 @@ _prepareContextClass (Class contextClass, [self release]; return nil; } - [woContext setActiveUser: activeUser]; - username = [newUrl user]; - if ([username length] == 0) - { - [self errorWithFormat: - @"attempt to instantiate a context with an empty owner"]; - [self release]; - return nil; - } - ASSIGN (ownerUser, [SOGoUser userWithLogin: username]); - if (!ownerUser) - { - [self errorWithFormat: - @"attempt to instantiate a context without a valid owner"]; - [self release]; - return nil; - } - ASSIGN (mapping, [MAPIStoreMapping mappingForUsername: username - withIndexing: indexingTdb]); - [mapping increaseUseCount]; - ASSIGN (contextUrl, newUrl); - mstoreCtx = newConnInfo->mstore_ctx; - connInfo = newConnInfo; } return self; @@ -256,36 +312,17 @@ _prepareContextClass (Class contextClass, - (void) dealloc { mapistore_mgmt_backend_unregister_user ([self connectionInfo], "SOGo", - [[[self authenticator] username] + [[userContext username] UTF8String]); - [baseFolder release]; - [woContext release]; - [authenticator release]; - [mapping decreaseUseCount]; - [mapping release]; [contextUrl release]; + [userContext release]; [super dealloc]; } -- (WOContext *) woContext +- (MAPIStoreUserContext *) userContext { - return woContext; -} - -- (MAPIStoreMapping *) mapping -{ - return mapping; -} - -- (void) setAuthenticator: (MAPIStoreAuthenticator *) newAuthenticator -{ - ASSIGN (authenticator, newAuthenticator); -} - -- (MAPIStoreAuthenticator *) authenticator -{ - return authenticator; + return userContext; } - (NSURL *) url @@ -298,34 +335,11 @@ _prepareContextClass (Class contextClass, return connInfo; } -- (void) setupRequest -{ - NSMutableDictionary *info; - - [MAPIApp setMAPIStoreContext: self]; - info = [[NSThread currentThread] threadDictionary]; - [info setObject: woContext forKey: @"WOContext"]; -} - -- (void) tearDownRequest -{ - NSMutableDictionary *info; - - info = [[NSThread currentThread] threadDictionary]; - [info removeObjectForKey: @"WOContext"]; - [MAPIApp setMAPIStoreContext: nil]; -} - - (SOGoUser *) activeUser { return activeUser; } -- (SOGoUser *) ownerUser -{ - return ownerUser; -} - // - (void) logRestriction: (struct mapi_SRestriction *) res // withState: (MAPIRestrictionState) state // { @@ -345,7 +359,7 @@ _prepareContextClass (Class contextClass, // TDB_DATA key, dbuf; url = [contextUrl absoluteString]; - objectURL = [mapping urlFromID: fmid]; + objectURL = [[userContext mapping] urlFromID: fmid]; if (objectURL) { if ([objectURL hasPrefix: url]) @@ -383,15 +397,64 @@ _prepareContextClass (Class contextClass, return rc; } +- (void) ensureContextFolder +{ +} + - (int) getRootFolder: (MAPIStoreFolder **) folderPtr withFID: (uint64_t) newFid { + enum mapistore_error rc; + MAPIStoreMapping *mapping; + MAPIStoreFolder *baseFolder; + SOGoFolder *currentFolder; + WOContext *woContext; + NSString *path; + NSArray *pathComponents; + NSUInteger count, max; + + mapping = [userContext mapping]; if (![mapping urlFromID: newFid]) [mapping registerURL: [contextUrl absoluteString] withID: newFid]; - *folderPtr = baseFolder; - return (baseFolder) ? MAPISTORE_SUCCESS: MAPISTORE_ERROR; + [userContext activateWithUser: activeUser]; + woContext = [userContext woContext]; + + [self ensureContextFolder]; + currentFolder = [self rootSOGoFolder]; + path = [contextUrl path]; + if ([path hasPrefix: @"/"]) + path = [path substringFromIndex: 1]; + if ([path hasSuffix: @"/"]) + path = [path substringToIndex: [path length] - 1]; + pathComponents = [path componentsSeparatedByString: @"/"]; + max = [pathComponents count]; + for (count = 0; currentFolder && count < max; count++) + { + [woContext setClientObject: currentFolder]; + currentFolder + = [currentFolder lookupName: [pathComponents objectAtIndex: count] + inContext: woContext + acquire: NO]; + if ([currentFolder isKindOfClass: NSExceptionK]) + currentFolder = nil; + } + + if (currentFolder) + { + baseFolder = [[self MAPIStoreFolderClass] + mapiStoreObjectWithSOGoObject: currentFolder + inContainer: nil]; + [baseFolder setContext: self]; + + *folderPtr = baseFolder; + rc = MAPISTORE_SUCCESS; + } + else + rc = MAPISTORE_ERR_NOT_FOUND; + + return rc; } /* utils */ @@ -426,6 +489,7 @@ _prepareContextClass (Class contextClass, inFolderURL: (NSString *) folderURL { NSString *childURL, *owner; + MAPIStoreMapping *mapping; uint64_t mappingId; uint32_t contextId; void *rootObject; @@ -434,6 +498,7 @@ _prepareContextClass (Class contextClass, childURL = [NSString stringWithFormat: @"%@%@", folderURL, key]; else childURL = folderURL; + mapping = [userContext mapping]; mappingId = [mapping idFromURL: childURL]; if (mappingId == NSNotFound) { @@ -442,11 +507,10 @@ _prepareContextClass (Class contextClass, [mapping registerURL: childURL withID: mappingId]; contextId = 0; - // FIXME: + 7 to skip the BOM or what? - mapistore_search_context_by_uri (mstoreCtx, [folderURL UTF8String] + 7, + mapistore_search_context_by_uri (connInfo->mstore_ctx, [folderURL UTF8String] + 7, &contextId, &rootObject); - owner = [ownerUser login]; - mapistore_indexing_record_add_mid (mstoreCtx, contextId, + owner = [userContext username]; + mapistore_indexing_record_add_mid (connInfo->mstore_ctx, contextId, [owner UTF8String], mappingId); } @@ -473,9 +537,34 @@ _prepareContextClass (Class contextClass, return nil; } -- (void) setupBaseFolder: (NSURL *) newURL ++ (enum mapistore_context_role) MAPIContextRole +{ + return -1; +} + ++ (NSString *) + createRootSecondaryFolderWithFID: (uint64_t) fid + andName: (NSString *) folderName + forUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb { [self subclassResponsibility: _cmd]; + + return nil; +} + +- (Class) MAPIStoreFolderClass +{ + [self subclassResponsibility: _cmd]; + + return nil; +} + +- (id) rootSOGoFolder +{ + [self subclassResponsibility: _cmd]; + + return nil; } @end diff --git a/OpenChange/MAPIStoreDeferredActionsContext.h b/OpenChange/MAPIStoreDeferredActionsContext.h deleted file mode 100644 index e4686b039..000000000 --- a/OpenChange/MAPIStoreDeferredActionsContext.h +++ /dev/null @@ -1,32 +0,0 @@ -/* MAPIStoreDeferredActionsContext.h - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#ifndef MAPISTOREDEFERREDACTIONSCONTEXT_H -#define MAPISTOREDEFERREDACTIONSCONTEXT_H - -#import "MAPIStoreFSBaseContext.h" - -@interface MAPIStoreDeferredActionsContext : MAPIStoreFSBaseContext - -@end - -#endif /* MAPISTOREDEFERREDACTIONSCONTEXT_H */ diff --git a/OpenChange/MAPIStoreDeferredActionsContext.m b/OpenChange/MAPIStoreDeferredActionsContext.m deleted file mode 100644 index 02bd8699b..000000000 --- a/OpenChange/MAPIStoreDeferredActionsContext.m +++ /dev/null @@ -1,36 +0,0 @@ -/* MAPIStoreDeferredActionsContext.m - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#import - -#import "MAPIStoreMapping.h" - -#import "MAPIStoreDeferredActionsContext.h" - -@implementation MAPIStoreDeferredActionsContext - -+ (NSString *) MAPIModuleName -{ - return @"deferred-actions"; -} - -@end diff --git a/OpenChange/MAPIStoreFAIMessage.m b/OpenChange/MAPIStoreFAIMessage.m index e0d5faeee..b8fe33988 100644 --- a/OpenChange/MAPIStoreFAIMessage.m +++ b/OpenChange/MAPIStoreFAIMessage.m @@ -22,6 +22,7 @@ #import "MAPIStoreActiveTables.h" #import "MAPIStoreContext.h" +#import "MAPIStoreUserContext.h" #import "NSObject+MAPIStore.h" #import "MAPIStoreFAIMessage.h" @@ -51,9 +52,11 @@ { enum mapistore_error rc; MAPIStoreContext *context; + SOGoUser *ownerUser; context = [self context]; - if ([[context activeUser] isEqual: [context ownerUser]]) + ownerUser = [[self userContext] sogoUser]; + if ([[context activeUser] isEqual: ownerUser]) rc = [super saveMessage]; else rc = MAPISTORE_ERR_DENIED; diff --git a/OpenChange/MAPIStoreFSBaseContext.m b/OpenChange/MAPIStoreFSBaseContext.m index bb34b4795..a3e448082 100644 --- a/OpenChange/MAPIStoreFSBaseContext.m +++ b/OpenChange/MAPIStoreFSBaseContext.m @@ -30,10 +30,14 @@ #import "MAPIStoreFSFolder.h" #import "MAPIStoreMapping.h" +#import "MAPIStoreUserContext.h" #import "SOGoMAPIFSFolder.h" #import "MAPIStoreFSBaseContext.h" +#undef DEBUG +#include + static Class MAPIStoreFSFolderK; @implementation MAPIStoreFSBaseContext @@ -48,12 +52,28 @@ static Class MAPIStoreFSFolderK; return nil; } -- (void) setupBaseFolder: (NSURL *) newURL +- (Class) MAPIStoreFolderClass { - [self logWithFormat: @"invoked %s", __PRETTY_FUNCTION__]; - baseFolder = [MAPIStoreFSFolderK baseFolderWithURL: newURL - inContext: self]; - [baseFolder retain]; + return MAPIStoreFSFolderK; +} + +- (void) ensureContextFolder +{ + SOGoMAPIFSFolder *contextFolder; + + contextFolder = [SOGoMAPIFSFolder folderWithURL: contextUrl + andTableType: MAPISTORE_MESSAGE_TABLE]; + [contextFolder ensureDirectory]; +} + +- (id) rootSOGoFolder +{ + NSString *urlString; + + urlString = [NSString stringWithFormat: @"sogo://%@@%@/", + [userContext username], [isa MAPIModuleName]]; + return [SOGoMAPIFSFolder folderWithURL: [NSURL URLWithString: urlString] + andTableType: MAPISTORE_MESSAGE_TABLE]; } @end diff --git a/OpenChange/MAPIStoreFSFolder.m b/OpenChange/MAPIStoreFSFolder.m index bb6cf3d7b..145781383 100644 --- a/OpenChange/MAPIStoreFSFolder.m +++ b/OpenChange/MAPIStoreFSFolder.m @@ -34,6 +34,7 @@ #import "MAPIStoreFSMessage.h" #import "MAPIStoreFSMessageTable.h" #import "MAPIStoreTypes.h" +#import "MAPIStoreUserContext.h" #import "SOGoMAPIFSFolder.h" #import "SOGoMAPIFSMessage.h" @@ -41,9 +42,7 @@ #undef DEBUG #include -// #include -// #include -// #include +#include static Class EOKeyValueQualifierK; @@ -64,20 +63,6 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; EOKeyValueQualifierK = [EOKeyValueQualifier class]; } -- (id) initWithURL: (NSURL *) newURL - inContext: (MAPIStoreContext *) newContext -{ - if ((self = [super initWithURL: newURL - inContext: newContext])) - { - sogoObject = [SOGoMAPIFSFolder folderWithURL: newURL - andTableType: MAPISTORE_MESSAGE_TABLE]; - [sogoObject retain]; - } - - return self; -} - - (MAPIStoreMessageTable *) messageTable { return [MAPIStoreFSMessageTable tableForContainer: self]; @@ -88,8 +73,9 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; return [MAPIStoreFSFolderTable tableForContainer: self]; } -- (NSString *) createFolder: (struct SRow *) aRow - withFID: (uint64_t) newFID +- (enum mapistore_error) createFolder: (struct SRow *) aRow + withFID: (uint64_t) newFID + andKey: (NSString **) newKeyP { NSString *newKey, *urlString; NSURL *childURL; @@ -102,8 +88,9 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; childFolder = [SOGoMAPIFSFolder folderWithURL: childURL andTableType: MAPISTORE_MESSAGE_TABLE]; [childFolder ensureDirectory]; + *newKeyP = newKey; - return newKey; + return MAPISTORE_SUCCESS; } - (MAPIStoreMessage *) createMessage @@ -126,8 +113,10 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; andSortOrderings: (NSArray *) sortOrderings { NSArray *keys; + SOGoUser *ownerUser; - if ([[context activeUser] isEqual: [context ownerUser]] + ownerUser = [[self userContext] sogoUser]; + if ([[context activeUser] isEqual: ownerUser] || [self subscriberCanReadMessages]) keys = [(SOGoMAPIFSFolder *) sogoObject toOneRelationshipKeysMatchingQualifier: qualifier @@ -295,4 +284,9 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; return [self _testRoleForActiveUser: MAPIStoreRightCreateSubfolders]; } +- (BOOL) supportsSubFolders +{ + return YES; +} + @end diff --git a/OpenChange/MAPIStoreFallbackContext.m b/OpenChange/MAPIStoreFallbackContext.m index e48b63802..c3ff666bb 100644 --- a/OpenChange/MAPIStoreFallbackContext.m +++ b/OpenChange/MAPIStoreFallbackContext.m @@ -20,12 +20,19 @@ * Boston, MA 02111-1307, USA. */ +#import #import +#import -#import "MAPIStoreFSFolder.h" +#import "MAPIStoreUserContext.h" +#import "NSString+MAPIStore.h" +#import "SOGoMAPIFSFolder.h" #import "MAPIStoreFallbackContext.h" +#undef DEBUG +#include + @implementation MAPIStoreFallbackContext + (NSString *) MAPIModuleName @@ -33,10 +40,63 @@ return @"fallback"; } -- (void) setupBaseFolder: (NSURL *) newURL ++ (enum mapistore_context_role) MAPIContextRole { - baseFolder = [MAPIStoreFSFolder baseFolderWithURL: newURL inContext: self]; - [baseFolder retain]; + return MAPISTORE_MAIL_ROLE; +} + ++ (struct mapistore_contexts_list *) listContextsForUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb + inMemCtx: (TALLOC_CTX *) memCtx +{ + struct mapistore_contexts_list *firstContext = NULL, *context; + SOGoMAPIFSFolder *root; + NSArray *names; + NSUInteger count, max; + NSString *baseURL, *url, *name; + + baseURL = [NSString stringWithFormat: @"sogo://%@@fallback/", userName]; + + context = talloc_zero (memCtx, struct mapistore_contexts_list); + context->url = [baseURL asUnicodeInMemCtx: context]; + context->name = "Fallback"; + context->main_folder = true; + context->role = MAPISTORE_FALLBACK_ROLE; + context->tag = "tag"; + + DLIST_ADD_END (firstContext, context, void); + + + /* Maybe emsmdbp_provisioning should be fixed in order to only take the uri + returned above to avoid deleting its entries... */ + root = [SOGoMAPIFSFolder folderWithURL: [NSURL URLWithString: baseURL] + andTableType: MAPISTORE_MESSAGE_TABLE]; + names = [root toManyRelationshipKeys]; + max = [names count]; + for (count = 0; count < max; count++) + { + name = [names objectAtIndex: count]; + url = [NSString stringWithFormat: @"%@%@/", baseURL, name]; + context = talloc_zero (memCtx, struct mapistore_contexts_list); + context->url = [url asUnicodeInMemCtx: context]; + context->name = [name asUnicodeInMemCtx: context]; + context->main_folder = false; + context->role = MAPISTORE_FALLBACK_ROLE; + context->tag = "tag"; + DLIST_ADD_END (firstContext, context, void); + } + + return firstContext; +} + ++ (NSString *) + createRootSecondaryFolderWithFID: (uint64_t) fid + andName: (NSString *) folderName + forUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb +{ + return [NSString stringWithFormat: @"sogo://%@@fallback/0x%.16"PRIx64"/", + userName, (unsigned long long) fid]; } @end diff --git a/OpenChange/MAPIStoreFolder.h b/OpenChange/MAPIStoreFolder.h index c124cdce7..b327e8d6d 100644 --- a/OpenChange/MAPIStoreFolder.h +++ b/OpenChange/MAPIStoreFolder.h @@ -25,12 +25,9 @@ #import -#import "MAPIStoreTable.h" - @class NSArray; @class NSMutableArray; @class NSNumber; -@class NSURL; @class EOQualifier; @@ -48,7 +45,6 @@ @interface MAPIStoreFolder : MAPIStoreObject { - NSURL *folderURL; MAPIStoreContext *context; NSArray *messageKeys; NSArray *faiMessageKeys; @@ -59,10 +55,7 @@ SOGoMAPIFSMessage *propsMessage; } -+ (id) baseFolderWithURL: (NSURL *) newURL - inContext: (MAPIStoreContext *) newContext; -- (id) initWithURL: (NSURL *) newURL - inContext: (MAPIStoreContext *) newContext; +- (void) setContext: (MAPIStoreContext *) newContext; - (NSArray *) activeMessageTables; - (NSArray *) activeFAIMessageTables; @@ -103,9 +96,9 @@ - (int) createFolder: (MAPIStoreFolder **) childFolderPtr withRow: (struct SRow *) aRow andFID: (uint64_t) fid; -- (int) deleteFolderWithFID: (uint64_t) fid; +- (int) deleteFolder; - (int) getChildCount: (uint32_t *) rowCount - ofTableType: (uint8_t) tableType; + ofTableType: (enum mapistore_table_type) tableType; - (int) createMessage: (MAPIStoreMessage **) messagePtr withMID: (uint64_t) mid @@ -128,12 +121,12 @@ - (int) getDeletedFMIDs: (struct I8Array_r **) fmidsPtr andCN: (uint64_t *) cnPtr fromChangeNumber: (uint64_t) changeNum - inTableType: (uint8_t) tableType + inTableType: (enum mapistore_table_type) tableType inMemCtx: (TALLOC_CTX *) mem_ctx; - (int) getTable: (MAPIStoreTable **) tablePtr andRowCount: (uint32_t *) count - tableType: (uint8_t) tableType + tableType: (enum mapistore_table_type) tableType andHandleId: (uint32_t) handleId; - (int) modifyPermissions: (struct PermissionData *) permissions @@ -150,10 +143,11 @@ andSortOrderings: (NSArray *) sortOrderings; - (NSArray *) getDeletedKeysFromChangeNumber: (uint64_t) changeNum andCN: (NSNumber **) cnNbr - inTableType: (uint8_t) tableType; + inTableType: (enum mapistore_table_type) tableType; -- (NSString *) createFolder: (struct SRow *) aRow - withFID: (uint64_t) newFID; +- (enum mapistore_error) createFolder: (struct SRow *) aRow + withFID: (uint64_t) newFID + andKey: (NSString **) newKeyP; - (NSCalendarDate *) lastMessageModificationTime; @@ -167,7 +161,10 @@ - (BOOL) subscriberCanDeleteMessages; - (BOOL) subscriberCanCreateSubFolders; +- (BOOL) supportsSubFolders; /* capability */ + /* subclass helpers */ +- (void) setupVersionsMessage; - (void) postNotificationsForMoveCopyMessagesWithMIDs: (uint64_t *) srcMids andMessageURLs: (NSArray *) oldMessageURLs andCount: (uint32_t) midCount diff --git a/OpenChange/MAPIStoreFolder.m b/OpenChange/MAPIStoreFolder.m index fa13b9da7..8c10feb07 100644 --- a/OpenChange/MAPIStoreFolder.m +++ b/OpenChange/MAPIStoreFolder.m @@ -43,6 +43,7 @@ #import "MAPIStorePermissionsTable.h" #import "MAPIStoreSamDBUtils.h" #import "MAPIStoreTypes.h" +#import "MAPIStoreUserContext.h" #import "NSDate+MAPIStore.h" #import "NSString+MAPIStore.h" #import "NSObject+MAPIStore.h" @@ -71,17 +72,6 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe MAPIStoreFolderTableK = [MAPIStoreFolderTable class]; } -+ (id) baseFolderWithURL: (NSURL *) newURL - inContext: (MAPIStoreContext *) newContext -{ - id newFolder; - - newFolder = [[self alloc] initWithURL: newURL inContext: newContext]; - [newFolder autorelease]; - - return newFolder; -} - - (id) init { if ((self = [super init])) @@ -90,7 +80,6 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe faiMessageKeys = nil; folderKeys = nil; faiFolder = nil; - folderURL = nil; context = nil; propsFolder = nil; @@ -100,54 +89,72 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe return self; } -/* from context */ -- (id) initWithURL: (NSURL *) newURL - inContext: (MAPIStoreContext *) newContext -{ - if ((self = [self init])) - { - context = newContext; - ASSIGN (folderURL, newURL); - ASSIGN (faiFolder, - [SOGoMAPIFSFolder folderWithURL: newURL - andTableType: MAPISTORE_FAI_TABLE]); - ASSIGN (propsFolder, - [SOGoMAPIFSFolder folderWithURL: newURL - andTableType: MAPISTORE_FOLDER_TABLE]); - ASSIGN (propsMessage, - [SOGoMAPIFSMessage objectWithName: @"properties.plist" - inContainer: propsFolder]); - } - - return self; -} - -/* from parent folder */ -- (id) initWithSOGoObject: (id) newSOGoObject - inContainer: (MAPIStoreObject *) newContainer +- (void) _setupAuxiliaryObjects { NSURL *propsURL; NSString *urlString; - if ((self = [super initWithSOGoObject: newSOGoObject inContainer: newContainer])) + urlString = [[self url] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; + propsURL = [NSURL URLWithString: urlString]; + [self logWithFormat: @"_setupAuxiliaryObjects: %@", propsURL]; + ASSIGN (faiFolder, + [SOGoMAPIFSFolder folderWithURL: propsURL + andTableType: MAPISTORE_FAI_TABLE]); + ASSIGN (propsFolder, + [SOGoMAPIFSFolder folderWithURL: propsURL + andTableType: MAPISTORE_FOLDER_TABLE]); + ASSIGN (propsMessage, + [SOGoMAPIFSMessage objectWithName: @"properties.plist" + inContainer: propsFolder]); + [self setupVersionsMessage]; +} + +- (id) initWithSOGoObject: (id) newSOGoObject + inContainer: (MAPIStoreObject *) newContainer +{ + /* The instantiation of auxiliary folders is postponed when newContainer is + nil since there is no way to deduce the parent url. When setContext: is + invoked, it becomes possible again. */ + if ((self = [super initWithSOGoObject: newSOGoObject + inContainer: newContainer]) + && newContainer) { - urlString = [[self url] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; - propsURL = [NSURL URLWithString: urlString]; - ASSIGN (faiFolder, - [SOGoMAPIFSFolder folderWithURL: propsURL - andTableType: MAPISTORE_FAI_TABLE]); - ASSIGN (propsFolder, - [SOGoMAPIFSFolder folderWithURL: propsURL - andTableType: MAPISTORE_FOLDER_TABLE]); - ASSIGN (propsMessage, - [SOGoMAPIFSMessage objectWithName: @"properties.plist" - inContainer: propsFolder]); + [self _setupAuxiliaryObjects]; } return self; } +- (void) setContext: (MAPIStoreContext *) newContext +{ + ASSIGN (context, newContext); + if (newContext) + [self _setupAuxiliaryObjects]; +} + +- (MAPIStoreContext *) context +{ + if (!context) + [self setContext: [container context]]; + + return context; +} + +- (void) dealloc +{ + [propsMessage release]; + [propsFolder release]; + [messageKeys release]; + [faiMessageKeys release]; + [folderKeys release]; + [faiFolder release]; + [context release]; + + [super dealloc]; +} + /* backend interface */ + - (SOGoMAPIFSMessage *) propertiesMessage { return propsMessage; @@ -185,7 +192,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe if ([[self folderKeys] containsObject: folderKey]) { - woContext = [[self context] woContext]; + woContext = [[self userContext] woContext]; sogoFolder = [sogoObject lookupName: folderKey inContext: woContext acquire: NO]; @@ -237,7 +244,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe acquire: NO]; if (msgObject && ![msgObject isKindOfClass: NSExceptionK]) { - [msgObject setContext: [[self context] woContext]]; + [msgObject setContext: [[self userContext] woContext]]; messageClass = [msgObject mapistoreMessageClass]; childMessage = [messageClass mapiStoreObjectWithSOGoObject: msgObject @@ -310,7 +317,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe [self logWithFormat: @"METHOD '%s' (%d)", __FUNCTION__, __LINE__]; - mapping = [[self context] mapping]; + mapping = [self mapping]; childURL = [mapping urlFromID: fid]; if (childURL) { @@ -333,20 +340,22 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe MAPIStoreMapping *mapping; NSString *baseURL, *childURL, *folderKey; MAPIStoreFolder *childFolder; + SOGoUser *ownerUser; [self logWithFormat: @"METHOD '%s' (%d)", __FUNCTION__, __LINE__]; - if ([[context activeUser] isEqual: [context ownerUser]] + ownerUser = [[self userContext] sogoUser]; + if ([[context activeUser] isEqual: ownerUser] || [self subscriberCanCreateSubFolders]) { - mapping = [[self context] mapping]; + mapping = [self mapping]; childURL = [mapping urlFromID: fid]; if (childURL) rc = MAPISTORE_ERR_EXIST; else { - folderKey = [self createFolder: aRow withFID: fid]; - if (folderKey) + rc = [self createFolder: aRow withFID: fid andKey: &folderKey]; + if (rc == MAPISTORE_SUCCESS) { [self cleanupCaches]; baseURL = [self url]; @@ -365,8 +374,6 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe [NSException raise: @"MAPIStoreIOException" format: @"unable to fetch created folder"]; } - else - rc = MAPISTORE_ERROR; } } else @@ -375,15 +382,19 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe return rc; } -- (int) deleteFolderWithFID: (uint64_t) fid +- (int) deleteFolder { - [self logWithFormat: @"UNIMPLEMENTED METHOD '%s' (%d)", __FUNCTION__, __LINE__]; + [propsMessage delete]; + [propsFolder delete]; + [faiFolder delete]; - return MAPISTORE_ERROR; + [self cleanupCaches]; + + return MAPISTORE_SUCCESS; } - (int) getChildCount: (uint32_t *) rowCount - ofTableType: (uint8_t) tableType + ofTableType: (enum mapistore_table_type) tableType { NSArray *keys; int rc = MAPISTORE_SUCCESS; @@ -415,16 +426,18 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe NSString *messageURL; MAPIStoreMapping *mapping; MAPIStoreMessage *message; + SOGoUser *ownerUser; int rc = MAPISTORE_ERR_NOT_FOUND; - mapping = [[self context] mapping]; + mapping = [self mapping]; messageURL = [mapping urlFromID: mid]; if (messageURL) { message = [self lookupMessageByURL: messageURL]; if (message) { - if ([[context activeUser] isEqual: [context ownerUser]] + ownerUser = [[self userContext] sogoUser]; + if ([[context activeUser] isEqual: ownerUser] || (readWrite && [message subscriberCanModifyMessage]) || (!readWrite && [message subscriberCanReadMessage])) { @@ -447,15 +460,18 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe MAPIStoreMessage *message; NSString *baseURL, *childURL; MAPIStoreMapping *mapping; + SOGoUser *ownerUser; [self logWithFormat: @"METHOD '%s' -- mid: 0x%.16llx associated: %d", __FUNCTION__, mid, isAssociated]; context = [self context]; - if ([[context activeUser] isEqual: [context ownerUser]] + ownerUser = [[self userContext] sogoUser]; + + if ([[context activeUser] isEqual: ownerUser] || (!isAssociated && [self subscriberCanCreateMessages])) { - mapping = [[self context] mapping]; + mapping = [self mapping]; if ([mapping urlFromID: mid]) rc = MAPISTORE_ERR_EXIST; else @@ -491,20 +507,23 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe NSArray *activeTables; NSUInteger count, max; id msgObject; + SOGoUser *ownerUser; struct mapistore_connection_info *connInfo; struct mapistore_object_notification_parameters *notif_parameters; int rc; [self logWithFormat: @"-deleteMessageWithMID: mid: 0x%.16llx flags: %d", mid, flags]; - mapping = [[self context] mapping]; + mapping = [self mapping]; childURL = [mapping urlFromID: mid]; if (childURL) { message = [self lookupMessageByURL: childURL]; if (message) { - if ([[context activeUser] isEqual: [context ownerUser]] + ownerUser = [[self userContext] sogoUser]; + + if ([[context activeUser] isEqual: ownerUser] || (![message isKindOfClass: MAPIStoreFAIMessageK] && [self subscriberCanDeleteMessages])) { @@ -685,16 +704,19 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe NSMutableArray *oldMessageURLs; NSString *oldMessageURL; MAPIStoreMapping *mapping; + SOGoUser *ownerUser; struct Binary_r *targetChangeKey; - if (wantCopy || [[context activeUser] isEqual: [context ownerUser]]) + ownerUser = [[self userContext] sogoUser]; + + if (wantCopy || [[context activeUser] isEqual: ownerUser]) { if ([sourceFolder isKindOfClass: isa] || [self isKindOfClass: [sourceFolder class]]) [self logWithFormat: @"%s: this class could probably implement" @" a specialized/optimized version", __FUNCTION__]; oldMessageURLs = [NSMutableArray arrayWithCapacity: midCount]; - mapping = [[self context] mapping]; + mapping = [self mapping]; for (count = 0; rc == MAPISTORE_SUCCESS && count < midCount; count++) { oldMessageURL = [mapping urlFromID: srcMids[count]]; @@ -753,6 +775,10 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe [aclFolder setRoles: roles forUser: user]; } +- (void) setupVersionsMessage +{ +} + - (void) postNotificationsForMoveCopyMessagesWithMIDs: (uint64_t *) srcMids andMessageURLs: (NSArray *) oldMessageURLs andCount: (uint32_t) midCount @@ -849,7 +875,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe talloc_free(notif_parameters); // table notification - mapping = [[self context] mapping]; + mapping = [self mapping]; for (count = 0; count < midCount; count++) { messageURL = [mapping urlFromID: targetMids[count]]; @@ -863,7 +889,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe - (int) getDeletedFMIDs: (struct I8Array_r **) fmidsPtr andCN: (uint64_t *) cnPtr fromChangeNumber: (uint64_t) changeNum - inTableType: (uint8_t) tableType + inTableType: (enum mapistore_table_type) tableType inMemCtx: (TALLOC_CTX *) memCtx { int rc; @@ -879,7 +905,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe inTableType: tableType]; if (keys) { - mapping = [[self context] mapping]; + mapping = [self mapping]; max = [keys count]; @@ -920,7 +946,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe - (int) getTable: (MAPIStoreTable **) tablePtr andRowCount: (uint32_t *) countPtr - tableType: (uint8_t) tableType + tableType: (enum mapistore_table_type) tableType andHandleId: (uint32_t) handleId { int rc = MAPISTORE_SUCCESS; @@ -976,27 +1002,6 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe [propsCopy release]; } -- (void) dealloc -{ - [propsMessage release]; - [propsFolder release]; - [folderURL release]; - [messageKeys release]; - [faiMessageKeys release]; - [folderKeys release]; - [faiFolder release]; - - [super dealloc]; -} - -- (MAPIStoreContext *) context -{ - if (!context) - context = [container context]; - - return context; -} - - (NSArray *) messageKeys { if (!messageKeys) @@ -1076,7 +1081,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe andType: MAPISTORE_FAI_TABLE]; } -- (void) _cleanupTableCaches: (uint8_t) tableType +- (void) _cleanupTableCaches: (enum mapistore_table_type) tableType { NSArray *tables; NSUInteger count, max; @@ -1132,16 +1137,20 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe inMemCtx: (TALLOC_CTX *) memCtx { uint32_t access = 0; + SOGoUser *ownerUser; BOOL userIsOwner; - userIsOwner = [[context activeUser] isEqual: [context ownerUser]]; + ownerUser = [[self userContext] sogoUser]; + + userIsOwner = [[context activeUser] isEqual: ownerUser]; if (userIsOwner || [self subscriberCanModifyMessages]) access |= 0x01; if (userIsOwner || [self subscriberCanReadMessages]) access |= 0x02; if (userIsOwner || [self subscriberCanDeleteMessages]) access |= 0x04; - if (userIsOwner || [self subscriberCanCreateSubFolders]) + if ((userIsOwner || [self subscriberCanCreateSubFolders]) + && [self supportsSubFolders]) access |= 0x08; if (userIsOwner || [self subscriberCanCreateMessages]) access |= 0x10; @@ -1284,18 +1293,19 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe else newMessage = [self createMessage]; [newMessage setIsNew: YES]; - woContext = [[self context] woContext]; + woContext = [[self userContext] woContext]; [[newMessage sogoObject] setContext: woContext]; return newMessage; } -- (NSString *) createFolder: (struct SRow *) aRow - withFID: (uint64_t) newFID +- (enum mapistore_error) createFolder: (struct SRow *) aRow + withFID: (uint64_t) newFID + andKey: (NSString **) newKeyP { [self errorWithFormat: @"new folders cannot be created in this context"]; - return nil; + return MAPISTORE_ERR_DENIED; } /* helpers */ @@ -1304,10 +1314,14 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe { NSString *url; - if (folderURL) - url = [folderURL absoluteString]; - else + if (container) url = [NSString stringWithFormat: @"%@/", [super url]]; + else + { + url = [[context url] absoluteString]; + if (![url hasSuffix: @"/"]) + url = [NSString stringWithFormat: @"%@/", url]; + } return url; } @@ -1511,10 +1525,10 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe { uint64_t objectId; - if (folderURL) - objectId = [self idForObjectWithKey: nil]; - else + if (container) objectId = [super objectId]; + else + objectId = [self idForObjectWithKey: nil]; return objectId; } @@ -1552,7 +1566,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe - (NSArray *) getDeletedKeysFromChangeNumber: (uint64_t) changeNum andCN: (NSNumber **) cnNbrs - inTableType: (uint8_t) tableType + inTableType: (enum mapistore_table_type) tableType { return nil; } @@ -1595,4 +1609,9 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe return NO; } +- (BOOL) supportsSubFolders +{ + return NO; +} + @end diff --git a/OpenChange/MAPIStoreFolderTable.m b/OpenChange/MAPIStoreFolderTable.m index dfcb8af46..1deb68998 100644 --- a/OpenChange/MAPIStoreFolderTable.m +++ b/OpenChange/MAPIStoreFolderTable.m @@ -85,6 +85,26 @@ return restrictedChildKeys; } +- (MAPIRestrictionState) evaluatePropertyRestriction: (struct mapi_SPropertyRestriction *) res + intoQualifier: (EOQualifier **) qualifier +{ + MAPIRestrictionState rc; + + switch ((uint32_t) res->ulPropTag) + { + /* HACK: we cheat here as we current have no mechanism for searching + folders based on PR_CHANGE_NUM, which is used by the oxcfxics + mechanism... */ + case PR_CHANGE_NUM: + rc = MAPIRestrictionStateAlwaysTrue; + break; + default: + rc = [super evaluatePropertyRestriction: res intoQualifier: qualifier]; + } + + return rc; +} + - (id) lookupChild: (NSString *) childKey { return [(MAPIStoreFolder *) container lookupFolder: childKey]; diff --git a/OpenChange/MAPIStoreFreebusyContext.h b/OpenChange/MAPIStoreFreebusyContext.h deleted file mode 100644 index 22ce8e9f3..000000000 --- a/OpenChange/MAPIStoreFreebusyContext.h +++ /dev/null @@ -1,32 +0,0 @@ -/* MAPIStoreFreebusyContext.h - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#ifndef MAPISTOREFREEBUSYCONTEXT_H -#define MAPISTOREFREEBUSYCONTEXT_H - -#import "MAPIStoreFSBaseContext.h" - -@interface MAPIStoreFreebusyContext : MAPIStoreFSBaseContext - -@end - -#endif /* MAPISTOREFREEBUSYCONTEXT_H */ diff --git a/OpenChange/MAPIStoreGCSBaseContext.m b/OpenChange/MAPIStoreGCSBaseContext.m index d3dd27376..a8e552e9c 100644 --- a/OpenChange/MAPIStoreGCSBaseContext.m +++ b/OpenChange/MAPIStoreGCSBaseContext.m @@ -20,10 +20,22 @@ * Boston, MA 02111-1307, USA. */ +#import +#import #import +#import +#import + +#import "MAPIStoreUserContext.h" +#import "NSString+MAPIStore.h" + #import "MAPIStoreGCSBaseContext.h" +#undef DEBUG +#include +#include + @implementation MAPIStoreGCSBaseContext + (NSString *) MAPIModuleName @@ -31,4 +43,78 @@ return nil; } ++ (struct mapistore_contexts_list *) listContextsForUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb + inMemCtx: (TALLOC_CTX *) memCtx +{ + struct mapistore_contexts_list *firstContext = NULL, *context; + NSString *moduleName, *baseUrl, *url, *nameInContainer; + NSArray *subfolders; + MAPIStoreUserContext *userContext; + SOGoParentFolder *parentFolder; + NSUInteger count, max; + SOGoGCSFolder *currentFolder; + + moduleName = [self MAPIModuleName]; + if (moduleName) + { + userContext = [MAPIStoreUserContext userContextWithUsername: userName + andTDBIndexing: indexingTdb]; + parentFolder = [[userContext rootFolders] objectForKey: moduleName]; + baseUrl = [NSString stringWithFormat: @"sogo://%@@%@/", + userName, moduleName]; + + subfolders = [parentFolder subFolders]; + max = [subfolders count]; + for (count = 0; count < max; count++) + { + currentFolder = [subfolders objectAtIndex: count]; + if ([[currentFolder ownerInContext: nil] isEqualToString: userName]) + { + context = talloc_zero (memCtx, struct mapistore_contexts_list); + nameInContainer = [currentFolder nameInContainer]; + url = [NSString stringWithFormat: @"%@%@", baseUrl, nameInContainer]; + context->url = [url asUnicodeInMemCtx: context]; + context->name = [[currentFolder displayName] + asUnicodeInMemCtx: context]; + context->main_folder = [nameInContainer isEqualToString: @"personal"]; + context->role = [self MAPIContextRole]; + context->tag = "tag"; + DLIST_ADD_END (firstContext, context, void); + } + } + } + + return firstContext; +} + ++ (NSString *) + createRootSecondaryFolderWithFID: (uint64_t) fid + andName: (NSString *) folderName + forUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb +{ + NSString *mapistoreURI, *nameInContainer, *moduleName; + MAPIStoreUserContext *userContext; + SOGoParentFolder *parentFolder; + + userContext = [MAPIStoreUserContext userContextWithUsername: userName + andTDBIndexing: indexingTdb]; + moduleName = [self MAPIModuleName]; + parentFolder = [[userContext rootFolders] objectForKey: moduleName]; + if (![parentFolder newFolderWithName: folderName + nameInContainer: &nameInContainer]) + mapistoreURI = [NSString stringWithFormat: @"sogo://%@@%@/%@/", + userName, moduleName, nameInContainer]; + else + mapistoreURI = nil; + + return mapistoreURI; +} + +- (id) rootSOGoFolder +{ + return [[userContext rootFolders] objectForKey: [isa MAPIModuleName]]; +} + @end diff --git a/OpenChange/MAPIStoreGCSFolder.m b/OpenChange/MAPIStoreGCSFolder.m index 0a88a9a96..f41aa5eb3 100644 --- a/OpenChange/MAPIStoreGCSFolder.m +++ b/OpenChange/MAPIStoreGCSFolder.m @@ -22,6 +22,7 @@ #import #import +#import #import #import #import @@ -29,11 +30,13 @@ #import #import #import +#import #import #import #import "MAPIStoreContext.h" #import "MAPIStoreTypes.h" +#import "MAPIStoreUserContext.h" #import "NSData+MAPIStore.h" #import "NSDate+MAPIStore.h" #import "NSString+MAPIStore.h" @@ -43,38 +46,28 @@ #undef DEBUG #include +#include @implementation MAPIStoreGCSFolder -- (id) initWithURL: (NSURL *) newURL - inContext: (MAPIStoreContext *) newContext -{ - if ((self = [super initWithURL: newURL - inContext: newContext])) - { - ASSIGN (versionsMessage, - [SOGoMAPIFSMessage objectWithName: @"versions.plist" - inContainer: propsFolder]); - activeUserRoles = nil; - } - - return self; -} - - (id) initWithSOGoObject: (id) newSOGoObject inContainer: (MAPIStoreObject *) newContainer { if ((self = [super initWithSOGoObject: newSOGoObject inContainer: newContainer])) { - ASSIGN (versionsMessage, - [SOGoMAPIFSMessage objectWithName: @"versions.plist" - inContainer: propsFolder]); activeUserRoles = nil; } return self; } +- (void) setupVersionsMessage +{ + ASSIGN (versionsMessage, + [SOGoMAPIFSMessage objectWithName: @"versions.plist" + inContainer: propsFolder]); +} + - (void) dealloc { [versionsMessage release]; @@ -82,10 +75,58 @@ [super dealloc]; } +- (int) deleteFolder +{ + int rc; + NSException *error; + NSString *name; + + name = [self nameInContainer]; + if ([name isEqualToString: @"personal"]) + rc = MAPISTORE_ERR_DENIED; + else + { + [[sogoObject container] removeSubFolder: name]; + error = [(SOGoGCSFolder *) sogoObject delete]; + if (error) + rc = MAPISTORE_ERROR; + else + { + if (![versionsMessage delete]) + rc = MAPISTORE_SUCCESS; + else + rc = MAPISTORE_ERROR; + } + } + + return (rc == MAPISTORE_SUCCESS) ? [super deleteFolder] : rc; +} + +- (void) addProperties: (NSDictionary *) newProperties +{ + NSString *newDisplayName; + NSMutableDictionary *propsCopy; + NSNumber *key; + + key = MAPIPropertyKey (PR_DISPLAY_NAME_UNICODE); + newDisplayName = [newProperties objectForKey: key]; + if (newDisplayName) + { + [sogoObject renameTo: newDisplayName]; + propsCopy = [newProperties mutableCopy]; + [propsCopy removeObjectForKey: key]; + [propsCopy autorelease]; + newProperties = propsCopy; + } + + [super addProperties: newProperties]; +} + - (NSArray *) messageKeysMatchingQualifier: (EOQualifier *) qualifier andSortOrderings: (NSArray *) sortOrderings { static NSArray *fields = nil; + SOGoUser *ownerUser; NSArray *records; NSMutableArray *qualifierArray; EOQualifier *fetchQualifier, *aclQualifier; @@ -98,7 +139,8 @@ initWithObjects: @"c_name", @"c_version", nil]; qualifierArray = [NSMutableArray new]; - if (![[context activeUser] isEqual: [context ownerUser]]) + ownerUser = [[self userContext] sogoUser]; + if (![[context activeUser] isEqual: ownerUser]) { aclQualifier = [self aclQualifier]; if (aclQualifier) @@ -528,12 +570,14 @@ - (NSArray *) activeUserRoles { SOGoUser *activeUser; + WOContext *woContext; if (!activeUserRoles) { activeUser = [[self context] activeUser]; + woContext = [[self userContext] woContext]; activeUserRoles = [activeUser rolesForObject: sogoObject - inContext: [context woContext]]; + inContext: woContext]; [activeUserRoles retain]; } diff --git a/OpenChange/MAPIStoreGCSMessage.m b/OpenChange/MAPIStoreGCSMessage.m index c5e945583..bb636f02d 100644 --- a/OpenChange/MAPIStoreGCSMessage.m +++ b/OpenChange/MAPIStoreGCSMessage.m @@ -30,6 +30,7 @@ #import "MAPIStoreContext.h" #import "MAPIStoreGCSFolder.h" #import "MAPIStoreTypes.h" +#import "MAPIStoreUserContext.h" #import "NSData+MAPIStore.h" #import "MAPIStoreGCSMessage.h" @@ -56,15 +57,17 @@ MAPIStoreContext *context; WOContext *woContext; SoSecurityManager *sm; + MAPIStoreUserContext *userContext; uint32_t access; context = [self context]; - if ([[context activeUser] isEqual: [context ownerUser]]) + userContext = [self userContext]; + if ([[context activeUser] isEqual: [userContext sogoUser]]) access = 0x03; else { sm = [SoSecurityManager sharedSecurityManager]; - woContext = [context woContext]; + woContext = [userContext woContext]; access = 0; if (![sm validatePermission: SoPerm_ChangeImagesAndFiles @@ -89,18 +92,19 @@ inMemCtx: (TALLOC_CTX *) memCtx { MAPIStoreContext *context; + MAPIStoreUserContext *userContext; WOContext *woContext; SoSecurityManager *sm; uint32_t accessLvl; context = [self context]; - if ([[context activeUser] isEqual: [context ownerUser]]) + userContext = [self userContext]; + if ([[context activeUser] isEqual: [userContext sogoUser]]) accessLvl = 1; else { sm = [SoSecurityManager sharedSecurityManager]; - woContext = [context woContext]; - + woContext = [userContext woContext]; if (![sm validatePermission: SoPerm_ChangeImagesAndFiles onObject: sogoObject inContext: woContext]) diff --git a/OpenChange/MAPIStoreGCSMessageTable.m b/OpenChange/MAPIStoreGCSMessageTable.m index 6eb096ebe..45589fcc9 100644 --- a/OpenChange/MAPIStoreGCSMessageTable.m +++ b/OpenChange/MAPIStoreGCSMessageTable.m @@ -58,7 +58,7 @@ NSCalendarDate *dateValue; int32_t longDate; - translatedRes = talloc (NULL, struct mapi_SPropertyRestriction); + translatedRes = talloc (memCtx, struct mapi_SPropertyRestriction); translatedRes->ulPropTag = (res->ulPropTag & 0xffff0000) | PT_LONG; translatedRes->relop = res->relop; dateValue = NSObjectFromMAPISPropValue (&res->lpProp); diff --git a/OpenChange/MAPIStoreJournalContext.h b/OpenChange/MAPIStoreJournalContext.h deleted file mode 100644 index b18c1fcfc..000000000 --- a/OpenChange/MAPIStoreJournalContext.h +++ /dev/null @@ -1,32 +0,0 @@ -/* MAPIStoreJournalContext.h - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#ifndef MAPISTOREJOURNALCONTEXT_H -#define MAPISTOREJOURNALCONTEXT_H - -#import "MAPIStoreFSBaseContext.h" - -@interface MAPIStoreJournalContext : MAPIStoreFSBaseContext - -@end - -#endif /* MAPISTOREJOURNALCONTEXT_H */ diff --git a/OpenChange/MAPIStoreJournalContext.m b/OpenChange/MAPIStoreJournalContext.m deleted file mode 100644 index 309fd56d2..000000000 --- a/OpenChange/MAPIStoreJournalContext.m +++ /dev/null @@ -1,36 +0,0 @@ -/* MAPIStoreJournalContext.m - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#import - -#import "MAPIStoreMapping.h" - -#import "MAPIStoreJournalContext.h" - -@implementation MAPIStoreJournalContext - -+ (NSString *) MAPIModuleName -{ - return @"journal"; -} - -@end diff --git a/OpenChange/MAPIStoreMailContext.h b/OpenChange/MAPIStoreMailContext.h index 44778857c..ab176cd1a 100644 --- a/OpenChange/MAPIStoreMailContext.h +++ b/OpenChange/MAPIStoreMailContext.h @@ -28,20 +28,6 @@ @interface MAPIStoreMailContext : MAPIStoreContext @end -@interface MAPIStoreInboxContext : MAPIStoreMailContext -@end - -@interface MAPIStoreSentItemsContext : MAPIStoreMailContext -@end - -@interface MAPIStoreDraftsContext : MAPIStoreMailContext -@end - -#import "MAPIStoreFSBaseContext.h" - -@interface MAPIStoreDeletedItemsContext : MAPIStoreFSBaseContext -@end - @interface MAPIStoreOutboxContext : MAPIStoreMailContext @end diff --git a/OpenChange/MAPIStoreMailContext.m b/OpenChange/MAPIStoreMailContext.m index 0ca511936..84d4c935a 100644 --- a/OpenChange/MAPIStoreMailContext.m +++ b/OpenChange/MAPIStoreMailContext.m @@ -20,94 +20,158 @@ * Boston, MA 02111-1307, USA. */ +#import +#import #import -#import "MAPIStoreMailFolder.h" -#import "MAPIStoreMapping.h" +#import +#import +#import "MAPIStoreMailFolder.h" +#import "MAPIStoreUserContext.h" +#import "NSString+MAPIStore.h" + +#import #import "MAPIStoreMailContext.h" +#include +#undef DEBUG +#include + +static Class MAPIStoreMailFolderK; + @implementation MAPIStoreMailContext -+ (NSString *) MAPIModuleName ++ (void) initialize { - return nil; + MAPIStoreMailFolderK = [MAPIStoreMailFolder class]; } -@end - -@implementation MAPIStoreInboxContext - + (NSString *) MAPIModuleName { - return @"inbox"; + return @"mail"; } -- (void) setupBaseFolder: (NSURL *) newURL ++ (enum mapistore_context_role) MAPIContextRole { - baseFolder = [MAPIStoreInboxFolder baseFolderWithURL: newURL - inContext: self]; - [baseFolder retain]; + return MAPISTORE_MAIL_ROLE; } -@end - -@implementation MAPIStoreSentItemsContext - -+ (NSString *) MAPIModuleName ++ (struct mapistore_contexts_list *) listContextsForUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb + inMemCtx: (TALLOC_CTX *) memCtx { - return @"sent-items"; + struct mapistore_contexts_list *firstContext = NULL, *context; + NSString *urlBase, *stringData, *currentName, *inboxName, *draftsName, *sentName, *trashName; + NSMutableArray *secondaryFolders; + enum mapistore_context_role role[] = {MAPISTORE_MAIL_ROLE, + MAPISTORE_DRAFTS_ROLE, + MAPISTORE_SENTITEMS_ROLE}; + NSString *folderName[3]; + NSUInteger count, max; + SOGoMailAccount *accountFolder; + MAPIStoreUserContext *userContext; + WOContext *woContext; + + userContext = [MAPIStoreUserContext userContextWithUsername: userName + andTDBIndexing: indexingTdb]; + accountFolder = [[userContext rootFolders] objectForKey: @"mail"]; + woContext = [userContext woContext]; + + inboxName = @"folderINBOX"; + folderName[0] = inboxName; + + draftsName = [NSString stringWithFormat: @"folder%@", + [accountFolder draftsFolderNameInContext: woContext]]; + folderName[1] = draftsName; + sentName = [NSString stringWithFormat: @"folder%@", + [accountFolder sentFolderNameInContext: woContext]]; + folderName[2] = sentName; + trashName = [NSString stringWithFormat: @"folder%@", + [accountFolder trashFolderNameInContext: woContext]]; + + urlBase = [NSString stringWithFormat: @"sogo://%@:%@@mail/", userName, userName]; + + for (count = 0; count < 3; count++) + { + context = talloc_zero (memCtx, struct mapistore_contexts_list); + stringData = [NSString stringWithFormat: @"%@%@", urlBase, + folderName[count]]; + context->url = [stringData asUnicodeInMemCtx: context]; + /* remove "folder" prefix */ + stringData = [[folderName[count] substringFromIndex: 6] fromCSSIdentifier]; + context->name = [stringData asUnicodeInMemCtx: context]; + context->main_folder = true; + context->role = role[count]; + context->tag = "tag"; + DLIST_ADD_END (firstContext, context, void); + } + + secondaryFolders = [[accountFolder toManyRelationshipKeysWithNamespaces: NO] + mutableCopy]; + [secondaryFolders autorelease]; + [secondaryFolders removeObject: inboxName]; + [secondaryFolders removeObject: draftsName]; + [secondaryFolders removeObject: draftsName]; + [secondaryFolders removeObject: sentName]; + [secondaryFolders removeObject: trashName]; + max = [secondaryFolders count]; + for (count = 0; count < max; count++) + { + context = talloc_zero (memCtx, struct mapistore_contexts_list); + currentName = [secondaryFolders objectAtIndex: count]; + stringData = [NSString stringWithFormat: @"%@%@", urlBase, currentName]; + context->url = [stringData asUnicodeInMemCtx: context]; + stringData = [[currentName substringFromIndex: 6] fromCSSIdentifier]; + context->name = [stringData asUnicodeInMemCtx: context]; + context->main_folder = false; + context->role = MAPISTORE_MAIL_ROLE; + context->tag = "tag"; + DLIST_ADD_END (firstContext, context, void); + } + + return firstContext; } -- (void) setupBaseFolder: (NSURL *) newURL ++ (NSString *) + createRootSecondaryFolderWithFID: (uint64_t) fid + andName: (NSString *) newFolderName + forUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb { - baseFolder = [MAPIStoreSentItemsFolder baseFolderWithURL: newURL - inContext: self]; - [baseFolder retain]; + NSString *mapistoreURI, *folderName; + MAPIStoreUserContext *userContext; + SOGoMailAccount *accountFolder; + SOGoMailFolder *newFolder; + + userContext = [MAPIStoreUserContext userContextWithUsername: userName + andTDBIndexing: indexingTdb]; + accountFolder = [[userContext rootFolders] objectForKey: @"mail"]; + folderName = [NSString stringWithFormat: @"folder%@", + [newFolderName asCSSIdentifier]]; + newFolder = [SOGoMailFolder objectWithName: folderName + inContainer: accountFolder]; + if ([newFolder create]) + mapistoreURI = [NSString stringWithFormat: @"sogo://%@:%@@mail/%@/", + userName, userName, folderName]; + else + mapistoreURI = nil; + + return mapistoreURI; } -@end - -@implementation MAPIStoreDraftsContext - -+ (NSString *) MAPIModuleName +- (Class) MAPIStoreFolderClass { - return @"drafts"; + return MAPIStoreMailFolderK; } -- (void) setupBaseFolder: (NSURL *) newURL +- (id) rootSOGoFolder { - baseFolder = [MAPIStoreDraftsFolder baseFolderWithURL: newURL - inContext: self]; - [baseFolder retain]; + return [[userContext rootFolders] objectForKey: @"mail"]; } @end -#import "MAPIStoreFSFolder.h" - -@implementation MAPIStoreDeletedItemsContext - -+ (NSString *) MAPIModuleName -{ - return @"deleted-items"; -} - -- (void) setupBaseFolder: (NSURL *) newURL -{ - baseFolder = [MAPIStoreFSFolder baseFolderWithURL: newURL inContext: self]; - [baseFolder retain]; -} - -// - (void) setupBaseFolder: (NSURL *) newURL -// { -// baseFolder = [MAPIStoreDeletedItemsFolder baseFolderWithURL: newURL -// inContext: self]; -// [baseFolder retain]; -// } - -@end - @implementation MAPIStoreOutboxContext + (NSString *) MAPIModuleName @@ -115,11 +179,35 @@ return @"outbox"; } -- (void) setupBaseFolder: (NSURL *) newURL ++ (struct mapistore_contexts_list *) listContextsForUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb + inMemCtx: (TALLOC_CTX *) memCtx { - baseFolder = [MAPIStoreOutboxFolder baseFolderWithURL: newURL - inContext: self]; - [baseFolder retain]; + struct mapistore_contexts_list *context; + NSString *url, *folderName; + SOGoMailAccount *accountFolder; + MAPIStoreUserContext *userContext; + WOContext *woContext; + + userContext = [MAPIStoreUserContext userContextWithUsername: userName + andTDBIndexing: indexingTdb]; + accountFolder = [[userContext rootFolders] objectForKey: @"mail"]; + woContext = [userContext woContext]; + folderName = [NSString stringWithFormat: @"folder%@", + [accountFolder draftsFolderNameInContext: woContext]]; + url = [NSString stringWithFormat: @"sogo://%@:%@@outbox/%@", userName, + userName, folderName]; + + context = talloc_zero (memCtx, struct mapistore_contexts_list); + context->url = [url asUnicodeInMemCtx: context]; + /* TODO: use a localized version of this display name */ + context->name = [@"Outbox" asUnicodeInMemCtx: context]; + context->main_folder = true; + context->role = MAPISTORE_OUTBOX_ROLE; + context->tag = "tag"; + context->prev = context; + + return context; } @end diff --git a/OpenChange/MAPIStoreMailFolder.h b/OpenChange/MAPIStoreMailFolder.h index 3d8bad3f2..8a5a51ec2 100644 --- a/OpenChange/MAPIStoreMailFolder.h +++ b/OpenChange/MAPIStoreMailFolder.h @@ -39,11 +39,6 @@ SOGoMAPIFSMessage *versionsMessage; } -/* subclasses */ -- (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) account - inContext: (WOContext *) woContext; - - /* synchronisation & versioning */ - (BOOL) synchroniseCache; - (NSNumber *) modseqFromMessageChangeNumber: (NSNumber *) changeNum; @@ -56,23 +51,4 @@ @end -@interface MAPIStoreInboxFolder : MAPIStoreMailFolder -{ - BOOL usesAltNameSpace; -} - -@end - -@interface MAPIStoreSentItemsFolder : MAPIStoreMailFolder -@end - -@interface MAPIStoreDraftsFolder : MAPIStoreMailFolder -@end - -// @interface MAPIStoreDeletedItemsFolder : MAPIStoreFFolder -// @end - -@interface MAPIStoreOutboxFolder : MAPIStoreMailFolder -@end - #endif /* MAPISTOREMAILFOLDER_H */ diff --git a/OpenChange/MAPIStoreMailFolder.m b/OpenChange/MAPIStoreMailFolder.m index 3647b0766..cd5c40371 100644 --- a/OpenChange/MAPIStoreMailFolder.m +++ b/OpenChange/MAPIStoreMailFolder.m @@ -77,71 +77,21 @@ static Class SOGoMailFolderK; [MAPIStoreAppointmentWrapper class]; } -- (id) initWithURL: (NSURL *) newURL - inContext: (MAPIStoreContext *) newContext +- (id) init { - SOGoUserFolder *userFolder; - SOGoMailAccounts *accountsFolder; - SOGoMailAccount *accountFolder; - SOGoFolder *currentContainer; - WOContext *woContext; - - if ((self = [super initWithURL: newURL - inContext: newContext])) + if ((self = [super init])) { - woContext = [newContext woContext]; - userFolder = [SOGoUserFolder objectWithName: [newURL user] - inContainer: MAPIApp]; - [parentContainersBag addObject: userFolder]; - [woContext setClientObject: userFolder]; - - accountsFolder = [userFolder lookupName: @"Mail" - inContext: woContext - acquire: NO]; - [parentContainersBag addObject: accountsFolder]; - [woContext setClientObject: accountsFolder]; - - accountFolder = [accountsFolder lookupName: @"0" - inContext: woContext - acquire: NO]; - [[accountFolder imap4Connection] - enableExtension: @"QRESYNC"]; - - [parentContainersBag addObject: accountFolder]; - [woContext setClientObject: accountFolder]; - - sogoObject = [self specialFolderFromAccount: accountFolder - inContext: woContext]; - [sogoObject retain]; - currentContainer = [sogoObject container]; - while (currentContainer != (SOGoFolder *) accountFolder) - { - [parentContainersBag addObject: currentContainer]; - currentContainer = [currentContainer container]; - } - - ASSIGN (versionsMessage, - [SOGoMAPIFSMessage objectWithName: @"versions.plist" - inContainer: propsFolder]); + versionsMessage = nil; } return self; } -- (id) initWithSOGoObject: (id) newSOGoObject - inContainer: (MAPIStoreObject *) newContainer +- (void) setupVersionsMessage { - // NSString *urlString; - - if ((self = [super initWithSOGoObject: newSOGoObject inContainer: newContainer])) - { - // urlString = [[self url] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; - ASSIGN (versionsMessage, - [SOGoMAPIFSMessage objectWithName: @"versions.plist" - inContainer: propsFolder]); - } - - return self; + ASSIGN (versionsMessage, + [SOGoMAPIFSMessage objectWithName: @"versions.plist" + inContainer: propsFolder]); } - (void) dealloc @@ -150,12 +100,25 @@ static Class SOGoMailFolderK; [super dealloc]; } -- (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) accountFolder - inContext: (WOContext *) woContext -{ - [self subclassResponsibility: _cmd]; - return nil; +- (void) addProperties: (NSDictionary *) newProperties +{ + NSString *newDisplayName; + NSMutableDictionary *propsCopy; + NSNumber *key; + + key = MAPIPropertyKey (PR_DISPLAY_NAME_UNICODE); + newDisplayName = [newProperties objectForKey: key]; + if (newDisplayName) + { + [(SOGoMailFolder *) sogoObject renameTo: newDisplayName]; + propsCopy = [newProperties mutableCopy]; + [propsCopy removeObjectForKey: key]; + [propsCopy autorelease]; + newProperties = propsCopy; + } + + [super addProperties: newProperties]; } - (MAPIStoreMessageTable *) messageTable @@ -164,10 +127,11 @@ static Class SOGoMailFolderK; return [MAPIStoreMailMessageTable tableForContainer: self]; } -- (NSString *) createFolder: (struct SRow *) aRow - withFID: (uint64_t) newFID - inContainer: (id) subfolderParent +- (enum mapistore_error) createFolder: (struct SRow *) aRow + withFID: (uint64_t) newFID + andKey: (NSString **) newKeyP { + enum mapistore_error rc; NSString *folderName, *nameInContainer; SOGoMailFolder *newFolder; int i; @@ -188,19 +152,42 @@ static Class SOGoMailFolderK; nameInContainer = [NSString stringWithFormat: @"folder%@", [folderName asCSSIdentifier]]; newFolder = [SOGoMailFolderK objectWithName: nameInContainer - inContainer: subfolderParent]; - if (![newFolder create]) - nameInContainer = nil; + inContainer: sogoObject]; + if ([newFolder create]) + *newKeyP = nameInContainer; + else if ([newFolder exists]) + rc = MAPISTORE_ERR_EXIST; + else + rc = MAPISTORE_ERR_DENIED; } - return nameInContainer; + return rc; } -- (NSString *) createFolder: (struct SRow *) aRow - withFID: (uint64_t) newFID +- (int) deleteFolder { - return [self createFolder: aRow withFID: newFID - inContainer: sogoObject]; + int rc; + NSException *error; + NSString *name; + + name = [self nameInContainer]; + if ([name isEqualToString: @"folderINBOX"]) + rc = MAPISTORE_ERR_DENIED; + else + { + error = [(SOGoMailFolder *) sogoObject delete]; + if (error) + rc = MAPISTORE_ERROR; + else + { + if (![versionsMessage delete]) + rc = MAPISTORE_SUCCESS; + else + rc = MAPISTORE_ERROR; + } + } + + return (rc == MAPISTORE_SUCCESS) ? [super deleteFolder] : rc; } - (int) getPrContentUnread: (void **) data @@ -848,7 +835,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP) /* sample: 1 OK [COPYUID 1311899334 1:3 11:13] Completed */ max = [line length]; - uniString = NSZoneMalloc (NULL, max * sizeof (unichar) + 1); + uniString = NSZoneMalloc (NULL, sizeof (unichar) * (max + 1)); [line getCharacters: uniString]; uniString[max] = 0; @@ -896,7 +883,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP) wantCopy: wantCopy]; /* Conversion of mids to IMAP uids */ - mapping = [[self context] mapping]; + mapping = [self mapping]; uids = [NSMutableArray arrayWithCapacity: midCount]; oldMessageURLs = [NSMutableArray arrayWithCapacity: midCount]; for (count = 0; count < midCount; count++) @@ -1025,6 +1012,8 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP) if (rights & RightsCreateSubfolders) [roles addObject: SOGoRole_FolderCreator]; + // [self logWithFormat: @"roles for rights %.8x = (%@)", rights, roles]; + return roles; } @@ -1051,172 +1040,10 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP) if (rights != 0) rights |= RoleNone; /* actually "folder visible" */ + + // [self logWithFormat: @"rights for roles (%@) = %.8x", roles, rights]; return rights; } @end - -@implementation MAPIStoreInboxFolder : MAPIStoreMailFolder - -- (id) initWithURL: (NSURL *) newURL - inContext: (MAPIStoreContext *) newContext -{ - NSDictionary *list, *response; - NGImap4Client *client; - - if ((self = [super initWithURL: newURL - inContext: newContext])) - { - client = [[(SOGoMailFolder *) sogoObject imap4Connection] client]; - list = [client list: @"" pattern: @"INBOX"]; - response = [[list objectForKey: @"RawResponse"] objectForKey: @"list"]; - usesAltNameSpace = [[response objectForKey: @"flags"] containsObject: @"noinferiors"]; - } - - return self; -} - -- (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) accountFolder - inContext: (WOContext *) woContext -{ - return [accountFolder inboxFolderInContext: woContext]; -} - -- (NSString *) createFolder: (struct SRow *) aRow - withFID: (uint64_t) newFID -{ - id subfolderParent; - - if (usesAltNameSpace) - subfolderParent = [(SOGoMailFolder *) sogoObject mailAccountFolder]; - else - subfolderParent = sogoObject; - - return [self createFolder: aRow withFID: newFID - inContainer: subfolderParent]; -} - -- (NSMutableString *) _imapFolderNameRepresentation: (NSString *) subfolderName -{ - NSMutableString *representation; - - if (usesAltNameSpace) - { - /* with "altnamespace", the subfolders are NEVER subfolders of INBOX... */; - if (![subfolderName hasPrefix: @"folder"]) - abort (); - representation - = [NSMutableString stringWithString: - [subfolderName substringFromIndex: 6]]; - } - else - representation = [super _imapFolderNameRepresentation: subfolderName]; - - return representation; -} - -- (NSArray *) folderKeysMatchingQualifier: (EOQualifier *) qualifier - andSortOrderings: (NSArray *) sortOrderings -{ - NSMutableArray *subfolderKeys; - SOGoMailAccount *account; - - if (usesAltNameSpace) - { - if (qualifier) - [self errorWithFormat: @"qualifier is not used for folders"]; - if (sortOrderings) - [self errorWithFormat: @"sort orderings are not used for folders"]; - - account = [(SOGoMailFolder *) sogoObject mailAccountFolder]; - subfolderKeys - = [[account toManyRelationshipKeysWithNamespaces: NO] - mutableCopy]; - [subfolderKeys removeObject: @"folderINBOX"]; - - [self _cleanupSubfolderKeys: subfolderKeys]; - } - else - subfolderKeys = [[super folderKeysMatchingQualifier: qualifier - andSortOrderings: sortOrderings] - mutableCopy]; - - /* TODO: remove special folders */ - - [subfolderKeys autorelease]; - - return subfolderKeys; -} - -- (id) lookupFolder: (NSString *) childKey -{ - MAPIStoreMailFolder *childFolder = nil; - SOGoMailAccount *account; - SOGoMailFolder *sogoFolder; - WOContext *woContext; - - if (usesAltNameSpace) - { - if ([[self folderKeys] containsObject: childKey]) - { - woContext = [[self context] woContext]; - account = [(SOGoMailFolder *) sogoObject mailAccountFolder]; - sogoFolder = [account lookupName: childKey inContext: woContext - acquire: NO]; - [sogoFolder setContext: woContext]; - childFolder = [MAPIStoreMailFolder mapiStoreObjectWithSOGoObject: sogoFolder - inContainer: self]; - } - } - else - childFolder = [super lookupFolder: childKey]; - - return childFolder; -} - -@end - -@implementation MAPIStoreSentItemsFolder : MAPIStoreMailFolder - -- (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) accountFolder - inContext: (WOContext *) woContext -{ - return [accountFolder sentFolderInContext: woContext]; -} - -@end - -@implementation MAPIStoreDraftsFolder : MAPIStoreMailFolder - -- (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) accountFolder - inContext: (WOContext *) woContext -{ - return [accountFolder draftsFolderInContext: woContext]; -} - -@end - -// @implementation MAPIStoreDeletedItemsFolder : MAPIStoreMailFolder - -// - (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) accountFolder -// inContext: (WOContext *) woContext -// { -// return [accountFolder trashFolderInContext: woContext]; -// } - -// @end - - -// -// -// -@implementation MAPIStoreOutboxFolder : MAPIStoreMailFolder - -- (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) accountFolder - inContext: (WOContext *) woContext -{ - return [accountFolder draftsFolderInContext: woContext]; -} - -@end diff --git a/OpenChange/MAPIStoreMailMessage.m b/OpenChange/MAPIStoreMailMessage.m index 8c165cc6e..00130d20c 100644 --- a/OpenChange/MAPIStoreMailMessage.m +++ b/OpenChange/MAPIStoreMailMessage.m @@ -62,7 +62,7 @@ @class iCalCalendar, iCalEvent; -static Class NSExceptionK, MAPIStoreSentItemsFolderK, MAPIStoreDraftsFolderK; +static Class NSExceptionK; @interface NSString (MAPIStoreMIME) @@ -105,8 +105,6 @@ static Class NSExceptionK, MAPIStoreSentItemsFolderK, MAPIStoreDraftsFolderK; + (void) initialize { NSExceptionK = [NSException class]; - MAPIStoreSentItemsFolderK = [MAPIStoreSentItemsFolder class]; - MAPIStoreDraftsFolderK = [MAPIStoreDraftsFolder class]; } - (id) init @@ -582,9 +580,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) coreInfos = [sogoObject fetchCoreInfos]; flags = [coreInfos objectForKey: @"flags"]; - if ([container isKindOfClass: MAPIStoreSentItemsFolderK] - || [container isKindOfClass: MAPIStoreDraftsFolderK]) - v |= MSGFLAG_FROMME; + // if ([container isKindOfClass: MAPIStoreSentItemsFolderK] + // || [container isKindOfClass: MAPIStoreDraftsFolderK]) + // v |= MSGFLAG_FROMME; if ([flags containsObject: @"seen"]) v |= MSGFLAG_READ; if ([[self attachmentKeys] diff --git a/OpenChange/MAPIStoreMailMessageTable.m b/OpenChange/MAPIStoreMailMessageTable.m index f4d5c2003..72133a210 100644 --- a/OpenChange/MAPIStoreMailMessageTable.m +++ b/OpenChange/MAPIStoreMailMessageTable.m @@ -326,7 +326,7 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK; - (int) getRow: (struct mapistore_property_data **) dataP withRowID: (uint32_t) rowId - andQueryType: (enum table_query_type) queryType + andQueryType: (enum mapistore_query_type) queryType inMemCtx: (TALLOC_CTX *) memCtx { if (!fetchedCoreInfos) diff --git a/OpenChange/MAPIStoreMailVolatileMessage.m b/OpenChange/MAPIStoreMailVolatileMessage.m index 5952eb405..a8e06b92c 100644 --- a/OpenChange/MAPIStoreMailVolatileMessage.m +++ b/OpenChange/MAPIStoreMailVolatileMessage.m @@ -813,7 +813,7 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, if (error) [self logWithFormat: @"an error occurred: '%@'", error]; - mapping = [[self context] mapping]; + mapping = [self mapping]; [mapping unregisterURLWithID: [self objectId]]; [self setIsNew: NO]; [properties removeAllObjects]; @@ -851,7 +851,7 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, newIdString = [[flag componentsSeparatedByString: @" "] objectAtIndex: 2]; mid = [self objectId]; - mapping = [[self context] mapping]; + mapping = [self mapping]; [mapping unregisterURLWithID: mid]; [sogoObject setNameInContainer: [NSString stringWithFormat: @"%@.eml", newIdString]]; [mapping registerURL: [self url] withID: mid]; diff --git a/OpenChange/MAPIStoreMessage.m b/OpenChange/MAPIStoreMessage.m index be25d9a18..7e7a61b2e 100644 --- a/OpenChange/MAPIStoreMessage.m +++ b/OpenChange/MAPIStoreMessage.m @@ -39,6 +39,7 @@ #import "MAPIStorePropertySelectors.h" #import "MAPIStoreSamDBUtils.h" #import "MAPIStoreTypes.h" +#import "MAPIStoreUserContext.h" #import "NSData+MAPIStore.h" #import "NSObject+MAPIStore.h" #import "NSString+MAPIStore.h" @@ -286,10 +287,11 @@ rtf2html (NSData *compressedRTF) { enum mapistore_error rc; MAPIStoreContext *context; + SOGoUser *ownerUser; context = [self context]; - - if ([[context activeUser] isEqual: [context ownerUser]] + ownerUser = [[self userContext] sogoUser]; + if ([[context activeUser] isEqual: ownerUser] || [self subscriberCanModifyMessage]) rc = [super addPropertiesFromRow: aRow]; else @@ -432,9 +434,11 @@ rtf2html (NSData *compressedRTF) uint64_t folderId; struct mapistore_context *mstoreCtx; MAPIStoreContext *context; + SOGoUser *ownerUser; context = [self context]; - if ([[context activeUser] isEqual: [context ownerUser]] + ownerUser = [[self userContext] sogoUser]; + if ([[context activeUser] isEqual: ownerUser] || ((isNew && [(MAPIStoreFolder *) container subscriberCanCreateMessages]) || (!isNew && [self subscriberCanModifyMessage]))) @@ -560,9 +564,11 @@ rtf2html (NSData *compressedRTF) uint32_t access = 0; BOOL userIsOwner; MAPIStoreContext *context; + SOGoUser *ownerUser; context = [self context]; - userIsOwner = [[context activeUser] isEqual: [context ownerUser]]; + ownerUser = [[self userContext] sogoUser]; + userIsOwner = [[context activeUser] isEqual: ownerUser]; if (userIsOwner || [self subscriberCanModifyMessage]) access |= 0x01; if (userIsOwner || [self subscriberCanReadMessage]) @@ -587,9 +593,11 @@ rtf2html (NSData *compressedRTF) uint32_t access = 0; BOOL userIsOwner; MAPIStoreContext *context; + SOGoUser *ownerUser; context = [self context]; - userIsOwner = [[context activeUser] isEqual: [context ownerUser]]; + ownerUser = [[self userContext] sogoUser]; + userIsOwner = [[context activeUser] isEqual: ownerUser]; if (userIsOwner || [self subscriberCanModifyMessage]) access = 0x01; else @@ -862,14 +870,15 @@ rtf2html (NSData *compressedRTF) - (NSArray *) activeUserRoles { MAPIStoreContext *context; + MAPIStoreUserContext *userContext; if (!activeUserRoles) { context = [self context]; - + userContext = [self userContext]; activeUserRoles = [[context activeUser] rolesForObject: sogoObject - inContext: [context woContext]]; + inContext: [userContext woContext]]; [activeUserRoles retain]; } diff --git a/OpenChange/MAPIStoreNotesContext.m b/OpenChange/MAPIStoreNotesContext.m index 7171f7da0..b73a4032a 100644 --- a/OpenChange/MAPIStoreNotesContext.m +++ b/OpenChange/MAPIStoreNotesContext.m @@ -27,6 +27,9 @@ #import "MAPIStoreNotesContext.h" +#undef DEBUG +#include + @implementation MAPIStoreNotesContext + (NSString *) MAPIModuleName @@ -34,11 +37,22 @@ return @"notes"; } -- (void) setupBaseFolder: (NSURL *) newURL ++ (struct mapistore_contexts_list *) listContextsForUser: (NSString *) userName + withTDBIndexing: (struct tdb_wrap *) indexingTdb + inMemCtx: (TALLOC_CTX *) memCtx { - baseFolder = [MAPIStoreNotesFolder baseFolderWithURL: newURL - inContext: self]; - [baseFolder retain]; + struct mapistore_contexts_list *context; + + context = talloc_zero(memCtx, struct mapistore_contexts_list); + context->url = talloc_asprintf (context, "sogo://%s@notes/", + [userName UTF8String]); + // context->name = "Notes personnelles"; + context->main_folder = true; + context->role = MAPISTORE_NOTES_ROLE; + context->tag = "tag"; + context->prev = context; + + return context; } @end diff --git a/OpenChange/MAPIStoreObject.h b/OpenChange/MAPIStoreObject.h index d3b10cfbe..773db8bd4 100644 --- a/OpenChange/MAPIStoreObject.h +++ b/OpenChange/MAPIStoreObject.h @@ -35,8 +35,11 @@ @class EOQualifier; +@class MAPIStoreContext; @class MAPIStoreFolder; +@class MAPIStoreMapping; @class MAPIStoreTable; +@class MAPIStoreUserContext; @interface MAPIStoreObject : NSObject { @@ -71,7 +74,9 @@ - (id) sogoObject; - (MAPIStoreObject *) container; -- (id) context; +- (MAPIStoreContext *) context; +- (MAPIStoreUserContext *) userContext; +- (MAPIStoreMapping *) mapping; - (void) cleanupCaches; diff --git a/OpenChange/MAPIStoreObject.m b/OpenChange/MAPIStoreObject.m index 6652940f1..d064aca30 100644 --- a/OpenChange/MAPIStoreObject.m +++ b/OpenChange/MAPIStoreObject.m @@ -30,6 +30,7 @@ #import "MAPIStoreFolder.h" #import "MAPIStorePropertySelectors.h" #import "MAPIStoreTypes.h" +#import "MAPIStoreUserContext.h" #import "NSDate+MAPIStore.h" #import "NSData+MAPIStore.h" #import "NSObject+MAPIStore.h" @@ -170,11 +171,21 @@ static Class NSExceptionK, MAPIStoreFolderK; return [sogoObject nameInContainer]; } -- (id) context +- (MAPIStoreContext *) context { return [container context]; } +- (MAPIStoreUserContext *) userContext +{ + return [[self context] userContext]; +} + +- (MAPIStoreMapping *) mapping +{ + return [[self userContext] mapping]; +} + - (void) cleanupCaches { } @@ -217,7 +228,7 @@ static Class NSExceptionK, MAPIStoreFolderK; NSTimeZone *tz; WOContext *woContext; - woContext = [[self context] woContext]; + woContext = [[self userContext] woContext]; owner = [sogoObject ownerInContext: woContext]; ud = [[SOGoUser userWithLogin: owner] userDefaults]; tz = [ud timeZone]; diff --git a/OpenChange/MAPIStoreRemindersContext.h b/OpenChange/MAPIStoreRemindersContext.h deleted file mode 100644 index 337635110..000000000 --- a/OpenChange/MAPIStoreRemindersContext.h +++ /dev/null @@ -1,32 +0,0 @@ -/* MAPIStoreRemindersContext.h - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#ifndef MAPISTOREREMINDERSCONTEXT_H -#define MAPISTOREREMINDERSCONTEXT_H - -#import "MAPIStoreFSBaseContext.h" - -@interface MAPIStoreRemindersContext : MAPIStoreFSBaseContext - -@end - -#endif /* MAPISTOREREMINDERSCONTEXT_H */ diff --git a/OpenChange/MAPIStoreRemindersContext.m b/OpenChange/MAPIStoreRemindersContext.m deleted file mode 100644 index 262633ed5..000000000 --- a/OpenChange/MAPIStoreRemindersContext.m +++ /dev/null @@ -1,36 +0,0 @@ -/* MAPIStoreRemindersContext.m - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#import - -#import "MAPIStoreMapping.h" - -#import "MAPIStoreRemindersContext.h" - -@implementation MAPIStoreRemindersContext - -+ (NSString *) MAPIModuleName -{ - return @"reminders"; -} - -@end diff --git a/OpenChange/MAPIStoreSOGo.m b/OpenChange/MAPIStoreSOGo.m index ffdbea6d0..7f6767eb6 100644 --- a/OpenChange/MAPIStoreSOGo.m +++ b/OpenChange/MAPIStoreSOGo.m @@ -42,18 +42,18 @@ #import "MAPIStoreObject.h" #import "MAPIStoreTable.h" #import "NSObject+MAPIStore.h" +#import "NSString+MAPIStore.h" -#undef DEBUG -#include -#include -#include #include #include +static Class MAPIStoreContextK = Nil; + static enum mapistore_error sogo_backend_unexpected_error() { NSLog (@" UNEXPECTED WEIRDNESS: RECEIVED NO OBJECT"); + abort(); return MAPISTORE_SUCCESS; } @@ -85,7 +85,7 @@ sogo_backend_init (void) [SOGoSystemDefaults sharedSystemDefaults]; - // /* We force the plugin to base its configuration on the SOGo tree. */ + /* We force the plugin to base its configuration on the SOGo tree. */ ud = [NSUserDefaults standardUserDefaults]; [ud registerDefaults: [ud persistentDomainForName: @"sogod"]]; @@ -102,6 +102,8 @@ sogo_backend_init (void) [[SOGoCache sharedCache] disableRequestsCache]; [[SOGoCache sharedCache] disableLocalCache]; + MAPIStoreContextK = NSClassFromString (@"MAPIStoreContext"); + [pool release]; return MAPISTORE_SUCCESS; @@ -122,7 +124,6 @@ sogo_backend_create_context(TALLOC_CTX *mem_ctx, const char *uri, void **context_object) { NSAutoreleasePool *pool; - Class MAPIStoreContextK; MAPIStoreContext *context; int rc; @@ -130,7 +131,6 @@ sogo_backend_create_context(TALLOC_CTX *mem_ctx, pool = [NSAutoreleasePool new]; - MAPIStoreContextK = NSClassFromString (@"MAPIStoreContext"); if (MAPIStoreContextK) { rc = [MAPIStoreContextK openContext: &context @@ -148,6 +148,72 @@ sogo_backend_create_context(TALLOC_CTX *mem_ctx, return rc; } +static enum mapistore_error +sogo_backend_create_root_folder (const char *username, + enum mapistore_context_role role, + uint64_t fid, const char *name, + struct tdb_wrap *indexingTdb, + TALLOC_CTX *mem_ctx, char **mapistore_urip) +{ + NSAutoreleasePool *pool; + NSString *userName, *folderName; + NSString *mapistoreUri; + int rc; + + DEBUG(0, ("[SOGo: %s:%d]\n", __FUNCTION__, __LINE__)); + + pool = [NSAutoreleasePool new]; + + if (MAPIStoreContextK) + { + userName = [NSString stringWithUTF8String: username]; + folderName = [NSString stringWithUTF8String: name]; + rc = [MAPIStoreContextK createRootFolder: &mapistoreUri + withFID: fid + andName: folderName + forUser: userName + withRole: role + andTDBIndexing: indexingTdb]; + if (rc == MAPISTORE_SUCCESS) + *mapistore_urip = [mapistoreUri asUnicodeInMemCtx: mem_ctx]; + } + else + rc = MAPISTORE_ERROR; + + [pool release]; + + return rc; +} + +static enum mapistore_error +sogo_backend_list_contexts(const char *username, struct tdb_wrap *indexingTdb, + TALLOC_CTX *mem_ctx, + struct mapistore_contexts_list **contexts_listp) +{ + NSAutoreleasePool *pool; + NSString *userName; + int rc; + + DEBUG(0, ("[SOGo: %s:%d]\n", __FUNCTION__, __LINE__)); + + pool = [NSAutoreleasePool new]; + + if (MAPIStoreContextK) + { + userName = [NSString stringWithUTF8String: username]; + *contexts_listp = [MAPIStoreContextK listAllContextsForUser: userName + withTDBIndexing: indexingTdb + inMemCtx: mem_ctx]; + rc = MAPISTORE_SUCCESS; + } + else + rc = MAPISTORE_ERROR; + + [pool release]; + + return rc; +} + // andFID: fid // uint64_t fid, // void **private_data) @@ -305,7 +371,7 @@ sogo_folder_create_folder(void *folder_object, TALLOC_CTX *mem_ctx, \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE_ERROR */ static enum mapistore_error -sogo_folder_delete_folder(void *folder_object, uint64_t fid) +sogo_folder_delete(void *folder_object) { struct MAPIStoreTallocWrapper *wrapper; NSAutoreleasePool *pool; @@ -319,7 +385,7 @@ sogo_folder_delete_folder(void *folder_object, uint64_t fid) wrapper = folder_object; folder = wrapper->MAPIStoreSOGoObject; pool = [NSAutoreleasePool new]; - rc = [folder deleteFolderWithFID: fid]; + rc = [folder deleteFolder]; [pool release]; } else @@ -331,7 +397,7 @@ sogo_folder_delete_folder(void *folder_object, uint64_t fid) } static enum mapistore_error -sogo_folder_get_child_count(void *folder_object, uint8_t table_type, uint32_t *child_count) +sogo_folder_get_child_count(void *folder_object, enum mapistore_table_type table_type, uint32_t *child_count) { struct MAPIStoreTallocWrapper *wrapper; NSAutoreleasePool *pool; @@ -494,7 +560,7 @@ sogo_folder_move_copy_messages(void *folder_object, static enum mapistore_error sogo_folder_get_deleted_fmids(void *folder_object, TALLOC_CTX *mem_ctx, - uint8_t table_type, uint64_t change_num, + enum mapistore_table_type table_type, uint64_t change_num, struct I8Array_r **fmidsp, uint64_t *cnp) { struct MAPIStoreTallocWrapper *wrapper; @@ -526,7 +592,7 @@ sogo_folder_get_deleted_fmids(void *folder_object, TALLOC_CTX *mem_ctx, static enum mapistore_error sogo_folder_open_table(void *folder_object, TALLOC_CTX *mem_ctx, - uint8_t table_type, uint32_t handle_id, + enum mapistore_table_type table_type, uint32_t handle_id, void **table_object, uint32_t *row_count) { struct MAPIStoreTallocWrapper *wrapper; @@ -974,7 +1040,7 @@ sogo_table_set_sort_order (void *table_object, struct SSortOrderSet *sort_order, static enum mapistore_error sogo_table_get_row (void *table_object, TALLOC_CTX *mem_ctx, - enum table_query_type query_type, uint32_t row_id, + enum mapistore_query_type query_type, uint32_t row_id, struct mapistore_property_data **data) { struct MAPIStoreTallocWrapper *wrapper; @@ -1003,7 +1069,7 @@ sogo_table_get_row (void *table_object, TALLOC_CTX *mem_ctx, static enum mapistore_error sogo_table_get_row_count (void *table_object, - enum table_query_type query_type, + enum mapistore_query_type query_type, uint32_t *row_countp) { struct MAPIStoreTallocWrapper *wrapper; @@ -1211,11 +1277,13 @@ int mapistore_init_backend(void) backend.backend.namespace = "sogo://"; backend.backend.init = sogo_backend_init; backend.backend.create_context = sogo_backend_create_context; + backend.backend.create_root_folder = sogo_backend_create_root_folder; + backend.backend.list_contexts = sogo_backend_list_contexts; backend.context.get_path = sogo_context_get_path; backend.context.get_root_folder = sogo_context_get_root_folder; backend.folder.open_folder = sogo_folder_open_folder; backend.folder.create_folder = sogo_folder_create_folder; - backend.folder.delete_folder = sogo_folder_delete_folder; + backend.folder.delete = sogo_folder_delete; backend.folder.open_message = sogo_folder_open_message; backend.folder.create_message = sogo_folder_create_message; backend.folder.delete_message = sogo_folder_delete_message; diff --git a/OpenChange/MAPIStoreScheduleContext.h b/OpenChange/MAPIStoreScheduleContext.h deleted file mode 100644 index 473299891..000000000 --- a/OpenChange/MAPIStoreScheduleContext.h +++ /dev/null @@ -1,32 +0,0 @@ -/* MAPIStoreScheduleContext.h - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#ifndef MAPISTORESCHEDULECONTEXT_H -#define MAPISTORESCHEDULECONTEXT_H - -#import "MAPIStoreFSBaseContext.h" - -@interface MAPIStoreScheduleContext : MAPIStoreFSBaseContext - -@end - -#endif /* MAPISTORESCHEDULECONTEXT_H */ diff --git a/OpenChange/MAPIStoreScheduleContext.m b/OpenChange/MAPIStoreScheduleContext.m deleted file mode 100644 index 4b0af83d6..000000000 --- a/OpenChange/MAPIStoreScheduleContext.m +++ /dev/null @@ -1,36 +0,0 @@ -/* MAPIStoreScheduleContext.m - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#import - -#import "MAPIStoreMapping.h" - -#import "MAPIStoreScheduleContext.h" - -@implementation MAPIStoreScheduleContext - -+ (NSString *) MAPIModuleName -{ - return @"schedule"; -} - -@end diff --git a/OpenChange/MAPIStoreSearchContext.h b/OpenChange/MAPIStoreSearchContext.h deleted file mode 100644 index c8aee3613..000000000 --- a/OpenChange/MAPIStoreSearchContext.h +++ /dev/null @@ -1,32 +0,0 @@ -/* MAPIStoreSearchContext.h - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#ifndef MAPISTORESEARCHCONTEXT_H -#define MAPISTORESEARCHCONTEXT_H - -#import "MAPIStoreFSBaseContext.h" - -@interface MAPIStoreSearchContext : MAPIStoreFSBaseContext - -@end - -#endif /* MAPISTORESEARCHCONTEXT_H */ diff --git a/OpenChange/MAPIStoreSearchContext.m b/OpenChange/MAPIStoreSearchContext.m deleted file mode 100644 index 3820eb22e..000000000 --- a/OpenChange/MAPIStoreSearchContext.m +++ /dev/null @@ -1,36 +0,0 @@ -/* MAPIStoreSearchContext.m - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#import - -#import "MAPIStoreMapping.h" - -#import "MAPIStoreSearchContext.h" - -@implementation MAPIStoreSearchContext - -+ (NSString *) MAPIModuleName -{ - return @"search"; -} - -@end diff --git a/OpenChange/MAPIStoreShortcutsContext.h b/OpenChange/MAPIStoreShortcutsContext.h deleted file mode 100644 index 280f84367..000000000 --- a/OpenChange/MAPIStoreShortcutsContext.h +++ /dev/null @@ -1,32 +0,0 @@ -/* MAPIStoreShortcutsContext.h - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#ifndef MAPISTORESHORTCUTSCONTEXT_H -#define MAPISTORESHORTCUTSCONTEXT_H - -#import "MAPIStoreFSBaseContext.h" - -@interface MAPIStoreShortcutsContext : MAPIStoreFSBaseContext - -@end - -#endif /* MAPISTORESHORTCUTSCONTEXT_H */ diff --git a/OpenChange/MAPIStoreShortcutsContext.m b/OpenChange/MAPIStoreShortcutsContext.m deleted file mode 100644 index 66a7aa1ce..000000000 --- a/OpenChange/MAPIStoreShortcutsContext.m +++ /dev/null @@ -1,36 +0,0 @@ -/* MAPIStoreShortcutsContext.m - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#import - -#import "MAPIStoreMapping.h" - -#import "MAPIStoreShortcutsContext.h" - -@implementation MAPIStoreShortcutsContext - -+ (NSString *) MAPIModuleName -{ - return @"shortcuts"; -} - -@end diff --git a/OpenChange/MAPIStoreTable.h b/OpenChange/MAPIStoreTable.h index 43314d313..fbf13a680 100644 --- a/OpenChange/MAPIStoreTable.h +++ b/OpenChange/MAPIStoreTable.h @@ -27,6 +27,9 @@ #import +#undef DEBUG +#include + #define SENSITIVITY_NONE 0 #define SENSITIVITY_PERSONAL 1 #define SENSITIVITY_PRIVATE 2 @@ -62,7 +65,7 @@ typedef enum { uint32_t currentRow; MAPIStoreObject *currentChild; - uint8_t tableType; /* mapistore */ + enum mapistore_table_type tableType; /* mapistore */ /* proof of concept */ uint16_t columnsCount; @@ -75,13 +78,13 @@ typedef enum { - (id) initForContainer: (MAPIStoreObject *) newContainer; - (id) container; -- (uint8_t) tableType; +- (enum mapistore_table_type) tableType; - (void) setHandleId: (uint32_t) newHandleId; - (void) destroyHandle: (uint32_t) handleId; - (id) childAtRowID: (uint32_t) rowId - forQueryType: (enum table_query_type) queryType; + forQueryType: (enum mapistore_query_type) queryType; - (void) cleanupCaches; @@ -92,10 +95,10 @@ typedef enum { withCount: (uint16_t) newColumCount; - (int) getRow: (struct mapistore_property_data **) dataP withRowID: (uint32_t) rowId - andQueryType: (enum table_query_type) queryType + andQueryType: (enum mapistore_query_type) queryType inMemCtx: (TALLOC_CTX *) memCtx; - (int) getRowCount: (uint32_t *) countP - withQueryType: (enum table_query_type) queryType; + withQueryType: (enum mapistore_query_type) queryType; - (void) notifyChangesForChild: (MAPIStoreObject *) child; diff --git a/OpenChange/MAPIStoreTable.m b/OpenChange/MAPIStoreTable.m index 530949709..620b8f248 100644 --- a/OpenChange/MAPIStoreTable.m +++ b/OpenChange/MAPIStoreTable.m @@ -337,7 +337,7 @@ static Class NSDataK, NSStringK; return container; } -- (uint8_t) tableType +- (enum mapistore_table_type) tableType { return tableType; } @@ -351,7 +351,7 @@ static Class NSDataK, NSStringK; - (void) destroyHandle: (uint32_t) tableHandleId { - if (handleId == tableHandleId) + if (tableHandleId && (handleId == tableHandleId)) [[MAPIStoreActiveTables activeTables] unregisterTable: self]; } @@ -765,7 +765,7 @@ static Class NSDataK, NSStringK; } - (id) childAtRowID: (uint32_t) rowId - forQueryType: (enum table_query_type) queryType + forQueryType: (enum mapistore_query_type) queryType { id child; NSArray *children, *restrictedChildren; @@ -833,7 +833,7 @@ static Class NSDataK, NSStringK; - (int) getRow: (struct mapistore_property_data **) dataP withRowID: (uint32_t) rowId - andQueryType: (enum table_query_type) queryType + andQueryType: (enum mapistore_query_type) queryType inMemCtx: (TALLOC_CTX *) memCtx { NSUInteger count; @@ -860,7 +860,7 @@ static Class NSDataK, NSStringK; } - (int) getRowCount: (uint32_t *) countP - withQueryType: (enum table_query_type) queryType + withQueryType: (enum mapistore_query_type) queryType { NSArray *children; diff --git a/OpenChange/MAPIStoreTasksContext.m b/OpenChange/MAPIStoreTasksContext.m index 1d71c56da..c8714c469 100644 --- a/OpenChange/MAPIStoreTasksContext.m +++ b/OpenChange/MAPIStoreTasksContext.m @@ -21,24 +21,38 @@ */ #import +#import #import "MAPIStoreTasksFolder.h" -#import "MAPIStoreMapping.h" +#import "MAPIStoreUserContext.h" #import "MAPIStoreTasksContext.h" +#undef DEBUG +#include + +static Class MAPIStoreTasksFolderK; + @implementation MAPIStoreTasksContext ++ (void) initialize +{ + MAPIStoreTasksFolderK = [MAPIStoreTasksFolder class]; +} + + (NSString *) MAPIModuleName { return @"tasks"; } -- (void) setupBaseFolder: (NSURL *) newURL ++ (enum mapistore_context_role) MAPIContextRole { - baseFolder = [MAPIStoreTasksFolder baseFolderWithURL: newURL - inContext: self]; - [baseFolder retain]; + return MAPISTORE_TASKS_ROLE; +} + +- (Class) MAPIStoreFolderClass +{ + return MAPIStoreTasksFolderK; } @end diff --git a/OpenChange/MAPIStoreTasksFolder.m b/OpenChange/MAPIStoreTasksFolder.m index 228fb8eb5..323bdf6e8 100644 --- a/OpenChange/MAPIStoreTasksFolder.m +++ b/OpenChange/MAPIStoreTasksFolder.m @@ -42,37 +42,6 @@ @implementation MAPIStoreTasksFolder -- (id) initWithURL: (NSURL *) newURL - inContext: (MAPIStoreContext *) newContext -{ - SOGoUserFolder *userFolder; - SOGoAppointmentFolders *parentFolder; - WOContext *woContext; - - if ((self = [super initWithURL: newURL - inContext: newContext])) - { - woContext = [newContext woContext]; - userFolder = [SOGoUserFolder objectWithName: [newURL user] - inContainer: MAPIApp]; - [parentContainersBag addObject: userFolder]; - [woContext setClientObject: userFolder]; - - parentFolder = [userFolder lookupName: @"Calendar" - inContext: woContext - acquire: NO]; - [parentContainersBag addObject: parentFolder]; - [woContext setClientObject: parentFolder]; - - sogoObject = [parentFolder lookupName: @"personal" - inContext: woContext - acquire: NO]; - [sogoObject retain]; - } - - return self; -} - - (MAPIStoreMessageTable *) messageTable { [self synchroniseCache]; diff --git a/OpenChange/MAPIStoreTasksMessage.m b/OpenChange/MAPIStoreTasksMessage.m index 1418d76fc..81c2a9ba9 100644 --- a/OpenChange/MAPIStoreTasksMessage.m +++ b/OpenChange/MAPIStoreTasksMessage.m @@ -22,8 +22,10 @@ */ #import +#import #import #import +#import #import #import #import @@ -325,13 +327,11 @@ iCalCalendar *vCalendar; iCalToDo *vToDo; id value; - SOGoUserDefaults *ud; - iCalTimeZone *tz; iCalDateTime *date; - NSString *owner, *status, *priority; + NSString *status, *priority; NSCalendarDate *now; + NSInteger tzOffset; - owner = [sogoObject ownerInContext: nil]; vToDo = [sogoObject component: YES secure: NO]; vCalendar = [vToDo parent]; [vCalendar setProdID: @"-//Inverse inc.//OpenChange+SOGo//EN"]; @@ -366,17 +366,16 @@ [vToDo setTimeStampAsDate: value]; } - ud = [[SOGoUser userWithLogin: owner] userDefaults]; - tz = [iCalTimeZone timeZoneForName: [ud timeZoneName]]; - [vCalendar addTimeZone: tz]; - // start value = [properties objectForKey: MAPIPropertyKey (PidLidTaskStartDate)]; if (value) { date = (iCalDateTime *) [vToDo uniqueChildWithTag: @"dtstart"]; - [date setTimeZone: tz]; - [date setDateTime: value]; + tzOffset = [[value timeZone] secondsFromGMTForDate: value]; + value = [value dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: -tzOffset]; + [date setDate: value]; } else { @@ -388,8 +387,11 @@ if (value) { date = (iCalDateTime *) [vToDo uniqueChildWithTag: @"due"]; - [date setTimeZone: tz]; - [date setDateTime: value]; + tzOffset = [[value timeZone] secondsFromGMTForDate: value]; + value = [value dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: -tzOffset]; + [date setDate: value]; } else { @@ -401,8 +403,11 @@ if (value) { date = (iCalDateTime *) [vToDo uniqueChildWithTag: @"completed"]; - [date setTimeZone: tz]; - [date setDateTime: value]; + tzOffset = [[value timeZone] secondsFromGMTForDate: value]; + value = [value dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: -tzOffset]; + [date setDate: value]; } else { diff --git a/OpenChange/MAPIStoreTypes.m b/OpenChange/MAPIStoreTypes.m index 06eadf4e9..980431d1b 100644 --- a/OpenChange/MAPIStoreTypes.m +++ b/OpenChange/MAPIStoreTypes.m @@ -148,7 +148,6 @@ NSObjectFromMAPISPropValue (const struct mapi_SPropValue *value) // #define PT_ERROR 0xa // #define PT_OBJECT 0xd // #define PT_I8 0x14 -// #define PT_SVREID 0xFB // #define PT_SRESTRICT 0xFD // #define PT_ACTIONS 0xFE result = [NSNull null]; @@ -244,7 +243,6 @@ NSObjectFromSPropValue (const struct SPropValue *value) // #define PT_ERROR 0xa // #define PT_OBJECT 0xd // #define PT_I8 0x14 -// #define PT_SVREID 0xFB // #define PT_SRESTRICT 0xFD // #define PT_ACTIONS 0xFE result = [NSNull null]; diff --git a/OpenChange/MAPIStoreUserContext.h b/OpenChange/MAPIStoreUserContext.h new file mode 100644 index 000000000..43ae2e7ed --- /dev/null +++ b/OpenChange/MAPIStoreUserContext.h @@ -0,0 +1,79 @@ +/* MAPIStoreUserContext.h - this file is part of $PROJECT_NAME_HERE$ + * + * Copyright (C) 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 2, 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. + */ + +#ifndef MAPISTOREUSERCONTEXT_H +#define MAPISTOREUSERCONTEXT_H + +#import + +@class NSMutableDictionary; +@class NSString; + +@class WOContext; + +@class SOGoAppointmentFolders; +@class SOGoContactFolders; +@class SOGoMailAccount; +@class SOGoUser; +@class SOGoUserFolder; + +@class MAPIStoreAuthenticator; +@class MAPIStoreMapping; + +@interface MAPIStoreUserContext : NSObject +{ + NSString *username; + SOGoUser *sogoUser; + + SOGoUserFolder *userFolder; + NSMutableArray *containersBag; + NSMutableDictionary *rootFolders; + + MAPIStoreMapping *mapping; + + WOContext *woContext; + MAPIStoreAuthenticator *authenticator; +} + ++ (id) userContextWithUsername: (NSString *) username + andTDBIndexing: (struct tdb_wrap *) indexingTdb; + +- (id) initWithUsername: (NSString *) newUsername + andTDBIndexing: (struct tdb_wrap *) indexingTdb; + +- (NSString *) username; +- (SOGoUser *) sogoUser; + +- (SOGoUserFolder *) userFolder; + +- (NSDictionary *) rootFolders; + +- (MAPIStoreMapping *) mapping; + +/* SOGo hacky magic */ +- (void) activateWithUser: (SOGoUser *) activeUser; +- (MAPIStoreAuthenticator *) authenticator; +- (WOContext *) woContext; + +@end + +#endif /* MAPISTOREUSERCONTEXT_H */ diff --git a/OpenChange/MAPIStoreUserContext.m b/OpenChange/MAPIStoreUserContext.m new file mode 100644 index 000000000..222f1d560 --- /dev/null +++ b/OpenChange/MAPIStoreUserContext.m @@ -0,0 +1,222 @@ +/* MAPIStoreUserContext.m - this file is part of SOGo + * + * Copyright (C) 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 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import + +#import +#import + +#import + +#import +#import +#import +#import + +#import "MAPIApplication.h" +#import "MAPIStoreAuthenticator.h" +#import "MAPIStoreMapping.h" + +#import "MAPIStoreUserContext.h" + +static NSMapTable *contextsTable = nil; + +@implementation MAPIStoreUserContext + ++ (void) initialize +{ + contextsTable = [NSMapTable mapTableWithStrongToWeakObjects]; + [contextsTable retain]; +} + ++ (id) userContextWithUsername: (NSString *) username + andTDBIndexing: (struct tdb_wrap *) indexingTdb; +{ + id userContext; + + userContext = [contextsTable objectForKey: username]; + if (!userContext) + { + userContext = [[self alloc] initWithUsername: username + andTDBIndexing: indexingTdb]; + [userContext autorelease]; + [contextsTable setObject: userContext forKey: username]; + } + + return userContext; +} + +- (id) init +{ + if ((self = [super init])) + { + username = nil; + sogoUser = nil; + + userFolder = nil; + containersBag = [NSMutableArray new]; + rootFolders = nil; + + mapping = nil; + + authenticator = nil; + woContext = [WOContext contextWithRequest: nil]; + [woContext retain]; + } + + return self; +} + +- (id) initWithUsername: (NSString *) newUsername + andTDBIndexing: (struct tdb_wrap *) indexingTdb +{ + if ((self = [self init])) + { + /* "username" will be retained by table */ + username = newUsername; + if (indexingTdb) + ASSIGN (mapping, [MAPIStoreMapping mappingForUsername: username + withIndexing: indexingTdb]); + + authenticator = [MAPIStoreAuthenticator new]; + [authenticator setUsername: username]; + /* TODO: very hackish (IMAP access) */ + [authenticator setPassword: username]; + } + + return self; +} + +- (void) dealloc +{ + [userFolder release]; + [containersBag release]; + [rootFolders release]; + + [authenticator release]; + [mapping release]; + + [sogoUser release]; + + [contextsTable removeObjectForKey: username]; + + [super dealloc]; +} + +- (NSString *) username +{ + return username; +} + +- (SOGoUser *) sogoUser +{ + if (!sogoUser) + ASSIGN (sogoUser, [SOGoUser userWithLogin: username]); + + return sogoUser; +} + +- (SOGoUserFolder *) userFolder +{ + if (!userFolder) + { + userFolder = [SOGoUserFolder objectWithName: username + inContainer: MAPIApp]; + [userFolder retain]; + } + + return userFolder; +} + +- (NSDictionary *) rootFolders +{ + SOGoMailAccounts *accountsFolder; + id currentFolder; + + if (!rootFolders) + { + rootFolders = [NSMutableDictionary new]; + [self userFolder]; + [woContext setClientObject: userFolder]; + + /* Calendar */ + currentFolder = [userFolder lookupName: @"Calendar" + inContext: woContext + acquire: NO]; + [rootFolders setObject: currentFolder + forKey: @"calendar"]; + [rootFolders setObject: currentFolder + forKey: @"tasks"]; + + /* Contacts */ + currentFolder = [userFolder lookupName: @"Contacts" + inContext: woContext + acquire: NO]; + [rootFolders setObject: currentFolder + forKey: @"contacts"]; + + /* Mail */ + accountsFolder = [userFolder lookupName: @"Mail" + inContext: woContext + acquire: NO]; + [containersBag addObject: accountsFolder]; + [woContext setClientObject: accountsFolder]; + currentFolder = [accountsFolder lookupName: @"0" + inContext: woContext + acquire: NO]; + [rootFolders setObject: currentFolder + forKey: @"mail"]; + [[currentFolder imap4Connection] + enableExtension: @"QRESYNC"]; + } + + return rootFolders; +} + +- (MAPIStoreMapping *) mapping +{ + return mapping; +} + +- (WOContext *) woContext +{ + return woContext; +} + +- (MAPIStoreAuthenticator *) authenticator +{ + return authenticator; +} + +- (void) activateWithUser: (SOGoUser *) activeUser; +{ + NSMutableDictionary *info; + + [MAPIApp setUserContext: self]; + [woContext setActiveUser: activeUser]; + info = [[NSThread currentThread] threadDictionary]; + [info setObject: woContext forKey: @"WOContext"]; +} + +@end diff --git a/OpenChange/MAPIStoreViewsContext.h b/OpenChange/MAPIStoreViewsContext.h deleted file mode 100644 index 7f6d31783..000000000 --- a/OpenChange/MAPIStoreViewsContext.h +++ /dev/null @@ -1,32 +0,0 @@ -/* MAPIStoreViewsContext.h - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#ifndef MAPISTOREVIEWSCONTEXT_H -#define MAPISTOREVIEWSCONTEXT_H - -#import "MAPIStoreFSBaseContext.h" - -@interface MAPIStoreViewsContext : MAPIStoreFSBaseContext - -@end - -#endif /* MAPISTOREVIEWSCONTEXT_H */ diff --git a/OpenChange/MAPIStoreViewsContext.m b/OpenChange/MAPIStoreViewsContext.m deleted file mode 100644 index a5a9a9940..000000000 --- a/OpenChange/MAPIStoreViewsContext.m +++ /dev/null @@ -1,36 +0,0 @@ -/* MAPIStoreViewsContext.m - this file is part of SOGo - * - * Copyright (C) 2010 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. - */ - -#import - -#import "MAPIStoreMapping.h" - -#import "MAPIStoreViewsContext.h" - -@implementation MAPIStoreViewsContext - -+ (NSString *) MAPIModuleName -{ - return @"views"; -} - -@end diff --git a/OpenChange/NSObject+MAPIStore.m b/OpenChange/NSObject+MAPIStore.m index 0774a5c14..198e1969a 100644 --- a/OpenChange/NSObject+MAPIStore.m +++ b/OpenChange/NSObject+MAPIStore.m @@ -106,6 +106,7 @@ static int MAPIStoreTallocWrapperDestroy (void *data) *data = [(NSCalendarDate * ) self asFileTimeInMemCtx: memCtx]; break; case PT_BINARY: + case PT_SVREID: *data = [(NSData *) self asBinaryInMemCtx: memCtx]; break; case PT_CLSID: diff --git a/OpenChange/SOGoMAPIFSFolder.m b/OpenChange/SOGoMAPIFSFolder.m index b2cf7cbe5..429e23f93 100644 --- a/OpenChange/SOGoMAPIFSFolder.m +++ b/OpenChange/SOGoMAPIFSFolder.m @@ -96,7 +96,7 @@ static NSString *privateDir = nil; - (id) initWithURL: (NSURL *) url andTableType: (uint8_t) tableType { - NSString *path, *tableParticle; + NSString *path, *username, *tableParticle; if ((self = [self init])) { @@ -116,9 +116,11 @@ static NSString *privateDir = nil; path = [url path]; if (![path hasSuffix: @"/"]) path = [NSString stringWithFormat: @"%@/", path]; + username = [url user]; directory = [NSString stringWithFormat: @"%@/mapistore/SOGo/%@/%@/%@%@", - privateDir, [url user], tableParticle, + privateDir, username, tableParticle, [url host], path]; + [self setOwner: username]; [self logWithFormat: @"directory: %@", directory]; [directory retain]; ASSIGN (nameInContainer, [path stringByDeletingLastPathComponent]); @@ -312,6 +314,23 @@ static NSString *privateDir = nil; return [self _fileAttributeForKey: NSFileModificationDate]; } +- (NSException *) delete +{ + NSFileManager *fm; + NSException *error; + + fm = [NSFileManager defaultManager]; + + if (![fm removeFileAtPath: directory handler: NULL]) + error = [NSException exceptionWithName: @"MAPIStoreIOException" + reason: @"could not delete folder" + userInfo: nil]; + else + error = nil; + + return error; +} + /* acl */ - (NSString *) defaultUserID { diff --git a/OpenChange/SOGoMAPIFSMessage.m b/OpenChange/SOGoMAPIFSMessage.m index 971e6ba99..15fe4afa7 100644 --- a/OpenChange/SOGoMAPIFSMessage.m +++ b/OpenChange/SOGoMAPIFSMessage.m @@ -141,14 +141,18 @@ - (NSException *) delete { NSFileManager *fm; + NSException *error; fm = [NSFileManager defaultManager]; if (![fm removeFileAtPath: [self completeFilename] handler: NULL]) - [NSException raise: @"MAPIStoreIOException" - format: @"could not delete message"]; + error = [NSException exceptionWithName: @"MAPIStoreIOException" + reason: @"could not delete message" + userInfo: nil]; + else + error = nil; - return nil; + return error; } - (id) _fileAttributeForKey: (NSString *) key diff --git a/SOPE/NGCards/CardElement.m b/SOPE/NGCards/CardElement.m index ec4d7404f..7064c81f9 100644 --- a/SOPE/NGCards/CardElement.m +++ b/SOPE/NGCards/CardElement.m @@ -460,8 +460,6 @@ _orderedValuesAreVoid (NSArray *orderedValues) NSMutableArray *orderedValues; NSUInteger count, max; - result = YES; - keys = [values allKeys]; max = [keys count]; for (count = 0; result && count < max; count++) diff --git a/SOPE/NGCards/CardGroup.h b/SOPE/NGCards/CardGroup.h index 4535f041d..117d00c54 100644 --- a/SOPE/NGCards/CardGroup.h +++ b/SOPE/NGCards/CardGroup.h @@ -46,12 +46,15 @@ - (CardElement *) uniqueChildWithTag: (NSString *) aTag; - (void) setUniqueChild: (CardElement *) aChild; +- (NSMutableArray *) children; + - (void) addChild: (CardElement *) aChild; - (void) addChildren: (NSArray *) someChildren; - (void) removeChild: (CardElement *) aChild; - (void) removeChildren: (NSArray *) someChildren; -- (NSMutableArray *) children; +- (void) cleanupEmptyChildren; + - (CardElement *) firstChildWithTag: (NSString *) aTag; - (NSArray *) childrenWithTag: (NSString *) aTag; - (NSArray *) childrenWithAttribute: (NSString *) anAttribute diff --git a/SOPE/NGCards/CardGroup.m b/SOPE/NGCards/CardGroup.m index c6ef8bb62..d19b1a321 100644 --- a/SOPE/NGCards/CardGroup.m +++ b/SOPE/NGCards/CardGroup.m @@ -142,6 +142,19 @@ static NGCardsSaxHandler *sax = nil; return nil; } +- (BOOL) isVoid +{ + BOOL isVoid = YES; + NSUInteger count, max; + + max = [children count]; + for (count = 0; isVoid && count < max; count++) + if (![[children objectAtIndex: count] isVoid]) + isVoid = NO; + + return isVoid; +} + - (void) addChild: (CardElement *) aChild { Class mappedClass; @@ -366,6 +379,23 @@ static NGCardsSaxHandler *sax = nil; [self addChild: newChild]; } +- (void) cleanupEmptyChildren +{ + NSUInteger max; + NSInteger count; + CardElement *child; + + max = [children count]; + for (count = max - 1; count > -1; count--) + { + child = [children objectAtIndex: count]; + if ([child isKindOfClass: [CardGroup class]]) + [(CardGroup *) child cleanupEmptyChildren]; + if ([child isVoid]) + [children removeObjectAtIndex: count]; + } +} + - (NSString *) description { NSMutableString *str; diff --git a/SOPE/NGCards/ChangeLog b/SOPE/NGCards/ChangeLog index d9f7cba2e..2cba81e4c 100644 --- a/SOPE/NGCards/ChangeLog +++ b/SOPE/NGCards/ChangeLog @@ -15,6 +15,14 @@ * NSString+NGCards.m (-vCardSubvalues): fixed allocation of parsing buffer to avoid a buffer overflow. +2011-12-30 Wolfgang Sourdeau + + * NGVCard.m (-initWithUid:): initialize "CLASS" and "PROFILE". + + * CardGroup.m (-isVoid): overriden method. + (-cleanupEmptyChildren): make use of "isVoid" to detect and remove + empty children. + 2011-11-21 Francis Lachapelle * iCalTimeZone.m (+knownTimeZoneNames): ignore files that don't diff --git a/SOPE/NGCards/NGVCard.m b/SOPE/NGCards/NGVCard.m index 97bcc92a0..db3833c6e 100644 --- a/SOPE/NGCards/NGVCard.m +++ b/SOPE/NGCards/NGVCard.m @@ -50,6 +50,8 @@ [self setTag: @"vcard"]; [self setUid: _uid]; [self setVersion: @"3.0"]; + [self setVClass: @"PUBLIC"]; + [self setProfile: @"VCARD"]; } return self; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 882f5cac9..cedffbb96 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -2617,11 +2617,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir return @"Appointment"; } -- (NSString *) outlookFolderClass -{ - return @"IPF.Appointment"; -} - - (BOOL) isActive { SOGoUserSettings *settings; diff --git a/SoObjects/Appointments/SOGoAppointmentFolders.h b/SoObjects/Appointments/SOGoAppointmentFolders.h index 36dff6dc7..a4b84a6dc 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolders.h +++ b/SoObjects/Appointments/SOGoAppointmentFolders.h @@ -26,6 +26,7 @@ #import @class NSArray; +@class NSMutableArray; @class SOGoWebAppointmentFolder; diff --git a/SoObjects/Contacts/GNUmakefile b/SoObjects/Contacts/GNUmakefile index 3e3ac2ed2..af5ed91f5 100644 --- a/SoObjects/Contacts/GNUmakefile +++ b/SoObjects/Contacts/GNUmakefile @@ -9,17 +9,20 @@ Contacts_PRINCIPAL_CLASS = SOGoContactsProduct Contacts_OBJC_FILES = \ Product.m \ - NGVCard+SOGo.m \ - NGVList+SOGo.m \ + NGVCard+SOGo.m \ + NGVList+SOGo.m \ SOGoFolder+CardDAV.m \ SOGoContactFolders.m \ SOGoContactGCSEntry.m \ SOGoContactGCSList.m \ SOGoContactGCSFolder.m \ SOGoContactLDIFEntry.m \ - SOGoContactSourceFolder.m \ - SOGoUserFolder+Contacts.m \ + SOGoContactSourceFolder.m \ + SOGoUserFolder+Contacts.m \ SOGoContactEntryPhoto.m \ + \ + NSDictionary+LDIF.m \ + NSString+LDIF.m Contacts_RESOURCE_FILES += \ product.plist \ diff --git a/SoObjects/Contacts/NGVCard+SOGo.h b/SoObjects/Contacts/NGVCard+SOGo.h index a758d86c8..49dcd55d0 100644 --- a/SoObjects/Contacts/NGVCard+SOGo.h +++ b/SoObjects/Contacts/NGVCard+SOGo.h @@ -25,9 +25,13 @@ #import +@class NSDictionary; +@class NSMutableDictionary; + @interface NGVCard (SOGoExtensions) -- (NSString *) ldifString; +- (void) updateFromLDIFRecord: (NSDictionary *) ldifRecord; +- (NSMutableDictionary *) asLDIFRecord; @end diff --git a/SoObjects/Contacts/NGVCard+SOGo.m b/SoObjects/Contacts/NGVCard+SOGo.m index 811ade06d..da86be3fa 100644 --- a/SoObjects/Contacts/NGVCard+SOGo.m +++ b/SoObjects/Contacts/NGVCard+SOGo.m @@ -21,153 +21,610 @@ */ #import +#import #import #import #import -#import +#import +#import + +#import "NSDictionary+LDIF.h" #import "NGVCard+SOGo.h" +/* +objectclass ( 2.5.6.6 NAME 'person' + DESC 'RFC2256: a person' + SUP top STRUCTURAL + MUST ( sn $ cn ) + MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) ) + +objectclass ( 2.5.6.7 NAME 'organizationalPerson' + DESC 'RFC2256: an organizational person' + SUP person STRUCTURAL + MAY ( title $ x121Address $ registeredAddress $ destinationIndicator $ + preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ + telephoneNumber $ internationaliSDNNumber $ + facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ + postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) ) + +objectclass ( 2.16.840.1.113730.3.2.2 + NAME 'inetOrgPerson' + DESC 'RFC2798: Internet Organizational Person' + SUP organizationalPerson + STRUCTURAL + MAY ( + audio $ businessCategory $ carLicense $ departmentNumber $ + displayName $ employeeNumber $ employeeType $ givenName $ + homePhone $ homePostalAddress $ initials $ jpegPhoto $ + labeledURI $ mail $ manager $ mobile $ o $ pager $ + photo $ roomNumber $ secretary $ uid $ userCertificate $ + x500uniqueIdentifier $ preferredLanguage $ + userSMIMECertificate $ userPKCS12 ) + ) + +objectclass ( 1.3.6.1.4.1.13769.9.1 NAME 'mozillaAbPersonAlpha' + SUP top AUXILIARY + MUST ( cn ) + MAY( c $ + description $ + displayName $ + facsimileTelephoneNumber $ + givenName $ + homePhone $ + l $ + mail $ + mobile $ + mozillaCustom1 $ + mozillaCustom2 $ + mozillaCustom3 $ + mozillaCustom4 $ + mozillaHomeCountryName $ + mozillaHomeLocalityName $ + mozillaHomePostalCode $ + mozillaHomeState $ + mozillaHomeStreet $ + mozillaHomeStreet2 $ + mozillaHomeUrl $ + mozillaNickname $ + mozillaSecondEmail $ + mozillaUseHtmlMail $ + mozillaWorkStreet2 $ + mozillaWorkUrl $ + nsAIMid $ + o $ + ou $ + pager $ + postalCode $ + postOfficeBox $ + sn $ + st $ + street $ + telephoneNumber $ + title ) ) + + additional vcard fields: +"vcardCategories" + +test contact (export from tb): + +dn:: Y249UHLDqW5vbSBOb20sbWFpbD1hZHIxQGVsZS5jb20= +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +objectclass: mozillaAbPersonAlpha +givenName:: UHLDqW5vbQ== +sn: Nom +cn:: UHLDqW5vbSBOb20= +mozillaNickname: Surnom +mail: adr1@ele.com +mozillaSecondEmail: adralt@ele.com +nsAIMid: pseudo aim +modifytimestamp: 1324509379 +telephoneNumber: travail +homePhone: dom +facsimiletelephonenumber: fax +pager: pager +mobile: port +mozillaHomeStreet:: YWRyMSBwcml2w6ll +mozillaHomeStreet2:: YWRyMiBwcml2w6ll +mozillaHomeLocalityName: ville/locallite +mozillaHomeState:: w6l0YXQvcHJvdg== +mozillaHomePostalCode: codepos +mozillaHomeCountryName: pays +street: adr1 pro +mozillaWorkStreet2: adr2 pro +l: ville pro +st: etat pro +postalCode: codepro +c: payspro +title: fonction pro +ou: service pro +o: soc pro +mozillaWorkUrl: webpro +mozillaHomeUrl: web +birthyear: 1946 +birthmonth: 12 +birthday: 04 +mozillaCustom1: d1 +mozillaCustom2: d2 +mozillaCustom3: d3 +mozillaCustom4: d4 +description: notes + +convention: +- our "LDIF records" are inetOrgPerson + mozillaAbPersonAlpha + a few custom + fields (categories) +- all keys are lowercase + + */ + @implementation NGVCard (SOGoExtensions) -- (NSString *) ldifString +/* LDIF -> VCARD */ +- (CardElement *) _elementWithTag: (NSString *) elementTag + ofType: (NSString *) type { - NSMutableString *rc; - NSString *buffer; - NSArray *array; - NSMutableArray *marray; - NSMutableDictionary *entry; + NSArray *elements; CardElement *element; - id tmp; - entry = [NSMutableDictionary dictionary]; + elements = [self childrenWithTag: elementTag + andAttribute: @"type" havingValue: type]; + if ([elements count] > 0) + element = [elements objectAtIndex: 0]; + else + { + element = [CardElement elementWithTag: elementTag]; + [element addType: type]; + [self addChild: element]; + } - [entry setObject: [NSString stringWithFormat: @"cn=%@,mail=%@", - [self fn], [self preferredEMail]] - forKey: @"dn"]; - [entry setObject: [NSArray arrayWithObjects: @"top", @"person", - @"organizationalPerson", @"inetOrgPerson", - @"mozillaAbPersonObsolete", nil] - forKey: @"objectclass"]; + return element; +} + +- (void) _setPhoneValues: (NSDictionary *) ldifRecord +{ + CardElement *phone; + + phone = [self _elementWithTag: @"tel" ofType: @"work"]; + [phone setSingleValue: [ldifRecord objectForKey: @"telephonenumber"] forKey: @""]; + phone = [self _elementWithTag: @"tel" ofType: @"home"]; + [phone setSingleValue: [ldifRecord objectForKey: @"homephone"] forKey: @""]; + phone = [self _elementWithTag: @"tel" ofType: @"cell"]; + [phone setSingleValue: [ldifRecord objectForKey: @"mobile"] forKey: @""]; + phone = [self _elementWithTag: @"tel" ofType: @"fax"]; + [phone setSingleValue: [ldifRecord objectForKey: @"facsimiletelephonenumber"] + forKey: @""]; + phone = [self _elementWithTag: @"tel" ofType: @"pager"]; + [phone setSingleValue: [ldifRecord objectForKey: @"pager"] forKey: @""]; +} + +- (void) _setEmails: (NSDictionary *) ldifRecord +{ + CardElement *mail, *homeMail; + + mail = [self _elementWithTag: @"email" ofType: @"work"]; + [mail setSingleValue: [ldifRecord objectForKey: @"mail"] forKey: @""]; + homeMail = [self _elementWithTag: @"email" ofType: @"home"]; + [homeMail setSingleValue: [ldifRecord objectForKey: @"mozillasecondemail"] forKey: @""]; + [[self uniqueChildWithTag: @"x-mozilla-html"] + setSingleValue: [ldifRecord objectForKey: @"mozillausehtmlmail"] + forKey: @""]; +} + +- (void) updateFromLDIFRecord: (NSDictionary *) ldifRecord +{ + CardElement *element; + NSArray *units; + NSInteger year, yearOfToday, month, day; + NSCalendarDate *now; + NSString *ou; + + [self setNWithFamily: [ldifRecord objectForKey: @"sn"] + given: [ldifRecord objectForKey: @"givenname"] + additional: nil prefixes: nil suffixes: nil]; + [self setNickname: [ldifRecord objectForKey: @"mozillanickname"]]; + [self setFn: [ldifRecord objectForKey: @"displayname"]]; + [self setTitle: [ldifRecord objectForKey: @"title"]]; + + element = [self _elementWithTag: @"adr" ofType: @"home"]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillahomestreet2"] + atIndex: 1 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillahomestreet"] + atIndex: 2 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillahomelocalityname"] + atIndex: 3 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillahomestate"] + atIndex: 4 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillahomepostalcode"] + atIndex: 5 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillahomecountryname"] + atIndex: 6 forKey: @""]; + + element = [self _elementWithTag: @"adr" ofType: @"work"]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillaworkstreet2"] + atIndex: 1 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"street"] + atIndex: 2 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"l"] + atIndex: 3 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"st"] + atIndex: 4 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"postalcode"] + atIndex: 5 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"c"] + atIndex: 6 forKey: @""]; + + ou = [ldifRecord objectForKey: @"ou"]; + if (ou) + units = [NSArray arrayWithObject: ou]; + else + units = nil; + [self setOrg: [ldifRecord objectForKey: @"o"] + units: units]; + + [self _setPhoneValues: ldifRecord]; + [self _setEmails: ldifRecord]; + [[self _elementWithTag: @"url" ofType: @"home"] + setSingleValue: [ldifRecord objectForKey: @"mozillahomeurl"] forKey: @""]; + [[self _elementWithTag: @"url" ofType: @"work"] + setSingleValue: [ldifRecord objectForKey: @"mozillaworkurl"] forKey: @""]; + + [[self uniqueChildWithTag: @"x-aim"] + setSingleValue: [ldifRecord objectForKey: @"nsaimid"] + forKey: @""]; + + now = [NSCalendarDate date]; + year = [[ldifRecord objectForKey: @"birthyear"] intValue]; + if (year < 100) + { + yearOfToday = [now yearOfCommonEra]; + if (year == 0) + year = yearOfToday; + else if (yearOfToday < (year + 2000)) + year += 1900; + else + year += 2000; + } + month = [[ldifRecord objectForKey: @"birthmonth"] intValue]; + day = [[ldifRecord objectForKey: @"birthday"] intValue]; + + if (year && month && day) + [self setBday: [NSString stringWithFormat: @"%.4d-%.2d-%.2d", + year, month, day]]; + else + [self setBday: @""]; + + [self setNote: [ldifRecord objectForKey: @"description"]]; + [self setCategories: [ldifRecord objectForKey: @"vcardcategories"]]; + + [self cleanupEmptyChildren]; +} + +/* VCARD -> LDIF */ +- (NSString *) _simpleValueForType: (NSString *) aType + inArray: (NSArray *) anArray + excluding: (NSString *) aTypeToExclude +{ + NSArray *elements; + NSString *value; + + elements = [anArray cardElementsWithAttribute: @"type" + havingValue: aType]; + value = nil; + + if ([elements count] > 0) + { + CardElement *ce; + int i; + + for (i = 0; i < [elements count]; i++) + { + ce = [elements objectAtIndex: i]; + value = [ce flattenedValuesForKey: @""]; + + if (!aTypeToExclude) + break; + + if (![ce hasAttribute: @"type" havingValue: aTypeToExclude]) + break; + + value = nil; + } + } + + return value; +} + +- (void) _setValue: (NSString *) key + to: (NSString *) aValue + inLDIFRecord: (NSMutableDictionary *) ldifRecord +{ + if (!aValue) + aValue = @""; + + [ldifRecord setObject: aValue forKey: key]; +} + +- (void) _setupEmailFieldsInLDIFRecord: (NSMutableDictionary *) ldifRecord +{ + NSArray *elements; + NSString *workMail, *homeMail, *potential; + unsigned int max; + + elements = [self childrenWithTag: @"email"]; + max = [elements count]; + workMail = [self _simpleValueForType: @"work" + inArray: elements excluding: nil]; + homeMail = [self _simpleValueForType: @"home" + inArray: elements excluding: nil]; + + if (max > 0) + { + potential = [[elements objectAtIndex: 0] flattenedValuesForKey: @""]; + if (!workMail) + { + if (homeMail && homeMail == potential) + { + if (max > 1) + workMail = [[elements objectAtIndex: 1] flattenedValuesForKey: @""]; + } + else + workMail = potential; + } + if (!homeMail && max > 1) + { + if (workMail && workMail == potential) + homeMail = [[elements objectAtIndex: 1] flattenedValuesForKey: @""]; + else + homeMail = potential; + } + } + + [self _setValue: @"mail" to: workMail inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillasecondemail" to: homeMail inLDIFRecord: ldifRecord]; + + [self _setValue: @"mozillausehtmlmail" + to: [[self uniqueChildWithTag: @"x-mozilla-html"] + flattenedValuesForKey: @""] + inLDIFRecord: ldifRecord]; +} + +- (void) _setupOrgFieldsInLDIFRecord: (NSMutableDictionary *) ldifRecord +{ + NSMutableArray *orgServices; + CardElement *org; + NSString *service; + NSUInteger count, max; + + org = [self org]; + [self _setValue: @"o" + to: [org flattenedValueAtIndex: 0 forKey: @""] + inLDIFRecord: ldifRecord]; + max = [[org valuesForKey: @""] count]; + if (max > 1) + { + orgServices = [NSMutableArray arrayWithCapacity: max]; + for (count = 1; count < max; count++) + { + service = [org flattenedValueAtIndex: count forKey: @""]; + if ([service length] > 0) + [orgServices addObject: service]; + } + + [self _setValue: @"ou" + to: [orgServices componentsJoinedByString: @", "] + inLDIFRecord: ldifRecord]; + } +} + +- (NSMutableDictionary *) asLDIFRecord +{ + NSArray *elements, *categories; + CardElement *element; + NSMutableDictionary *ldifRecord; + NSCalendarDate *birthDay; + NSString *dn, *stringValue, *stringValue2; + + ldifRecord = [NSMutableDictionary dictionaryWithCapacity: 32]; + [ldifRecord setObject: [NSArray arrayWithObjects: @"top", @"inetOrgPerson", + @"mozillaAbPersonAlpha", nil] + forKey: @"objectClass"]; element = [self n]; - tmp = [element flattenedValueAtIndex: 1 forKey: @""]; - if ([tmp length] > 0) - [entry setObject: tmp forKey: @"givenName"]; - - tmp = [element flattenedValueAtIndex: 0 forKey: @""]; - if ([tmp length] > 0) - [entry setObject: tmp forKey: @"sn"]; + [self _setValue: @"sn" + to: [element flattenedValueAtIndex: 0 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"givenname" + to: [element flattenedValueAtIndex: 1 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"displayname" to: [self fn] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillanickname" to: [self nickname] + inLDIFRecord: ldifRecord]; - tmp = [self fn]; - if (tmp) - [entry setObject: tmp forKey: @"cn"]; - - tmp = [self preferredEMail]; - if (tmp) - [entry setObject: tmp forKey: @"mail"]; - - [entry setObject: @"0Z" forKey: @"modifytimestamp"]; + elements = [self childrenWithTag: @"tel"]; + // We do this (exclude FAX) in order to avoid setting the WORK number as the FAX + // one if we do see the FAX field BEFORE the WORK number. + [self _setValue: @"telephonenumber" + to: [self _simpleValueForType: @"work" inArray: elements + excluding: @"fax"] + inLDIFRecord: ldifRecord]; + [self _setValue: @"homephone" + to: [self _simpleValueForType: @"home" inArray: elements + excluding: @"fax"] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mobile" + to: [self _simpleValueForType: @"cell" inArray: elements + excluding: nil] + inLDIFRecord: ldifRecord]; + [self _setValue: @"facsimiletelephonenumber" + to: [self _simpleValueForType: @"fax" inArray: elements + excluding: nil] + inLDIFRecord: ldifRecord]; + [self _setValue: @"pager" + to: [self _simpleValueForType: @"pager" inArray: elements + excluding: nil] + inLDIFRecord: ldifRecord]; - buffer = [self nickname]; - if (buffer && [buffer length] > 0) - [entry setObject: buffer forKey: @"mozillaNickname"]; + // If we don't have a "home" and "work" phone number but + // we have a "voice" one defined, we set it to the "work" value + // This can happen when we have : + // VERSION:2.1 + // N:name;surname;;;; + // TEL;VOICE;HOME: + // TEL;VOICE;WORK: + // TEL;PAGER: + // TEL;FAX;WORK: + // TEL;CELL:514 123 1234 + // TEL;VOICE:450 456 6789 + // ADR;HOME:;;;;;; + // ADR;WORK:;;;;;; + // ADR:;;;;;; + if ([[ldifRecord objectForKey: @"telephonenumber"] length] == 0 && + [[ldifRecord objectForKey: @"homephone"] length] == 0 && + [elements count] > 0) + [self _setValue: @"telephonenumber" + to: [self _simpleValueForType: @"voice" inArray: elements + excluding: nil] + inLDIFRecord: ldifRecord]; - marray = [NSMutableArray arrayWithArray: [self childrenWithTag: @"email"]]; - [marray removeObjectsInArray: [self childrenWithTag: @"email" - andAttribute: @"type" - havingValue: @"pref"]]; - if ([marray count]) + [self _setupEmailFieldsInLDIFRecord: ldifRecord]; + + [self _setValue: @"nsaimid" + to: [[self uniqueChildWithTag: @"x-aim"] + flattenedValuesForKey: @""] + inLDIFRecord: ldifRecord]; + + elements = [self childrenWithTag: @"adr" + andAttribute: @"type" havingValue: @"work"]; + if (elements && [elements count] > 0) { - buffer = [[marray objectAtIndex: [marray count]-1] - flattenedValuesForKey: @""]; - - if ([buffer caseInsensitiveCompare: [self preferredEMail]] != NSOrderedSame) - [entry setObject: buffer forKey: @"mozillaSecondEmail"]; + element = [elements objectAtIndex: 0]; + [self _setValue: @"mozillaworkstreet2" + to: [element flattenedValueAtIndex: 1 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"street" + to: [element flattenedValueAtIndex: 2 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"l" + to: [element flattenedValueAtIndex: 3 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"st" + to: [element flattenedValueAtIndex: 4 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"postalcode" + to: [element flattenedValueAtIndex: 5 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"c" + to: [element flattenedValueAtIndex: 6 forKey: @""] + inLDIFRecord: ldifRecord]; } - array = [self childrenWithTag: @"tel" andAttribute: @"type" havingValue: @"home"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"homePhone"]; - array = [self childrenWithTag: @"tel" andAttribute: @"type" havingValue: @"fax"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"fax"]; - array = [self childrenWithTag: @"tel" andAttribute: @"type" havingValue: @"cell"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"mobile"]; - array = [self childrenWithTag: @"tel" andAttribute: @"type" havingValue: @"pager"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"pager"]; - - array = [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"home"]; - if ([array count]) + elements = [self childrenWithTag: @"adr" + andAttribute: @"type" havingValue: @"home"]; + if (elements && [elements count] > 0) { - tmp = [array objectAtIndex: 0]; - [entry setObject: [tmp flattenedValueAtIndex: 1 forKey: @""] - forKey: @"mozillaHomeStreet2"]; - [entry setObject: [tmp flattenedValueAtIndex: 2 forKey: @""] - forKey: @"homeStreet"]; - [entry setObject: [tmp flattenedValueAtIndex: 3 forKey: @""] - forKey: @"mozillaHomeLocalityName"]; - [entry setObject: [tmp flattenedValueAtIndex: 4 forKey: @""] - forKey: @"mozillaHomeState"]; - [entry setObject: [tmp flattenedValueAtIndex: 5 forKey: @""] - forKey: @"mozillaHomePostalCode"]; - [entry setObject: [tmp flattenedValueAtIndex: 6 forKey: @""] - forKey: @"mozillaHomeCountryName"]; + element = [elements objectAtIndex: 0]; + [self _setValue: @"mozillahomestreet2" + to: [element flattenedValueAtIndex: 1 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillahomestreet" + to: [element flattenedValueAtIndex: 2 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillahomelocalityname" + to: [element flattenedValueAtIndex: 3 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillahomestate" + to: [element flattenedValueAtIndex: 4 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillahomepostalcode" + to: [element flattenedValueAtIndex: 5 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillahomecountryname" + to: [element flattenedValueAtIndex: 6 forKey: @""] + inLDIFRecord: ldifRecord]; } - element = [self org]; - tmp = [element flattenedValueAtIndex: 0 forKey: @""]; - if ([tmp length] > 0) - [entry setObject: tmp forKey: @"o"]; - - array = [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"work"]; - if ([array count]) + elements = [self childrenWithTag: @"url"]; + [self _setValue: @"mozillaworkurl" + to: [self _simpleValueForType: @"work" inArray: elements + excluding: nil] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillahomeurl" + to: [self _simpleValueForType: @"home" inArray: elements + excluding: nil] + inLDIFRecord: ldifRecord]; + + // If we don't have a "work" or "home" URL but we still have + // an URL field present, let's add it to the "home" value + if ([[ldifRecord objectForKey: @"mozillaworkurl"] length] == 0 && + [[ldifRecord objectForKey: @"mozillahomeurl"] length] == 0 && + [elements count] > 0) + [self _setValue: @"mozillahomeurl" + to: [[elements objectAtIndex: 0] + flattenedValuesForKey: @""] + inLDIFRecord: ldifRecord]; + // If we do have a "work" URL but no "home" URL but two + // values URLs present, let's add the second one as the home URL + else if ([[ldifRecord objectForKey: @"mozillaworkurl"] length] > 0 && + [[ldifRecord objectForKey: @"mozillahomeurl"] length] == 0 && + [elements count] > 1) { - tmp = [array objectAtIndex: 0]; - [entry setObject: [tmp flattenedValueAtIndex: 1 forKey: @""] - forKey: @"mozillaWorkStreet2"]; - [entry setObject: [tmp flattenedValueAtIndex: 2 forKey: @""] - forKey: @"street"]; - [entry setObject: [tmp flattenedValueAtIndex: 3 forKey: @""] - forKey: @"l"]; - [entry setObject: [tmp flattenedValueAtIndex: 4 forKey: @""] - forKey: @"st"]; - [entry setObject: [tmp flattenedValueAtIndex: 5 forKey: @""] - forKey: @"postalCode"]; - [entry setObject: [tmp flattenedValueAtIndex: 6 forKey: @""] - forKey: @"c"]; + int i; + + for (i = 0; i < [elements count]; i++) + { + if ([[[elements objectAtIndex: i] flattenedValuesForKey: @""] + caseInsensitiveCompare: [ldifRecord objectForKey: @"mozillaworkurl"]] != NSOrderedSame) + { + [self _setValue: @"mozillahomeurl" + to: [[elements objectAtIndex: i] + flattenedValuesForKey: @""] + inLDIFRecord: ldifRecord]; + break; + } + } } + + [self _setValue: @"title" to: [self title] inLDIFRecord: ldifRecord]; + [self _setupOrgFieldsInLDIFRecord: ldifRecord]; - array = [self childrenWithTag: @"tel" andAttribute: @"type" havingValue: @"work"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"telephoneNumber"]; + categories = [self categories]; + if ([categories count] > 0) + [ldifRecord setValue: categories forKey: @"vcardcategories"]; - array = [self childrenWithTag: @"url" andAttribute: @"type" havingValue: @"work"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"workurl"]; + birthDay = [[self bday] asCalendarDate]; + if (birthDay) + { + stringValue = [NSString stringWithFormat: @"%.4d", [birthDay yearOfCommonEra]]; + [self _setValue: @"birthyear" to: stringValue inLDIFRecord: ldifRecord]; + stringValue = [NSString stringWithFormat: @"%.2d", [birthDay monthOfYear]]; + [self _setValue: @"birthmonth" to: stringValue inLDIFRecord: ldifRecord]; + stringValue = [NSString stringWithFormat: @"%.2d", [birthDay dayOfMonth]]; + [self _setValue: @"birthday" to: stringValue inLDIFRecord: ldifRecord]; + } + [self _setValue: @"description" to: [self note] inLDIFRecord: ldifRecord]; - array = [self childrenWithTag: @"url" andAttribute: @"type" havingValue: @"home"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"homeurl"]; + stringValue = [ldifRecord objectForKey: @"displayname"]; + stringValue2 = [ldifRecord objectForKey: @"mail"]; + if ([stringValue length] > 0) + { + if ([stringValue2 length] > 0) + dn = [NSString stringWithFormat: @"cn=%@,mail=%@", + stringValue, stringValue2]; + else + dn = [NSString stringWithFormat: @"cn=%@", stringValue]; + } + else if ([stringValue2 length] > 0) + dn = [NSString stringWithFormat: @"mail=%@", stringValue2]; + else + dn = @""; + [ldifRecord setObject: dn forKey: @"dn"]; - tmp = [self note]; - if (tmp && [tmp length]) - [entry setObject: tmp forKey: @"description"]; - - rc = [NSMutableString stringWithString: [entry userRecordAsLDIFEntry]]; - [rc appendFormat: @"\n"]; - - return rc; + return ldifRecord; } @end /* NGVCard */ diff --git a/SoObjects/Contacts/NGVList+SOGo.m b/SoObjects/Contacts/NGVList+SOGo.m index f0bc5fd46..4e6fd7c78 100644 --- a/SoObjects/Contacts/NGVList+SOGo.m +++ b/SoObjects/Contacts/NGVList+SOGo.m @@ -27,7 +27,7 @@ #import -#import +#import "NSDictionary+LDIF.h" #import "NGVList+SOGo.h" @@ -65,7 +65,7 @@ } [entry setObject: members forKey: @"member"]; - rc = [NSMutableString stringWithString: [entry userRecordAsLDIFEntry]]; + rc = [NSMutableString stringWithString: [entry ldifRecordAsString]]; [rc appendFormat: @"\n"]; return rc; diff --git a/OpenChange/MAPIStoreSpoolerContext.h b/SoObjects/Contacts/NSDictionary+LDIF.h similarity index 70% rename from OpenChange/MAPIStoreSpoolerContext.h rename to SoObjects/Contacts/NSDictionary+LDIF.h index 466f2c063..1fcae50dc 100644 --- a/OpenChange/MAPIStoreSpoolerContext.h +++ b/SoObjects/Contacts/NSDictionary+LDIF.h @@ -1,4 +1,4 @@ -/* MAPIStoreSpoolerContext.h - this file is part of SOGo +/* NSDictionary+LDIF.h - this file is part of SOGo * * Copyright (C) 2011 Inverse inc * @@ -6,7 +6,7 @@ * * 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) + * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This file is distributed in the hope that it will be useful, @@ -20,13 +20,15 @@ * Boston, MA 02111-1307, USA. */ -#ifndef MAPISTORESPOOLERCONTEXT_H -#define MAPISTORESPOOLERCONTEXT_H +#ifndef NSDICTIONARY_LDIF_H +#define NSDICTIONARY_LDIF_H -#import "MAPIStoreFSBaseContext.h" +#import -@interface MAPIStoreSpoolerContext : MAPIStoreFSBaseContext +@interface NSDictionary (SOGoLDIF) + +- (NSString *) ldifRecordAsString; @end -#endif /* MAPISTORESPOOLERCONTEXT_H */ +#endif /* NSDICTIONARY_LDIF_H */ diff --git a/SoObjects/Contacts/NSDictionary+LDIF.m b/SoObjects/Contacts/NSDictionary+LDIF.m new file mode 100644 index 000000000..f475c0fc5 --- /dev/null +++ b/SoObjects/Contacts/NSDictionary+LDIF.m @@ -0,0 +1,111 @@ +/* NSDictionary+LDIF.m - this file is part of SOGo + * + * Copyright (C) 2011 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 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import + +#import "NSString+LDIF.h" + +#import "NSDictionary+LDIF.h" + +@implementation NSDictionary (SOGoLDIF) + +- (void) _appendLDIFKey: (NSString *) key + value: (NSString *) value + toString: (NSMutableString *) ldifString +{ + if ([value isKindOfClass: [NSString class]] && [value length] > 0) + { + if ([value mustEncodeLDIFValue]) + [ldifString appendFormat: @"%@:: %@\n", + key, [value stringByEncodingBase64]]; + else + [ldifString appendFormat: @"%@: %@\n", key, value]; + } +} + +- (void) _appendLDIFKey: (NSString *) key + toString: (NSMutableString *) ldifString +{ + id value; + int count, max; + + value = [self objectForKey: key]; + if ([value isKindOfClass: [NSArray class]]) + { + max = [value count]; + for (count = 0; count < max; count++) + [self _appendLDIFKey: key value: [value objectAtIndex: count] + toString: ldifString]; + } + else + [self _appendLDIFKey: key value: [self objectForKey: key] + toString: ldifString]; +} + +- (void) _appendObjectClassesToString: (NSMutableString *) ldifString +{ + NSEnumerator *classes; + NSString *currentClass; + NSArray *objectClass; + + objectClass = [self objectForKey: @"objectClass"]; + if ([objectClass isKindOfClass: [NSString class]]) + [self _appendLDIFKey: @"objectClass" value: (NSString *) objectClass + toString: ldifString]; + else + { + classes = [objectClass objectEnumerator]; + while ((currentClass = [classes nextObject])) + [self _appendLDIFKey: @"objectClass" value: currentClass + toString: ldifString]; + } +} + +- (NSString *) ldifRecordAsString +{ + NSArray *keys; + NSMutableString *ldifString; + NSUInteger count, max; + NSString *currentKey; + +// {CalendarAccess = YES; MailAccess = YES; c_cn = "Wolfgang Sourdeau"; c_emails = ("wolfgang@test.com"); c_name = "wolfgang@test.com"; c_uid = "wolfgang@test.com"; cn = "wolfgang@test.com"; displayName = "Wolfgang Sourdeau"; dn = "cn=wolfgang@test.com,ou=evariste,o=inverse.ca"; givenName = Wolfgang; mail = "wolfgang@test.com"; objectClass = organizationalPerson; sn = Sourdeau; } + + ldifString = [NSMutableString string]; + [self _appendLDIFKey: @"dn" toString: ldifString]; + [self _appendObjectClassesToString: ldifString]; + + keys = [self allKeys]; + max = [keys count]; + for (count = 0; count < max; count++) + { + currentKey = [keys objectAtIndex: count]; + if (!([currentKey hasPrefix: @"objectClass"] + || [currentKey isEqualToString: @"dn"])) + [self _appendLDIFKey: currentKey toString: ldifString]; + } + + return ldifString; +} + +@end diff --git a/OpenChange/MAPIStoreSpoolerContext.m b/SoObjects/Contacts/NSString+LDIF.h similarity index 75% rename from OpenChange/MAPIStoreSpoolerContext.m rename to SoObjects/Contacts/NSString+LDIF.h index 92d531ca8..5f6c0f1da 100644 --- a/OpenChange/MAPIStoreSpoolerContext.m +++ b/SoObjects/Contacts/NSString+LDIF.h @@ -1,4 +1,4 @@ -/* MAPIStoreSpoolerContext.m - this file is part of SOGo +/* NSString+LDIF.h - this file is part of SOGo * * Copyright (C) 2011 Inverse inc * @@ -6,7 +6,7 @@ * * 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) + * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This file is distributed in the hope that it will be useful, @@ -20,15 +20,15 @@ * Boston, MA 02111-1307, USA. */ +#ifndef NSSTRING_LDIF_H +#define NSSTRING_LDIF_H + #import -#import "MAPIStoreSpoolerContext.h" +@interface NSString (SOGoLDIF) -@implementation MAPIStoreSpoolerContext - -+ (NSString *) MAPIModuleName -{ - return @"spooler-queue"; -} +- (BOOL) mustEncodeLDIFValue; @end + +#endif /* NSSTRING_LDIF_H */ diff --git a/SoObjects/Contacts/NSString+LDIF.m b/SoObjects/Contacts/NSString+LDIF.m new file mode 100644 index 000000000..91593a446 --- /dev/null +++ b/SoObjects/Contacts/NSString+LDIF.m @@ -0,0 +1,67 @@ +/* NSString+LDIF.m - this file is part of SOGo + * + * Copyright (C) 2011 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 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +#import "NSString+LDIF.h" + +@implementation NSString (SOGoLDIF) + +static NSMutableCharacterSet *safeLDIFChars = nil; +static NSMutableCharacterSet *safeLDIFStartChars = nil; + +- (void) _initSafeLDIFChars +{ + safeLDIFChars = [NSMutableCharacterSet new]; + [safeLDIFChars addCharactersInRange: NSMakeRange (0x01, 9)]; + [safeLDIFChars addCharactersInRange: NSMakeRange (0x0b, 2)]; + [safeLDIFChars addCharactersInRange: NSMakeRange (0x0e, 114)]; + + safeLDIFStartChars = [safeLDIFChars mutableCopy]; + [safeLDIFStartChars removeCharactersInString: @" :<"]; +} + +- (BOOL) mustEncodeLDIFValue +{ + int count, max; + BOOL rc; + + if (!safeLDIFChars) + [self _initSafeLDIFChars]; + + rc = NO; + + max = [self length]; + if (max > 0) + { + if ([safeLDIFStartChars characterIsMember: [self characterAtIndex: 0]]) + for (count = 1; !rc && count < max; count++) + rc = ![safeLDIFChars + characterIsMember: [self characterAtIndex: count]]; + else + rc = YES; + } + + return rc; +} + +@end diff --git a/SoObjects/Contacts/SOGoContactEntryPhoto.h b/SoObjects/Contacts/SOGoContactEntryPhoto.h index e35eb2cd7..6c44d0757 100644 --- a/SoObjects/Contacts/SOGoContactEntryPhoto.h +++ b/SoObjects/Contacts/SOGoContactEntryPhoto.h @@ -30,11 +30,6 @@ int photoID; } -+ (id) entryPhotoWithID: (int) photoId - inContainer: (id) container; - -- (void) setPhotoID: (int) newPhotoID; - - (NSString *) davContentType; @end diff --git a/SoObjects/Contacts/SOGoContactEntryPhoto.m b/SoObjects/Contacts/SOGoContactEntryPhoto.m index d90750460..7f571ca98 100644 --- a/SoObjects/Contacts/SOGoContactEntryPhoto.m +++ b/SoObjects/Contacts/SOGoContactEntryPhoto.m @@ -35,36 +35,9 @@ @implementation SOGoContactEntryPhoto -+ (id) entryPhotoWithID: (int) photoID - inContainer: (id) container -{ - id photo; - - photo - = [super objectWithName: [NSString stringWithFormat: @"photo%d", photoID] - inContainer: container]; - [photo setPhotoID: photoID]; - - return photo; -} - -- (void) setPhotoID: (int) newPhotoID -{ - photoID = newPhotoID; -} - - (NGVCardPhoto *) photo { - NGVCardPhoto *photo; - NSArray *photoElements; - - photoElements = [[container vCard] childrenWithTag: @"photo"]; - if ([photoElements count] > photoID) - photo = [photoElements objectAtIndex: photoID]; - else - photo = nil; - - return photo; + return (NGVCardPhoto *) [[container vCard] firstChildWithTag: @"photo"]; } - (id) GETAction: (WOContext *) localContext diff --git a/SoObjects/Contacts/SOGoContactFolders.h b/SoObjects/Contacts/SOGoContactFolders.h index 169046b67..820efb92d 100644 --- a/SoObjects/Contacts/SOGoContactFolders.h +++ b/SoObjects/Contacts/SOGoContactFolders.h @@ -27,6 +27,10 @@ @interface SOGoContactFolders : SOGoParentFolder +- (NSException *) renameLDAPAddressBook: (NSString *) sourceID + withDisplayName: (NSString *) newDisplayName; +- (NSException *) removeLDAPAddressBook: (NSString *) sourceID; + @end #endif /* SOGOCONTACTFOLDERS_H */ diff --git a/SoObjects/Contacts/SOGoContactFolders.m b/SoObjects/Contacts/SOGoContactFolders.m index f0f3574f3..01b1f3abb 100644 --- a/SoObjects/Contacts/SOGoContactFolders.m +++ b/SoObjects/Contacts/SOGoContactFolders.m @@ -33,6 +33,7 @@ #import #import +#import #import #import @@ -45,6 +46,7 @@ #import "SOGoContactGCSFolder.h" #import "SOGoContactSourceFolder.h" + #import "SOGoContactFolders.h" #define XMLNS_INVERSEDAV @"urn:inverse:params:xml:ns:inverse-dav" @@ -60,6 +62,62 @@ return [SOGoContactGCSFolder class]; } +- (void) _fetchLDAPAddressBooks: (id ) source +{ + id abSource; + SOGoContactSourceFolder *folder; + NSArray *abSources; + NSUInteger count, max; + NSString *name; + + abSources = [source addressBookSourcesForUser: owner]; + max = [abSources count]; + for (count = 0; count < max; count++) + { + abSource = [abSources objectAtIndex: count]; + name = [abSource sourceID]; + folder = [SOGoContactSourceFolder folderWithName: name + andDisplayName: [abSource displayName] + inContainer: self]; + [folder setSource: abSource]; + [folder setIsPersonalSource: YES]; + [subFolders setObject: folder forKey: name]; + } +} + +- (NSException *) appendPersonalSources +{ + SOGoUser *currentUser; + NSException *result; + id source; + + currentUser = [context activeUser]; + source = [currentUser authenticationSource]; + if ([source hasUserAddressBooks]) + { + result = nil; + /* We don't handle ACLs for user LDAP addressbooks yet, therefore only + the owner has access to his addressbooks. */ + if (activeUserIsOwner + || [[currentUser login] isEqualToString: owner]) + { + [self _fetchLDAPAddressBooks: source]; + if (![subFolders objectForKey: @"personal"]) + { + result = [source addAddressBookSource: @"personal" + withDisplayName: [self defaultFolderName] + forUser: owner]; + if (!result) + [self _fetchLDAPAddressBooks: source]; + } + } + } + else + result = [super appendPersonalSources]; + + return result; +} + - (NSException *) appendSystemSources { SOGoUserManager *um; @@ -101,6 +159,88 @@ return nil; } + +- (NSException *) newFolderWithName: (NSString *) name + andNameInContainer: (NSString *) newNameInContainer +{ + SOGoUser *currentUser; + NSException *result; + id source; + + currentUser = [context activeUser]; + source = [currentUser authenticationSource]; + if ([source hasUserAddressBooks]) + { + result = nil; + /* We don't handle ACLs for user LDAP addressbooks yet, therefore only + the owner has access to his addressbooks. */ + if (activeUserIsOwner + || [[currentUser login] isEqualToString: owner]) + { + result = [source addAddressBookSource: newNameInContainer + withDisplayName: name + forUser: owner]; + if (!result) + [self _fetchLDAPAddressBooks: source]; + } + } + else + result = [super newFolderWithName: name + andNameInContainer: newNameInContainer]; + + return result; +} + +- (NSException *) renameLDAPAddressBook: (NSString *) sourceID + withDisplayName: (NSString *) newDisplayName +{ + NSException *result; + SOGoUser *currentUser; + id source; + + currentUser = [context activeUser]; + source = [currentUser authenticationSource]; + /* We don't handle ACLs for user LDAP addressbooks yet, therefore only + the owner has access to his addressbooks. */ + if (activeUserIsOwner + || [[currentUser login] isEqualToString: owner]) + result = [source renameAddressBookSource: sourceID + withDisplayName: newDisplayName + forUser: owner]; + else + result = [NSException exceptionWithHTTPStatus: 403 + reason: @"operation denied"]; + + return result; +} + +- (NSException *) removeLDAPAddressBook: (NSString *) sourceID +{ + NSException *result; + SOGoUser *currentUser; + id source; + + if ([sourceID isEqualToString: @"personal"]) + result = [NSException exceptionWithHTTPStatus: 403 + reason: @"folder 'personal' cannot be deleted"]; + else + { + result = nil; + currentUser = [context activeUser]; + source = [currentUser authenticationSource]; + /* We don't handle ACLs for user LDAP addressbooks yet, therefore only + the owner has access to his addressbooks. */ + if (activeUserIsOwner + || [[currentUser login] isEqualToString: owner]) + result = [source removeAddressBookSource: sourceID + forUser: owner]; + else + result = [NSException exceptionWithHTTPStatus: 403 + reason: @"operation denied"]; + } + + return result; +} - (NSString *) defaultFolderName { diff --git a/SoObjects/Contacts/SOGoContactGCSEntry.m b/SoObjects/Contacts/SOGoContactGCSEntry.m index 61cb32708..09c18ebbd 100644 --- a/SoObjects/Contacts/SOGoContactGCSEntry.m +++ b/SoObjects/Contacts/SOGoContactGCSEntry.m @@ -19,11 +19,15 @@ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ + #import +#import #import #import +#import +#import "NGVCard+SOGo.h" #import "SOGoContactEntryPhoto.h" #import "SOGoContactGCSEntry.h" @@ -62,6 +66,21 @@ return card; } +- (void) setLDIFRecord: (NSDictionary *) newLDIFRecord +{ + [[self vCard] updateFromLDIFRecord: newLDIFRecord]; +} + +- (NSDictionary *) ldifRecord +{ + return [[self vCard] asLDIFRecord]; +} + +- (BOOL) hasPhoto +{ + return ([[self vCard] firstChildWithTag: @"photo"] != nil); +} + /* actions */ - (id) lookupName: (NSString *) lookupName @@ -69,16 +88,12 @@ acquire: (BOOL) acquire { id obj; - int photoIndex; - NSArray *photoElements; - if ([lookupName hasPrefix: @"photo"]) + if ([lookupName isEqualToString: @"photo"]) { - photoElements = [[self vCard] childrenWithTag: @"photo"]; - photoIndex = [[lookupName substringFromIndex: 5] intValue]; - if (photoIndex > -1 && photoIndex < [photoElements count]) - obj = [SOGoContactEntryPhoto entryPhotoWithID: photoIndex - inContainer: self]; + if ([self hasPhoto]) + obj = [SOGoContactEntryPhoto objectWithName: lookupName + inContainer: self]; else obj = nil; } @@ -127,13 +142,16 @@ /* specialized actions */ -- (void) save +- (NSException *) save { - NGVCard *vcard; + NSException *result; - vcard = [self vCard]; + if (card) + result = [self saveContentString: [card versitString]]; + else + result = nil; /* TODO: we should probably return an exception instead */ - [self saveContentString: [vcard versitString]]; + return result; } - (NSException *) saveContentString: (NSString *) newContent diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.m b/SoObjects/Contacts/SOGoContactGCSFolder.m index 73114b811..c98e2ecd5 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.m +++ b/SoObjects/Contacts/SOGoContactGCSFolder.m @@ -45,6 +45,7 @@ #import #import #import +#import #import "SOGoContactGCSEntry.h" #import "SOGoContactGCSList.h" @@ -375,11 +376,6 @@ static NSArray *folderListingFields = nil; return @"Contact"; } -- (NSString *) outlookFolderClass -{ - return @"IPF.Contact"; -} - /* TODO: multiget reorg */ - (NSString *) _nodeTagForProperty: (NSString *) property { diff --git a/SoObjects/Contacts/SOGoContactLDIFEntry.h b/SoObjects/Contacts/SOGoContactLDIFEntry.h index d89b13d30..e2625e665 100644 --- a/SoObjects/Contacts/SOGoContactLDIFEntry.h +++ b/SoObjects/Contacts/SOGoContactLDIFEntry.h @@ -31,8 +31,8 @@ @interface SOGoContactLDIFEntry : SOGoObject { + BOOL isNew; NSDictionary *ldifEntry; - NGVCard *vcard; } + (SOGoContactLDIFEntry *) contactEntryWithName: (NSString *) newName @@ -42,6 +42,9 @@ withLDIFEntry: (NSDictionary *) newEntry inContainer: (id) newContainer; +- (BOOL) isNew; +- (void) setIsNew: (BOOL) newIsNew; + - (NSString *) davEntityTag; @end diff --git a/SoObjects/Contacts/SOGoContactLDIFEntry.m b/SoObjects/Contacts/SOGoContactLDIFEntry.m index 17d562709..f4b09e800 100644 --- a/SoObjects/Contacts/SOGoContactLDIFEntry.m +++ b/SoObjects/Contacts/SOGoContactLDIFEntry.m @@ -29,9 +29,13 @@ #import #import +#import +#import +#import "NGVCard+SOGo.h" #import "SOGoContactGCSEntry.h" #import "SOGoContactLDIFEntry.h" +#import "SOGoContactSourceFolder.h" @implementation SOGoContactLDIFEntry @@ -42,8 +46,8 @@ SOGoContactLDIFEntry *entry; entry = [[self alloc] initWithName: newName - withLDIFEntry: newEntry - inContainer: newContainer]; + withLDIFEntry: newEntry + inContainer: newContainer]; [entry autorelease]; return entry; @@ -56,7 +60,7 @@ if ((self = [self initWithName: newName inContainer: newContainer])) { ASSIGN (ldifEntry, newEntry); - vcard = nil; + isNew = NO; } return self; @@ -64,169 +68,34 @@ - (void) dealloc { - [vcard release]; [ldifEntry release]; [super dealloc]; } +- (BOOL) isNew +{ + return isNew; +} + +- (void) setIsNew: (BOOL) newIsNew +{ + isNew = newIsNew; +} + - (NSString *) contentAsString { return [[self vCard] versitString]; } -- (void) _setPhonesOfVCard: (NGVCard *) vCard -{ - NSString *info; - - info = [ldifEntry objectForKey: @"telephonenumber"]; - if (info) - [vCard addTel: info - types: [NSArray arrayWithObjects: @"work", @"voice", @"pref", nil]]; - info = [ldifEntry objectForKey: @"homephone"]; - if (info) - [vCard addTel: info - types: [NSArray arrayWithObjects: @"home", @"voice", nil]]; - info = [ldifEntry objectForKey: @"fax"]; - if (info) - [vCard addTel: info - types: [NSArray arrayWithObjects: @"work", @"fax", nil]]; - info = [ldifEntry objectForKey: @"pager"]; - if (info) - [vCard addTel: info - types: [NSArray arrayWithObjects: @"pager", nil]]; - info = [ldifEntry objectForKey: @"mobile"]; - if (info) - [vCard addTel: info - types: [NSArray arrayWithObjects: @"cell", @"voice", nil]]; - -// telephoneNumber: work phone -// homePhone: home phone -// fax: fax phone -// pager: page phone -// mobile: mobile phone - -} - - (NGVCard *) vCard { - NSString *info, *surname, *streetAddress, *location, *region, *postalCode, *country, *org, *orgunit; - CardElement *element; - unsigned int count; + NGVCard *vcard; - if (!vcard) - { - vcard = [[NGVCard alloc] initWithUid: [self nameInContainer]]; - [vcard setVClass: @"PUBLIC"]; - [vcard setProdID: [NSString - stringWithFormat: @"-//Inverse inc./SOGo %@//EN", - SOGoVersion]]; - [vcard setProfile: @"VCARD"]; - info = [ldifEntry objectForKey: @"c_cn"]; - if (![info length]) - { - info = [ldifEntry objectForKey: @"displayname"]; - if (![info length]) - info = [ldifEntry objectForKey: @"cn"]; - } - [vcard setFn: info]; - surname = [ldifEntry objectForKey: @"sn"]; - if (!surname) - surname = [ldifEntry objectForKey: @"surname"]; - [vcard setNWithFamily: surname - given: [ldifEntry objectForKey: @"givenname"] - additional: nil - prefixes: nil - suffixes: nil]; - info = [ldifEntry objectForKey: @"title"]; - if (info) - [vcard setTitle: info]; - info = [ldifEntry objectForKey: @"mozillanickname"]; - if (info) - [vcard setNickname: info]; - - /* If "c_info" is defined, we set as the NOTE value in order for - Thunderbird (or any other CardDAV client) to display it. */ - info = [ldifEntry objectForKey: @"c_info"]; - if (![info length]) - info = [ldifEntry objectForKey: @"description"]; - if ([info length]) - [vcard setNote: info]; - - info = [ldifEntry objectForKey: @"mail"]; - if (info) - [vcard addEmail: info - types: [NSArray arrayWithObjects: @"internet", @"pref", nil]]; - [self _setPhonesOfVCard: vcard]; - - streetAddress = [ldifEntry objectForKey: @"street"]; - if (!streetAddress) - streetAddress = [ldifEntry objectForKey: @"streetaddress"]; - - location = [ldifEntry objectForKey: @"l"]; - if (!location) - location = [ldifEntry objectForKey: @"locality"]; - - region = [ldifEntry objectForKey: @"st"]; - if (!region) - region = [ldifEntry objectForKey: @"region"]; - - postalCode = [ldifEntry objectForKey: @"postalcode"]; - if (!postalCode) - postalCode = [ldifEntry objectForKey: @"zip"]; - - country = [ldifEntry objectForKey: @"c"]; - if (!country) - country = [ldifEntry objectForKey: @"countryname"]; - - element = [CardElement elementWithTag: @"adr"]; - [element setValue: 0 ofAttribute: @"type" to: @"work"]; - - if (streetAddress) - [element setSingleValue: streetAddress atIndex: 2 forKey: @""]; - if (location) - [element setSingleValue: location atIndex: 3 forKey: @""]; - if (region) - [element setSingleValue: region atIndex: 4 forKey: @""]; - if (postalCode) - [element setSingleValue: postalCode atIndex: 5 forKey: @""]; - if (country) - [element setSingleValue: country atIndex: 6 forKey: @""]; - - if (streetAddress || location || region || postalCode || country) - [vcard addChild: element]; - - // We handle the org/orgunit stuff - element = [CardElement elementWithTag: @"org"]; - org = [ldifEntry objectForKey: @"o"]; - orgunit = [ldifEntry objectForKey: @"ou"]; - if (!orgunit) - orgunit = [ldifEntry objectForKey: @"orgunit"]; - - if (org) - [element setSingleValue: org atIndex: 0 forKey: @""]; - if (orgunit) - [element setSingleValue: orgunit atIndex: 1 forKey: @""]; - - if (org || orgunit) - [vcard addChild: element]; - - info = [ldifEntry objectForKey: @"calFBURL"]; - if (info) - [vcard addChildWithTag: @"FBURL" - types: nil - singleValue: info]; - for (count = 1; count < 5; count++) - { - info = [ldifEntry objectForKey: - [NSString stringWithFormat: @"mozillacustom%d", - count]]; - if (info) - [vcard addChildWithTag: [NSString stringWithFormat: @"CUSTOM%d", - count] - types: nil - singleValue: info]; - } - } + vcard = [NGVCard cardWithUid: [self nameInContainer]]; + [vcard setProdID: [NSString + stringWithFormat: @"-//Inverse inc./SOGo %@//EN", + SOGoVersion]]; + [vcard updateFromLDIFRecord: [self ldifRecord]]; return vcard; } @@ -236,6 +105,21 @@ return NO; } +- (void) setLDIFRecord: (NSDictionary *) newLDIFRecord +{ + ASSIGN (ldifEntry, newLDIFRecord); +} + +- (NSDictionary *) ldifRecord +{ + return ldifEntry; +} + +- (BOOL) hasPhoto +{ + return NO; +} + - (NSString *) davEntityTag { unsigned int hash; @@ -251,13 +135,47 @@ return @"text/x-vcard"; } -- (NSArray *) aclsForUser: (NSString *) uid +- (NSException *) save { - return nil; + return [(SOGoContactSourceFolder *) container saveLDIFEntry: self]; } -- (void) save +- (NSException *) delete { + return [(SOGoContactSourceFolder *) container deleteLDIFEntry: self]; +} + +/* acl */ + +- (NSArray *) aclsForUser: (NSString *) uid +{ + NSMutableArray *acls; + NSArray *containerAcls; + + acls = [NSMutableArray array]; + /* this is unused... */ +// ownAcls = [container aclsForUser: uid +// forObjectAtPath: [self pathArrayToSOGoObject]]; +// [acls addObjectsFromArray: ownAcls]; + containerAcls = [container aclsForUser: uid]; + if ([containerAcls count] > 0) + { + [acls addObjectsFromArray: containerAcls]; + /* The creation of an object is actually a "modification" to an + unexisting object. When the object is new, we give the + "ObjectCreator" the "ObjectModifier" role temporarily while we + disallow the "ObjectModifier" users to modify them, unless they are + ObjectCreators too. */ + if (isNew) + { + if ([containerAcls containsObject: SOGoRole_ObjectCreator]) + [acls addObject: SOGoRole_ObjectEditor]; + else + [acls removeObject: SOGoRole_ObjectEditor]; + } + } + + return acls; } /* DAV */ diff --git a/SoObjects/Contacts/SOGoContactObject.h b/SoObjects/Contacts/SOGoContactObject.h index 74b911540..6b718eefa 100644 --- a/SoObjects/Contacts/SOGoContactObject.h +++ b/SoObjects/Contacts/SOGoContactObject.h @@ -22,25 +22,20 @@ #ifndef __Contacts_SOGoContactObject_H__ #define __Contacts_SOGoContactObject_H__ -/* - SOGoContactObject - - Represents a single contact. This SOPE controller object manages all the - attendee storages (that is, it might store into multiple folders for meeting - appointments!). - - Note: SOGoContactObject do not need to exist yet. They can also be "new" - appointments with an externally generated unique key. -*/ - @class NSDictionary; -@class NSString; @class NGVCard; @protocol SOGoContactObject - (NGVCard *) vCard; -- (void) save; +- (BOOL) hasPhoto; + +/* web editing */ +- (void) setLDIFRecord: (NSDictionary *) newLDIFRecord; +- (NSDictionary *) ldifRecord; + +- (NSException *) save; +- (NSException *) delete; @end diff --git a/SoObjects/Contacts/SOGoContactSourceFolder.h b/SoObjects/Contacts/SOGoContactSourceFolder.h index 5cde076b7..d37b1c631 100644 --- a/SoObjects/Contacts/SOGoContactSourceFolder.h +++ b/SoObjects/Contacts/SOGoContactSourceFolder.h @@ -26,14 +26,16 @@ #import "SOGoContactFolder.h" #import "SOGoFolder+CardDAV.h" -@class NSMutableDictionary; +#import -#import "../SOGo/SOGoSource.h" +@class NSMutableDictionary; +@class SOGoContactLDIFEntry; @interface SOGoContactSourceFolder : SOGoFolder { - id source; + id source; NSMutableDictionary *childRecords; + BOOL isPersonalSource; } + (id) folderWithName: (NSString *) aName @@ -42,7 +44,13 @@ - (id) initWithName: (NSString *) newName andDisplayName: (NSString *) newDisplayName inContainer: (id) newContainer; -- (void) setSource: (id) newSource; +- (void) setSource: (id ) newSource; + +- (NSException *) saveLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry; +- (NSException *) deleteLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry; + +- (void) setIsPersonalSource: (BOOL) isPersonal; +- (BOOL) isPersonalSource; @end diff --git a/SoObjects/Contacts/SOGoContactSourceFolder.m b/SoObjects/Contacts/SOGoContactSourceFolder.m index 16346f935..cc906ce14 100644 --- a/SoObjects/Contacts/SOGoContactSourceFolder.m +++ b/SoObjects/Contacts/SOGoContactSourceFolder.m @@ -38,11 +38,16 @@ #import #import -#import #import #import #import +#import +#import +#import +#import +#import "SOGoContactFolders.h" +#import "SOGoContactGCSFolder.h" #import "SOGoContactLDIFEntry.h" #import "SOGoContactSourceFolder.h" @@ -97,11 +102,26 @@ [super dealloc]; } -- (void) setSource: (id) newSource +- (void) setSource: (id ) newSource { ASSIGN (source, newSource); } +- (id ) source +{ + return source; +} + +- (void) setIsPersonalSource: (BOOL) isPersonal +{ + isPersonalSource = isPersonal; +} + +- (BOOL) isPersonalSource +{ + return isPersonalSource; +} + - (NSString *) groupDavResourceType { return @"vcard-collection"; @@ -127,7 +147,10 @@ acquire: (BOOL) acquire { NSDictionary *ldifEntry; - id obj; + SOGoContactLDIFEntry *obj; + NSString *url; + BOOL isNew = NO; + NSArray *baseClasses; /* first check attributes directly bound to the application */ obj = [super lookupName: objectName inContext: lookupContext acquire: NO]; @@ -140,11 +163,28 @@ ldifEntry = [source lookupContactEntry: objectName]; if (ldifEntry) [childRecords setObject: ldifEntry forKey: objectName]; + else if ([self isValidContentName: objectName]) + { + url = [[[lookupContext request] uri] urlWithoutParameters]; + if ([url hasSuffix: @"AsContact"]) + { + baseClasses = [NSArray arrayWithObjects: @"inetorgperson", + @"mozillaabpersonalpha", nil]; + ldifEntry = [NSMutableDictionary + dictionaryWithObject: baseClasses + forKey: @"objectclass"]; + isNew = YES; + } + } } if (ldifEntry) - obj = [SOGoContactLDIFEntry contactEntryWithName: objectName - withLDIFEntry: ldifEntry - inContainer: self]; + { + obj = [SOGoContactLDIFEntry contactEntryWithName: objectName + withLDIFEntry: ldifEntry + inContainer: self]; + if (isNew) + [obj setIsNew: YES]; + } else obj = [NSException exceptionWithHTTPStatus: 404]; } @@ -157,6 +197,19 @@ return [source allEntryIDs]; } +- (NSException *) saveLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry +{ + return (([ldifEntry isNew]) + ? [source addContactEntry: [ldifEntry ldifRecord] + withID: [ldifEntry nameInContainer]] + : [source updateContactEntry: [ldifEntry ldifRecord]]); +} + +- (NSException *) deleteLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry +{ + return [source removeContactEntryWithID: [ldifEntry nameInContainer]]; +} + - (NSDictionary *) _flattenedRecord: (NSDictionary *) oldRecord { NSMutableDictionary *newRecord; @@ -267,7 +320,8 @@ result = nil; - if ([filter length] > 0 && [criteria isEqualToString: @"name_or_address"]) + if (([filter length] > 0 && [criteria isEqualToString: @"name_or_address"]) + || ![source listRequiresDot]) { records = [source fetchContactsMatching: filter]; [childRecords setObjects: records @@ -308,22 +362,88 @@ - (NSComparisonResult) compare: (id) otherFolder { NSComparisonResult comparison; + BOOL otherIsPersonal; - if ([NSStringFromClass([otherFolder class]) - isEqualToString: @"SOGoContactGCSFolder"]) - comparison = NSOrderedDescending; + otherIsPersonal = ([otherFolder isKindOfClass: [SOGoContactGCSFolder class]] + || ([otherFolder isKindOfClass: isa] && [otherFolder isPersonalSource])); + + if (isPersonalSource) + { + if (otherIsPersonal && ![nameInContainer isEqualToString: @"personal"]) + { + if ([[otherFolder nameInContainer] isEqualToString: @"personal"]) + comparison = NSOrderedDescending; + else + comparison + = [[self displayName] + localizedCaseInsensitiveCompare: [otherFolder displayName]]; + } + else + comparison = NSOrderedAscending; + } else - comparison - = [[self displayName] - localizedCaseInsensitiveCompare: [otherFolder displayName]]; + { + if (otherIsPersonal) + comparison = NSOrderedDescending; + else + comparison + = [[self displayName] + localizedCaseInsensitiveCompare: [otherFolder displayName]]; + } return comparison; } +/* common methods */ + +- (NSException *) delete +{ + NSException *error; + + if (isPersonalSource) + { + error = [(SOGoContactFolders *) container + removeLDAPAddressBook: nameInContainer]; + if (!error && [[context request] handledByDefaultHandler]) + [self sendFolderAdvisoryTemplate: @"Removal"]; + } + else + error = [NSException exceptionWithHTTPStatus: 501 /* not implemented */ + reason: @"delete not available on system sources"]; + + return error; +} + +- (void) renameTo: (NSString *) newName +{ + NSException *error; + + if (isPersonalSource) + { + if (![[source displayName] isEqualToString: newName]) + { + error = [(SOGoContactFolders *) container + renameLDAPAddressBook: nameInContainer + withDisplayName: newName]; + if (!error) + [self setDisplayName: newName]; + } + } + /* If public source then method is ignored, maybe we should return an + NSException instead... */ +} + /* acls */ - (NSString *) ownerInContext: (WOContext *) noContext { - return @"nobody"; + NSString *sourceOwner; + + if (isPersonalSource) + sourceOwner = [[source modifiers] objectAtIndex: 0]; + else + sourceOwner = @"nobody"; + + return sourceOwner; } - (NSArray *) subscriptionRoles @@ -331,10 +451,26 @@ return [NSArray arrayWithObject: SoRole_Authenticated]; } -/* TODO: this might change one day when we support LDAP acls */ - (NSArray *) aclsForUser: (NSString *) uid { - return nil; + NSArray *acls, *modifiers; + static NSArray *modifierRoles = nil; + + if (!modifierRoles) + modifierRoles = [[NSArray alloc] initWithObjects: @"Owner", + @"ObjectViewer", + @"ObjectEditor", @"ObjectCreator", + @"ObjectEraser", nil]; + + modifiers = [source modifiers]; + if ([modifiers containsObject: uid]) + acls = [modifierRoles copy]; + else + acls = [NSArray new]; + + [acls autorelease]; + + return acls; } @end diff --git a/SoObjects/Contacts/SOGoFolder+CardDAV.m b/SoObjects/Contacts/SOGoFolder+CardDAV.m index 2f62f1641..dfb761dfa 100644 --- a/SoObjects/Contacts/SOGoFolder+CardDAV.m +++ b/SoObjects/Contacts/SOGoFolder+CardDAV.m @@ -118,10 +118,10 @@ newString = [theString lowercaseString]; - return ([theString isEqualToString: @"sn"] - || [theString isEqualToString: @"givenname"] - || [theString isEqualToString: @"mail"] - || [theString isEqualToString: @"telephonenumber"]); + return ([newString isEqualToString: @"sn"] + || [newString isEqualToString: @"givenname"] + || [newString isEqualToString: @"mail"] + || [newString isEqualToString: @"telephonenumber"]); } - (NSDictionary *) _parseContactFilter: (id ) filterElement diff --git a/SoObjects/Mailer/SOGoMailFolder.h b/SoObjects/Mailer/SOGoMailFolder.h index bd23afbeb..98bc94c85 100644 --- a/SoObjects/Mailer/SOGoMailFolder.h +++ b/SoObjects/Mailer/SOGoMailFolder.h @@ -82,10 +82,14 @@ - (void) markForExpunge; - (void) expungeLastMarkedFolder; +- (BOOL) exists; + - (BOOL) create; - (NSException *) expunge; +- (NSException *) renameTo: (NSString *) newName; + - (NSCalendarDate *) mostRecentMessageDate; /* flags */ @@ -94,8 +98,6 @@ /* folder type */ -- (NSString *) outlookFolderClass; - - (NSArray *) subfolders; - (BOOL) isSpecialFolder; diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index ab8723508..87cece7d8 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -248,7 +248,7 @@ static NSString *defaultUserID = @"anyone"; if (!filenames) { filenames = [NSMutableArray new]; - if ([[self imap4Connection] doesMailboxExistAtURL: [self imap4URL]]) + if ([self exists]) { uids = [self fetchUIDsMatchingQualifier: nil sortOrdering: @"DATE"]; if (![uids isKindOfClass: [NSException class]]) @@ -267,6 +267,53 @@ static NSString *defaultUserID = @"anyone"; return filenames; } +- (NSException *) renameTo: (NSString *) newName +{ + NSException *error; + SOGoMailFolder *inbox; + NSURL *destURL; + NSString *path; + NGImap4Client *client; + + if ([newName length] > 0) + { + [self imap4URL]; + + [self imap4Connection]; + client = [imap4 client]; + + inbox = [[self mailAccountFolder] inboxFolderInContext: context]; + [client select: [inbox absoluteImap4Name]]; + + path = [[imap4URL path] stringByDeletingLastPathComponent]; + if (![path hasSuffix: @"/"]) + path = [path stringByAppendingString: @"/"]; + destURL = [[NSURL alloc] initWithScheme: [imap4URL scheme] + host: [imap4URL host] + path: [NSString stringWithFormat: @"%@%@", + path, newName]]; + [destURL autorelease]; + error = [imap4 moveMailboxAtURL: imap4URL + toURL: destURL]; + if (!error) + { + // We unsubscribe to the old one, and subscribe back to the new one + if ([[[context activeUser] userDefaults] + mailShowSubscribedFoldersOnly]) + { + [client subscribe: [destURL path]]; + [client unsubscribe: [imap4URL path]]; + } + } + } + else + error = [NSException exceptionWithName: @"SOGoMailException" + reason: @"given name is empty" + userInfo: nil]; + + return error; +} + /* messages */ - (void) prefetchCoreInfosForMessageKeys: (NSArray *) keys { @@ -508,7 +555,7 @@ static NSString *defaultUserID = @"anyone"; NSString *archiveName; EOQualifier *notDeleted; - if ([[self imap4Connection] doesMailboxExistAtURL: [self imap4URL]]) + if ([self exists]) { notDeleted = [EOQualifier qualifierWithQualifierFormat: @"(not (flags = %@))", @"deleted"]; @@ -675,7 +722,7 @@ static NSString *defaultUserID = @"anyone"; { // We check for the existence of the IMAP folder (likely to be the // Sent mailbox) prior to appending messages to it. - if ([[self imap4Connection] doesMailboxExistAtURL: [self imap4URL]] + if ([self exists] || ![[self imap4Connection] createMailbox: [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]] atURL: [[self mailAccountFolder] imap4URL]]) return [[self imap4Connection] postData: _data flags: _flags @@ -788,7 +835,7 @@ static NSString *defaultUserID = @"anyone"; inContainer: self]; } else if (isdigit ([_key characterAtIndex: 0]) - && [[self imap4Connection] doesMailboxExistAtURL: [self imap4URL]]) + && [self exists]) { obj = [SOGoMailObject objectWithName: _key inContainer: self]; if ([_key hasSuffix: @".eml"]) @@ -816,6 +863,11 @@ static NSString *defaultUserID = @"anyone"; return [[self imap4Connection] createMailbox:_name atURL:[self imap4URL]]; } +- (BOOL) exists +{ + return [[self imap4Connection] doesMailboxExistAtURL: [self imap4URL]]; +} + - (BOOL) create { NSException *error; @@ -889,32 +941,6 @@ static NSString *defaultUserID = @"anyone"; return @"Mail"; } -- (NSString *) outlookFolderClass -{ - // TODO: detect Trash/Sent/Drafts folders - SOGoMailAccount *account; - NSString *name; - - if (!folderType) - { - account = [self mailAccountFolder]; - name = [self traversalFromMailAccount]; - - if ([name isEqualToString: [account trashFolderNameInContext: nil]]) - folderType = @"IPF.Trash"; - else if ([name - isEqualToString: [account inboxFolderNameInContext: nil]]) - folderType = @"IPF.Inbox"; - else if ([name - isEqualToString: [account sentFolderNameInContext: nil]]) - folderType = @"IPF.Sent"; - else - folderType = @"IPF.Folder"; - } - - return folderType; -} - /* acls */ - (NSArray *) _imapAclsToSOGoAcls: (NSString *) imapAcls diff --git a/SoObjects/Mailer/SOGoSentFolder.m b/SoObjects/Mailer/SOGoSentFolder.m index 92648a793..9dce47464 100644 --- a/SoObjects/Mailer/SOGoSentFolder.m +++ b/SoObjects/Mailer/SOGoSentFolder.m @@ -25,10 +25,4 @@ @implementation SOGoSentFolder -/* folder type */ - -- (NSString *)outlookFolderClass { - return @"IPF.Sent"; -} - @end /* SOGoSentFolder */ diff --git a/SoObjects/Mailer/SOGoTrashFolder.m b/SoObjects/Mailer/SOGoTrashFolder.m index b034bfb16..e0e224af0 100644 --- a/SoObjects/Mailer/SOGoTrashFolder.m +++ b/SoObjects/Mailer/SOGoTrashFolder.m @@ -25,10 +25,4 @@ @implementation SOGoTrashFolder -/* folder type */ - -- (NSString *)outlookFolderClass { - return @"IPF.Trash"; -} - @end /* SOGoTrashFolder */ diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index 5a3a05a79..c099e3c49 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -28,6 +28,7 @@ SOGo_HEADER_FILES = \ \ SOGoUserManager.h \ LDAPSource.h \ + LDAPSourceSchema.h \ SQLSource.h \ SOGoUserProfile.h \ SOGoDateFormatter.h \ @@ -97,6 +98,7 @@ SOGo_OBJC_FILES = \ SOGoStartupLogger.m \ SOGoUserManager.m \ LDAPSource.m \ + LDAPSourceSchema.m \ SQLSource.m \ SOGoUserProfile.m \ SOGoSQLUserProfile.m \ diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index 82755953a..6842dd220 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -30,10 +30,12 @@ #include "SOGoSource.h" #include "SOGoConstants.h" -@class NSDictionary; -@class NSString; -@class NGLdapConnection; +@class LDAPSourceSchema; @class NGLdapEntry; +@class NSException; +@class NSMutableArray; +@class NSMutableDictionary; +@class NSString; @interface LDAPSource : NSObject { @@ -41,6 +43,8 @@ int queryTimeout; NSString *sourceID; + NSString *displayName; + NSString *bindDN; // The bindDN/password could be either the source's one NSString *password; // or the current user if _bindAsCurrentUser is set to YES NSString *sourceBindDN; // while sourceBindDN/sourceBindPassword always belong to the source @@ -49,21 +53,27 @@ unsigned int port; NSString *encryption; NSString *_filter; + BOOL _bindAsCurrentUser; NSString *_scope; NSString *_userPasswordAlgorithm; NSString *baseDN; + LDAPSourceSchema *schema; NSString *IDField; // the first part of a user DN NSString *CNField; NSString *UIDField; NSArray *mailFields, *searchFields; NSString *IMAPHostField, *IMAPLoginField; NSArray *bindFields; - BOOL _bindAsCurrentUser; + + BOOL listRequiresDot; NSString *domain; NSString *contactInfoAttribute; + NSDictionary *contactMapping; + NSArray *contactObjectClasses; + NSDictionary *modulesConstraints; NSMutableArray *searchAttributes; @@ -77,6 +87,12 @@ NSString *multipleBookingsField; NSString *MSExchangeHostname; + + /* user addressbooks */ + NSString *abOU; + + /* ACL */ + NSArray *modifiers; } - (void) setBindDN: (NSString *) newBindDN @@ -98,6 +114,11 @@ kindField: (NSString *) newKindField andMultipleBookingsField: (NSString *) newMultipleBookingsField; +/* This enable the convertion of a contact entry with inetOrgPerson and mozillaAbPerson + to and from an LDAP record */ +- (void) setContactMapping: (NSDictionary *) newMapping + andObjectClasses: (NSArray *) newObjectClasses; + - (NGLdapEntry *) lookupGroupEntryByUID: (NSString *) theUID; - (NGLdapEntry *) lookupGroupEntryByEmail: (NSString *) theEmail; - (NGLdapEntry *) lookupGroupEntryByAttribute: (NSString *) theAttribute diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index 470aee52f..e8e5b23ae 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -26,6 +26,7 @@ #import #import +#import #import #import @@ -36,100 +37,85 @@ #import #import +#import "LDAPSourceSchema.h" #import "NSArray+Utilities.h" #import "NSString+Utilities.h" #import "SOGoDomainDefaults.h" #import "SOGoSystemDefaults.h" #import "LDAPSource.h" - #import "../../Main/SOGo.h" +static Class NSStringK; + #define SafeLDAPCriteria(x) [[[x stringByReplacingString: @"\\" withString: @"\\\\"] \ stringByReplacingString: @"'" withString: @"\\'"] \ stringByReplacingString: @"%" withString: @"%%"] -static NSArray *commonSearchFields; + +@interface NGLdapAttribute (SOGoLDAP) + +- (id) _asArrayOrString; + +@end + +@implementation NGLdapAttribute (SOGoLDAP) + +- (id) _asArrayOrString +{ + id value; + NSArray *arrayValue; + + arrayValue = [self allStringValues]; + if ([arrayValue count] == 1) + value = [arrayValue objectAtIndex: 0]; + else + value = arrayValue; + + return value; +} + +@end + +@interface NGLdapEntry (SOGoLDAP) + +- (NSMutableDictionary *) _asDictionary; + +@end + +@implementation NGLdapEntry (SOGoLDAP) + +- (NSMutableDictionary *) _asDictionary +{ + NSMutableDictionary *ldapRecord; + NSDictionary *ldapAttributes; + NSArray *keys; + NSString *key; + NSUInteger count, max; + id value; + + ldapAttributes = [self attributes]; + keys = [ldapAttributes allKeys]; + max = [keys count]; + + ldapRecord = [NSMutableDictionary dictionaryWithCapacity: max]; + for (count = 0; count < max; count++) + { + key = [keys objectAtIndex: count]; + value = [[ldapAttributes objectForKey: key] _asArrayOrString]; + if (value) + [ldapRecord setObject: value forKey: [key lowercaseString]]; + } + + return ldapRecord; +} + +@end @implementation LDAPSource + (void) initialize { - if (!commonSearchFields) - { - commonSearchFields = [NSArray arrayWithObjects: - @"title", - @"company", - @"o", - @"displayname", - @"modifytimestamp", - @"mozillahomestate", - @"mozillahomeurl", - @"homeurl", - @"st", - @"region", - @"mozillacustom2", - @"custom2", - @"mozillahomecountryname", - @"description", - @"notes", - @"department", - @"departmentnumber", - @"ou", - @"orgunit", - @"mobile", - @"cellphone", - @"carphone", - @"mozillacustom1", - @"custom1", - @"mozillanickname", - @"xmozillanickname", - @"mozillaworkurl", - @"workurl", - @"fax", - @"facsimiletelephonenumber", - @"telephonenumber", - @"mozillahomestreet", - @"mozillasecondemail", - @"xmozillasecondemail", - @"mozillacustom4", - @"custom4", - @"nsaimid", - @"nscpaimscreenname", - @"street", - @"streetaddress", - @"postofficebox", - @"homephone", - @"cn", - @"commonname", - @"givenname", - @"mozillahomepostalcode", - @"mozillahomelocalityname", - @"mozillaworkstreet2", - @"mozillausehtmlmail", - @"xmozillausehtmlmail", - @"mozillahomestreet2", - @"postalcode", - @"zip", - @"c", - @"countryname", - @"pager", - @"pagerphone", - @"mail", - @"sn", - @"surname", - @"mozillacustom3", - @"custom3", - @"l", - @"locality", - @"birthyear", - @"serialnumber", - @"calfburl", - @"proxyaddresses", - // MS Exchange - @"msExchHomeServerName", - nil]; - [commonSearchFields retain]; - } + NSStringK = [NSString class]; } // @@ -154,6 +140,9 @@ static NSArray *commonSearchFields; { if ((self = [super init])) { + sourceID = nil; + displayName = nil; + bindDN = nil; password = nil; sourceBindDN = nil; @@ -161,15 +150,16 @@ static NSArray *commonSearchFields; hostname = nil; port = 389; encryption = nil; - sourceID = nil; domain = nil; baseDN = nil; + schema = nil; IDField = @"cn"; /* the first part of a user DN */ CNField = @"cn"; UIDField = @"uid"; mailFields = [NSArray arrayWithObject: @"mail"]; [mailFields retain]; + contactMapping = nil; searchFields = [NSArray arrayWithObjects: @"sn", @"displayname", @"telephonenumber", nil]; [searchFields retain]; IMAPHostField = nil; @@ -178,6 +168,7 @@ static NSArray *commonSearchFields; _scope = @"sub"; _filter = nil; _userPasswordAlgorithm = nil; + listRequiresDot = YES; searchAttributes = nil; passwordPolicy = NO; @@ -188,6 +179,7 @@ static NSArray *commonSearchFields; MSExchangeHostname = nil; _dnCache = [[NSMutableDictionary alloc] init]; + modifiers = nil; } return self; @@ -198,6 +190,7 @@ static NSArray *commonSearchFields; // - (void) dealloc { + [schema release]; [bindDN release]; [password release]; [sourceBindDN release]; @@ -208,6 +201,7 @@ static NSArray *commonSearchFields; [IDField release]; [CNField release]; [UIDField release]; + [contactMapping release]; [mailFields release]; [searchFields release]; [IMAPHostField release]; @@ -224,6 +218,7 @@ static NSArray *commonSearchFields; [kindField release]; [multipleBookingsField release]; [MSExchangeHostname release]; + [modifiers release]; [super dealloc]; } @@ -234,11 +229,12 @@ static NSArray *commonSearchFields; inDomain: (NSString *) sourceDomain { SOGoDomainDefaults *dd; - NSNumber *udQueryLimit, *udQueryTimeout; + NSNumber *udQueryLimit, *udQueryTimeout, *dotValue; if ((self = [self init])) { - ASSIGN(sourceID, [udSource objectForKey: @"id"]); + [self setSourceID: [udSource objectForKey: @"id"]]; + [self setDisplayName: [udSource objectForKey: @"displayName"]]; [self setBindDN: [udSource objectForKey: @"bindDN"] password: [udSource objectForKey: @"bindPassword"] @@ -259,6 +255,15 @@ static NSArray *commonSearchFields; kindField: [udSource objectForKey: @"KindFieldName"] andMultipleBookingsField: [udSource objectForKey: @"MultipleBookingsFieldName"]]; + dotValue = [udSource objectForKey: @"listRequiresDot"]; + if (dotValue) + [self setListRequiresDot: [dotValue boolValue]]; + [self setContactMapping: [udSource objectForKey: @"mapping"] + andObjectClasses: [udSource objectForKey: @"objectClasses"]]; + + [self setModifiers: [udSource objectForKey: @"modifiers"]]; + ASSIGN (abOU, [udSource objectForKey: @"abOU"]); + if ([sourceDomain length]) { dd = [SOGoDomainDefaults defaultsForDomain: sourceDomain]; @@ -301,7 +306,7 @@ static NSArray *commonSearchFields; ASSIGN(MSExchangeHostname, [udSource objectForKey: @"MSExchangeHostname"]); } - + return self; } @@ -369,15 +374,15 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField { ASSIGN(baseDN, [newBaseDN lowercaseString]); if (newIDField) - ASSIGN(IDField, newIDField); + ASSIGN(IDField, [newIDField lowercaseString]); if (newCNField) - ASSIGN(CNField, newCNField); + ASSIGN(CNField, [newCNField lowercaseString]); if (newUIDField) - ASSIGN(UIDField, newUIDField); + ASSIGN(UIDField, [newUIDField lowercaseString]); if (newIMAPHostField) - ASSIGN(IMAPHostField, newIMAPHostField); + ASSIGN(IMAPHostField, [newIMAPHostField lowercaseString]); if (newIMAPLoginField) - ASSIGN(IMAPLoginField, newIMAPLoginField); + ASSIGN(IMAPLoginField, [newIMAPLoginField lowercaseString]); if (newMailFields) ASSIGN(mailFields, newMailFields); if (newSearchFields) @@ -406,9 +411,26 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField } } if (newKindField) - ASSIGN(kindField, newKindField); + ASSIGN(kindField, [newKindField lowercaseString]); if (newMultipleBookingsField) - ASSIGN(multipleBookingsField, newMultipleBookingsField); + ASSIGN(multipleBookingsField, [newMultipleBookingsField lowercaseString]); +} + +- (void) setListRequiresDot: (BOOL) aBool +{ + listRequiresDot = aBool; +} + +- (BOOL) listRequiresDot +{ + return listRequiresDot; +} + +- (void) setContactMapping: (NSDictionary *) newMapping + andObjectClasses: (NSArray *) newObjectClasses +{ + ASSIGN (contactMapping, newMapping); + ASSIGN (contactObjectClasses, newObjectClasses); } // @@ -456,13 +478,20 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField [ldapConnection setQuerySizeLimit: queryLimit]; if (queryTimeout > 0) [ldapConnection setQueryTimeLimit: queryTimeout]; + if (!schema) + { + schema = [LDAPSourceSchema new]; + [schema readSchemaFromConnection: ldapConnection]; + } } else ldapConnection = nil; } NS_HANDLER { - NSLog(@"Could not bind to the LDAP server %@ (%d) using the bind DN: %@", hostname, port, bindDN); + [self errorWithFormat: @"Could not bind to the LDAP server %@ (%d)" + @" using the bind DN: %@", + hostname, port, bindDN]; ldapConnection = nil; } NS_ENDHANDLER; @@ -742,6 +771,13 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; } + else if (!listRequiresDot) + { + qs = [NSMutableString stringWithFormat: @"(%@='*')", CNField]; + if ([_filter length]) + [qs appendFormat: @" AND %@", _filter]; + qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; + } else qualifier = nil; @@ -792,44 +828,6 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField return fields; } -- (NSArray *) _searchAttributes -{ - if (!searchAttributes) - { - searchAttributes = [NSMutableArray new]; - [searchAttributes addObject: @"objectClass"]; - if (CNField) - [searchAttributes addObject: CNField]; - if (UIDField) - [searchAttributes addObject: UIDField]; - [searchAttributes addObjectsFromArray: mailFields]; - [searchAttributes addObjectsFromArray: [self _constraintsFields]]; - [searchAttributes addObjectsFromArray: commonSearchFields]; - [searchAttributes addObjectUniquely: IDField]; - - // Add SOGoLDAPContactInfoAttribute from user defaults - if ([contactInfoAttribute length]) - [searchAttributes addObjectUniquely: contactInfoAttribute]; - - // Add IMAP hostname from user defaults - if ([IMAPHostField length]) - [searchAttributes addObjectUniquely: IMAPHostField]; - - // Add IMAP login from user defaults - if ([IMAPLoginField length]) - [searchAttributes addObjectUniquely: IMAPLoginField]; - - // Add the resources handling attributes - if ([kindField length]) - [searchAttributes addObjectUniquely: kindField]; - - if ([multipleBookingsField length]) - [searchAttributes addObjectUniquely: multipleBookingsField]; - } - - return searchAttributes; -} - - (NSArray *) allEntryIDs { NSEnumerator *entries; @@ -868,7 +866,7 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField } - (void) _fillEmailsOfEntry: (NGLdapEntry *) ldapEntry - intoContactEntry: (NSMutableDictionary *) contactEntry + intoLDIFRecord: (NSMutableDictionary *) ldifRecord { NSEnumerator *emailFields; NSString *currentFieldName, *ldapValue; @@ -883,27 +881,27 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField allStringValues]; [emails addObjectsFromArray: allValues]; } - [contactEntry setObject: emails forKey: @"c_emails"]; + [ldifRecord setObject: emails forKey: @"c_emails"]; [emails release]; if (IMAPHostField) { ldapValue = [[ldapEntry attributeWithName: IMAPHostField] stringValueAtIndex: 0]; if ([ldapValue length] > 0) - [contactEntry setObject: ldapValue forKey: @"c_imaphostname"]; + [ldifRecord setObject: ldapValue forKey: @"c_imaphostname"]; } if (IMAPLoginField) { ldapValue = [[ldapEntry attributeWithName: IMAPLoginField] stringValueAtIndex: 0]; if ([ldapValue length] > 0) - [contactEntry setObject: ldapValue forKey: @"c_imaplogin"]; + [ldifRecord setObject: ldapValue forKey: @"c_imaplogin"]; } } - (void) _fillConstraints: (NGLdapEntry *) ldapEntry forModule: (NSString *) module - intoContactEntry: (NSMutableDictionary *) contactEntry + intoLDIFRecord: (NSMutableDictionary *) ldifRecord { NSDictionary *constraints; NSEnumerator *matches; @@ -929,23 +927,96 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField } } - [contactEntry setObject: [NSNumber numberWithBool: result] - forKey: [NSString stringWithFormat: @"%@Access", module]]; + [ldifRecord setObject: [NSNumber numberWithBool: result] + forKey: [NSString stringWithFormat: @"%@Access", module]]; +} + +/* conversion LDAP -> SOGo inetOrgPerson entry */ +- (void) _applyContactMappingToResult: (NSMutableDictionary *) ldifRecord +{ + NSArray *sourceFields; + NSArray *keys; + NSString *key, *field, *value; + NSUInteger count, max, fieldCount, fieldMax; + BOOL filled; + + keys = [contactMapping allKeys]; + max = [keys count]; + for (count = 0; count < max; count++) + { + key = [keys objectAtIndex: count]; + sourceFields = [contactMapping objectForKey: key]; + if ([sourceFields isKindOfClass: NSStringK]) + sourceFields = [NSArray arrayWithObject: sourceFields]; + fieldMax = [sourceFields count]; + filled = NO; + for (fieldCount = 0; + !filled && fieldCount < fieldMax; + fieldCount++) + { + field = [[sourceFields objectAtIndex: fieldCount] lowercaseString]; + value = [ldifRecord objectForKey: field]; + if (value) + { + [ldifRecord setObject: value forKey: [key lowercaseString]]; + filled = YES; + } + } + } +} + +/* conversion SOGo inetOrgPerson entry -> LDAP */ +- (void) _applyContactMappingToOutput: (NSMutableDictionary *) ldifRecord +{ + NSArray *sourceFields; + NSArray *keys; + NSString *key, *lowerKey, *field, *value; + NSUInteger count, max, fieldCount, fieldMax; + + if (contactObjectClasses) + [ldifRecord setObject: contactObjectClasses + forKey: @"objectclass"]; + + keys = [contactMapping allKeys]; + max = [keys count]; + for (count = 0; count < max; count++) + { + key = [keys objectAtIndex: count]; + lowerKey = [key lowercaseString]; + value = [ldifRecord objectForKey: lowerKey]; + if ([value length] > 0) + { + sourceFields = [contactMapping objectForKey: key]; + if ([sourceFields isKindOfClass: NSStringK]) + sourceFields = [NSArray arrayWithObject: sourceFields]; + + fieldMax = [sourceFields count]; + for (fieldCount = 0; fieldCount < fieldMax; fieldCount++) + { + field = [[sourceFields objectAtIndex: fieldCount] + lowercaseString]; + [ldifRecord setObject: value forKey: field]; + } + } + } } - (NSDictionary *) _convertLDAPEntryToContact: (NGLdapEntry *) ldapEntry { - NSMutableDictionary *contactEntry; - NSEnumerator *attributes; - NSString *currentAttribute, *value; + NSMutableDictionary *ldifRecord; + NSString *value; + static NSArray *resourceKinds = nil; NSMutableArray *classes; id o; - contactEntry = [NSMutableDictionary dictionary]; - [contactEntry setObject: self forKey: @"source"]; - [contactEntry setObject: [ldapEntry dn] forKey: @"dn"]; - attributes = [[self _searchAttributes] objectEnumerator]; + if (!resourceKinds) + resourceKinds = [[NSArray alloc] initWithObjects: @"location", @"thing", + @"group", nil]; + ldifRecord = [ldapEntry _asDictionary]; + [ldifRecord setObject: self forKey: @"source"]; + [ldifRecord setObject: [ldapEntry dn] forKey: @"dn"]; + // We get our objectClass attribute values. We lowercase // everything for ease of search after. o = [ldapEntry objectClasses]; @@ -964,9 +1035,6 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField if (classes) { - [contactEntry setObject: classes - forKey: @"objectclasses"]; - // We check if our entry is a group. If so, we set the // 'isGroup' custom attribute. if ([classes containsObject: @"group"] || @@ -974,63 +1042,48 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField [classes containsObject: @"groupofuniquenames"] || [classes containsObject: @"posixgroup"]) { - [contactEntry setObject: [NSNumber numberWithInt: 1] - forKey: @"isGroup"]; + [ldifRecord setObject: [NSNumber numberWithInt: 1] + forKey: @"isGroup"]; } // We check if our entry is a resource. We also support // determining resources based on the KindFieldName attribute // value - see below. else if ([classes containsObject: @"calendarresource"]) { - [contactEntry setObject: [NSNumber numberWithInt: 1] - forKey: @"isResource"]; + [ldifRecord setObject: [NSNumber numberWithInt: 1] + forKey: @"isResource"]; } } - while ((currentAttribute = [attributes nextObject])) + // We check if that entry corresponds to a resource. For this, + // kindField must be defined and it must hold one of those values + // + // location + // thing + // group + // + if ([kindField length] > 0) { - value = [[ldapEntry attributeWithName: currentAttribute] - stringValueAtIndex: 0]; + value = [ldifRecord objectForKey: [kindField lowercaseString]]; + if ([value isKindOfClass: NSStringK] + && [resourceKinds containsObject: value]) + [ldifRecord setObject: [NSNumber numberWithInt: 1] + forKey: @"isResource"]; + } - // It's important here to set our attributes' key in lowercase. - if (value) - { - currentAttribute = [currentAttribute lowercaseString]; - [contactEntry setObject: value forKey: currentAttribute]; - - // We check if that entry corresponds to a resource. For this, - // kindField must be defined and it must hold one of those values - // - // location - // thing - // group - // - if (kindField && - [kindField caseInsensitiveCompare: currentAttribute] == NSOrderedSame) - { - if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame || - [value caseInsensitiveCompare: @"thing"] == NSOrderedSame || - [value caseInsensitiveCompare: @"group"] == NSOrderedSame) - { - [contactEntry setObject: [NSNumber numberWithInt: 1] - forKey: @"isResource"]; - } - } - // We check for the number of simultanous bookings that is allowed. - // A value of 0 means that there's no limit. - if (multipleBookingsField && - [multipleBookingsField caseInsensitiveCompare: currentAttribute] == NSOrderedSame) - { - [contactEntry setObject: [NSNumber numberWithInt: [value intValue]] - forKey: @"numberOfSimultaneousBookings"]; - } - } + // We check for the number of simultanous bookings that is allowed. + // A value of 0 means that there's no limit. + if ([multipleBookingsField length] > 0) + { + value = [ldifRecord objectForKey: [multipleBookingsField lowercaseString]]; + [ldifRecord setObject: [NSNumber numberWithInt: [value intValue]] + forKey: @"numberOfSimultaneousBookings"]; } value = [[ldapEntry attributeWithName: IDField] stringValueAtIndex: 0]; if (!value) value = @""; - [contactEntry setObject: value forKey: @"c_name"]; + [ldifRecord setObject: value forKey: @"c_name"]; value = [[ldapEntry attributeWithName: UIDField] stringValueAtIndex: 0]; if (!value) value = @""; @@ -1039,11 +1092,14 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField // Eventually, we could check at this point if the entry is a group // and prefix the UID with a "@" // } - [contactEntry setObject: value forKey: @"c_uid"]; + [ldifRecord setObject: value forKey: @"c_uid"]; value = [[ldapEntry attributeWithName: CNField] stringValueAtIndex: 0]; if (!value) value = @""; - [contactEntry setObject: value forKey: @"c_cn"]; + [ldifRecord setObject: value forKey: @"c_cn"]; + /* if "displayName" is not set, we use CNField because it must exist */ + if (![ldifRecord objectForKey: @"displayname"]) + [ldifRecord setObject: value forKey: @"displayname"]; if (contactInfoAttribute) { @@ -1054,21 +1110,24 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField } else value = @""; - [contactEntry setObject: value forKey: @"c_info"]; + [ldifRecord setObject: value forKey: @"c_info"]; if (domain) value = domain; else value = @""; - [contactEntry setObject: value forKey: @"c_domain"]; + [ldifRecord setObject: value forKey: @"c_domain"]; - [self _fillEmailsOfEntry: ldapEntry intoContactEntry: contactEntry]; + [self _fillEmailsOfEntry: ldapEntry intoLDIFRecord: ldifRecord]; [self _fillConstraints: ldapEntry forModule: @"Calendar" - intoContactEntry: (NSMutableDictionary *) contactEntry]; + intoLDIFRecord: (NSMutableDictionary *) ldifRecord]; [self _fillConstraints: ldapEntry forModule: @"Mail" - intoContactEntry: (NSMutableDictionary *) contactEntry]; + intoLDIFRecord: (NSMutableDictionary *) ldifRecord]; - return contactEntry; + if (contactMapping) + [self _applyContactMappingToResult: ldifRecord]; + + return ldifRecord; } - (NSArray *) fetchContactsMatching: (NSString *) match @@ -1082,11 +1141,12 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField contacts = [NSMutableArray array]; - if ([match length] > 0) + if ([match length] > 0 || !listRequiresDot) { ldapConnection = [self _ldapConnection]; qualifier = [self _qualifierForFilter: match]; - attributes = [self _searchAttributes]; + // attributes = [self _searchAttributes]; + attributes = [NSArray arrayWithObject: @"*"]; if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) entries = [ldapConnection baseSearchAtBaseDN: baseDN @@ -1108,83 +1168,71 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField return contacts; } +- (NGLdapEntry *) _lookupLDAPEntry: (EOQualifier *) qualifier +{ + NGLdapConnection *ldapConnection; + NSArray *attributes; + NSEnumerator *entries; + + // attributes = [self _searchAttributes]; + ldapConnection = [self _ldapConnection]; + attributes = [NSArray arrayWithObject: @"*"]; + + if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) + entries = [ldapConnection baseSearchAtBaseDN: baseDN + qualifier: qualifier + attributes: attributes]; + else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) + entries = [ldapConnection flatSearchAtBaseDN: baseDN + qualifier: qualifier + attributes: attributes]; + else + entries = [ldapConnection deepSearchAtBaseDN: baseDN + qualifier: qualifier + attributes: attributes]; + + return [entries nextObject]; +} + - (NSDictionary *) lookupContactEntry: (NSString *) theID { NGLdapEntry *ldapEntry; - NGLdapConnection *ldapConnection; - NSEnumerator *entries; EOQualifier *qualifier; - NSArray *attributes; NSString *s; - NSDictionary *contactEntry; + NSDictionary *ldifRecord; - contactEntry = nil; + ldifRecord = nil; if ([theID length] > 0) { - ldapConnection = [self _ldapConnection]; s = [NSString stringWithFormat: @"(%@='%@')", IDField, SafeLDAPCriteria(theID)]; qualifier = [EOQualifier qualifierWithQualifierFormat: s]; - attributes = [self _searchAttributes]; - - if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) - entries = [ldapConnection baseSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) - entries = [ldapConnection flatSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - else - entries = [ldapConnection deepSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - - ldapEntry = [entries nextObject]; + ldapEntry = [self _lookupLDAPEntry: qualifier]; if (ldapEntry) - contactEntry = [self _convertLDAPEntryToContact: ldapEntry]; + ldifRecord = [self _convertLDAPEntryToContact: ldapEntry]; } - return contactEntry; + return ldifRecord; } - (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) uid { - NGLdapConnection *ldapConnection; NGLdapEntry *ldapEntry; - NSEnumerator *entries; EOQualifier *qualifier; - NSArray *attributes; - NSDictionary *contactEntry; + NSDictionary *ldifRecord; - contactEntry = nil; + ldifRecord = nil; if ([uid length] > 0) { - ldapConnection = [self _ldapConnection]; qualifier = [self _qualifierForUIDFilter: uid]; - attributes = [self _searchAttributes]; - - if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) - entries = [ldapConnection baseSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) - entries = [ldapConnection flatSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - else - entries = [ldapConnection deepSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - - ldapEntry = [entries nextObject]; + ldapEntry = [self _lookupLDAPEntry: qualifier]; if (ldapEntry) - contactEntry = [self _convertLDAPEntryToContact: ldapEntry]; + ldifRecord = [self _convertLDAPEntryToContact: ldapEntry]; } - return contactEntry; + return ldifRecord; } - (NSString *) lookupLoginByDN: (NSString *) theDN @@ -1226,43 +1274,24 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField - (NGLdapEntry *) lookupGroupEntryByAttribute: (NSString *) theAttribute andValue: (NSString *) theValue { - NSMutableArray *attributes; - NSEnumerator *entries; EOQualifier *qualifier; NSString *s; - NGLdapConnection *ldapConnection; NGLdapEntry *ldapEntry; if ([theValue length] > 0) { - ldapConnection = [self _ldapConnection]; - s = [NSString stringWithFormat: @"(%@='%@')", theAttribute, SafeLDAPCriteria(theValue)]; qualifier = [EOQualifier qualifierWithQualifierFormat: s]; // We look for additional attributes - the ones related to group // membership - attributes = [NSMutableArray arrayWithArray: [self _searchAttributes]]; - [attributes addObject: @"member"]; - [attributes addObject: @"uniqueMember"]; - [attributes addObject: @"memberUid"]; - [attributes addObject: @"memberOf"]; - - if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) - entries = [ldapConnection baseSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) - entries = [ldapConnection flatSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - else - entries = [ldapConnection deepSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - - ldapEntry = [entries nextObject]; + // attributes = [NSMutableArray arrayWithArray: [self _searchAttributes]]; + // [attributes addObject: @"member"]; + // [attributes addObject: @"uniqueMember"]; + // [attributes addObject: @"memberUid"]; + // [attributes addObject: @"memberOf"]; + ldapEntry = [self _lookupLDAPEntry: qualifier]; } else ldapEntry = nil; @@ -1270,11 +1299,26 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField return ldapEntry; } +- (void) setSourceID: (NSString *) newSourceID +{ + ASSIGN (sourceID, newSourceID); +} + - (NSString *) sourceID { return sourceID; } +- (void) setDisplayName: (NSString *) newDisplayName +{ + ASSIGN (displayName, newDisplayName); +} + +- (NSString *) displayName +{ + return displayName; +} + - (NSString *) baseDN { return baseDN; @@ -1285,4 +1329,496 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField return MSExchangeHostname; } +- (void) setModifiers: (NSArray *) newModifiers +{ + ASSIGN (modifiers, newModifiers); +} + +- (NSArray *) modifiers +{ + return modifiers; +} + +static NSArray * +_convertRecordToLDAPAttributes (LDAPSourceSchema *schema, NSDictionary *ldifRecord) +{ + /* convert resulting record to NGLdapEntry: + - strip non-existing object classes + - ignore fields with empty values + - ignore extra fields + - use correct case for LDAP attribute matching classes */ + NSMutableArray *validClasses, *validFields, *attributes; + NGLdapAttribute *attribute; + NSArray *classes, *fields, *values; + NSString *objectClass, *field, *lowerField, *value; + NSUInteger count, max, valueCount, valueMax; + + classes = [ldifRecord objectForKey: @"objectclass"]; + if ([classes isKindOfClass: NSStringK]) + classes = [NSArray arrayWithObject: classes]; + max = [classes count]; + validClasses = [NSMutableArray array]; + validFields = [NSMutableArray array]; + for (count = 0; count < max; count++) + { + objectClass = [classes objectAtIndex: count]; + fields = [schema fieldsForClass: objectClass]; + if ([fields count] > 0) + { + [validClasses addObject: objectClass]; + [validFields addObjectsFromArray: fields]; + } + } + + attributes = [NSMutableArray new]; + max = [validFields count]; + for (count = 0; count < max; count++) + { + attribute = nil; + field = [validFields objectAtIndex: count]; + lowerField = [field lowercaseString]; + if (![lowerField isEqualToString: @"dn"]) + { + if ([lowerField isEqualToString: @"objectclass"]) + values = validClasses; + else + { + values = [ldifRecord objectForKey: lowerField]; + if ([values isKindOfClass: NSStringK]) + values = [NSArray arrayWithObject: values]; + } + valueMax = [values count]; + for (valueCount = 0; valueCount < valueMax; valueCount++) + { + value = [values objectAtIndex: valueCount]; + if ([value length] > 0) + { + if (!attribute) + { + attribute = [[NGLdapAttribute alloc] + initWithAttributeName: field]; + [attributes addObject: attribute]; + [attribute release]; + } + [attribute addStringValue: value]; + } + } + } + } + + return attributes; +} + +- (NSException *) addContactEntry: (NSDictionary *) roLdifRecord + withID: (NSString *) aId +{ + NSException *result; + NGLdapEntry *newEntry; + NSMutableDictionary *ldifRecord; + NSArray *attributes; + NSString *dn, *cnValue; + NGLdapConnection *ldapConnection; + + if ([aId length] > 0) + { + ldapConnection = [self _ldapConnection]; + ldifRecord = [roLdifRecord mutableCopy]; + [ldifRecord autorelease]; + [ldifRecord setObject: aId forKey: UIDField]; + + /* if CN is not set, we use aId because it must exist */ + if (![ldifRecord objectForKey: CNField]) + { + cnValue = [ldifRecord objectForKey: @"displayname"]; + if ([cnValue length] == 0) + cnValue = aId; + [ldifRecord setObject: aId forKey: @"cn"]; + } + + [self _applyContactMappingToOutput: ldifRecord]; + + /* since the id might have changed due to the mapping above, we + reload the record ID */ + aId = [ldifRecord objectForKey: UIDField]; + dn = [NSString stringWithFormat: @"%@=%@,%@", IDField, + [aId escapedForLDAPDN], baseDN]; + attributes = _convertRecordToLDAPAttributes (schema, ldifRecord); + + newEntry = [[NGLdapEntry alloc] initWithDN: dn + attributes: attributes]; + [newEntry autorelease]; + [attributes release]; + NS_DURING + { + [ldapConnection addEntry: newEntry]; + result = nil; + } + NS_HANDLER + { + result = localException; + } + NS_ENDHANDLER; + } + else + [self errorWithFormat: @"no value for id field '%@'", IDField]; + + return result; +} + +static NSArray * +_makeLDAPChanges (NGLdapConnection *ldapConnection, + NSString *dn, NSArray *attributes) +{ + NSMutableArray *changes, *attributeNames, *origAttributeNames; + NGLdapEntry *origEntry; + NSArray *values; + NGLdapAttribute *attribute, *origAttribute; + NSString *name; + NSDictionary *origAttributes; + NSUInteger count, max, valueCount, valueMax; + BOOL allStrings; + + /* additions and modifications */ + origEntry = [ldapConnection entryAtDN: dn + attributes: [NSArray arrayWithObject: @"*"]]; + origAttributes = [origEntry attributes]; + + max = [attributes count]; + changes = [NSMutableArray arrayWithCapacity: max]; + attributeNames = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) + { + attribute = [attributes objectAtIndex: count]; + name = [attribute attributeName]; + [attributeNames addObject: name]; + origAttribute = [origAttributes objectForKey: name]; + if (origAttribute) + { + if (![origAttribute isEqual: attribute]) + [changes + addObject: [NGLdapModification replaceModification: attribute]]; + } + else + [changes addObject: [NGLdapModification addModification: attribute]]; + } + + /* deletions */ + origAttributeNames = [[origAttributes allKeys] mutableCopy]; + [origAttributeNames autorelease]; + [origAttributeNames removeObjectsInArray: attributeNames]; + max = [origAttributeNames count]; + for (count = 0; count < max; count++) + { + name = [origAttributeNames objectAtIndex: count]; + origAttribute = [origAttributes objectForKey: name]; + /* the attribute must only have string values, otherwise it will anyway + be missing from the new record */ + allStrings = YES; + values = [origAttribute allValues]; + valueMax = [values count]; + for (valueCount = 0; allStrings && valueCount < valueMax; valueCount++) + if (![[values objectAtIndex: valueCount] isKindOfClass: NSStringK]) + allStrings = NO; + if (allStrings) + [changes + addObject: [NGLdapModification deleteModification: origAttribute]]; + } + + return changes; +} + +- (NSException *) updateContactEntry: (NSDictionary *) roLdifRecord +{ + NSException *result; + NSString *dn; + NSMutableDictionary *ldifRecord; + NSArray *attributes, *changes; + NGLdapConnection *ldapConnection; + + dn = [roLdifRecord objectForKey: @"dn"]; + if ([dn length] > 0) + { + ldapConnection = [self _ldapConnection]; + ldifRecord = [roLdifRecord mutableCopy]; + [ldifRecord autorelease]; + [self _applyContactMappingToOutput: ldifRecord]; + attributes = _convertRecordToLDAPAttributes (schema, ldifRecord); + + changes = _makeLDAPChanges (ldapConnection, dn, attributes); + + NS_DURING + { + [ldapConnection modifyEntryWithDN: dn + changes: changes]; + result = nil; + } + NS_HANDLER + { + result = localException; + } + NS_ENDHANDLER; + } + else + [self errorWithFormat: @"expected dn for modified record"]; + + return result; +} + +- (NSException *) removeContactEntryWithID: (NSString *) aId +{ + NSException *result; + NGLdapConnection *ldapConnection; + NSString *dn; + + ldapConnection = [self _ldapConnection]; + dn = [NSString stringWithFormat: @"%@=%@,%@", IDField, + [aId escapedForLDAPDN], baseDN]; + NS_DURING + { + [ldapConnection removeEntryWithDN: dn]; + result = nil; + } + NS_HANDLER + { + result = localException; + } + NS_ENDHANDLER; + + return result; +} + +/* user addressbooks */ +- (BOOL) hasUserAddressBooks +{ + return ([abOU length] > 0); +} + +- (NSArray *) addressBookSourcesForUser: (NSString *) user +{ + NSMutableArray *sources; + NSString *abBaseDN; + NGLdapConnection *ldapConnection; + NSArray *attributes, *modifier; + NSEnumerator *entries; + NGLdapEntry *entry; + NSMutableDictionary *entryRecord; + NSDictionary *sourceRec; + LDAPSource *ab; + + if ([self hasUserAddressBooks]) + { + /* list subentries */ + sources = [NSMutableArray array]; + + ldapConnection = [self _ldapConnection]; + abBaseDN = [NSString stringWithFormat: @"ou=%@,%@=%@,%@", + [abOU escapedForLDAPDN], IDField, + [user escapedForLDAPDN], baseDN]; + + /* test ou=addressbooks entry */ + attributes = [NSArray arrayWithObject: @"*"]; + entries = [ldapConnection baseSearchAtBaseDN: abBaseDN + qualifier: nil + attributes: attributes]; + entry = [entries nextObject]; + if (entry) + { + attributes = [NSArray arrayWithObjects: @"ou", @"description", nil]; + entries = [ldapConnection flatSearchAtBaseDN: abBaseDN + qualifier: nil + attributes: attributes]; + modifier = [NSArray arrayWithObject: user]; + while ((entry = [entries nextObject])) + { + sourceRec = [entry _asDictionary]; + ab = [LDAPSource new]; + [ab setSourceID: [sourceRec objectForKey: @"ou"]]; + [ab setDisplayName: [sourceRec objectForKey: @"description"]]; + [ab setBindDN: bindDN + password: password + hostname: hostname + port: [NSString stringWithFormat: @"%d", port] + encryption: encryption + bindAsCurrentUser: NO]; + [ab setBaseDN: [entry dn] + IDField: @"cn" + CNField: @"displayName" + UIDField: @"cn" + mailFields: nil + searchFields: nil + IMAPHostField: nil + IMAPLoginField: nil + bindFields: nil + kindField: nil + andMultipleBookingsField: nil]; + [ab setListRequiresDot: NO]; + [ab setModifiers: modifier]; + [sources addObject: ab]; + [ab release]; + } + } + else + { + entryRecord = [NSMutableDictionary dictionary]; + [entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"]; + [entryRecord setObject: @"addressbooks" forKey: @"ou"]; + attributes = _convertRecordToLDAPAttributes (schema, entryRecord); + entry = [[NGLdapEntry alloc] initWithDN: abBaseDN + attributes: attributes]; + [entry autorelease]; + [attributes release]; + NS_DURING + { + [ldapConnection addEntry: entry]; + } + NS_HANDLER + { + [self errorWithFormat: @"failed to create ou=addressbooks" + @" entry for user"]; + } + NS_ENDHANDLER; + } + } + else + sources = nil; + + return sources; +} + +- (NSException *) addAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user +{ + NSException *result; + NSString *abDN; + NGLdapConnection *ldapConnection; + NSArray *attributes; + NGLdapEntry *entry; + NSMutableDictionary *entryRecord; + + if ([self hasUserAddressBooks]) + { + abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@", + [newId escapedForLDAPDN], [abOU escapedForLDAPDN], + IDField, [user escapedForLDAPDN], baseDN]; + entryRecord = [NSMutableDictionary dictionary]; + [entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"]; + [entryRecord setObject: newId forKey: @"ou"]; + if ([newDisplayName length] > 0) + [entryRecord setObject: newDisplayName forKey: @"description"]; + ldapConnection = [self _ldapConnection]; + attributes = _convertRecordToLDAPAttributes (schema, entryRecord); + entry = [[NGLdapEntry alloc] initWithDN: abDN + attributes: attributes]; + [entry autorelease]; + [attributes release]; + NS_DURING + { + [ldapConnection addEntry: entry]; + result = nil; + } + NS_HANDLER + { + [self errorWithFormat: @"failed to create addressbook entry"]; + result = localException; + } + NS_ENDHANDLER; + } + else + result = [NSException exceptionWithName: @"LDAPSourceIOException" + reason: @"user addressbooks" + @" are not supported" + userInfo: nil]; + + return result; +} + +- (NSException *) renameAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user +{ + NSException *result; + NSString *abDN; + NGLdapConnection *ldapConnection; + NSArray *attributes, *changes; + NSMutableDictionary *entryRecord; + + if ([self hasUserAddressBooks]) + { + abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@", + [newId escapedForLDAPDN], [abOU escapedForLDAPDN], + IDField, [user escapedForLDAPDN], baseDN]; + entryRecord = [NSMutableDictionary dictionary]; + [entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"]; + [entryRecord setObject: newId forKey: @"ou"]; + if ([newDisplayName length] > 0) + [entryRecord setObject: newDisplayName forKey: @"description"]; + ldapConnection = [self _ldapConnection]; + attributes = _convertRecordToLDAPAttributes (schema, entryRecord); + changes = _makeLDAPChanges (ldapConnection, abDN, attributes); + [attributes release]; + NS_DURING + { + [ldapConnection modifyEntryWithDN: abDN + changes: changes]; + result = nil; + } + NS_HANDLER + { + [self errorWithFormat: @"failed to rename addressbook entry"]; + result = localException; + } + NS_ENDHANDLER; + } + else + result = [NSException exceptionWithName: @"LDAPSourceIOException" + reason: @"user addressbooks" + @" are not supported" + userInfo: nil]; + + return result; +} + +- (NSException *) removeAddressBookSource: (NSString *) newId + forUser: (NSString *) user +{ + NSException *result; + NSString *abDN; + NGLdapConnection *ldapConnection; + NSEnumerator *entries; + NGLdapEntry *entry; + + if ([self hasUserAddressBooks]) + { + abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@", + [newId escapedForLDAPDN], [abOU escapedForLDAPDN], + IDField, [user escapedForLDAPDN], baseDN]; + ldapConnection = [self _ldapConnection]; + NS_DURING + { + /* we must remove the ab sub=entries prior to the ab entry */ + entries = [ldapConnection flatSearchAtBaseDN: abDN + qualifier: nil + attributes: nil]; + while ((entry = [entries nextObject])) + [ldapConnection removeEntryWithDN: [entry dn]]; + [ldapConnection removeEntryWithDN: abDN]; + result = nil; + } + NS_HANDLER + { + [self errorWithFormat: @"failed to remove addressbook entry"]; + result = localException; + } + NS_ENDHANDLER; + } + else + result = [NSException exceptionWithName: @"LDAPSourceIOException" + reason: @"user addressbooks" + @" are not supported" + userInfo: nil]; + + return result; +} + @end diff --git a/OpenChange/MAPIStoreFreebusyContext.m b/SoObjects/SOGo/LDAPSourceSchema.h similarity index 54% rename from OpenChange/MAPIStoreFreebusyContext.m rename to SoObjects/SOGo/LDAPSourceSchema.h index ea506e5fc..13d415bae 100644 --- a/OpenChange/MAPIStoreFreebusyContext.m +++ b/SoObjects/SOGo/LDAPSourceSchema.h @@ -1,12 +1,12 @@ -/* MAPIStoreFreebusyContext.m - this file is part of SOGo +/* LDAPSourceSchema.h - this file is part of SOGo * - * Copyright (C) 2010 Inverse inc. + * Copyright (C) 2011 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) + * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This file is distributed in the hope that it will be useful, @@ -20,21 +20,26 @@ * Boston, MA 02111-1307, USA. */ -#import +#ifndef LDAPSOURCESCHEMA_H +#define LDAPSOURCESCHEMA_H -#import +#import -#import "MAPIApplication.h" -#import "MAPIStoreAuthenticator.h" -#import "MAPIStoreMapping.h" +@class NSMutableDictionary; +@class NGLdapConnection; -#import "MAPIStoreFreebusyContext.h" - -@implementation MAPIStoreFreebusyContext - -+ (NSString *) MAPIModuleName +@interface LDAPSourceSchema : NSObject { - return @"freebusy"; + NSMutableDictionary *schema; } +- (void) readSchemaFromConnection: (NGLdapConnection *) conn; + +- (NSArray *) fieldsForClass: (NSString *) className; + +/* merged list of attributes with unique names */ +- (NSArray *) fieldsForClasses: (NSArray *) className; + @end + +#endif /* LDAPSOURCESCHEMA_H */ diff --git a/SoObjects/SOGo/LDAPSourceSchema.m b/SoObjects/SOGo/LDAPSourceSchema.m new file mode 100644 index 000000000..a81417782 --- /dev/null +++ b/SoObjects/SOGo/LDAPSourceSchema.m @@ -0,0 +1,295 @@ +/* LDAPSourceSchema.m - this file is part of SOGo + * + * Copyright (C) 2011 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 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import +#import + +#import + +#import +#import +#import + +#import "LDAPSourceSchema.h" +#import "NSDictionary+Utilities.h" + +static EOQualifier *allOCQualifier = nil; + +@implementation LDAPSourceSchema + ++ (void) initialize +{ + allOCQualifier = [[EOKeyValueQualifier alloc] + initWithKey: @"objectClass" + operatorSelector: EOQualifierOperatorEqual + value: @"*"]; +} + +- (id) init +{ + if ((self = [super init])) + { + schema = nil; + } + + return self; +} + +- (void) dealloc +{ + [schema release]; + [super dealloc]; +} + +static NSArray * +schemaTokens (NSString *schema) +{ + unichar *characters; + NSUInteger count, max, parenLevel = 0, firstChar = (NSUInteger) -1; + NSMutableArray *arrayString, *parentArray, *currentArray = nil; + NSArray *topArray = nil; + NSString *token; + + arrayString = [NSMutableArray array]; + + max = [schema length]; + characters = malloc ((max + 1) * sizeof (unichar)); + characters[max] = 0; + [schema getCharacters: characters]; + + for (count = 0; count < max; count++) + { + switch (characters[count]) + { + case '(': + // NSLog (@"increase"); + parenLevel++; + parentArray = currentArray; + currentArray = [NSMutableArray array]; + if (parentArray == nil) + topArray = currentArray; + [parentArray addObject: currentArray]; + [arrayString addObject: currentArray]; + break; + case ')': + // NSLog (@"decrease"); + parenLevel--; + [arrayString removeLastObject]; + currentArray = [arrayString lastObject]; + break; + case ' ': + if (firstChar != (NSUInteger) -1) + { + token = [NSString stringWithCharacters: characters + firstChar + length: (count - firstChar)]; + if (![token isEqualToString: @"$"]) + [currentArray addObject: token]; + // NSLog (@"added token: %@", token); + firstChar = (NSUInteger) -1; + } + break; + default: + if (currentArray && (firstChar == (NSUInteger) -1)) + firstChar = count; + } + } + + free (characters); + + return topArray; +} + +static inline id +schemaValue (NSArray *tokens, NSString *key) +{ + NSUInteger idx; + id value; + + idx = [tokens indexOfObject: key]; + if (idx != NSNotFound) + value = [tokens objectAtIndex: (idx + 1)]; + else + value = nil; + + return value; +} + +static NSMutableDictionary * +parseSchema (NSString *schema) +{ + NSArray *tokens; + NSMutableDictionary *schemaDict; + NSMutableArray *fields; + id value; + + schemaDict = [NSMutableDictionary dictionaryWithCapacity: 6]; + tokens = schemaTokens (schema); + // [schemaDict setObject: [tokens objectAtIndex: 0] + // forKey: @"oid"]; + value = schemaValue (tokens, @"NAME"); + if (value) + { + /* sometimes, objectClasses can have two names */ + if ([value isKindOfClass: [NSString class]]) + value = [NSArray arrayWithObject: value]; + [schemaDict setObject: value forKey: @"names"]; + } + + value = schemaValue (tokens, @"SUP"); + if (value) + [schemaDict setObject: value forKey: @"sup"]; + + fields = [NSMutableArray new]; + [schemaDict setObject: fields forKey: @"fields"]; + [fields release]; + value = schemaValue (tokens, @"MUST"); + if (value) + { + if ([value isKindOfClass: [NSArray class]]) + [fields addObjectsFromArray: value]; + else + [fields addObject: value]; + } + value = schemaValue (tokens, @"MAY"); + if (value) + { + if ([value isKindOfClass: [NSArray class]]) + [fields addObjectsFromArray: value]; + else + [fields addObject: value]; + } + + return schemaDict; +} + +static void +fillSchemaFromEntry (NSMutableDictionary *schema, NGLdapEntry *entry) +{ + NSEnumerator *strings; + NGLdapAttribute *attr; + NSMutableDictionary *schemaDict; + NSArray *names; + NSString *string, *name; + NSUInteger count, max; + + attr = [entry attributeWithName: @"objectclasses"]; + strings = [attr stringValueEnumerator]; + while ((string = [strings nextObject])) + { + schemaDict = parseSchema (string); + names = [schemaDict objectForKey: @"names"]; + max = [names count]; + for (count = 0; count < max; count++) + { + name = [[names objectAtIndex: count] lowercaseString]; + if ([name hasPrefix: @"'"] && [name hasSuffix: @"'"]) + name + = [name substringWithRange: NSMakeRange (1, [name length] - 2)]; + [schema setObject: schemaDict forKey: name]; + } + /* the list of names is no longer required from the schema itself */ + [schemaDict removeObjectForKey: @"names"]; + } +} + +- (void) readSchemaFromConnection: (NGLdapConnection *) conn +{ + NSEnumerator *entries; + NGLdapEntry *entry; + NSString *dn; + + ASSIGN (schema, [NSMutableDictionary new]); + [schema release]; + + entries = [conn baseSearchAtBaseDN: @"" + qualifier: allOCQualifier + attributes: [NSArray arrayWithObject: @"subschemaSubentry"]]; + entry = [entries nextObject]; + if (entry) + { + dn = [[entry attributeWithName: @"subschemaSubentry"] + stringValueAtIndex: 0]; + if (dn) + { + entries = [conn baseSearchAtBaseDN: dn + qualifier: allOCQualifier + attributes: [NSArray arrayWithObject: @"objectclasses"]]; + entry = [entries nextObject]; + if (entry) + fillSchemaFromEntry (schema, entry); + } + } +} + +static void +fillFieldsForClass (NSMutableDictionary *schema, NSString *schemaName, + NSMutableArray *fields) +{ + NSDictionary *schemaDict; + NSString *sup; + NSArray *schemaFields; + + schemaDict = [schema objectForKey: [schemaName lowercaseString]]; + if (schemaDict) + { + schemaFields = [schemaDict objectForKey: @"fields"]; + if ([schemaFields count] > 0) + [fields addObjectsFromArray: schemaFields]; + sup = [schemaDict objectForKey: @"sup"]; + if ([sup length] > 0) + fillFieldsForClass (schema, sup, fields); + } +} + +- (NSArray *) fieldsForClass: (NSString *) className +{ + NSMutableArray *fields; + + fields = [NSMutableArray arrayWithCapacity: 128]; + fillFieldsForClass (schema, className, fields); + + return fields; +} + +- (NSArray *) fieldsForClasses: (NSArray *) classNames +{ + NSMutableDictionary *fieldHash; + NSNumber *yesValue; + NSString *name; + NSUInteger count, max; + + yesValue = [NSNumber numberWithBool: YES]; + + fieldHash = [NSMutableDictionary dictionary]; + max = [classNames count]; + for (count = 0; count < max; count++) + { + name = [classNames objectAtIndex: count]; + [fieldHash setObject: yesValue forKeys: [self fieldsForClass: name]]; + } + + return [fieldHash allKeys]; +} + +@end diff --git a/SoObjects/SOGo/NSDictionary+Utilities.h b/SoObjects/SOGo/NSDictionary+Utilities.h index c8e08ea7c..92fe50f34 100644 --- a/SoObjects/SOGo/NSDictionary+Utilities.h +++ b/SoObjects/SOGo/NSDictionary+Utilities.h @@ -35,8 +35,7 @@ - (NSString *) jsonRepresentation; - (NSString *) keysWithFormat: (NSString *) keyFormat; -// LDIF methods -- (NSString *) userRecordAsLDIFEntry; +- (NSComparisonResult) caseInsensitiveDisplayNameCompare: (NSDictionary *) theDictionary; @end diff --git a/SoObjects/SOGo/NSDictionary+Utilities.m b/SoObjects/SOGo/NSDictionary+Utilities.m index c38dce316..79e0c6c6d 100644 --- a/SoObjects/SOGo/NSDictionary+Utilities.m +++ b/SoObjects/SOGo/NSDictionary+Utilities.m @@ -26,12 +26,8 @@ #import #import #import -#import - -#import #import "NSArray+Utilities.h" -#import "NSObject+Utilities.h" #import "NSString+Utilities.h" #import "NSDictionary+Utilities.h" @@ -109,84 +105,6 @@ return [[self objectForKey: @"cn"] caseInsensitiveCompare: [theDictionary objectForKey: @"cn"]]; } -// LDIF Methods -#warning We might want to support more than just strings here -- (void) _appendLDIFKey: (NSString *) key - value: (NSString *) value - toString: (NSMutableString *) ldifString -{ - if ([value isKindOfClass: [NSString class]]) - { - if ([value _isLDIFSafe]) - [ldifString appendFormat: @"%@: %@\n", key, value]; - else - [ldifString appendFormat: @"%@:: %@\n", - key, [value stringByEncodingBase64]]; - } -} - -- (void) _appendLDIFKey: (NSString *) key - toString: (NSMutableString *) ldifString -{ - id value; - int count, max; - - value = [self objectForKey: key]; - if ([value isKindOfClass: [NSArray class]]) - { - max = [value count]; - for (count = 0; count < max; count++) - [self _appendLDIFKey: key value: [value objectAtIndex: count] - toString: ldifString]; - } - else - [self _appendLDIFKey: key value: [self objectForKey: key] - toString: ldifString]; -} - -- (void) _appendObjectClassesToString: (NSMutableString *) ldifString -{ - NSEnumerator *classes; - NSString *currentClass; - - classes = [[self objectForKey: @"objectClasses"] objectEnumerator]; - while ((currentClass = [classes nextObject])) - [self _appendLDIFKey: @"objectClass" value: currentClass - toString: ldifString]; -} - -- (NSString *) userRecordAsLDIFEntry -{ - NSMutableString *ldifString; - NSEnumerator *keys; - NSString *currentKey; - -// {CalendarAccess = YES; MailAccess = YES; c_cn = "Wolfgang Sourdeau"; c_emails = ("wolfgang@test.com"); c_name = "wolfgang@test.com"; c_uid = "wolfgang@test.com"; cn = "wolfgang@test.com"; displayName = "Wolfgang Sourdeau"; dn = "cn=wolfgang@test.com,ou=evariste,o=inverse.ca"; givenName = Wolfgang; mail = "wolfgang@test.com"; objectClass = organizationalPerson; sn = Sourdeau; } - - ldifString = [NSMutableString string]; - [self _appendLDIFKey: @"dn" toString: ldifString]; - [self _appendObjectClassesToString: ldifString]; - - keys = [[self allKeys] objectEnumerator]; - while ((currentKey = [keys nextObject])) - { - if (!([currentKey isEqualToString: @"CalendarAccess"] - || [currentKey isEqualToString: @"MailAccess"] - || [currentKey isEqualToString: @"ContactAccess"] - || [currentKey hasPrefix: @"objectClass"] - || [currentKey hasPrefix: @"c_"] - || [currentKey isEqualToString: @"dn"] - || [currentKey isEqualToString: @"isGroup"] - || [currentKey isEqualToString: @"isResource"] - || [currentKey isEqualToString: @"numberOfSimultaneousBookings"] - || [currentKey isEqualToString: @"canAuthenticate"])) - [self _appendLDIFKey: currentKey toString: ldifString]; - } - - return ldifString; -} - - @end @implementation NSMutableDictionary (SOGoDictionaryUtilities) diff --git a/SoObjects/SOGo/NSString+Utilities.h b/SoObjects/SOGo/NSString+Utilities.h index 78af47b7b..53bf190b9 100644 --- a/SoObjects/SOGo/NSString+Utilities.h +++ b/SoObjects/SOGo/NSString+Utilities.h @@ -62,8 +62,6 @@ - (int) timeValue; -- (BOOL) _isLDIFSafe; - - (BOOL) isJSONString; - (id) objectFromJSONString; diff --git a/SoObjects/SOGo/NSString+Utilities.m b/SoObjects/SOGo/NSString+Utilities.m index 77607775e..0d01f9970 100644 --- a/SoObjects/SOGo/NSString+Utilities.m +++ b/SoObjects/SOGo/NSString+Utilities.m @@ -486,44 +486,6 @@ static int cssEscapingCount; return time; } -static NSMutableCharacterSet *safeLDIFChars = nil; -static NSMutableCharacterSet *safeLDIFStartChars = nil; - -- (void) _initSafeLDIFChars -{ - safeLDIFChars = [NSMutableCharacterSet new]; - [safeLDIFChars addCharactersInRange: NSMakeRange (0x01, 9)]; - [safeLDIFChars addCharactersInRange: NSMakeRange (0x0b, 2)]; - [safeLDIFChars addCharactersInRange: NSMakeRange (0x0e, 114)]; - - safeLDIFStartChars = [safeLDIFChars mutableCopy]; - [safeLDIFStartChars removeCharactersInString: @" :<"]; -} - -- (BOOL) _isLDIFSafe -{ - int count, max; - BOOL rc; - - if (!safeLDIFChars) - [self _initSafeLDIFChars]; - - rc = YES; - - max = [self length]; - if (max > 0) - { - if ([safeLDIFStartChars characterIsMember: [self characterAtIndex: 0]]) - for (count = 1; rc && count < max; count++) - rc = [safeLDIFChars - characterIsMember: [self characterAtIndex: count]]; - else - rc = NO; - } - - return rc; -} - - (BOOL) isJSONString { NSDictionary *jsonData; diff --git a/SoObjects/SOGo/SOGoFolder.h b/SoObjects/SOGo/SOGoFolder.h index cc97cf153..1fa1c6ef8 100644 --- a/SoObjects/SOGo/SOGoFolder.h +++ b/SoObjects/SOGo/SOGoFolder.h @@ -25,6 +25,8 @@ #import "SOGoObject.h" +@class NSString; + @interface SOGoFolder : SOGoObject { NSMutableString *displayName; @@ -52,8 +54,8 @@ /* dav */ - (NSArray *) davResourceType; -/* outlook */ -- (NSString *) outlookFolderClass; +/* email advisories */ +- (void) sendFolderAdvisoryTemplate: (NSString *) template; @end diff --git a/SoObjects/SOGo/SOGoFolder.m b/SoObjects/SOGo/SOGoFolder.m index 5587f18b8..a27466cdd 100644 --- a/SoObjects/SOGo/SOGoFolder.m +++ b/SoObjects/SOGo/SOGoFolder.m @@ -27,6 +27,7 @@ #import #import +#import #import #import #import @@ -37,12 +38,16 @@ #import +#import + #import "DOMNode+SOGo.h" #import "NSArray+Utilities.h" #import "NSObject+DAV.h" #import "NSString+DAV.h" #import "NSString+Utilities.h" #import "SOGoPermissions.h" +#import "SOGoUser.h" +#import "SOGoDomainDefaults.h" #import "SOGoWebDAVAclManager.h" #import "WORequest+SOGo.h" #import "WOResponse+SOGo.h" @@ -248,6 +253,30 @@ return comparison; } +/* email advisories */ + +- (void) sendFolderAdvisoryTemplate: (NSString *) template +{ + NSString *pageName; + SOGoUser *user; + SOGoFolderAdvisory *page; + NSString *language; + + user = [SOGoUser userWithLogin: [self ownerInContext: context]]; + if ([[user domainDefaults] foldersSendEMailNotifications]) + { + language = [[user userDefaults] language]; + pageName = [NSString stringWithFormat: @"SOGoFolder%@%@Advisory", + language, template]; + + page = [[WOApplication application] pageWithName: pageName + inContext: context]; + [page setFolderObject: self]; + [page setRecipientUID: [user login]]; + [page send]; + } +} + /* WebDAV */ - (NSString *) davEntityTag @@ -534,13 +563,6 @@ isEqualToString: [otherFolder nameInContainer]]); } -- (NSString *) outlookFolderClass -{ - [self subclassResponsibility: _cmd]; - - return nil; -} - /* acls */ - (NSString *) defaultUserID diff --git a/SoObjects/SOGo/SOGoGCSFolder.h b/SoObjects/SOGo/SOGoGCSFolder.h index 69c641020..b4575b815 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.h +++ b/SoObjects/SOGo/SOGoGCSFolder.h @@ -118,9 +118,6 @@ - (void) removeAclsForUsers: (NSArray *) users forObjectAtPath: (NSArray *) objectPathArray; -/* advisories */ -- (void) sendFolderAdvisoryTemplate: (NSString *) template; - /* DAV */ - (NSURL *) publicDavURL; - (NSURL *) realDavURL; diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index 723d32609..5ee38847c 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -52,7 +52,6 @@ #import #import #import -#import #import "NSDictionary+Utilities.h" #import "NSArray+Utilities.h" @@ -500,28 +499,6 @@ static NSArray *childRecordFields = nil; return folder; } -- (void) sendFolderAdvisoryTemplate: (NSString *) template -{ - NSString *pageName; - SOGoUser *user; - SOGoFolderAdvisory *page; - NSString *language; - - user = [SOGoUser userWithLogin: [self ownerInContext: context]]; - if ([[user domainDefaults] foldersSendEMailNotifications]) - { - language = [[user userDefaults] language]; - pageName = [NSString stringWithFormat: @"SOGoFolder%@%@Advisory", - language, template]; - - page = [[WOApplication application] pageWithName: pageName - inContext: context]; - [page setFolderObject: self]; - [page setRecipientUID: [user login]]; - [page send]; - } -} - - (BOOL) create { NSException *result; diff --git a/SoObjects/SOGo/SOGoParentFolder.h b/SoObjects/SOGo/SOGoParentFolder.h index cfd272121..c0041879b 100644 --- a/SoObjects/SOGo/SOGoParentFolder.h +++ b/SoObjects/SOGo/SOGoParentFolder.h @@ -42,6 +42,9 @@ - (NSString *) defaultFolderName; +- (NSException *) appendPersonalSources; +- (void) removeSubFolder: (NSString *) subfolderName; + - (void) setBaseOCSPath: (NSString *) newOCSPath; - (NSArray *) toManyRelationshipKeys; diff --git a/SoObjects/SOGo/SOGoParentFolder.m b/SoObjects/SOGo/SOGoParentFolder.m index 64cf44218..58e19958b 100644 --- a/SoObjects/SOGo/SOGoParentFolder.m +++ b/SoObjects/SOGo/SOGoParentFolder.m @@ -183,7 +183,6 @@ static SoSecurityManager *sm = nil; SOGoGCSFolder *folder; NSString *key; NSException *error; - SOGoUser *currentUser; if (!subFolderClass) subFolderClass = [[self class] subFolderClass]; @@ -191,8 +190,6 @@ static SoSecurityManager *sm = nil; error = [fc evaluateExpressionX: sql]; if (!error) { - currentUser = [context activeUser]; - attrs = [fc describeResults: NO]; while ((row = [fc fetchAttributes: attrs withZone: NULL])) { @@ -364,6 +361,11 @@ static SoSecurityManager *sm = nil; return error; } +- (void) removeSubFolder: (NSString *) subfolderName +{ + [subFolders removeObjectForKey: subfolderName]; +} + - (NSException *) initSubscribedSubFolders { NSException *error; diff --git a/SoObjects/SOGo/SOGoSource.h b/SoObjects/SOGo/SOGoSource.h index d7cc7db04..3a500cd4a 100644 --- a/SoObjects/SOGo/SOGoSource.h +++ b/SoObjects/SOGo/SOGoSource.h @@ -28,9 +28,10 @@ #import "SOGoConstants.h" @class NSDictionary; +@class NSException; @class NSString; -@protocol SOGoSource +@protocol SOGoSource + (id) sourceFromUDSource: (NSDictionary *) udSource inDomain: (NSString *) domain; @@ -40,6 +41,10 @@ - (NSString *) domain; +/* requires a "." to obtain the full list of contacts */ +- (void) setListRequiresDot: (BOOL) aBool; +- (BOOL) listRequiresDot; + - (BOOL) checkLogin: (NSString *) _login password: (NSString *) _pwd perr: (SOGoPasswordPolicyError *) _perr @@ -56,8 +61,36 @@ - (NSArray *) allEntryIDs; - (NSArray *) fetchContactsMatching: (NSString *) filter; + +- (void) setSourceID: (NSString *) newSourceID; - (NSString *) sourceID; +- (void) setDisplayName: (NSString *) newDisplayName; +- (NSString *) displayName; + +- (void) setModifiers: (NSArray *) newModifiers; +- (NSArray *) modifiers; + +- (BOOL) hasUserAddressBooks; +- (NSArray *) addressBookSourcesForUser: (NSString *) user; + +- (NSException *) addContactEntry: (NSDictionary *) roLdifRecord + withID: (NSString *) aId; +- (NSException *) updateContactEntry: (NSDictionary *) ldifRecord; +- (NSException *) removeContactEntryWithID: (NSString *) aId; + +/* user address books */ +- (NSArray *) addressBookSourcesForUser: (NSString *) user; + +- (NSException *) addAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user; +- (NSException *) renameAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user; +- (NSException *) removeAddressBookSource: (NSString *) newId + forUser: (NSString *) user; + @end @protocol SOGoDNSource diff --git a/SoObjects/SOGo/SOGoUser.h b/SoObjects/SOGo/SOGoUser.h index 7682f283f..2ed1b1cf8 100644 --- a/SoObjects/SOGo/SOGoUser.h +++ b/SoObjects/SOGo/SOGoUser.h @@ -52,6 +52,8 @@ @class SOGoUserProfile; @class SOGoUserSettings; +@protocol SOGoSource; + @interface SOGoUser : SoUser { SOGoUserDefaults *_defaults; @@ -88,6 +90,7 @@ /* properties */ - (NSString *) domain; +- (id ) authenticationSource; - (NSArray *) allEmails; - (BOOL) hasEmail: (NSString *) email; diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 68678e519..721f857ad 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -287,6 +287,17 @@ return [self _fetchFieldForUser: @"c_domain"]; } +- (id ) authenticationSource +{ + NSString *sourceID; + SOGoUserManager *um; + + sourceID = [self _fetchFieldForUser: @"SOGoSource"]; + um = [SOGoUserManager sharedUserManager]; + + return [um sourceWithID: sourceID]; +} + - (NSArray *) allEmails { if (!allEmails) diff --git a/SoObjects/SOGo/SOGoUserManager.m b/SoObjects/SOGo/SOGoUserManager.m index 91915716a..061dd0aff 100644 --- a/SoObjects/SOGo/SOGoUserManager.m +++ b/SoObjects/SOGo/SOGoUserManager.m @@ -586,7 +586,7 @@ NSMutableArray *emails; NSDictionary *userEntry; NSEnumerator *sogoSources; - NSObject *currentSource; + NSObject *currentSource; NSString *sourceID, *cn, *c_domain, *c_uid, *c_imaphostname, *c_imaplogin; NSArray *c_emails; BOOL access; @@ -605,12 +605,14 @@ sogoSources = [[self authenticationSourceIDsInDomain: domain] objectEnumerator]; - while ((sourceID = [sogoSources nextObject])) + userEntry = nil; + while (!userEntry && (sourceID = [sogoSources nextObject])) { currentSource = [_sources objectForKey: sourceID]; userEntry = [currentSource lookupContactEntryWithUIDorEmail: uid]; if (userEntry) { + [currentUser setObject: sourceID forKey: @"SOGoSource"]; if (!cn) cn = [userEntry objectForKey: @"c_cn"]; if (!c_uid) diff --git a/SoObjects/SOGo/SQLSource.m b/SoObjects/SOGo/SQLSource.m index 84777c8ab..59bdf969e 100644 --- a/SoObjects/SOGo/SQLSource.m +++ b/SoObjects/SOGo/SQLSource.m @@ -22,8 +22,9 @@ */ #import -#import #import +#import +#import #import #import #import @@ -379,6 +380,7 @@ { NSMutableDictionary *response; NSMutableArray *qualifiers; + NSArray *fieldNames; EOAdaptorChannel *channel; EOQualifier *loginQualifier, *qualifier; GCSChannelManager *cm; @@ -463,6 +465,16 @@ [response autorelease]; [channel cancelFetch]; + /* Convert all c_ fields to obtain their ldif equivalent */ + fieldNames = [response allKeys]; + for (i = 0; i < [fieldNames count]; i++) + { + field = [fieldNames objectAtIndex: i]; + if ([field hasPrefix: @"c_"]) + [response setObject: [response objectForKey: field] + forKey: [field substringFromIndex: 2]]; + } + // We have to do this here since we do not manage modules // constraints right now over a SQL backend. [response setObject: [NSNumber numberWithBool: YES] forKey: @"CalendarAccess"]; @@ -560,6 +572,8 @@ forKey: @"numberOfSimultaneousBookings"]; } } + + [response setObject: self forKey: @"source"]; } else [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; @@ -666,7 +680,12 @@ attrs = [channel describeResults: NO]; while ((row = [channel fetchAttributes: attrs withZone: NULL])) - [results addObject: row]; + { + row = [row mutableCopy]; + [(NSMutableDictionary *) row setObject: self forKey: @"source"]; + [results addObject: row]; + [row release]; + } } else [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; @@ -679,9 +698,142 @@ return results; } +- (void) setSourceID: (NSString *) newSourceID +{ +} + - (NSString *) sourceID { return _sourceID; } +- (void) setDisplayName: (NSString *) newDisplayName +{ +} + +- (NSString *) displayName +{ + /* This method is only used when supporting user "source" addressbooks, + which is only supported by the LDAP backend for now. */ + return _sourceID; +} + +- (void) setListRequiresDot: (BOOL) newListRequiresDot +{ +} + +- (BOOL) listRequiresDot +{ + /* This method is not implemented for SQLSource. It must enable a mechanism + where using "." is not required to list the content of addressbooks. */ + return YES; +} + +/* card editing */ +- (void) setModifiers: (NSArray *) newModifiers +{ +} + +- (NSArray *) modifiers +{ + /* This method is only used when supporting card editing, + which is only supported by the LDAP backend for now. */ + return nil; +} + +- (NSException *) addContactEntry: (NSDictionary *) roLdifRecord + withID: (NSString *) aId +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +- (NSException *) updateContactEntry: (NSDictionary *) roLdifRecord +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +- (NSException *) removeContactEntryWithID: (NSString *) aId +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +/* user addressbooks */ +- (BOOL) hasUserAddressBooks +{ + return NO; +} + +- (NSArray *) addressBookSourcesForUser: (NSString *) user +{ + return nil; +} + +- (NSException *) addAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +- (NSException *) renameAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +- (NSException *) removeAddressBookSource: (NSString *) newId + forUser: (NSString *) user +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + @end diff --git a/Tools/GNUmakefile b/Tools/GNUmakefile index 920ad2d82..33417f935 100644 --- a/Tools/GNUmakefile +++ b/Tools/GNUmakefile @@ -4,6 +4,7 @@ include ../config.make include $(GNUSTEP_MAKEFILES)/common.make include ../Version +### SOGO_TOOL = sogo-tool $(SOGO_TOOL)_INSTALL_DIR = $(SOGO_ADMIN_TOOLS) $(SOGO_TOOL)_OBJC_FILES += \ @@ -18,7 +19,9 @@ $(SOGO_TOOL)_OBJC_FILES += \ SOGoToolRenameUser.m \ SOGoToolUserPreferences.m \ SOGoToolExpireAutoReply.m +TOOL_NAME += $(SOGO_TOOL) +### SOGO_SLAPD_SOCKD = sogo-slapd-sockd $(SOGO_SLAPD_SOCKD)_INSTALL_DIR = $(SOGO_ADMIN_TOOLS) $(SOGO_SLAPD_SOCKD)_OBJC_FILES += \ @@ -26,16 +29,18 @@ $(SOGO_SLAPD_SOCKD)_OBJC_FILES += \ \ SOGoSockD.m \ SOGoSockDScanner.m \ - SOGoSockDOperation.m \ + SOGoSockDOperation.m +TOOL_NAME += $(SOGO_SLAPD_SOCKD) +### SOGO_EALARMS_NOTIFY = sogo-ealarms-notify $(SOGO_EALARMS_NOTIFY)_INSTALL_DIR = $(SOGO_ADMIN_TOOLS) $(SOGO_EALARMS_NOTIFY)_OBJC_FILES += \ sogo-ealarms-notify.m \ \ SOGoEAlarmsNotifier.m +TOOL_NAME += $(SOGO_EALARMS_NOTIFY) -TOOL_NAME = $(SOGO_TOOL) $(SOGO_SLAPD_SOCKD) $(SOGO_EALARMS_NOTIFY) -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/tool.make diff --git a/Tools/SOGoSockDOperation.m b/Tools/SOGoSockDOperation.m index 6e7057a34..62d4d8226 100644 --- a/Tools/SOGoSockDOperation.m +++ b/Tools/SOGoSockDOperation.m @@ -29,6 +29,7 @@ #import #import #import +#import #import #import #import @@ -120,13 +121,13 @@ Class SOGoContactSourceFolderKlass = Nil; value = [entry objectForKey: key]; if ([value isKindOfClass: [NSString class]] && [value length] > 0) { - if ([value _isLDIFSafe]) - [result appendFormat: @"%@: %@\n", - [key substringFromIndex: 2], value]; - else + if ([value mustEncodeLDIFValue]) [result appendFormat: @"%@:: %@\n", [key substringFromIndex: 2], [value stringByEncodingBase64]]; + else + [result appendFormat: @"%@: %@\n", + [key substringFromIndex: 2], value]; } } [result appendString: @"\n"]; diff --git a/Tools/SOGoToolBackup.m b/Tools/SOGoToolBackup.m index 5b2c6588e..8dd7ea04a 100644 --- a/Tools/SOGoToolBackup.m +++ b/Tools/SOGoToolBackup.m @@ -37,11 +37,12 @@ #import #import #import -#import +#import #import #import #import #import +#import #import "SOGoTool.h" @@ -61,6 +62,12 @@ @implementation SOGoToolBackup ++ (void) initialize +{ + [[SOGoProductLoader productLoader] + loadProducts: [NSArray arrayWithObject: @"Contacts.SOGo"]]; +} + + (NSString *) command { return @"backup"; @@ -376,7 +383,7 @@ userEntry = [currentSource lookupContactEntry: uid]; if (userEntry) { - [userRecord setObject: [userEntry userRecordAsLDIFEntry] + [userRecord setObject: [userEntry ldifRecordAsString] forKey: @"ldif_record"]; done = YES; } diff --git a/UI/Contacts/UIxContactActions.m b/UI/Contacts/UIxContactActions.m index 03f5f6a07..aa4cc7138 100644 --- a/UI/Contacts/UIxContactActions.m +++ b/UI/Contacts/UIxContactActions.m @@ -52,6 +52,8 @@ categories = [[self categories] mutableCopy]; [categories autorelease]; + if (!categories) + categories = [NSMutableArray array]; if (set) { if (![categories containsObject: category]) diff --git a/UI/Contacts/UIxContactEditor.h b/UI/Contacts/UIxContactEditor.h index f0998f2f2..e01c07767 100644 --- a/UI/Contacts/UIxContactEditor.h +++ b/UI/Contacts/UIxContactEditor.h @@ -27,22 +27,18 @@ @class NSString; @class NSMutableDictionary; -@class NGVCard; - @class SOGoContactFolder; @interface UIxContactEditor : UIxComponent { id addressBookItem; - NSString *preferredEmail; NSString *item; - NGVCard *card; - NSMutableArray *photosURL; - NSMutableDictionary *snapshot; /* contains the values for editing */ + NSMutableDictionary *ldifRecord; /* contains the values for editing */ SOGoContactFolder *componentAddressBook; - NSArray *contactCategories; } +- (NSMutableDictionary *) ldifRecord; + - (void) setAddressBookItem: (id) _item; - (id) addressBookItem; diff --git a/UI/Contacts/UIxContactEditor.m b/UI/Contacts/UIxContactEditor.m index dfbc48644..73a88d448 100644 --- a/UI/Contacts/UIxContactEditor.m +++ b/UI/Contacts/UIxContactEditor.m @@ -34,8 +34,6 @@ #import #import -#import -#import #import #import @@ -50,20 +48,23 @@ #import "UIxContactEditor.h" +static Class SOGoContactGCSEntryK = Nil; + @implementation UIxContactEditor ++ (void) initialize +{ + SOGoContactGCSEntryK = [SOGoContactGCSEntry class]; +} + - (id) init { if ((self = [super init])) { - snapshot = [[NSMutableDictionary alloc] initWithCapacity: 16]; - preferredEmail = nil; - photosURL = nil; + ldifRecord = nil; addressBookItem = nil; item = nil; - card = nil; componentAddressBook = nil; - contactCategories = nil; } return self; @@ -71,18 +72,35 @@ - (void) dealloc { - [snapshot release]; - [preferredEmail release]; - [photosURL release]; + [ldifRecord release]; [addressBookItem release]; [item release]; [componentAddressBook release]; - [contactCategories release]; [super dealloc]; } /* accessors */ +- (NSMutableDictionary *) ldifRecord +{ + NSDictionary *clientLDIFRecord; + NSString *queryValue; + + if (!ldifRecord) + { + clientLDIFRecord = [[self clientObject] ldifRecord]; + ldifRecord = [clientLDIFRecord mutableCopy]; + queryValue = [self queryParameterForKey: @"contactEmail"]; + if ([queryValue length] > 0) + [ldifRecord setObject: queryValue forKey: @"mail"]; + queryValue = [self queryParameterForKey: @"contactFN"]; + if ([queryValue length] > 0) + [ldifRecord setObject: queryValue forKey: @"displayname"]; + } + + return ldifRecord; +} + - (void) setAddressBookItem: (id) _item { ASSIGN (addressBookItem, _item); @@ -130,27 +148,6 @@ /* load/store content format */ -// - (void) _fixupSnapshot -// { -// NSString *currentKey, *currentString; -// NSMutableString *newString; -// NSArray *keys; -// unsigned int count, max; - -// keys = [snapshot allKeys]; -// max = [keys count]; -// for (count = 0; count < max; count++) -// { -// currentKey = [keys objectAtIndex: count]; -// currentString = [snapshot objectForKey: currentKey]; -// newString = [currentString mutableCopy]; -// [newString autorelease]; -// [newString replaceString: @";" withString: @"\\;"]; -// if (![newString isEqualToString: currentString]) -// [snapshot setObject: newString forKey: currentKey]; -// } -// } - /* helper */ - (NSString *) _completeURIForMethod: (NSString *) _method @@ -179,12 +176,7 @@ - (BOOL) isNew { - id co; - - co = [self clientObject]; - - return ([co isKindOfClass: [SOGoContentObject class]] - && [co isNew]); + return ([[self clientObject] isNew]); } - (NSArray *) addressBooksList @@ -205,14 +197,13 @@ while (currentFolder) { if ([currentFolder isEqual: folder] || - ([currentFolder isKindOfClass: [SOGoContactGCSFolder class]] && - ![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles - onObject: currentFolder - inContext: context])) + ![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles + onObject: currentFolder + inContext: context]) [addressBooksList addObject: currentFolder]; currentFolder = [folders nextObject]; } - + return addressBooksList; } @@ -245,26 +236,30 @@ return fDisplayName; } -- (void) setContactCategories: (NSString *) jsonCategories +- (BOOL) supportCategories +{ + return [[self clientObject] isKindOfClass: SOGoContactGCSEntryK]; +} + +- (void) setJsonContactCategories: (NSString *) jsonCategories { NSArray *newCategories; newCategories = [jsonCategories objectFromJSONString]; if ([newCategories isKindOfClass: [NSArray class]]) - ASSIGN (contactCategories, newCategories); + [[self ldifRecord] setObject: newCategories + forKey: @"vcardcategories"]; + else + [[self ldifRecord] removeObjectForKey: @"vcardcategories"]; } -- (NSString *) contactCategories +- (NSString *) jsonContactCategories { - NSString *jsonCats; + NSArray *categories; - if (!contactCategories) - ASSIGN (contactCategories, [card categories]); - jsonCats = [contactCategories jsonRepresentation]; - if (!jsonCats) - jsonCats = @"[]"; + categories = [[self ldifRecord] objectForKey: @"vcardcategories"]; - return jsonCats; + return [categories jsonRepresentation]; } - (NSArray *) _languageContactsCategories @@ -279,11 +274,11 @@ return [categoryLabels trimmedComponents]; } -- (NSArray *) _fetchAndCombineCategoriesList: (NSArray *) contactCats +- (NSArray *) _fetchAndCombineCategoriesList { NSString *ownerLogin; SOGoUserDefaults *ud; - NSArray *cats, *newCats; + NSArray *cats, *newCats, *contactCategories; ownerLogin = [[self clientObject] ownerInContext: context]; ud = [[SOGoUser userWithLogin: ownerLogin] userDefaults]; @@ -291,9 +286,10 @@ if (!cats) cats = [self _languageContactsCategories]; - if (contactCats) + contactCategories = [[self ldifRecord] objectForKey: @"vcardcategories"]; + if (contactCategories) { - newCats = [cats mergedArrayWithArray: contactCats]; + newCats = [cats mergedArrayWithArray: contactCategories]; if ([newCats count] != [cats count]) { cats = [newCats sortedArrayUsingSelector: @@ -311,7 +307,7 @@ NSArray *cats; NSString *list; - cats = [self _fetchAndCombineCategoriesList: [card categories]]; + cats = [self _fetchAndCombineCategoriesList]; list = [cats jsonRepresentation]; if (!list) list = @"[]"; @@ -328,311 +324,7 @@ actionName = [[request requestHandlerPath] lastPathComponent]; - return ([[self clientObject] isKindOfClass: [SOGoContactGCSEntry class]] - && [actionName hasPrefix: @"save"]); -} - -- (void) _setSnapshotValue: (NSString *) key - to: (NSString *) aValue -{ - if (!aValue) - aValue = @""; - - [snapshot setObject: aValue forKey: key]; -} - -- (NSMutableDictionary *) snapshot -{ - return snapshot; -} - -- (NSString *) _simpleValueForType: (NSString *) aType - inArray: (NSArray *) anArray - excluding: (NSString *) aTypeToExclude -{ - NSArray *elements; - NSString *value; - - elements = [anArray cardElementsWithAttribute: @"type" - havingValue: aType]; - - value = nil; - - if ([elements count] > 0) - { - CardElement *ce; - int i; - - for (i = 0; i < [elements count]; i++) - { - ce = [elements objectAtIndex: i]; - value = [ce flattenedValuesForKey: @""]; - - if (!aTypeToExclude) - break; - - if (![ce hasAttribute: @"type" havingValue: aTypeToExclude]) - break; - - value = nil; - } - } - - return value; -} - -- (void) _setupEmailFields -{ - NSArray *elements; - NSString *workMail, *homeMail, *prefMail, *potential; - unsigned int max; - - elements = [card childrenWithTag: @"email"]; - max = [elements count]; - workMail = [self _simpleValueForType: @"work" - inArray: elements excluding: nil]; - homeMail = [self _simpleValueForType: @"home" - inArray: elements excluding: nil]; - prefMail = [self _simpleValueForType: @"pref" - inArray: elements excluding: nil]; - - if (max > 0) - { - potential = [[elements objectAtIndex: 0] flattenedValuesForKey: @""]; - if (!workMail) - { - if (homeMail && homeMail == potential) - { - if (max > 1) - workMail = [[elements objectAtIndex: 1] flattenedValuesForKey: @""]; - } - else - workMail = potential; - } - if (!homeMail && max > 1) - { - if (workMail && workMail == potential) - homeMail = [[elements objectAtIndex: 1] flattenedValuesForKey: @""]; - else - homeMail = potential; - } - - if (prefMail) - { - if (prefMail == workMail) - preferredEmail = @"work"; - else if (prefMail == homeMail) - preferredEmail = @"home"; - } - } - - [self _setSnapshotValue: @"workMail" to: workMail]; - [self _setSnapshotValue: @"homeMail" to: homeMail]; - - [self _setSnapshotValue: @"mozillaUseHtmlMail" - to: [[card uniqueChildWithTag: @"x-mozilla-html"] flattenedValuesForKey: @""]]; -} - -- (void) _setupOrgFields -{ - NSMutableArray *orgServices; - CardElement *org; - NSString *service; - NSUInteger count, max; - - org = [card org]; - [self _setSnapshotValue: @"workCompany" - to: [org flattenedValueAtIndex: 0 forKey: @""]]; - max = [[org valuesForKey: @""] count]; - if (max > 1) - { - orgServices = [NSMutableArray arrayWithCapacity: max]; - for (count = 1; count < max; count++) - { - service = [org flattenedValueAtIndex: count forKey: @""]; - if ([service length] > 0) - [orgServices addObject: service]; - } - - [self _setSnapshotValue: @"workService" - to: [orgServices componentsJoinedByString: @", "]]; - } -} - -- (NSString *) preferredEmail -{ - return preferredEmail; -} - -- (void) setPreferredEmail: (NSString *) aString -{ - preferredEmail = aString; -} - -- (void) _retrieveQueryParameter: (NSString *) queryKey - intoSnapshotValue: (NSString *) snapshotKey -{ - NSString *queryValue; - - queryValue = [self queryParameterForKey: queryKey]; - if (queryValue && [queryValue length] > 0) - [self _setSnapshotValue: snapshotKey to: queryValue]; -} - -- (void) initSnapshot -{ - NSArray *elements; - CardElement *element; - - element = [card n]; - [self _setSnapshotValue: @"sn" - to: [element flattenedValueAtIndex: 0 forKey: @""]]; - [self _setSnapshotValue: @"givenName" - to: [element flattenedValueAtIndex: 1 forKey: @""]]; - [self _setSnapshotValue: @"fn" to: [card fn]]; - [self _setSnapshotValue: @"nickname" to: [card nickname]]; - - elements = [card childrenWithTag: @"tel"]; - // We do this (exclude FAX) in order to avoid setting the WORK number as the FAX - // one if we do see the FAX field BEFORE the WORK number. - [self _setSnapshotValue: @"telephoneNumber" - to: [self _simpleValueForType: @"work" inArray: elements excluding: @"fax"]]; - [self _setSnapshotValue: @"homeTelephoneNumber" - to: [self _simpleValueForType: @"home" inArray: elements excluding: @"fax"]]; - [self _setSnapshotValue: @"mobile" - to: [self _simpleValueForType: @"cell" inArray: elements excluding: nil]]; - [self _setSnapshotValue: @"facsimileTelephoneNumber" - to: [self _simpleValueForType: @"fax" inArray: elements excluding: nil]]; - [self _setSnapshotValue: @"pager" - to: [self _simpleValueForType: @"pager" inArray: elements excluding: nil]]; - - // If we don't have a "home" and "work" phone number but - // we have a "voice" one defined, we set it to the "work" value - // This can happen when we have : - // VERSION:2.1 - // N:name;surname;;;; - // TEL;VOICE;HOME: - // TEL;VOICE;WORK: - // TEL;PAGER: - // TEL;FAX;WORK: - // TEL;CELL:514 123 1234 - // TEL;VOICE:450 456 6789 - // ADR;HOME:;;;;;; - // ADR;WORK:;;;;;; - // ADR:;;;;;; - if ([[snapshot objectForKey: @"telephoneNumber"] length] == 0 && - [[snapshot objectForKey: @"homeTelephoneNumber"] length] == 0 && - [elements count] > 0) - { - [self _setSnapshotValue: @"telephoneNumber" - to: [self _simpleValueForType: @"voice" inArray: elements excluding: nil]]; - } - - [self _setupEmailFields]; - - [self _setSnapshotValue: @"screenName" - to: [[card uniqueChildWithTag: @"x-aim"] flattenedValuesForKey: @""]]; - - elements = [card childrenWithTag: @"adr" - andAttribute: @"type" havingValue: @"work"]; - if (elements && [elements count] > 0) - { - element = [elements objectAtIndex: 0]; - [self _setSnapshotValue: @"workExtendedAddress" - to: [element flattenedValueAtIndex: 1 forKey: @""]]; - [self _setSnapshotValue: @"workStreetAddress" - to: [element flattenedValueAtIndex: 2 forKey: @""]]; - [self _setSnapshotValue: @"workCity" - to: [element flattenedValueAtIndex: 3 forKey: @""]]; - [self _setSnapshotValue: @"workState" - to: [element flattenedValueAtIndex: 4 forKey: @""]]; - [self _setSnapshotValue: @"workPostalCode" - to: [element flattenedValueAtIndex: 5 forKey: @""]]; - [self _setSnapshotValue: @"workCountry" - to: [element flattenedValueAtIndex: 6 forKey: @""]]; - } - - elements = [card childrenWithTag: @"adr" - andAttribute: @"type" havingValue: @"home"]; - if (elements && [elements count] > 0) - { - element = [elements objectAtIndex: 0]; - [self _setSnapshotValue: @"homeExtendedAddress" - to: [element flattenedValueAtIndex: 1 forKey: @""]]; - [self _setSnapshotValue: @"homeStreetAddress" - to: [element flattenedValueAtIndex: 2 forKey: @""]]; - [self _setSnapshotValue: @"homeCity" - to: [element flattenedValueAtIndex: 3 forKey: @""]]; - [self _setSnapshotValue: @"homeState" - to: [element flattenedValueAtIndex: 4 forKey: @""]]; - [self _setSnapshotValue: @"homePostalCode" - to: [element flattenedValueAtIndex: 5 forKey: @""]]; - [self _setSnapshotValue: @"homeCountry" - to: [element flattenedValueAtIndex: 6 forKey: @""]]; - } - - elements = [card childrenWithTag: @"url"]; - [self _setSnapshotValue: @"workURL" - to: [self _simpleValueForType: @"work" inArray: elements excluding: nil]]; - [self _setSnapshotValue: @"homeURL" - to: [self _simpleValueForType: @"home" inArray: elements excluding: nil]]; - - // If we don't have a "work" or "home" URL but we still have - // an URL field present, let's add it to the "home" value - if ([[snapshot objectForKey: @"workURL"] length] == 0 && - [[snapshot objectForKey: @"homeURL"] length] == 0 && - [elements count] > 0) - { - [self _setSnapshotValue: @"homeURL" - to: [[elements objectAtIndex: 0] flattenedValuesForKey: @""]]; - } - // If we do have a "work" URL but no "home" URL but two - // values URLs present, let's add the second one as the home URL - else if ([[snapshot objectForKey: @"workURL"] length] > 0 && - [[snapshot objectForKey: @"homeURL"] length] == 0 && - [elements count] > 1) - { - int i; - - for (i = 0; i < [elements count]; i++) - { - if ([[[elements objectAtIndex: i] flattenedValuesForKey: @""] - caseInsensitiveCompare: [snapshot objectForKey: @"workURL"]] != NSOrderedSame) - { - [self _setSnapshotValue: @"homeURL" - to: [[elements objectAtIndex: i] flattenedValuesForKey: @""]]; - break; - } - } - } - - - [self _setSnapshotValue: @"calFBURL" - to: [[card uniqueChildWithTag: @"FBURL"] flattenedValuesForKey: @""]]; - - [self _setSnapshotValue: @"title" to: [card title]]; - [self _setupOrgFields]; - - [self _setSnapshotValue: @"bday" to: [card bday]]; - [self _setSnapshotValue: @"tz" to: [card tz]]; - [self _setSnapshotValue: @"note" to: [card note]]; - - [self _retrieveQueryParameter: @"contactEmail" - intoSnapshotValue: @"workMail"]; - [self _retrieveQueryParameter: @"contactFN" - intoSnapshotValue: @"fn"]; -} - -- (id ) defaultAction -{ - card = [[self clientObject] vCard]; - if (card) - [self initSnapshot]; - else - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason: @"could not open contact"]; - - return self; + return ([actionName hasPrefix: @"save"]); } - (NSString *) viewActionName @@ -647,216 +339,60 @@ return @"editAsContact"; } -- (BOOL) canCreateOrModify +- (BOOL) supportPhotos { - SOGoObject *co; - - co = [self clientObject]; - - return ([co isKindOfClass: [SOGoContentObject class]] - && [super canCreateOrModify]); + return [[self clientObject] isKindOfClass: SOGoContactGCSEntryK]; } -- (NSArray *) photosURL +- (BOOL) hasPhoto +{ + return [[self clientObject] hasPhoto]; +} + +- (NSString *) photoURL { - NSArray *photoElements; NSURL *soURL; - NSString *baseInlineURL, *photoURL; - NGVCardPhoto *photo; - int count, max; - if (!photosURL) - { - soURL = [[self clientObject] soURL]; - baseInlineURL = [soURL absoluteString]; - photoElements = [card childrenWithTag: @"photo"]; - max = [photoElements count]; - photosURL = [[NSMutableArray alloc] initWithCapacity: max]; - for (count = 0; count < max; count++) - { - photo = [photoElements objectAtIndex: count]; - if ([photo isInline]) - photoURL = [NSString stringWithFormat: @"%@/photo%d", - baseInlineURL, count]; - else - photoURL = [photo flattenedValuesForKey: @""]; - [photosURL addObject: photoURL]; - } - } + soURL = [[self clientObject] soURL]; - return photosURL; -} - -- (CardElement *) _elementWithTag: (NSString *) tag - ofType: (NSString *) type -{ - NSArray *elements; - CardElement *element; - - elements = [card childrenWithTag: tag - andAttribute: @"type" havingValue: type]; - if ([elements count] > 0) - element = [elements objectAtIndex: 0]; - else - { - element = [CardElement new]; - [element autorelease]; - [element setTag: tag]; - [element addType: type]; - [card addChild: element]; - } - - return element; -} - -- (void) _savePhoneValues -{ - CardElement *phone; - - phone = [self _elementWithTag: @"tel" ofType: @"work"]; - [phone setSingleValue: [snapshot objectForKey: @"telephoneNumber"] forKey: @""]; - phone = [self _elementWithTag: @"tel" ofType: @"home"]; - [phone setSingleValue: [snapshot objectForKey: @"homeTelephoneNumber"] forKey: @""]; - phone = [self _elementWithTag: @"tel" ofType: @"cell"]; - [phone setSingleValue: [snapshot objectForKey: @"mobile"] forKey: @""]; - phone = [self _elementWithTag: @"tel" ofType: @"fax"]; - [phone setSingleValue: [snapshot objectForKey: @"facsimileTelephoneNumber"] - forKey: @""]; - phone = [self _elementWithTag: @"tel" ofType: @"pager"]; - [phone setSingleValue: [snapshot objectForKey: @"pager"] forKey: @""]; -} - -- (void) _saveEmails -{ - CardElement *workMail, *homeMail; - - workMail = [self _elementWithTag: @"email" ofType: @"work"]; - [workMail setSingleValue: [snapshot objectForKey: @"workMail"] forKey: @""]; - homeMail = [self _elementWithTag: @"email" ofType: @"home"]; - [homeMail setSingleValue: [snapshot objectForKey: @"homeMail"] forKey: @""]; - if (preferredEmail) - { - if ([preferredEmail isEqualToString: @"work"]) - [card setPreferred: workMail]; - else - [card setPreferred: homeMail]; - } - - [[card uniqueChildWithTag: @"x-mozilla-html"] - setSingleValue: [snapshot objectForKey: @"mozillaUseHtmlMail"] - forKey: @""]; -} - -- (void) _saveSnapshot -{ - CardElement *element; - NSArray *units; - - [card setNWithFamily: [snapshot objectForKey: @"sn"] - given: [snapshot objectForKey: @"givenName"] - additional: nil - prefixes: nil - suffixes: nil]; - [card setNickname: [snapshot objectForKey: @"nickname"]]; - [card setFn: [snapshot objectForKey: @"fn"]]; - [card setTitle: [snapshot objectForKey: @"title"]]; - [card setBday: [snapshot objectForKey: @"bday"]]; - [card setNote: [snapshot objectForKey: @"note"]]; - [card setTz: [snapshot objectForKey: @"tz"]]; - - element = [self _elementWithTag: @"adr" ofType: @"home"]; - [element setSingleValue: [snapshot objectForKey: @"homeExtendedAddress"] - atIndex: 1 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"homeStreetAddress"] - atIndex: 2 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"homeCity"] - atIndex: 3 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"homeState"] - atIndex: 4 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"homePostalCode"] - atIndex: 5 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"homeCountry"] - atIndex: 6 forKey: @""]; - - element = [self _elementWithTag: @"adr" ofType: @"work"]; - [element setSingleValue: [snapshot objectForKey: @"workExtendedAddress"] - atIndex: 1 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"workStreetAddress"] - atIndex: 2 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"workCity"] - atIndex: 3 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"workState"] - atIndex: 4 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"workPostalCode"] - atIndex: 5 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"workCountry"] - atIndex: 6 forKey: @""]; - - element = [CardElement simpleElementWithTag: @"fburl" - value: [snapshot objectForKey: @"calFBURL"]]; - [card setUniqueChild: element]; - - units = [NSArray arrayWithObject: [snapshot objectForKey: @"workService"]]; - [card setOrg: [snapshot objectForKey: @"workCompany"] - units: units]; - - [self _savePhoneValues]; - [self _saveEmails]; - [[self _elementWithTag: @"url" ofType: @"home"] - setSingleValue: [snapshot objectForKey: @"homeURL"] forKey: @""]; - [[self _elementWithTag: @"url" ofType: @"work"] - setSingleValue: [snapshot objectForKey: @"workURL"] forKey: @""]; - - [[card uniqueChildWithTag: @"x-aim"] - setSingleValue: [snapshot objectForKey: @"screenName"] - forKey: @""]; + return [NSString stringWithFormat: @"%@/photo", [soURL absoluteString]]; } - (id ) saveAction { - SOGoContactGCSEntry *contact; + SOGoObject *contact; id result; NSString *jsRefreshMethod; SoSecurityManager *sm; contact = [self clientObject]; - card = [contact vCard]; - if (card) - { -// [self _fixupSnapshot]; - [self _saveSnapshot]; - [card setCategories: contactCategories]; - [self _fetchAndCombineCategoriesList: contactCategories]; - [contact save]; + [contact setLDIFRecord: ldifRecord]; + [self _fetchAndCombineCategoriesList]; + [contact save]; - if (componentAddressBook && componentAddressBook != [self componentAddressBook]) - { - sm = [SoSecurityManager sharedSecurityManager]; - if (![sm validatePermission: SoPerm_DeleteObjects - onObject: componentAddressBook - inContext: context]) - { - if (![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles - onObject: componentAddressBook - inContext: context]) - [contact moveToFolder: (SOGoGCSFolder *)componentAddressBook]; // TODO: handle exception - } - } - - if ([[[[self context] request] formValueForKey: @"nojs"] intValue]) - result = [self redirectToLocation: [self modulePath]]; - else + if (componentAddressBook && componentAddressBook != [self componentAddressBook]) + { + sm = [SoSecurityManager sharedSecurityManager]; + if (![sm validatePermission: SoPerm_DeleteObjects + onObject: componentAddressBook + inContext: context]) { - jsRefreshMethod - = [NSString stringWithFormat: @"refreshContacts(\"%@\")", - [contact nameInContainer]]; - result = [self jsCloseWithRefreshMethod: jsRefreshMethod]; + if (![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles + onObject: componentAddressBook + inContext: context]) + [contact moveToFolder: (SOGoGCSFolder *)componentAddressBook]; // TODO: handle exception } } + + if ([[[[self context] request] formValueForKey: @"nojs"] intValue]) + result = [self redirectToLocation: [self modulePath]]; else - result = [NSException exceptionWithHTTPStatus: 400 /* Bad Request */ - reason: @"method cannot be invoked on " - @"the specified object"]; + { + jsRefreshMethod + = [NSString stringWithFormat: @"refreshContacts(\"%@\")", + [contact nameInContainer]]; + result = [self jsCloseWithRefreshMethod: jsRefreshMethod]; + } return result; } @@ -866,17 +402,15 @@ NSString *email, *cn, *url; NSMutableString *address; - card = [[self clientObject] vCard]; - [self initSnapshot]; - if ([preferredEmail isEqualToString: @"home"]) - email = [snapshot objectForKey: @"homeMail"]; - else - email = [snapshot objectForKey: @"workMail"]; + [self ldifRecord]; + email = [ldifRecord objectForKey: @"mail"]; + if ([email length] == 0) + email = [ldifRecord objectForKey: @"mozillasecondemail"]; if (email) { address = [NSMutableString string]; - cn = [card fn]; + cn = [ldifRecord objectForKey: @"cn"]; if ([cn length] > 0) [address appendFormat: @"%@ <%@>", cn, email]; else diff --git a/UI/Contacts/UIxContactFolderActions.m b/UI/Contacts/UIxContactFolderActions.m index 2dd503615..8adbcaa2b 100644 --- a/UI/Contacts/UIxContactFolderActions.m +++ b/UI/Contacts/UIxContactFolderActions.m @@ -35,6 +35,7 @@ #import #import #import +#import #import #import @@ -76,9 +77,10 @@ inContext: [self context] acquire: NO]; if ([currentChild respondsToSelector: @selector (vCard)]) - [content appendFormat: [[currentChild vCard] ldifString]]; + [content appendFormat: [[currentChild ldifRecord] ldifRecordAsString]]; else if ([currentChild respondsToSelector: @selector (vList)]) [content appendFormat: [[currentChild vList] ldifString]]; + [content appendString: @"\n"]; } response = [context response]; diff --git a/UI/Contacts/UIxContactFoldersView.m b/UI/Contacts/UIxContactFoldersView.m index a9d56ac3e..e520318a7 100644 --- a/UI/Contacts/UIxContactFoldersView.m +++ b/UI/Contacts/UIxContactFoldersView.m @@ -52,8 +52,16 @@ #import "UIxContactFoldersView.h" +Class SOGoContactSourceFolderK, SOGoGCSFolderK; + @implementation UIxContactFoldersView ++ (void) initialize +{ + SOGoContactSourceFolderK = [SOGoContactSourceFolder class]; + SOGoGCSFolderK = [SOGoGCSFolder class]; +} + - (id) init { if ((self = [super init])) @@ -110,11 +118,13 @@ folders = [self clientObject]; folder = [folders lookupPersonalFolder: @"personal" ignoringRights: YES]; - - contactInfos = [folder lookupContactsWithFilter: nil - onCriteria: nil - sortBy: @"c_cn" - ordering: NSOrderedAscending]; + if (folder && [folder conformsToProtocol: @protocol (SOGoContactFolder)]) + contactInfos = [folder lookupContactsWithFilter: nil + onCriteria: nil + sortBy: @"c_cn" + ordering: NSOrderedAscending]; + else + contactInfos = nil; return contactInfos; } @@ -183,7 +193,7 @@ { folder = [folders objectAtIndex: i]; /* We first search in LDAP folders (in case of duplicated entries in GCS folders) */ - if ([folder isKindOfClass: [SOGoContactSourceFolder class]]) + if ([folder isKindOfClass: SOGoContactSourceFolderK]) [sortedFolders insertObject: folder atIndex: 0]; else [sortedFolders addObject: folder]; @@ -287,10 +297,23 @@ - (NSString *) currentContactFolderClass { - return ([currentFolder isKindOfClass: [SOGoContactSourceFolder class]] + return (([currentFolder isKindOfClass: SOGoContactSourceFolderK] + && ![currentFolder isPersonalSource]) ? @"remote" : @"local"); } +- (NSString *) currentContactFolderAclEditing +{ + return ([currentFolder isKindOfClass: SOGoGCSFolderK] + ? @"available": @"unavailable"); +} + +- (NSString *) currentContactFolderListEditing +{ + return ([currentFolder isKindOfClass: SOGoGCSFolderK] + ? @"available": @"unavailable"); +} + - (NSString *) verticalDragHandleStyle { NSString *vertical; diff --git a/UI/Contacts/UIxContactView.m b/UI/Contacts/UIxContactView.m index 498bf2e91..3d3bf53c2 100644 --- a/UI/Contacts/UIxContactView.m +++ b/UI/Contacts/UIxContactView.m @@ -52,6 +52,7 @@ - (void) dealloc { + [card release]; [photosURL release]; [super dealloc]; } @@ -377,9 +378,7 @@ { NSString *data; - data = nil; - - if (url) + if ([url length] > 0) { if (![[url lowercaseString] rangeOfString: @"://"].length) url = [NSString stringWithFormat: @"http://%@", url]; @@ -388,6 +387,8 @@ @"%@", url, url]; } + else + data = nil; return [self _cardStringWithLabel: nil value: data]; } @@ -646,30 +647,12 @@ /* action */ -- (id ) vcardAction -{ -#warning this method is unused - WOResponse *response; - - card = [[self clientObject] vCard]; - if (card) - { - response = [context response]; - [response setHeader: @"text/vcard" forKey: @"Content-type"]; - [response appendContentString: [card versitString]]; - } - else - return [NSException exceptionWithHTTPStatus: 404 /* Not Found */ - reason:@"could not locate contact"]; - - return response; -} - - (id ) defaultAction { card = [[self clientObject] vCard]; if (card) { + [card retain]; phones = nil; homeAdr = nil; workAdr = nil; diff --git a/UI/Contacts/product.plist b/UI/Contacts/product.plist index e88d39ca5..8aadf26a2 100644 --- a/UI/Contacts/product.plist +++ b/UI/Contacts/product.plist @@ -139,6 +139,11 @@ pageName = "UIxContactEditor"; actionName = "new"; }; + renameFolder = { + protectedBy = "Change Permissions"; + actionClass = "UIxFolderActions"; + actionName = "renameFolder"; + }; mailer-contacts = { protectedBy = ""; pageName = "UIxContactFoldersView"; @@ -234,13 +239,27 @@ SOGoContactLDIFEntry = { methods = { view = { - protectedBy = ""; + protectedBy = "Access Contents Information"; pageName = "UIxContactView"; }; edit = { - protectedBy = ""; + protectedBy = "Access Contents Information"; pageName = "UIxContactEditor"; }; + editAsContact = { + protectedBy = "Access Contents Information"; + pageName = "UIxContactEditor"; + }; + save = { + protectedBy = "Change Images And Files"; + pageName = "UIxContactEditor"; + actionName = "save"; + }; + saveAsContact = { + protectedBy = "Change Images And Files"; + pageName = "UIxContactEditor"; + actionName = "save"; + }; write = { protectedBy = ""; pageName = "UIxContactEditor"; diff --git a/UI/MailerUI/UIxMailFolderActions.m b/UI/MailerUI/UIxMailFolderActions.m index 57f94973e..2dc1371ba 100644 --- a/UI/MailerUI/UIxMailFolderActions.m +++ b/UI/MailerUI/UIxMailFolderActions.m @@ -84,70 +84,24 @@ return response; } -- (NSURL *) _urlOfFolder: (NSURL *) srcURL - renamedTo: (NSString *) folderName -{ - NSString *path; - NSURL *destURL; - - path = [[srcURL path] stringByDeletingLastPathComponent]; - if (![path hasSuffix: @"/"]) - path = [path stringByAppendingString: @"/"]; - - destURL = [[NSURL alloc] initWithScheme: [srcURL scheme] - host: [srcURL host] - path: [NSString stringWithFormat: @"%@%@", - path, folderName]]; - [destURL autorelease]; - - return destURL; -} - - (WOResponse *) renameFolderAction { - SOGoMailFolder *co, *inbox; + SOGoMailFolder *co; WOResponse *response; - NGImap4Connection *connection; NSException *error; NSString *folderName; - NSURL *srcURL, *destURL; co = [self clientObject]; folderName = [[context request] formValueForKey: @"name"]; - if ([folderName length] > 0) - { - srcURL = [co imap4URL]; - destURL = [self _urlOfFolder: srcURL renamedTo: folderName]; - connection = [co imap4Connection]; - inbox = [[co mailAccountFolder] inboxFolderInContext: context]; - [[connection client] select: [inbox absoluteImap4Name]]; - error = [connection moveMailboxAtURL: srcURL - toURL: destURL]; - - if (error) - { - response = [self responseWithStatus: 500]; - [response appendContentString: @"Unable to rename folder."]; - } - else - { - // We unsubscribe to the old one, and subscribe back to the new one - if ([[[context activeUser] userDefaults] - mailShowSubscribedFoldersOnly]) - { - [[connection client] subscribe: [destURL path]]; - [[connection client] unsubscribe: [srcURL path]]; - } - - response = [self responseWith204]; - } - } - else + error = [co renameTo: folderName]; + if (error) { response = [self responseWithStatus: 500]; - [response appendContentString: @"Missing 'name' parameter."]; + [response appendContentString: @"Unable to rename folder."]; } + else + response = [self responseWith204]; return response; } diff --git a/UI/Templates/ContactsUI/UIxContactEditor.wox b/UI/Templates/ContactsUI/UIxContactEditor.wox index ebb3eb41c..37849396b 100644 --- a/UI/Templates/ContactsUI/UIxContactEditor.wox +++ b/UI/Templates/ContactsUI/UIxContactEditor.wox @@ -1,4 +1,4 @@ - + -
  • -
  • +
  • +
  • -
  • -
  • +
  • +
  • @@ -46,34 +50,34 @@ + + var:value="ldifRecord.sn" + /> + + @@ -82,26 +86,26 @@ + + + @@ -111,9 +115,8 @@ - + selection="ldifRecord.mozillausehtmlmail" + /> @@ -123,9 +126,9 @@ @@ -133,24 +136,19 @@ + name="homephone" id="homephone" + var:value="ldifRecord.homephone" + /> + name="facsimiletelephonenumber" + id="facsimiletelephonenumber" + var:value="ldifRecord.facsimiletelephonenumber" + /> @@ -159,38 +157,37 @@ - + var:value="ldifRecord.pager" + /> + var:value="ldifRecord.mobile" + /> -
    -
    -
    -
    +
    +
    + - - -
    + + +
    @@ -198,64 +195,64 @@ + + + + + + + @@ -267,128 +264,116 @@ - + var:value="ldifRecord.title" + /> + + + + + + + + +
    -
    - -
    -
    +
    + +
    - - - - - - diff --git a/UI/Templates/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/ContactsUI/UIxContactFoldersView.wox index e8f304862..a8a9a9653 100644 --- a/UI/Templates/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/ContactsUI/UIxContactFoldersView.wox @@ -121,6 +121,8 @@ >
  • diff --git a/UI/WebServerResources/ContactsUI.js b/UI/WebServerResources/ContactsUI.js index b5b8faed6..9417f80e8 100644 --- a/UI/WebServerResources/ContactsUI.js +++ b/UI/WebServerResources/ContactsUI.js @@ -258,17 +258,6 @@ function onContactContextMenuHide(event) { this.stopObserving("contextmenu:hide", onContactContextMenuHide); } -function onFolderMenuHide(event) { - var topNode = $('d'); - - if (topNode.menuSelectedEntry) { - topNode.menuSelectedEntry.deselect(); - topNode.menuSelectedEntry = null; - } - if (topNode.selectedEntry) - topNode.selectedEntry.selectElement(); -} - function _onContactMenuAction(folderItem, action, refresh) { var selectedFolders = $("contactFolders").getSelectedNodes(); var folderId = $(folderItem).readAttribute("folderId"); @@ -308,8 +297,9 @@ function onContactMenuMove(event) { function onMenuExportContact (event) { var selectedFolders = $("contactFolders").getSelectedNodes(); - var selectedFolderId = $(selectedFolders[0]).readAttribute("id"); - if (selectedFolderId != "/shared") { + var canExport = (selectedFolders[0].getAttribute("owner") != "nobody"); + if (canExport) { + var selectedFolderId = $(selectedFolders[0]).readAttribute("id"); var contactIds = $(document.menuTarget).collect(function(row) { return row.getAttribute("id"); }); @@ -512,9 +502,6 @@ function onToolbarDeleteSelectedContactsConfirm(dialogId) { var contactsList = $('contactsList'); var rows = contactsList.getSelectedRowsId(); for (var i = 0; i < rows.length; i++) { - var row = $(rows[i]); - row.deselect(); - row.hide(); delete cachedContacts[Contact.currentAddressBook + "/" + rows[i]]; var urlstr = (URLForFolderID(Contact.currentAddressBook) + "/" + rows[i] + "/delete"); @@ -532,6 +519,7 @@ function onContactDeleteEventCallback(http) { $("contactView").update(); Contact.currentContact = null; } + Contact.deleteContactsRequestCount--; if (Contact.deleteContactsRequestCount == 0) { var nextRow = row.next("tr"); @@ -543,7 +531,10 @@ function onContactDeleteEventCallback(http) { loadContact(Contact.currentContact); } } - row.parentNode.removeChild(row); + if (row) { + row.deselect(); + row.parentNode.removeChild(row); + } } else if (parseInt(http.status) == 403) { var row = $(http.callbackData); @@ -598,10 +589,11 @@ function newContact(sender) { function newList(sender) { var li = $(Contact.currentAddressBook); - if (li.hasClassName("remote")) - showAlertDialog(_("You cannot create a list in a shared address book.")); - else + var listEditing = li.getAttribute("list-editing"); + if (listEditing && listEditing == "available") openContactWindow(URLForFolderID(Contact.currentAddressBook) + "/newlist"); + else + showAlertDialog(_("You cannot create a list in a shared address book.")); return false; } @@ -1068,15 +1060,15 @@ function onMenuSharing(event) { var folders = $("contactFolders"); var selected = folders.getSelectedNodes()[0]; - var owner = selected.getAttribute("owner"); - if (owner == "nobody") - showAlertDialog(_("The user rights cannot be edited for this object!")); - else { + var aclEditing = selected.getAttribute("acl-editing"); + if (aclEditing && aclEditing == "available") { var title = this.innerHTML; var url = URLForFolderID(selected.getAttribute("id")); openAclWindow(url + "/acls", title); } + else + showAlertDialog(_("The user rights cannot be edited for this object!")); } function onAddressBooksMenuPrepareVisibility() { @@ -1089,30 +1081,55 @@ function onAddressBooksMenuPrepareVisibility() { var menu = $("contactFoldersMenu").down("ul");; var listElements = menu.childNodesWithTag("li"); var modifyOption = listElements[0]; + var newListOption = listElements[3]; var removeOption = listElements[5]; var exportOption = listElements[7]; + var importOption = listElements[8]; var sharingOption = listElements[listElements.length - 1]; // Disable the "Sharing" and "Modify" options when address book // is not owned by user if (folderOwner == UserLogin || IsSuperUser) { - modifyOption.removeClassName("disabled"); // WARNING: will fail for super users anyway - sharingOption.removeClassName("disabled"); + modifyOption.removeClassName("disabled"); // WARNING: will fail + // for super users + // anyway + var aclEditing = selected[0].getAttribute("acl-editing"); + if (aclEditing && aclEditing == "available") { + sharingOption.removeClassName("disabled"); + } + else { + sharingOption.addClassName("disabled"); + } } else { modifyOption.addClassName("disabled"); sharingOption.addClassName("disabled"); } + var listEditing = selected[0].getAttribute("list-editing"); + if (listEditing && listEditing == "available") { + newListOption.removeClassName("disabled"); + } + else { + newListOption.addClassName("disabled"); + } + /* Disable the "remove" and "export ab" options when address book is public */ if (folderOwner == "nobody") { exportOption.addClassName("disabled"); + importOption.addClassName("disabled"); removeOption.addClassName("disabled"); } else { exportOption.removeClassName("disabled"); - removeOption.removeClassName("disabled"); + importOption.removeClassName("disabled"); + if (selected[0].getAttribute("id") == "/personal") { + removeOption.addClassName("disabled"); + } + else { + removeOption.removeClassName("disabled"); + } } return true; diff --git a/UI/WebServerResources/UIxContactEditor.css b/UI/WebServerResources/UIxContactEditor.css index 58112a94d..26f3c8ba6 100644 --- a/UI/WebServerResources/UIxContactEditor.css +++ b/UI/WebServerResources/UIxContactEditor.css @@ -69,3 +69,9 @@ INPUT.comboBoxField, #emptyCategory #otherInfos TEXTAREA { width: 70%; } + +#birthday, #birthmonth +{ width: 18px; } + +#birthyear +{ width: 36px; } diff --git a/UI/WebServerResources/UIxContactEditor.js b/UI/WebServerResources/UIxContactEditor.js index f9f3a53bd..1db214b58 100644 --- a/UI/WebServerResources/UIxContactEditor.js +++ b/UI/WebServerResources/UIxContactEditor.js @@ -22,9 +22,9 @@ 02111-1307, USA. */ -var dateRegex = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/; +var dateRegex = /^(([0-9]{2})?[0-9])?[0-9]-[0-9]?[0-9]-[0-9]?[0-9]$/; -var displayNameChanged = false; +var displaynameChanged = false; var tabIndex = 0; @@ -37,43 +37,43 @@ function unescapeCallbackParameter(s) { } function copyContact(type, email, uid, sn, - cn, givenName, telephoneNumber, facsimileTelephoneNumber, - mobile, postalAddress, homePostalAddress, - departmentNumber, l) + displayname, givenname, telephonenumber, facsimiletelephonenumber, + mobile, postalAddress, homePostalAddress, + departmentnumber, l) { // var type = arguments[0]; // var email = arguments[1]; // var uid = arguments[2]; // var sn = arguments[3]; - // var givenName = arguments[4]; - // var telephoneNumber = arguments[5]; - // var facsimileTelephoneNumber = arguments[6]; + // var givenname = arguments[4]; + // var telephonenumber = arguments[5]; + // var facsimiletelephonenumber = arguments[6]; // var mobile = arguments[7]; - // var postalAddress = arguments[8]; - // var homePostalAddress = arguments[9]; - // var departmentNumber = arguments[10]; + // var postaladdress = arguments[8]; + // var homepostaladdress = arguments[9]; + // var departmentnumber = arguments[10]; // var l = arguments[11]; var e; - e = $('cn'); - e.setAttribute('value', unescapeCallbackParameter(cn)); + e = $('displayname'); + e.setAttribute('value', unescapeCallbackParameter(displayname)); e = $('email'); e.setAttribute('value', email); e = $('sn'); e.setAttribute('value', unescapeCallbackParameter(sn)); - e = $('givenName'); - e.setAttribute('value', unescapeCallbackParameter(givenName)); - e = $('telephoneNumber'); - e.setAttribute('value', telephoneNumber); - e = $('facsimileTelephoneNumber'); - e.setAttribute('value', facsimileTelephoneNumber); + e = $('givenname'); + e.setAttribute('value', unescapeCallbackParameter(givenname)); + e = $('telephonenumber'); + e.setAttribute('value', telephonenumber); + e = $('facsimiletelephonenumber'); + e.setAttribute('value', facsimileTelephonenumber); e = $('mobile'); e.setAttribute('value', mobile); - e = $('postalAddress'); + e = $('postaladdress'); e.setAttribute('value', unescapeCallbackParameter(postalAddress)); - e = $('homePostalAddress'); + e = $('homepostaladdress'); e.setAttribute('value', unescapeCallbackParameter(homePostalAddress)); - e = $('departmentNumber'); - e.setAttribute('value', unescapeCallbackParameter(departmentNumber)); + e = $('departmentnumber'); + e.setAttribute('value', unescapeCallbackParameter(departmentnumber)); e = $('l'); e.setAttribute('value', unescapeCallbackParameter(l)); }; @@ -81,23 +81,25 @@ function copyContact(type, email, uid, sn, function validateContactEditor() { var rc = true; - var e = $('workMail'); + var e = $('mail'); if (e.value.length > 0 && !emailRE.test(e.value)) { alert(_("invalidemailwarn")); rc = false; } - e = $('homeMail'); + e = $('mozillasecondemail'); if (e.value.length > 0 && !emailRE.test(e.value)) { alert(_("invalidemailwarn")); rc = false; } - e = $('birthday'); - if (e.value.length > 0 - && !dateRegex.test(e.value)) { + var byear = $('birthyear'); + var bmonth = $('birthmonth'); + var bday = $('birthday'); + var bdayValue = byear.value + "-" + bmonth.value + "-" + bday.value; + if (bdayValue != "--" && !dateRegex.test(bdayValue)) { alert(_("invaliddatewarn")); rc = false; } @@ -105,25 +107,25 @@ function validateContactEditor() { return rc; } -function onFnKeyDown() { - var fn = $("fn"); +function onDisplaynameKeyDown() { + var fn = $("displayname"); fn.onkeydown = null; - displayNameChanged = true; + displaynameChanged = true; return true; } -function onFnNewValue(event) { - if (!displayNameChanged) { +function onDisplaynameNewValue(event) { + if (!displaynameChanged) { var sn = $("sn").value.trim(); - var givenName = $("givenName").value.trim(); + var givenname = $("givenname").value.trim(); - var fullName = givenName; - if (fullName && sn) - fullName += ' '; - fullName += sn; + var fullname = givenname; + if (fullname && sn) + fullname += ' '; + fullname += sn; - $("fn").value = fullName; + $("displayname").value = fullname; } return true; @@ -144,7 +146,7 @@ function onEditorSubmitClick(event) { function saveCategories() { var container = $("categoryContainer"); - var catsInput = $("contactCategories"); + var catsInput = $("jsonContactCategories"); if (container && catsInput) { var newCategories = $([]); var inputs = container.select("INPUT"); @@ -164,8 +166,8 @@ function onDocumentKeydown(event) { var target = Event.element(event); if (target.tagName == "INPUT" || target.tagName == "SELECT") { if (event.keyCode == Event.KEY_RETURN) { - var fcn = onEditorSubmitClick.bind($("submitButton")); - fcn(); + var fdisplayname = onEditorSubmitClick.bind($("submitButton")); + fdisplayname(); Event.stop(event); } } @@ -267,10 +269,10 @@ function initEditorForm() { var controller = new SOGoTabsController(); controller.attachToTabsContainer(tabsContainer); - displayNameChanged = ($("fn").value.length > 0); - $("fn").onkeydown = onFnKeyDown; - $("sn").onkeyup = onFnNewValue; - $("givenName").onkeyup = onFnNewValue; + displaynameChanged = ($("displayname").value.length > 0); + $("displayname").onkeydown = onDisplaynameKeyDown; + $("sn").onkeyup = onDisplaynameNewValue; + $("givenname").onkeyup = onDisplaynameNewValue; $("cancelButton").observe("click", onEditorCancelClick); var submitButton = $("submitButton"); @@ -280,8 +282,10 @@ function initEditorForm() { Event.observe(document, "keydown", onDocumentKeydown); - regenerateCategoriesMenu(); - var catsInput = $("contactCategories"); + if (typeof(gCategories) != "undefined") { + regenerateCategoriesMenu(); + } + var catsInput = $("jsonContactCategories"); if (catsInput && catsInput.value.length > 0) { var contactCats = $(catsInput.value.evalJSON(false)); for (var i = 0; i < contactCats.length; i++) { @@ -294,6 +298,6 @@ function initEditorForm() { emptyCategory.tabIndex = 10000; emptyCategory.observe("click", onEmptyCategoryClick); } -} +} document.observe("dom:loaded", initEditorForm); diff --git a/Version b/Version index 640430005..09503ebca 100644 --- a/Version +++ b/Version @@ -2,6 +2,6 @@ # This file is included by library makefiles to set the version information # of the executable. -MAJOR_VERSION=1 -MINOR_VERSION=3 -SUBMINOR_VERSION=12 +MAJOR_VERSION=2 +MINOR_VERSION=0 +SUBMINOR_VERSION=0
    - + + - + + - - - - + var:value="ldifRecord.birthday"/>
    - -
    +