From 928b6b596e7b5929f1b0c1d065827e73c557ed95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 14 Jul 2015 13:09:09 +0200 Subject: [PATCH 01/20] oc: OC_DEBUG inside macros These macros are used before and after every performed operation --- OpenChange/MAPIStoreSOGo.m | 105 +++---------------------------------- 1 file changed, 6 insertions(+), 99 deletions(-) diff --git a/OpenChange/MAPIStoreSOGo.m b/OpenChange/MAPIStoreSOGo.m index 4ec0ba53a..a41dd5a28 100644 --- a/OpenChange/MAPIStoreSOGo.m +++ b/OpenChange/MAPIStoreSOGo.m @@ -61,11 +61,14 @@ static BOOL initialization_done = NO; if (!initialization_done) { \ OC_DEBUG(5, "[SOGo] You should call sogo_backend_init() first. Current thread: %p, pid: %d\n", \ GSCurrentThread(), getpid()); \ - } + } \ + OC_DEBUG(5, "[SOGo] --->"); + #define NS_CURRENT_THREAD_TRY_UNREGISTER() \ if (__nsrct_thread_registered) { \ GSUnregisterCurrentThread(); \ - } + } \ + OC_DEBUG(6, "[SOGo] <---"); #define TRYCATCH_START @try { #define TRYCATCH_END(pool) \ @@ -214,8 +217,6 @@ sogo_backend_create_context(TALLOC_CTX *mem_ctx, MAPIStoreContext *context; int rc; - OC_DEBUG(5, "[SOGo]"); - NS_CURRENT_THREAD_REGISTER(); pool = [NSAutoreleasePool new]; @@ -251,8 +252,6 @@ sogo_backend_create_root_folder (const char *username, NSString *mapistoreUri; int rc; - OC_DEBUG(5, "[SOGo]"); - NS_CURRENT_THREAD_REGISTER(); pool = [NSAutoreleasePool new]; @@ -288,8 +287,6 @@ sogo_backend_list_contexts(const char *username, struct indexing_context *indexi NSString *userName; int rc; - OC_DEBUG(5, "[SOGo]"); - NS_CURRENT_THREAD_REGISTER(); pool = [NSAutoreleasePool new]; @@ -336,8 +333,6 @@ sogo_context_get_path(void *backend_object, TALLOC_CTX *mem_ctx, MAPIStoreContext *context; int rc; - OC_DEBUG(5, "[SOGo]"); - if (backend_object) { wrapper = backend_object; @@ -370,8 +365,6 @@ sogo_context_get_root_folder(void *backend_object, TALLOC_CTX *mem_ctx, MAPIStoreFolder *folder; int rc; - OC_DEBUG(5, "[SOGo]"); - if (backend_object) { wrapper = backend_object; @@ -413,8 +406,6 @@ sogo_folder_open_folder(void *folder_object, TALLOC_CTX *mem_ctx, uint64_t fid, MAPIStoreFolder *folder, *childFolder; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -442,7 +433,7 @@ sogo_folder_open_folder(void *folder_object, TALLOC_CTX *mem_ctx, uint64_t fid, /** \details Create a folder in the sogo backend - + \param private_data pointer to the current sogo context \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE_ERROR @@ -457,8 +448,6 @@ sogo_folder_create_folder(void *folder_object, TALLOC_CTX *mem_ctx, MAPIStoreFolder *folder, *childFolder; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -500,8 +489,6 @@ sogo_folder_delete(void *folder_object) MAPIStoreFolder *folder; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -532,8 +519,6 @@ sogo_folder_get_child_count(void *folder_object, enum mapistore_table_type table MAPIStoreFolder *folder; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -568,8 +553,6 @@ sogo_folder_open_message(void *folder_object, MAPIStoreMessage *message; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -610,8 +593,6 @@ sogo_folder_create_message(void *folder_object, MAPIStoreMessage *message; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -646,8 +627,6 @@ sogo_folder_delete_message(void *folder_object, uint64_t mid, uint8_t flags) MAPIStoreFolder *folder; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -684,8 +663,6 @@ sogo_folder_move_copy_messages(void *folder_object, struct MAPIStoreTallocWrapper *wrapper; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -728,8 +705,6 @@ sogo_folder_move_folder(void *folder_object, void *target_folder_object, struct MAPIStoreTallocWrapper *wrapper; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -778,8 +753,6 @@ sogo_folder_copy_folder(void *folder_object, void *target_folder_object, TALLOC_ struct MAPIStoreTallocWrapper *wrapper; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -822,8 +795,6 @@ sogo_folder_get_deleted_fmids(void *folder_object, TALLOC_CTX *mem_ctx, MAPIStoreFolder *folder; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -861,8 +832,6 @@ sogo_folder_open_table(void *folder_object, TALLOC_CTX *mem_ctx, MAPIStoreTable *table; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -900,8 +869,6 @@ sogo_folder_modify_permissions(void *folder_object, uint8_t flags, MAPIStoreFolder *folder; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -934,8 +901,6 @@ sogo_folder_preload_message_bodies(void *folder_object, enum mapistore_table_typ MAPIStoreFolder *folder; int rc; - OC_DEBUG(5, "[SOGo]"); - if (folder_object) { wrapper = folder_object; @@ -969,8 +934,6 @@ sogo_message_get_message_data(void *message_object, MAPIStoreMessage *message; int rc; - OC_DEBUG(5, "[SOGo]"); - if (message_object) { wrapper = message_object; @@ -1004,8 +967,6 @@ sogo_message_create_attachment (void *message_object, TALLOC_CTX *mem_ctx, void MAPIStoreAttachment *attachment; int rc; - OC_DEBUG(5, "[SOGo]"); - if (message_object) { wrapper = message_object; @@ -1041,8 +1002,6 @@ sogo_message_open_attachment (void *message_object, TALLOC_CTX *mem_ctx, MAPIStoreAttachment *attachment; int rc; - OC_DEBUG(5, "[SOGo]"); - if (message_object) { wrapper = message_object; @@ -1077,8 +1036,6 @@ sogo_message_get_attachment_table (void *message_object, TALLOC_CTX *mem_ctx, vo MAPIStoreAttachmentTable *table; int rc; - OC_DEBUG(5, "[SOGo]"); - if (message_object) { wrapper = message_object; @@ -1116,9 +1073,6 @@ sogo_message_modify_recipients (void *message_object, MAPIStoreMessage *message; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (message_object) { wrapper = message_object; @@ -1152,9 +1106,6 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag) MAPIStoreMessage *message; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (message_object) { wrapper = message_object; @@ -1186,9 +1137,6 @@ sogo_message_save (void *message_object, TALLOC_CTX *mem_ctx) MAPIStoreMessage *message; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (message_object) { wrapper = message_object; @@ -1220,9 +1168,6 @@ sogo_message_submit (void *message_object, enum SubmitFlags flags) MAPIStoreMailVolatileMessage *message; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (message_object) { wrapper = message_object; @@ -1259,9 +1204,6 @@ sogo_message_attachment_open_embedded_message (void *attachment_object, MAPIStoreEmbeddedMessage *message; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (attachment_object) { wrapper = attachment_object; @@ -1301,9 +1243,6 @@ sogo_message_attachment_create_embedded_message (void *attachment_object, MAPIStoreEmbeddedMessage *message; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (attachment_object) { wrapper = attachment_object; @@ -1338,9 +1277,6 @@ static enum mapistore_error sogo_table_get_available_properties(void *table_obje MAPIStoreTable *table; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (table_object) { wrapper = table_object; @@ -1371,9 +1307,6 @@ sogo_table_set_columns (void *table_object, uint16_t count, enum MAPITAGS *prope MAPIStoreTable *table; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (table_object) { wrapper = table_object; @@ -1405,9 +1338,6 @@ sogo_table_set_restrictions (void *table_object, struct mapi_SRestriction *restr MAPIStoreTable *table; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (table_object) { wrapper = table_object; @@ -1441,9 +1371,6 @@ sogo_table_set_sort_order (void *table_object, struct SSortOrderSet *sort_order, MAPIStoreTable *table; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (table_object) { wrapper = table_object; @@ -1479,9 +1406,6 @@ sogo_table_get_row (void *table_object, TALLOC_CTX *mem_ctx, MAPIStoreTable *table; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (table_object) { wrapper = table_object; @@ -1515,9 +1439,6 @@ sogo_table_get_row_count (void *table_object, MAPIStoreTable *table; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (table_object) { wrapper = table_object; @@ -1549,9 +1470,6 @@ sogo_table_handle_destructor (void *table_object, uint32_t handle_id) MAPIStoreTable *table; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (table_object) { wrapper = table_object; @@ -1584,9 +1502,6 @@ static enum mapistore_error sogo_properties_get_available_properties(void *objec MAPIStoreObject *propObject; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (object) { wrapper = object; @@ -1620,9 +1535,6 @@ sogo_properties_get_properties (void *object, MAPIStoreObject *propObject; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (object) { wrapper = object; @@ -1655,9 +1567,6 @@ sogo_properties_set_properties (void *object, struct SRow *aRow) MAPIStoreObject *propObject; int rc; - OC_DEBUG(5, "[SOGo]"); - - if (object) { wrapper = object; @@ -1691,8 +1600,6 @@ sogo_manager_generate_uri (TALLOC_CTX *mem_ctx, NSAutoreleasePool *pool; NSString *partialURLString, *username, *directory; - OC_DEBUG(5, "[SOGo]"); - if (uri == NULL) return MAPISTORE_ERR_INVALID_PARAMETER; /* This fixes a crash occurring during the instantiation of the From 503a70118f431dd0aa0d4ef733d36edaad8aeed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 14 Jul 2015 13:15:01 +0200 Subject: [PATCH 02/20] oc: remove tabs and unused code --- OpenChange/MAPIStoreSOGo.m | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/OpenChange/MAPIStoreSOGo.m b/OpenChange/MAPIStoreSOGo.m index a41dd5a28..f82cd3814 100644 --- a/OpenChange/MAPIStoreSOGo.m +++ b/OpenChange/MAPIStoreSOGo.m @@ -603,7 +603,7 @@ sogo_folder_create_message(void *folder_object, TRYCATCH_START rc = [folder createMessage: &message withMID: mid - isAssociated: associated]; + isAssociated: associated]; if (rc == MAPISTORE_SUCCESS) *message_object = [message tallocWrapper: mem_ctx]; TRYCATCH_END(pool) @@ -978,7 +978,6 @@ sogo_message_create_attachment (void *message_object, TALLOC_CTX *mem_ctx, void rc = [message createAttachment: &attachment inAID: aidp]; if (rc == MAPISTORE_SUCCESS) *attachment_object = [attachment tallocWrapper: mem_ctx]; - // [context tearDownRequest]; TRYCATCH_END(pool) [pool release]; @@ -1013,7 +1012,6 @@ sogo_message_open_attachment (void *message_object, TALLOC_CTX *mem_ctx, rc = [message getAttachment: &attachment withAID: aid]; if (rc == MAPISTORE_SUCCESS) *attachment_object = [attachment tallocWrapper: mem_ctx]; - // [context tearDownRequest]; TRYCATCH_END(pool) [pool release]; @@ -1048,7 +1046,6 @@ sogo_message_get_attachment_table (void *message_object, TALLOC_CTX *mem_ctx, vo andRowCount: row_count]; if (rc == MAPISTORE_SUCCESS) *table_object = [table tallocWrapper: mem_ctx]; - // [context tearDownRequest]; TRYCATCH_END(pool) [pool release]; @@ -1084,7 +1081,6 @@ sogo_message_modify_recipients (void *message_object, rc = [message modifyRecipientsWithRecipients: recipients andCount: count andColumns: columns]; - // [context tearDownRequest]; TRYCATCH_END(pool) [pool release]; @@ -1115,7 +1111,6 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag) TRYCATCH_START rc = [message setReadFlag: flag]; - // [context tearDownRequest]; TRYCATCH_END(pool) [pool release]; @@ -1146,7 +1141,6 @@ sogo_message_save (void *message_object, TALLOC_CTX *mem_ctx) TRYCATCH_START rc = [message saveMessage: mem_ctx]; - // [context tearDownRequest]; TRYCATCH_END(pool) [pool release]; @@ -1177,7 +1171,6 @@ sogo_message_submit (void *message_object, enum SubmitFlags flags) TRYCATCH_START rc = [message submitWithFlags: flags]; - // [context tearDownRequest]; TRYCATCH_END(pool) [pool release]; @@ -1590,10 +1583,10 @@ sogo_properties_set_properties (void *object, struct SRow *aRow) } static enum mapistore_error -sogo_manager_generate_uri (TALLOC_CTX *mem_ctx, - const char *user, - const char *folder, - const char *message, +sogo_manager_generate_uri (TALLOC_CTX *mem_ctx, + const char *user, + const char *folder, + const char *message, const char *rootURI, char **uri) { @@ -1645,9 +1638,9 @@ sogo_manager_generate_uri (TALLOC_CTX *mem_ctx, */ int mapistore_init_backend(void) { - struct mapistore_backend backend; - int ret; - static BOOL registered = NO; + struct mapistore_backend backend; + int ret; + static BOOL registered = NO; if (registered) ret = MAPISTORE_SUCCESS; From 3bf7afdfb7b39b4062d181cde5f71b0e1d8d58f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 14 Jul 2015 16:30:51 +0200 Subject: [PATCH 03/20] oc: remove and disable userContext after each call This adds [MAPIStoreUserContext activate] method to use it instead of activateWithUser. A cleanup operation is executed after each public function so there won't be any conflicts with future calls. In practice, this will deactivate the current user context set on MAPIApp, this means two things: (1) set nil as current user context on MAPIApp and (2) remove woContext from current thread dictionary --- OpenChange/MAPIApplication.h | 3 ++- OpenChange/MAPIApplication.m | 10 ++++++++++ OpenChange/MAPIStoreSOGo.m | 22 ++++++++++++++++++++-- OpenChange/MAPIStoreUserContext.h | 2 ++ OpenChange/MAPIStoreUserContext.m | 26 ++++++++++++++++++++++++++ 5 files changed, 60 insertions(+), 3 deletions(-) diff --git a/OpenChange/MAPIApplication.h b/OpenChange/MAPIApplication.h index e96d2d545..44fdaa15a 100644 --- a/OpenChange/MAPIApplication.h +++ b/OpenChange/MAPIApplication.h @@ -33,8 +33,9 @@ } - (id) authenticatorInContext: (id) context; - +- (MAPIStoreUserContext *) userContext; - (void) setUserContext: (MAPIStoreUserContext *) newContext; +- (void) cleanup; @end diff --git a/OpenChange/MAPIApplication.m b/OpenChange/MAPIApplication.m index 47438c2b1..818ec1199 100644 --- a/OpenChange/MAPIApplication.m +++ b/OpenChange/MAPIApplication.m @@ -76,6 +76,11 @@ MAPIApplication *MAPIApp = nil; return NO; } +- (MAPIStoreUserContext *) userContext +{ + return userContext; +} + - (void) setUserContext: (MAPIStoreUserContext *) newContext { /* user contexts must not be retained here ad their holder (mapistore) @@ -88,4 +93,9 @@ MAPIApplication *MAPIApp = nil; return [userContext authenticator]; } +- (void) cleanup +{ + [userContext deactivate]; +} + @end diff --git a/OpenChange/MAPIStoreSOGo.m b/OpenChange/MAPIStoreSOGo.m index f82cd3814..c11967a0e 100644 --- a/OpenChange/MAPIStoreSOGo.m +++ b/OpenChange/MAPIStoreSOGo.m @@ -74,10 +74,12 @@ static BOOL initialization_done = NO; #define TRYCATCH_END(pool) \ } @catch (NSException * e) { \ enum mapistore_error ret = sogo_backend_handle_objc_exception(e, __PRETTY_FUNCTION__, __LINE__); \ + mapiapp_cleanup(); \ [pool release]; \ NS_CURRENT_THREAD_TRY_UNREGISTER(); \ return ret; \ - } + } \ + mapiapp_cleanup(); static enum mapistore_error @@ -199,12 +201,28 @@ sogo_backend_init (void) return MAPISTORE_SUCCESS; } +/** + \details Cleanup operation to execute after an action has been performed + so there won't be any conflicts with future calls. + In practice this will deactivate the current user context set on MAPIApp + (which is the current WOApplication), this means two things: (1) set nil + as current user context on MAPIApp and (2) remove woContext from current + thread dictionary (this is used on WOContext.m). +*/ +static void mapiapp_cleanup(void) +{ + Class MAPIApplicationK; + MAPIApplicationK = NSClassFromString (@"MAPIApplication"); + if (MAPIApplicationK) + [[MAPIApplicationK application] cleanup]; +} + /** \details Create a connection context to the sogo backend \param mem_ctx pointer to the memory context \param uri pointer to the sogo path - \param private_data pointer to the private backend context + \param private_data pointer to the private backend context */ static enum mapistore_error diff --git a/OpenChange/MAPIStoreUserContext.h b/OpenChange/MAPIStoreUserContext.h index f9867d15a..b8defd0cd 100644 --- a/OpenChange/MAPIStoreUserContext.h +++ b/OpenChange/MAPIStoreUserContext.h @@ -86,6 +86,8 @@ /* SOGo hacky magic */ - (void) activateWithUser: (SOGoUser *) activeUser; +- (void) activate; +- (void) deactivate; - (MAPIStoreAuthenticator *) authenticator; - (WOContext *) woContext; diff --git a/OpenChange/MAPIStoreUserContext.m b/OpenChange/MAPIStoreUserContext.m index 159748519..cf3fe46fe 100644 --- a/OpenChange/MAPIStoreUserContext.m +++ b/OpenChange/MAPIStoreUserContext.m @@ -39,6 +39,7 @@ #import #import #import +#import #import #import @@ -369,6 +370,11 @@ static NSMapTable *contextsTable = nil; return authenticator; } +- (void) activate +{ + [self activateWithUser: [self sogoUser]]; +} + - (void) activateWithUser: (SOGoUser *) activeUser; { NSMutableDictionary *info; @@ -379,4 +385,24 @@ static NSMapTable *contextsTable = nil; [info setObject: woContext forKey: @"WOContext"]; } +- (void) deactivate +{ + NSMutableDictionary *info; + + if (self == [MAPIApp userContext]) + [MAPIApp setUserContext: nil]; + else + [self errorWithFormat: @"Error: Tried to deactivate an user context " + @"not enabled (%@ vs %@)", + [self username], [[MAPIApp userContext] username]]; + + info = [[NSThread currentThread] threadDictionary]; + if (woContext == [info objectForKey: @"WOContext"]) + [info removeObjectForKey: @"WOContext"]; + else + [self errorWithFormat: @"Error: Tried to deactivate a WOContext " + @"not enabled (%@ vs %@)", + woContext, [info objectForKey: @"WOContext"]]; +} + @end From 7d2f96e96b2b5b242be021d8d2e6ce5fc8e38d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 14 Jul 2015 16:31:28 +0200 Subject: [PATCH 04/20] oc: use activate instead of activateWithUser 100% of the times the user is the one that MAPIStoreUserContext already has, so no need to have it as an argument. --- OpenChange/MAPIStoreCalendarFolder.m | 6 +----- OpenChange/MAPIStoreCalendarMessage.m | 4 ++-- OpenChange/MAPIStoreContext.m | 4 ++-- OpenChange/MAPIStoreGCSBaseContext.m | 3 +-- OpenChange/MAPIStoreMailContext.m | 5 ++--- OpenChange/MAPIStoreMailFolder.m | 9 +++------ 6 files changed, 11 insertions(+), 20 deletions(-) diff --git a/OpenChange/MAPIStoreCalendarFolder.m b/OpenChange/MAPIStoreCalendarFolder.m index 5f57c0560..2e131387b 100644 --- a/OpenChange/MAPIStoreCalendarFolder.m +++ b/OpenChange/MAPIStoreCalendarFolder.m @@ -63,19 +63,15 @@ SOGoAppointmentObject *newEntry; NSString *name; - //[self logWithFormat: @"METHOD '%s' (%d)", __FUNCTION__, __LINE__]; + [[self userContext] activate]; name = [NSString stringWithFormat: @"%@.ics", [SOGoObject globallyUniqueObjectId]]; newEntry = [SOGoAppointmentObject objectWithName: name inContainer: sogoObject]; [newEntry setIsNew: YES]; - /* the WOContext is required here for resolving notification pages */ - [newEntry setContext: [[self userContext] woContext]]; newMessage = [MAPIStoreCalendarMessage mapiStoreObjectWithSOGoObject: newEntry inContainer: self]; - - return newMessage; } diff --git a/OpenChange/MAPIStoreCalendarMessage.m b/OpenChange/MAPIStoreCalendarMessage.m index f5d00e8d4..9e2952102 100644 --- a/OpenChange/MAPIStoreCalendarMessage.m +++ b/OpenChange/MAPIStoreCalendarMessage.m @@ -577,7 +577,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK; } } -- (void) save: (TALLOC_CTX *) memCtx +- (void) save: (TALLOC_CTX *) memCtx { // iCalCalendar *vCalendar; // NSCalendarDate *now; @@ -634,7 +634,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK; withActiveUser: activeUser inMemCtx: memCtx]; [self _updateAttachedEvents]; - [[self userContext] activateWithUser: activeUser]; + [[self userContext] activate]; [sogoObject updateContentWithCalendar: calendar fromRequest: nil]; [self updateVersions]; diff --git a/OpenChange/MAPIStoreContext.m b/OpenChange/MAPIStoreContext.m index 2f4592cb8..62d80c6e4 100644 --- a/OpenChange/MAPIStoreContext.m +++ b/OpenChange/MAPIStoreContext.m @@ -114,7 +114,7 @@ static NSMutableDictionary *contextClassMapping; userContext = [MAPIStoreUserContext userContextWithUsername: userName andTDBIndexing: indexing]; - [userContext activateWithUser: [userContext sogoUser]]; + [userContext activate]; classes = GSObjCAllSubclassesOfClass (self); max = [classes count]; @@ -426,7 +426,7 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) NSArray *pathComponents; NSUInteger count, max; - [userContext activateWithUser: activeUser]; + [userContext activate]; woContext = [userContext woContext]; [self ensureContextFolder]; diff --git a/OpenChange/MAPIStoreGCSBaseContext.m b/OpenChange/MAPIStoreGCSBaseContext.m index 384859c5d..e2de733b0 100644 --- a/OpenChange/MAPIStoreGCSBaseContext.m +++ b/OpenChange/MAPIStoreGCSBaseContext.m @@ -121,7 +121,7 @@ userContext = [MAPIStoreUserContext userContextWithUsername: userName andTDBIndexing: NULL]; - [MAPIApp setUserContext: userContext]; + [userContext activate]; moduleName = [self MAPIModuleName]; parentFolder = [[userContext rootFolders] objectForKey: moduleName]; nameInContainer = nil; @@ -134,7 +134,6 @@ moduleName, nameInContainer]; else mapistoreURI = nil; - [MAPIApp setUserContext: nil]; return mapistoreURI; } diff --git a/OpenChange/MAPIStoreMailContext.m b/OpenChange/MAPIStoreMailContext.m index f7a62fcd8..7d3e3592c 100644 --- a/OpenChange/MAPIStoreMailContext.m +++ b/OpenChange/MAPIStoreMailContext.m @@ -67,7 +67,7 @@ MakeDisplayFolderName (NSString *folderName) NSArray *parts; NSString *lastFolder; NSUInteger max; - + parts = [folderName componentsSeparatedByString: @"/"]; max = [parts count]; if (max > 1) @@ -198,7 +198,7 @@ MakeDisplayFolderName (NSString *folderName) userContext = [MAPIStoreUserContext userContextWithUsername: userName andTDBIndexing: NULL]; - [MAPIApp setUserContext: userContext]; + [userContext activate]; accountFolder = [[userContext rootFolders] objectForKey: @"mail"]; folderName = [NSString stringWithFormat: @"folder%@", [newFolderName asCSSIdentifier]]; @@ -213,7 +213,6 @@ MakeDisplayFolderName (NSString *folderName) [[folderName stringByEncodingImap4FolderName] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]; else mapistoreURI = nil; - [MAPIApp setUserContext: nil]; return mapistoreURI; } diff --git a/OpenChange/MAPIStoreMailFolder.m b/OpenChange/MAPIStoreMailFolder.m index 3e24145c1..993b1c136 100644 --- a/OpenChange/MAPIStoreMailFolder.m +++ b/OpenChange/MAPIStoreMailFolder.m @@ -139,7 +139,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK; [[self mapping] updateID: fid withURL: [self url]]; [dbFolder setNameInContainer: newNameInContainer]; [self cleanupCaches]; - + propsCopy = [newProperties mutableCopy]; [propsCopy removeObjectForKey: key]; [propsCopy autorelease]; @@ -186,10 +186,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK; nameInContainer = [NSString stringWithFormat: @"folder%@", [[folderName stringByEncodingImap4FolderName] asCSSIdentifier]]; - /* it may be the operation is interleaved with operations - from other users having cached information in the thread - with the other user, so it'd better activate the user again here... */ - [[self userContext] activateWithUser: [[[self userContext] woContext] activeUser]]; + [[self userContext] activate]; newFolder = [SOGoMailFolderK objectWithName: nameInContainer inContainer: sogoObject]; @@ -245,7 +242,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK; sortOrdering: nil] count]; *data = MAPILongValue (memCtx, longValue); - + return MAPISTORE_SUCCESS; } From 2ce85f6c5c17214019041ddd9f5713acdebdc5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Amor=20Garc=C3=ADa?= Date: Fri, 17 Jul 2015 15:46:33 +0200 Subject: [PATCH 05/20] oc-mail: Added [MAPIStoreFolder getPidTagDisplayName:inMemCtx:] This getter is necessary to decode folder names in utf7 encoding. --- OpenChange/MAPIStoreMailFolder.m | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/OpenChange/MAPIStoreMailFolder.m b/OpenChange/MAPIStoreMailFolder.m index 3e24145c1..f63392828 100644 --- a/OpenChange/MAPIStoreMailFolder.m +++ b/OpenChange/MAPIStoreMailFolder.m @@ -257,6 +257,16 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK; return MAPISTORE_SUCCESS; } + +- (int) getPidTagDisplayName: (void **) data + inMemCtx: (TALLOC_CTX *) memCtx +{ + NSString *displayName = [[sogoObject displayName] stringByDecodingImap4FolderName]; + *data = [displayName asUnicodeInMemCtx: memCtx]; + + return MAPISTORE_SUCCESS; +} + - (EOQualifier *) nonDeletedQualifier { static EOQualifier *nonDeletedQualifier = nil; From 959b4e015da7531bb09ee90e07964de87114c91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Amor=20Garc=C3=ADa?= Date: Mon, 20 Jul 2015 14:56:29 +0200 Subject: [PATCH 06/20] [SOgoMailFolder displayName] decodes IMAP4 --- OpenChange/MAPIStoreMailFolder.m | 10 ---------- SoObjects/Mailer/SOGoMailFolder.m | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/OpenChange/MAPIStoreMailFolder.m b/OpenChange/MAPIStoreMailFolder.m index f63392828..3e24145c1 100644 --- a/OpenChange/MAPIStoreMailFolder.m +++ b/OpenChange/MAPIStoreMailFolder.m @@ -257,16 +257,6 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK; return MAPISTORE_SUCCESS; } - -- (int) getPidTagDisplayName: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - NSString *displayName = [[sogoObject displayName] stringByDecodingImap4FolderName]; - *data = [displayName asUnicodeInMemCtx: memCtx]; - - return MAPISTORE_SUCCESS; -} - - (EOQualifier *) nonDeletedQualifier { static EOQualifier *nonDeletedQualifier = nil; diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index c65c6c43c..671a6caf6 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -1553,7 +1553,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data) - (NSString *) displayName { - return [self relativeImap4Name]; + return [[self relativeImap4Name] stringByDecodingImap4FolderName]; } - (NSDictionary *) davIMAPFieldsTable From cfab18e1b845bf24ac896014bce4d61a46f44fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 14 Jul 2015 16:47:38 +0200 Subject: [PATCH 07/20] oc: activate user context on initialization --- OpenChange/MAPIStoreContext.m | 7 +++---- OpenChange/MAPIStoreGCSBaseContext.m | 1 - OpenChange/MAPIStoreMailContext.m | 1 - OpenChange/MAPIStoreUserContext.m | 2 ++ 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/OpenChange/MAPIStoreContext.m b/OpenChange/MAPIStoreContext.m index 62d80c6e4..2ee8cd026 100644 --- a/OpenChange/MAPIStoreContext.m +++ b/OpenChange/MAPIStoreContext.m @@ -108,13 +108,12 @@ static NSMutableDictionary *contextClassMapping; NSArray *classes; Class currentClass; NSUInteger count, max; - MAPIStoreUserContext *userContext; list = NULL; - userContext = [MAPIStoreUserContext userContextWithUsername: userName - andTDBIndexing: indexing]; - [userContext activate]; + // User context is activated on initialization + [MAPIStoreUserContext userContextWithUsername: userName + andTDBIndexing: indexing]; classes = GSObjCAllSubclassesOfClass (self); max = [classes count]; diff --git a/OpenChange/MAPIStoreGCSBaseContext.m b/OpenChange/MAPIStoreGCSBaseContext.m index e2de733b0..e0f06482d 100644 --- a/OpenChange/MAPIStoreGCSBaseContext.m +++ b/OpenChange/MAPIStoreGCSBaseContext.m @@ -121,7 +121,6 @@ userContext = [MAPIStoreUserContext userContextWithUsername: userName andTDBIndexing: NULL]; - [userContext activate]; moduleName = [self MAPIModuleName]; parentFolder = [[userContext rootFolders] objectForKey: moduleName]; nameInContainer = nil; diff --git a/OpenChange/MAPIStoreMailContext.m b/OpenChange/MAPIStoreMailContext.m index 7d3e3592c..7ec79ad50 100644 --- a/OpenChange/MAPIStoreMailContext.m +++ b/OpenChange/MAPIStoreMailContext.m @@ -198,7 +198,6 @@ MakeDisplayFolderName (NSString *folderName) userContext = [MAPIStoreUserContext userContextWithUsername: userName andTDBIndexing: NULL]; - [userContext activate]; accountFolder = [[userContext rootFolders] objectForKey: @"mail"]; folderName = [NSString stringWithFormat: @"folder%@", [newFolderName asCSSIdentifier]]; diff --git a/OpenChange/MAPIStoreUserContext.m b/OpenChange/MAPIStoreUserContext.m index cf3fe46fe..d0ffbee66 100644 --- a/OpenChange/MAPIStoreUserContext.m +++ b/OpenChange/MAPIStoreUserContext.m @@ -159,6 +159,8 @@ static NSMapTable *contextsTable = nil; if ([userPassword length] == 0) userPassword = username; [authenticator setPassword: userPassword]; + // Activate the profile on initialization + [self activate]; } return self; From f4e3b9804256339adef761d2490db7f02ee59727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 14 Jul 2015 17:08:39 +0200 Subject: [PATCH 08/20] Nothing changed: format and hoarding code removed --- OpenChange/MAPIStoreContext.m | 68 ++++++++--------------------------- 1 file changed, 15 insertions(+), 53 deletions(-) diff --git a/OpenChange/MAPIStoreContext.m b/OpenChange/MAPIStoreContext.m index 2ee8cd026..2a0e1c58e 100644 --- a/OpenChange/MAPIStoreContext.m +++ b/OpenChange/MAPIStoreContext.m @@ -33,7 +33,6 @@ #import #import "MAPIStoreAttachment.h" -// #import "MAPIStoreAttachmentTable.h" #import "MAPIStoreFallbackContext.h" #import "MAPIStoreFolder.h" #import "MAPIStoreFolderTable.h" @@ -89,19 +88,19 @@ static NSMutableDictionary *contextClassMapping; currentClass = [classes objectAtIndex: count]; moduleName = [currentClass MAPIModuleName]; if (moduleName) - { - [contextClassMapping setObject: currentClass + { + [contextClassMapping setObject: currentClass forKey: moduleName]; - NSLog (@" registered class '%@' as handler of '%@' contexts", - NSStringFromClass (currentClass), moduleName); - } + NSLog (@" registered class '%@' as handler of '%@' contexts", + NSStringFromClass (currentClass), moduleName); + } } MAPIStoreFallbackContextK = [MAPIStoreFallbackContext class]; } + (struct mapistore_contexts_list *) listAllContextsForUser: (NSString *) userName - withIndexing: (struct indexing_context *) indexing + withIndexing: (struct indexing_context *) indexing inMemCtx: (TALLOC_CTX *) memCtx { struct mapistore_contexts_list *list, *current; @@ -121,7 +120,7 @@ static NSMutableDictionary *contextClassMapping; { currentClass = [classes objectAtIndex: count]; current = [currentClass listContextsForUser: userName - withIndexing: indexing + withIndexing: indexing inMemCtx: memCtx]; if (current) DLIST_CONCATENATE(list, current, void); @@ -131,7 +130,7 @@ static NSMutableDictionary *contextClassMapping; } + (struct mapistore_contexts_list *) listContextsForUser: (NSString *) userName - withIndexing: (struct indexing_context *) indexing + withIndexing: (struct indexing_context *) indexing inMemCtx: (TALLOC_CTX *) memCtx { return NULL; @@ -224,8 +223,6 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) NSURL *baseURL; int rc = MAPISTORE_ERR_NOT_FOUND; - //NSLog (@"METHOD '%s' (%d) -- uri: '%s'", __FUNCTION__, __LINE__, newUri); - context = nil; baseURL = CompleteURLFromMapistoreURI (newUri); @@ -297,13 +294,6 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) ASSIGN (userContext, [MAPIStoreUserContext userContextWithUsername: username andTDBIndexing: indexing]); - -#if 0 - mapistore_mgmt_backend_register_user (newConnInfo, - "SOGo", - [username UTF8String]); -#endif - connInfo = newConnInfo; username = [NSString stringWithUTF8String: newConnInfo->username]; ASSIGN (activeUser, [SOGoUser userWithLogin: username]); @@ -321,12 +311,6 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) - (void) dealloc { -#if 0 - mapistore_mgmt_backend_unregister_user ([self connectionInfo], "SOGo", - [[userContext username] - UTF8String]); -#endif - [contextUrl release]; [userContext release]; [containersBag release]; @@ -354,27 +338,16 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) return activeUser; } -// - (void) logRestriction: (struct mapi_SRestriction *) res -// withState: (MAPIRestrictionState) state -// { -// NSString *resStr; - -// resStr = MAPIStringForRestriction (res); - -// [self logWithFormat: @"%@ --> %@", resStr, MAPIStringForRestrictionState (state)]; -// } - - (int) getPath: (char **) path ofFMID: (uint64_t) fmid inMemCtx: (TALLOC_CTX *) memCtx { int rc; NSString *objectURL, *url; - // TDB_DATA key, dbuf; url = [contextUrl absoluteString]; // FIXME transform percent escapes but not for user part of the url - //stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; + //[xxxx stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; objectURL = [[userContext mapping] urlFromID: fmid]; if (objectURL) { @@ -395,14 +368,6 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) else { [self errorWithFormat: @"%s: you should *never* get here", __PRETTY_FUNCTION__]; - // /* attempt to populate our mapping dict with data from indexing.tdb */ - // key.dptr = (unsigned char *) talloc_asprintf (memCtx, "0x%.16llx", - // (long long unsigned int )fmid); - // key.dsize = strlen ((const char *) key.dptr); - - // dbuf = tdb_fetch (memCtx->indexing_list->index_ctx->tdb, key); - // talloc_free (key.dptr); - // uri = talloc_strndup (memCtx, (const char *)dbuf.dptr, dbuf.dsize); *path = NULL; rc = MAPISTORE_SUCCESS; } @@ -487,7 +452,7 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) /* utils */ - (NSString *) extractChildNameFromURL: (NSString *) objectURL - andFolderURLAt: (NSString **) folderURL; + andFolderURLAt: (NSString **) folderURL; { NSString *childKey; NSRange lastSlash; @@ -496,15 +461,15 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) if ([objectURL hasSuffix: @"/"]) objectURL = [objectURL substringToIndex: [objectURL length] - 2]; lastSlash = [objectURL rangeOfString: @"/" - options: NSBackwardsSearch]; + options: NSBackwardsSearch]; if (lastSlash.location != NSNotFound) { slashPtr = NSMaxRange (lastSlash); childKey = [objectURL substringFromIndex: slashPtr]; if ([childKey length] == 0) - childKey = nil; + childKey = nil; if (folderURL) - *folderURL = [objectURL substringToIndex: slashPtr]; + *folderURL = [objectURL substringToIndex: slashPtr]; } else childKey = nil; @@ -530,8 +495,6 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) mappingId = [mapping idFromURL: childURL]; if (mappingId == NSNotFound) { - //[self warnWithFormat: @"no id exist yet for '%@', requesting one...", - // childURL]; mapistore_indexing_get_new_folderID (connInfo->mstore_ctx, &mappingId); [mapping registerURL: childURL withID: mappingId]; contextId = 0; @@ -594,15 +557,14 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) memCtx = talloc_zero(NULL, TALLOC_CTX); newFMIDs = [NSMutableArray arrayWithCapacity: max]; - + if (mapistore_indexing_get_new_folderIDs (connInfo->mstore_ctx, memCtx, max, &numbers) != MAPISTORE_SUCCESS || numbers->cValues != max) abort (); for (count = 0; count < max; count++) { - newNumber - = [NSString stringWithUnsignedLongLong: numbers->lpui8[count]]; + newNumber = [NSString stringWithUnsignedLongLong: numbers->lpui8[count]]; [newFMIDs addObject: newNumber]; } From 68ae978b1392cfa5c77d67c14fb34b818beb68e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 14 Jul 2015 17:17:51 +0200 Subject: [PATCH 09/20] oc-mail: activate user context needed Before creating new sogo objects --- OpenChange/MAPIStoreCalendarMessage.m | 1 + OpenChange/MAPIStoreMailMessage.m | 2 ++ OpenChange/MAPIStoreMailVolatileMessage.m | 1 + 3 files changed, 4 insertions(+) diff --git a/OpenChange/MAPIStoreCalendarMessage.m b/OpenChange/MAPIStoreCalendarMessage.m index 9e2952102..79a11e58b 100644 --- a/OpenChange/MAPIStoreCalendarMessage.m +++ b/OpenChange/MAPIStoreCalendarMessage.m @@ -494,6 +494,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK; folder = [sogoObject container]; /* reinstantiate the old sogo object and attach it to self */ + [[self userContext] activate]; woContext = [[self userContext] woContext]; if (isNew) { diff --git a/OpenChange/MAPIStoreMailMessage.m b/OpenChange/MAPIStoreMailMessage.m index 77ab5be40..37d4fc420 100644 --- a/OpenChange/MAPIStoreMailMessage.m +++ b/OpenChange/MAPIStoreMailMessage.m @@ -1614,6 +1614,8 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) max = [keyParts count]; if (max > 0) { + [[self userContext] activate]; + currentPart = [sogoObject lookupName: [keyParts objectAtIndex: 0] inContext: nil acquire: NO]; diff --git a/OpenChange/MAPIStoreMailVolatileMessage.m b/OpenChange/MAPIStoreMailVolatileMessage.m index 33c803173..ca9f1c42e 100644 --- a/OpenChange/MAPIStoreMailVolatileMessage.m +++ b/OpenChange/MAPIStoreMailVolatileMessage.m @@ -1077,6 +1077,7 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS dd = [activeUser domainDefaults]; from = [[activeUser allEmails] objectAtIndex: 0]; + [[self userContext] activate]; woContext = [[self userContext] woContext]; authenticator = [sogoObject authenticatorInContext: woContext]; error = [[SOGoMailer mailerWithDomainDefaults: dd] From 393b6d51280b687a5283112b7a3305d8afa87265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 14 Jul 2015 17:19:55 +0200 Subject: [PATCH 10/20] oc-folder: simplify lookupFolder method use activate and no need to set wocontext, it's already set properly --- OpenChange/MAPIStoreFolder.m | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/OpenChange/MAPIStoreFolder.m b/OpenChange/MAPIStoreFolder.m index 240652e5c..0e2652ff8 100644 --- a/OpenChange/MAPIStoreFolder.m +++ b/OpenChange/MAPIStoreFolder.m @@ -231,30 +231,19 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe - (id) lookupFolder: (NSString *) folderKey { - MAPIStoreFolder *childFolder; + MAPIStoreFolder *childFolder = nil; SOGoFolder *sogoFolder; - WOContext *woContext; - childFolder = nil; if ([[self folderKeys] containsObject: folderKey]) { - woContext = [[self userContext] woContext]; - /* We activate the user for the context using the root folder - context as there are times where the active user is not - matching with the one stored in the application context - and SOGo object is storing cached data with the wrong user */ - [[self userContext] activateWithUser: [woContext activeUser]]; - sogoFolder = [sogoObject lookupName: folderKey inContext: woContext + [[self userContext] activate]; + sogoFolder = [sogoObject lookupName: folderKey + inContext: nil acquire: NO]; if (sogoFolder && ![sogoFolder isKindOfClass: NSExceptionK]) - { - [sogoFolder setContext: woContext]; - childFolder = [isa mapiStoreObjectWithSOGoObject: sogoFolder - inContainer: self]; - } + childFolder = [isa mapiStoreObjectWithSOGoObject: sogoFolder + inContainer: self]; } - else - childFolder = nil; return childFolder; } From 10bc15d41d8a29e9c515f7b12145673cd7e39259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 14 Jul 2015 17:41:33 +0200 Subject: [PATCH 11/20] oc-folder: activate user context when needed Before creating sogo objects --- OpenChange/MAPIStoreContactsFolder.m | 7 +++++-- OpenChange/MAPIStoreDBFolder.m | 22 +++++++++++++-------- OpenChange/MAPIStoreFolder.m | 29 +++++++++++++++++----------- OpenChange/MAPIStoreMailFolder.m | 27 +++++++++++++------------- OpenChange/MAPIStoreTasksFolder.m | 6 ++++-- 5 files changed, 55 insertions(+), 36 deletions(-) diff --git a/OpenChange/MAPIStoreContactsFolder.m b/OpenChange/MAPIStoreContactsFolder.m index fc0c35840..823f467cd 100644 --- a/OpenChange/MAPIStoreContactsFolder.m +++ b/OpenChange/MAPIStoreContactsFolder.m @@ -28,6 +28,7 @@ #import #import "MAPIApplication.h" +#import "MAPIStoreUserContext.h" #import "MAPIStoreContactsContext.h" #import "MAPIStoreContactsMessage.h" #import "MAPIStoreContactsMessageTable.h" @@ -58,10 +59,12 @@ SOGoContactGCSEntry *newEntry; NSString *name; + [[self userContext] activate]; + name = [NSString stringWithFormat: @"%@.vcf", [SOGoObject globallyUniqueObjectId]]; newEntry = [SOGoContactGCSEntry objectWithName: name - inContainer: sogoObject]; + inContainer: sogoObject]; [newEntry setIsNew: YES]; newMessage = [MAPIStoreContactsMessage mapiStoreObjectWithSOGoObject: newEntry inContainer: self]; @@ -100,7 +103,7 @@ rights |= RightsReadItems; if (rights != 0) rights |= RoleNone; /* actually "folder visible" */ - + return rights; } diff --git a/OpenChange/MAPIStoreDBFolder.m b/OpenChange/MAPIStoreDBFolder.m index 4dd7fbfd9..d1734dc7c 100644 --- a/OpenChange/MAPIStoreDBFolder.m +++ b/OpenChange/MAPIStoreDBFolder.m @@ -161,7 +161,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; else [dbFolder changePathTo: [NSString stringWithFormat: @"/fallback/%@", pathComponent] intoNewContainer: nil]; - + mapping = [self mapping]; if (targetFolder) @@ -193,6 +193,8 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; SOGoMAPIDBMessage *fsObject; NSString *newKey; + [[self userContext] activate]; + newKey = [NSString stringWithFormat: @"%@.plist", [SOGoObject globallyUniqueObjectId]]; fsObject = [SOGoMAPIDBMessage objectWithName: newKey @@ -214,10 +216,13 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; ownerUser = [[self userContext] sogoUser]; if ([[context activeUser] isEqual: ownerUser] || [self subscriberCanReadMessages]) - keys = [(SOGoCacheGCSFolder *) sogoObject childKeysOfType: MAPIMessageCacheObject - includeDeleted: NO - matchingQualifier: qualifier - andSortOrderings: sortOrderings]; + { + [[self userContext] activate]; + keys = [(SOGoCacheGCSFolder *) sogoObject childKeysOfType: MAPIMessageCacheObject + includeDeleted: NO + matchingQualifier: qualifier + andSortOrderings: sortOrderings]; + } else keys = [NSArray array]; @@ -227,6 +232,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; - (NSArray *) folderKeysMatchingQualifier: (EOQualifier *) qualifier andSortOrderings: (NSArray *) sortOrderings { + [[self userContext] activate]; return [dbFolder childKeysOfType: MAPIFolderCacheObject includeDeleted: NO matchingQualifier: qualifier @@ -262,7 +268,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; if ([date laterDate: fileDate] == fileDate) { //[self logWithFormat: @"current date: %@", date]; - + date = fileDate; } } @@ -326,7 +332,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; rights |= RightsFolderContact; if (rights != 0) rights |= RoleNone; /* actually "folder visible" */ - + return rights; } @@ -361,7 +367,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; subscribed to read an open contained messages in order to enable them to find the "LocalFreebusy" message */ [sogoObject reloadIfNeeded]; - + displayName = [[sogoObject properties] objectForKey: MAPIPropertyKey (PidTagDisplayName)]; diff --git a/OpenChange/MAPIStoreFolder.m b/OpenChange/MAPIStoreFolder.m index 0e2652ff8..15ca68fc0 100644 --- a/OpenChange/MAPIStoreFolder.m +++ b/OpenChange/MAPIStoreFolder.m @@ -120,6 +120,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe folderName = [folderURL host]; userContext = [self userContext]; + [userContext activate]; [userContext ensureFolderTableExists]; ASSIGN (dbFolder, @@ -274,6 +275,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe if (messageKey) { + [[self userContext] activate]; msgObject = [sogoObject lookupName: messageKey inContext: nil acquire: NO]; @@ -303,6 +305,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe if (messageKey) { + [[self userContext] activate]; if ([[self faiMessageKeys] containsObject: messageKey]) { msgObject = [dbFolder lookupName: messageKey @@ -535,6 +538,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe context = [self context]; ownerUser = [[self userContext] sogoUser]; + [[self userContext] activate]; if ([[context activeUser] isEqual: ownerUser] || (!isAssociated && [self subscriberCanCreateMessages])) @@ -747,9 +751,9 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe } else rc = MAPISTORE_ERR_DENIED; - + //talloc_free (memCtx); - + return rc; } @@ -920,7 +924,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe NSArray *newIDs; uint64_t idNbr; bool softDeleted; - + baseURL = [self url]; mapping = [self mapping]; @@ -1094,6 +1098,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe - (NSArray *) faiMessageKeysMatchingQualifier: (EOQualifier *) qualifier andSortOrderings: (NSArray *) sortOrderings { + [[self userContext] activate]; return [dbFolder childKeysOfType: MAPIFAICacheObject includeDeleted: NO matchingQualifier: qualifier @@ -1202,7 +1207,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe /* Possible values are: - + 0x00000001 Modify 0x00000002 Read 0x00000004 Delete @@ -1233,7 +1238,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe access |= 0x10; if (userIsOwner) access |= 0x20; - + *data = MAPILongValue (memCtx, access); return MAPISTORE_SUCCESS; @@ -1262,7 +1267,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe rights |= RightsCreateSubfolders; if (userIsOwner) rights |= RightsFolderOwner | RightsFolderContact; - + *data = MAPILongValue (memCtx, rights); return MAPISTORE_SUCCESS; @@ -1298,7 +1303,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe inMemCtx: (TALLOC_CTX *) memCtx { *data = MAPIBoolValue (memCtx, [self supportsSubFolders] && [[self folderKeys] count] > 0); - + return MAPISTORE_SUCCESS; } @@ -1306,7 +1311,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe inMemCtx: (TALLOC_CTX *) memCtx { *data = MAPILongValue (memCtx, [[self folderKeys] count]); - + return MAPISTORE_SUCCESS; } @@ -1408,7 +1413,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe [dbObject setIsNew: YES]; newMessage = [MAPIStoreFAIMessageK mapiStoreObjectWithSOGoObject: dbObject inContainer: self]; - + return newMessage; } @@ -1417,6 +1422,8 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe MAPIStoreMessage *newMessage; WOContext *woContext; + [[self userContext] activate]; + if (isAssociated) newMessage = [self _createAssociatedMessage]; else @@ -1616,7 +1623,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe permissionUser = nil; permissionRoles = nil; - + if (currentPermission->PermissionDataFlags == ROW_ADD) isAdd = YES; else if (currentPermission->PermissionDataFlags == ROW_MODIFY) @@ -1767,7 +1774,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe { [self subclassResponsibility: _cmd]; - return nil; + return nil; } - (NSArray *) getDeletedKeysFromChangeNumber: (uint64_t) changeNum diff --git a/OpenChange/MAPIStoreMailFolder.m b/OpenChange/MAPIStoreMailFolder.m index 993b1c136..d4defea1e 100644 --- a/OpenChange/MAPIStoreMailFolder.m +++ b/OpenChange/MAPIStoreMailFolder.m @@ -250,7 +250,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK; inMemCtx: (TALLOC_CTX *) memCtx { *data = [@"IPF.Note" asUnicodeInMemCtx: memCtx]; - + return MAPISTORE_SUCCESS; } @@ -262,7 +262,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK; if (!nonDeletedQualifier) { deletedQualifier - = [[EOKeyValueQualifier alloc] + = [[EOKeyValueQualifier alloc] initWithKey: @"FLAGS" operatorSelector: EOQualifierOperatorContains value: [NSArray arrayWithObject: @"Deleted"]]; @@ -375,7 +375,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK; [self warnWithFormat: @"qualifier is only used for folders with name"]; if (sortOrderings) [self errorWithFormat: @"sort orderings are not used for folders"]; - + /* FIXME: Flush any cache before retrieving the hierarchy, this slows things down but it is safer */ if (!qualifier) @@ -469,7 +469,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK; } else supportsSubFolders = YES; - + return supportsSubFolders; } @@ -638,7 +638,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data) fetchResults = [fetchResults sortedArrayUsingFunction: _compareFetchResultsByMODSEQ context: NULL]; - + for (count = 0; count < max; count++) { result = [fetchResults objectAtIndex: count]; @@ -948,7 +948,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data) } } [self _setChangeKey: changeKey forMessageEntry: messageEntry]; - + [versionsMessage save]; } @@ -1175,7 +1175,7 @@ _parseIMAPRange (const unichar *uniString, NSArray **UIDsP) count++; } *UIDsP = UIDs; - + return count; } @@ -1453,7 +1453,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP) [uids addObject: [self messageUIDFromMessageKey: childKey]]; } - result = [client copyUids: uids + result = [client copyUids: uids toFolder: newFolderIMAPName]; if ([[result objectForKey: @"result"] boolValue]) { @@ -1492,9 +1492,10 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP) { SOGoCacheObject *childObject; - childObject = [SOGoCacheObject objectWithName: [SOGoCacheObject - globallyUniqueObjectId] - inContainer: sogoObject]; + [[[self context] userContext] activate]; + + childObject = [SOGoCacheObject objectWithName: [SOGoCacheObject globallyUniqueObjectId] + inContainer: sogoObject]; return [MAPIStoreMailVolatileMessage mapiStoreObjectWithSOGoObject: childObject inContainer: self]; @@ -1570,7 +1571,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP) rights |= RoleNone; /* actually "folder visible" */ // [self logWithFormat: @"rights for roles (%@) = %.8x", roles, rights]; - + return rights; } @@ -1621,7 +1622,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP) } } } - + client = [[(SOGoMailFolder *) sogoObject imap4Connection] client]; [client select: [sogoObject absoluteImap4Name]]; response = [client fetchUids: [keyAssoc allKeys] diff --git a/OpenChange/MAPIStoreTasksFolder.m b/OpenChange/MAPIStoreTasksFolder.m index 7b8990fc8..d777b8e8b 100644 --- a/OpenChange/MAPIStoreTasksFolder.m +++ b/OpenChange/MAPIStoreTasksFolder.m @@ -31,6 +31,7 @@ #import #import "MAPIApplication.h" +#import "MAPIStoreUserContext.h" #import "MAPIStoreTasksContext.h" #import "MAPIStoreTasksMessage.h" #import "MAPIStoreTasksMessageTable.h" @@ -61,6 +62,7 @@ SOGoTaskObject *newEntry; NSString *name; + [[[self context] userContext] activate]; name = [NSString stringWithFormat: @"%@.ics", [SOGoObject globallyUniqueObjectId]]; newEntry = [SOGoTaskObject objectWithName: name @@ -69,7 +71,7 @@ newMessage = [MAPIStoreTasksMessage mapiStoreObjectWithSOGoObject: newEntry inContainer: self]; - + return newMessage; } @@ -116,7 +118,7 @@ rights |= RightsReadItems; if (rights != 0) rights |= RoleNone; /* actually "folder visible" */ - + return rights; } From c9f2fe2f85ace90dca4065fc2f240962b83b201b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 14 Jul 2015 17:44:17 +0200 Subject: [PATCH 12/20] oc-folder: user context activation on rootFolders Several fixes related with userContext activation --- OpenChange/MAPIStoreUserContext.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/OpenChange/MAPIStoreUserContext.m b/OpenChange/MAPIStoreUserContext.m index d0ffbee66..bc91f56b0 100644 --- a/OpenChange/MAPIStoreUserContext.m +++ b/OpenChange/MAPIStoreUserContext.m @@ -217,7 +217,7 @@ static NSMapTable *contextsTable = nil; if (!userFolder) { userFolder = [SOGoUserFolder objectWithName: username - inContainer: MAPIApp]; + inContainer: nil]; [userFolder retain]; } @@ -235,7 +235,8 @@ static NSMapTable *contextsTable = nil; if (!rootFolders) { rootFolders = [NSMutableDictionary new]; - [self userFolder]; + [self activate]; + [self userFolder]; // force lazy initialization [woContext setClientObject: userFolder]; /* Calendar */ @@ -260,10 +261,10 @@ static NSMapTable *contextsTable = nil; acquire: NO]; [containersBag addObject: accountsFolder]; [woContext setClientObject: accountsFolder]; + currentFolder = [accountsFolder lookupName: @"0" inContext: woContext acquire: NO]; - [rootFolders setObject: currentFolder forKey: @"mail"]; connection = [currentFolder imap4Connection]; @@ -271,8 +272,7 @@ static NSMapTable *contextsTable = nil; /* ensure the folder cache is filled */ [currentFolder toManyRelationshipKeysWithNamespaces: YES]; - hierarchy = [connection - cachedHierarchyResultsForURL: [currentFolder imap4URL]]; + hierarchy = [connection cachedHierarchyResultsForURL: [currentFolder imap4URL]]; flags = [[hierarchy objectForKey: @"list"] objectForKey: @"/INBOX"]; inboxHasNoInferiors = [flags containsObject: @"noinferiors"]; } @@ -341,7 +341,7 @@ static NSMapTable *contextsTable = nil; cm = [GCSChannelManager defaultChannelManager]; channel = [cm acquireOpenChannelForURL: folderTableURL]; - + /* FIXME: make use of [EOChannelAdaptor describeTableNames] instead */ tableName = [[folderTableURL path] lastPathComponent]; if ([channel evaluateExpressionX: @@ -358,7 +358,7 @@ static NSMapTable *contextsTable = nil; [channel cancelFetch]; - [cm releaseChannel: channel]; + [cm releaseChannel: channel]; } /* SOGo context objects */ From 1336a1d5a8c2751d88ba9fad17e2652494daa810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Fri, 17 Apr 2015 13:55:39 +0200 Subject: [PATCH 13/20] oc: remove useless mapistore_indexing call [mapping registerURL ...] will insert the mappingId in indexing database there is no need to call, again, mapistore_indexing_record_add_mid --- OpenChange/MAPIStoreContext.m | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/OpenChange/MAPIStoreContext.m b/OpenChange/MAPIStoreContext.m index 2a0e1c58e..6a9c43da3 100644 --- a/OpenChange/MAPIStoreContext.m +++ b/OpenChange/MAPIStoreContext.m @@ -480,11 +480,9 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) - (uint64_t) idForObjectWithKey: (NSString *) key inFolderURL: (NSString *) folderURL { - NSString *childURL, *owner; + NSString *childURL; MAPIStoreMapping *mapping; uint64_t mappingId; - uint32_t contextId; - void *rootObject; if (key) childURL = [NSString stringWithFormat: @"%@%@", folderURL, @@ -495,15 +493,9 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) mappingId = [mapping idFromURL: childURL]; if (mappingId == NSNotFound) { + [self logWithFormat: @"No id exist yet for '%@', requesting one", childURL]; mapistore_indexing_get_new_folderID (connInfo->mstore_ctx, &mappingId); [mapping registerURL: childURL withID: mappingId]; - contextId = 0; - - mapistore_search_context_by_uri (connInfo->mstore_ctx, [folderURL UTF8String], - &contextId, &rootObject); - owner = [userContext username]; - mapistore_indexing_record_add_mid (connInfo->mstore_ctx, contextId, - [owner UTF8String], mappingId); } return mappingId; From 7bde181d3f7d83895fbb727632fa8300300e5c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 21 Jul 2015 19:45:03 +0200 Subject: [PATCH 14/20] oc: check ret value get_new_folderID On [MAPIStoreContext idForObjectWithKey: key inFolderUrl: url] check the ret value of mapistore_indexing_get_new_folderID. This should never happen (oh my...) but if this happens it will be reported --- OpenChange/MAPIStoreContext.m | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/OpenChange/MAPIStoreContext.m b/OpenChange/MAPIStoreContext.m index 6a9c43da3..e16b23d9b 100644 --- a/OpenChange/MAPIStoreContext.m +++ b/OpenChange/MAPIStoreContext.m @@ -483,6 +483,7 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) NSString *childURL; MAPIStoreMapping *mapping; uint64_t mappingId; + enum mapistore_error ret; if (key) childURL = [NSString stringWithFormat: @"%@%@", folderURL, @@ -494,8 +495,12 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) if (mappingId == NSNotFound) { [self logWithFormat: @"No id exist yet for '%@', requesting one", childURL]; - mapistore_indexing_get_new_folderID (connInfo->mstore_ctx, &mappingId); - [mapping registerURL: childURL withID: mappingId]; + ret = mapistore_indexing_get_new_folderID (connInfo->mstore_ctx, &mappingId); + if (ret == MAPI_E_SUCCESS) + [mapping registerURL: childURL withID: mappingId]; + else + [self errorWithFormat: @"Error trying to get new folder id (%d): %s", + ret, mapistore_errstr (ret)]; } return mappingId; From c5b5a4243db989149590ea223878dda00d5c6833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Sat, 25 Jul 2015 09:56:21 +0200 Subject: [PATCH 15/20] oc: Fix compilation warning in MAPIStoreContext --- OpenChange/MAPIStoreContext.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenChange/MAPIStoreContext.m b/OpenChange/MAPIStoreContext.m index e16b23d9b..9b8e0a37a 100644 --- a/OpenChange/MAPIStoreContext.m +++ b/OpenChange/MAPIStoreContext.m @@ -496,7 +496,7 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) { [self logWithFormat: @"No id exist yet for '%@', requesting one", childURL]; ret = mapistore_indexing_get_new_folderID (connInfo->mstore_ctx, &mappingId); - if (ret == MAPI_E_SUCCESS) + if (ret == MAPISTORE_SUCCESS) [mapping registerURL: childURL withID: mappingId]; else [self errorWithFormat: @"Error trying to get new folder id (%d): %s", From 10ac445f7cc7c1b361fee583696e7a16d4e2a29b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Mon, 27 Jul 2015 11:18:14 +0200 Subject: [PATCH 16/20] oc-mail: Fallback to PidTagSubject unicode when creating new mail Some clients such as OpenChange client does not send the following properties PidTagNormalizedSubject or PidTagSubjectPrefix as suggested by [MS-OXCMAIL]. --- OpenChange/MAPIStoreMailVolatileMessage.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/OpenChange/MAPIStoreMailVolatileMessage.m b/OpenChange/MAPIStoreMailVolatileMessage.m index ca9f1c42e..f2581a54a 100644 --- a/OpenChange/MAPIStoreMailVolatileMessage.m +++ b/OpenChange/MAPIStoreMailVolatileMessage.m @@ -771,6 +771,12 @@ FillMessageHeadersFromProperties (NGMutableHashMap *headers, subjectData = [mailProperties objectForKey: MAPIPropertyKey (PR_NORMALIZED_SUBJECT_UNICODE)]; if (subjectData) [subject appendString: subjectData]; + if ([subject length] == 0) + { + subjectData = [mailProperties objectForKey: MAPIPropertyKey (PR_SUBJECT_UNICODE)]; + if (subjectData) + [subject appendString: subjectData]; + } [headers setObject: [subject asQPSubjectString: @"utf-8"] forKey: @"subject"]; messageId = [mailProperties objectForKey: MAPIPropertyKey (PR_INTERNET_MESSAGE_ID_UNICODE)]; From 3dc8cc78eaa5e60297405a5848ae4e269fdbcd14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 28 Jul 2015 12:37:33 +0200 Subject: [PATCH 17/20] Revert merge from inverse https://github.com/Zentyal/sogo/pull/150 Because the login on web with the use of outlook is broken after including the DomainLessLogin feature --- ActiveSync/NSData+ActiveSync.m | 28 +-- ActiveSync/NSString+ActiveSync.m | 99 +--------- ActiveSync/SOGoActiveSyncDispatcher+Sync.m | 21 +- ActiveSync/SOGoActiveSyncDispatcher.m | 181 ++++-------------- ActiveSync/SOGoMailObject+ActiveSync.m | 46 +---- ActiveSync/iCalEvent+ActiveSync.m | 26 +-- Documentation/SOGoInstallationGuide.asciidoc | 24 +-- NEWS | 20 -- Scripts/sogo-systemd.conf | 2 - ...sh => sql-update-2.1.17_to_2.3.0-mysql.sh} | 1 - ...2.3.0.sh => sql-update-2.1.17_to_2.3.0.sh} | 3 +- .../Appointments/SOGoAppointmentFolder.m | 2 - .../Appointments/SOGoAppointmentObject.m | 23 +-- SoObjects/Appointments/iCalAlarm+SOGo.h | 6 +- SoObjects/Appointments/iCalAlarm+SOGo.m | 5 +- SoObjects/Mailer/SOGoMailObject.m | 2 + SoObjects/SOGo/NSString+Utilities.h | 5 +- SoObjects/SOGo/NSString+Utilities.m | 23 +-- SoObjects/SOGo/SOGoDefaults.plist | 7 +- SoObjects/SOGo/SOGoDomainDefaults.h | 2 + SoObjects/SOGo/SOGoDomainDefaults.m | 5 + SoObjects/SOGo/SOGoUser.m | 26 +-- SoObjects/SOGo/SOGoUserDefaults.m | 2 +- SoObjects/SOGo/SOGoUserManager.m | 153 +++++---------- Tools/SOGoToolBackup.m | 47 ++--- Tools/SOGoToolRestore.h | 1 - Tools/SOGoToolRestore.m | 30 +-- Tools/sogo-tool.m | 4 +- UI/Contacts/UIxContactEditor.m | 7 +- UI/Contacts/UIxContactView.m | 39 ++-- UI/Contacts/UIxListEditor.m | 9 +- UI/MainUI/SOGoUserHomePage.m | 2 +- UI/PreferencesUI/UIxPreferences.h | 1 + UI/PreferencesUI/UIxPreferences.m | 11 ++ UI/SOGoUI/UIxComponent.m | 4 +- .../ContactsUI/UIxContactFoldersView.wox | 10 +- UI/Templates/UIxJSClose.wox | 2 +- UI/WebServerResources/ContactsUI.js | 82 ++++---- .../JavascriptAPIExtensions.js | 41 +--- UI/WebServerResources/SchedulerUI.js | 45 ++--- packaging/rhel/sogo.spec | 4 - 41 files changed, 284 insertions(+), 767 deletions(-) delete mode 100644 Scripts/sogo-systemd.conf rename Scripts/{sql-update-2.2.17_to_2.3.0-mysql.sh => sql-update-2.1.17_to_2.3.0-mysql.sh} (95%) rename Scripts/{sql-update-2.2.17_to_2.3.0.sh => sql-update-2.1.17_to_2.3.0.sh} (89%) diff --git a/ActiveSync/NSData+ActiveSync.m b/ActiveSync/NSData+ActiveSync.m index cf059e66f..dd925c715 100644 --- a/ActiveSync/NSData+ActiveSync.m +++ b/ActiveSync/NSData+ActiveSync.m @@ -66,33 +66,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // - (NSString *) activeSyncRepresentationInContext: (WOContext *) context { - NSString *tmp, *s; - unichar *buf, *start, c; - int len, i, j; - - tmp = [self stringByEncodingBase64] ; - - len = [tmp length]; - - start = buf = (unichar *)malloc(len*sizeof(unichar)); - [tmp getCharacters: buf range: NSMakeRange(0, len)]; - - for (i = 0, j = 0; i < len; i++) - { - c = *buf; - - if (!(c == 0xA)) - { - *(start+j) = c; - j++; - } - - buf++; - } - - s = [[NSString alloc] initWithCharactersNoCopy: start length: j freeWhenDone: YES]; - - return AUTORELEASE(s); + return [[self stringByEncodingBase64] stringByReplacingString: @"\n" withString: @""]; } - (NSData *) wbxml2xml diff --git a/ActiveSync/NSString+ActiveSync.m b/ActiveSync/NSString+ActiveSync.m index 1564b7482..a66f38252 100644 --- a/ActiveSync/NSString+ActiveSync.m +++ b/ActiveSync/NSString+ActiveSync.m @@ -46,99 +46,6 @@ static NSArray *easCommandParameters = nil; @implementation NSString (ActiveSync) -// -// This is a copy from NSString+XMLEscaping.m from SOPE. -// The difference here is that we use wchar_t instead of unichar. -// This is needed to get the rigth numeric character reference. -// e.g. SMILING FACE WITH OPEN MOUTH -// ok: wchar_t -> 😃 wrong: unichar -> � � -// -// We avoir naming it like the one in SOPE since if the ActiveSync -// bundle is loaded, it'll overwrite the one provided by SOPE. -// -- (NSString *) _stringByEscapingXMLStringUsingCharacters { - register unsigned i, len, j; - register wchar_t *buf; - const wchar_t *chars; - unsigned escapeCount; - - if ([self length] == 0) return @""; - - NSData *data = [self dataUsingEncoding:NSUTF32StringEncoding]; - chars = [data bytes]; - len = [data length]/4; - - /* check for characters to escape ... */ - for (i = 0, escapeCount = 0; i < len; i++) { - switch (chars[i]) { - case '&': case '"': case '<': case '>': case '\r': - escapeCount++; - break; - default: - if (chars[i] > 127) - escapeCount++; - break; - } - } - if (escapeCount == 0 ) { - /* nothing to escape ... */ - return [[self copy] autorelease]; - } - - buf = calloc((len + 5) + (escapeCount * 16), sizeof(wchar_t)); - for (i = 0, j = 0; i < len; i++) { - switch (chars[i]) { - /* escape special chars */ - case '\r': - buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '1'; j++; - buf[j] = '3'; j++; buf[j] = ';'; j++; - break; - case '&': - buf[j] = '&'; j++; buf[j] = 'a'; j++; buf[j] = 'm'; j++; - buf[j] = 'p'; j++; buf[j] = ';'; j++; - break; - case '"': - buf[j] = '&'; j++; buf[j] = 'q'; j++; buf[j] = 'u'; j++; - buf[j] = 'o'; j++; buf[j] = 't'; j++; buf[j] = ';'; j++; - break; - case '<': - buf[j] = '&'; j++; buf[j] = 'l'; j++; buf[j] = 't'; j++; - buf[j] = ';'; j++; - break; - case '>': - buf[j] = '&'; j++; buf[j] = 'g'; j++; buf[j] = 't'; j++; - buf[j] = ';'; j++; - break; - - default: - /* escape big chars */ - if (chars[i] > 127) { - unsigned char nbuf[32]; - unsigned int k; - - sprintf((char *)nbuf, "&#%i;", (int)chars[i]); - for (k = 0; nbuf[k] != '\0'; k++) { - buf[j] = nbuf[k]; - j++; - } - } - else if (chars[i] == 0x9 || chars[i] == 0xA || chars[i] == 0xD || chars[i] >= 0x20) { // ignore any unsupported control character - /* nothing to escape */ - buf[j] = chars[i]; - j++; - } - break; - } - } - - self = [[NSString alloc] initWithBytesNoCopy: buf - length: (j*sizeof(wchar_t)) - encoding: NSUTF32StringEncoding - freeWhenDone: YES]; - - return [self autorelease]; -} - - (NSString *) sanitizedServerIdWithType: (SOGoMicrosoftActiveSyncFolderType) folderType { if (folderType == ActiveSyncEventFolder) @@ -158,7 +65,11 @@ static NSArray *easCommandParameters = nil; - (NSString *) activeSyncRepresentationInContext: (WOContext *) context { - return [self _stringByEscapingXMLStringUsingCharacters]; + NSString *s; + + s = [self safeString]; + + return [s stringByEscapingHTMLString]; } - (int) activeSyncFolderType diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index 47ec81990..e0561ff31 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -453,27 +453,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType inBuffer: (NSMutableString *) theBuffer { - - id aDelete, sogoObject, value; NSArray *deletions; NSString *serverId; - BOOL deletesAsMoves, useTrash; + id aDelete, sogoObject; int i; deletions = (id)[theDocumentElement getElementsByTagName: @"Delete"]; if ([deletions count]) { - // From the documention, if DeletesAsMoves is missing, we must assume it's a YES. - // See https://msdn.microsoft.com/en-us/library/gg675480(v=exchg.80).aspx for all details. - value = [theDocumentElement getElementsByTagName: @"DeletesAsMoves"]; - deletesAsMoves = YES; - useTrash = YES; - - if ([value count] && [[[value lastObject] textValue] length]) - deletesAsMoves = [[[value lastObject] textValue] boolValue]; - for (i = 0; i < [deletions count]; i++) { aDelete = [deletions objectAtIndex: i]; @@ -485,13 +474,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. acquire: NO]; if (![sogoObject isKindOfClass: [NSException class]]) - { - // FIXME: handle errors here - if (deletesAsMoves) - [(SOGoMailFolder *)[sogoObject container] deleteUIDs: [NSArray arrayWithObjects: serverId, nil] useTrashFolder: &useTrash inContext: context]; - else - [sogoObject delete]; - } + [sogoObject delete]; [theBuffer appendString: @""]; [theBuffer appendFormat: @"%@", serverId]; diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index b107b94f0..c2f64786e 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -453,7 +453,7 @@ static BOOL debugOn = NO; [s appendString: @""]; [s appendFormat: @"%d", 1]; [s appendFormat: @"%@", syncKey]; - [s appendFormat: @"%@", [nameInContainer stringByEscapingURL]]; + [s appendFormat: @"%@", nameInContainer]; [s appendString: @""]; d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; @@ -1084,8 +1084,6 @@ static BOOL debugOn = NO; SOGoMailAccounts *accountsFolder; SOGoUserFolder *userFolder; SOGoMailObject *mailObject; - NSArray *partKeys; - int p; NSRange r1, r2; @@ -1105,14 +1103,7 @@ static BOOL debugOn = NO; acquire: NO]; mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO]; - - partKeys = [pathToPart componentsSeparatedByString: @"."]; - - currentBodyPart = [mailObject lookupImap4BodyPartKey: [partKeys objectAtIndex:0] inContext: context]; - for (p = 1; p < [partKeys count]; p++) - { - currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context]; - } + currentBodyPart = [mailObject lookupImap4BodyPartKey: pathToPart inContext: context]; [theResponse setHeader: [NSString stringWithFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]] forKey: @"Content-Type"]; @@ -1167,11 +1158,11 @@ static BOOL debugOn = NO; { collectionId = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"CollectionId"] lastObject] textValue]; realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; - + if (folderType == ActiveSyncMailFolder) - nameInCache = [NSString stringWithFormat: @"folder%@", realCollectionId]; + nameInCache = [NSString stringWithFormat: @"folder%@", realCollectionId]; else - nameInCache = collectionId; + nameInCache = collectionId; realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; @@ -1243,11 +1234,10 @@ static BOOL debugOn = NO; - (void) processItemOperations: (id ) theDocumentElement inResponse: (WOResponse *) theResponse { - NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *collectionId; + NSString *fileReference, *realCollectionId; NSMutableString *s; NSArray *fetchRequests; id aFetch; - NSData *d; int i; SOGoMicrosoftActiveSyncFolderType folderType; @@ -1257,6 +1247,8 @@ static BOOL debugOn = NO; [s appendString: @""]; [s appendString: @""]; [s appendString: @""]; + [s appendString: @"1"]; + [s appendString: @""]; fetchRequests = (id)[theDocumentElement getElementsByTagName: @"Fetch"]; @@ -1264,25 +1256,17 @@ static BOOL debugOn = NO; { NSMutableData *bytes, *parts; NSMutableArray *partLength; + NSData *d; bytes = [NSMutableData data]; parts = [NSMutableData data]; partLength = [NSMutableArray array]; - [s appendString: @"1"]; - [s appendString: @""]; - for (i = 0; i < [fetchRequests count]; i++) { aFetch = [fetchRequests objectAtIndex: i]; fileReference = [[[(id)[aFetch getElementsByTagName: @"FileReference"] lastObject] textValue] stringByUnescapingURL]; - collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue]; - - // its either a itemOperation to fetch an attachment or an email - if ([fileReference length]) - realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType]; - else - realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; + realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType]; if (folderType == ActiveSyncMailFolder) { @@ -1292,80 +1276,43 @@ static BOOL debugOn = NO; SOGoUserFolder *userFolder; SOGoMailObject *mailObject; - if ([fileReference length]) - { - // fetch attachment - NSRange r1, r2; - NSArray *partKeys; - int p; + NSRange r1, r2; - r1 = [realCollectionId rangeOfString: @"/"]; - r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)]; + r1 = [realCollectionId rangeOfString: @"/"]; + r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)]; - folderName = [realCollectionId substringToIndex: r1.location]; - messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)]; - pathToPart = [realCollectionId substringFromIndex: r2.location+1]; + folderName = [realCollectionId substringToIndex: r1.location]; + messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)]; + pathToPart = [realCollectionId substringFromIndex: r2.location+1]; - userFolder = [[context activeUser] homeFolderInContext: context]; - accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; - currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; - currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", folderName] - inContext: context - acquire: NO]; + currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", folderName] + inContext: context + acquire: NO]; - mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO]; + mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO]; + currentBodyPart = [mailObject lookupImap4BodyPartKey: pathToPart inContext: context]; - partKeys = [pathToPart componentsSeparatedByString: @"."]; + [s appendString: @""]; + [s appendString: @"1"]; + [s appendFormat: @"%@", [fileReference stringByEscapingURL]]; + [s appendString: @""]; - currentBodyPart = [mailObject lookupImap4BodyPartKey: [partKeys objectAtIndex:0] inContext: context]; - for (p = 1; p < [partKeys count]; p++) - { - currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context]; - } - - [s appendString: @""]; - [s appendString: @"1"]; - [s appendFormat: @"%@", [fileReference stringByEscapingURL]]; - [s appendString: @""]; + [s appendFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]]; - [s appendFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]]; - - if ([[theResponse headerForKey: @"Content-Type"] isEqualToString:@"application/vnd.ms-sync.multipart"]) - { - NSData *d; - d = [currentBodyPart fetchBLOB]; - - [s appendFormat: @"%d", i+1]; - [partLength addObject: [NSNumber numberWithInteger: [d length]]]; - [parts appendData: d]; - } - else - { - NSString *a; - a = [[currentBodyPart fetchBLOB] activeSyncRepresentationInContext: context]; - - [s appendFormat: @"0-%d", [a length]-1]; - [s appendFormat: @"%@", a]; - } + if ([[theResponse headerForKey: @"Content-Type"] isEqualToString:@"application/vnd.ms-sync.multipart"]) + { + [s appendFormat: @"%d", i+1]; + [partLength addObject: [NSNumber numberWithInteger: [[currentBodyPart fetchBLOB] length]]]; + [parts appendData:[currentBodyPart fetchBLOB]]; } else { - // fetch mail - realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; - serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue]; - bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue]; - [context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"]; - - currentCollection = [self collectionFromId: realCollectionId type: folderType]; - - mailObject = [currentCollection lookupName: serverId inContext: context acquire: NO]; - [s appendString: @""]; - [s appendString: @"1"]; - [s appendFormat: @"%@", collectionId]; - [s appendFormat: @"%@", serverId]; - [s appendString: @""]; - [s appendString: [mailObject activeSyncRepresentationInContext: context]]; + [s appendFormat: @"0-%d", [[[currentBodyPart fetchBLOB] activeSyncRepresentationInContext: context] length]-1]; + [s appendFormat: @"%@", [[currentBodyPart fetchBLOB] activeSyncRepresentationInContext: context]]; } [s appendString: @""]; @@ -1419,62 +1366,6 @@ static BOOL debugOn = NO; { [theResponse setContent: d]; } - } - else if ([theDocumentElement getElementsByTagName: @"EmptyFolderContents"]) - { - NGImap4Connection *connection; - NSEnumerator *subfolders; - NSException *error; - NSURL *currentURL; - id co; - - collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue]; - realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; - realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; - - if (folderType == ActiveSyncMailFolder) - { - co = [self collectionFromId: realCollectionId type: folderType]; - error = [co addFlagsToAllMessages: @"deleted"]; - - if (!error) - error = [(SOGoMailFolder *)co expunge]; - - if (!error) - { - [co flushMailCaches]; - - if ([theDocumentElement getElementsByTagName: @"DeleteSubFolders"]) - { - // Delete sub-folders - connection = [co imap4Connection]; - subfolders = [[co allFolderURLs] objectEnumerator]; - - while ((currentURL = [subfolders nextObject])) - { - [[connection client] unsubscribe: [currentURL path]]; - [connection deleteMailboxAtURL: currentURL]; - } - } - - [s appendString: @"1"]; - [s appendString: @""]; - } - - if (error) - { - [s appendString: @"3"]; - [s appendString: @""]; - } - - d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; - [theResponse setContent: d]; - } - else - { - [theResponse setStatus: 500]; - return; - } } } diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index 164abb6db..d3095a966 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -381,6 +381,11 @@ struct GlobalObjectId { if (s) { + // We sanitize the content immediately, in case we have non-UNICODE safe + // characters that would be re-encoded later in HTML entities and thus, + // ignore afterwards. + s = [s safeString]; + body = [s dataUsingEncoding: NSUTF8StringEncoding]; } @@ -861,41 +866,10 @@ struct GlobalObjectId { if (d) { - NSMutableData *sanitizedData; NSString *content; int len, truncated; - - // Outlook fails to decode quoted-printable (see #3082) if lines are not termined by CRLF. - const char *bytes; - char *mbytes; - int mlen; - - len = [d length]; - mlen = 0; - - sanitizedData = [NSMutableData dataWithLength: len*2]; - - bytes = [d bytes]; - mbytes = [sanitizedData mutableBytes]; - - while (len > 0) - { - if (*bytes == '\n' && *(bytes-1) != '\r' && mlen > 0) - { - *mbytes = '\r'; - mbytes++; - mlen++; - } - - *mbytes = *bytes; - mbytes++; bytes++; - len--; - mlen++; - } - - [sanitizedData setLength: mlen]; - - content = [[NSString alloc] initWithData: sanitizedData encoding: NSUTF8StringEncoding]; + + content = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding]; // FIXME: This is a hack. We should normally avoid doing this as we might get // broken encodings. We should rather tell that the data was truncated and expect @@ -905,15 +879,15 @@ struct GlobalObjectId { // for an "interesting" discussion around this. // if (!content) - content = [[NSString alloc] initWithData: sanitizedData encoding: NSISOLatin1StringEncoding]; + content = [[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding]; AUTORELEASE(content); content = [content activeSyncRepresentationInContext: context]; truncated = 0; - + len = [content length]; - + if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) { [s appendFormat: @"%@", content]; diff --git a/ActiveSync/iCalEvent+ActiveSync.m b/ActiveSync/iCalEvent+ActiveSync.m index 2156b1eac..586bf5798 100644 --- a/ActiveSync/iCalEvent+ActiveSync.m +++ b/ActiveSync/iCalEvent+ActiveSync.m @@ -506,14 +506,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [self setOrganizer: person]; } + // + // iOS is plain stupid here. It seends event invitations with no Organizer. + // We check this corner-case and if MeetingStatus == 1 (see http://msdn.microsoft.com/en-us/library/ee219342(v=exchg.80).aspx or details) + // and there's no organizer, we fake one. + // if ((o = [theValues objectForKey: @"MeetingStatus"])) { - // - // iOS is plain stupid here. It seends event invitations with no Organizer. - // We check this corner-case and if MeetingStatus == 1 (see http://msdn.microsoft.com/en-us/library/ee219342(v=exchg.80).aspx or details) - // and there's no organizer, we fake one. - // - if ([o intValue] == 1 && ![theValues objectForKey: @"Organizer_Email"] && ![[[self organizer] rfc822Email] length]) + if ([o intValue] == 1 && ![theValues objectForKey: @"Organizer_Email"]) { iCalPerson *person; @@ -523,20 +523,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [person setPartStat: @"ACCEPTED"]; [self setOrganizer: person]; } - - // - // When MeetingResponse fails Outlook still sends a new calendar entry with MeetingStatus=3. - // Use the organizer from the request if the event has no organizer. - // - if ([o intValue] == 3 && [theValues objectForKey: @"Organizer_Email"] && ![[[self organizer] rfc822Email] length]) - { - iCalPerson *person; - person = [iCalPerson elementWithTag: @"organizer"]; - [person setEmail: [theValues objectForKey: @"Organizer_Email"]]; - [person setCn: [theValues objectForKey: @"Organizer_Name"]]; - [person setPartStat: @"ACCEPTED"]; - [self setOrganizer: person]; - } } diff --git a/Documentation/SOGoInstallationGuide.asciidoc b/Documentation/SOGoInstallationGuide.asciidoc index e337f6eb0..5ecd53450 100644 --- a/Documentation/SOGoInstallationGuide.asciidoc +++ b/Documentation/SOGoInstallationGuide.asciidoc @@ -1920,11 +1920,10 @@ events. This parameter is an array of arbitrary strings. Defaults to a list that depends on the language. -|U |SOGoCalendarCategoriesColors -|Parameter used to define the colour of categories. This parameter -is a dictionary of category name/color. +|U |SOGoCalendarDefaultCategoryColor +|Parameter used to define the default colour of categories. -Defaults to `#F0F0F0` for all categories when unset. +Defaults to `#F0F0F0` when unset. |U |SOGoCalendarEventsDefaultClassification |Parameter used to defined the default classification for new events. @@ -2137,8 +2136,7 @@ Multi-domains Configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you want your installation to isolate two groups of users, you must -define a distinct authentication source for each _domain_. Your domain keys -must have the same value as your email domain you want to add. Following is +define a distinct authentication source for each _domain_. Following is the same configuration that now includes two domains (acme.com and coyote.com): @@ -2146,7 +2144,7 @@ coyote.com): { ... domains = { - acme.com = { + acme = { SOGoMailDomain = acme.com; SOGoDraftsFolderName = Drafts; SOGoUserSources = ( @@ -2167,7 +2165,7 @@ coyote.com): } ); }; - coyote.com = { + coyote = { SOGoMailDomain = coyote.com; SOGoIMAPServer = imap.coyote.com; SOGoUserSources = ( @@ -2198,7 +2196,7 @@ domains. [cols="3,47,50a"] |======================================================================= |S |SOGoEnableDomainBasedUID -|Parameter used to enable user identification by domain. Users will be +|Parameter used to activate user identification by domain. Users will be able (without being required) to login using the form `username@domain`, meaning that values of _UIDFieldName_ no longer have to be unique among all domains but only within the same domain. Internally, users will @@ -2713,11 +2711,6 @@ current version of SOGo from the previous release. [cols="100a"] |======================================================================= -h|2.3.1 -|The SOGoCalendarDefaultCategoryColor default has been removed. If you -want to customize the color of calendar categories, use the -SOGoCalendarCategories and SOGoCalendarCategoriesColors defaults. - h|2.3.0 |Run the shell script `sql-update-2.2.17_to_2.3.0.sh` or `sql-update-2.2.17_to_2.3.0-mysql.sh` (if you use MySQL). @@ -2725,9 +2718,6 @@ h|2.3.0 This will grow the "participant states" field of calendar quick tables to a larger size and add the the "c_description" column to calendar quick tables. -Moreover, if you are using a multi-domain configuration, make sure the keys for -your domains match the email domains you have defined. - h|2.2.8 |The configuration configuration parameters were renamed: diff --git a/NEWS b/NEWS index 080088c49..ec8cf7a00 100644 --- a/NEWS +++ b/NEWS @@ -1,23 +1,3 @@ -2.3.1 (2015-06-XX) ------------------- - -Enhancements - - improved EAS speed, especially when fetching big attachments - - now always enforce the organizer's default identity in appointments - - improved the handling of default calendar categories/colors (#3200) - - added support for DeletesAsMoves over EAS - -Bug fixes - - EAS's GetItemEstimate/ItemOperations now support fetching mails and empty folders - - fixed some rare cornercases in multidomain configurations - - properly escape folder after creation using EAS (#3237) - - fixed potential organizer highjacking when using EAS (#3131) - - properly support big characters in EAS and fix encoding QP EAS error for Outlook (#3082) - - properly encode id of DOM elements in Address Book module (#3239, #3245) - - fixed multi-domain support for sogo-tool backup/restore (#2600) - - fixed data ordering in events list of Calendar module (#3261) - - fixed data ordering in tasks list of Calendar module (#3267) - 2.3.0 (2015-06-01) ------------------- diff --git a/Scripts/sogo-systemd.conf b/Scripts/sogo-systemd.conf deleted file mode 100644 index 6755a2969..000000000 --- a/Scripts/sogo-systemd.conf +++ /dev/null @@ -1,2 +0,0 @@ -# SOGo needs directory in /var/run -d /var/run/sogo 0755 sogo sogo diff --git a/Scripts/sql-update-2.2.17_to_2.3.0-mysql.sh b/Scripts/sql-update-2.1.17_to_2.3.0-mysql.sh similarity index 95% rename from Scripts/sql-update-2.2.17_to_2.3.0-mysql.sh rename to Scripts/sql-update-2.1.17_to_2.3.0-mysql.sh index 1ed8a4818..bd8f3dd77 100755 --- a/Scripts/sql-update-2.2.17_to_2.3.0-mysql.sh +++ b/Scripts/sql-update-2.1.17_to_2.3.0-mysql.sh @@ -49,7 +49,6 @@ function adjustSchema() { echo "This script will ask for the sql password twice" >&2 echo "Converting c_partstates from VARCHAR(255) to mediumtext in calendar quick tables" >&2 -echo "Adding c_description column as mediumtext in calendar quick tables" >&2 tables=`mysql -p -s -u $username -h $hostname $database -e "select SUBSTRING_INDEX(c_quick_location, '/', -1) from $indextable where c_path3 = 'Calendar';"` for table in $tables; diff --git a/Scripts/sql-update-2.2.17_to_2.3.0.sh b/Scripts/sql-update-2.1.17_to_2.3.0.sh similarity index 89% rename from Scripts/sql-update-2.2.17_to_2.3.0.sh rename to Scripts/sql-update-2.1.17_to_2.3.0.sh index a9ff62559..fb6010d49 100755 --- a/Scripts/sql-update-2.2.17_to_2.3.0.sh +++ b/Scripts/sql-update-2.1.17_to_2.3.0.sh @@ -45,8 +45,7 @@ function adjustSchema() { IFS="$oldIFS" } -echo "Converting c_partstates from VARCHAR(255) to mediumtext in calendar quick tables" >&2 -echo "Adding c_description column as mediumtext in calendar quick tables" >&2 +echo "Converting c_cycleinfo from VARCHAR(255) to TEXT in calendar quick tables" >&2 tables=`psql -t -U $username -h $hostname $database -c "select split_part(c_quick_location, '/', 5) from $indextable where c_path3 = 'Calendar';"` for table in $tables; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index f0d310bd7..915fd4aa5 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -3157,8 +3157,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir inContainer: self]; [object setIsNew: YES]; content = [NSMutableString stringWithString: @"BEGIN:VCALENDAR\n"]; - [content appendFormat: @"PRODID:-//Inverse inc./SOGo %@//EN\n", SOGoVersion]; - if (timezone) [content appendFormat: @"%@\n", [timezone versitString]]; [content appendFormat: @"%@\nEND:VCALENDAR", [event versitString]]; diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index e26bb405d..3e1fd73ff 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -1820,28 +1820,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent [self warnWithFormat: @"Invalid event: no end date; setting duration to %@", [event duration]]; } - if ([event organizer]) + if ([event organizer] && ![[[event organizer] cn] length]) { - NSString *uid; - - if (![[[event organizer] cn] length]) - { - [[event organizer] setCn: [[event organizer] rfc822Email]]; - } - - // We now make sure that the organizer, if managed by SOGo, is using - // its default email when creating events and inviting attendees. - uid = [[SOGoUserManager sharedUserManager] getUIDForEmail: [[event organizer] rfc822Email]]; - if (uid) - { - NSDictionary *defaultIdentity; - SOGoUser *organizer; - - organizer = [SOGoUser userWithLogin: uid]; - defaultIdentity = [organizer defaultIdentity]; - [[event organizer] setCn: [defaultIdentity objectForKey: @"fullName"]]; - [[event organizer] setEmail: [defaultIdentity objectForKey: @"email"]]; - } + [[event organizer] setCn: [[event organizer] rfc822Email]]; } } } diff --git a/SoObjects/Appointments/iCalAlarm+SOGo.h b/SoObjects/Appointments/iCalAlarm+SOGo.h index 741be01fa..2980f7bc6 100644 --- a/SoObjects/Appointments/iCalAlarm+SOGo.h +++ b/SoObjects/Appointments/iCalAlarm+SOGo.h @@ -1,6 +1,6 @@ /* iCalAlarm+SOGo.h - this file is part of SOGo * - * Copyright (C) 2015 Inverse inc. + * Copyright (C) 2014 Inverse inc. * * 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 @@ -20,11 +20,11 @@ #import -@class iCalEntityObject; +@class iCalRepeatableEntityObject; @interface iCalAlarm (SOGoExtensions) -+ (id) alarmForEvent: (iCalEntityObject *) theEntity ++ (id) alarmForEvent: (iCalRepeatableEntityObject *) theEntity owner: (NSString *) theOwner action: (NSString *) reminderAction unit: (NSString *) reminderUnit diff --git a/SoObjects/Appointments/iCalAlarm+SOGo.m b/SoObjects/Appointments/iCalAlarm+SOGo.m index cb1cc57c0..c98c316db 100644 --- a/SoObjects/Appointments/iCalAlarm+SOGo.m +++ b/SoObjects/Appointments/iCalAlarm+SOGo.m @@ -1,6 +1,6 @@ /* iCalAlarm+SOGo.m - this file is part of SOGo * - * Copyright (C) 2015 Inverse inc. + * Copyright (C) 2014 Inverse inc. * * 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 @@ -28,7 +28,6 @@ #import #import -#import @implementation iCalAlarm (SOGoExtensions) @@ -66,7 +65,7 @@ [alarm addChild: aAttendee]; } -+ (id) alarmForEvent: (iCalEntityObject *) theEntity ++ (id) alarmForEvent: (iCalRepeatableEntityObject *) theEntity owner: (NSString *) theOwner action: (NSString *) reminderAction unit: (NSString *) reminderUnit diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index 2aabcaa75..8dd632d65 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -777,6 +777,8 @@ static BOOL debugSoParts = NO; filename = [NSString stringWithFormat: @"unknown_%@", path]; else if ([mimeType isEqualToString: @"message/rfc822"]) filename = [NSString stringWithFormat: @"email_%@.eml", path]; + else if ([mimeType isEqualToString: @"text/calendar"]) + filename = [NSString stringWithFormat: @"calendar_%@.ics", path]; if (filename) diff --git a/SoObjects/SOGo/NSString+Utilities.h b/SoObjects/SOGo/NSString+Utilities.h index d172ff167..8474dc4e3 100644 --- a/SoObjects/SOGo/NSString+Utilities.h +++ b/SoObjects/SOGo/NSString+Utilities.h @@ -1,6 +1,6 @@ /* NSString+Utilities.h - this file is part of SOGo * - * Copyright (C) 2006-2015 Inverse inc. + * Copyright (C) 2006-2014 Inverse inc. * * 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 @@ -46,9 +46,6 @@ - (NSString *) asCSSIdentifier; - (NSString *) fromCSSIdentifier; -/* JavaScript safety */ -- (NSString *) asSafeJSString; - /* SQL safety */ - (NSString *) asSafeSQLString; diff --git a/SoObjects/SOGo/NSString+Utilities.m b/SoObjects/SOGo/NSString+Utilities.m index d7d85e5f5..46f42af8e 100644 --- a/SoObjects/SOGo/NSString+Utilities.m +++ b/SoObjects/SOGo/NSString+Utilities.m @@ -1,6 +1,6 @@ /* NSString+Utilities.m - this file is part of SOGo * - * Copyright (C) 2006-2015 Inverse inc. + * Copyright (C) 2006-2014 Inverse inc. * * 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 @@ -257,7 +257,7 @@ static int cssEscapingCount; return selfCopy; } -- (NSString *) asSafeJSString +- (NSString *) doubleQuotedString { NSMutableString *representation; @@ -270,12 +270,7 @@ static int cssEscapingCount; [representation replaceString: @"\r" withString: @"\\r"]; [representation replaceString: @"\t" withString: @"\\t"]; - return representation; -} - -- (NSString *) doubleQuotedString -{ - return [NSString stringWithFormat: @"\"%@\"", [self asSafeJSString]]; + return [NSString stringWithFormat: @"\"%@\"", representation]; } // @@ -338,18 +333,12 @@ static int cssEscapingCount; int count; strings = [NSArray arrayWithObjects: @"_U_", @"_D_", @"_H_", @"_A_", @"_S_", - @"_C_", @"_SC_", - @"_CO_", @"_SP_", @"_SQ_", @"_DQ_", - @"_LP_", @"_RP_", @"_LS_", @"_RS_", @"_LC_", @"_RC_", - @"_AM_", @"_P_", @"_DS_", nil]; + @"_C_", @"_CO_", @"_SP_", @"_SQ_", @"_AM_", @"_P_", @"_DS_", nil]; [strings retain]; cssEscapingStrings = [strings asPointersOfObjects]; - characters = [NSArray arrayWithObjects: @"_", @".", @"#", @"@", @"*", - @":", @";", - @",", @" ", @"'", @"\"", - @"(", @")", @"[", @"]", @"{", @"}", - @"&", @"+", @"$", nil]; + characters = [NSArray arrayWithObjects: @"_", @".", @"#", @"@", @"*", @":", + @",", @" ", @"'", @"&", @"+", @"$", nil]; cssEscapingCount = [strings count]; cssEscapingCharacters = NSZoneMalloc (NULL, (cssEscapingCount + 1) diff --git a/SoObjects/SOGo/SOGoDefaults.plist b/SoObjects/SOGo/SOGoDefaults.plist index d0557c666..e804095f0 100644 --- a/SoObjects/SOGo/SOGoDefaults.plist +++ b/SoObjects/SOGo/SOGoDefaults.plist @@ -70,6 +70,7 @@ SOGoMailAutoSave = "5"; + SOGoCalendarDefaultCategoryColor = "#aaa"; SOGoCalendarShouldDisplayWeekend = YES; SOGoCalendarEventsDefaultClassification = "PUBLIC"; SOGoCalendarTasksDefaultClassification = "PUBLIC"; @@ -86,10 +87,6 @@ $label4 = ("To Do", "#3333FF"); $label5 = ("Later", "#993399"); }; - - SOGoCalendarCategories = ("Customer", "Calls", "Favorites", "Meeting", "Ideas", "Miscellaneous", "Birthday", "Anniversary", "Vacation", "Travel", "Projects", "Suppliers", "Gifts", "Clients", "Issues", "Business", "Holidays", "Personal", "Status", "Competition", "Follow up", "Public Holiday"); - - SOGoCalendarCategoriesColors = { "Customer" = "#F0F0F0"; "Calls" = "#F0F0F0"; "Favorites" = "#F0F0F0"; "Meeting" = "#F0F0F0"; "Ideas" = "#F0F0F0"; "Miscellaneous" = "#F0F0F0"; "Birthday" = "#F0F0F0"; "Anniversary" = "#F0F0F0"; "Vacation" = "#F0F0F0"; "Travel" = "#F0F0F0"; "Projects" = "#F0F0F0"; "Suppliers" = "#F0F0F0"; "Gifts" = "#F0F0F0"; "Clients" = "#F0F0F0"; "Issues" = "#F0F0F0"; "Business" = "#F0F0F0"; "Holidays" = "#F0F0F0"; "Personal" = "#F0F0F0"; "Status" = "#F0F0F0"; "Competition" = "#F0F0F0"; "Follow up" = "#F0F0F0"; "Public Holiday" = "#F0F0F0"; }; - + SOGoSubscriptionFolderFormat = "%{FolderName} (%{UserName} <%{Email}>)"; } diff --git a/SoObjects/SOGo/SOGoDomainDefaults.h b/SoObjects/SOGo/SOGoDomainDefaults.h index 7dea88121..fbc665852 100644 --- a/SoObjects/SOGo/SOGoDomainDefaults.h +++ b/SoObjects/SOGo/SOGoDomainDefaults.h @@ -68,6 +68,8 @@ - (NSArray *) refreshViewIntervals; - (NSString *) subscriptionFolderFormat; +- (NSString *) calendarDefaultCategoryColor; + - (NSArray *) freeBusyDefaultInterval; - (int) davCalendarStartTimeLimit; diff --git a/SoObjects/SOGo/SOGoDomainDefaults.m b/SoObjects/SOGo/SOGoDomainDefaults.m index b55cc0851..ff02b6a96 100644 --- a/SoObjects/SOGo/SOGoDomainDefaults.m +++ b/SoObjects/SOGo/SOGoDomainDefaults.m @@ -294,6 +294,11 @@ return [self stringForKey: @"SOGoLDAPContactInfoAttribute"]; } +- (NSString *) calendarDefaultCategoryColor +{ + return [self stringForKey: @"SOGoCalendarDefaultCategoryColor"]; +} + - (NSArray *) freeBusyDefaultInterval { return [self arrayForKey: @"SOGoFreeBusyDefaultInterval"]; diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index d81dcf52c..91d3b1b5a 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -165,9 +165,10 @@ // The domain is probably appended to the username; // make sure it is defined as a domain in the configuration. domain = [newLogin substringFromIndex: (r.location + r.length)]; - if ([[sd domainIds] containsObject: domain] && - ![sd enableDomainBasedUID]) + if ([[sd domainIds] containsObject: domain]) newLogin = [newLogin substringToIndex: r.location]; + else + domain = nil; if (domain != nil && ![sd enableDomainBasedUID]) // Login domains are enabled (SOGoLoginDomains) but not @@ -196,25 +197,8 @@ // When the user is associated to a domain, the [SOGoUser login] // method returns the combination login@domain while // [SOGoUser loginInDomain] only returns the login. - r = [realUID rangeOfString: domain options: NSBackwardsSearch|NSCaseInsensitiveSearch]; - - // Do NOT strip @domain.com if SOGoEnableDomainBasedUID is enabled since - // the real login most likely is the email address. - if (r.location != NSNotFound && ![sd enableDomainBasedUID]) - uid = [realUID substringToIndex: r.location-1]; - // If we don't have the domain in the UID but SOGoEnableDomainBasedUID is - // enabled, let's add it internally so so it becomes unique across - // all potential domains. - else if (r.location == NSNotFound && [sd enableDomainBasedUID]) - { - uid = [NSString stringWithString: realUID]; - realUID = [NSString stringWithFormat: @"%@@%@", realUID, domain]; - } - // We found the domain and SOGoEnableDomainBasedUID is enabled, - // we keep realUID.. This would happen for example if the user - // authenticates with foo@bar.com and the UIDFieldName is also foo@bar.com - else if ([sd enableDomainBasedUID]) - uid = [NSString stringWithString: realUID]; + uid = [NSString stringWithString: realUID]; + realUID = [NSString stringWithFormat: @"%@@%@", realUID, domain]; } } diff --git a/SoObjects/SOGo/SOGoUserDefaults.m b/SoObjects/SOGo/SOGoUserDefaults.m index 9cc4b38e2..5d975f146 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.m +++ b/SoObjects/SOGo/SOGoUserDefaults.m @@ -702,7 +702,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; - (NSDictionary *) calendarCategoriesColors { - return [self objectForKey: @"SOGoCalendarCategoriesColors"]; + return [self dictionaryForKey: @"SOGoCalendarCategoriesColors"]; } - (void) setCalendarShouldDisplayWeekend: (BOOL) newValue diff --git a/SoObjects/SOGo/SOGoUserManager.m b/SoObjects/SOGo/SOGoUserManager.m index f93cef7e6..127c6756c 100644 --- a/SoObjects/SOGo/SOGoUserManager.m +++ b/SoObjects/SOGo/SOGoUserManager.m @@ -494,10 +494,10 @@ static Class NSNullK; NSMutableDictionary *currentUser; NSDictionary *failedCount; NSString *dictPassword, *username, *jsonUser; - SOGoSystemDefaults *sd; + SOGoSystemDefaults *dd; BOOL checkOK; - sd = [SOGoSystemDefaults sharedSystemDefaults]; + dd = [SOGoSystemDefaults sharedSystemDefaults]; username = _login; @@ -517,9 +517,21 @@ static Class NSNullK; if (r.location != NSNotFound) { + NSArray *allDomains; + int i; + *_domain = [username substringFromIndex: r.location+1]; - if (![[[SOGoSystemDefaults sharedSystemDefaults] domainIds] containsObject: *_domain]) + allDomains = [[dd dictionaryForKey: @"domains"] allValues]; + + for (i = 0; i < [allDomains count]; i++) + { + if ([*_domain isEqualToString: [[allDomains objectAtIndex: i] objectForKey: @"SOGoMailDomain"]]) + break; + } + + // We haven't found one + if (i == [allDomains count]) *_domain = nil; } } @@ -536,10 +548,10 @@ static Class NSNullK; start_time = [[failedCount objectForKey: @"InitialDate"] unsignedIntValue]; delta = current_time - start_time; - block_time = [sd failedLoginBlockInterval]; + block_time = [dd failedLoginBlockInterval]; - if ([[failedCount objectForKey: @"FailedCount"] intValue] >= [sd maximumFailedLoginCount] && - delta >= [sd maximumFailedLoginInterval] && + if ([[failedCount objectForKey: @"FailedCount"] intValue] >= [dd maximumFailedLoginCount] && + delta >= [dd maximumFailedLoginInterval] && delta <= block_time ) { *_perr = PolicyAccountLocked; @@ -558,28 +570,6 @@ static Class NSNullK; // authentication source and try to validate there, then cache it. jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: username]; currentUser = [jsonUser objectFromJSONString]; - - // - // If we are using multidomain and the UIDFieldName is not part of the email address - // we must bind without the domain part since internally, SOGo will use - // UIDFieldName @ domain as its unique identifier if the UIDFieldName is used to - // authenticate. This can happen for example of one has in LDAP: - // - // dn: uid=foo,dc=example,dc=com - // uid: foo - // mail: broccoli@example.com - // - // and authenticates with "foo", using bindFields = (uid, mail) and SOGoEnableDomainBasedUID = YES; - // Otherwise, -_sourceCheckLogin:... would have failed because SOGo would try to bind using: foo@example.com - // - if ([[currentUser objectForKey: @"DomainLessLogin"] boolValue]) - { - NSRange r; - - r = [_login rangeOfString: [NSString stringWithFormat: @"@%@", *_domain]]; - _login = [_login substringToIndex: r.location]; - } - dictPassword = [currentUser objectForKey: @"password"]; if (useCache && currentUser && dictPassword) { @@ -599,18 +589,6 @@ static Class NSNullK; currentUser = [NSMutableDictionary dictionary]; } - // Before caching user attributes, we must check if SOGoEnableDomainBasedUID is enabled - // but we don't have a domain. That would happen for example if the user authenticates - // without the domain part. We must also cache that information, since SOGo will try - // afterward to bind with UIDFieldName@domain, and it could potentially not exist - // in the authentication source. See the rationale in _sourceCheckLogin: ... - if ([sd enableDomainBasedUID] && - [username rangeOfString: @"@"].location == NSNotFound) - { - username = [NSString stringWithFormat: @"%@@%@", username, *_domain]; - [currentUser setObject: [NSNumber numberWithBool: YES] forKey: @"DomainLessLogin"]; - } - // It's important to cache the password here as we might have cached the // user's entry in -contactInfosForUserWithUIDorEmail: and if we don't // set the password and recache the entry, the password would never be @@ -624,7 +602,7 @@ static Class NSNullK; else { // If failed login "rate-limiting" is enabled, we adjust the stats - if ([sd maximumFailedLoginCount]) + if ([dd maximumFailedLoginCount]) { [[SOGoCache sharedCache] setFailedCount: ([[failedCount objectForKey: @"FailedCount"] intValue] + 1) forLogin: username]; @@ -732,9 +710,9 @@ static Class NSNullK; // // // -- (void) _fillContactInfosForUser: (NSMutableDictionary *) theCurrentUser - withUIDorEmail: (NSString *) theUID - inDomain: (NSString *) theDomain +- (void) _fillContactInfosForUser: (NSMutableDictionary *) currentUser + withUIDorEmail: (NSString *) uid + inDomain: (NSString *) domain { NSString *sourceID, *cn, *c_domain, *c_uid, *c_imaphostname, *c_imaplogin, *c_sievehostname; NSObject *currentSource; @@ -761,28 +739,19 @@ static Class NSNullK; enumerator = [access_types_list objectEnumerator]; while ((access_type = [enumerator nextObject]) != nil) - [theCurrentUser setObject: [NSNumber numberWithBool: YES] - forKey: access_type]; + [currentUser setObject: [NSNumber numberWithBool: YES] + forKey: access_type]; - if ([[theCurrentUser objectForKey: @"DomainLessLogin"] boolValue]) - { - NSRange r; - - r = [theUID rangeOfString: [NSString stringWithFormat: @"@%@", theDomain]]; - theUID = [theUID substringToIndex: r.location]; - } - - sogoSources = [[self authenticationSourceIDsInDomain: theDomain] objectEnumerator]; + sogoSources = [[self authenticationSourceIDsInDomain: domain] objectEnumerator]; userEntry = nil; while (!userEntry && (sourceID = [sogoSources nextObject])) { currentSource = [_sources objectForKey: sourceID]; - - userEntry = [currentSource lookupContactEntryWithUIDorEmail: theUID - inDomain: theDomain]; + userEntry = [currentSource lookupContactEntryWithUIDorEmail: uid + inDomain: domain]; if (userEntry) { - [theCurrentUser setObject: sourceID forKey: @"SOGoSource"]; + [currentUser setObject: sourceID forKey: @"SOGoSource"]; if (!cn) cn = [userEntry objectForKey: @"c_cn"]; if (!c_uid) @@ -804,27 +773,27 @@ static Class NSNullK; { access = [[userEntry objectForKey: access_type] boolValue]; if (!access) - [theCurrentUser setObject: [NSNumber numberWithBool: NO] - forKey: access_type]; + [currentUser setObject: [NSNumber numberWithBool: NO] + forKey: access_type]; } // We check if it's a group isGroup = [userEntry objectForKey: @"isGroup"]; if (isGroup) - [theCurrentUser setObject: isGroup forKey: @"isGroup"]; + [currentUser setObject: isGroup forKey: @"isGroup"]; // We also fill the resource attributes, if any if ([userEntry objectForKey: @"isResource"]) - [theCurrentUser setObject: [userEntry objectForKey: @"isResource"] - forKey: @"isResource"]; + [currentUser setObject: [userEntry objectForKey: @"isResource"] + forKey: @"isResource"]; if ([userEntry objectForKey: @"numberOfSimultaneousBookings"]) - [theCurrentUser setObject: [userEntry objectForKey: @"numberOfSimultaneousBookings"] - forKey: @"numberOfSimultaneousBookings"]; + [currentUser setObject: [userEntry objectForKey: @"numberOfSimultaneousBookings"] + forKey: @"numberOfSimultaneousBookings"]; // This is Active Directory specific attribute (needed on OpenChange/* layer) if ([userEntry objectForKey: @"samaccountname"]) - [theCurrentUser setObject: [userEntry objectForKey: @"samaccountname"] - forKey: @"sAMAccountName"]; + [currentUser setObject: [userEntry objectForKey: @"samaccountname"] + forKey: @"sAMAccountName"]; } } @@ -836,20 +805,20 @@ static Class NSNullK; c_domain = @""; if (c_imaphostname) - [theCurrentUser setObject: c_imaphostname forKey: @"c_imaphostname"]; + [currentUser setObject: c_imaphostname forKey: @"c_imaphostname"]; if (c_imaplogin) - [theCurrentUser setObject: c_imaplogin forKey: @"c_imaplogin"]; + [currentUser setObject: c_imaplogin forKey: @"c_imaplogin"]; if (c_sievehostname) - [theCurrentUser setObject: c_sievehostname forKey: @"c_sievehostname"]; + [currentUser setObject: c_sievehostname forKey: @"c_sievehostname"]; - [theCurrentUser setObject: emails forKey: @"emails"]; - [theCurrentUser setObject: cn forKey: @"cn"]; - [theCurrentUser setObject: c_uid forKey: @"c_uid"]; - [theCurrentUser setObject: c_domain forKey: @"c_domain"]; + [currentUser setObject: emails forKey: @"emails"]; + [currentUser setObject: cn forKey: @"cn"]; + [currentUser setObject: c_uid forKey: @"c_uid"]; + [currentUser setObject: c_domain forKey: @"c_domain"]; // If our LDAP queries gave us nothing, we add at least one default // email address based on the default domain. - [self _fillContactMailRecords: theCurrentUser]; + [self _fillContactMailRecords: currentUser]; } // @@ -943,9 +912,8 @@ static Class NSNullK; - (NSDictionary *) contactInfosForUserWithUIDorEmail: (NSString *) uid inDomain: (NSString *) domain { - NSString *aUID, *cacheUid, *jsonUser; NSMutableDictionary *currentUser; - + NSString *aUID, *cacheUid, *jsonUser; BOOL newUser; if ([uid isEqualToString: @"anonymous"]) @@ -954,14 +922,12 @@ static Class NSNullK; { // Remove the "@" prefix used to identified groups in the ACL tables. aUID = [uid hasPrefix: @"@"] ? [uid substringFromIndex: 1] : uid; - if (domain && [aUID rangeOfString: @"@"].location == NSNotFound) + if (domain) cacheUid = [NSString stringWithFormat: @"%@@%@", aUID, domain]; else cacheUid = aUID; - jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: cacheUid]; currentUser = [jsonUser objectFromJSONString]; - if ([currentUser isKindOfClass: NSNullK]) currentUser = nil; else if (!([currentUser objectForKey: @"emails"] @@ -971,10 +937,8 @@ static Class NSNullK; // that we have an occurence with only a cached password. In the // latter case, we update the entry with the remaining information // and recache the value. - if (!currentUser || - ([currentUser count] == 1 && [currentUser objectForKey: @"password"]) || - ([currentUser count] == 2 && [currentUser objectForKey: @"password"] && [currentUser objectForKey: @"DomainLessLogin"])) - { + if (!currentUser || ([currentUser count] == 1 && [currentUser objectForKey: @"password"])) + { newUser = YES; if (!currentUser) @@ -994,22 +958,9 @@ static Class NSNullK; currentUser = nil; } else - { - SOGoSystemDefaults *sd; - - sd = [SOGoSystemDefaults sharedSystemDefaults]; - - // SOGoEnableDomainBasedUID is set to YES but we don't have a domain part. This happens in - // multi-domain environments authenticating only with the UIDFieldName - if ([sd enableDomainBasedUID] && !domain) - { - cacheUid = [NSString stringWithFormat: @"%@@%@", cacheUid, [currentUser objectForKey: @"c_domain"]]; - [currentUser setObject: [NSNumber numberWithBool: YES] forKey: @"DomainLessLogin"]; - } - - [self _retainUser: currentUser withLogin: cacheUid]; - } - } + [self _retainUser: currentUser + withLogin: cacheUid]; + } } } else diff --git a/Tools/SOGoToolBackup.m b/Tools/SOGoToolBackup.m index fb2b4d016..8dd7ea04a 100644 --- a/Tools/SOGoToolBackup.m +++ b/Tools/SOGoToolBackup.m @@ -1,6 +1,9 @@ /* SOGoToolBackup.m - this file is part of SOGo * - * Copyright (C) 2009-2015 Inverse inc. + * Copyright (C) 2009-2011 Inverse inc. + * + * Author: Wolfgang Sourdeau + * Francis Lachapelle * * 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 @@ -39,7 +42,6 @@ #import #import #import -#import #import #import "SOGoTool.h" @@ -53,7 +55,7 @@ @interface SOGoToolBackup : SOGoTool { NSString *directory; - NSArray *usersToBackup; + NSArray *userIDs; } @end @@ -81,7 +83,7 @@ if ((self = [super init])) { directory = nil; - usersToBackup = nil; + userIDs = nil; } return self; @@ -90,7 +92,7 @@ - (void) dealloc { [directory release]; - [usersToBackup release]; + [userIDs release]; [super dealloc]; } @@ -141,7 +143,6 @@ lm = [SOGoUserManager sharedUserManager]; pool = [[NSAutoreleasePool alloc] init]; - max = [users count]; user = [users objectAtIndex: 0]; @@ -197,11 +198,11 @@ NSLog (@"user '%@' unknown", user); } [allUsers autorelease]; - - ASSIGN (usersToBackup, allUsers); + + ASSIGN (userIDs, [allUsers objectsForKey: @"c_uid" notFoundMarker: nil]); DESTROY(pool); - return ([usersToBackup count] > 0); + return ([userIDs count] > 0); } - (BOOL) parseArguments @@ -409,29 +410,19 @@ return YES; } -- (BOOL) exportUser: (NSDictionary *) theUser +- (BOOL) exportUser: (NSString *) uid { - NSString *exportPath, *gcsUID, *ldapUID; NSMutableDictionary *userRecord; - SOGoSystemDefaults *sd; - - sd = [SOGoSystemDefaults sharedSystemDefaults]; + NSString *exportPath; + userRecord = [NSMutableDictionary dictionary]; + exportPath = [directory stringByAppendingPathComponent: uid]; - ldapUID = [theUser objectForKey: @"c_uid"]; - exportPath = [directory stringByAppendingPathComponent: ldapUID]; - - gcsUID = [theUser objectForKey: @"c_uid"]; - - if ([sd enableDomainBasedUID] && [gcsUID rangeOfString: @"@"].location == NSNotFound) - gcsUID = [NSString stringWithFormat: @"%@@%@", gcsUID, [theUser objectForKey: @"c_domain"]]; - - - return ([self extractUserFolders: gcsUID + return ([self extractUserFolders: uid intoRecord: userRecord] - && [self extractUserLDIFRecord: ldapUID + && [self extractUserLDIFRecord: uid intoRecord: userRecord] - && [self extractUserPreferences: gcsUID + && [self extractUserPreferences: uid intoRecord: userRecord] && [userRecord writeToFile: exportPath atomically: NO]); @@ -447,10 +438,10 @@ pool = [NSAutoreleasePool new]; - max = [usersToBackup count]; + max = [userIDs count]; for (count = 0; rc && count < max; count++) { - rc = [self exportUser: [usersToBackup objectAtIndex: count]]; + rc = [self exportUser: [userIDs objectAtIndex: count]]; if ((count % 10) == 0) [pool emptyPool]; } diff --git a/Tools/SOGoToolRestore.h b/Tools/SOGoToolRestore.h index 61438ac18..f5be03928 100644 --- a/Tools/SOGoToolRestore.h +++ b/Tools/SOGoToolRestore.h @@ -33,7 +33,6 @@ typedef enum SOGoToolRestoreMode { { NSString *directory; NSString *userID; - NSString *filename; NSString *restoreFolder; BOOL destructive; /* destructive mode not handled */ SOGoToolRestoreMode restoreMode; diff --git a/Tools/SOGoToolRestore.m b/Tools/SOGoToolRestore.m index 712c680ba..a0f411bd9 100644 --- a/Tools/SOGoToolRestore.m +++ b/Tools/SOGoToolRestore.m @@ -1,6 +1,6 @@ /* SOGoToolRestore.m - this file is part of SOGo * - * Copyright (C) 2009-2015 Inverse inc. + * Copyright (C) 2009-2014 Inverse inc. * * 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 @@ -73,7 +73,6 @@ { directory = nil; userID = nil; - filename = nil; restoreFolder = nil; destructive = NO; } @@ -85,7 +84,6 @@ { [directory release]; [userID release]; - [filename release]; [restoreFolder release]; [super dealloc]; } @@ -154,35 +152,25 @@ - (BOOL) fetchUserID: (NSString *) identifier { - SOGoSystemDefaults *sd; + BOOL rc; SOGoUserManager *lm; NSDictionary *infos; + SOGoSystemDefaults *sd; NSString *uid = nil; - BOOL rc; - lm = [SOGoUserManager sharedUserManager]; infos = [lm contactInfosForUserWithUIDorEmail: identifier]; - uid = nil; - if (infos) { sd = [SOGoSystemDefaults sharedSystemDefaults]; - uid = [infos objectForKey: @"c_uid"]; - - if ([sd enableDomainBasedUID] && [uid rangeOfString: @"@"].location == NSNotFound) + if ([sd enableDomainBasedUID]) uid = [NSString stringWithFormat: @"%@@%@", - [infos objectForKey: @"c_uid"], - [infos objectForKey: @"c_domain"]]; - - if ([[infos objectForKey: @"DomainLessLogin"] boolValue]) - ASSIGN(filename, [infos objectForKey: @"c_uid"]); + [infos objectForKey: @"c_uid"], + [infos objectForKey: @"c_domain"]]; else - ASSIGN(filename, uid); + uid = [infos objectForKey: @"c_uid"]; } - ASSIGN (userID, uid); - if (userID) rc = YES; else @@ -620,7 +608,7 @@ NSString *importPath; BOOL rc; - importPath = [directory stringByAppendingPathComponent: filename]; + importPath = [directory stringByAppendingPathComponent: userID]; userRecord = [NSDictionary dictionaryWithContentsOfFile: importPath]; if (userRecord) { @@ -638,7 +626,7 @@ else { rc = NO; - NSLog(@"user backup (%@) file could not be loaded", importPath); + NSLog (@"user backup file could not be loaded"); } return rc; diff --git a/Tools/sogo-tool.m b/Tools/sogo-tool.m index bc4755416..d2d8b5c87 100644 --- a/Tools/sogo-tool.m +++ b/Tools/sogo-tool.m @@ -1,6 +1,8 @@ /* sogo-tool.m - this file is part of SOGo * - * Copyright (C) 2009-2015 Inverse inc. + * Copyright (C) 2009 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 diff --git a/UI/Contacts/UIxContactEditor.m b/UI/Contacts/UIxContactEditor.m index 418945337..82ad1941e 100644 --- a/UI/Contacts/UIxContactEditor.m +++ b/UI/Contacts/UIxContactEditor.m @@ -1,6 +1,6 @@ /* Copyright (C) 2004-2005 SKYRIX Software AG - Copyright (C) 2005-2015 Inverse inc. + Copyright (C) 2005-2010 Inverse inc. This file is part of SOGo @@ -378,8 +378,9 @@ static Class SOGoContactGCSEntryK = Nil; result = [self redirectToLocation: [self modulePath]]; else { - jsRefreshMethod = [NSString stringWithFormat: @"refreshContacts('%@')", - [contact nameInContainer]]; + jsRefreshMethod + = [NSString stringWithFormat: @"refreshContacts(\"%@\")", + [contact nameInContainer]]; result = [self jsCloseWithRefreshMethod: jsRefreshMethod]; } diff --git a/UI/Contacts/UIxContactView.m b/UI/Contacts/UIxContactView.m index 4a4541794..259a518e9 100644 --- a/UI/Contacts/UIxContactView.m +++ b/UI/Contacts/UIxContactView.m @@ -1,6 +1,6 @@ /* Copyright (C) 2004 SKYRIX Software AG - Copyright (C) 2005-2015 Inverse inc. + Copyright (C) 2005-2014 Inverse inc. This file is part of SOGo. @@ -138,13 +138,9 @@ if ([email length] > 0) { fn = [card fn]; - if ([fn length] > 0) - attrs = [NSString stringWithFormat: @"%@ <%@>", fn, email]; - else - attrs = email; - attrs = [attrs stringByReplacingString: @"'" withString: @"\\'"]; - attrs = [attrs stringByReplacingString: @"\"" withString: @"\\\""]; - attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@');\"", attrs]; + fn = [fn stringByReplacingString: @"\"" withString: @""]; + fn = [fn stringByReplacingString: @"'" withString: @"\\\'"]; + attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@ <%@>');\"", fn, email]; } else { @@ -185,23 +181,16 @@ for (i = 0; i < [emails count]; i++) { email = [[emails objectAtIndex: i] flattenedValuesForKey: @""]; - if ([email length]) - { - fn = [card fn]; - if ([fn length]) - attrs = [NSString stringWithFormat: @"%@ <%@>", fn, email]; - else - attrs = email; - attrs = [attrs stringByReplacingString: @"'" withString: @"\\'"]; - attrs = [attrs stringByReplacingString: @"\"" withString: @"\\\""]; - attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@');\"", attrs]; - - [secondaryEmails addObject: [self _cardStringWithLabel: nil - value: email - byEscapingHTMLString: YES - asLinkScheme: @"mailto:" - withLinkAttributes: attrs]]; - } + fn = [card fn]; + fn = [fn stringByReplacingString: @"\"" withString: @""]; + fn = [fn stringByReplacingString: @"'" withString: @"\\\'"]; + attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@ <%@>');\"", fn, email]; + + [secondaryEmails addObject: [self _cardStringWithLabel: nil + value: email + byEscapingHTMLString: YES + asLinkScheme: @"mailto:" + withLinkAttributes: attrs]]; } } else diff --git a/UI/Contacts/UIxListEditor.m b/UI/Contacts/UIxListEditor.m index ea4920b15..69d63bd21 100644 --- a/UI/Contacts/UIxListEditor.m +++ b/UI/Contacts/UIxListEditor.m @@ -1,6 +1,6 @@ /* UIxListEditor.m - this file is part of SOGo * - * Copyright (C) 2008-2015 Inverse inc. + * Copyright (C) 2008-2014 Inverse inc. * * 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 @@ -29,8 +29,6 @@ #import #import -#import - #import #import #import @@ -291,8 +289,9 @@ result = [self redirectToLocation: [self modulePath]]; else { - jsRefreshMethod = [NSString stringWithFormat: @"refreshContacts('%@')", - [co nameInContainer]]; + jsRefreshMethod + = [NSString stringWithFormat: @"refreshContacts(\"%@\")", + [co nameInContainer]]; result = [self jsCloseWithRefreshMethod: jsRefreshMethod]; } } diff --git a/UI/MainUI/SOGoUserHomePage.m b/UI/MainUI/SOGoUserHomePage.m index 762de1f85..42f373f8d 100644 --- a/UI/MainUI/SOGoUserHomePage.m +++ b/UI/MainUI/SOGoUserHomePage.m @@ -463,7 +463,7 @@ if (!activeUserIsInDomain || ![uid isEqualToString: login]) { jsonLine = [NSMutableArray arrayWithCapacity: 4]; - if ([domain length] && [uid rangeOfString: @"@"].location == NSNotFound) + if ([domain length]) uid = [NSString stringWithFormat: @"%@@%@", uid, domain]; [jsonLine addObject: uid]; [jsonLine addObject: [contact objectForKey: @"cn"]]; diff --git a/UI/PreferencesUI/UIxPreferences.h b/UI/PreferencesUI/UIxPreferences.h index 51607c9a1..bbbb0a2b1 100644 --- a/UI/PreferencesUI/UIxPreferences.h +++ b/UI/PreferencesUI/UIxPreferences.h @@ -44,6 +44,7 @@ NSDictionary *calendarCategoriesColors; NSArray *contactsCategories; + NSString *defaultCategoryColor; NSCalendarDate *today; // Mail labels/tags diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index c73206d0b..990fda1ed 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -128,6 +128,7 @@ static NSArray *reminderValues = nil; calendarCategories = nil; calendarCategoriesColors = nil; + defaultCategoryColor = nil; category = nil; label = nil; @@ -174,6 +175,7 @@ static NSArray *reminderValues = nil; [vacationOptions release]; [calendarCategories release]; [calendarCategoriesColors release]; + [defaultCategoryColor release]; [category release]; [label release]; [mailLabels release]; @@ -1519,6 +1521,15 @@ static NSArray *reminderValues = nil; ASSIGN (calendarCategoriesColors, [userDefaults calendarCategoriesColors]); categoryColor = [calendarCategoriesColors objectForKey: category]; + if (!categoryColor) + { + if (!defaultCategoryColor) + { + dd = [[context activeUser] domainDefaults]; + ASSIGN (defaultCategoryColor, [dd calendarDefaultCategoryColor]); + } + categoryColor = defaultCategoryColor; + } return categoryColor; } diff --git a/UI/SOGoUI/UIxComponent.m b/UI/SOGoUI/UIxComponent.m index b2a1315af..336bd734d 100644 --- a/UI/SOGoUI/UIxComponent.m +++ b/UI/SOGoUI/UIxComponent.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2007-2015 Inverse inc. + Copyright (C) 2007-2012 Inverse inc. Copyright (C) 2004 SKYRIX Software AG This file is part of SOGo @@ -452,7 +452,7 @@ static SoProduct *commonProduct = nil; jsClose = [UIxJSClose new]; [jsClose autorelease]; - [jsClose setRefreshMethod: [methodName doubleQuotedString]]; + [jsClose setRefreshMethod: methodName]; return jsClose; } diff --git a/UI/Templates/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/ContactsUI/UIxContactFoldersView.wox index 323badb7d..2f8a4c415 100644 --- a/UI/Templates/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/ContactsUI/UIxContactFoldersView.wox @@ -149,11 +149,11 @@ - - + var:categories="currentContact.c_categories" + var:id="currentContact.c_name" + var:contactname="currentContact.c_cn"> + + diff --git a/UI/Templates/UIxJSClose.wox b/UI/Templates/UIxJSClose.wox index f1bdef0ef..3a4487f5b 100644 --- a/UI/Templates/UIxJSClose.wox +++ b/UI/Templates/UIxJSClose.wox @@ -17,7 +17,7 @@ > diff --git a/UI/WebServerResources/ContactsUI.js b/UI/WebServerResources/ContactsUI.js index 3386bff6e..fd460859c 100644 --- a/UI/WebServerResources/ContactsUI.js +++ b/UI/WebServerResources/ContactsUI.js @@ -31,7 +31,7 @@ function openContactsFolder(contactsFolder, reload, idx) { var selection; if (idx) { - selection = [idx.asCSSIdentifier()]; + selection = [idx]; } else if (contactsFolder == Contact.currentAddressBook) { var contactsList = $("contactsList"); @@ -74,7 +74,7 @@ function contactsListCallback(http) { var contact = data[i]; var row = rows[i]; row.className = contact["c_component"]; - row.setAttribute("id", contact["c_name"].asCSSIdentifier()); + row.setAttribute("id", contact["c_name"]); row.setAttribute("categories", contact["c_categories"]); row.setAttribute("contactname", contact["c_cn"]); var cells = row.getElementsByTagName("TD"); @@ -111,7 +111,7 @@ function contactsListCallback(http) { for (var j = i; j < data.length; j++) { var contact = data[j]; var row = createElement("tr", - contact["c_name"].asCSSIdentifier(), + contact["c_name"], contact["c_component"], null, { categories: contact["c_categories"], @@ -272,7 +272,7 @@ function _onContactMenuAction(folderItem, action, refresh) { if (Object.isArray(document.menuTarget) && selectedFolders.length > 0) { var selectedFolderId = $(selectedFolders[0]).readAttribute("id"); var contactIds = $(document.menuTarget).collect(function(row) { - return row.getAttribute("id").fromCSSIdentifier(); + return row.getAttribute("id"); }); for (var i = 0; i < contactIds.length; i++) { @@ -283,7 +283,9 @@ function _onContactMenuAction(folderItem, action, refresh) { } var url = ApplicationBaseURL + "/" + selectedFolderId + "/" + action; - var uids = contactIds.collect(encodeURIComponent).join('&uid='); + var uids = contactIds.collect(function (s) { + return encodeURIComponent(s.unescapeHTML()); + }).join('&uid='); if (refresh) triggerAjaxRequest(url, actionContactCallback, selectedFolderId, ('folder='+ folderId + '&uid=' + uids), @@ -310,22 +312,22 @@ function onMenuExportContact (event) { if (canExport) { var selectedFolderId = $(selectedFolders[0]).readAttribute("id"); var contactIds = document.menuTarget.collect(function(row) { - return row.readAttribute("id").fromCSSIdentifier(); + return row.readAttribute("id"); }); var url = ApplicationBaseURL + "/" + selectedFolderId + "/export" - + "?uid=" + contactIds.collect(encodeURIComponent).join("&uid="); + + "?uid=" + contactIds.join("&uid="); window.location.href = url; } } function onMenuRawContact (event) { var cname = document.menuTarget.collect(function(row) { - return row.readAttribute("id").fromCSSIdentifier(); + return row.readAttribute("id"); }); $(function() { openGenericWindow(URLForFolderID(Contact.currentAddressBook) - + "/" + encodeURIComponent(cname) + "/raw"); + + "/" + cname + "/raw"); }).delay(0.1); } @@ -348,22 +350,22 @@ function actionContactCallback(http) { } } -function loadContact(cname) { +function loadContact(idx) { if (document.contactAjaxRequest) { document.contactAjaxRequest.aborted = true; document.contactAjaxRequest.abort(); } - if (cachedContacts[Contact.currentAddressBook + "/" + cname]) { + if (cachedContacts[Contact.currentAddressBook + "/" + idx]) { var div = $('contactView'); - Contact.currentContactId = cname; - div.innerHTML = cachedContacts[Contact.currentAddressBook + "/" + cname]; + Contact.currentContactId = idx; + div.innerHTML = cachedContacts[Contact.currentAddressBook + "/" + idx]; } else { var url = (URLForFolderID(Contact.currentAddressBook) - + "/" + encodeURIComponent(cname) + "/view?noframe=1"); + + "/" + encodeURIComponent(idx.unescapeHTML()) + "/view?noframe=1"); document.contactAjaxRequest - = triggerAjaxRequest(url, contactLoadCallback, cname); + = triggerAjaxRequest(url, contactLoadCallback, idx); } } @@ -416,9 +418,8 @@ function moveTo(uri) { /* contact menu entries */ function onContactRowDblClick(event) { var t = getTarget(event); - var cname = t.parentNode.getAttribute('id').fromCSSIdentifier(); + var cname = t.parentNode.getAttribute('id'); - cname = encodeURIComponent(cname); openContactWindow(URLForFolderID(Contact.currentAddressBook) + "/" + cname + "/edit", cname); @@ -437,7 +438,7 @@ function onContactSelectionChange(event) { if (rows.length == 1) { var node = $(rows[0]); - loadContact(node.getAttribute('id').fromCSSIdentifier()); + loadContact(node.getAttribute('id')); } else if (rows.length > 1) { $('contactView').update(); @@ -478,9 +479,8 @@ function onToolbarEditSelectedContacts(event) { } for (var i = 0; i < rows.length; i++) { - var id = encodeURIComponent(rows[i].fromCSSIdentifier()); openContactWindow(URLForFolderID(Contact.currentAddressBook) - + "/" + id + "/edit", rows[i]); + + "/" + rows[i] + "/edit", rows[i]); } return false; @@ -488,17 +488,16 @@ function onToolbarEditSelectedContacts(event) { function onToolbarWriteToSelectedContacts(event) { var contactsList = $('contactsList'); - var rowIds = contactsList.getSelectedRowsId(); + var rows = contactsList.getSelectedRowsId(); + var rowsWithEmail = 0; - if (rowIds.length == 0) { + if (rows.length == 0) { showAlertDialog(_("Please select a contact.")); } else { openMailComposeWindow(ApplicationBaseURL + "/../Mail/compose" + "?folder=" + Contact.currentAddressBook.substring(1) - + "&uid=" + rowIds.collect(function(id) { - return encodeURIComponent(id.fromCSSIdentifier()); - }).join("&uid=")); + + "&uid=" + rows.join("&uid=")); if (document.body.hasClassName("popup")) window.close(); } @@ -525,28 +524,26 @@ function onToolbarDeleteSelectedContactsConfirm(dialogId) { var contactsList = $('contactsList'); var rowIds = contactsList.getSelectedRowsId(); var urlstr = (URLForFolderID(Contact.currentAddressBook) + "/batchDelete"); - for (var i = 0; i < rowIds.length; i++) $(rowIds[i]).hide(); triggerAjaxRequest(urlstr, onContactDeleteEventCallback, rowIds, - ('ids=' + rowIds.collect(function(id) { - return encodeURIComponent(id.fromCSSIdentifier()); + ('ids=' + rowIds.collect(function (s) { + return encodeURIComponent(s.unescapeHTML()); }).join(",")), { "Content-type": "application/x-www-form-urlencoded" }); } function onContactDeleteEventCallback(http) { + var rowIds = http.callbackData; if (http.readyState == 4) { if (isHttpStatus204(http.status)) { - var rowIds = http.callbackData; var row; var nextRow = null; for (var i = 0; i < rowIds.length; i++) { - var id = rowIds[i].fromCSSIdentifier(); - delete cachedContacts[Contact.currentAddressBook + "/" + id]; + delete cachedContacts[Contact.currentAddressBook + "/" + rowIds[i]]; row = $(rowIds[i]); var displayName = row.readAttribute("contactname"); - if (Contact.currentContactId == id) { + if (Contact.currentContactId == row) { Contact.currentContactId = null; } var nextRow = row.next("tr"); @@ -558,7 +555,7 @@ function onContactDeleteEventCallback(http) { } } if (nextRow) { - Contact.currentContactId = nextRow.getAttribute("id").fromCSSIdentifier(); + Contact.currentContactId = nextRow.getAttribute("id"); nextRow.selectElement(); loadContact(Contact.currentContactId); } @@ -673,7 +670,7 @@ function onConfirmContactSelection(event) { var contactsList = $("contactsList"); var rows = contactsList.getSelectedRows(); for (i = 0; i < rows.length; i++) { - var cid = rows[i].getAttribute("id").fromCSSIdentifier(); + var cid = rows[i].getAttribute("id"); if (cid.endsWith(".vlf")) { addListToOpener(tag, Contact.currentAddressBook, currentAddressBookName, cid); } @@ -1298,7 +1295,7 @@ function onDocumentKeydown(event) { else if (keyCode == Event.KEY_DOWN || keyCode == Event.KEY_UP) { if (Contact.currentContactId) { - var row = $(Contact.currentContactId.asCSSIdentifier()); + var row = $(Contact.currentContactId); var nextRow; if (keyCode == Event.KEY_DOWN) nextRow = row.next("tr"); @@ -1322,7 +1319,7 @@ function onDocumentKeydown(event) { // Select and load the next message nextRow.selectElement(); - loadContact(nextRow.readAttribute("id").fromCSSIdentifier()); + loadContact(nextRow.readAttribute("id")); } Event.stop(event); } @@ -1468,12 +1465,11 @@ function onCategoriesMenuItemClick() { var rowIds = contactsList.getSelectedRowsId(); if (rowIds.length > 0) { for (var i = 0; i < rowIds.length; i++) { - var id = rowIds[i].fromCSSIdentifier(); var url = (URLForFolderID(Contact.currentAddressBook) - + "/" + encodeURIComponent(id) + "/" + method); + + "/" + rowIds[i] + "/" + method); url += "?category=" + encodeURIComponent(this.category); triggerAjaxRequest(url, onCategoriesMenuItemCallback, - { 'addressBook' : Contact.currentAddressBook, 'id' : id }); + { 'addressBook' : Contact.currentAddressBook, 'id' : rowIds[i] }); if (set) { setCategoryOnNode($(rowIds[i]), this.category); } @@ -1501,7 +1497,7 @@ function onCategoriesMenuItemCallback(http) { function setCategoryOnNode(contactNode, category) { var catList = contactNode.getAttribute("categories"); - var catsArray = catList? catList.split(",") : []; + var catsArray = catList.split(","); if (catsArray.indexOf(category) == -1) { catsArray.push(category); contactNode.setAttribute("categories", catsArray.join(",")); @@ -1611,9 +1607,9 @@ function dropSelectedContacts(action, toId) { if ((!currentFolderIsRemote() || action != "move") && fromId.substring(1) != toId) { - var url = ApplicationBaseURL + fromId + "/" + action; - var uids = contactIds.collect(function(id) { - return encodeURIComponent(id.fromCSSIdentifier()); + var url = ApplicationBaseURL + "/" + fromId + "/" + action; + var uids = contactIds.collect(function (s) { + return encodeURIComponent(s.unescapeHTML()); }).join('&uid='); triggerAjaxRequest(url, actionContactCallback, fromId, ('folder='+ toId + '&uid=' + uids), diff --git a/UI/WebServerResources/JavascriptAPIExtensions.js b/UI/WebServerResources/JavascriptAPIExtensions.js index 12db867bd..245078268 100644 --- a/UI/WebServerResources/JavascriptAPIExtensions.js +++ b/UI/WebServerResources/JavascriptAPIExtensions.js @@ -73,10 +73,6 @@ String.prototype.decodeEntities = function() { }); }; -String.prototype.unescapeHTMLEntities = function() { - return this.unescapeHTML().replace(/"/g,'"'); -}; - String.prototype.asDate = function () { var newDate; var date = this.split("/"); @@ -98,39 +94,20 @@ String.prototype.asDate = function () { return newDate; }; -RegExp.escape = function(text) { - return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); -} - -var css_invalid_characters = [ '_' , '.', '#' , '@' , '*', ':' , ';' , ',' , ' ', - '(', ')', '[', ']', '{', '}', - "'", '"', '&', '+' ]; -var css_escape_characters = [ '_U_', '_D_', '_H_', '_A_', '_S_', '_C_', '_SC_', '_CO_', '_SP_', - '_LP_', '_RP_', '_LS_', '_RQ_', '_LC_', '_RC_', - '_SQ_', '_DQ_', '_AM_', '_P_' ]; - String.prototype.asCSSIdentifier = function() { + var characters = [ '_' , '\\.', '#' , '@' , '\\*', ':' , ',' , ' ' + , "'", '&', '\\+' ]; + var escapeds = [ '_U_', '_D_', '_H_', '_A_', '_S_', '_C_', '_CO_', + '_SP_', '_SQ_', '_AM_', '_P_' ]; + var newString = this; - for (var i = 0; i < css_invalid_characters.length; i++) { - var re = new RegExp(RegExp.escape(css_invalid_characters[i]), 'g'); - newString = newString.replace(re, css_escape_characters[i]); + for (var i = 0; i < characters.length; i++) { + var re = new RegExp(characters[i], 'g'); + newString = newString.replace(re, escapeds[i]); } - if (/^\d/.test(newString)) + if (/^\d+/.test(newString)) { newString = '_' + newString; - - return newString; -}; - -String.prototype.fromCSSIdentifier = function() { - var newString = this; - - if (/^_\d/.test(newString)) - newString = newString.substring(1); - - for (var i = 0; i < css_escape_characters.length; i++) { - var re = new RegExp(css_escape_characters[i], 'g'); - newString = newString.replace(re, css_invalid_characters[i]); } return newString; diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index 2151ce95e..5ee6cb528 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -1015,32 +1015,6 @@ function eventsListCallback(http) { if (http.responseText.length > 0) { var data = http.responseText.evalJSON(true); - - // [0] Event ID - // [1] Calendar ID - // [2] Calendar name - // [3] Status - // [4] Title - // [5] Start date - // [6] End date - // [7] Location - // [8] Is all day? - // [9] Classification (0 = public, 1, = private, 2 = confidential) - // [10] Category - // [11] Participants email addresses - // [12] Participants states - // [13] Owner - // [14] Is cyclic? - // [15] Next alarm - // [16] recurrence-id - // [17] isException - // [18] Editable? - // [19] Erasable? - // [20] Owner is organizer? - // [21] Description - // [22] Formatted start date - // [23] Formatted end date - for (var i = 0; i < data.length; i++) { var row = createElement("tr"); table.tBodies[0].appendChild(row); @@ -1082,12 +1056,12 @@ function eventsListCallback(http) { td = createElement("td"); row.appendChild(td); td.observe("mousedown", listRowMouseDownHandler, true); - td.update(data[i][22]); // start date + td.update(data[i][21]); // start date td = createElement("td"); row.appendChild(td); td.observe("mousedown", listRowMouseDownHandler, true); - td.update(data[i][23]); // end date + td.update(data[i][22]); // end date td = createElement("td"); row.appendChild(td); @@ -1191,9 +1165,8 @@ function tasksListCallback(http) { // [12] Owner // [13] recurrence-id // [14] isException - // [15] Description - // [16] Status CSS class (duelater, completed, etc) - // [17] Due date (formatted) + // [15] Status CSS class (duelater, completed, etc) + // [16] Due date (formatted) for (var i = 0; i < data.length; i++) { var row = createElement("tr"); @@ -1209,12 +1182,16 @@ function tasksListCallback(http) { if (rTime) id += "-" + escape(rTime); row.setAttribute("id", id); + //row.cname = escape(data[i][0]); + //row.calendar = calendar; if (rTime) row.recurrenceTime = escape(rTime); row.isException = data[i][14]; + + //row.setAttribute("id", calendar + "-" + cname); //listItem.addClassName(data[i][5]); // Classification - row.addClassName(data[i][16]); // status + //row.addClassName(data[i][14]); // status row.addClassName("taskRow"); row.calendar = calendar; row.cname = cname; @@ -1259,8 +1236,8 @@ function tasksListCallback(http) { cell = createElement("td"); row.appendChild(cell); - if (data[i][17]) - cell.update(data[i][17]); // end date + if (data[i][16]) + cell.update(data[i][16]); // end date cell = createElement("td"); row.appendChild(cell); diff --git a/packaging/rhel/sogo.spec b/packaging/rhel/sogo.spec index d853d23b0..0b73d702b 100644 --- a/packaging/rhel/sogo.spec +++ b/packaging/rhel/sogo.spec @@ -253,9 +253,6 @@ cp Scripts/logrotate ${RPM_BUILD_ROOT}/etc/logrotate.d/sogo %if 0%{?_with_systemd} cp Scripts/sogo-systemd-redhat ${RPM_BUILD_ROOT}/usr/lib/systemd/system/sogod.service chmod 644 ${RPM_BUILD_ROOT}/usr/lib/systemd/system/sogod.service - mkdir ${RPM_BUILD_ROOT}/etc/tmpfiles.d - cp Scripts/sogo-systemd.conf ${RPM_BUILD_ROOT}/etc/tmpfiles.d/sogo.conf - chmod 644 ${RPM_BUILD_ROOT}/etc/tmpfiles.d/sogo.conf %else cp Scripts/sogo-init.d-redhat ${RPM_BUILD_ROOT}/etc/init.d/sogod chmod 755 ${RPM_BUILD_ROOT}/etc/init.d/sogod @@ -292,7 +289,6 @@ rm -fr ${RPM_BUILD_ROOT} %if 0%{?_with_systemd} /usr/lib/systemd/system/sogod.service -/etc/tmpfiles.d/sogo.conf %else /etc/init.d/sogod %endif From 9414df26c52fc9086233ff0110949dd6c1bf0116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Wed, 29 Jul 2015 16:35:20 +0200 Subject: [PATCH 18/20] getUIDForEmail works on multidomain: returns login This method is used everywhere to try to retrieve the login of the user (and normally use the return value to [SOGoUser initwithLogin: ...]) In multidomain environments (with DomainLessLogin = false) there were several paths (mostly in SOGoAppointmentObject.m) that were trying to create SOGoUser objects with incorrect login: using only the uid part, not full email. Then like domain based uid was enabled, these users had DomainLessLogin set to true and further calls tried to authenticate only with the uid part (and they should not). This affects to several methods in: * ActiveSync/SOGoActiveSyncDispatcher.m * Appointments/SOGoAppointmentFolder.m * Appointments/SOGoAppointmentObject.m * Appointments/SOGoCalendarComponent.m * SOGoSAML2Session.m Probably a few features related with calendars are now fixed or working as intended in multidomain environments where the email is used as login --- SoObjects/SOGo/SOGoUserManager.m | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/SoObjects/SOGo/SOGoUserManager.m b/SoObjects/SOGo/SOGoUserManager.m index 127c6756c..af21f8635 100644 --- a/SoObjects/SOGo/SOGoUserManager.m +++ b/SoObjects/SOGo/SOGoUserManager.m @@ -394,11 +394,22 @@ static Class NSNullK; - (NSString *) getUIDForEmail: (NSString *) email { - NSDictionary *contactInfos; + NSDictionary *info; + SOGoSystemDefaults *sd; + NSString *uid, *domain; - contactInfos = [self contactInfosForUserWithUIDorEmail: email]; + info = [self contactInfosForUserWithUIDorEmail: email]; + uid = [info objectForKey: @"c_uid"]; - return [contactInfos objectForKey: @"c_uid"]; + sd = [SOGoSystemDefaults sharedSystemDefaults]; + if ([sd enableDomainBasedUID] + && ![[info objectForKey: @"DomainLessLogin"] boolValue]) + { + domain = [info objectForKey: @"c_domain"]; + uid = [NSString stringWithFormat: @"%@@%@", uid, domain]; + } + + return uid; } - (BOOL) _sourceChangePasswordForLogin: (NSString *) login From 2aba083147bc9b89d93fac1185c536baa44fcc65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Thu, 30 Jul 2015 10:07:55 +0200 Subject: [PATCH 19/20] oc: Update ChangeNumber after setting folder properties As required by operations like SynchronizationImportHierarchyChanges a new change number must be generated when a change in a folder is set. This affects to subfolders. See [MS-OXCFXICS] Section 3.2.5.9.4.3 for details. --- OpenChange/MAPIStoreFolder.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/OpenChange/MAPIStoreFolder.m b/OpenChange/MAPIStoreFolder.m index 15ca68fc0..7ca80e749 100644 --- a/OpenChange/MAPIStoreFolder.m +++ b/OpenChange/MAPIStoreFolder.m @@ -1053,9 +1053,10 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe { static enum MAPITAGS bannedProps[] = { PR_MID, PR_FID, PR_PARENT_FID, PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, - PR_CHANGE_KEY, 0x00000000 }; + PR_CHANGE_KEY, PidTagChangeNumber, 0x00000000 }; enum MAPITAGS *currentProp; NSMutableDictionary *propsCopy; + uint64_t cn; /* TODO: this should no longer be required once mapistore v2 API is in place, when we can then do this from -dealloc below */ @@ -1073,6 +1074,12 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe } [properties addEntriesFromDictionary: propsCopy]; + + /* Update change number after setting the properties */ + cn = [[self context] getNewChangeNumber]; + [properties setObject: [NSNumber numberWithUnsignedLongLong: cn] + forKey: MAPIPropertyKey (PidTagChangeNumber)]; + [dbFolder save]; } From 26bd1b30fac3c168d0ea33b9c2464ebd9a363b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20Garc=C3=ADa?= Date: Mon, 3 Aug 2015 09:48:06 +0200 Subject: [PATCH 20/20] Revert "Merge pull request #164 from Zentyal/jgarcia/revert-merge-inverse" This reverts commit 1404dbdb8818d950bb74aa2b7fbee49ace70f1f5, reversing changes made to 72cbd9a45f2becdd3c840356b028e9aca4feb4ee. --- ActiveSync/NSData+ActiveSync.m | 28 ++- ActiveSync/NSString+ActiveSync.m | 99 ++++++++- ActiveSync/SOGoActiveSyncDispatcher+Sync.m | 21 +- ActiveSync/SOGoActiveSyncDispatcher.m | 195 ++++++++++++++---- ActiveSync/SOGoMailObject+ActiveSync.m | 46 ++++- ActiveSync/iCalEvent+ActiveSync.m | 26 ++- Documentation/SOGoInstallationGuide.asciidoc | 24 ++- NEWS | 20 ++ Scripts/sogo-systemd.conf | 2 + ...sh => sql-update-2.2.17_to_2.3.0-mysql.sh} | 1 + ...2.3.0.sh => sql-update-2.2.17_to_2.3.0.sh} | 3 +- .../Appointments/SOGoAppointmentFolder.m | 2 + .../Appointments/SOGoAppointmentObject.m | 23 ++- SoObjects/Appointments/iCalAlarm+SOGo.h | 6 +- SoObjects/Appointments/iCalAlarm+SOGo.m | 5 +- SoObjects/Mailer/SOGoMailObject.m | 2 - SoObjects/SOGo/NSString+Utilities.h | 5 +- SoObjects/SOGo/NSString+Utilities.m | 23 ++- SoObjects/SOGo/SOGoDefaults.plist | 7 +- SoObjects/SOGo/SOGoDomainDefaults.h | 2 - SoObjects/SOGo/SOGoDomainDefaults.m | 5 - SoObjects/SOGo/SOGoUser.m | 26 ++- SoObjects/SOGo/SOGoUserDefaults.m | 2 +- SoObjects/SOGo/SOGoUserManager.m | 153 +++++++++----- Tools/SOGoToolBackup.m | 47 +++-- Tools/SOGoToolRestore.h | 1 + Tools/SOGoToolRestore.m | 30 ++- Tools/sogo-tool.m | 4 +- UI/Contacts/UIxContactEditor.m | 7 +- UI/Contacts/UIxContactView.m | 39 ++-- UI/Contacts/UIxListEditor.m | 9 +- UI/MainUI/SOGoUserHomePage.m | 2 +- UI/PreferencesUI/UIxPreferences.h | 1 - UI/PreferencesUI/UIxPreferences.m | 11 - UI/SOGoUI/UIxComponent.m | 4 +- .../ContactsUI/UIxContactFoldersView.wox | 10 +- UI/Templates/UIxJSClose.wox | 2 +- UI/WebServerResources/ContactsUI.js | 82 ++++---- .../JavascriptAPIExtensions.js | 41 +++- UI/WebServerResources/SchedulerUI.js | 45 +++- packaging/rhel/sogo.spec | 4 + 41 files changed, 774 insertions(+), 291 deletions(-) create mode 100644 Scripts/sogo-systemd.conf rename Scripts/{sql-update-2.1.17_to_2.3.0-mysql.sh => sql-update-2.2.17_to_2.3.0-mysql.sh} (95%) rename Scripts/{sql-update-2.1.17_to_2.3.0.sh => sql-update-2.2.17_to_2.3.0.sh} (89%) diff --git a/ActiveSync/NSData+ActiveSync.m b/ActiveSync/NSData+ActiveSync.m index dd925c715..cf059e66f 100644 --- a/ActiveSync/NSData+ActiveSync.m +++ b/ActiveSync/NSData+ActiveSync.m @@ -66,7 +66,33 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // - (NSString *) activeSyncRepresentationInContext: (WOContext *) context { - return [[self stringByEncodingBase64] stringByReplacingString: @"\n" withString: @""]; + NSString *tmp, *s; + unichar *buf, *start, c; + int len, i, j; + + tmp = [self stringByEncodingBase64] ; + + len = [tmp length]; + + start = buf = (unichar *)malloc(len*sizeof(unichar)); + [tmp getCharacters: buf range: NSMakeRange(0, len)]; + + for (i = 0, j = 0; i < len; i++) + { + c = *buf; + + if (!(c == 0xA)) + { + *(start+j) = c; + j++; + } + + buf++; + } + + s = [[NSString alloc] initWithCharactersNoCopy: start length: j freeWhenDone: YES]; + + return AUTORELEASE(s); } - (NSData *) wbxml2xml diff --git a/ActiveSync/NSString+ActiveSync.m b/ActiveSync/NSString+ActiveSync.m index a66f38252..1564b7482 100644 --- a/ActiveSync/NSString+ActiveSync.m +++ b/ActiveSync/NSString+ActiveSync.m @@ -46,6 +46,99 @@ static NSArray *easCommandParameters = nil; @implementation NSString (ActiveSync) +// +// This is a copy from NSString+XMLEscaping.m from SOPE. +// The difference here is that we use wchar_t instead of unichar. +// This is needed to get the rigth numeric character reference. +// e.g. SMILING FACE WITH OPEN MOUTH +// ok: wchar_t -> 😃 wrong: unichar -> � � +// +// We avoir naming it like the one in SOPE since if the ActiveSync +// bundle is loaded, it'll overwrite the one provided by SOPE. +// +- (NSString *) _stringByEscapingXMLStringUsingCharacters { + register unsigned i, len, j; + register wchar_t *buf; + const wchar_t *chars; + unsigned escapeCount; + + if ([self length] == 0) return @""; + + NSData *data = [self dataUsingEncoding:NSUTF32StringEncoding]; + chars = [data bytes]; + len = [data length]/4; + + /* check for characters to escape ... */ + for (i = 0, escapeCount = 0; i < len; i++) { + switch (chars[i]) { + case '&': case '"': case '<': case '>': case '\r': + escapeCount++; + break; + default: + if (chars[i] > 127) + escapeCount++; + break; + } + } + if (escapeCount == 0 ) { + /* nothing to escape ... */ + return [[self copy] autorelease]; + } + + buf = calloc((len + 5) + (escapeCount * 16), sizeof(wchar_t)); + for (i = 0, j = 0; i < len; i++) { + switch (chars[i]) { + /* escape special chars */ + case '\r': + buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '1'; j++; + buf[j] = '3'; j++; buf[j] = ';'; j++; + break; + case '&': + buf[j] = '&'; j++; buf[j] = 'a'; j++; buf[j] = 'm'; j++; + buf[j] = 'p'; j++; buf[j] = ';'; j++; + break; + case '"': + buf[j] = '&'; j++; buf[j] = 'q'; j++; buf[j] = 'u'; j++; + buf[j] = 'o'; j++; buf[j] = 't'; j++; buf[j] = ';'; j++; + break; + case '<': + buf[j] = '&'; j++; buf[j] = 'l'; j++; buf[j] = 't'; j++; + buf[j] = ';'; j++; + break; + case '>': + buf[j] = '&'; j++; buf[j] = 'g'; j++; buf[j] = 't'; j++; + buf[j] = ';'; j++; + break; + + default: + /* escape big chars */ + if (chars[i] > 127) { + unsigned char nbuf[32]; + unsigned int k; + + sprintf((char *)nbuf, "&#%i;", (int)chars[i]); + for (k = 0; nbuf[k] != '\0'; k++) { + buf[j] = nbuf[k]; + j++; + } + } + else if (chars[i] == 0x9 || chars[i] == 0xA || chars[i] == 0xD || chars[i] >= 0x20) { // ignore any unsupported control character + /* nothing to escape */ + buf[j] = chars[i]; + j++; + } + break; + } + } + + self = [[NSString alloc] initWithBytesNoCopy: buf + length: (j*sizeof(wchar_t)) + encoding: NSUTF32StringEncoding + freeWhenDone: YES]; + + return [self autorelease]; +} + - (NSString *) sanitizedServerIdWithType: (SOGoMicrosoftActiveSyncFolderType) folderType { if (folderType == ActiveSyncEventFolder) @@ -65,11 +158,7 @@ static NSArray *easCommandParameters = nil; - (NSString *) activeSyncRepresentationInContext: (WOContext *) context { - NSString *s; - - s = [self safeString]; - - return [s stringByEscapingHTMLString]; + return [self _stringByEscapingXMLStringUsingCharacters]; } - (int) activeSyncFolderType diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index e0561ff31..47ec81990 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -453,16 +453,27 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType inBuffer: (NSMutableString *) theBuffer { + + id aDelete, sogoObject, value; NSArray *deletions; NSString *serverId; - id aDelete, sogoObject; + BOOL deletesAsMoves, useTrash; int i; deletions = (id)[theDocumentElement getElementsByTagName: @"Delete"]; if ([deletions count]) { + // From the documention, if DeletesAsMoves is missing, we must assume it's a YES. + // See https://msdn.microsoft.com/en-us/library/gg675480(v=exchg.80).aspx for all details. + value = [theDocumentElement getElementsByTagName: @"DeletesAsMoves"]; + deletesAsMoves = YES; + useTrash = YES; + + if ([value count] && [[[value lastObject] textValue] length]) + deletesAsMoves = [[[value lastObject] textValue] boolValue]; + for (i = 0; i < [deletions count]; i++) { aDelete = [deletions objectAtIndex: i]; @@ -474,7 +485,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. acquire: NO]; if (![sogoObject isKindOfClass: [NSException class]]) - [sogoObject delete]; + { + // FIXME: handle errors here + if (deletesAsMoves) + [(SOGoMailFolder *)[sogoObject container] deleteUIDs: [NSArray arrayWithObjects: serverId, nil] useTrashFolder: &useTrash inContext: context]; + else + [sogoObject delete]; + } [theBuffer appendString: @""]; [theBuffer appendFormat: @"%@", serverId]; diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index c2f64786e..b107b94f0 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -453,7 +453,7 @@ static BOOL debugOn = NO; [s appendString: @""]; [s appendFormat: @"%d", 1]; [s appendFormat: @"%@", syncKey]; - [s appendFormat: @"%@", nameInContainer]; + [s appendFormat: @"%@", [nameInContainer stringByEscapingURL]]; [s appendString: @""]; d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; @@ -1084,6 +1084,8 @@ static BOOL debugOn = NO; SOGoMailAccounts *accountsFolder; SOGoUserFolder *userFolder; SOGoMailObject *mailObject; + NSArray *partKeys; + int p; NSRange r1, r2; @@ -1103,7 +1105,14 @@ static BOOL debugOn = NO; acquire: NO]; mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO]; - currentBodyPart = [mailObject lookupImap4BodyPartKey: pathToPart inContext: context]; + + partKeys = [pathToPart componentsSeparatedByString: @"."]; + + currentBodyPart = [mailObject lookupImap4BodyPartKey: [partKeys objectAtIndex:0] inContext: context]; + for (p = 1; p < [partKeys count]; p++) + { + currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context]; + } [theResponse setHeader: [NSString stringWithFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]] forKey: @"Content-Type"]; @@ -1158,11 +1167,11 @@ static BOOL debugOn = NO; { collectionId = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"CollectionId"] lastObject] textValue]; realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; - + if (folderType == ActiveSyncMailFolder) - nameInCache = [NSString stringWithFormat: @"folder%@", realCollectionId]; + nameInCache = [NSString stringWithFormat: @"folder%@", realCollectionId]; else - nameInCache = collectionId; + nameInCache = collectionId; realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; @@ -1234,10 +1243,11 @@ static BOOL debugOn = NO; - (void) processItemOperations: (id ) theDocumentElement inResponse: (WOResponse *) theResponse { - NSString *fileReference, *realCollectionId; + NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *collectionId; NSMutableString *s; NSArray *fetchRequests; id aFetch; + NSData *d; int i; SOGoMicrosoftActiveSyncFolderType folderType; @@ -1247,8 +1257,6 @@ static BOOL debugOn = NO; [s appendString: @""]; [s appendString: @""]; [s appendString: @""]; - [s appendString: @"1"]; - [s appendString: @""]; fetchRequests = (id)[theDocumentElement getElementsByTagName: @"Fetch"]; @@ -1256,17 +1264,25 @@ static BOOL debugOn = NO; { NSMutableData *bytes, *parts; NSMutableArray *partLength; - NSData *d; bytes = [NSMutableData data]; parts = [NSMutableData data]; partLength = [NSMutableArray array]; + [s appendString: @"1"]; + [s appendString: @""]; + for (i = 0; i < [fetchRequests count]; i++) { aFetch = [fetchRequests objectAtIndex: i]; fileReference = [[[(id)[aFetch getElementsByTagName: @"FileReference"] lastObject] textValue] stringByUnescapingURL]; - realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType]; + collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue]; + + // its either a itemOperation to fetch an attachment or an email + if ([fileReference length]) + realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType]; + else + realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; if (folderType == ActiveSyncMailFolder) { @@ -1276,43 +1292,80 @@ static BOOL debugOn = NO; SOGoUserFolder *userFolder; SOGoMailObject *mailObject; - NSRange r1, r2; - - r1 = [realCollectionId rangeOfString: @"/"]; - r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)]; - - folderName = [realCollectionId substringToIndex: r1.location]; - messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)]; - pathToPart = [realCollectionId substringFromIndex: r2.location+1]; - - userFolder = [[context activeUser] homeFolderInContext: context]; - accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; - currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; - - currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", folderName] - inContext: context - acquire: NO]; - - mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO]; - currentBodyPart = [mailObject lookupImap4BodyPartKey: pathToPart inContext: context]; - - [s appendString: @""]; - [s appendString: @"1"]; - [s appendFormat: @"%@", [fileReference stringByEscapingURL]]; - [s appendString: @""]; - - [s appendFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]]; - - if ([[theResponse headerForKey: @"Content-Type"] isEqualToString:@"application/vnd.ms-sync.multipart"]) + if ([fileReference length]) { - [s appendFormat: @"%d", i+1]; - [partLength addObject: [NSNumber numberWithInteger: [[currentBodyPart fetchBLOB] length]]]; - [parts appendData:[currentBodyPart fetchBLOB]]; + // fetch attachment + NSRange r1, r2; + NSArray *partKeys; + int p; + + r1 = [realCollectionId rangeOfString: @"/"]; + r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)]; + + folderName = [realCollectionId substringToIndex: r1.location]; + messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)]; + pathToPart = [realCollectionId substringFromIndex: r2.location+1]; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", folderName] + inContext: context + acquire: NO]; + + mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO]; + + partKeys = [pathToPart componentsSeparatedByString: @"."]; + + currentBodyPart = [mailObject lookupImap4BodyPartKey: [partKeys objectAtIndex:0] inContext: context]; + for (p = 1; p < [partKeys count]; p++) + { + currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context]; + } + + [s appendString: @""]; + [s appendString: @"1"]; + [s appendFormat: @"%@", [fileReference stringByEscapingURL]]; + [s appendString: @""]; + + [s appendFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]]; + + if ([[theResponse headerForKey: @"Content-Type"] isEqualToString:@"application/vnd.ms-sync.multipart"]) + { + NSData *d; + d = [currentBodyPart fetchBLOB]; + + [s appendFormat: @"%d", i+1]; + [partLength addObject: [NSNumber numberWithInteger: [d length]]]; + [parts appendData: d]; + } + else + { + NSString *a; + a = [[currentBodyPart fetchBLOB] activeSyncRepresentationInContext: context]; + + [s appendFormat: @"0-%d", [a length]-1]; + [s appendFormat: @"%@", a]; + } } else { - [s appendFormat: @"0-%d", [[[currentBodyPart fetchBLOB] activeSyncRepresentationInContext: context] length]-1]; - [s appendFormat: @"%@", [[currentBodyPart fetchBLOB] activeSyncRepresentationInContext: context]]; + // fetch mail + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; + serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue]; + bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue]; + [context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"]; + + currentCollection = [self collectionFromId: realCollectionId type: folderType]; + + mailObject = [currentCollection lookupName: serverId inContext: context acquire: NO]; + [s appendString: @""]; + [s appendString: @"1"]; + [s appendFormat: @"%@", collectionId]; + [s appendFormat: @"%@", serverId]; + [s appendString: @""]; + [s appendString: [mailObject activeSyncRepresentationInContext: context]]; } [s appendString: @""]; @@ -1366,6 +1419,62 @@ static BOOL debugOn = NO; { [theResponse setContent: d]; } + } + else if ([theDocumentElement getElementsByTagName: @"EmptyFolderContents"]) + { + NGImap4Connection *connection; + NSEnumerator *subfolders; + NSException *error; + NSURL *currentURL; + id co; + + collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue]; + realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; + + if (folderType == ActiveSyncMailFolder) + { + co = [self collectionFromId: realCollectionId type: folderType]; + error = [co addFlagsToAllMessages: @"deleted"]; + + if (!error) + error = [(SOGoMailFolder *)co expunge]; + + if (!error) + { + [co flushMailCaches]; + + if ([theDocumentElement getElementsByTagName: @"DeleteSubFolders"]) + { + // Delete sub-folders + connection = [co imap4Connection]; + subfolders = [[co allFolderURLs] objectEnumerator]; + + while ((currentURL = [subfolders nextObject])) + { + [[connection client] unsubscribe: [currentURL path]]; + [connection deleteMailboxAtURL: currentURL]; + } + } + + [s appendString: @"1"]; + [s appendString: @""]; + } + + if (error) + { + [s appendString: @"3"]; + [s appendString: @""]; + } + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + [theResponse setContent: d]; + } + else + { + [theResponse setStatus: 500]; + return; + } } } diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index d3095a966..164abb6db 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -381,11 +381,6 @@ struct GlobalObjectId { if (s) { - // We sanitize the content immediately, in case we have non-UNICODE safe - // characters that would be re-encoded later in HTML entities and thus, - // ignore afterwards. - s = [s safeString]; - body = [s dataUsingEncoding: NSUTF8StringEncoding]; } @@ -866,10 +861,41 @@ struct GlobalObjectId { if (d) { + NSMutableData *sanitizedData; NSString *content; int len, truncated; - - content = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding]; + + // Outlook fails to decode quoted-printable (see #3082) if lines are not termined by CRLF. + const char *bytes; + char *mbytes; + int mlen; + + len = [d length]; + mlen = 0; + + sanitizedData = [NSMutableData dataWithLength: len*2]; + + bytes = [d bytes]; + mbytes = [sanitizedData mutableBytes]; + + while (len > 0) + { + if (*bytes == '\n' && *(bytes-1) != '\r' && mlen > 0) + { + *mbytes = '\r'; + mbytes++; + mlen++; + } + + *mbytes = *bytes; + mbytes++; bytes++; + len--; + mlen++; + } + + [sanitizedData setLength: mlen]; + + content = [[NSString alloc] initWithData: sanitizedData encoding: NSUTF8StringEncoding]; // FIXME: This is a hack. We should normally avoid doing this as we might get // broken encodings. We should rather tell that the data was truncated and expect @@ -879,15 +905,15 @@ struct GlobalObjectId { // for an "interesting" discussion around this. // if (!content) - content = [[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding]; + content = [[NSString alloc] initWithData: sanitizedData encoding: NSISOLatin1StringEncoding]; AUTORELEASE(content); content = [content activeSyncRepresentationInContext: context]; truncated = 0; - + len = [content length]; - + if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) { [s appendFormat: @"%@", content]; diff --git a/ActiveSync/iCalEvent+ActiveSync.m b/ActiveSync/iCalEvent+ActiveSync.m index 586bf5798..2156b1eac 100644 --- a/ActiveSync/iCalEvent+ActiveSync.m +++ b/ActiveSync/iCalEvent+ActiveSync.m @@ -506,14 +506,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [self setOrganizer: person]; } - // - // iOS is plain stupid here. It seends event invitations with no Organizer. - // We check this corner-case and if MeetingStatus == 1 (see http://msdn.microsoft.com/en-us/library/ee219342(v=exchg.80).aspx or details) - // and there's no organizer, we fake one. - // if ((o = [theValues objectForKey: @"MeetingStatus"])) { - if ([o intValue] == 1 && ![theValues objectForKey: @"Organizer_Email"]) + // + // iOS is plain stupid here. It seends event invitations with no Organizer. + // We check this corner-case and if MeetingStatus == 1 (see http://msdn.microsoft.com/en-us/library/ee219342(v=exchg.80).aspx or details) + // and there's no organizer, we fake one. + // + if ([o intValue] == 1 && ![theValues objectForKey: @"Organizer_Email"] && ![[[self organizer] rfc822Email] length]) { iCalPerson *person; @@ -523,6 +523,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [person setPartStat: @"ACCEPTED"]; [self setOrganizer: person]; } + + // + // When MeetingResponse fails Outlook still sends a new calendar entry with MeetingStatus=3. + // Use the organizer from the request if the event has no organizer. + // + if ([o intValue] == 3 && [theValues objectForKey: @"Organizer_Email"] && ![[[self organizer] rfc822Email] length]) + { + iCalPerson *person; + person = [iCalPerson elementWithTag: @"organizer"]; + [person setEmail: [theValues objectForKey: @"Organizer_Email"]]; + [person setCn: [theValues objectForKey: @"Organizer_Name"]]; + [person setPartStat: @"ACCEPTED"]; + [self setOrganizer: person]; + } } diff --git a/Documentation/SOGoInstallationGuide.asciidoc b/Documentation/SOGoInstallationGuide.asciidoc index 5ecd53450..e337f6eb0 100644 --- a/Documentation/SOGoInstallationGuide.asciidoc +++ b/Documentation/SOGoInstallationGuide.asciidoc @@ -1920,10 +1920,11 @@ events. This parameter is an array of arbitrary strings. Defaults to a list that depends on the language. -|U |SOGoCalendarDefaultCategoryColor -|Parameter used to define the default colour of categories. +|U |SOGoCalendarCategoriesColors +|Parameter used to define the colour of categories. This parameter +is a dictionary of category name/color. -Defaults to `#F0F0F0` when unset. +Defaults to `#F0F0F0` for all categories when unset. |U |SOGoCalendarEventsDefaultClassification |Parameter used to defined the default classification for new events. @@ -2136,7 +2137,8 @@ Multi-domains Configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you want your installation to isolate two groups of users, you must -define a distinct authentication source for each _domain_. Following is +define a distinct authentication source for each _domain_. Your domain keys +must have the same value as your email domain you want to add. Following is the same configuration that now includes two domains (acme.com and coyote.com): @@ -2144,7 +2146,7 @@ coyote.com): { ... domains = { - acme = { + acme.com = { SOGoMailDomain = acme.com; SOGoDraftsFolderName = Drafts; SOGoUserSources = ( @@ -2165,7 +2167,7 @@ coyote.com): } ); }; - coyote = { + coyote.com = { SOGoMailDomain = coyote.com; SOGoIMAPServer = imap.coyote.com; SOGoUserSources = ( @@ -2196,7 +2198,7 @@ domains. [cols="3,47,50a"] |======================================================================= |S |SOGoEnableDomainBasedUID -|Parameter used to activate user identification by domain. Users will be +|Parameter used to enable user identification by domain. Users will be able (without being required) to login using the form `username@domain`, meaning that values of _UIDFieldName_ no longer have to be unique among all domains but only within the same domain. Internally, users will @@ -2711,6 +2713,11 @@ current version of SOGo from the previous release. [cols="100a"] |======================================================================= +h|2.3.1 +|The SOGoCalendarDefaultCategoryColor default has been removed. If you +want to customize the color of calendar categories, use the +SOGoCalendarCategories and SOGoCalendarCategoriesColors defaults. + h|2.3.0 |Run the shell script `sql-update-2.2.17_to_2.3.0.sh` or `sql-update-2.2.17_to_2.3.0-mysql.sh` (if you use MySQL). @@ -2718,6 +2725,9 @@ h|2.3.0 This will grow the "participant states" field of calendar quick tables to a larger size and add the the "c_description" column to calendar quick tables. +Moreover, if you are using a multi-domain configuration, make sure the keys for +your domains match the email domains you have defined. + h|2.2.8 |The configuration configuration parameters were renamed: diff --git a/NEWS b/NEWS index ec8cf7a00..080088c49 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,23 @@ +2.3.1 (2015-06-XX) +------------------ + +Enhancements + - improved EAS speed, especially when fetching big attachments + - now always enforce the organizer's default identity in appointments + - improved the handling of default calendar categories/colors (#3200) + - added support for DeletesAsMoves over EAS + +Bug fixes + - EAS's GetItemEstimate/ItemOperations now support fetching mails and empty folders + - fixed some rare cornercases in multidomain configurations + - properly escape folder after creation using EAS (#3237) + - fixed potential organizer highjacking when using EAS (#3131) + - properly support big characters in EAS and fix encoding QP EAS error for Outlook (#3082) + - properly encode id of DOM elements in Address Book module (#3239, #3245) + - fixed multi-domain support for sogo-tool backup/restore (#2600) + - fixed data ordering in events list of Calendar module (#3261) + - fixed data ordering in tasks list of Calendar module (#3267) + 2.3.0 (2015-06-01) ------------------- diff --git a/Scripts/sogo-systemd.conf b/Scripts/sogo-systemd.conf new file mode 100644 index 000000000..6755a2969 --- /dev/null +++ b/Scripts/sogo-systemd.conf @@ -0,0 +1,2 @@ +# SOGo needs directory in /var/run +d /var/run/sogo 0755 sogo sogo diff --git a/Scripts/sql-update-2.1.17_to_2.3.0-mysql.sh b/Scripts/sql-update-2.2.17_to_2.3.0-mysql.sh similarity index 95% rename from Scripts/sql-update-2.1.17_to_2.3.0-mysql.sh rename to Scripts/sql-update-2.2.17_to_2.3.0-mysql.sh index bd8f3dd77..1ed8a4818 100755 --- a/Scripts/sql-update-2.1.17_to_2.3.0-mysql.sh +++ b/Scripts/sql-update-2.2.17_to_2.3.0-mysql.sh @@ -49,6 +49,7 @@ function adjustSchema() { echo "This script will ask for the sql password twice" >&2 echo "Converting c_partstates from VARCHAR(255) to mediumtext in calendar quick tables" >&2 +echo "Adding c_description column as mediumtext in calendar quick tables" >&2 tables=`mysql -p -s -u $username -h $hostname $database -e "select SUBSTRING_INDEX(c_quick_location, '/', -1) from $indextable where c_path3 = 'Calendar';"` for table in $tables; diff --git a/Scripts/sql-update-2.1.17_to_2.3.0.sh b/Scripts/sql-update-2.2.17_to_2.3.0.sh similarity index 89% rename from Scripts/sql-update-2.1.17_to_2.3.0.sh rename to Scripts/sql-update-2.2.17_to_2.3.0.sh index fb6010d49..a9ff62559 100755 --- a/Scripts/sql-update-2.1.17_to_2.3.0.sh +++ b/Scripts/sql-update-2.2.17_to_2.3.0.sh @@ -45,7 +45,8 @@ function adjustSchema() { IFS="$oldIFS" } -echo "Converting c_cycleinfo from VARCHAR(255) to TEXT in calendar quick tables" >&2 +echo "Converting c_partstates from VARCHAR(255) to mediumtext in calendar quick tables" >&2 +echo "Adding c_description column as mediumtext in calendar quick tables" >&2 tables=`psql -t -U $username -h $hostname $database -c "select split_part(c_quick_location, '/', 5) from $indextable where c_path3 = 'Calendar';"` for table in $tables; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 915fd4aa5..f0d310bd7 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -3157,6 +3157,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir inContainer: self]; [object setIsNew: YES]; content = [NSMutableString stringWithString: @"BEGIN:VCALENDAR\n"]; + [content appendFormat: @"PRODID:-//Inverse inc./SOGo %@//EN\n", SOGoVersion]; + if (timezone) [content appendFormat: @"%@\n", [timezone versitString]]; [content appendFormat: @"%@\nEND:VCALENDAR", [event versitString]]; diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 3e1fd73ff..e26bb405d 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -1820,9 +1820,28 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent [self warnWithFormat: @"Invalid event: no end date; setting duration to %@", [event duration]]; } - if ([event organizer] && ![[[event organizer] cn] length]) + if ([event organizer]) { - [[event organizer] setCn: [[event organizer] rfc822Email]]; + NSString *uid; + + if (![[[event organizer] cn] length]) + { + [[event organizer] setCn: [[event organizer] rfc822Email]]; + } + + // We now make sure that the organizer, if managed by SOGo, is using + // its default email when creating events and inviting attendees. + uid = [[SOGoUserManager sharedUserManager] getUIDForEmail: [[event organizer] rfc822Email]]; + if (uid) + { + NSDictionary *defaultIdentity; + SOGoUser *organizer; + + organizer = [SOGoUser userWithLogin: uid]; + defaultIdentity = [organizer defaultIdentity]; + [[event organizer] setCn: [defaultIdentity objectForKey: @"fullName"]]; + [[event organizer] setEmail: [defaultIdentity objectForKey: @"email"]]; + } } } } diff --git a/SoObjects/Appointments/iCalAlarm+SOGo.h b/SoObjects/Appointments/iCalAlarm+SOGo.h index 2980f7bc6..741be01fa 100644 --- a/SoObjects/Appointments/iCalAlarm+SOGo.h +++ b/SoObjects/Appointments/iCalAlarm+SOGo.h @@ -1,6 +1,6 @@ /* iCalAlarm+SOGo.h - this file is part of SOGo * - * Copyright (C) 2014 Inverse inc. + * Copyright (C) 2015 Inverse inc. * * 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 @@ -20,11 +20,11 @@ #import -@class iCalRepeatableEntityObject; +@class iCalEntityObject; @interface iCalAlarm (SOGoExtensions) -+ (id) alarmForEvent: (iCalRepeatableEntityObject *) theEntity ++ (id) alarmForEvent: (iCalEntityObject *) theEntity owner: (NSString *) theOwner action: (NSString *) reminderAction unit: (NSString *) reminderUnit diff --git a/SoObjects/Appointments/iCalAlarm+SOGo.m b/SoObjects/Appointments/iCalAlarm+SOGo.m index c98c316db..cb1cc57c0 100644 --- a/SoObjects/Appointments/iCalAlarm+SOGo.m +++ b/SoObjects/Appointments/iCalAlarm+SOGo.m @@ -1,6 +1,6 @@ /* iCalAlarm+SOGo.m - this file is part of SOGo * - * Copyright (C) 2014 Inverse inc. + * Copyright (C) 2015 Inverse inc. * * 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 @@ -28,6 +28,7 @@ #import #import +#import @implementation iCalAlarm (SOGoExtensions) @@ -65,7 +66,7 @@ [alarm addChild: aAttendee]; } -+ (id) alarmForEvent: (iCalRepeatableEntityObject *) theEntity ++ (id) alarmForEvent: (iCalEntityObject *) theEntity owner: (NSString *) theOwner action: (NSString *) reminderAction unit: (NSString *) reminderUnit diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index 8dd632d65..2aabcaa75 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -777,8 +777,6 @@ static BOOL debugSoParts = NO; filename = [NSString stringWithFormat: @"unknown_%@", path]; else if ([mimeType isEqualToString: @"message/rfc822"]) filename = [NSString stringWithFormat: @"email_%@.eml", path]; - else if ([mimeType isEqualToString: @"text/calendar"]) - filename = [NSString stringWithFormat: @"calendar_%@.ics", path]; if (filename) diff --git a/SoObjects/SOGo/NSString+Utilities.h b/SoObjects/SOGo/NSString+Utilities.h index 8474dc4e3..d172ff167 100644 --- a/SoObjects/SOGo/NSString+Utilities.h +++ b/SoObjects/SOGo/NSString+Utilities.h @@ -1,6 +1,6 @@ /* NSString+Utilities.h - this file is part of SOGo * - * Copyright (C) 2006-2014 Inverse inc. + * Copyright (C) 2006-2015 Inverse inc. * * 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 @@ -46,6 +46,9 @@ - (NSString *) asCSSIdentifier; - (NSString *) fromCSSIdentifier; +/* JavaScript safety */ +- (NSString *) asSafeJSString; + /* SQL safety */ - (NSString *) asSafeSQLString; diff --git a/SoObjects/SOGo/NSString+Utilities.m b/SoObjects/SOGo/NSString+Utilities.m index 46f42af8e..d7d85e5f5 100644 --- a/SoObjects/SOGo/NSString+Utilities.m +++ b/SoObjects/SOGo/NSString+Utilities.m @@ -1,6 +1,6 @@ /* NSString+Utilities.m - this file is part of SOGo * - * Copyright (C) 2006-2014 Inverse inc. + * Copyright (C) 2006-2015 Inverse inc. * * 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 @@ -257,7 +257,7 @@ static int cssEscapingCount; return selfCopy; } -- (NSString *) doubleQuotedString +- (NSString *) asSafeJSString { NSMutableString *representation; @@ -270,7 +270,12 @@ static int cssEscapingCount; [representation replaceString: @"\r" withString: @"\\r"]; [representation replaceString: @"\t" withString: @"\\t"]; - return [NSString stringWithFormat: @"\"%@\"", representation]; + return representation; +} + +- (NSString *) doubleQuotedString +{ + return [NSString stringWithFormat: @"\"%@\"", [self asSafeJSString]]; } // @@ -333,12 +338,18 @@ static int cssEscapingCount; int count; strings = [NSArray arrayWithObjects: @"_U_", @"_D_", @"_H_", @"_A_", @"_S_", - @"_C_", @"_CO_", @"_SP_", @"_SQ_", @"_AM_", @"_P_", @"_DS_", nil]; + @"_C_", @"_SC_", + @"_CO_", @"_SP_", @"_SQ_", @"_DQ_", + @"_LP_", @"_RP_", @"_LS_", @"_RS_", @"_LC_", @"_RC_", + @"_AM_", @"_P_", @"_DS_", nil]; [strings retain]; cssEscapingStrings = [strings asPointersOfObjects]; - characters = [NSArray arrayWithObjects: @"_", @".", @"#", @"@", @"*", @":", - @",", @" ", @"'", @"&", @"+", @"$", nil]; + characters = [NSArray arrayWithObjects: @"_", @".", @"#", @"@", @"*", + @":", @";", + @",", @" ", @"'", @"\"", + @"(", @")", @"[", @"]", @"{", @"}", + @"&", @"+", @"$", nil]; cssEscapingCount = [strings count]; cssEscapingCharacters = NSZoneMalloc (NULL, (cssEscapingCount + 1) diff --git a/SoObjects/SOGo/SOGoDefaults.plist b/SoObjects/SOGo/SOGoDefaults.plist index e804095f0..d0557c666 100644 --- a/SoObjects/SOGo/SOGoDefaults.plist +++ b/SoObjects/SOGo/SOGoDefaults.plist @@ -70,7 +70,6 @@ SOGoMailAutoSave = "5"; - SOGoCalendarDefaultCategoryColor = "#aaa"; SOGoCalendarShouldDisplayWeekend = YES; SOGoCalendarEventsDefaultClassification = "PUBLIC"; SOGoCalendarTasksDefaultClassification = "PUBLIC"; @@ -87,6 +86,10 @@ $label4 = ("To Do", "#3333FF"); $label5 = ("Later", "#993399"); }; - + + SOGoCalendarCategories = ("Customer", "Calls", "Favorites", "Meeting", "Ideas", "Miscellaneous", "Birthday", "Anniversary", "Vacation", "Travel", "Projects", "Suppliers", "Gifts", "Clients", "Issues", "Business", "Holidays", "Personal", "Status", "Competition", "Follow up", "Public Holiday"); + + SOGoCalendarCategoriesColors = { "Customer" = "#F0F0F0"; "Calls" = "#F0F0F0"; "Favorites" = "#F0F0F0"; "Meeting" = "#F0F0F0"; "Ideas" = "#F0F0F0"; "Miscellaneous" = "#F0F0F0"; "Birthday" = "#F0F0F0"; "Anniversary" = "#F0F0F0"; "Vacation" = "#F0F0F0"; "Travel" = "#F0F0F0"; "Projects" = "#F0F0F0"; "Suppliers" = "#F0F0F0"; "Gifts" = "#F0F0F0"; "Clients" = "#F0F0F0"; "Issues" = "#F0F0F0"; "Business" = "#F0F0F0"; "Holidays" = "#F0F0F0"; "Personal" = "#F0F0F0"; "Status" = "#F0F0F0"; "Competition" = "#F0F0F0"; "Follow up" = "#F0F0F0"; "Public Holiday" = "#F0F0F0"; }; + SOGoSubscriptionFolderFormat = "%{FolderName} (%{UserName} <%{Email}>)"; } diff --git a/SoObjects/SOGo/SOGoDomainDefaults.h b/SoObjects/SOGo/SOGoDomainDefaults.h index fbc665852..7dea88121 100644 --- a/SoObjects/SOGo/SOGoDomainDefaults.h +++ b/SoObjects/SOGo/SOGoDomainDefaults.h @@ -68,8 +68,6 @@ - (NSArray *) refreshViewIntervals; - (NSString *) subscriptionFolderFormat; -- (NSString *) calendarDefaultCategoryColor; - - (NSArray *) freeBusyDefaultInterval; - (int) davCalendarStartTimeLimit; diff --git a/SoObjects/SOGo/SOGoDomainDefaults.m b/SoObjects/SOGo/SOGoDomainDefaults.m index ff02b6a96..b55cc0851 100644 --- a/SoObjects/SOGo/SOGoDomainDefaults.m +++ b/SoObjects/SOGo/SOGoDomainDefaults.m @@ -294,11 +294,6 @@ return [self stringForKey: @"SOGoLDAPContactInfoAttribute"]; } -- (NSString *) calendarDefaultCategoryColor -{ - return [self stringForKey: @"SOGoCalendarDefaultCategoryColor"]; -} - - (NSArray *) freeBusyDefaultInterval { return [self arrayForKey: @"SOGoFreeBusyDefaultInterval"]; diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 91d3b1b5a..d81dcf52c 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -165,10 +165,9 @@ // The domain is probably appended to the username; // make sure it is defined as a domain in the configuration. domain = [newLogin substringFromIndex: (r.location + r.length)]; - if ([[sd domainIds] containsObject: domain]) + if ([[sd domainIds] containsObject: domain] && + ![sd enableDomainBasedUID]) newLogin = [newLogin substringToIndex: r.location]; - else - domain = nil; if (domain != nil && ![sd enableDomainBasedUID]) // Login domains are enabled (SOGoLoginDomains) but not @@ -197,8 +196,25 @@ // When the user is associated to a domain, the [SOGoUser login] // method returns the combination login@domain while // [SOGoUser loginInDomain] only returns the login. - uid = [NSString stringWithString: realUID]; - realUID = [NSString stringWithFormat: @"%@@%@", realUID, domain]; + r = [realUID rangeOfString: domain options: NSBackwardsSearch|NSCaseInsensitiveSearch]; + + // Do NOT strip @domain.com if SOGoEnableDomainBasedUID is enabled since + // the real login most likely is the email address. + if (r.location != NSNotFound && ![sd enableDomainBasedUID]) + uid = [realUID substringToIndex: r.location-1]; + // If we don't have the domain in the UID but SOGoEnableDomainBasedUID is + // enabled, let's add it internally so so it becomes unique across + // all potential domains. + else if (r.location == NSNotFound && [sd enableDomainBasedUID]) + { + uid = [NSString stringWithString: realUID]; + realUID = [NSString stringWithFormat: @"%@@%@", realUID, domain]; + } + // We found the domain and SOGoEnableDomainBasedUID is enabled, + // we keep realUID.. This would happen for example if the user + // authenticates with foo@bar.com and the UIDFieldName is also foo@bar.com + else if ([sd enableDomainBasedUID]) + uid = [NSString stringWithString: realUID]; } } diff --git a/SoObjects/SOGo/SOGoUserDefaults.m b/SoObjects/SOGo/SOGoUserDefaults.m index 5d975f146..9cc4b38e2 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.m +++ b/SoObjects/SOGo/SOGoUserDefaults.m @@ -702,7 +702,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; - (NSDictionary *) calendarCategoriesColors { - return [self dictionaryForKey: @"SOGoCalendarCategoriesColors"]; + return [self objectForKey: @"SOGoCalendarCategoriesColors"]; } - (void) setCalendarShouldDisplayWeekend: (BOOL) newValue diff --git a/SoObjects/SOGo/SOGoUserManager.m b/SoObjects/SOGo/SOGoUserManager.m index af21f8635..d5a6a94d0 100644 --- a/SoObjects/SOGo/SOGoUserManager.m +++ b/SoObjects/SOGo/SOGoUserManager.m @@ -505,10 +505,10 @@ static Class NSNullK; NSMutableDictionary *currentUser; NSDictionary *failedCount; NSString *dictPassword, *username, *jsonUser; - SOGoSystemDefaults *dd; + SOGoSystemDefaults *sd; BOOL checkOK; - dd = [SOGoSystemDefaults sharedSystemDefaults]; + sd = [SOGoSystemDefaults sharedSystemDefaults]; username = _login; @@ -528,21 +528,9 @@ static Class NSNullK; if (r.location != NSNotFound) { - NSArray *allDomains; - int i; - *_domain = [username substringFromIndex: r.location+1]; - allDomains = [[dd dictionaryForKey: @"domains"] allValues]; - - for (i = 0; i < [allDomains count]; i++) - { - if ([*_domain isEqualToString: [[allDomains objectAtIndex: i] objectForKey: @"SOGoMailDomain"]]) - break; - } - - // We haven't found one - if (i == [allDomains count]) + if (![[[SOGoSystemDefaults sharedSystemDefaults] domainIds] containsObject: *_domain]) *_domain = nil; } } @@ -559,10 +547,10 @@ static Class NSNullK; start_time = [[failedCount objectForKey: @"InitialDate"] unsignedIntValue]; delta = current_time - start_time; - block_time = [dd failedLoginBlockInterval]; + block_time = [sd failedLoginBlockInterval]; - if ([[failedCount objectForKey: @"FailedCount"] intValue] >= [dd maximumFailedLoginCount] && - delta >= [dd maximumFailedLoginInterval] && + if ([[failedCount objectForKey: @"FailedCount"] intValue] >= [sd maximumFailedLoginCount] && + delta >= [sd maximumFailedLoginInterval] && delta <= block_time ) { *_perr = PolicyAccountLocked; @@ -581,6 +569,28 @@ static Class NSNullK; // authentication source and try to validate there, then cache it. jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: username]; currentUser = [jsonUser objectFromJSONString]; + + // + // If we are using multidomain and the UIDFieldName is not part of the email address + // we must bind without the domain part since internally, SOGo will use + // UIDFieldName @ domain as its unique identifier if the UIDFieldName is used to + // authenticate. This can happen for example of one has in LDAP: + // + // dn: uid=foo,dc=example,dc=com + // uid: foo + // mail: broccoli@example.com + // + // and authenticates with "foo", using bindFields = (uid, mail) and SOGoEnableDomainBasedUID = YES; + // Otherwise, -_sourceCheckLogin:... would have failed because SOGo would try to bind using: foo@example.com + // + if ([[currentUser objectForKey: @"DomainLessLogin"] boolValue]) + { + NSRange r; + + r = [_login rangeOfString: [NSString stringWithFormat: @"@%@", *_domain]]; + _login = [_login substringToIndex: r.location]; + } + dictPassword = [currentUser objectForKey: @"password"]; if (useCache && currentUser && dictPassword) { @@ -600,6 +610,18 @@ static Class NSNullK; currentUser = [NSMutableDictionary dictionary]; } + // Before caching user attributes, we must check if SOGoEnableDomainBasedUID is enabled + // but we don't have a domain. That would happen for example if the user authenticates + // without the domain part. We must also cache that information, since SOGo will try + // afterward to bind with UIDFieldName@domain, and it could potentially not exist + // in the authentication source. See the rationale in _sourceCheckLogin: ... + if ([sd enableDomainBasedUID] && + [username rangeOfString: @"@"].location == NSNotFound) + { + username = [NSString stringWithFormat: @"%@@%@", username, *_domain]; + [currentUser setObject: [NSNumber numberWithBool: YES] forKey: @"DomainLessLogin"]; + } + // It's important to cache the password here as we might have cached the // user's entry in -contactInfosForUserWithUIDorEmail: and if we don't // set the password and recache the entry, the password would never be @@ -613,7 +635,7 @@ static Class NSNullK; else { // If failed login "rate-limiting" is enabled, we adjust the stats - if ([dd maximumFailedLoginCount]) + if ([sd maximumFailedLoginCount]) { [[SOGoCache sharedCache] setFailedCount: ([[failedCount objectForKey: @"FailedCount"] intValue] + 1) forLogin: username]; @@ -721,9 +743,9 @@ static Class NSNullK; // // // -- (void) _fillContactInfosForUser: (NSMutableDictionary *) currentUser - withUIDorEmail: (NSString *) uid - inDomain: (NSString *) domain +- (void) _fillContactInfosForUser: (NSMutableDictionary *) theCurrentUser + withUIDorEmail: (NSString *) theUID + inDomain: (NSString *) theDomain { NSString *sourceID, *cn, *c_domain, *c_uid, *c_imaphostname, *c_imaplogin, *c_sievehostname; NSObject *currentSource; @@ -750,19 +772,28 @@ static Class NSNullK; enumerator = [access_types_list objectEnumerator]; while ((access_type = [enumerator nextObject]) != nil) - [currentUser setObject: [NSNumber numberWithBool: YES] - forKey: access_type]; + [theCurrentUser setObject: [NSNumber numberWithBool: YES] + forKey: access_type]; - sogoSources = [[self authenticationSourceIDsInDomain: domain] objectEnumerator]; + if ([[theCurrentUser objectForKey: @"DomainLessLogin"] boolValue]) + { + NSRange r; + + r = [theUID rangeOfString: [NSString stringWithFormat: @"@%@", theDomain]]; + theUID = [theUID substringToIndex: r.location]; + } + + sogoSources = [[self authenticationSourceIDsInDomain: theDomain] objectEnumerator]; userEntry = nil; while (!userEntry && (sourceID = [sogoSources nextObject])) { currentSource = [_sources objectForKey: sourceID]; - userEntry = [currentSource lookupContactEntryWithUIDorEmail: uid - inDomain: domain]; + + userEntry = [currentSource lookupContactEntryWithUIDorEmail: theUID + inDomain: theDomain]; if (userEntry) { - [currentUser setObject: sourceID forKey: @"SOGoSource"]; + [theCurrentUser setObject: sourceID forKey: @"SOGoSource"]; if (!cn) cn = [userEntry objectForKey: @"c_cn"]; if (!c_uid) @@ -784,27 +815,27 @@ static Class NSNullK; { access = [[userEntry objectForKey: access_type] boolValue]; if (!access) - [currentUser setObject: [NSNumber numberWithBool: NO] - forKey: access_type]; + [theCurrentUser setObject: [NSNumber numberWithBool: NO] + forKey: access_type]; } // We check if it's a group isGroup = [userEntry objectForKey: @"isGroup"]; if (isGroup) - [currentUser setObject: isGroup forKey: @"isGroup"]; + [theCurrentUser setObject: isGroup forKey: @"isGroup"]; // We also fill the resource attributes, if any if ([userEntry objectForKey: @"isResource"]) - [currentUser setObject: [userEntry objectForKey: @"isResource"] - forKey: @"isResource"]; + [theCurrentUser setObject: [userEntry objectForKey: @"isResource"] + forKey: @"isResource"]; if ([userEntry objectForKey: @"numberOfSimultaneousBookings"]) - [currentUser setObject: [userEntry objectForKey: @"numberOfSimultaneousBookings"] - forKey: @"numberOfSimultaneousBookings"]; + [theCurrentUser setObject: [userEntry objectForKey: @"numberOfSimultaneousBookings"] + forKey: @"numberOfSimultaneousBookings"]; // This is Active Directory specific attribute (needed on OpenChange/* layer) if ([userEntry objectForKey: @"samaccountname"]) - [currentUser setObject: [userEntry objectForKey: @"samaccountname"] - forKey: @"sAMAccountName"]; + [theCurrentUser setObject: [userEntry objectForKey: @"samaccountname"] + forKey: @"sAMAccountName"]; } } @@ -816,20 +847,20 @@ static Class NSNullK; c_domain = @""; if (c_imaphostname) - [currentUser setObject: c_imaphostname forKey: @"c_imaphostname"]; + [theCurrentUser setObject: c_imaphostname forKey: @"c_imaphostname"]; if (c_imaplogin) - [currentUser setObject: c_imaplogin forKey: @"c_imaplogin"]; + [theCurrentUser setObject: c_imaplogin forKey: @"c_imaplogin"]; if (c_sievehostname) - [currentUser setObject: c_sievehostname forKey: @"c_sievehostname"]; + [theCurrentUser setObject: c_sievehostname forKey: @"c_sievehostname"]; - [currentUser setObject: emails forKey: @"emails"]; - [currentUser setObject: cn forKey: @"cn"]; - [currentUser setObject: c_uid forKey: @"c_uid"]; - [currentUser setObject: c_domain forKey: @"c_domain"]; + [theCurrentUser setObject: emails forKey: @"emails"]; + [theCurrentUser setObject: cn forKey: @"cn"]; + [theCurrentUser setObject: c_uid forKey: @"c_uid"]; + [theCurrentUser setObject: c_domain forKey: @"c_domain"]; // If our LDAP queries gave us nothing, we add at least one default // email address based on the default domain. - [self _fillContactMailRecords: currentUser]; + [self _fillContactMailRecords: theCurrentUser]; } // @@ -923,8 +954,9 @@ static Class NSNullK; - (NSDictionary *) contactInfosForUserWithUIDorEmail: (NSString *) uid inDomain: (NSString *) domain { - NSMutableDictionary *currentUser; NSString *aUID, *cacheUid, *jsonUser; + NSMutableDictionary *currentUser; + BOOL newUser; if ([uid isEqualToString: @"anonymous"]) @@ -933,12 +965,14 @@ static Class NSNullK; { // Remove the "@" prefix used to identified groups in the ACL tables. aUID = [uid hasPrefix: @"@"] ? [uid substringFromIndex: 1] : uid; - if (domain) + if (domain && [aUID rangeOfString: @"@"].location == NSNotFound) cacheUid = [NSString stringWithFormat: @"%@@%@", aUID, domain]; else cacheUid = aUID; + jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: cacheUid]; currentUser = [jsonUser objectFromJSONString]; + if ([currentUser isKindOfClass: NSNullK]) currentUser = nil; else if (!([currentUser objectForKey: @"emails"] @@ -948,8 +982,10 @@ static Class NSNullK; // that we have an occurence with only a cached password. In the // latter case, we update the entry with the remaining information // and recache the value. - if (!currentUser || ([currentUser count] == 1 && [currentUser objectForKey: @"password"])) - { + if (!currentUser || + ([currentUser count] == 1 && [currentUser objectForKey: @"password"]) || + ([currentUser count] == 2 && [currentUser objectForKey: @"password"] && [currentUser objectForKey: @"DomainLessLogin"])) + { newUser = YES; if (!currentUser) @@ -969,9 +1005,22 @@ static Class NSNullK; currentUser = nil; } else - [self _retainUser: currentUser - withLogin: cacheUid]; - } + { + SOGoSystemDefaults *sd; + + sd = [SOGoSystemDefaults sharedSystemDefaults]; + + // SOGoEnableDomainBasedUID is set to YES but we don't have a domain part. This happens in + // multi-domain environments authenticating only with the UIDFieldName + if ([sd enableDomainBasedUID] && !domain) + { + cacheUid = [NSString stringWithFormat: @"%@@%@", cacheUid, [currentUser objectForKey: @"c_domain"]]; + [currentUser setObject: [NSNumber numberWithBool: YES] forKey: @"DomainLessLogin"]; + } + + [self _retainUser: currentUser withLogin: cacheUid]; + } + } } } else diff --git a/Tools/SOGoToolBackup.m b/Tools/SOGoToolBackup.m index 8dd7ea04a..fb2b4d016 100644 --- a/Tools/SOGoToolBackup.m +++ b/Tools/SOGoToolBackup.m @@ -1,9 +1,6 @@ /* SOGoToolBackup.m - this file is part of SOGo * - * Copyright (C) 2009-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Francis Lachapelle + * Copyright (C) 2009-2015 Inverse inc. * * 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 @@ -42,6 +39,7 @@ #import #import #import +#import #import #import "SOGoTool.h" @@ -55,7 +53,7 @@ @interface SOGoToolBackup : SOGoTool { NSString *directory; - NSArray *userIDs; + NSArray *usersToBackup; } @end @@ -83,7 +81,7 @@ if ((self = [super init])) { directory = nil; - userIDs = nil; + usersToBackup = nil; } return self; @@ -92,7 +90,7 @@ - (void) dealloc { [directory release]; - [userIDs release]; + [usersToBackup release]; [super dealloc]; } @@ -143,6 +141,7 @@ lm = [SOGoUserManager sharedUserManager]; pool = [[NSAutoreleasePool alloc] init]; + max = [users count]; user = [users objectAtIndex: 0]; @@ -198,11 +197,11 @@ NSLog (@"user '%@' unknown", user); } [allUsers autorelease]; - - ASSIGN (userIDs, [allUsers objectsForKey: @"c_uid" notFoundMarker: nil]); + + ASSIGN (usersToBackup, allUsers); DESTROY(pool); - return ([userIDs count] > 0); + return ([usersToBackup count] > 0); } - (BOOL) parseArguments @@ -410,19 +409,29 @@ return YES; } -- (BOOL) exportUser: (NSString *) uid +- (BOOL) exportUser: (NSDictionary *) theUser { + NSString *exportPath, *gcsUID, *ldapUID; NSMutableDictionary *userRecord; - NSString *exportPath; - + SOGoSystemDefaults *sd; + + sd = [SOGoSystemDefaults sharedSystemDefaults]; userRecord = [NSMutableDictionary dictionary]; - exportPath = [directory stringByAppendingPathComponent: uid]; - return ([self extractUserFolders: uid + ldapUID = [theUser objectForKey: @"c_uid"]; + exportPath = [directory stringByAppendingPathComponent: ldapUID]; + + gcsUID = [theUser objectForKey: @"c_uid"]; + + if ([sd enableDomainBasedUID] && [gcsUID rangeOfString: @"@"].location == NSNotFound) + gcsUID = [NSString stringWithFormat: @"%@@%@", gcsUID, [theUser objectForKey: @"c_domain"]]; + + + return ([self extractUserFolders: gcsUID intoRecord: userRecord] - && [self extractUserLDIFRecord: uid + && [self extractUserLDIFRecord: ldapUID intoRecord: userRecord] - && [self extractUserPreferences: uid + && [self extractUserPreferences: gcsUID intoRecord: userRecord] && [userRecord writeToFile: exportPath atomically: NO]); @@ -438,10 +447,10 @@ pool = [NSAutoreleasePool new]; - max = [userIDs count]; + max = [usersToBackup count]; for (count = 0; rc && count < max; count++) { - rc = [self exportUser: [userIDs objectAtIndex: count]]; + rc = [self exportUser: [usersToBackup objectAtIndex: count]]; if ((count % 10) == 0) [pool emptyPool]; } diff --git a/Tools/SOGoToolRestore.h b/Tools/SOGoToolRestore.h index f5be03928..61438ac18 100644 --- a/Tools/SOGoToolRestore.h +++ b/Tools/SOGoToolRestore.h @@ -33,6 +33,7 @@ typedef enum SOGoToolRestoreMode { { NSString *directory; NSString *userID; + NSString *filename; NSString *restoreFolder; BOOL destructive; /* destructive mode not handled */ SOGoToolRestoreMode restoreMode; diff --git a/Tools/SOGoToolRestore.m b/Tools/SOGoToolRestore.m index a0f411bd9..712c680ba 100644 --- a/Tools/SOGoToolRestore.m +++ b/Tools/SOGoToolRestore.m @@ -1,6 +1,6 @@ /* SOGoToolRestore.m - this file is part of SOGo * - * Copyright (C) 2009-2014 Inverse inc. + * Copyright (C) 2009-2015 Inverse inc. * * 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 @@ -73,6 +73,7 @@ { directory = nil; userID = nil; + filename = nil; restoreFolder = nil; destructive = NO; } @@ -84,6 +85,7 @@ { [directory release]; [userID release]; + [filename release]; [restoreFolder release]; [super dealloc]; } @@ -152,25 +154,35 @@ - (BOOL) fetchUserID: (NSString *) identifier { - BOOL rc; + SOGoSystemDefaults *sd; SOGoUserManager *lm; NSDictionary *infos; - SOGoSystemDefaults *sd; NSString *uid = nil; + BOOL rc; + lm = [SOGoUserManager sharedUserManager]; infos = [lm contactInfosForUserWithUIDorEmail: identifier]; + uid = nil; + if (infos) { sd = [SOGoSystemDefaults sharedSystemDefaults]; - if ([sd enableDomainBasedUID]) + uid = [infos objectForKey: @"c_uid"]; + + if ([sd enableDomainBasedUID] && [uid rangeOfString: @"@"].location == NSNotFound) uid = [NSString stringWithFormat: @"%@@%@", - [infos objectForKey: @"c_uid"], - [infos objectForKey: @"c_domain"]]; + [infos objectForKey: @"c_uid"], + [infos objectForKey: @"c_domain"]]; + + if ([[infos objectForKey: @"DomainLessLogin"] boolValue]) + ASSIGN(filename, [infos objectForKey: @"c_uid"]); else - uid = [infos objectForKey: @"c_uid"]; + ASSIGN(filename, uid); } + ASSIGN (userID, uid); + if (userID) rc = YES; else @@ -608,7 +620,7 @@ NSString *importPath; BOOL rc; - importPath = [directory stringByAppendingPathComponent: userID]; + importPath = [directory stringByAppendingPathComponent: filename]; userRecord = [NSDictionary dictionaryWithContentsOfFile: importPath]; if (userRecord) { @@ -626,7 +638,7 @@ else { rc = NO; - NSLog (@"user backup file could not be loaded"); + NSLog(@"user backup (%@) file could not be loaded", importPath); } return rc; diff --git a/Tools/sogo-tool.m b/Tools/sogo-tool.m index d2d8b5c87..bc4755416 100644 --- a/Tools/sogo-tool.m +++ b/Tools/sogo-tool.m @@ -1,8 +1,6 @@ /* sogo-tool.m - this file is part of SOGo * - * Copyright (C) 2009 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2009-2015 Inverse inc. * * 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 diff --git a/UI/Contacts/UIxContactEditor.m b/UI/Contacts/UIxContactEditor.m index 82ad1941e..418945337 100644 --- a/UI/Contacts/UIxContactEditor.m +++ b/UI/Contacts/UIxContactEditor.m @@ -1,6 +1,6 @@ /* Copyright (C) 2004-2005 SKYRIX Software AG - Copyright (C) 2005-2010 Inverse inc. + Copyright (C) 2005-2015 Inverse inc. This file is part of SOGo @@ -378,9 +378,8 @@ static Class SOGoContactGCSEntryK = Nil; result = [self redirectToLocation: [self modulePath]]; else { - jsRefreshMethod - = [NSString stringWithFormat: @"refreshContacts(\"%@\")", - [contact nameInContainer]]; + jsRefreshMethod = [NSString stringWithFormat: @"refreshContacts('%@')", + [contact nameInContainer]]; result = [self jsCloseWithRefreshMethod: jsRefreshMethod]; } diff --git a/UI/Contacts/UIxContactView.m b/UI/Contacts/UIxContactView.m index 259a518e9..4a4541794 100644 --- a/UI/Contacts/UIxContactView.m +++ b/UI/Contacts/UIxContactView.m @@ -1,6 +1,6 @@ /* Copyright (C) 2004 SKYRIX Software AG - Copyright (C) 2005-2014 Inverse inc. + Copyright (C) 2005-2015 Inverse inc. This file is part of SOGo. @@ -138,9 +138,13 @@ if ([email length] > 0) { fn = [card fn]; - fn = [fn stringByReplacingString: @"\"" withString: @""]; - fn = [fn stringByReplacingString: @"'" withString: @"\\\'"]; - attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@ <%@>');\"", fn, email]; + if ([fn length] > 0) + attrs = [NSString stringWithFormat: @"%@ <%@>", fn, email]; + else + attrs = email; + attrs = [attrs stringByReplacingString: @"'" withString: @"\\'"]; + attrs = [attrs stringByReplacingString: @"\"" withString: @"\\\""]; + attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@');\"", attrs]; } else { @@ -181,16 +185,23 @@ for (i = 0; i < [emails count]; i++) { email = [[emails objectAtIndex: i] flattenedValuesForKey: @""]; - fn = [card fn]; - fn = [fn stringByReplacingString: @"\"" withString: @""]; - fn = [fn stringByReplacingString: @"'" withString: @"\\\'"]; - attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@ <%@>');\"", fn, email]; - - [secondaryEmails addObject: [self _cardStringWithLabel: nil - value: email - byEscapingHTMLString: YES - asLinkScheme: @"mailto:" - withLinkAttributes: attrs]]; + if ([email length]) + { + fn = [card fn]; + if ([fn length]) + attrs = [NSString stringWithFormat: @"%@ <%@>", fn, email]; + else + attrs = email; + attrs = [attrs stringByReplacingString: @"'" withString: @"\\'"]; + attrs = [attrs stringByReplacingString: @"\"" withString: @"\\\""]; + attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@');\"", attrs]; + + [secondaryEmails addObject: [self _cardStringWithLabel: nil + value: email + byEscapingHTMLString: YES + asLinkScheme: @"mailto:" + withLinkAttributes: attrs]]; + } } } else diff --git a/UI/Contacts/UIxListEditor.m b/UI/Contacts/UIxListEditor.m index 69d63bd21..ea4920b15 100644 --- a/UI/Contacts/UIxListEditor.m +++ b/UI/Contacts/UIxListEditor.m @@ -1,6 +1,6 @@ /* UIxListEditor.m - this file is part of SOGo * - * Copyright (C) 2008-2014 Inverse inc. + * Copyright (C) 2008-2015 Inverse inc. * * 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 @@ -29,6 +29,8 @@ #import #import +#import + #import #import #import @@ -289,9 +291,8 @@ result = [self redirectToLocation: [self modulePath]]; else { - jsRefreshMethod - = [NSString stringWithFormat: @"refreshContacts(\"%@\")", - [co nameInContainer]]; + jsRefreshMethod = [NSString stringWithFormat: @"refreshContacts('%@')", + [co nameInContainer]]; result = [self jsCloseWithRefreshMethod: jsRefreshMethod]; } } diff --git a/UI/MainUI/SOGoUserHomePage.m b/UI/MainUI/SOGoUserHomePage.m index 42f373f8d..762de1f85 100644 --- a/UI/MainUI/SOGoUserHomePage.m +++ b/UI/MainUI/SOGoUserHomePage.m @@ -463,7 +463,7 @@ if (!activeUserIsInDomain || ![uid isEqualToString: login]) { jsonLine = [NSMutableArray arrayWithCapacity: 4]; - if ([domain length]) + if ([domain length] && [uid rangeOfString: @"@"].location == NSNotFound) uid = [NSString stringWithFormat: @"%@@%@", uid, domain]; [jsonLine addObject: uid]; [jsonLine addObject: [contact objectForKey: @"cn"]]; diff --git a/UI/PreferencesUI/UIxPreferences.h b/UI/PreferencesUI/UIxPreferences.h index bbbb0a2b1..51607c9a1 100644 --- a/UI/PreferencesUI/UIxPreferences.h +++ b/UI/PreferencesUI/UIxPreferences.h @@ -44,7 +44,6 @@ NSDictionary *calendarCategoriesColors; NSArray *contactsCategories; - NSString *defaultCategoryColor; NSCalendarDate *today; // Mail labels/tags diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index 990fda1ed..c73206d0b 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -128,7 +128,6 @@ static NSArray *reminderValues = nil; calendarCategories = nil; calendarCategoriesColors = nil; - defaultCategoryColor = nil; category = nil; label = nil; @@ -175,7 +174,6 @@ static NSArray *reminderValues = nil; [vacationOptions release]; [calendarCategories release]; [calendarCategoriesColors release]; - [defaultCategoryColor release]; [category release]; [label release]; [mailLabels release]; @@ -1521,15 +1519,6 @@ static NSArray *reminderValues = nil; ASSIGN (calendarCategoriesColors, [userDefaults calendarCategoriesColors]); categoryColor = [calendarCategoriesColors objectForKey: category]; - if (!categoryColor) - { - if (!defaultCategoryColor) - { - dd = [[context activeUser] domainDefaults]; - ASSIGN (defaultCategoryColor, [dd calendarDefaultCategoryColor]); - } - categoryColor = defaultCategoryColor; - } return categoryColor; } diff --git a/UI/SOGoUI/UIxComponent.m b/UI/SOGoUI/UIxComponent.m index 336bd734d..b2a1315af 100644 --- a/UI/SOGoUI/UIxComponent.m +++ b/UI/SOGoUI/UIxComponent.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2007-2012 Inverse inc. + Copyright (C) 2007-2015 Inverse inc. Copyright (C) 2004 SKYRIX Software AG This file is part of SOGo @@ -452,7 +452,7 @@ static SoProduct *commonProduct = nil; jsClose = [UIxJSClose new]; [jsClose autorelease]; - [jsClose setRefreshMethod: methodName]; + [jsClose setRefreshMethod: [methodName doubleQuotedString]]; return jsClose; } diff --git a/UI/Templates/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/ContactsUI/UIxContactFoldersView.wox index 2f8a4c415..323badb7d 100644 --- a/UI/Templates/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/ContactsUI/UIxContactFoldersView.wox @@ -149,11 +149,11 @@ - - + var:categories="currentContact.c_categories.asSafeJSString" + var:id="currentContact.c_name.asCSSIdentifier" + var:contactname="currentContact.c_cn.asSafeJSString"> + + diff --git a/UI/Templates/UIxJSClose.wox b/UI/Templates/UIxJSClose.wox index 3a4487f5b..f1bdef0ef 100644 --- a/UI/Templates/UIxJSClose.wox +++ b/UI/Templates/UIxJSClose.wox @@ -17,7 +17,7 @@ > diff --git a/UI/WebServerResources/ContactsUI.js b/UI/WebServerResources/ContactsUI.js index fd460859c..3386bff6e 100644 --- a/UI/WebServerResources/ContactsUI.js +++ b/UI/WebServerResources/ContactsUI.js @@ -31,7 +31,7 @@ function openContactsFolder(contactsFolder, reload, idx) { var selection; if (idx) { - selection = [idx]; + selection = [idx.asCSSIdentifier()]; } else if (contactsFolder == Contact.currentAddressBook) { var contactsList = $("contactsList"); @@ -74,7 +74,7 @@ function contactsListCallback(http) { var contact = data[i]; var row = rows[i]; row.className = contact["c_component"]; - row.setAttribute("id", contact["c_name"]); + row.setAttribute("id", contact["c_name"].asCSSIdentifier()); row.setAttribute("categories", contact["c_categories"]); row.setAttribute("contactname", contact["c_cn"]); var cells = row.getElementsByTagName("TD"); @@ -111,7 +111,7 @@ function contactsListCallback(http) { for (var j = i; j < data.length; j++) { var contact = data[j]; var row = createElement("tr", - contact["c_name"], + contact["c_name"].asCSSIdentifier(), contact["c_component"], null, { categories: contact["c_categories"], @@ -272,7 +272,7 @@ function _onContactMenuAction(folderItem, action, refresh) { if (Object.isArray(document.menuTarget) && selectedFolders.length > 0) { var selectedFolderId = $(selectedFolders[0]).readAttribute("id"); var contactIds = $(document.menuTarget).collect(function(row) { - return row.getAttribute("id"); + return row.getAttribute("id").fromCSSIdentifier(); }); for (var i = 0; i < contactIds.length; i++) { @@ -283,9 +283,7 @@ function _onContactMenuAction(folderItem, action, refresh) { } var url = ApplicationBaseURL + "/" + selectedFolderId + "/" + action; - var uids = contactIds.collect(function (s) { - return encodeURIComponent(s.unescapeHTML()); - }).join('&uid='); + var uids = contactIds.collect(encodeURIComponent).join('&uid='); if (refresh) triggerAjaxRequest(url, actionContactCallback, selectedFolderId, ('folder='+ folderId + '&uid=' + uids), @@ -312,22 +310,22 @@ function onMenuExportContact (event) { if (canExport) { var selectedFolderId = $(selectedFolders[0]).readAttribute("id"); var contactIds = document.menuTarget.collect(function(row) { - return row.readAttribute("id"); + return row.readAttribute("id").fromCSSIdentifier(); }); var url = ApplicationBaseURL + "/" + selectedFolderId + "/export" - + "?uid=" + contactIds.join("&uid="); + + "?uid=" + contactIds.collect(encodeURIComponent).join("&uid="); window.location.href = url; } } function onMenuRawContact (event) { var cname = document.menuTarget.collect(function(row) { - return row.readAttribute("id"); + return row.readAttribute("id").fromCSSIdentifier(); }); $(function() { openGenericWindow(URLForFolderID(Contact.currentAddressBook) - + "/" + cname + "/raw"); + + "/" + encodeURIComponent(cname) + "/raw"); }).delay(0.1); } @@ -350,22 +348,22 @@ function actionContactCallback(http) { } } -function loadContact(idx) { +function loadContact(cname) { if (document.contactAjaxRequest) { document.contactAjaxRequest.aborted = true; document.contactAjaxRequest.abort(); } - if (cachedContacts[Contact.currentAddressBook + "/" + idx]) { + if (cachedContacts[Contact.currentAddressBook + "/" + cname]) { var div = $('contactView'); - Contact.currentContactId = idx; - div.innerHTML = cachedContacts[Contact.currentAddressBook + "/" + idx]; + Contact.currentContactId = cname; + div.innerHTML = cachedContacts[Contact.currentAddressBook + "/" + cname]; } else { var url = (URLForFolderID(Contact.currentAddressBook) - + "/" + encodeURIComponent(idx.unescapeHTML()) + "/view?noframe=1"); + + "/" + encodeURIComponent(cname) + "/view?noframe=1"); document.contactAjaxRequest - = triggerAjaxRequest(url, contactLoadCallback, idx); + = triggerAjaxRequest(url, contactLoadCallback, cname); } } @@ -418,8 +416,9 @@ function moveTo(uri) { /* contact menu entries */ function onContactRowDblClick(event) { var t = getTarget(event); - var cname = t.parentNode.getAttribute('id'); + var cname = t.parentNode.getAttribute('id').fromCSSIdentifier(); + cname = encodeURIComponent(cname); openContactWindow(URLForFolderID(Contact.currentAddressBook) + "/" + cname + "/edit", cname); @@ -438,7 +437,7 @@ function onContactSelectionChange(event) { if (rows.length == 1) { var node = $(rows[0]); - loadContact(node.getAttribute('id')); + loadContact(node.getAttribute('id').fromCSSIdentifier()); } else if (rows.length > 1) { $('contactView').update(); @@ -479,8 +478,9 @@ function onToolbarEditSelectedContacts(event) { } for (var i = 0; i < rows.length; i++) { + var id = encodeURIComponent(rows[i].fromCSSIdentifier()); openContactWindow(URLForFolderID(Contact.currentAddressBook) - + "/" + rows[i] + "/edit", rows[i]); + + "/" + id + "/edit", rows[i]); } return false; @@ -488,16 +488,17 @@ function onToolbarEditSelectedContacts(event) { function onToolbarWriteToSelectedContacts(event) { var contactsList = $('contactsList'); - var rows = contactsList.getSelectedRowsId(); - var rowsWithEmail = 0; + var rowIds = contactsList.getSelectedRowsId(); - if (rows.length == 0) { + if (rowIds.length == 0) { showAlertDialog(_("Please select a contact.")); } else { openMailComposeWindow(ApplicationBaseURL + "/../Mail/compose" + "?folder=" + Contact.currentAddressBook.substring(1) - + "&uid=" + rows.join("&uid=")); + + "&uid=" + rowIds.collect(function(id) { + return encodeURIComponent(id.fromCSSIdentifier()); + }).join("&uid=")); if (document.body.hasClassName("popup")) window.close(); } @@ -524,26 +525,28 @@ function onToolbarDeleteSelectedContactsConfirm(dialogId) { var contactsList = $('contactsList'); var rowIds = contactsList.getSelectedRowsId(); var urlstr = (URLForFolderID(Contact.currentAddressBook) + "/batchDelete"); + for (var i = 0; i < rowIds.length; i++) $(rowIds[i]).hide(); triggerAjaxRequest(urlstr, onContactDeleteEventCallback, rowIds, - ('ids=' + rowIds.collect(function (s) { - return encodeURIComponent(s.unescapeHTML()); + ('ids=' + rowIds.collect(function(id) { + return encodeURIComponent(id.fromCSSIdentifier()); }).join(",")), { "Content-type": "application/x-www-form-urlencoded" }); } function onContactDeleteEventCallback(http) { - var rowIds = http.callbackData; if (http.readyState == 4) { if (isHttpStatus204(http.status)) { + var rowIds = http.callbackData; var row; var nextRow = null; for (var i = 0; i < rowIds.length; i++) { - delete cachedContacts[Contact.currentAddressBook + "/" + rowIds[i]]; + var id = rowIds[i].fromCSSIdentifier(); + delete cachedContacts[Contact.currentAddressBook + "/" + id]; row = $(rowIds[i]); var displayName = row.readAttribute("contactname"); - if (Contact.currentContactId == row) { + if (Contact.currentContactId == id) { Contact.currentContactId = null; } var nextRow = row.next("tr"); @@ -555,7 +558,7 @@ function onContactDeleteEventCallback(http) { } } if (nextRow) { - Contact.currentContactId = nextRow.getAttribute("id"); + Contact.currentContactId = nextRow.getAttribute("id").fromCSSIdentifier(); nextRow.selectElement(); loadContact(Contact.currentContactId); } @@ -670,7 +673,7 @@ function onConfirmContactSelection(event) { var contactsList = $("contactsList"); var rows = contactsList.getSelectedRows(); for (i = 0; i < rows.length; i++) { - var cid = rows[i].getAttribute("id"); + var cid = rows[i].getAttribute("id").fromCSSIdentifier(); if (cid.endsWith(".vlf")) { addListToOpener(tag, Contact.currentAddressBook, currentAddressBookName, cid); } @@ -1295,7 +1298,7 @@ function onDocumentKeydown(event) { else if (keyCode == Event.KEY_DOWN || keyCode == Event.KEY_UP) { if (Contact.currentContactId) { - var row = $(Contact.currentContactId); + var row = $(Contact.currentContactId.asCSSIdentifier()); var nextRow; if (keyCode == Event.KEY_DOWN) nextRow = row.next("tr"); @@ -1319,7 +1322,7 @@ function onDocumentKeydown(event) { // Select and load the next message nextRow.selectElement(); - loadContact(nextRow.readAttribute("id")); + loadContact(nextRow.readAttribute("id").fromCSSIdentifier()); } Event.stop(event); } @@ -1465,11 +1468,12 @@ function onCategoriesMenuItemClick() { var rowIds = contactsList.getSelectedRowsId(); if (rowIds.length > 0) { for (var i = 0; i < rowIds.length; i++) { + var id = rowIds[i].fromCSSIdentifier(); var url = (URLForFolderID(Contact.currentAddressBook) - + "/" + rowIds[i] + "/" + method); + + "/" + encodeURIComponent(id) + "/" + method); url += "?category=" + encodeURIComponent(this.category); triggerAjaxRequest(url, onCategoriesMenuItemCallback, - { 'addressBook' : Contact.currentAddressBook, 'id' : rowIds[i] }); + { 'addressBook' : Contact.currentAddressBook, 'id' : id }); if (set) { setCategoryOnNode($(rowIds[i]), this.category); } @@ -1497,7 +1501,7 @@ function onCategoriesMenuItemCallback(http) { function setCategoryOnNode(contactNode, category) { var catList = contactNode.getAttribute("categories"); - var catsArray = catList.split(","); + var catsArray = catList? catList.split(",") : []; if (catsArray.indexOf(category) == -1) { catsArray.push(category); contactNode.setAttribute("categories", catsArray.join(",")); @@ -1607,9 +1611,9 @@ function dropSelectedContacts(action, toId) { if ((!currentFolderIsRemote() || action != "move") && fromId.substring(1) != toId) { - var url = ApplicationBaseURL + "/" + fromId + "/" + action; - var uids = contactIds.collect(function (s) { - return encodeURIComponent(s.unescapeHTML()); + var url = ApplicationBaseURL + fromId + "/" + action; + var uids = contactIds.collect(function(id) { + return encodeURIComponent(id.fromCSSIdentifier()); }).join('&uid='); triggerAjaxRequest(url, actionContactCallback, fromId, ('folder='+ toId + '&uid=' + uids), diff --git a/UI/WebServerResources/JavascriptAPIExtensions.js b/UI/WebServerResources/JavascriptAPIExtensions.js index 245078268..12db867bd 100644 --- a/UI/WebServerResources/JavascriptAPIExtensions.js +++ b/UI/WebServerResources/JavascriptAPIExtensions.js @@ -73,6 +73,10 @@ String.prototype.decodeEntities = function() { }); }; +String.prototype.unescapeHTMLEntities = function() { + return this.unescapeHTML().replace(/"/g,'"'); +}; + String.prototype.asDate = function () { var newDate; var date = this.split("/"); @@ -94,20 +98,39 @@ String.prototype.asDate = function () { return newDate; }; -String.prototype.asCSSIdentifier = function() { - var characters = [ '_' , '\\.', '#' , '@' , '\\*', ':' , ',' , ' ' - , "'", '&', '\\+' ]; - var escapeds = [ '_U_', '_D_', '_H_', '_A_', '_S_', '_C_', '_CO_', - '_SP_', '_SQ_', '_AM_', '_P_' ]; +RegExp.escape = function(text) { + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); +} +var css_invalid_characters = [ '_' , '.', '#' , '@' , '*', ':' , ';' , ',' , ' ', + '(', ')', '[', ']', '{', '}', + "'", '"', '&', '+' ]; +var css_escape_characters = [ '_U_', '_D_', '_H_', '_A_', '_S_', '_C_', '_SC_', '_CO_', '_SP_', + '_LP_', '_RP_', '_LS_', '_RQ_', '_LC_', '_RC_', + '_SQ_', '_DQ_', '_AM_', '_P_' ]; + +String.prototype.asCSSIdentifier = function() { var newString = this; - for (var i = 0; i < characters.length; i++) { - var re = new RegExp(characters[i], 'g'); - newString = newString.replace(re, escapeds[i]); + for (var i = 0; i < css_invalid_characters.length; i++) { + var re = new RegExp(RegExp.escape(css_invalid_characters[i]), 'g'); + newString = newString.replace(re, css_escape_characters[i]); } - if (/^\d+/.test(newString)) { + if (/^\d/.test(newString)) newString = '_' + newString; + + return newString; +}; + +String.prototype.fromCSSIdentifier = function() { + var newString = this; + + if (/^_\d/.test(newString)) + newString = newString.substring(1); + + for (var i = 0; i < css_escape_characters.length; i++) { + var re = new RegExp(css_escape_characters[i], 'g'); + newString = newString.replace(re, css_invalid_characters[i]); } return newString; diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index 5ee6cb528..2151ce95e 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -1015,6 +1015,32 @@ function eventsListCallback(http) { if (http.responseText.length > 0) { var data = http.responseText.evalJSON(true); + + // [0] Event ID + // [1] Calendar ID + // [2] Calendar name + // [3] Status + // [4] Title + // [5] Start date + // [6] End date + // [7] Location + // [8] Is all day? + // [9] Classification (0 = public, 1, = private, 2 = confidential) + // [10] Category + // [11] Participants email addresses + // [12] Participants states + // [13] Owner + // [14] Is cyclic? + // [15] Next alarm + // [16] recurrence-id + // [17] isException + // [18] Editable? + // [19] Erasable? + // [20] Owner is organizer? + // [21] Description + // [22] Formatted start date + // [23] Formatted end date + for (var i = 0; i < data.length; i++) { var row = createElement("tr"); table.tBodies[0].appendChild(row); @@ -1056,12 +1082,12 @@ function eventsListCallback(http) { td = createElement("td"); row.appendChild(td); td.observe("mousedown", listRowMouseDownHandler, true); - td.update(data[i][21]); // start date + td.update(data[i][22]); // start date td = createElement("td"); row.appendChild(td); td.observe("mousedown", listRowMouseDownHandler, true); - td.update(data[i][22]); // end date + td.update(data[i][23]); // end date td = createElement("td"); row.appendChild(td); @@ -1165,8 +1191,9 @@ function tasksListCallback(http) { // [12] Owner // [13] recurrence-id // [14] isException - // [15] Status CSS class (duelater, completed, etc) - // [16] Due date (formatted) + // [15] Description + // [16] Status CSS class (duelater, completed, etc) + // [17] Due date (formatted) for (var i = 0; i < data.length; i++) { var row = createElement("tr"); @@ -1182,16 +1209,12 @@ function tasksListCallback(http) { if (rTime) id += "-" + escape(rTime); row.setAttribute("id", id); - //row.cname = escape(data[i][0]); - //row.calendar = calendar; if (rTime) row.recurrenceTime = escape(rTime); row.isException = data[i][14]; - - //row.setAttribute("id", calendar + "-" + cname); //listItem.addClassName(data[i][5]); // Classification - //row.addClassName(data[i][14]); // status + row.addClassName(data[i][16]); // status row.addClassName("taskRow"); row.calendar = calendar; row.cname = cname; @@ -1236,8 +1259,8 @@ function tasksListCallback(http) { cell = createElement("td"); row.appendChild(cell); - if (data[i][16]) - cell.update(data[i][16]); // end date + if (data[i][17]) + cell.update(data[i][17]); // end date cell = createElement("td"); row.appendChild(cell); diff --git a/packaging/rhel/sogo.spec b/packaging/rhel/sogo.spec index 0b73d702b..d853d23b0 100644 --- a/packaging/rhel/sogo.spec +++ b/packaging/rhel/sogo.spec @@ -253,6 +253,9 @@ cp Scripts/logrotate ${RPM_BUILD_ROOT}/etc/logrotate.d/sogo %if 0%{?_with_systemd} cp Scripts/sogo-systemd-redhat ${RPM_BUILD_ROOT}/usr/lib/systemd/system/sogod.service chmod 644 ${RPM_BUILD_ROOT}/usr/lib/systemd/system/sogod.service + mkdir ${RPM_BUILD_ROOT}/etc/tmpfiles.d + cp Scripts/sogo-systemd.conf ${RPM_BUILD_ROOT}/etc/tmpfiles.d/sogo.conf + chmod 644 ${RPM_BUILD_ROOT}/etc/tmpfiles.d/sogo.conf %else cp Scripts/sogo-init.d-redhat ${RPM_BUILD_ROOT}/etc/init.d/sogod chmod 755 ${RPM_BUILD_ROOT}/etc/init.d/sogod @@ -289,6 +292,7 @@ rm -fr ${RPM_BUILD_ROOT} %if 0%{?_with_systemd} /usr/lib/systemd/system/sogod.service +/etc/tmpfiles.d/sogo.conf %else /etc/init.d/sogod %endif