diff --git a/ChangeLog b/ChangeLog index 1f6cc956e..37b7b4aef 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,32 @@ +2007-12-04 Wolfgang Sourdeau + + * SoObjects/Appointments/SOGoAppointmentFolder.m + ([SOGoAppointmentFolder -davCalendarQuery:queryContext]): fixed a leak. + + * SoObjects/SOGo/SOGoGCSFolder.m ([SOGoGCSFolder -davSubscribe:localContext]) + ([SOGoGCSFolder -davUnsubscribe:localContext]): subscribe an + unsubscribe from DAV-based accesses. We could be compatible with + Microsoft's extensions but we have no need for a "subcription id", + so we implement our own. + + * SoObjects/SOGo/SOGoObject.m ([SOGoObject + -POSTAction:localContext]): new method to intercept DAV POSTs, + which we now use to implement certain custom commands such as + "subscribe" and "unsubscribe". + + * SoObjects/SOGo/SOGoUserFolder.m ([SOGoUserFolder + -davNamespaces]): declare the + "urn:inverse:params:xml:ns:inverse-dav" xml ns. + ([SOGoUserFolder -foldersOfType:folderTypeforUID:uid]): new method + designed to replace the UIxContactFoldersView.m mechanism for + displaying folders to subcribe to, as a common code base for both + Web and DAV-based subscriptions. + ([SOGoUserFolder -foldersOfType:typematchingUID:uid]): same as + above. + ([SOGoUserFolder -davCollectionQuery:queryContext]): new method + that implement a custom DAV-based protocol query for querying + folder based on specified attributes. + 2007-12-03 Ludovic Marcotte * Main/NSException+Stacktrace.{h,m} - new files diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index f4753e7c3..6deb3e3c5 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -238,8 +238,7 @@ static NSNumber *sharedYes = nil; NSMutableArray *filters; NSDictionary *filter; - filters = [NSMutableArray new]; - + filters = [NSMutableArray array]; children = [[parentNode getElementsByTagName: @"comp-filter"] objectEnumerator]; node = [children nextObject]; @@ -268,8 +267,7 @@ static NSNumber *sharedYes = nil; max = [filters count]; for (count = 0; count < max; count++) { -#warning huh? why not objectAtIndex: count? - currentFilter = [filters objectAtIndex: 0]; + currentFilter = [filters objectAtIndex: count]; apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"] to: [currentFilter objectForKey: @"end"] title: [currentFilter objectForKey: @"title"] diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index 1c5646d59..b1f0e6df4 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -25,12 +25,14 @@ #import #import #import +#import #import #import #import #import #import +#import #import #import #import @@ -432,6 +434,75 @@ static BOOL sendFolderAdvisories = NO; return names; } +#warning this code should be cleaned up +#warning this code is a dup of UIxFolderActions,\ + we should remove the methods there instead +- (WOResponse *) _subscribe: (BOOL) reallyDo + inContext: (WOContext *) localContext +{ + WOResponse *response; + NSMutableArray *folderSubscription; + NSString *subscriptionPointer, *baseFolder, *folder; + SOGoUser *activeUser; + NSUserDefaults *ud; + NSArray *realFolderPath; + NSMutableDictionary *moduleSettings; + + activeUser = [localContext activeUser]; + ud = [activeUser userSettings]; + baseFolder = [container nameInContainer]; + moduleSettings = [ud objectForKey: baseFolder]; + + response = [localContext response]; + if ([owner isEqualToString: [activeUser login]]) + { + [response setStatus: 403]; + [response appendContentString: + @"You cannot (un)subscribe to a folder that you own!"]; + } + else + { + folderSubscription + = [moduleSettings objectForKey: @"SubscribedFolders"]; + if (!(folderSubscription + && [folderSubscription isKindOfClass: [NSMutableArray class]])) + { + folderSubscription = [NSMutableArray array]; + [moduleSettings setObject: folderSubscription + forKey: @"SubscribedFolders"]; + } + + realFolderPath = [nameInContainer componentsSeparatedByString: @"_"]; + if ([realFolderPath count] > 1) + folder = [realFolderPath objectAtIndex: 1]; + else + folder = [realFolderPath objectAtIndex: 0]; + + subscriptionPointer = [NSString stringWithFormat: @"%@:%@/%@", + owner, baseFolder, folder]; + if (reallyDo) + [folderSubscription addObjectUniquely: subscriptionPointer]; + else + [folderSubscription removeObject: subscriptionPointer]; + + [ud synchronize]; + + [response setStatus: 204]; + } + + return response; +} + +- (id ) davSubscribe: (WOContext *) localContext +{ + return [self _subscribe: YES inContext: localContext]; +} + +- (id ) davUnsubscribe: (WOContext *) localContext +{ + return [self _subscribe: NO inContext: localContext]; +} + /* acls as a container */ - (NSArray *) aclUsersForObjectAtPath: (NSArray *) objectPathArray; diff --git a/SoObjects/SOGo/SOGoObject.m b/SoObjects/SOGo/SOGoObject.m index ff29a3c46..a9a490039 100644 --- a/SoObjects/SOGo/SOGoObject.m +++ b/SoObjects/SOGo/SOGoObject.m @@ -46,6 +46,7 @@ #import #import #import +#import #import #import @@ -646,6 +647,42 @@ static BOOL kontactGroupDAV = YES; return response; } +- (NSString *) _parseXMLCommand: (id ) document +{ + NSString *command; + + command = [[document firstChild] nodeName]; + + return [NSString stringWithFormat: @"%@:", command]; +} + +- (id) POSTAction: (id) localContext +{ + id obj; + NSString *cType, *command; + id document; + SEL commandSel; + WORequest *rq; + + obj = nil; + + rq = [localContext request]; + if ([rq isSoWebDAVRequest]) + { + cType = [rq headerForKey: @"content-type"]; + if ([cType isEqualToString: @"application/xml"]) + { + document = [rq contentAsDOMDocument]; + command = [[self _parseXMLCommand: document] davMethodToObjC]; + commandSel = NSSelectorFromString (command); + if ([self respondsToSelector: commandSel]) + obj = [self performSelector: commandSel withObject: localContext]; + } + } + + return obj; +} + - (id) GETAction: (id) localContext { // TODO: I guess this should really be done by SOPE (redirect to diff --git a/SoObjects/SOGo/SOGoUserFolder.h b/SoObjects/SOGo/SOGoUserFolder.h index 970f5e843..ccb30545e 100644 --- a/SoObjects/SOGo/SOGoUserFolder.h +++ b/SoObjects/SOGo/SOGoUserFolder.h @@ -36,6 +36,8 @@ /SOGo/so/znek/Calendar */ +@class NSArray; +@class NSDictionary; @class NSString; @class WOContext; @@ -49,6 +51,11 @@ - (NSString *) ownerInContext: (WOContext *) _ctx; +- (NSArray *) foldersOfType: (NSString *) folderType + forUID: (NSString *) uid; +- (NSDictionary *) foldersOfType: (NSString *) type + matchingUID: (NSString *) uid; + /* TODO: not implemented, bad bad */ // - (id)lookupFreeBusyObject; diff --git a/SoObjects/SOGo/SOGoUserFolder.m b/SoObjects/SOGo/SOGoUserFolder.m index 035cbcb47..688d3f043 100644 --- a/SoObjects/SOGo/SOGoUserFolder.m +++ b/SoObjects/SOGo/SOGoUserFolder.m @@ -21,18 +21,29 @@ #import #import +#import #import #import +#import #import +#import #import +#import +#import +#import +#import +#import +#import #import #import #import #import +#import "NSDictionary+Utilities.h" +#import "LDAPUserManager.h" #import "SOGoPermissions.h" #import "SOGoUser.h" @@ -96,6 +107,212 @@ return self; } +- (NSArray *) davNamespaces +{ + return [NSArray arrayWithObject: @"urn:inverse:params:xml:ns:inverse-dav"]; +} + +- (NSDictionary *) _parseCollectionFilters: (id ) parentNode +{ + NSEnumerator *children; + NGDOMNode *node; + NSMutableDictionary *filter; + NSString *componentName; + + filter = [NSMutableDictionary dictionaryWithCapacity: 2]; + children = [[parentNode getElementsByTagName: @"prop-match"] + objectEnumerator]; + while ((node = [children nextObject])) + { + componentName = [[node attribute: @"name"] lowercaseString]; + [filter setObject: [node textValue] forKey: componentName]; + } + + return filter; +} + +#warning UIxContactsFoldersView should use these methods\ + instead from now on... + +- (NSArray *) _subFoldersFromFolder: (SOGoParentFolder *) parentFolder +{ + NSMutableArray *folders; + NSEnumerator *subfolders; + SOGoFolder *currentFolder; + NSString *folderName; + NSMutableDictionary *currentDictionary; + SoSecurityManager *securityManager; + + securityManager = [SoSecurityManager sharedSecurityManager]; + + folders = [NSMutableArray array]; + + subfolders = [[parentFolder subFolders] objectEnumerator]; + while ((currentFolder = [subfolders nextObject])) + { + if (![securityManager validatePermission: SOGoPerm_AccessObject + onObject: currentFolder inContext: context]) + { + folderName = [NSString stringWithFormat: @"/%@/%@", + [parentFolder nameInContainer], + [currentFolder nameInContainer]]; + currentDictionary + = [NSMutableDictionary dictionaryWithCapacity: 3]; + [currentDictionary setObject: [currentFolder displayName] + forKey: @"displayName"]; + [currentDictionary setObject: folderName forKey: @"name"]; + [currentDictionary setObject: [currentFolder folderType] + forKey: @"type"]; + [folders addObject: currentDictionary]; + } + } + + return folders; +} + +- (NSArray *) foldersOfType: (NSString *) folderType + forUID: (NSString *) uid +{ + NSObject *userFolder; + SOGoParentFolder *parentFolder; + NSMutableArray *folders; + + folders = [NSMutableArray array]; + + userFolder = [container lookupName: uid inContext: context acquire: NO]; + + /* FIXME: should be moved in the SOGo* classes. Maybe by having a SOGoFolderManager. */ + if ([folderType length] == 0 || [folderType isEqualToString: @"calendar"]) + { + parentFolder = [userFolder lookupName: @"Calendar" + inContext: context acquire: NO]; + [folders + addObjectsFromArray: [self _subFoldersFromFolder: parentFolder]]; + } + if ([folderType length] == 0 || [folderType isEqualToString: @"contact"]) + { + parentFolder = [userFolder lookupName: @"Contacts" + inContext: context acquire: NO]; + [folders + addObjectsFromArray: [self _subFoldersFromFolder: parentFolder]]; + } + + return folders; +} + +- (NSDictionary *) foldersOfType: (NSString *) type + matchingUID: (NSString *) uid +{ + NSArray *contacts, *folders; + NSEnumerator *enumerator; + NSDictionary *contact; + NSMutableDictionary *results; + + results = [NSMutableDictionary dictionary]; + + contacts + = [[LDAPUserManager sharedUserManager] fetchContactsMatching: uid]; + enumerator = [contacts objectEnumerator]; + while ((contact = [enumerator nextObject])) + { + uid = [contact objectForKey: @"c_uid"]; + folders = [self foldersOfType: type + forUID: [contact objectForKey: @"c_uid"]]; + [results setObject: folders forKey: contact]; + } + + return results; +} + +- (NSString *) _baseDAVURLWithSuffix: (NSString *) suffix +{ + NSURL *prefixURL; + + prefixURL = [NSURL URLWithString: [NSString stringWithFormat: @"../%@", suffix] + relativeToURL: [self davURL]]; + + return [[prefixURL standardizedURL] absoluteString]; +} + +- (void) _appendFolders: (NSDictionary *) users + toResponse: (WOResponse *) r +{ + NSDictionary *currentContact, *currentFolder; + NSEnumerator *keys, *folders; + NSString *baseHREF, *data; + + baseHREF = [self _baseDAVURLWithSuffix: @"./"]; + + keys = [[users allKeys] objectEnumerator]; + while ((currentContact = [keys nextObject])) + { + folders = [[users objectForKey: currentContact] objectEnumerator]; + while ((currentFolder = [folders nextObject])) + { + [r appendContentString: @""]; + data = [NSString stringWithFormat: @"%@%@%@", baseHREF, + [currentContact objectForKey: @"c_uid"], + [currentFolder objectForKey: @"name"]]; + [r appendContentString: data]; + [r appendContentString: @""]; + [r appendContentString: @"HTTP/1.1 200 OK"]; + [r appendContentString: @""]; + data = [NSString stringWithFormat: @"%@users/%@", baseHREF, + [currentContact objectForKey: @"c_uid"]]; + [r appendContentString: data]; + [r appendContentString: @""]; + data = [currentContact keysWithFormat: @"%{cn} <%{c_email}>"]; + [r appendContentString: [data stringByEscapingXMLString]]; + [r appendContentString: @""]; + data = [currentFolder objectForKey: @"displayName"]; + [r appendContentString: [data stringByEscapingXMLString]]; + [r appendContentString: @"\r\n"]; + } + } +} + +- (void) _appendCollectionsMatchingFilter: (NSDictionary *) filter + toResponse: (WOResponse *) r +{ + NSString *prefix, *queryOwner, *uid; + NSDictionary *folders; + + prefix = [self _baseDAVURLWithSuffix: @"users/"]; + queryOwner = [filter objectForKey: @"owner"]; + if ([queryOwner hasPrefix: prefix]) + { + uid = [queryOwner substringFromIndex: [prefix length]]; + folders = [self foldersOfType: [filter objectForKey: @"resource-type"] + matchingUID: uid]; + [self _appendFolders: folders toResponse: r]; + } +} + +- (id) davCollectionQuery: (id) queryContext +{ + WOResponse *r; + NSDictionary *filter; + id document; + + r = [context response]; + [r setStatus: 207]; + [r setContentEncoding: NSUTF8StringEncoding]; + [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"]; + [r setHeader: @"no-cache" forKey: @"pragma"]; + [r setHeader: @"no-cache" forKey: @"cache-control"]; + [r appendContentString:@"\r\n"]; + [r appendContentString: @"\r\n"]; + + document = [[context request] contentAsDOMDocument]; + filter = [self _parseCollectionFilters: document]; + [self _appendCollectionsMatchingFilter: filter toResponse: r]; + + [r appendContentString:@"\r\n"]; + + return r; +} + // - (SOGoGroupsFolder *) lookupGroupsFolder // { // return [self lookupName: @"Groups" inContext: nil acquire: NO];