diff --git a/ChangeLog b/ChangeLog index c4de35897..316fecbeb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,76 @@ 2010-01-14 Wolfgang Sourdeau + * SoObjects/Appointments/SOGoAppointmentFolder.m + (+webdavAclManager): fixed "read-free-busy" namespace declaration. + + * SoObjects/SOGo/SOGoObject.m (-davPrincipalCollectionSet): when + used with iCal 4, we provide a "DAV" header with the proper + compliance classes, since iCal 4 doesn't want to use OPTIONS. + + * Tests/test-webdav.py (WebDAVTest.testExpandProperty): new method + to test the new "expand-propery" code in SOGo. + + * SoObjects/SOGo/SOGoFolder.m (-davExpandProperty): new method to + handle the "expand-property" REPORT. + + * SoObjects/Appointments/SOGoCalendarProxy.m: + adapted module to new code for handling chosen calendars + delegation. + + * Main/SOGo+DAV.[hm): new category module for handling dav methods + pertaining to /SOGo/dav/ as the principal-collection-set. Now + implements 3 methods that were previously found in SOGoUserFolder + categories. + + * SoObjects/Appointments/SOGoUser+Appointments.[hm]: new category + module. + + * SoObjects/Appointments/SOGoAppointmentInboxFolder.[hm]: new + class module that implements the basic "schedule-inbox" + collection mechanisms, enough to let iCal work properly. To be + completed for full invitation handling. + + * SoObjects/SOGo/SOGoObject.m (-lookupObjectAtDAVUrl:): new method + that enables DAV reports to lookup objects based on the urls + passed in their parameters. + (-davURLAsString): simplified method by making it recurse from its + top parent. + (-setRoles:forUsers:): treat a list of users at once. + (-davPrincipalMatch): removed method since it really pertains to + /SOGo/dav, which the correct principal-collection-set. + + * SoObjects/SOGo/SOGoParentFolder.m (-initSubcribedSubFolders): + enable the super user to see any user's subscriptions. This + enables a new set of tests to work properly in super user mode. + (-appendSubscribedSources): consequently to the above, the + references must be based on the owner user rather than the current + user. + + * SoObjects/SOGo/SOGoUser.m: removed commented out methods. + + * SoObjects/SOGo/SOGoUserSettings.m (-setProxiedCalendars) + (-setCalendarProxyUsers:withWriteAccess:): + (-setCalendarProxySubscriptionUsers:withWriteAccess:): new method + for handling caldav-proxy related settings. + + * SoObjects/SOGo/SOGoWebDAVAclManager.m (_registerChild:of:): only + emits a warning of the specified parent cannot be found, instead + of crashing. + + * SoObjects/SOGo/WOResponse+SOGo.m (-prepareDAVResponse): new + method with common code for DAV responses generated from SOGo. + + * Tests/webdavlib.py (WebDAVPROPPATCH): new class implementing the + "PROPPATCH" method. + (WebDAVExpandProperty): new class implementing the + "expand-property" REPORT. + (_WD_XMLTreeElement.appendSubtree): new method that enables the + addition of a tree of objects from a dictionary passed as + parameter. + + * Tests/test-ical.py: new test script for tesing iCal-related + behaviours. + * Tools/SOGoToolBackup.m (-extractUserPreferences:intoRecord:): fixed compilation warning. diff --git a/Main/GNUmakefile b/Main/GNUmakefile index d8d1b3398..c169325f0 100644 --- a/Main/GNUmakefile +++ b/Main/GNUmakefile @@ -19,6 +19,7 @@ all:: $(SOGOD)_OBJC_FILES += \ sogod.m \ SOGo.m \ + SOGo+DAV.m \ SOGoProductLoader.m \ build.m diff --git a/Main/SOGo+DAV.h b/Main/SOGo+DAV.h new file mode 100644 index 000000000..6fc096505 --- /dev/null +++ b/Main/SOGo+DAV.h @@ -0,0 +1,39 @@ +/* SOGo+DAV.h - this file is part of SOGo + * + * Copyright (C) 2010 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SOGO_DAV_H +#define SOGO_DAV_H + +@class WOContext; +@class WOResponse; + +#import "SOGo.h" + +@interface SOGo (SOGoWebDAVExtensions) + +- (WOResponse *) davPrincipalMatch: (WOContext *) localContext; +- (WOResponse *) davPrincipalPropertySearch: (WOContext *) localContext; +- (WOResponse *) davPrincipalSearchPropertySet: (WOContext *) localContext; + +@end + +#endif /* SOGO_DAV_H */ diff --git a/Main/SOGo+DAV.m b/Main/SOGo+DAV.m new file mode 100644 index 000000000..bcc111ca9 --- /dev/null +++ b/Main/SOGo+DAV.m @@ -0,0 +1,513 @@ +/* SOGo+DAV.m - this file is part of SOGo + * + * Copyright (C) 2010 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +#import +#import +#import + +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import +#import + +#import "SOGo+DAV.h" + +@implementation SOGo (SOGoWebDAVExtensions) + +- (WOResponse *) davPrincipalSearchPropertySet: (WOContext *) localContext +{ + static NSDictionary *davResponse = nil; + NSMutableArray *propertySet; + NSDictionary *property, *prop, *description; + NSArray *currentValue, *properties, *descriptions, *namespaces; + int count, max; + WOResponse *r; + + if (!davResponse) + { + properties = [NSArray arrayWithObjects: + @"calendar-user-type", + @"calendar-user-address-set", + @"displayname", + @"first-name", + @"last-name", + @"email-address-set", + nil]; + descriptions + = [properties resultsOfSelector: @selector (asDAVPropertyDescription)]; + namespaces = [NSArray arrayWithObjects: + XMLNS_CALDAV, + XMLNS_CALDAV, + XMLNS_WEBDAV, + XMLNS_CalendarServerOrg, + XMLNS_CalendarServerOrg, + XMLNS_CalendarServerOrg, + nil]; + + max = [properties count]; + propertySet = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) + { + property = davElement([properties objectAtIndex: count], + [namespaces objectAtIndex: count]); + prop = davElementWithContent(@"prop", @"DAV:", + property); + description + = davElementWithContent(@"description", @"DAV:", + [descriptions objectAtIndex: count]); + currentValue = [NSArray arrayWithObjects: prop, description, nil]; + [propertySet + addObject: davElementWithContent(@"principal-search-property", + @"DAV:", + currentValue)]; + } + davResponse = davElementWithContent (@"principal-search-property-set", + @"DAV:", + propertySet); + [davResponse retain]; + } + + r = [localContext response]; + [r prepareDAVResponse]; + [r appendContentString: [davResponse asWebDavStringWithNamespaces: nil]]; + + return r; +} + +#warning all REPORT method should be standardized... +- (NSDictionary *) _formalizePrincipalMatchResponse: (NSArray *) hrefs +{ + NSDictionary *multiStatus; + NSEnumerator *hrefList; + NSString *currentHref; + NSMutableArray *responses; + NSArray *responseElements; + + responses = [NSMutableArray array]; + + hrefList = [hrefs objectEnumerator]; + while ((currentHref = [hrefList nextObject])) + { + responseElements + = [NSArray arrayWithObjects: davElementWithContent (@"href", + XMLNS_WEBDAV, + currentHref), + davElementWithContent (@"status", XMLNS_WEBDAV, + @"HTTP/1.1 200 OK"), + nil]; + [responses addObject: davElementWithContent (@"response", XMLNS_WEBDAV, + responseElements)]; + } + + multiStatus = davElementWithContent (@"multistatus", XMLNS_WEBDAV, + responses); + + return multiStatus; +} + +- (NSDictionary *) _handlePrincipalMatchSelfInContext: (WOContext *) localContext +{ + NSString *davURL, *userLogin; + NSArray *principalURL; + + davURL = [self davURLAsString]; + userLogin = [[localContext activeUser] login]; + principalURL + = [NSArray arrayWithObject: [NSString stringWithFormat: @"%@%@/", davURL, + userLogin]]; + return [self _formalizePrincipalMatchResponse: principalURL]; +} + +- (void) _fillArrayWithPrincipalsOwnedBySelf: (NSMutableArray *) hrefs + inContext: (WOContext *) localContext +{ + NSArray *roles; + + roles = [[localContext activeUser] rolesForObject: self + inContext: localContext]; + if ([roles containsObject: SoRole_Owner]) + [hrefs addObject: [self davURLAsString]]; +} + +- (NSDictionary *) + _handlePrincipalMatchPrincipalProperty: (id ) child + inContext: (WOContext *) localContext +{ + NSMutableArray *hrefs; + NSDictionary *response; + + hrefs = [NSMutableArray array]; + [self _fillArrayWithPrincipalsOwnedBySelf: hrefs + inContext: localContext]; + + response = [self _formalizePrincipalMatchResponse: hrefs]; + + return response; +} + +- (NSDictionary *) _handlePrincipalMatchReport: (id ) document + inContext: (WOContext *) localContext +{ + NSDictionary *response; + id documentElement, queryChild; + NSArray *children; + NSString *queryTag, *error; + + documentElement = [document documentElement]; + children = [self domNode: documentElement + getChildNodesByType: DOM_ELEMENT_NODE]; + if ([children count] == 1) + { + queryChild = [children objectAtIndex: 0]; + queryTag = [queryChild tagName]; + if ([queryTag isEqualToString: @"self"]) + response = [self _handlePrincipalMatchSelfInContext: localContext]; + else if ([queryTag isEqualToString: @"principal-property"]) + response = [self _handlePrincipalMatchPrincipalProperty: queryChild + inContext: localContext]; + else + { + error = (@"Query element must be either '{DAV:}principal-property'" + @" or '{DAV:}self'"); + response = [NSException exceptionWithHTTPStatus: 400 + reason: error]; + } + } + else + { + error = (@"Query must have one element: '{DAV:}principal-property'" + @" or '{DAV:}self'"); + response = [NSException exceptionWithHTTPStatus: 400 + reason: error]; + } + + return response; +} + +- (WOResponse *) davPrincipalMatch: (WOContext *) localContext +{ + WOResponse *r; + id document; + NSDictionary *xmlResponse; + + r = [localContext response]; + + document = [[localContext request] contentAsDOMDocument]; + xmlResponse = [self _handlePrincipalMatchReport: document + inContext: localContext]; + if ([xmlResponse isKindOfClass: [NSException class]]) + r = (WOResponse *) xmlResponse; + else + { + [r prepareDAVResponse]; + [r appendContentString: [xmlResponse asWebDavStringWithNamespaces: nil]]; + } + + return r; +} + +- (void) _fillMatches: (NSMutableDictionary *) matches + fromElement: (NSObject *) searchElement +{ + NSObject *list; + NSObject *valueNode; + NSArray *elements; + NSString *property, *match; + + list = [searchElement getElementsByTagName: @"prop"]; + if ([list length]) + { + elements = [self domNode: [list objectAtIndex: 0] + getChildNodesByType: DOM_ELEMENT_NODE]; + if ([elements count]) + { + valueNode = [elements objectAtIndex: 0]; + property = [NSString stringWithFormat: @"{%@}%@", + [valueNode namespaceURI], + [valueNode nodeName]]; + } + } + list = [searchElement getElementsByTagName: @"match"]; + if ([list length]) + { + valueNode = [[list objectAtIndex: 0] firstChild]; + match = [valueNode nodeValue]; + } + + [matches setObject: match forKey: property]; +} + +- (void) _fillProperties: (NSMutableArray *) properties + fromElement: (NSObject *) propElement +{ + NSEnumerator *elements; + NSObject *propNode; + NSString *property; + + elements = [[self domNode: propElement + getChildNodesByType: DOM_ELEMENT_NODE] + objectEnumerator]; + while ((propNode = [elements nextObject])) + { + property = [NSString stringWithFormat: @"{%@}%@", + [propNode namespaceURI], + [propNode nodeName]]; + [properties addObject: property]; + } +} + +- (void) _fillPrincipalMatches: (NSMutableDictionary *) matches + andProperties: (NSMutableArray *) properties + fromElement: (NSObject *) documentElement +{ + NSEnumerator *children; + NSObject *currentElement; + NSString *tag; + + children = [[self domNode: documentElement + getChildNodesByType: DOM_ELEMENT_NODE] + objectEnumerator]; + while ((currentElement = [children nextObject])) + { + tag = [currentElement tagName]; + if ([tag isEqualToString: @"property-search"]) + [self _fillMatches: matches fromElement: currentElement]; + else if ([tag isEqualToString: @"prop"]) + [self _fillProperties: properties fromElement: currentElement]; + else + [self errorWithFormat: @"principal-property-search: unknown tag '%@'", + tag]; + } +} + +#warning this is a bit ugly, as usual +- (void) _fillCollections: (NSMutableArray *) collections + withCalendarHomeSetMatching: (NSString *) value + inContext: (WOContext *) localContext +{ + SOGoUserFolder *collection; + NSRange substringRange; + + substringRange = [value rangeOfString: @"/SOGo/dav/"]; + value = [value substringFromIndex: NSMaxRange (substringRange)]; + substringRange = [value rangeOfString: @"/Calendar"]; + value = [value substringToIndex: substringRange.location]; + collection = [[SOGoUser userWithLogin: value] + homeFolderInContext: localContext]; + if (collection) + [collections addObject: collection]; +} + +- (NSMutableArray *) _firstPrincipalCollectionsWhere: (NSString *) key + matches: (NSString *) value + inContext: (WOContext *) localContext +{ + NSMutableArray *collections; + + collections = [NSMutableArray array]; + if ([key + isEqualToString: @"{urn:ietf:params:xml:ns:caldav}calendar-home-set"]) + [self _fillCollections: collections withCalendarHomeSetMatching: value + inContext: localContext]; + else + [self errorWithFormat: @"principal-property-search: unhandled key '%@'", + key]; + + return collections; +} + +#warning unused stub +- (BOOL) collectionDavKey: (NSString *) key + matches: (NSString *) value +{ + return YES; +} + +- (void) _principalCollections: (NSMutableArray **) baseCollections + where: (NSString *) key + matches: (NSString *) value + inContext: (WOContext *) localContext +{ + SOGoUserFolder *currentCollection; + unsigned int count, max; + + if (!*baseCollections) + *baseCollections = [self _firstPrincipalCollectionsWhere: key + matches: value + inContext: localContext]; + else + { + max = [*baseCollections count]; + for (count = max; count > 0; count--) + { + currentCollection = [*baseCollections objectAtIndex: count - 1]; + if (![currentCollection collectionDavKey: key matches: value]) + [*baseCollections removeObjectAtIndex: count - 1]; + } + } +} + +- (NSArray *) _principalCollectionsMatching: (NSDictionary *) matches + inContext: (WOContext *) localContext +{ + NSMutableArray *collections; + NSEnumerator *allKeys; + NSString *currentKey; + + collections = nil; + + allKeys = [[matches allKeys] objectEnumerator]; + while ((currentKey = [allKeys nextObject])) + [self _principalCollections: &collections + where: currentKey + matches: [matches objectForKey: currentKey] + inContext: localContext]; + + return collections; +} + +/* + + + + + doE + + + + + + Sales + + + + + + + + + */ + +- (void) _appendProperties: (NSArray *) properties + ofCollection: (SOGoUserFolder *) collection + toResponses: (NSMutableArray *) responses +{ + unsigned int count, max; + SEL methodSel; + NSString *currentProperty; + id currentValue; + NSMutableArray *response, *props; + NSDictionary *keyTuple; + + response = [NSMutableArray array]; + [response addObject: davElementWithContent (@"href", XMLNS_WEBDAV, + [collection davURLAsString])]; + props = [NSMutableArray array]; + max = [properties count]; + for (count = 0; count < max; count++) + { + currentProperty = [properties objectAtIndex: count]; + methodSel = SOGoSelectorForPropertyGetter (currentProperty); + if (methodSel && [collection respondsToSelector: methodSel]) + { + currentValue = [collection performSelector: methodSel]; + #warning evil eVIL EVIl! + if ([currentValue isKindOfClass: [NSArray class]]) + { + currentValue = [currentValue objectAtIndex: 0]; + currentValue + = davElementWithContent ([currentValue objectAtIndex: 0], + [currentValue objectAtIndex: 1], + [currentValue objectAtIndex: 3]); + } + keyTuple = [currentProperty asWebDAVTuple]; + [props addObject: davElementWithContent ([keyTuple objectForKey: @"method"], + [keyTuple objectForKey: @"ns"], + currentValue)]; + } + } + [response addObject: davElementWithContent (@"propstat", XMLNS_WEBDAV, + davElementWithContent + (@"prop", XMLNS_WEBDAV, + props))]; + [responses addObject: davElementWithContent (@"response", XMLNS_WEBDAV, + response)]; +} + +- (void) _appendProperties: (NSArray *) properties + ofCollections: (NSArray *) collections + toResponse: (WOResponse *) response +{ + NSDictionary *mStatus; + NSMutableArray *responses; + unsigned int count, max; + + max = [collections count]; + responses = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) + [self _appendProperties: properties + ofCollection: [collections objectAtIndex: count] + toResponses: responses]; + mStatus = davElementWithContent (@"multistatus", XMLNS_WEBDAV, responses); + [response appendContentString: [mStatus asWebDavStringWithNamespaces: nil]]; +} + +/* rfc3744, should be moved into SOGoUserFolder */ +- (WOResponse *) davPrincipalPropertySearch: (WOContext *) localContext +{ + NSObject *document; + NSObject *documentElement; + NSArray *collections; + NSMutableDictionary *matches; + NSMutableArray *properties; + WOResponse *r; + + document = [[localContext request] contentAsDOMDocument]; + documentElement = [document documentElement]; + + matches = [NSMutableDictionary dictionary]; + properties = [NSMutableArray array]; + [self _fillPrincipalMatches: matches andProperties: properties + fromElement: documentElement]; + collections = [self _principalCollectionsMatching: matches + inContext: localContext]; + r = [localContext response]; + [r prepareDAVResponse]; + [self _appendProperties: properties ofCollections: collections + toResponse: r]; + + return r; +} + + + +@end diff --git a/Main/SOGo.m b/Main/SOGo.m index 41d10a471..91bce24a9 100644 --- a/Main/SOGo.m +++ b/Main/SOGo.m @@ -54,6 +54,7 @@ #import #import #import +#import #import "build.h" #import "SOGoProductLoader.h" @@ -61,6 +62,8 @@ #import "SOGo.h" +#warning might be useful to have a SOGoObject-derived proxy class for \ + handling requests and avoid duplicating methods @implementation SOGo static unsigned int vMemSizeLimit; @@ -107,10 +110,8 @@ static BOOL debugLeaks; /* to allow public access to all contained objects (subkeys) */ [sInfo setDefaultAccess: @"allow"]; - basicRoles = [NSArray arrayWithObjects: SoRole_Authenticated, - SOGoRole_FreeBusy, nil]; - /* require Authenticated role for View and WebDAV */ + basicRoles = [NSArray arrayWithObject: SoRole_Authenticated]; [sInfo declareRoles: basicRoles asDefaultForPermission: SoPerm_View]; [sInfo declareRoles: basicRoles asDefaultForPermission: SoPerm_WebDAVAccess]; @@ -268,15 +269,6 @@ static BOOL debugLeaks; /* name lookup */ -- (BOOL) isUserName: (NSString *) _key - inContext: (id) _ctx -{ - if ([_key length] < 1) - return NO; - - return YES; -} - - (id) lookupUser: (NSString *) _key inContext: (id)_ctx { @@ -311,16 +303,29 @@ static BOOL debugLeaks; acquire: (BOOL) _flag { id obj; - SOGoSystemDefaults *sd; + WORequest *request; + BOOL isDAVRequest; /* put locale info into the context in case it's not there */ [self _setupLocaleInContext:_ctx]; - sd = [SOGoSystemDefaults sharedSystemDefaults]; - if ([sd isWebAccessEnabled] || [[_ctx request] isSoWebDAVRequest]) + request = [_ctx request]; + isDAVRequest = [request isSoWebDAVRequest]; + if (isDAVRequest + || [[SOGoSystemDefaults sharedSystemDefaults] isWebAccessEnabled]) { - /* first check attributes directly bound to the application */ - obj = [super lookupName:_key inContext:_ctx acquire:_flag]; + if (isDAVRequest) + { + if ([[request method] isEqualToString: @"REPORT"]) + obj = [self davReportInvocationForKey: _key]; + else + obj = nil; + } + else + { + /* first check attributes directly bound to the application */ + obj = [super lookupName:_key inContext:_ctx acquire:_flag]; + } if (!obj) { /* @@ -331,11 +336,8 @@ static BOOL debugLeaks; "GET" if no method was provided in the query path. */ - if (![_key isEqualToString:@"favicon.ico"]) - { - if ([self isUserName: _key inContext: _ctx]) - obj = [self lookupUser: _key inContext: _ctx]; - } + if ([_key length] > 0 && ![_key isEqualToString:@"favicon.ico"]) + obj = [self lookupUser: _key inContext: _ctx]; } } else @@ -589,15 +591,21 @@ static BOOL debugLeaks; { NSURL *davURL; NSString *davURLAsString; - - davURL = [self davURL]; + WORequest *request; /* we know that GNUstep returns a "/" suffix for the absoluteString but not for the path method. Therefore we add one. */ if (useRelativeURLs) - davURLAsString = [NSString stringWithFormat: @"%@/", [davURL path]]; + { + request = [[self context] request]; + davURLAsString = [NSString stringWithFormat: @"/%@/dav/", + [request applicationName]]; + } else - davURLAsString = [davURL absoluteString]; + { + davURL = [self davURL]; + davURLAsString = [davURL absoluteString]; + } return davURLAsString; } diff --git a/NEWS b/NEWS index 3a315929b..75e2436b8 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,10 @@ values more easily - added sensible default configuration values - updated ckeditor to version 3.1 +- added support for iCal 4 delegation +- added support for letting the user choose which calendars should be shared + with iCal delegation +- added fixes for bugs in GNUstep 1.19.3 (NSURL) 1.1-20091028 (1.1.0) -------------------- diff --git a/SOPE/sope-patchset-r1664.diff b/SOPE/sope-patchset-r1664.diff index 28421d383..50a8d2b21 100644 --- a/SOPE/sope-patchset-r1664.diff +++ b/SOPE/sope-patchset-r1664.diff @@ -1162,7 +1162,7 @@ Index: sope-mime/NGImap4/NGImap4Connection.m } return self; } -@@ -100,11 +102,13 @@ +@@ -100,14 +102,16 @@ return self->creationTime; } @@ -1179,7 +1179,11 @@ Index: sope-mime/NGImap4/NGImap4Connection.m + return [self->subfolders objectForKey:[_url absoluteString]]; } - (void)flushFolderHierarchyCache { - [self->subfolders release]; self->subfolders = nil; +- [self->subfolders release]; self->subfolders = nil; ++ [self->subfolders release]; self->subfolders = [NSMutableDictionary new]; + [self->urlToRights release]; self->urlToRights = nil; + } + @@ -152,7 +156,6 @@ ASSIGN(self->cachedUIDs, nil); } @@ -1341,7 +1345,7 @@ Index: sope-mime/NGImap4/NGImap4Connection.m if (![[result valueForKey:@"result"] boolValue]) { return [self errorForResult:result text:@"Failed to change flags of IMAP4 message"]; -@@ -737,14 +770,17 @@ +@@ -737,20 +770,21 @@ - (BOOL)doesMailboxExistAtURL:(NSURL *)_url { NSString *folderName; @@ -1361,9 +1365,15 @@ Index: sope-mime/NGImap4/NGImap4Connection.m + + result = [[caches objectAtIndex: count] objectForKey:@"list"]; p = [_url path]; - #if __APPLE__ +-#if __APPLE__ /* normalized results already have the / in front on libFoundation?! */ -@@ -760,11 +796,11 @@ + if ([p hasPrefix:@"/"]) + p = [p substringFromIndex:1]; +-#endif + if ([p hasSuffix:@"/"]) + p = [p substringToIndex:[p length]-1]; + return ([(NSDictionary *)result objectForKey:p] != nil) ? YES : NO; +@@ -760,11 +794,11 @@ // TODO: we should probably just fetch the whole hierarchy? folderName = [self imap4FolderNameForURL:_url]; @@ -1380,7 +1390,7 @@ Index: sope-mime/NGImap4/NGImap4Connection.m } - (id)infoForMailboxAtURL:(NSURL *)_url { -@@ -789,7 +825,8 @@ +@@ -789,7 +823,8 @@ /* construct path */ newPath = [self imap4FolderNameForURL:_url]; @@ -2389,7 +2399,14 @@ Index: sope-mime/NGImap4/ChangeLog =================================================================== --- sope-mime/NGImap4/ChangeLog (revision 1664) +++ sope-mime/NGImap4/ChangeLog (working copy) -@@ -1,3 +1,92 @@ +@@ -1,3 +1,99 @@ ++2010-01-14 Wolfgang Sourdeau ++ ++ * NGImap4Connection.m (-doesMailboxExistAtURL:): mailbox paths can ++ start with '/' on non-Apple platforms. ++ (-flushFolderHierarchyCache): reassign a new dictionary to ++ self->subfolders to avoid disappearing folders. ++ +2010-01-05 Wolfgang Sourdeau + + * NGImap4ResponseParser.m (_parseUntaggedResponse): now accepts @@ -4993,6 +5010,20 @@ Index: sope-xml/libxmlSAXDriver/libxmlHTMLSAXDriver.m return self->contentHandler; } +Index: sope-xml/SaxObjC/XMLNamespaces.h +=================================================================== +--- sope-xml/SaxObjC/XMLNamespaces.h (revision 1664) ++++ sope-xml/SaxObjC/XMLNamespaces.h (working copy) +@@ -292,6 +292,9 @@ + #ifndef XMLNS_AppleCalServer + # define XMLNS_AppleCalServer @"http://apple.com/ns/calendarserver/" + #endif ++#ifndef XMLNS_CalendarServerOrg ++# define XMLNS_CalendarServerOrg @"http://calendarserver.org/ns/" ++#endif + #ifndef XMLNS_AppleCalApp + # define XMLNS_AppleCalApp @"com.apple.ical:" + #endif Index: sope-appserver/mod_ngobjweb/GNUmakefile =================================================================== --- sope-appserver/mod_ngobjweb/GNUmakefile (revision 1664) @@ -6447,7 +6478,18 @@ Index: sope-appserver/NGObjWeb/ChangeLog =================================================================== --- sope-appserver/NGObjWeb/ChangeLog (revision 1664) +++ sope-appserver/NGObjWeb/ChangeLog (working copy) -@@ -1,3 +1,97 @@ +@@ -1,3 +1,108 @@ ++2010-01-14 Wolfgang Sourdeau ++ ++ * SoObjects/SoObject.m (-isFolderish): now a real category method, ++ defaulting to NO. ++ ++ * WebDAV/SoWebDAVRenderer.m (-renderSearchResultEntry:...): take ++ the potential ending slash of the request to keep or remove the ++ ending slash of the hrefs to the returned objects, in order to ++ avoid confusing iCal with otherwise standard urls to DAV ++ collections. ++ +2009-12-22 Wolfgang Sourdeau + + * WOWatchDogApplicationMain.m (_listeningAddress): read "WOPort" @@ -6557,6 +6599,15 @@ Index: sope-appserver/NGObjWeb/DAVPropMap.plist /* CardDAV */ "{urn:ietf:params:xml:ns:carddav}addressbook-home-set" = davAddressbookHomeSet; +@@ -168,6 +169,8 @@ + "{http://calendarserver.org/ns/}dropbox-home-URL" = davDropboxHomeURL; + "{http://calendarserver.org/ns/}notifications-URL" = davNotificationsURL; + "{http://calendarserver.org/ns/}getctag" = davCollectionTag; ++ "{http://calendarserver.org/ns/}calendar-proxy-read-for" = davCalendarProxyReadFor; ++ "{http://calendarserver.org/ns/}calendar-proxy-write-for" = davCalendarProxyWriteFor; + + /* Apple extensions */ + "{http://apple.com/ns/ical/}calendar-color" = davCalendarColor; Index: sope-appserver/NGObjWeb/WebDAV/SoObjectResultEntry.m =================================================================== --- sope-appserver/NGObjWeb/WebDAV/SoObjectResultEntry.m (revision 1664) @@ -6616,11 +6667,32 @@ Index: sope-appserver/NGObjWeb/WebDAV/SoObjectResultEntry.m if ([_key isEqualToString:@"{DAV:}status"]) return nil; +@@ -102,6 +135,12 @@ + } + } + ++/* SoObject */ ++- (BOOL)isFolderish ++{ ++ return [self->object isFolderish]; ++} ++ + /* description */ + + - (NSString *)description { Index: sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m =================================================================== --- sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m (revision 1664) +++ sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m (working copy) -@@ -49,6 +49,8 @@ +@@ -25,6 +25,7 @@ + #include "SoObject+SoDAV.h" + #include "EOFetchSpecification+SoDAV.h" + #include "NSException+HTTP.h" ++#include + #include + #include + #include +@@ -49,6 +50,8 @@ #define XMLNS_INTTASK \ @"{http://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/}" @@ -6629,7 +6701,7 @@ Index: sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m @interface SoWebDAVRenderer(Privates) - (BOOL)renderStatusResult:(id)_object withDefaultStatus:(int)_defStatus inContext:(WOContext *)_ctx; -@@ -79,6 +81,8 @@ +@@ -79,6 +82,8 @@ if ((debugOn = [ud boolForKey:@"SoRendererDebugEnabled"])) NSLog(@"enabled debugging in SoWebDAVRenderer (SoRendererDebugEnabled)"); @@ -6638,7 +6710,7 @@ Index: sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m } + (id)sharedRenderer { -@@ -616,16 +620,19 @@ +@@ -616,16 +621,19 @@ [r appendContentString:s]; } else { @@ -6666,7 +6738,19 @@ Index: sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m if (formatOutput) [r appendContentCharacter:'\n']; } } -@@ -694,8 +701,13 @@ +@@ -646,8 +654,9 @@ + NSString *key; + id href = nil; + id stat = nil; +- BOOL isBrief; +- ++ BOOL isBrief, hasSlash; ++ ++ hasSlash = [[[_ctx request] uri] hasSuffix: @"/"]; + r = [_ctx response]; + isBrief = [[[_ctx request] headerForKey:@"brief"] hasPrefix:@"t"] ? YES : NO; + +@@ -694,8 +703,13 @@ } /* tidy href */ @@ -6682,6 +6766,30 @@ Index: sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m /* tidy status */ stat = [self tidyStatus:stat]; } +@@ -703,7 +717,22 @@ + href = [baseURL stringValue]; + stat = @"HTTP/1.1 200 OK"; + } +- ++ ++ /* make the presence of the href slash correspond to the request slash */ ++ if (hasSlash) { ++ /* megahack: we consider entry to be the base entry if it's an ++ NSDictionary */ ++ if (![href hasSuffix: @"/"] ++ && ([entry isFolderish] ++ || [entry isKindOfClass: [NSDictionary class]])) { ++ href = [href stringByAppendingString: @"/"]; ++ } ++ } ++ else { ++ if ([href hasSuffix: @"/"]) ++ href = [href substringToIndex: [href length] - 2]; ++ } ++ + if (debugOn) { + [self debugWithFormat:@" status: %@", stat]; + [self debugWithFormat:@" href: %@", href]; Index: sope-appserver/NGObjWeb/WODirectAction.m =================================================================== --- sope-appserver/NGObjWeb/WODirectAction.m (revision 1664) @@ -6863,11 +6971,33 @@ Index: sope-appserver/NGObjWeb/WOMessage.m } return nil; } +Index: sope-appserver/NGObjWeb/SoObjects/SoObject.h +=================================================================== +--- sope-appserver/NGObjWeb/SoObjects/SoObject.h (revision 1664) ++++ sope-appserver/NGObjWeb/SoObjects/SoObject.h (working copy) +@@ -59,6 +59,8 @@ + - (NSString *)defaultMethodNameInContext:(id)_ctx; + - (id)lookupDefaultMethod; + ++- (BOOL)isFolderish; ++ + /* binding (returns self by default [unbound objects]) */ + + - (id)bindToObject:(id)_object inContext:(id)_ctx; Index: sope-appserver/NGObjWeb/SoObjects/SoObject.m =================================================================== --- sope-appserver/NGObjWeb/SoObjects/SoObject.m (revision 1664) +++ sope-appserver/NGObjWeb/SoObjects/SoObject.m (working copy) -@@ -39,22 +39,34 @@ +@@ -30,31 +30,39 @@ + #include + #include "common.h" + +-@interface NSObject(Folders) +-- (BOOL)isFolderish; +-@end +- + @implementation NSObject(SoObject) + static int debugLookup = -1; static int debugBaseURL = -1; static int useRelativeURLs = -1; @@ -6908,7 +7038,32 @@ Index: sope-appserver/NGObjWeb/SoObjects/SoObject.m } /* classes */ -@@ -318,56 +330,61 @@ +@@ -241,6 +249,11 @@ + return pathArray; + } + ++- (BOOL) isFolderish ++{ ++ return NO; ++} ++ + - (NSString *)baseURLInContext:(id)_ctx { + NSString *baseURL; + id parent; +@@ -284,10 +297,8 @@ + /* add a trailing slash for folders */ + + if (![baseURL hasSuffix:@"/"]) { +- if ([self respondsToSelector:@selector(isFolderish)]) { +- if ([self isFolderish]) +- baseURL = [baseURL stringByAppendingString:@"/"]; +- } ++ if ([self isFolderish]) ++ baseURL = [baseURL stringByAppendingString:@"/"]; + } + + return baseURL; +@@ -318,56 +329,61 @@ rq = [_ctx request]; ms = [[NSMutableString alloc] initWithCapacity:128]; @@ -7724,3 +7879,16 @@ Index: sope-appserver/NGObjWeb/WOCoreApplication.m if ([woport isKindOfClass:[NSNumber class]]) return woport; woport = [woport stringValue]; +Index: sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.m +=================================================================== +--- sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.m (revision 1664) ++++ sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.m (working copy) +@@ -59,6 +59,8 @@ + /* RFC 3253 (DeltaV) */ + @"REPORT", + @"VERSION-CONTROL", ++ /* RFC 3744 (WebDAV ACL) */ ++ @"ACL", + /* RFC 4791 (CalDAV) */ + @"MKCALENDAR", + /* http://ietfreport.isoc.org/idref/draft-daboo-carddav/ (CardDAV) */ diff --git a/SoObjects/Appointments/GNUmakefile b/SoObjects/Appointments/GNUmakefile index ffa7e23d3..9ef6e934e 100644 --- a/SoObjects/Appointments/GNUmakefile +++ b/SoObjects/Appointments/GNUmakefile @@ -24,9 +24,11 @@ Appointments_OBJC_FILES = \ SOGoAppointmentOccurence.m \ SOGoTaskOccurence.m \ SOGoAppointmentFolder.m \ + SOGoAppointmentInboxFolder.m \ SOGoWebAppointmentFolder.m \ SOGoAppointmentFolders.m \ SOGoFreeBusyObject.m \ + SOGoUser+Appointments.m \ SOGoUserFolder+Appointments.m \ \ SOGoCalendarProxy.m \ diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.h b/SoObjects/Appointments/SOGoAppointmentFolder.h index 94ccd2c14..d53adbaa0 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.h +++ b/SoObjects/Appointments/SOGoAppointmentFolder.h @@ -67,12 +67,6 @@ - (NSString *) calendarColor; - (void) setCalendarColor: (NSString *) newColor; -- (NSString *) syncTag; -- (void) setSyncTag: (NSString *) newSyncTag; - -- (BOOL) synchronizeCalendar; -- (void) setSynchronizeCalendar: (BOOL) new; - /* selection */ - (NSArray *) calendarUIDs; @@ -144,13 +138,24 @@ - (BOOL) showCalendarTasks; - (void) setShowCalendarTasks: (BOOL) new; -- (NSArray *) proxySubscribersWithWriteAccess: (BOOL) hasWriteAccess; -- (NSException *) setProxySubscribers: (NSArray *) newSubscribers - withWriteAccess: (BOOL) hasWriteAccess; +- (BOOL) isProxied; +- (void) setIsProxied: (BOOL) isProxied; + +- (NSString *) syncTag; +- (void) setSyncTag: (NSString *) newSyncTag; + +- (BOOL) synchronizeCalendar; +- (void) setSynchronizeCalendar: (BOOL) new; - (BOOL) importComponent: (iCalEntityObject *) event; - (int) importCalendar: (iCalCalendar *) calendar; +/* caldav proxy */ + +- (void) adjustProxyRolesForUsers: (NSArray *) proxyUsers + remove: (BOOL) remove + forWriteAccess: (BOOL) write; + @end #endif /* __Appointments_SOGoAppointmentFolder_H__ */ diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 0d51e7fd8..cf7afd1be 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -67,6 +67,7 @@ #import #import #import +#import #import "iCalEntityObject+SOGo.h" #import "iCalPerson+SOGo.h" @@ -106,16 +107,16 @@ static NSNumber *sharedYes = nil; aclManager = [SOGoWebDAVAclManager new]; [aclManager registerDAVPermission: davElement (@"read", XMLNS_WEBDAV) - abstract: YES - withEquivalent: SoPerm_WebDAVAccess - asChildOf: davElement (@"all", XMLNS_WEBDAV)]; + abstract: YES + withEquivalent: SoPerm_WebDAVAccess + asChildOf: davElement (@"all", XMLNS_WEBDAV)]; [aclManager registerDAVPermission: davElement (@"read-current-user-privilege-set", XMLNS_WEBDAV) - abstract: YES - withEquivalent: SoPerm_WebDAVAccess - asChildOf: davElement (@"read", XMLNS_WEBDAV)]; - [aclManager registerDAVPermission: davElement (@"read-free-busy", XMLNS_WEBDAV) + abstract: YES + withEquivalent: SoPerm_WebDAVAccess + asChildOf: davElement (@"read", XMLNS_WEBDAV)]; + [aclManager registerDAVPermission: davElement (@"read-free-busy", XMLNS_CALDAV) abstract: NO - withEquivalent: SoPerm_AccessContentsInformation + withEquivalent: SOGoCalendarPerm_ReadFreeBusy asChildOf: davElement (@"read", XMLNS_WEBDAV)]; [aclManager registerDAVPermission: davElement (@"write", XMLNS_WEBDAV) abstract: YES @@ -1668,31 +1669,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir return nil; } -- (SOGoWebDAVValue *) davCalendarFreeBusySet -{ - NSEnumerator *subFolders; - SOGoAppointmentFolder *currentFolder; - NSMutableArray *response; - SOGoWebDAVValue *responseValue; - - response = [NSMutableArray array]; - subFolders = [[container subFolders] objectEnumerator]; - while ((currentFolder = [subFolders nextObject])) - [response - addObject: davElementWithContent (@"href", XMLNS_WEBDAV, - [currentFolder davURLAsString])]; - responseValue = [davElementWithContent (@"calendar-free-busy-set", XMLNS_CALDAV, response) - asWebDAVValue]; - - return responseValue; -} - -/* This method is ignored but we must return a success value. */ -- (NSException *) setDavCalendarFreeBusySet: (NSString *) newFreeBusySet -{ - return nil; -} - - (void) _appendComponentProperties: (NSDictionary *) properties matchingFilters: (NSArray *) filters toResponse: (WOResponse *) response @@ -1713,9 +1689,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir if ([currentField length]) [fields addObjectUniquely: currentField]; baseURL = [self davURLAsString]; -#warning review this when fixing http://www.scalableogo.org/bugs/view.php?id=276 - if (![baseURL hasSuffix: @"/"]) - baseURL = [NSString stringWithFormat: @"%@/", baseURL]; propertiesArray = [[properties allKeys] asPointersOfObjects]; propertiesCount = [properties count]; @@ -1775,12 +1748,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir DOMElement *documentElement, *propElement; 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 prepareDAVResponse]; [r appendContentString: @""]; @@ -1987,12 +1955,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir DOMElement *documentElement, *propElement; 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 prepareDAVResponse]; [r appendContentString: @""]; @@ -2370,8 +2333,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir login = [[context activeUser] login]; if ([login isEqualToString: [self ownerInContext: self]]) { - [colType addObject: [NSArray arrayWithObjects: @"schedule-inbox", - XMLNS_CALDAV, nil]]; + // [colType addObject: [NSArray arrayWithObjects: @"schedule-inbox", + // XMLNS_CALDAV, nil]]; [colType addObject: [NSArray arrayWithObjects: @"schedule-outbox", XMLNS_CALDAV, nil]]; } @@ -2919,128 +2882,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir return folders; } -// - (id) lookupGroupFolderForUIDs: (NSArray *) _uids -// inContext: (id)_ctx -// { -// SOGoCustomGroupFolder *folder; - -// if (_uids == nil) -// return nil; - -// folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self]; -// return [folder autorelease]; -// } - -// - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids -// inContext: (id) _ctx -// { -// SOGoCustomGroupFolder *folder; - -// if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil) -// return nil; - -// folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO]; -// if (![folder isNotNull]) -// return nil; -// if ([folder isKindOfClass:[NSException class]]) { -// [self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@", -// folder]; -// return nil; -// } - -// return folder; -// } - -/* bulk fetches */ - -// #warning We only support ONE calendar per user at this time -// - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders -// toFolderList: (NSMutableArray *) calendarFolders -// { -// NSEnumerator *keys; -// NSString *currentKey; -// NSMutableDictionary *currentCalendar; -// BOOL firstShouldBeActive; -// unsigned int count; - -// firstShouldBeActive = YES; - -// keys = [[subscribedFolders allKeys] objectEnumerator]; -// currentKey = [keys nextObject]; -// count = 1; -// while (currentKey) -// { -// currentCalendar = [NSMutableDictionary new]; -// [currentCalendar autorelease]; -// [currentCalendar -// setDictionary: [subscribedFolders objectForKey: currentKey]]; -// [currentCalendar setObject: currentKey forKey: @"folder"]; -// [calendarFolders addObject: currentCalendar]; -// if ([[currentCalendar objectForKey: @"active"] boolValue]) -// firstShouldBeActive = NO; -// count++; -// currentKey = [keys nextObject]; -// } - -// return firstShouldBeActive; -// } - -// - (NSArray *) calendarFolders -// { -// NSMutableDictionary *userCalendar, *calendarDict; -// NSMutableArray *calendarFolders; -// SOGoUser *calendarUser; -// BOOL firstActive; - -// calendarFolders = [NSMutableArray new]; -// [calendarFolders autorelease]; - -// calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context]]; -// userCalendar = [NSMutableDictionary new]; -// [userCalendar autorelease]; -// [userCalendar setObject: @"/" forKey: @"folder"]; -// [userCalendar setObject: @"Calendar" forKey: @"displayName"]; -// [calendarFolders addObject: userCalendar]; - -// calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"]; -// firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue]; -// firstActive = ([self _appendSubscribedFolders: -// [calendarDict objectForKey: @"SubscribedFolders"] -// toFolderList: calendarFolders] -// || firstActive); -// [userCalendar setObject: [NSNumber numberWithBool: firstActive] -// forKey: @"active"]; - -// return calendarFolders; -// } - -// - (NSArray *) fetchContentObjectNames -// { -// NSMutableArray *objectNames; -// NSArray *records; -// NSCalendarDate *today, *startDate, *endDate; - -// #warning this should be user-configurable -// objectNames = [NSMutableArray array]; -// today = [[NSCalendarDate calendarDate] beginOfDay]; -// [today setTimeZone: timeZone]; - -// startDate = [today dateByAddingYears: 0 months: 0 days: -1 -// hours: 0 minutes: 0 seconds: 0]; -// endDate = [startDate dateByAddingYears: 0 months: 0 days: 2 -// hours: 0 minutes: 0 seconds: 0]; -// records = [self fetchFields: [NSArray arrayWithObject: @"c_name"] -// from: startDate to: endDate -// component: @"vevent"]; -// [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]]; -// records = [self fetchFields: [NSArray arrayWithObject: @"c_name"] -// from: startDate to: endDate -// component: @"vtodo"]; -// [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]]; - -// return objectNames; -// } - /* folder type */ - (NSString *) folderType @@ -3065,122 +2906,45 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir return (![inactiveFolders containsObject: nameInContainer]); } -- (NSArray *) requiredProxyRolesWithWriteAccess: (BOOL) hasWriteAccess +- (BOOL) isProxied { - static NSArray *writeAccessRoles = nil; - static NSArray *readAccessRoles = nil; + NSArray *proxiedCalendars; + SOGoUser *ownerUser; - if (!writeAccessRoles) - { - writeAccessRoles = [NSArray arrayWithObjects: - SOGoCalendarRole_ConfidentialModifier, - SOGoRole_ObjectCreator, - SOGoRole_ObjectEraser, - SOGoCalendarRole_PrivateModifier, - SOGoCalendarRole_PublicModifier, - nil]; - [writeAccessRoles retain]; - } + ownerUser = [SOGoUser userWithLogin: [self ownerInContext: nil]]; + proxiedCalendars = [[ownerUser userSettings] proxiedCalendars]; - if (!readAccessRoles) - { - readAccessRoles = [NSArray arrayWithObjects: - SOGoCalendarRole_ConfidentialViewer, - SOGoCalendarRole_PrivateViewer, - SOGoCalendarRole_PublicViewer, - nil]; - [readAccessRoles retain]; - } - - return (hasWriteAccess) ? writeAccessRoles : readAccessRoles; + return [proxiedCalendars containsObject: [self realNameInContainer]]; } -- (BOOL) _user: (NSString *) user - isProxyWithWriteAccess: (BOOL) hasWriteAccess +- (void) setIsProxied: (BOOL) isProxied { - NSArray *userRoles, *reqRoles; - BOOL isProxy; + NSMutableArray *proxiedCalendars; + NSArray *subscriptionUsers; + SOGoUser *ownerUser; + SOGoUserSettings *us; - if ([self userIsSubscriber: user]) - { - userRoles = [[self aclsForUser: user] - sortedArrayUsingSelector: @selector (compare:)]; - reqRoles = [self requiredProxyRolesWithWriteAccess: hasWriteAccess]; - isProxy = [reqRoles isEqualToArray: userRoles]; - } + ownerUser = [SOGoUser userWithLogin: [self ownerInContext: nil]]; + us = [ownerUser userSettings]; + proxiedCalendars = [[us proxiedCalendars] mutableCopy]; + if (isProxied) + [proxiedCalendars addObjectUniquely: [self realNameInContainer]]; else - isProxy = NO; + [proxiedCalendars removeObject: [self realNameInContainer]]; - return isProxy; -} + [us setProxiedCalendars: proxiedCalendars]; -- (NSArray *) proxySubscribersWithWriteAccess: (BOOL) hasWriteAccess -{ - NSMutableArray *subscribers; - NSEnumerator *aclUsers; - NSString *currentUser, *defaultUser; + subscriptionUsers = [us calendarProxyUsersWithWriteAccess: YES]; + [self adjustProxyRolesForUsers: subscriptionUsers + remove: !isProxied + forWriteAccess: YES]; + subscriptionUsers = [us calendarProxyUsersWithWriteAccess: NO]; + [self adjustProxyRolesForUsers: subscriptionUsers + remove: !isProxied + forWriteAccess: NO]; - subscribers = [NSMutableArray array]; - - defaultUser = [self defaultUserID]; - aclUsers = [[self aclUsers] objectEnumerator]; - while ((currentUser = [aclUsers nextObject])) - { - if (![currentUser isEqualToString: defaultUser] - && [self _user: currentUser - isProxyWithWriteAccess: hasWriteAccess]) - [subscribers addObject: currentUser]; - } - - return subscribers; -} - -- (NSException *) setProxySubscribers: (NSArray *) newSubscribers - withWriteAccess: (BOOL) hasWriteAccess -{ - int count, max; - NSArray *oldSubscribers; - NSString *login, *reason; - NSException *error; - - error = nil; - - max = [newSubscribers count]; - for (count = 0; !error && count < max; count++) - { - login = [newSubscribers objectAtIndex: count]; - if (![SOGoUser userWithLogin: login roles: nil]) - { - reason = [NSString stringWithFormat: @"User '%@' does not exist.", login]; - error = [NSException exceptionWithHTTPStatus: 403 reason: reason]; - } - } - - if (!error) - { - oldSubscribers = [self proxySubscribersWithWriteAccess: hasWriteAccess]; - for (count = 0; !error && count < max; count++) - { - login = [newSubscribers objectAtIndex: count]; - [self - setRoles: [self requiredProxyRolesWithWriteAccess: hasWriteAccess] - forUser: login]; - [self subscribeUser: login reallyDo: YES]; - } - - max = [oldSubscribers count]; - for (count = 0; count < max; count++) - { - login = [oldSubscribers objectAtIndex: count]; - if (![newSubscribers containsObject: login]) - { - [self subscribeUser: login reallyDo: NO]; - [self removeAclsForUsers: [NSArray arrayWithObject: login]]; - } - } - } - - return error; + [us synchronize]; + [proxiedCalendars autorelease]; } - (BOOL) importComponent: (iCalEntityObject *) event @@ -3224,4 +2988,74 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir return imported; } +/* acls */ +- (NSArray *) aclsForUser: (NSString *) uid + forObjectAtPath: (NSArray *) objectPathArray +{ + NSMutableArray *aclsForUser; + NSArray *superAcls; + + superAcls = [super aclsForUser: uid forObjectAtPath: objectPathArray]; + if ([uid isEqualToString: [self defaultUserID]]) + { + if (superAcls) + { + aclsForUser = [superAcls mutableCopy]; + [aclsForUser autorelease]; + } + else + aclsForUser = [NSMutableArray array]; + [aclsForUser addObject: SoRole_Authenticated]; + } + else + aclsForUser = (NSMutableArray *) superAcls; + + return aclsForUser; +} + +- (NSArray *) requiredProxyRolesWithWriteAccess: (BOOL) hasWriteAccess +{ + static NSArray *writeAccessRoles = nil; + static NSArray *readAccessRoles = nil; + + if (!writeAccessRoles) + { + writeAccessRoles = [NSArray arrayWithObjects: + SOGoCalendarRole_ConfidentialModifier, + SOGoRole_ObjectCreator, + SOGoRole_ObjectEraser, + SOGoCalendarRole_PrivateModifier, + SOGoCalendarRole_PublicModifier, + nil]; + [writeAccessRoles retain]; + } + + if (!readAccessRoles) + { + readAccessRoles = [NSArray arrayWithObjects: + SOGoCalendarRole_ConfidentialViewer, + SOGoCalendarRole_PrivateViewer, + SOGoCalendarRole_PublicViewer, + nil]; + [readAccessRoles retain]; + } + + return (hasWriteAccess) ? writeAccessRoles : readAccessRoles; +} + +- (void) adjustProxyRolesForUsers: (NSArray *) proxyUsers + remove: (BOOL) remove + forWriteAccess: (BOOL) write +{ + NSArray *roles; + + if (remove) + [self removeAclsForUsers: proxyUsers]; + else + { + roles = [self requiredProxyRolesWithWriteAccess: write]; + [self setRoles: roles forUsers: proxyUsers]; + } +} + @end /* SOGoAppointmentFolder */ diff --git a/SoObjects/Appointments/SOGoAppointmentFolders.h b/SoObjects/Appointments/SOGoAppointmentFolders.h index 19fa464ff..38f8ccc84 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolders.h +++ b/SoObjects/Appointments/SOGoAppointmentFolders.h @@ -29,9 +29,16 @@ @interface SOGoAppointmentFolders : SOGoParentFolder -- (NSArray *) proxyFoldersWithWriteAccess: (BOOL) hasWriteAccess; - (NSArray *) webCalendarIds; +- (void) adjustProxyRolesForUsers: (NSArray *) proxyUsers + remove: (BOOL) remove + forWriteAccess: (BOOL) write; + +- (void) adjustProxySubscriptionsForUsers: (NSArray *) proxyUsers + remove: (BOOL) remove + forWriteAccess: (BOOL) write; + @end #endif /* SOGOAPPOINTMENTFOLDERS_H */ diff --git a/SoObjects/Appointments/SOGoAppointmentFolders.m b/SoObjects/Appointments/SOGoAppointmentFolders.m index 9e29baa44..b3eb98b30 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolders.m +++ b/SoObjects/Appointments/SOGoAppointmentFolders.m @@ -33,18 +33,22 @@ #import +#import #import #import #import -#import -#import #import #import +#import +#import +#import #import #import "SOGoAppointmentFolder.h" +#import "SOGoAppointmentInboxFolder.h" #import "SOGoWebAppointmentFolder.h" +#import "SOGoUser+Appointments.h" #import "SOGoAppointmentFolders.h" @@ -73,17 +77,19 @@ - (NSArray *) toManyRelationshipKeys { + NSMutableArray *keys; NSEnumerator *sortedSubFolders; SOGoGCSFolder *currentFolder; NSString *login; - NSMutableArray *keys; + SOGoUser *ownerUser; - login = [[context activeUser] login]; if ([[context request] isICal]) { + login = [[context activeUser] login]; keys = [NSMutableArray array]; if ([owner isEqualToString: login]) { + [keys addObject: @"inbox"]; sortedSubFolders = [[self subFolders] objectEnumerator]; while ((currentFolder = [sortedSubFolders nextObject])) { @@ -93,7 +99,11 @@ } } else - [keys addObject: @"personal"]; + { + ownerUser = [SOGoUser userWithLogin: owner]; + keys = (NSMutableArray *) [[ownerUser userSettings] + proxiedCalendars]; + } } else keys = (NSMutableArray *) [super toManyRelationshipKeys]; @@ -101,6 +111,28 @@ return keys; } +- (id) lookupName: (NSString *) name + inContext: (WOContext *) lookupContext + acquire: (BOOL) acquire +{ + id obj; + NSString *login; + + if ([name isEqualToString: @"inbox"]) + { + login = [[context activeUser] login]; + if ([owner isEqualToString: login]) + obj = [SOGoAppointmentInboxFolder objectWithName: name + inContainer: self]; + else + obj = nil; + } + else + obj = [super lookupName: name inContext: lookupContext acquire: NO]; + + return obj; +} + - (NSString *) _fetchPropertyWithName: (NSString *) propertyName inArray: (NSArray *) section { @@ -245,37 +277,6 @@ return componentSet; } -- (NSArray *) proxyFoldersWithWriteAccess: (BOOL) hasWriteAccess -{ - NSMutableArray *proxyFolders; - NSArray *proxySubscribers; - NSEnumerator *folders; - SOGoAppointmentFolder *currentFolder; - NSString *folderOwner, *currentUser; - - proxyFolders = [NSMutableArray array]; - - currentUser = [[context activeUser] login]; - - [self initSubscribedSubFolders]; - folders = [subscribedSubFolders objectEnumerator]; - while ((currentFolder = [folders nextObject])) - { - folderOwner = [currentFolder ownerInContext: context]; - /* we currently only list the users of which we have subscribed to the - personal folder */ - if ([[currentFolder realNameInContainer] isEqualToString: @"personal"]) - { - proxySubscribers - = [currentFolder proxySubscribersWithWriteAccess: hasWriteAccess]; - if ([proxySubscribers containsObject: currentUser]) - [proxyFolders addObject: currentFolder]; - } - } - - return proxyFolders; -} - - (NSArray *) webCalendarIds { SOGoUserSettings *us; @@ -371,4 +372,48 @@ return aclManager; } +- (void) adjustProxyRolesForUsers: (NSArray *) proxyUsers + remove: (BOOL) remove + forWriteAccess: (BOOL) write +{ + NSArray *calendars; + SOGoUser *ownerUser; + SOGoAppointmentFolder *folder; + int count, max; + + ownerUser = [SOGoUser userWithLogin: owner]; + calendars = [[ownerUser userSettings] proxiedCalendars]; + max = [calendars count]; + for (count = 0; count < max; count++) + { + folder = [self lookupName: [calendars objectAtIndex: count] + inContext: context + acquire: NO]; + [folder adjustProxyRolesForUsers: proxyUsers + remove: remove + forWriteAccess: write]; + } +} + +- (void) adjustProxySubscriptionsForUsers: (NSArray *) proxyUsers + remove: (BOOL) remove + forWriteAccess: (BOOL) write +{ + int count, max; + SOGoUser *proxyUser; + + max = [proxyUsers count]; + for (count = 0; count < max; count++) + { + proxyUser = [SOGoUser userWithLogin: [proxyUsers objectAtIndex: count]]; + if (proxyUser) + [proxyUser adjustProxySubscriptionToUser: owner + remove: remove + forWriteAccess: write]; + else + [self warnWithFormat: @"(%@) user '%@' is invalid (ignored)", + NSStringFromSelector (_cmd), [proxyUser login]]; + } +} + @end diff --git a/SoObjects/Appointments/SOGoAppointmentInboxFolder.h b/SoObjects/Appointments/SOGoAppointmentInboxFolder.h new file mode 100644 index 000000000..427db1757 --- /dev/null +++ b/SoObjects/Appointments/SOGoAppointmentInboxFolder.h @@ -0,0 +1,43 @@ +/* SOGoAppointmentInboxFolder.h - this file is part of SOGo + * + * Copyright (C) 2010 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SOGOAPPOINTMENTINBOXFOLDER_H +#define SOGOAPPOINTMENTINBOXFOLDER_H + +#import "SOGoAppointmentFolder.h" + +@class NSArray; +@class NSException; +@class NSString; + +@class SOGoWebDAVValue; + +@interface SOGoAppointmentInboxFolder : SOGoAppointmentFolder + +- (NSString *) davCollectionTag; +- (NSArray *) davResourceType; +- (SOGoWebDAVValue *) davCalendarFreeBusySet; +- (NSException *) setDavCalendarFreeBusySet: (NSString *) newFreeBusySet; + +@end + +#endif /* SOGOAPPOINTMENTINBOXFOLDER_H */ diff --git a/SoObjects/Appointments/SOGoAppointmentInboxFolder.m b/SoObjects/Appointments/SOGoAppointmentInboxFolder.m new file mode 100644 index 000000000..01931a07f --- /dev/null +++ b/SoObjects/Appointments/SOGoAppointmentInboxFolder.m @@ -0,0 +1,84 @@ +/* SOGoAppointmentInboxFolder.m - this file is part of SOGo + * + * Copyright (C) 2010 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import +#import +#import + +#import +#import +#import + +#import "SOGoAppointmentFolders.h" + +#import "SOGoAppointmentInboxFolder.h" + +@implementation SOGoAppointmentInboxFolder + +- (NSString *) davCollectionTag +{ + return @"-1"; +} + +- (NSArray *) davResourceType +{ + NSMutableArray *colType; + NSString *login; + + colType = [NSMutableArray arrayWithCapacity: 10]; + [colType addObject: @"collection"]; + login = [[context activeUser] login]; + if ([login isEqualToString: [self ownerInContext: self]]) + [colType addObject: [NSArray arrayWithObjects: @"schedule-inbox", + XMLNS_CALDAV, nil]]; + + return colType; +} + +- (SOGoWebDAVValue *) davCalendarFreeBusySet +{ + NSEnumerator *subFolders; + SOGoAppointmentFolder *currentFolder; + NSMutableArray *response; + SOGoWebDAVValue *responseValue; + + response = [NSMutableArray array]; + subFolders = [[container subFolders] objectEnumerator]; + while ((currentFolder = [subFolders nextObject])) + [response + addObject: davElementWithContent (@"href", XMLNS_WEBDAV, + [currentFolder davURLAsString])]; + responseValue = [davElementWithContent (@"calendar-free-busy-set", XMLNS_CALDAV, response) + asWebDAVValue]; + + return responseValue; +} + +/* This method is ignored but we must return a success value. */ +- (NSException *) setDavCalendarFreeBusySet: (NSString *) newFreeBusySet +{ + return nil; +} + +@end diff --git a/SoObjects/Appointments/SOGoCalendarProxy.m b/SoObjects/Appointments/SOGoCalendarProxy.m index 711d487e7..bf2d21e4a 100644 --- a/SoObjects/Appointments/SOGoCalendarProxy.m +++ b/SoObjects/Appointments/SOGoCalendarProxy.m @@ -23,17 +23,19 @@ #import #import #import - +#import +#import +#import #import #import +#import + +#import "SOGoAppointmentFolders.h" -#import "SOGoAppointmentFolder.h" #import "SOGoCalendarProxy.h" @implementation SOGoCalendarProxy -#define XMLNS_CALENDARSERVER @"http://calendarserver.org/ns/" - - (id) init { if ((self = [super init])) @@ -61,7 +63,7 @@ else proxyType = @"calendar-proxy-read"; [rType addObject: [NSArray arrayWithObjects: proxyType, - XMLNS_CALENDARSERVER, nil]]; + XMLNS_CalendarServerOrg, nil]]; return rType; } @@ -69,24 +71,24 @@ - (NSArray *) davGroupMemberSet { NSMutableArray *members; - NSEnumerator *subscribers; - NSArray *member; + NSArray *proxyUsers, *member; SOGoUser *ownerUser; - SOGoAppointmentFolder *folder; - NSString *subscriber; + NSString *appName, *proxyUser; + int count, max; - members = [NSMutableArray array]; + appName = [[context request] applicationName]; - ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context] - roles: nil]; - folder = [ownerUser personalCalendarFolderInContext: context]; - subscribers = [[folder proxySubscribersWithWriteAccess: hasWriteAccess] - objectEnumerator]; - while ((subscriber = [subscribers nextObject])) + ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context]]; + proxyUsers = [[ownerUser userSettings] + calendarProxyUsersWithWriteAccess: hasWriteAccess]; + max = [proxyUsers count]; + members = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) { - member = [NSArray arrayWithObjects: @"href", @"DAV:", @"D", - [NSString stringWithFormat: @"/SOGo/dav/%@/", - subscriber], + proxyUser = [proxyUsers objectAtIndex: count]; + member = [NSArray arrayWithObjects: @"href", XMLNS_WEBDAV, @"D", + [NSString stringWithFormat: @"/%@/dav/%@/", + appName, proxyUser], nil]; [members addObject: member]; } @@ -94,11 +96,6 @@ return members; } -- (NSString *) davGroupMembership -{ - return nil; -} - - (NSString *) _parseSubscriber: (NSString *) memberSet until: (int) length { @@ -143,14 +140,49 @@ - (NSException *) setDavGroupMemberSet: (NSString *) memberSet { SOGoUser *ownerUser; - SOGoAppointmentFolder *folder; + SOGoUserSettings *us; + NSMutableArray *addedUsers, *removedUsers; + NSArray *oldProxyUsers, *newProxyUsers; + NSString *login; + SOGoAppointmentFolders *folders; - ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context] - roles: nil]; - folder = [ownerUser personalCalendarFolderInContext: context]; + login = [self ownerInContext: context]; + ownerUser = [SOGoUser userWithLogin: login roles: nil]; + us = [ownerUser userSettings]; + oldProxyUsers = [us calendarProxyUsersWithWriteAccess: hasWriteAccess]; + if (!oldProxyUsers) + oldProxyUsers = [NSMutableArray array]; + newProxyUsers = [self _parseSubscribers: memberSet]; + if (!newProxyUsers) + newProxyUsers = [NSMutableArray array]; + [us setCalendarProxyUsers: newProxyUsers + withWriteAccess: hasWriteAccess]; - return [folder setProxySubscribers: [self _parseSubscribers: memberSet] - withWriteAccess: hasWriteAccess]; + folders = [container lookupName: @"Calendar" inContext: context + acquire: NO]; + addedUsers = [newProxyUsers mutableCopy]; + [addedUsers removeObjectsInArray: oldProxyUsers]; + [folders adjustProxyRolesForUsers: addedUsers + remove: NO + forWriteAccess: hasWriteAccess]; + [folders adjustProxySubscriptionsForUsers: addedUsers + remove: NO + forWriteAccess: hasWriteAccess]; + [addedUsers autorelease]; + + removedUsers = [oldProxyUsers mutableCopy]; + [removedUsers removeObjectsInArray: newProxyUsers]; + [folders adjustProxyRolesForUsers: removedUsers + remove: YES + forWriteAccess: hasWriteAccess]; + [folders adjustProxySubscriptionsForUsers: removedUsers + remove: YES + forWriteAccess: hasWriteAccess]; + [removedUsers autorelease]; + + [us synchronize]; + + return nil; } @end diff --git a/SoObjects/Appointments/SOGoUser+Appointments.h b/SoObjects/Appointments/SOGoUser+Appointments.h new file mode 100644 index 000000000..8057ad2af --- /dev/null +++ b/SoObjects/Appointments/SOGoUser+Appointments.h @@ -0,0 +1,36 @@ +/* SOGoUser+Appointments.h - this file is part of SOGo + * + * Copyright (C) 2010 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SOGOUSER_APPOINTMENTS_H +#define SOGOUSER_APPOINTMENTS_H + +#import + +@interface SOGoUser (SOGoCalDAVSupport) + +- (void) adjustProxySubscriptionToUser: (NSString *) ownerUser + remove: (BOOL) remove + forWriteAccess: (BOOL) write; + +@end + +#endif /* SOGOUSER_APPOINTMENTS_H */ diff --git a/SoObjects/Appointments/SOGoUser+Appointments.m b/SoObjects/Appointments/SOGoUser+Appointments.m new file mode 100644 index 000000000..6f0dcf931 --- /dev/null +++ b/SoObjects/Appointments/SOGoUser+Appointments.m @@ -0,0 +1,74 @@ +/* SOGoUser+Appointments.m - this file is part of SOGo + * + * Copyright (C) 2010 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import + +#import "SOGoUser+Appointments.h" + +@implementation SOGoUser (SOGoCalDAVSupport) + +- (void) adjustProxySubscriptionToUser: (NSString *) ownerUser + remove: (BOOL) remove + forWriteAccess: (BOOL) write +{ + SOGoUserSettings *us; + NSMutableArray *subscriptions; + + us = [self userSettings]; + + /* first, we want to ensure the subscription does not appear in the + opposite list... */ + if (!remove) + { + subscriptions + = [[us calendarProxySubscriptionUsersWithWriteAccess: !write] + mutableCopy]; + [subscriptions autorelease]; + if ([subscriptions containsObject: ownerUser]) + { + [subscriptions removeObject: ownerUser]; + [us setCalendarProxySubscriptionUsers: subscriptions + withWriteAccess: !write]; + + } + } + + subscriptions + = [[us calendarProxySubscriptionUsersWithWriteAccess: write] + mutableCopy]; + [subscriptions autorelease]; + if (remove) + [subscriptions removeObject: ownerUser]; + else + { + if (!subscriptions) + subscriptions = [NSMutableArray array]; + [subscriptions addObjectUniquely: ownerUser]; + } + + [us setCalendarProxySubscriptionUsers: subscriptions + withWriteAccess: write]; + [us synchronize]; +} + +@end diff --git a/SoObjects/Appointments/SOGoUserFolder+Appointments.h b/SoObjects/Appointments/SOGoUserFolder+Appointments.h index 6f9933b6a..00307b7e5 100644 --- a/SoObjects/Appointments/SOGoUserFolder+Appointments.h +++ b/SoObjects/Appointments/SOGoUserFolder+Appointments.h @@ -32,10 +32,7 @@ - (NSArray *) davCalendarUserAddressSet; - (NSArray *) davCalendarHomeSet; -- (NSArray *) davCalendarScheduleInboxURL; - (NSArray *) davCalendarScheduleOutboxURL; -- (NSArray *) davDropboxHomeURL; -- (NSArray *) davNotificationsURL; @end diff --git a/SoObjects/Appointments/SOGoUserFolder+Appointments.m b/SoObjects/Appointments/SOGoUserFolder+Appointments.m index 506e1ac7c..f07592700 100644 --- a/SoObjects/Appointments/SOGoUserFolder+Appointments.m +++ b/SoObjects/Appointments/SOGoUserFolder+Appointments.m @@ -28,13 +28,17 @@ #import #import #import +#import #import -#import -#import +#import #import #import - +#import +#import +#import +#import +#import #import #import "SOGoAppointmentFolders.h" @@ -95,417 +99,141 @@ return [NSArray arrayWithObject: tag]; } -- (NSArray *) _davPersonalCalendarURL +- (NSArray *) _davSpecialCalendarURLWithName: (NSString *) name { SOGoAppointmentFolders *parent; - NSArray *tag; - NSString *parentURL; + NSArray *tag, *response; + NSString *parentURL, *login; - parent = [self privateCalendars: @"Calendar" inContext: context]; - parentURL = [parent davURLAsString]; + login = [[context activeUser] login]; + if ([login isEqualToString: owner]) + { + parent = [self privateCalendars: @"Calendar" inContext: context]; + parentURL = [parent davURLAsString]; - if ([parentURL hasSuffix: @"/"]) - parentURL = [parentURL substringToIndex: [parentURL length]-1]; + if ([parentURL hasSuffix: @"/"]) + parentURL = [parentURL substringToIndex: [parentURL length]-1]; - tag = [NSArray arrayWithObjects: @"href", @"DAV:", @"D", - [NSString stringWithFormat: @"%@/personal/", parentURL], - nil]; + tag = [NSArray arrayWithObjects: @"href", @"DAV:", @"D", + [NSString stringWithFormat: @"%@/%@/", parentURL, name], + nil]; + response = [NSArray arrayWithObject: tag]; + } + else + response = nil; - return [NSArray arrayWithObject: tag]; + return response; } - (NSArray *) davCalendarScheduleInboxURL { - NSArray *url; - - if ([[context request] isICal4]) - url = nil; - else - url = [self _davPersonalCalendarURL]; - - return url; + return [self _davSpecialCalendarURLWithName: @"inbox"]; } - (NSArray *) davCalendarScheduleOutboxURL { - NSArray *url; - - if ([[context request] isICal4]) - url = nil; - else - url = [self _davPersonalCalendarURL]; - - return url; + return [self _davSpecialCalendarURLWithName: @"personal"]; } -- (NSArray *) davDropboxHomeURL +- (NSArray *) _calendarProxiedUsersWithWriteAccess: (BOOL) write { - NSArray *url; + NSMutableArray *proxiedUsers; + SOGoUser *ownerUser; + NSArray *subscriptions; + NSString *ownerLogin, *currentLogin; + int count, max; - if ([[context request] isICal4]) - url = nil; - else - url = [self _davPersonalCalendarURL]; - - return url; -} - -- (NSArray *) davNotificationsURL -{ - NSArray *url; - - if ([[context request] isICal4]) - url = nil; - else - url = [self _davPersonalCalendarURL]; - - return url; -} - -- (WOResponse *) _prepareResponseFromContext: (WOContext *) queryContext -{ - WOResponse *r; - - r = [queryContext 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"]; - - return r; -} - -- (void) _fillMatches: (NSMutableDictionary *) matches - fromElement: (NSObject *) searchElement -{ - NSObject *list; - NSObject *valueNode; - NSArray *elements; - NSString *property, *match; - - list = [searchElement getElementsByTagName: @"prop"]; - if ([list length]) - { - elements = [self domNode: [list objectAtIndex: 0] - getChildNodesByType: DOM_ELEMENT_NODE]; - if ([elements count]) - { - valueNode = [elements objectAtIndex: 0]; - property = [NSString stringWithFormat: @"{%@}%@", - [valueNode namespaceURI], - [valueNode nodeName]]; - } - } - list = [searchElement getElementsByTagName: @"match"]; - if ([list length]) - { - valueNode = [[list objectAtIndex: 0] firstChild]; - match = [valueNode nodeValue]; - } - - [matches setObject: match forKey: property]; -} - -- (void) _fillProperties: (NSMutableArray *) properties - fromElement: (NSObject *) propElement -{ - NSEnumerator *elements; - NSObject *propNode; - NSString *property; - - elements = [[self domNode: propElement - getChildNodesByType: DOM_ELEMENT_NODE] - objectEnumerator]; - while ((propNode = [elements nextObject])) - { - property = [NSString stringWithFormat: @"{%@}%@", - [propNode namespaceURI], - [propNode nodeName]]; - [properties addObject: property]; - } -} - -- (void) _fillPrincipalMatches: (NSMutableDictionary *) matches - andProperties: (NSMutableArray *) properties - fromElement: (NSObject *) documentElement -{ - NSEnumerator *children; - NSObject *currentElement; - NSString *tag; - - children = [[self domNode: documentElement - getChildNodesByType: DOM_ELEMENT_NODE] - objectEnumerator]; - while ((currentElement = [children nextObject])) - { - tag = [currentElement tagName]; - if ([tag isEqualToString: @"property-search"]) - [self _fillMatches: matches fromElement: currentElement]; - else if ([tag isEqualToString: @"prop"]) - [self _fillProperties: properties fromElement: currentElement]; - else - [self errorWithFormat: @"principal-property-search: unknown tag '%@'", - tag]; - } -} - -#warning this is a bit ugly, as usual -- (void) _fillCollections: (NSMutableArray *) collections - withCalendarHomeSetMatching: (NSString *) value -{ - SOGoUserFolder *collection; - NSRange substringRange; - - substringRange = [value rangeOfString: @"/SOGo/dav/"]; - value = [value substringFromIndex: NSMaxRange (substringRange)]; - substringRange = [value rangeOfString: @"/Calendar"]; - value = [value substringToIndex: substringRange.location]; - collection = [[SOGoUser userWithLogin: value] - homeFolderInContext: context]; - if (collection) - [collections addObject: collection]; -} - -- (NSMutableArray *) _firstPrincipalCollectionsWhere: (NSString *) key - matches: (NSString *) value -{ - NSMutableArray *collections; - - collections = [NSMutableArray array]; - if ([key - isEqualToString: @"{urn:ietf:params:xml:ns:caldav}calendar-home-set"]) - [self _fillCollections: collections withCalendarHomeSetMatching: value]; - else - [self errorWithFormat: @"principal-property-search: unhandled key '%@'", - key]; - - return collections; -} - -#warning unused stub -- (BOOL) collectionDavKey: (NSString *) key - matches: (NSString *) value -{ - return YES; -} - -- (void) _principalCollections: (NSMutableArray **) baseCollections - where: (NSString *) key - matches: (NSString *) value -{ - SOGoUserFolder *currentCollection; - unsigned int count, max; - - if (!*baseCollections) - *baseCollections = [self _firstPrincipalCollectionsWhere: key - matches: value]; - else - { - max = [*baseCollections count]; - for (count = max; count > 0; count--) - { - currentCollection = [*baseCollections objectAtIndex: count - 1]; - if (![currentCollection collectionDavKey: key matches: value]) - [*baseCollections removeObjectAtIndex: count - 1]; - } - } -} - -- (NSArray *) _principalCollectionsMatching: (NSDictionary *) matches -{ - NSMutableArray *collections; - NSEnumerator *allKeys; - NSString *currentKey; - - collections = nil; - - allKeys = [[matches allKeys] objectEnumerator]; - while ((currentKey = [allKeys nextObject])) - [self _principalCollections: &collections - where: currentKey - matches: [matches objectForKey: currentKey]]; - - return collections; -} - -/* - - - - - doE - - - - - - Sales - - - - - - - - - */ - -- (void) _appendProperties: (NSArray *) properties - ofCollection: (SOGoUserFolder *) collection - toResponses: (NSMutableArray *) responses -{ - unsigned int count, max; - SEL methodSel; - NSString *currentProperty; - id currentValue; - NSMutableArray *response, *props; - NSDictionary *keyTuple; - - response = [NSMutableArray array]; - [response addObject: davElementWithContent (@"href", XMLNS_WEBDAV, - [collection davURLAsString])]; - props = [NSMutableArray array]; - max = [properties count]; + ownerLogin = [self ownerInContext: nil]; + ownerUser = [SOGoUser userWithLogin: ownerLogin]; + subscriptions = [[ownerUser userSettings] + calendarProxySubscriptionUsersWithWriteAccess: write]; + max = [subscriptions count]; + proxiedUsers = [NSMutableArray arrayWithCapacity: max]; for (count = 0; count < max; count++) { - currentProperty = [properties objectAtIndex: count]; - methodSel = SOGoSelectorForPropertyGetter (currentProperty); - if (methodSel && [collection respondsToSelector: methodSel]) - { - currentValue = [collection performSelector: methodSel]; - #warning evil eVIL EVIl! - if ([currentValue isKindOfClass: [NSArray class]]) - { - currentValue = [currentValue objectAtIndex: 0]; - currentValue - = davElementWithContent ([currentValue objectAtIndex: 0], - [currentValue objectAtIndex: 1], - [currentValue objectAtIndex: 3]); - } - keyTuple = [currentProperty asWebDAVTuple]; - [props addObject: davElementWithContent ([keyTuple objectForKey: @"method"], - [keyTuple objectForKey: @"ns"], - currentValue)]; - } + currentLogin = [subscriptions objectAtIndex: count]; + [proxiedUsers addObject: currentLogin]; } - [response addObject: davElementWithContent (@"propstat", XMLNS_WEBDAV, - davElementWithContent - (@"prop", XMLNS_WEBDAV, - props))]; - [responses addObject: davElementWithContent (@"response", XMLNS_WEBDAV, - response)]; + + return proxiedUsers; } -- (void) _appendProperties: (NSArray *) properties - ofCollections: (NSArray *) collections - toResponse: (WOResponse *) response +- (void) _addGroupMembershipToArray: (NSMutableArray *) groups + forWriteAccess: (BOOL) write { - NSDictionary *mStatus; - NSMutableArray *responses; - unsigned int count, max; + NSArray *proxiedUsers, *tag; + NSString *appName, *groupId, *proxiedUser; + int count, max; - responses = [NSMutableArray new]; - - max = [collections count]; - for (count = 0; count < max; count++) - [self _appendProperties: properties - ofCollection: [collections objectAtIndex: count] - toResponses: responses]; - mStatus = davElementWithContent (@"multistatus", XMLNS_WEBDAV, responses); - [response appendContentString: [mStatus asWebDavStringWithNamespaces: nil]]; - [responses release]; -} - -- (WOResponse *) davPrincipalPropertySearch: (WOContext *) queryContext -{ - NSObject *document; - NSObject *documentElement; - NSArray *collections; - NSMutableDictionary *matches; - NSMutableArray *properties; - WOResponse *r; - - document = [[context request] contentAsDOMDocument]; - documentElement = [document documentElement]; - - matches = [NSMutableDictionary dictionary]; - properties = [NSMutableArray array]; - [self _fillPrincipalMatches: matches andProperties: properties - fromElement: documentElement]; - collections = [self _principalCollectionsMatching: matches]; - r = [self _prepareResponseFromContext: queryContext]; - [self _appendProperties: properties ofCollections: collections - toResponse: r]; - - return r; -// @"/SOGo/dav/wsourdeau/" -// @"" -// @"HTTP/1.1 200 OK" -// @"" -// @"/SOGo/dav/wsourdeau/Calendar/" -// @"MAILTO:wsourdeau@inverse.ca" -// @"/SOGo/dav/wsourdeau/Calendar/personal/" -// @"/SOGo/dav/wsourdeau/Calendar/personal/" -// @"" -// @""]; - -// -// -// -// -// /SOGo/dav/wsourdeau/Calendar -// -// -// -// -// -// -// -} - -- (void) _addFolders: (NSEnumerator *) folders - withGroupTag: (NSString *) groupTag - toArray: (NSMutableArray *) groups -{ - SOGoAppointmentFolder *currentFolder; - NSString *folderOwner; - NSArray *tag; - - while ((currentFolder = [folders nextObject])) + proxiedUsers = [self _calendarProxiedUsersWithWriteAccess: write]; + max = [proxiedUsers count]; + if (max) { - folderOwner = [currentFolder ownerInContext: context]; - tag = [NSArray arrayWithObjects: @"href", @"DAV:", @"D", - [NSString stringWithFormat: @"/SOGo/dav/%@/%@/", - folderOwner, groupTag], + appName = [[context request] applicationName]; + groupId = [NSString stringWithFormat: @"calendar-proxy-%@", + (write ? @"write" : @"read")]; + for (count = 0; count < max; count++) + { + proxiedUser = [proxiedUsers objectAtIndex: count]; + tag = [NSArray arrayWithObjects: @"href", @"DAV:", @"D", + [NSString stringWithFormat: @"/%@/dav/%@/%@/", + appName, proxiedUser, groupId], nil]; - [groups addObject: tag]; + [groups addObject: tag]; + } } } - (NSArray *) davGroupMembership { - SOGoAppointmentFolders *calendars; - NSArray *writeFolders, *readFolders; NSMutableArray *groups; groups = [NSMutableArray array]; - [self ownerInContext: context]; - - calendars = [self privateCalendars: @"Calendar" inContext: context]; - writeFolders = [calendars proxyFoldersWithWriteAccess: YES]; - [self _addFolders: [writeFolders objectEnumerator] - withGroupTag: @"calendar-proxy-write" - toArray: groups]; - - readFolders = [calendars proxyFoldersWithWriteAccess: NO]; - [self _addFolders: [readFolders objectEnumerator] - withGroupTag: @"calendar-proxy-read" - toArray: groups]; + [self _addGroupMembershipToArray: groups + forWriteAccess: YES]; + [self _addGroupMembershipToArray: groups + forWriteAccess: NO]; return groups; } +- (NSMutableArray *) _davCalendarProxyForWrite: (BOOL) write +{ + NSMutableArray *proxyFor; + NSArray *proxiedUsers, *tag; + NSString *appName, *proxiedUser; + int count, max; + + appName = [[context request] applicationName]; + + proxiedUsers = [self _calendarProxiedUsersWithWriteAccess: write]; + max = [proxiedUsers count]; + proxyFor = [NSMutableArray arrayWithCapacity: max]; + if (max) + { + for (count = 0; count < max; count++) + { + proxiedUser = [proxiedUsers objectAtIndex: count]; + tag = [NSArray arrayWithObjects: @"href", @"DAV:", @"D", + [NSString stringWithFormat: @"/%@/dav/%@/", + appName, proxiedUser], + nil]; + [proxyFor addObject: tag]; + } + } + + return proxyFor; +} + +- (NSArray *) davCalendarProxyWriteFor +{ + return [self _davCalendarProxyForWrite: YES]; +} + +- (NSArray *) davCalendarProxyReadFor +{ + return [self _davCalendarProxyForWrite: NO]; +} + @end diff --git a/SoObjects/Appointments/product.plist b/SoObjects/Appointments/product.plist index 3e2327c72..af11c1799 100644 --- a/SoObjects/Appointments/product.plist +++ b/SoObjects/Appointments/product.plist @@ -33,7 +33,7 @@ SOGoAppointmentFolder = { superclass = "SOGoGCSFolder"; defaultRoles = { -/* "FreeBusyLookup" = ( "Owner", "FreeBusy", "AuthorizedSubscriber" ); */ + "Read FreeBusy" = ( "Authenticated" ); "Access Contents Information" = ( "Owner", "PublicResponder", "PublicModifier", "PublicViewer", "PublicDAndTViewer", "PrivateResponder", "PrivateModifier", "PrivateViewer", "PrivateDAndTViewer", "ConfidentialResponder", "ConfidentialModifier", "ConfidentialViewer", "ConfidentialDAndTViewer" ); "ViewWholePublicRecords" = ( "Owner", "PublicResponder", "PublicModifier", "PublicViewer" ); "ViewDAndTOfPublicRecords" = ( "Owner", "PublicDAndTViewer" ); @@ -49,6 +49,9 @@ "RespondToConfidentialRecords" = ( "Owner", "ConfidentialModifier", "ConfidentialResponder" ); }; }; + SOGoAppointmentInboxFolder = { + superclass = "SOGoAppointmentFolder"; + }; // SOGoGroupAppointmentFolder = { // superclass = "SOGoAppointmentFolder"; // }; diff --git a/SoObjects/Contacts/SOGoFolder+CardDAV.m b/SoObjects/Contacts/SOGoFolder+CardDAV.m index 087f7c3ce..8ad51dc28 100644 --- a/SoObjects/Contacts/SOGoFolder+CardDAV.m +++ b/SoObjects/Contacts/SOGoFolder+CardDAV.m @@ -32,6 +32,8 @@ #import #import +#import + #import "SOGoContactFolder.h" #import "SOGoContactGCSEntry.h" @@ -131,12 +133,7 @@ id document; r = [queryContext 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 prepareDAVResponse]; [r appendContentString: @""]; diff --git a/SoObjects/Mailer/SOGoMailBaseObject.m b/SoObjects/Mailer/SOGoMailBaseObject.m index 176dfaef0..de3ab5f8b 100644 --- a/SoObjects/Mailer/SOGoMailBaseObject.m +++ b/SoObjects/Mailer/SOGoMailBaseObject.m @@ -19,6 +19,7 @@ 02111-1307, USA. */ +#import #import #import #import diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index 22d933938..24468d62f 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -54,6 +54,7 @@ #import #import #import +#import #import "EOQualifier+MailDAV.h" #import "SOGoMailObject.h" @@ -1372,10 +1373,7 @@ static NSString *defaultUserID = @"anyone"; EOQualifier *searchQualifier; r = [context response]; - [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 prepareDAVResponse]; document = [[context request] contentAsDOMDocument]; documentElement = (DOMElement *) [document documentElement]; @@ -1394,8 +1392,6 @@ static NSString *defaultUserID = @"anyone"; messages = [self _fetchMessageProperties: properties matchingQualifier: searchQualifier andSortOrderings: sortOrderings]; - [r setStatus: 207]; - [r appendContentString: @"\n"]; [self _appendProperties: [properties allKeys] fromMessages: messages toResponse: r]; diff --git a/SoObjects/SOGo/DAVReportMap.plist b/SoObjects/SOGo/DAVReportMap.plist index a541f60cb..ceb65bccf 100644 --- a/SoObjects/SOGo/DAVReportMap.plist +++ b/SoObjects/SOGo/DAVReportMap.plist @@ -1,4 +1,7 @@ { /* -*-java-*- */ + /* WebDAV */ + "{DAV:}expand-property" = davExpandProperty; + /* CalDAV */ "{urn:ietf:params:xml:ns:caldav}calendar-query" = davCalendarQuery; "{urn:ietf:params:xml:ns:caldav}calendar-multiget" = davCalendarMultiget; @@ -10,11 +13,13 @@ "{urn:ietf:params:xml:ns:carddav}supported-collation-set" = davSupportedCollectionSet; - /* DeltaV */ + /* ACL */ + "{DAV:}acl-principal-prop-set" = davAclPrincipalPropSet; "{DAV:}principal-match" = davPrincipalMatch; "{DAV:}principal-property-search" = davPrincipalPropertySearch; "{DAV:}principal-search-property-set" = davPrincipalSearchPropertySet; - "{DAV:}principal-search-property-set" = davPrincipalSearchPropertySet; + + /* WebDAV sync */ "{DAV:}sync-collection" = davSyncCollection; /* Inverse DAV */ diff --git a/SoObjects/SOGo/DOMNode+SOGo.h b/SoObjects/SOGo/DOMNode+SOGo.h index 8ba80188c..b4297f6cb 100644 --- a/SoObjects/SOGo/DOMNode+SOGo.h +++ b/SoObjects/SOGo/DOMNode+SOGo.h @@ -27,6 +27,13 @@ @class DOMElement; +@interface NGDOMElement (SOGo) + +- (NSString *) asPropertyName; +- (NSString *) asPropertyPropertyName; + +@end + @interface NGDOMNodeWithChildren (SOGoDOMExtensions) - (id ) childElementsWithTag: (NSString *) tagName; diff --git a/SoObjects/SOGo/DOMNode+SOGo.m b/SoObjects/SOGo/DOMNode+SOGo.m index 6fa3514c1..f18299c34 100644 --- a/SoObjects/SOGo/DOMNode+SOGo.m +++ b/SoObjects/SOGo/DOMNode+SOGo.m @@ -24,9 +24,37 @@ #import #import +#import + +#import #import "DOMNode+SOGo.h" +@implementation NGDOMElement (SOGo) + +/* returns as "{ns}prop" identifier from an element tag */ +- (NSString *) asPropertyName +{ + return [NSString stringWithFormat: @"{%@}%@", + [self namespaceURI], + [self tagName]]; +} + +/* returns as "{ns}prop" identifier from a tag */ +- (NSString *) asPropertyPropertyName +{ + NSString *ns, *tag; + + ns = [self attribute: @"namespace"]; + if (!ns) + ns = XMLNS_WEBDAV; + tag = [self attribute: @"name"]; + + return [NSString stringWithFormat: @"{%@}%@", ns, tag]; +} + +@end + @implementation NGDOMNodeWithChildren (SOGo) - (id ) childElementsWithTag: (NSString *) tagName @@ -93,7 +121,6 @@ NSMutableArray *propertyNames; id children; DOMElement *currentElement; - NSString *property; unsigned int count, max; propertyNames = [NSMutableArray array]; @@ -104,12 +131,7 @@ { currentElement = [children objectAtIndex: count]; if ([currentElement nodeType] == DOM_ELEMENT_NODE) - { - property = [NSString stringWithFormat: @"{%@}%@", - [currentElement namespaceURI], - [currentElement tagName]]; - [propertyNames addObject: property]; - } + [propertyNames addObject: [currentElement asPropertyName]]; } return propertyNames; diff --git a/SoObjects/SOGo/NSArray+Utilities.h b/SoObjects/SOGo/NSArray+Utilities.h index f6213738d..7917af434 100644 --- a/SoObjects/SOGo/NSArray+Utilities.h +++ b/SoObjects/SOGo/NSArray+Utilities.h @@ -29,6 +29,9 @@ @interface NSArray (SOGoArrayUtilities) ++ (id) arrayWithObject: (id) member + repeatCount: (int) repeatCount; + - (id *) asPointersOfObjects; - (NSString *) jsonRepresentation; diff --git a/SoObjects/SOGo/NSArray+Utilities.m b/SoObjects/SOGo/NSArray+Utilities.m index 146ad8efc..8d9315ea2 100644 --- a/SoObjects/SOGo/NSArray+Utilities.m +++ b/SoObjects/SOGo/NSArray+Utilities.m @@ -30,6 +30,19 @@ @implementation NSArray (SOGoArrayUtilities) ++ (id) arrayWithObject: (id) member + repeatCount: (int) repeatCount +{ + NSMutableArray *newArray; + int count; + + newArray = [NSMutableArray arrayWithCapacity: repeatCount]; + for (count = 0; count < repeatCount; count++) + [newArray addObject: member]; + + return newArray; +} + - (id *) asPointersOfObjects { id *pointers; diff --git a/SoObjects/SOGo/NSObject+DAV.h b/SoObjects/SOGo/NSObject+DAV.h index 774d89e8e..e1d0e76a2 100644 --- a/SoObjects/SOGo/NSObject+DAV.h +++ b/SoObjects/SOGo/NSObject+DAV.h @@ -24,11 +24,21 @@ #define NSOBJECT_DAV_H #import +#import @class NSMutableDictionary; @class NSString; + +@class SoSelectorInvocation; + @class SOGoWebDAVValue; +typedef enum _HTTPStatusCode { + HTTPStatus200 = 0, + HTTPStatus201, + HTTPStatus404, +} HTTPStatusCode; + #define davElement(t,n) \ [NSDictionary dictionaryWithObjectsAndKeys: t, @"method", n, @"ns", nil] @@ -37,12 +47,26 @@ n, @"ns", \ c, @"content", nil] +SEL SOGoSelectorForPropertyGetter (NSString *property); +SEL SOGoSelectorForPropertySetter (NSString *property); + @interface NSObject (SOGoWebDAVExtensions) - (NSString *) asWebDavStringWithNamespaces: (NSMutableDictionary *) namespaces; - (SOGoWebDAVValue *) asWebDAVValue; +- (SOGoWebDAVValue *) davSupportedReportSet; + +- (SEL) davPropertySelectorForKey: (NSString *) key; +- (NSString *) davReportSelectorForKey: (NSString *) key; +- (SoSelectorInvocation *) davReportInvocationForKey: (NSString *) key; + +/* response helpers */ +- (NSDictionary *) responseForURL: (NSString *) url + withProperties200: (NSArray *) properties200 + andProperties404: (NSArray *) properties404; + @end #endif /* NSOBJECT_DAV_H */ diff --git a/SoObjects/SOGo/NSObject+DAV.m b/SoObjects/SOGo/NSObject+DAV.m index e36a8d5fd..30f5ab016 100644 --- a/SoObjects/SOGo/NSObject+DAV.m +++ b/SoObjects/SOGo/NSObject+DAV.m @@ -1,6 +1,6 @@ /* NSObject+DAV.m - this file is part of SOGo * - * Copyright (C) 2008 Inverse inc. + * Copyright (C) 2008-2010 Inverse inc. * * Author: Wolfgang Sourdeau * @@ -20,12 +20,110 @@ * Boston, MA 02111-1307, USA. */ +#import +#import +#import +#import + +#import +#import +#import +#import + +#import + +#import + +#import "NSArray+DAV.h" +#import "NSString+DAV.h" +#import "SOGoUser.h" +#import "SOGoUserFolder.h" #import "SOGoWebDAVValue.h" +#import "SOGoObject.h" + #import "NSObject+DAV.h" +static NSMutableDictionary *setterMap = nil; +static NSMutableDictionary *getterMap = nil; +static NSDictionary *reportMap = nil; + +SEL SOGoSelectorForPropertyGetter (NSString *property) +{ + SEL propSel; + NSValue *propPtr; + NSDictionary *map; + NSString *methodName; + + if (!getterMap) + getterMap = [NSMutableDictionary new]; + propPtr = [getterMap objectForKey: property]; + if (propPtr) + propSel = [propPtr pointerValue]; + else + { + map = [SOGoObject defaultWebDAVAttributeMap]; + methodName = [map objectForKey: property]; + if (methodName) + { + propSel = NSSelectorFromString (methodName); + if (propSel) + [getterMap setObject: [NSValue valueWithPointer: propSel] + forKey: property]; + } + else + propSel = NULL; + } + + return propSel; +} + +SEL SOGoSelectorForPropertySetter (NSString *property) +{ + SEL propSel; + NSValue *propPtr; + NSDictionary *map; + NSString *methodName; + + if (!setterMap) + setterMap = [NSMutableDictionary new]; + propPtr = [setterMap objectForKey: property]; + if (propPtr) + propSel = [propPtr pointerValue]; + else + { + map = [SOGoObject defaultWebDAVAttributeMap]; + methodName = [map objectForKey: property]; + if (methodName) + { + propSel = NSSelectorFromString ([methodName davSetterName]); + if (propSel) + [setterMap setObject: [NSValue valueWithPointer: propSel] + forKey: property]; + } + else + propSel = NULL; + } + + return propSel; +} + @implementation NSObject (SOGoWebDAVExtensions) +- (void) loadReportMAP +{ + NSBundle *bundle; + NSString *filename; + + bundle = [NSBundle bundleForClass: [SOGoObject class]]; + filename = [bundle pathForResource: @"DAVReportMap" ofType: @"plist"]; + if (filename + && [[NSFileManager defaultManager] fileExistsAtPath: filename]) + reportMap = [[NSDictionary alloc] initWithContentsOfFile: filename]; + else + [self logWithFormat: @"DAV REPORT map not found!"]; +} + - (NSString *) asWebDavStringWithNamespaces: (NSMutableDictionary *) namespaces { @@ -41,4 +139,130 @@ attributes: nil]; } +- (SEL) davPropertySelectorForKey: (NSString *) key +{ + static NSMutableDictionary *attrSelectorMap = nil; + NSDictionary *attrMap; + NSString *methodName; + NSValue *methodValue; + SEL propertySel; + + methodValue = [attrSelectorMap objectForKey: key]; + if (!methodValue) + { + if (!attrSelectorMap) + attrSelectorMap = [NSMutableDictionary new]; + attrMap = [[self class] defaultWebDAVAttributeMap]; + methodName = [attrMap objectForKey: key]; + if (methodName) + propertySel = NSSelectorFromString (methodName); + else + propertySel = NULL; + methodValue = [NSValue valueWithPointer: propertySel]; + [attrSelectorMap setObject: methodValue forKey: key]; + } + + return [methodValue pointerValue]; +} + +- (NSString *) davReportSelectorForKey: (NSString *) key +{ + NSString *methodName, *objcMethod, *resultName; + SEL reportSel; + + resultName = nil; + + if (!reportMap) + [self loadReportMAP]; + + methodName = [reportMap objectForKey: key]; + if (methodName) + { + objcMethod = [NSString stringWithFormat: @"%@:", methodName]; + reportSel = NSSelectorFromString (objcMethod); + if ([self respondsToSelector: reportSel]) + resultName = objcMethod; + } + + return resultName; +} + +- (SoSelectorInvocation *) davReportInvocationForKey: (NSString *) key +{ + NSString *objCMethod; + SoSelectorInvocation *invocation; + + objCMethod = [self davReportSelectorForKey: key]; + if (objCMethod) + { + invocation = [[SoSelectorInvocation alloc] + initWithSelectorNamed: objCMethod + addContextParameter: YES]; + [invocation autorelease]; + } + else + invocation = nil; + + return invocation; +} + +- (SOGoWebDAVValue *) davSupportedReportSet +{ + NSDictionary *currentValue; + NSEnumerator *reportKeys; + NSMutableArray *reportSet; + NSString *currentKey; + + reportSet = [NSMutableArray array]; + + if (!reportMap) + [self loadReportMAP]; + + reportKeys = [[reportMap allKeys] objectEnumerator]; + while ((currentKey = [reportKeys nextObject])) + if ([self davReportSelectorForKey: currentKey]) + { + currentValue = davElementWithContent(@"report", + @"DAV:", + [currentKey asDavInvocation]); + [reportSet addObject: davElementWithContent(@"supported-report", + @"DAV:", currentValue)]; + } + + return [davElementWithContent (@"supported-report-set", @"DAV:", reportSet) + asWebDAVValue]; +} + +- (NSDictionary *) responseForURL: (NSString *) url + withProperties200: (NSArray *) properties200 + andProperties404: (NSArray *) properties404 +{ + static NSString *statusStrings[] = { @"HTTP/1.1 200 OK", + @"HTTP/1.1 201 Created", + @"HTTP/1.1 404 Not Found" }; + NSString *status; + NSDictionary *responseElement; + NSMutableArray *elements; + + elements = [NSMutableArray arrayWithCapacity: 3]; + + [elements addObject: davElementWithContent (@"href", XMLNS_WEBDAV, + url)]; + if ([properties200 count]) + { + status = statusStrings[HTTPStatus200]; + [elements addObject: [properties200 asDAVPropstatWithStatus: status]]; + } + if ([properties404 count]) + { + status = statusStrings[HTTPStatus404]; + [elements addObject: [properties404 asDAVPropstatWithStatus: status]]; + } + + responseElement = davElementWithContent (@"response", XMLNS_WEBDAV, + elements); + + return responseElement; +} + @end diff --git a/SoObjects/SOGo/NSObject+Utilities.h b/SoObjects/SOGo/NSObject+Utilities.h index b890d6511..7708e99fe 100644 --- a/SoObjects/SOGo/NSObject+Utilities.h +++ b/SoObjects/SOGo/NSObject+Utilities.h @@ -25,12 +25,17 @@ #import +#import + @class NSString; @interface NSObject (SOGoObjectUtilities) - (NSString *) jsonRepresentation; +- (NSArray *) domNode: (id ) node + getChildNodesByType: (DOMNodeType) type; + @end #endif /* NSOBJECT+UTILITIES_H */ diff --git a/SoObjects/SOGo/NSObject+Utilities.m b/SoObjects/SOGo/NSObject+Utilities.m index 412b2ff4e..5a64b49f6 100644 --- a/SoObjects/SOGo/NSObject+Utilities.m +++ b/SoObjects/SOGo/NSObject+Utilities.m @@ -20,6 +20,7 @@ * Boston, MA 02111-1307, USA. */ +#import #import #import "NSObject+Utilities.h" @@ -33,4 +34,23 @@ return nil; } +- (NSArray *) domNode: (id ) node + getChildNodesByType: (DOMNodeType ) type +{ + NSMutableArray *nodes; + id currentChild; + + nodes = [NSMutableArray array]; + + currentChild = [node firstChild]; + while (currentChild) + { + if ([currentChild nodeType] == type) + [nodes addObject: currentChild]; + currentChild = [currentChild nextSibling]; + } + + return nodes; +} + @end diff --git a/SoObjects/SOGo/NSString+DAV.h b/SoObjects/SOGo/NSString+DAV.h index 156d06ced..29478150b 100644 --- a/SoObjects/SOGo/NSString+DAV.h +++ b/SoObjects/SOGo/NSString+DAV.h @@ -34,6 +34,13 @@ - (NSMutableDictionary *) asWebDAVTuple; - (NSMutableDictionary *) asWebDAVTupleWithContent: (id) content; + +- (NSString *) davMethodToObjC; +- (NSString *) davSetterName; +- (NSDictionary *) asDavInvocation; + +- (NSString *) removeOutsideTags; + @end #endif /* NSSTRING_DAV_H */ diff --git a/SoObjects/SOGo/NSString+DAV.m b/SoObjects/SOGo/NSString+DAV.m index 9052c6d54..73f53f78d 100644 --- a/SoObjects/SOGo/NSString+DAV.m +++ b/SoObjects/SOGo/NSString+DAV.m @@ -24,6 +24,8 @@ #import +#import "NSArray+Utilities.h" + #import "NSString+DAV.h" @implementation NSString (SOGoWebDAVExtensions) @@ -34,6 +36,16 @@ return [self stringByEscapingXMLString]; } +- (NSString *) asDAVPropertyDescription +{ + NSArray *components; + + components = [[self componentsSeparatedByString: @"-"] + resultsOfSelector: @selector (capitalizedString)]; + + return [components componentsJoinedByString: @" "]; +} + #warning we should use the same nomenclature as the webdav values... - (NSMutableDictionary *) asWebDAVTuple { @@ -49,6 +61,60 @@ nodeName, @"method", nil]; } + +- (NSString *) davMethodToObjC +{ + NSMutableString *newName; + NSArray *components; + + newName = [NSMutableString stringWithString: @"dav"]; + components = [[self componentsSeparatedByString: @"-"] + resultsOfSelector: @selector (capitalizedString)]; + [newName appendString: [components componentsJoinedByString: @""]]; + + return newName; +} + +- (NSString *) davSetterName +{ + unichar firstLetter; + NSString *firstString; + + firstLetter = [self characterAtIndex: 0]; + firstString = [[NSString stringWithCharacters: &firstLetter length: 1] + uppercaseString]; + return [NSString stringWithFormat: @"set%@%@:", + firstString, [self substringFromIndex: 1]]; +} + +- (NSDictionary *) asDavInvocation +{ + NSMutableDictionary *davInvocation; + NSRange nsEnclosing, methodEnclosing; + unsigned int length; + + davInvocation = nil; + if ([self hasPrefix: @"{"]) + { + nsEnclosing = [self rangeOfString: @"}"]; + length = [self length]; + if (nsEnclosing.length > 0 && nsEnclosing.location < (length - 1)) + { + methodEnclosing = NSMakeRange (nsEnclosing.location + 1, + length - nsEnclosing.location - 1); + nsEnclosing.length = nsEnclosing.location - 1; + nsEnclosing.location = 1; + davInvocation = [NSMutableDictionary dictionaryWithCapacity: 2]; + [davInvocation setObject: [self substringWithRange: nsEnclosing] + forKey: @"ns"]; + [davInvocation setObject: [self substringWithRange: methodEnclosing] + forKey: @"method"]; + } + } + + return davInvocation; +} + - (NSMutableDictionary *) asWebDAVTupleWithContent: (id) content { NSMutableDictionary *tuple; @@ -59,4 +125,28 @@ return tuple; } +- (NSString *) removeOutsideTags +{ + NSString *newString; + NSRange range; + + newString = nil; + + /* we extract the text between >XXX"]; + if (range.location != NSNotFound) + { + newString = [self substringFromIndex: range.location + 1]; + range = [newString rangeOfString: @"<" options: NSBackwardsSearch]; + if (range.location != NSNotFound) + newString = [newString substringToIndex: range.location]; + else + newString = nil; + } + else + newString = nil; + + return newString; +} + @end diff --git a/SoObjects/SOGo/NSString+Utilities.h b/SoObjects/SOGo/NSString+Utilities.h index 7ae807b8b..d13892776 100644 --- a/SoObjects/SOGo/NSString+Utilities.h +++ b/SoObjects/SOGo/NSString+Utilities.h @@ -36,10 +36,6 @@ - (NSString *) urlWithoutParameters; -- (NSString *) davMethodToObjC; -- (NSString *) davSetterName; -- (NSDictionary *) asDavInvocation; - - (NSString *) stringByDetectingURLs; - (NSString *) doubleQuotedString; diff --git a/SoObjects/SOGo/NSString+Utilities.m b/SoObjects/SOGo/NSString+Utilities.m index 9f2a67d06..27daaf6a5 100644 --- a/SoObjects/SOGo/NSString+Utilities.m +++ b/SoObjects/SOGo/NSString+Utilities.m @@ -102,60 +102,6 @@ static int cssEscapingCount; return newUrl; } -- (NSString *) davMethodToObjC -{ - NSMutableString *newName; - NSEnumerator *components; - NSString *component; - - newName = [NSMutableString stringWithString: @"dav"]; - components = [[self componentsSeparatedByString: @"-"] objectEnumerator]; - while ((component = [components nextObject])) - [newName appendString: [component capitalizedString]]; - - return newName; -} - -- (NSString *) davSetterName -{ - unichar firstLetter; - NSString *firstString; - - firstLetter = [self characterAtIndex: 0]; - firstString = [[NSString stringWithCharacters: &firstLetter length: 1] - uppercaseString]; - return [NSString stringWithFormat: @"set%@%@:", - firstString, [self substringFromIndex: 1]]; -} - -- (NSDictionary *) asDavInvocation -{ - NSMutableDictionary *davInvocation; - NSRange nsEnclosing, methodEnclosing; - unsigned int length; - - davInvocation = nil; - if ([self hasPrefix: @"{"]) - { - nsEnclosing = [self rangeOfString: @"}"]; - length = [self length]; - if (nsEnclosing.length > 0 && nsEnclosing.location < (length - 1)) - { - methodEnclosing = NSMakeRange (nsEnclosing.location + 1, - length - nsEnclosing.location - 1); - nsEnclosing.length = nsEnclosing.location - 1; - nsEnclosing.location = 1; - davInvocation = [NSMutableDictionary dictionaryWithCapacity: 2]; - [davInvocation setObject: [self substringWithRange: nsEnclosing] - forKey: @"ns"]; - [davInvocation setObject: [self substringWithRange: methodEnclosing] - forKey: @"method"]; - } - } - - return davInvocation; -} - - (NSRange) _rangeOfURLInRange: (NSRange) refRange { int start, length; diff --git a/SoObjects/SOGo/SOGoFolder.m b/SoObjects/SOGo/SOGoFolder.m index cd02a43a2..b47a9fce2 100644 --- a/SoObjects/SOGo/SOGoFolder.m +++ b/SoObjects/SOGo/SOGoFolder.m @@ -25,14 +25,25 @@ #import #import #import +#import + +#import +#import + +#import + +#import #import +#import "DOMNode+SOGo.h" +#import "NSArray+Utilities.h" #import "NSObject+DAV.h" +#import "NSString+DAV.h" #import "NSString+Utilities.h" - #import "SOGoPermissions.h" #import "SOGoWebDAVAclManager.h" +#import "WOResponse+SOGo.h" #import "SOGoFolder.h" @@ -42,6 +53,14 @@ @end +@interface SOGoFolder (private) + +- (NSArray *) _interpretWebDAVArrayValue: (id) value; +- (NSDictionary *) _expandPropertyResponse: (NGDOMElement *) property + forHREF: (NSString *) href; + +@end + @implementation SOGoFolder + (SOGoWebDAVAclManager *) webdavAclManager @@ -51,14 +70,14 @@ if (!webdavAclManager) { webdavAclManager = [SOGoWebDAVAclManager new]; - [webdavAclManager registerDAVPermission: davElement (@"read", @"DAV:") + [webdavAclManager registerDAVPermission: davElement (@"read", XMLNS_WEBDAV) abstract: YES withEquivalent: SoPerm_WebDAVAccess - asChildOf: davElement (@"all", @"DAV:")]; - [webdavAclManager registerDAVPermission: davElement (@"read-current-user-privilege-set", @"DAV:") + asChildOf: davElement (@"all", XMLNS_WEBDAV)]; + [webdavAclManager registerDAVPermission: davElement (@"read-current-user-privilege-set", XMLNS_WEBDAV) abstract: YES withEquivalent: nil - asChildOf: davElement (@"read", @"DAV:")]; + asChildOf: davElement (@"read", XMLNS_WEBDAV)]; } return webdavAclManager; @@ -150,6 +169,12 @@ return YES; } +- (NSString *) davURLAsString +{ + return [[container davURLAsString] + stringByAppendingFormat: @"%@/", nameInContainer]; +} + - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid { return [[self soURL] absoluteString]; @@ -250,6 +275,218 @@ return YES; } +- (NSArray *) _extractHREFSFromPropertyValues: (NSArray *) values +{ + NSMutableArray *hrefs; + NSDictionary *value; + int count, max; + + max = [values count]; + hrefs = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) + { + value = [values objectAtIndex: count]; + if ([value isKindOfClass: [NSDictionary class]]) + { + if ([[value objectForKey: @"method"] isEqualToString: @"href"]) + [hrefs addObject: [value objectForKey: @"content"]]; + else + [self errorWithFormat: @"value is not an href"]; + } + else if ([value isKindOfClass: [NSString class]]) + { + /* we extract the text between XXX, but in a bad way */ + [hrefs addObject: [(NSString *) value removeOutsideTags]]; + } + else + [self errorWithFormat: @"value class is '%@' instead of NSDictionary", + NSStringFromClass ([value class])]; + } + + return hrefs; +} + +- (NSArray *) _interpretSoWebDAVValue: (SoWebDAVValue *) value +{ + NSString *valueString; + + valueString = [value stringForTag: @"" rawName: @"" + inContext: context prefixes: nil]; + + return [NSArray arrayWithObject: [valueString removeOutsideTags]]; +} + +- (NSArray *) _interpretWebDAVValue: (id) value +{ + NSArray *result; + + if ([value isKindOfClass: [NSString class]]) + result = [NSArray arrayWithObject: value]; + else if ([value isKindOfClass: [SoWebDAVValue class]]) + result = [self _interpretSoWebDAVValue: value]; + else if ([value isKindOfClass: [NSArray class]]) + result = [self _interpretWebDAVArrayValue: value]; + else + result = nil; + + return result; +} + +- (NSArray *) _interpretWebDAVArrayValue: (id) value +{ + int count, max; + NSMutableArray *results; + id subValue, subResults; + + max = [value count]; + results = [NSMutableArray arrayWithCapacity: max]; + if (max > 0) + { + subValue = [value objectAtIndex: 0]; + if ([subValue isKindOfClass: [NSString class]]) + [results addObject: + davElementWithContent (subValue, + [value objectAtIndex: 1], + [value objectAtIndex: 3])]; + else + { + for (count = 0; count < max; count++) + { + subResults + = [self _interpretWebDAVValue: [value objectAtIndex: count]]; + [results addObjectsFromArray: subResults]; + } + } + } + + return results; +} + +- (NSArray *) _expandedPropertyValue: (NGDOMElement *) property + forObject: (SOGoObject *) currentObject +{ + NSString *propertyTag; + SEL propertySel; + id value; + + propertyTag = [property asPropertyPropertyName]; + propertySel = [self davPropertySelectorForKey: propertyTag]; + if (propertySel) + value = [currentObject performSelector: propertySel]; + else + value = nil; + + return [self _interpretWebDAVValue: value]; +} + +- (NSArray *) _expandPropertyValue: (NGDOMElement *) property + forObject: (SOGoObject *) currentObject +{ + NSArray *values, *hrefs; + NSString *href; + NSMutableArray *expandedValues; + int count, max; + BOOL needsExpansion; + + needsExpansion = ([[property childElementsWithTag: @"property"] length] > 0); + values = [self _expandedPropertyValue: property + forObject: currentObject]; + max = [values count]; + expandedValues = [NSMutableArray arrayWithCapacity: max]; + if (max) + { + if (needsExpansion) + { + hrefs = [self _extractHREFSFromPropertyValues: values]; + max = [hrefs count]; + for (count = 0; count < max; count++) + { + href = [hrefs objectAtIndex: count]; + [expandedValues addObject: [self _expandPropertyResponse: property + forHREF: href]]; + } + } + else + [expandedValues setArray: values]; + } + + return expandedValues; +} + +- (NSDictionary *) _expandPropertyResponse: (NGDOMElement *) property + forObject: (SOGoObject *) currentObject +{ + id properties; + NSArray *childValue; + NGDOMElement *childProperty; + NSDictionary *response; + NSMutableArray *properties200, *properties404; + int count, max; + NSString *tagName, *tagNS; + + properties = [property childElementsWithTag: @"property"]; + max = [properties length]; + properties200 = [NSMutableArray arrayWithCapacity: max]; + properties404 = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) + { + childProperty = [properties objectAtIndex: count]; + childValue = [self _expandPropertyValue: childProperty + forObject: currentObject]; + tagNS = [childProperty attribute: @"namespace"]; + if (!tagNS) + tagNS = XMLNS_WEBDAV; + tagName = [childProperty attribute: @"name"]; + if ([childValue count]) + [properties200 addObject: davElementWithContent (tagName, + tagNS, + childValue)]; + else + [properties404 addObject: davElement (tagName, tagNS)]; + } + response = [self responseForURL: [currentObject davURLAsString] + withProperties200: properties200 + andProperties404: properties404]; + + return response; +} + +- (NSDictionary *) _expandPropertyResponse: (NGDOMElement *) property + forHREF: (NSString *) href +{ + SOGoObject *lookupObject; + NSDictionary *response; + + lookupObject = [self lookupObjectAtDAVUrl: href]; + if (lookupObject) + response = [self _expandPropertyResponse: property + forObject: lookupObject]; + else + response = nil; + + return response; +} + +- (WOResponse *) davExpandProperty: (WOContext *) localContext +{ + WOResponse *r; + id document; + NSDictionary *response, *multistatus; + NGDOMElement *documentElement; + + r = [localContext response]; + [r prepareDAVResponse]; + + document = [[context request] contentAsDOMDocument]; + documentElement = (NGDOMElement *) [document documentElement]; + response = [self _expandPropertyResponse: documentElement forObject: self]; + multistatus = davElementWithContent (@"multistatus", XMLNS_WEBDAV, + response); + [r appendContentString: [multistatus asWebDavStringWithNamespaces: nil]]; + + return r; +} + /* web dav acl helper */ - (void) _fillArrayWithPrincipalsOwnedBySelf: (NSMutableArray *) hrefs { diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index 8d0d4a971..7c488531c 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -89,14 +89,14 @@ static NSArray *childRecordFields = nil; if (!aclManager) { aclManager = [SOGoWebDAVAclManager new]; - [aclManager registerDAVPermission: davElement (@"read", @"DAV:") + /* [aclManager registerDAVPermission: davElement (@"read", @"DAV:") abstract: YES withEquivalent: SoPerm_WebDAVAccess asChildOf: davElement (@"all", @"DAV:")]; [aclManager registerDAVPermission: davElement (@"read-current-user-privilege-set", @"DAV:") abstract: YES withEquivalent: SoPerm_WebDAVAccess - asChildOf: davElement (@"read", @"DAV:")]; + asChildOf: davElement (@"read", @"DAV:")]; */ [aclManager registerDAVPermission: davElement (@"write", @"DAV:") abstract: YES withEquivalent: nil @@ -156,6 +156,7 @@ static NSArray *childRecordFields = nil; NSArray *elements, *pathElements; NSString *path, *objectPath, *login, *currentUser, *ocsName, *folderName; WOContext *context; + BOOL isSubscription; elements = [reference componentsSeparatedByString: @":"]; login = [elements objectAtIndex: 0]; @@ -175,7 +176,8 @@ static NSArray *childRecordFields = nil; newFolder = [self objectWithName: folderName inContainer: aContainer]; [newFolder setOCSPath: path]; [newFolder setOwner: login]; - [newFolder setIsSubscription: ![login isEqualToString: currentUser]]; + isSubscription = ![login isEqualToString: [aContainer ownerInContext: context]]; + [newFolder setIsSubscription: isSubscription]; if (![newFolder displayName]) newFolder = nil; @@ -1111,10 +1113,7 @@ static NSArray *childRecordFields = nil; NSArray *records; r = [context response]; - [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 prepareDAVResponse]; document = [[context request] contentAsDOMDocument]; documentElement = (DOMElement *) [document documentElement]; @@ -1127,15 +1126,10 @@ static NSArray *childRecordFields = nil; records = [self _fetchSyncTokenFields: properties matchingSyncToken: syncToken]; if (![syncToken length] || [records count]) - { - [r setStatus: 207]; - [r appendContentString: @"\r\n"]; - [self _appendComponentProperties: [properties allKeys] - fromRecords: records - matchingSyncToken: [syncToken intValue] - toResponse: r]; - } + [self _appendComponentProperties: [properties allKeys] + fromRecords: records + matchingSyncToken: [syncToken intValue] + toResponse: r]; else [r appendDAVError: davElement (@"valid-sync-token", XMLNS_WEBDAV)]; @@ -1340,8 +1334,8 @@ static NSArray *childRecordFields = nil; [aclsForObject removeObjectsForKeys: usersAndGroups]; uids = [usersAndGroups componentsJoinedByString: @"') OR (c_uid = '"]; qs = [NSString - stringWithFormat: @"(c_object = '/%@') AND ((c_uid = '%@'))", - objectPath, uids]; + stringWithFormat: @"(c_object = '/%@') AND ((c_uid = '%@'))", + objectPath, uids]; qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; [[self ocsFolder] deleteAclMatchingQualifier: qualifier]; } @@ -1363,10 +1357,10 @@ static NSArray *childRecordFields = nil; while ((currentRole = [userRoles nextObject])) { SQL = [NSString stringWithFormat: @"INSERT INTO %@" - @" (c_object, c_uid, c_role)" - @" VALUES ('/%@', '%@', '%@')", - [folder aclTableName], - objectPath, uid, currentRole]; + @" (c_object, c_uid, c_role)" + @" VALUES ('/%@', '%@', '%@')", + [folder aclTableName], + objectPath, uid, currentRole]; [channel evaluateExpressionX: SQL]; } @@ -1437,9 +1431,9 @@ static NSArray *childRecordFields = nil; - (void) setRoles: (NSArray *) roles forUser: (NSString *) uid { - return [self setRoles: roles - forUser: uid - forObjectAtPath: [self pathArrayToFolder]]; + return [self setRoles: roles + forUser: uid + forObjectAtPath: [self pathArrayToFolder]]; } - (void) removeAclsForUsers: (NSArray *) users diff --git a/SoObjects/SOGo/SOGoObject.h b/SoObjects/SOGo/SOGoObject.h index f0c0182a6..b27a30632 100644 --- a/SoObjects/SOGo/SOGoObject.h +++ b/SoObjects/SOGo/SOGoObject.h @@ -24,8 +24,6 @@ #import -#import - #if LIB_FOUNDATION_LIBRARY #error SOGo will not work properly with libFoundation. #error Please use gnustep-base instead. @@ -62,9 +60,6 @@ #define $(class) NSClassFromString(class) -SEL SOGoSelectorForPropertyGetter (NSString *property); -SEL SOGoSelectorForPropertySetter (NSString *property); - @interface SOGoObject : NSObject { WOContext *context; @@ -132,6 +127,8 @@ SEL SOGoSelectorForPropertySetter (NSString *property); - (NSArray *) aclsForUser: (NSString *) uid; - (void) setRoles: (NSArray *) roles forUser: (NSString *) uid; +- (void) setRoles: (NSArray *) roles + forUsers: (NSArray *) users; - (void) removeAclsForUsers: (NSArray *) users; - (NSString *) defaultUserID; @@ -141,6 +138,8 @@ SEL SOGoSelectorForPropertySetter (NSString *property); - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid; - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid; +- (SOGoObject *) lookupObjectAtDAVUrl: (NSString *) davURL; + - (NSArray *) davComplianceClassesInContext: (WOContext *) localContext; - (id) davPOSTRequest: (WORequest *) request @@ -166,11 +165,4 @@ SEL SOGoSelectorForPropertySetter (NSString *property); @end -@interface SOGoObject (SOGoDomHelpers) - -- (NSArray *) domNode: (id ) node - getChildNodesByType: (DOMNodeType) type; - -@end - #endif /* __SoObjects_SOGoObject_H__ */ diff --git a/SoObjects/SOGo/SOGoObject.m b/SoObjects/SOGo/SOGoObject.m index 5f19fdbce..c03dd3ec9 100644 --- a/SoObjects/SOGo/SOGoObject.m +++ b/SoObjects/SOGo/SOGoObject.m @@ -33,7 +33,6 @@ #import #import -#import #import #import #import @@ -48,6 +47,8 @@ #import #import #import +#import + #import #import "NSArray+Utilities.h" @@ -55,6 +56,7 @@ #import "NSDictionary+Utilities.h" #import "NSObject+DAV.h" #import "NSObject+Utilities.h" +#import "NSString+DAV.h" #import "NSString+Utilities.h" #import "SOGoCache.h" #import "SOGoDomainDefaults.h" @@ -65,73 +67,11 @@ #import "SOGoUserFolder.h" #import "SOGoWebDAVAclManager.h" #import "SOGoWebDAVValue.h" +#import "WORequest+SOGo.h" +#import "WOResponse+SOGo.h" #import "SOGoObject.h" -static NSDictionary *reportMap = nil; -static NSMutableDictionary *setterMap = nil; -static NSMutableDictionary *getterMap = nil; - -SEL SOGoSelectorForPropertyGetter (NSString *property) -{ - SEL propSel; - NSValue *propPtr; - NSDictionary *map; - NSString *methodName; - - if (!getterMap) - getterMap = [NSMutableDictionary new]; - propPtr = [getterMap objectForKey: property]; - if (propPtr) - propSel = [propPtr pointerValue]; - else - { - map = [SOGoObject defaultWebDAVAttributeMap]; - methodName = [map objectForKey: property]; - if (methodName) - { - propSel = NSSelectorFromString (methodName); - if (propSel) - [getterMap setObject: [NSValue valueWithPointer: propSel] - forKey: property]; - } - else - propSel = NULL; - } - - return propSel; -} - -SEL SOGoSelectorForPropertySetter (NSString *property) -{ - SEL propSel; - NSValue *propPtr; - NSDictionary *map; - NSString *methodName; - - if (!setterMap) - setterMap = [NSMutableDictionary new]; - propPtr = [setterMap objectForKey: property]; - if (propPtr) - propSel = [propPtr pointerValue]; - else - { - map = [SOGoObject defaultWebDAVAttributeMap]; - methodName = [map objectForKey: property]; - if (methodName) - { - propSel = NSSelectorFromString ([methodName davSetterName]); - if (propSel) - [setterMap setObject: [NSValue valueWithPointer: propSel] - forKey: property]; - } - else - propSel = NULL; - } - - return propSel; -} - @implementation SOGoObject + (SOGoWebDAVAclManager *) webdavAclManager @@ -152,15 +92,15 @@ SEL SOGoSelectorForPropertySetter (NSString *property) if (!permissions) { permissions = [NSDictionary dictionaryWithObjectsAndKeys: - davElement (@"read", @"DAV:"), + davElement (@"read", XMLNS_WEBDAV), SoPerm_AccessContentsInformation, - davElement (@"bind", @"DAV:"), + davElement (@"bind", XMLNS_WEBDAV), SoPerm_AddDocumentsImagesAndFiles, - davElement (@"unbind", @"DAV:"), + davElement (@"unbind", XMLNS_WEBDAV), SoPerm_DeleteObjects, - davElement (@"write-acl", @"DAV:"), + davElement (@"write-acl", XMLNS_WEBDAV), SoPerm_ChangePermissions, - davElement (@"write-content", @"DAV:"), + davElement (@"write-content", XMLNS_WEBDAV), SoPerm_ChangeImagesAndFiles, NULL]; [permissions retain]; } @@ -168,36 +108,6 @@ SEL SOGoSelectorForPropertySetter (NSString *property) return permissions; } */ -+ (void) initialize -{ - NSString *filename; - NSBundle *bundle; - - if (!reportMap) - { - bundle = [NSBundle bundleForClass: self]; - filename = [bundle pathForResource: @"DAVReportMap" ofType: @"plist"]; - if (filename - && [[NSFileManager defaultManager] fileExistsAtPath: filename]) - reportMap = [[NSDictionary alloc] initWithContentsOfFile: filename]; - else - [self logWithFormat: @"DAV REPORT map not found!"]; - } -// SoClass security declarations - -// require View permission to access the root (bound to authenticated ...) -// [[self soClassSecurityInfo] declareObjectProtected: SoPerm_View]; - -// to allow public access to all contained objects (subkeys) -// [[self soClassSecurityInfo] setDefaultAccess: @"allow"]; - -// /* require Authenticated role for View and WebDAV */ -// [[self soClassSecurityInfo] declareRole: SoRole_Owner -// asDefaultForPermission: SoPerm_View]; -// [[self soClassSecurityInfo] declareRole: SoRole_Owner -// asDefaultForPermission: SoPerm_WebDAVAccess]; -} - + (NSString *) globallyUniqueObjectId { /* @@ -349,32 +259,13 @@ SEL SOGoSelectorForPropertySetter (NSString *property) return ma; } -- (NSString *) _reportSelector: (NSString *) reportName -{ - NSString *methodName, *objcMethod, *resultName; - SEL reportSel; - - resultName = nil; - - methodName = [reportMap objectForKey: reportName]; - if (methodName) - { - objcMethod = [NSString stringWithFormat: @"%@:", methodName]; - reportSel = NSSelectorFromString (objcMethod); - if ([self respondsToSelector: reportSel]) - resultName = objcMethod; - } - - return resultName; -} - - (id) lookupName: (NSString *) lookupName inContext: (id) localContext acquire: (BOOL) acquire { id obj; SOGoCache *cache; - NSString *objcMethod, *httpMethod; + NSString *httpMethod; cache = [SOGoCache sharedCache]; obj = [cache objectNamed: lookupName inContainer: self]; @@ -382,16 +273,7 @@ SEL SOGoSelectorForPropertySetter (NSString *property) { httpMethod = [[localContext request] method]; if ([httpMethod isEqualToString: @"REPORT"]) - { - objcMethod = [self _reportSelector: lookupName]; - if (objcMethod) - { - obj = [[SoSelectorInvocation alloc] - initWithSelectorNamed: objcMethod - addContextParameter: YES]; - [obj autorelease]; - } - } + obj = [self davReportInvocationForKey: lookupName]; else { obj = [[self soClass] lookupKey: lookupName inContext: localContext]; @@ -458,9 +340,9 @@ SEL SOGoSelectorForPropertySetter (NSString *property) usersUrl = [NSString stringWithFormat: @"%@%@/", [[WOApplication application] davURLAsString], owner]; - ownerHREF = davElementWithContent (@"href", @"DAV:", usersUrl); + ownerHREF = davElementWithContent (@"href", XMLNS_WEBDAV, usersUrl); - return [davElementWithContent (@"owner", @"DAV:", ownerHREF) + return [davElementWithContent (@"owner", XMLNS_WEBDAV, ownerHREF) asWebDAVValue]; } @@ -469,11 +351,11 @@ SEL SOGoSelectorForPropertySetter (NSString *property) NSArray *restrictions; restrictions = [NSArray arrayWithObjects: - davElement (@"grant-only", @"DAV:"), - davElement (@"no-invert", @"DAV:"), + davElement (@"grant-only", XMLNS_WEBDAV), + davElement (@"no-invert", XMLNS_WEBDAV), nil]; - return [davElementWithContent (@"acl-restrictions", @"DAV:", restrictions) + return [davElementWithContent (@"acl-restrictions", XMLNS_WEBDAV, restrictions) asWebDAVValue]; } @@ -481,15 +363,22 @@ SEL SOGoSelectorForPropertySetter (NSString *property) { NSString *usersUrl; NSDictionary *collectionHREF; + NSString *classes; + + if ([[context request] isICal4]) + { + classes = [[self davComplianceClassesInContext: context] + componentsJoinedByString: @", "]; + [[context response] setHeader: classes forKey: @"DAV"]; + } /* WOApplication has no support for the DAV methods we define here so we use the user's principal object as a reference */ - usersUrl = [NSString stringWithFormat: @"%@%@/", - [[WOApplication application] davURLAsString], owner]; - collectionHREF = davElementWithContent (@"href", @"DAV:", usersUrl); + usersUrl = [[WOApplication application] davURLAsString]; + collectionHREF = davElementWithContent (@"href", XMLNS_WEBDAV, usersUrl); return [davElementWithContent (@"principal-collection-set", - @"DAV:", + XMLNS_WEBDAV, [NSArray arrayWithObject: collectionHREF]) asWebDAVValue]; } @@ -505,7 +394,7 @@ SEL SOGoSelectorForPropertySetter (NSString *property) privileges = [[webdavAclManager davPermissionsForRoles: roles onObject: self] objectEnumerator]; while ((privilege = [privileges nextObject])) - [davPrivileges addObject: davElementWithContent (@"privilege", @"DAV:", + [davPrivileges addObject: davElementWithContent (@"privilege", XMLNS_WEBDAV, privilege)]; return davPrivileges; @@ -518,7 +407,7 @@ SEL SOGoSelectorForPropertySetter (NSString *property) userRoles = [[context activeUser] rolesForObject: self inContext: context]; return [davElementWithContent (@"current-user-privilege-set", - @"DAV:", + XMLNS_WEBDAV, [self _davPrivilegesFromRoles: userRoles]) asWebDAVValue]; } @@ -526,7 +415,7 @@ SEL SOGoSelectorForPropertySetter (NSString *property) - (SOGoWebDAVValue *) davSupportedPrivilegeSet { return [davElementWithContent (@"supported-privilege-set", - @"DAV:", + XMLNS_WEBDAV, [webdavAclManager treeAsWebDAVValue]) asWebDAVValue]; } @@ -549,67 +438,85 @@ SEL SOGoSelectorForPropertySetter (NSString *property) principalURL = [NSString stringWithFormat: @"%@%@/", [[WOApplication application] davURLAsString], currentUID]; - userHREF = davElementWithContent (@"href", @"DAV:", principalURL); - [currentAce addObject: davElementWithContent (@"principal", @"DAV:", + userHREF = davElementWithContent (@"href", XMLNS_WEBDAV, principalURL); + [currentAce addObject: davElementWithContent (@"principal", XMLNS_WEBDAV, userHREF)]; currentGrant - = davElementWithContent (@"grant", @"DAV:", + = davElementWithContent (@"grant", XMLNS_WEBDAV, [self _davPrivilegesFromRoles: roles]); [currentAce addObject: currentGrant]; - [aces addObject: davElementWithContent (@"ace", @"DAV:", currentAce)]; + [aces addObject: davElementWithContent (@"ace", XMLNS_WEBDAV, currentAce)]; } } - (void) _fillAcesWithRolesForPseudoPrincipals: (NSMutableArray *) aces { NSArray *roles, *currentAce; - NSDictionary *principal, *currentGrant; + NSDictionary *principal, *currentGrant, *principalHREF, *inherited; + NSString *principalURL; SOGoUser *user; // DAV:self, DAV:property(owner), DAV:authenticated user = [context activeUser]; + + principalURL = [NSString stringWithFormat: @"%@%@/", + [[WOApplication application] davURLAsString], + [self ownerInContext: nil]]; + principalHREF = davElementWithContent (@"href", XMLNS_WEBDAV, principalURL); + inherited = davElementWithContent (@"inherited", XMLNS_WEBDAV, + principalHREF), + roles = [user rolesForObject: self inContext: context]; if ([roles count]) { - principal = davElement (@"self", @"DAV:"); + principal = davElement (@"self", XMLNS_WEBDAV); currentGrant - = davElementWithContent (@"grant", @"DAV:", + = davElementWithContent (@"grant", XMLNS_WEBDAV, [self _davPrivilegesFromRoles: roles]); - currentAce = [NSArray arrayWithObjects: - davElementWithContent (@"principal", @"DAV:", - principal), - currentGrant, nil]; - [aces addObject: davElementWithContent (@"ace", @"DAV:", currentAce)]; + currentAce = [NSArray + arrayWithObjects: davElementWithContent (@"principal", + XMLNS_WEBDAV, + principal), + currentGrant, + inherited, + nil]; + [aces addObject: davElementWithContent (@"ace", XMLNS_WEBDAV, currentAce)]; } user = [SOGoUser userWithLogin: [self ownerInContext: context] roles: nil]; roles = [user rolesForObject: self inContext: context]; if ([roles count]) { - principal = davElementWithContent (@"property", @"DAV:", - davElement (@"owner", @"DAV:")); + principal = davElementWithContent (@"property", XMLNS_WEBDAV, + davElement (@"owner", XMLNS_WEBDAV)); currentGrant - = davElementWithContent (@"grant", @"DAV:", + = davElementWithContent (@"grant", XMLNS_WEBDAV, [self _davPrivilegesFromRoles: roles]); - currentAce = [NSArray arrayWithObjects: - davElementWithContent (@"principal", @"DAV:", - principal), - currentGrant, nil]; - [aces addObject: davElementWithContent (@"ace", @"DAV:", currentAce)]; + currentAce = [NSArray + arrayWithObjects: davElementWithContent (@"principal", + XMLNS_WEBDAV, + principal), + currentGrant, + inherited, + nil]; + [aces addObject: davElementWithContent (@"ace", XMLNS_WEBDAV, currentAce)]; } roles = [self aclsForUser: [self defaultUserID]]; if ([roles count]) { - principal = davElement (@"authenticated", @"DAV:"); + principal = davElement (@"authenticated", XMLNS_WEBDAV); currentGrant - = davElementWithContent (@"grant", @"DAV:", + = davElementWithContent (@"grant", XMLNS_WEBDAV, [self _davPrivilegesFromRoles: roles]); - currentAce = [NSArray arrayWithObjects: - davElementWithContent (@"principal", @"DAV:", - principal), - currentGrant, nil]; - [aces addObject: davElementWithContent (@"ace", @"DAV:", currentAce)]; + currentAce = [NSArray + arrayWithObjects: davElementWithContent (@"principal", + XMLNS_WEBDAV, + principal), + currentGrant, + inherited, + nil]; + [aces addObject: davElementWithContent (@"ace", XMLNS_WEBDAV, currentAce)]; } } @@ -626,133 +533,10 @@ SEL SOGoSelectorForPropertySetter (NSString *property) while ((currentUID = [uids nextObject])) [self _fillAces: aces withRolesForUID: currentUID]; - return [davElementWithContent (@"acl", @"DAV:", aces) + return [davElementWithContent (@"acl", XMLNS_WEBDAV, aces) asWebDAVValue]; } -#warning all REPORT method should be standardized... -- (NSDictionary *) _formalizePrincipalMatchResponse: (NSArray *) hrefs -{ - NSDictionary *multiStatus; - NSEnumerator *hrefList; - NSString *currentHref; - NSMutableArray *responses; - NSArray *responseElements; - - responses = [NSMutableArray array]; - - hrefList = [hrefs objectEnumerator]; - while ((currentHref = [hrefList nextObject])) - { - responseElements - = [NSArray arrayWithObjects: davElementWithContent (@"href", @"DAV:", - currentHref), - davElementWithContent (@"status", @"DAV:", - @"HTTP/1.1 200 OK"), - nil]; - [responses addObject: davElementWithContent (@"response", @"DAV:", - responseElements)]; - } - - multiStatus = davElementWithContent (@"multistatus", @"DAV:", responses); - - return multiStatus; -} - -- (NSDictionary *) _handlePrincipalMatchSelf -{ - NSString *davURL, *userLogin; - NSArray *principalURL; - - davURL = [[WOApplication application] davURLAsString]; - userLogin = [[context activeUser] login]; - principalURL - = [NSArray arrayWithObject: [NSString stringWithFormat: @"%@%@/", davURL, - userLogin]]; - return [self _formalizePrincipalMatchResponse: principalURL]; -} - -- (void) _fillArrayWithPrincipalsOwnedBySelf: (NSMutableArray *) hrefs -{ - NSArray *roles; - - roles = [[context activeUser] rolesForObject: self inContext: context]; - if ([roles containsObject: SoRole_Owner]) - [hrefs addObject: [self davURLAsString]]; -} - -- (NSDictionary *) - _handlePrincipalMatchPrincipalProperty: (id ) child -{ - NSMutableArray *hrefs; - NSDictionary *response; - - hrefs = [NSMutableArray array]; - [self _fillArrayWithPrincipalsOwnedBySelf: hrefs]; - - response = [self _formalizePrincipalMatchResponse: hrefs]; - - return response; -} - -- (NSDictionary *) _handlePrincipalMatchReport: (id ) document -{ - NSDictionary *response; - id documentElement, queryChild; - NSArray *children; - NSString *queryTag; - - documentElement = [document documentElement]; - children = [self domNode: documentElement - getChildNodesByType: DOM_ELEMENT_NODE]; - if ([children count] == 1) - { - queryChild = [children objectAtIndex: 0]; - queryTag = [queryChild tagName]; - if ([queryTag isEqualToString: @"self"]) - response = [self _handlePrincipalMatchSelf]; - else if ([queryTag isEqualToString: @"principal-property"]) - response = [self _handlePrincipalMatchPrincipalProperty: queryChild]; - else - response = [NSException exceptionWithHTTPStatus: 400 - reason: @"Query element must be either " - @" '{DAV:}principal-property' or '{DAV:}self'"]; - } - else - response = [NSException exceptionWithHTTPStatus: 400 - reason: @"Query must have one element:" - @" '{DAV:}principal-property' or '{DAV:}self'"]; - - return response; -} - -- (WOResponse *) davPrincipalMatch: (WOContext *) localContext -{ - WOResponse *r; - id document; - NSDictionary *xmlResponse; - - r = [context response]; - - document = [[context request] contentAsDOMDocument]; - xmlResponse = [self _handlePrincipalMatchReport: document]; - if ([xmlResponse isKindOfClass: [NSException class]]) - r = (WOResponse *) xmlResponse; - else - { - [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: [xmlResponse asWebDavStringWithNamespaces: nil]]; - } - - return r; -} - /* actions */ - (id) DELETEAction: (id) _ctx @@ -1073,6 +857,16 @@ SEL SOGoSelectorForPropertySetter (NSString *property) [self subclassResponsibility: _cmd]; } +- (void) setRoles: (NSArray *) roles + forUsers: (NSArray *) users +{ + int count, max; + + max = [users count]; + for (count = 0; count < max; count++) + [self setRoles: roles forUser: [users objectAtIndex: count]]; +} + - (void) removeAclsForUsers: (NSArray *) users { [self subclassResponsibility: _cmd]; @@ -1168,13 +962,8 @@ SEL SOGoSelectorForPropertySetter (NSString *property) - (NSString *) davURLAsString { - NSURL *davURL; - SOGoSystemDefaults *sd; - - davURL = [self davURL]; - sd = [SOGoSystemDefaults sharedSystemDefaults]; - - return ([sd useRelativeURLs] ? [davURL path] : [davURL absoluteString]); + return [[container davURLAsString] + stringByAppendingFormat: @"%@", nameInContainer]; } - (NSURL *) soURL @@ -1315,14 +1104,53 @@ SEL SOGoSelectorForPropertySetter (NSString *property) [self nameInContainer]]; } +- (SOGoObject *) lookupObjectAtDAVUrl: (NSString *) davURL +{ + NSString *appName, *baseSearchURL, *subString, *objectName; + NSRange searchRange; + NSArray *sogoObjects; + SOGoObject *currentObject, *resultObject; + int count, max; + + resultObject = nil; + + appName = [[context request] applicationName]; + baseSearchURL = [NSString stringWithFormat: @"%@/dav/", appName]; + searchRange = [davURL rangeOfString: baseSearchURL]; + if (searchRange.location != NSNotFound) + { + subString = [davURL substringFromIndex: NSMaxRange (searchRange)]; + currentObject = (SOGoObject *) [WOApplication application]; + sogoObjects = [subString componentsSeparatedByString: @"/"]; + max = [sogoObjects count]; + for (count = 0; currentObject && count < max; count++) + { + objectName = [sogoObjects objectAtIndex: count]; + if ([objectName length]) + currentObject = [currentObject lookupName: objectName + inContext: context + acquire: NO]; + } + resultObject = currentObject; + } + + return resultObject; +} + - (NSArray *) davComplianceClassesInContext: (WOContext *) localContext { - NSMutableArray *newClasses; + static NSMutableArray *newClasses = nil; + NSArray *selfClasses; - newClasses - = [NSMutableArray arrayWithArray: - [super davComplianceClassesInContext: localContext]]; - [newClasses addObject: @"access-control"]; + if (!newClasses) + { + newClasses + = [[super davComplianceClassesInContext: localContext] mutableCopy]; + selfClasses = [NSArray arrayWithObjects: @"access-control", + @"calendar-access", @"calendar-schedule", + @"calendar-proxy", nil]; + [newClasses addObjectsFromArray: selfClasses]; + } return newClasses; } @@ -1503,7 +1331,7 @@ SEL SOGoSelectorForPropertySetter (NSString *property) for (i = 0; i < [allUsers count]; i++) { [self setRoles: allRoles - forUser: [allUsers objectAtIndex: i]]; + forUser: [allUsers objectAtIndex: i]]; } result = @""; } @@ -1656,50 +1484,4 @@ SEL SOGoSelectorForPropertySetter (NSString *property) return exception; } -- (SOGoWebDAVValue *) davSupportedReportSet -{ - NSDictionary *currentValue; - NSEnumerator *reportKeys; - NSMutableArray *reportSet; - NSString *currentKey; - - reportSet = [NSMutableArray array]; - - reportKeys = [[reportMap allKeys] objectEnumerator]; - while ((currentKey = [reportKeys nextObject])) - if ([self _reportSelector: currentKey]) - { - currentValue = [currentKey asDavInvocation]; - [reportSet addObject: davElementWithContent(@"report", - @"DAV:", - currentValue)]; - } - - return [davElementWithContent (@"supported-report-set", @"DAV:", reportSet) - asWebDAVValue]; -} - @end /* SOGoObject */ - -@implementation SOGoObject (SOGoDomHelpers) - -- (NSArray *) domNode: (id ) node - getChildNodesByType: (DOMNodeType ) type -{ - NSMutableArray *nodes; - id currentChild; - - nodes = [NSMutableArray array]; - - currentChild = [node firstChild]; - while (currentChild) - { - if ([currentChild nodeType] == type) - [nodes addObject: currentChild]; - currentChild = [currentChild nextSibling]; - } - - return nodes; -} - -@end diff --git a/SoObjects/SOGo/SOGoParentFolder.m b/SoObjects/SOGo/SOGoParentFolder.m index 258b76177..4b0d93d12 100644 --- a/SoObjects/SOGo/SOGoParentFolder.m +++ b/SoObjects/SOGo/SOGoParentFolder.m @@ -35,6 +35,7 @@ #import #import #import +#import #import #import "NSObject+DAV.h" @@ -265,6 +266,7 @@ static SoSecurityManager *sm = nil; - (NSException *) appendSubscribedSources { NSArray *subscribedReferences; + SOGoUser *ownerUser; SOGoUserSettings *settings; NSEnumerator *allKeys; NSString *currentKey; @@ -272,7 +274,8 @@ static SoSecurityManager *sm = nil; error = nil; /* we ignore non-DB errors at this time... */ - settings = [[context activeUser] userSettings]; + ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context]]; + settings = [ownerUser userSettings]; subscribedReferences = [[settings objectForKey: nameInContainer] objectForKey: @"SubscribedFolders"]; if ([subscribedReferences isKindOfClass: [NSArray class]]) @@ -357,16 +360,17 @@ static SoSecurityManager *sm = nil; - (NSException *) initSubscribedSubFolders { - NSString *login; NSException *error; + SOGoUser *currentUser; if (!subFolderClass) subFolderClass = [[self class] subFolderClass]; error = nil; /* we ignore non-DB errors at this time... */ - login = [[context activeUser] login]; - - if (!subscribedSubFolders && [login isEqualToString: owner]) + currentUser = [context activeUser]; + if (!subscribedSubFolders + && ([[currentUser login] isEqualToString: owner] + || [currentUser isSuperUser])) { subscribedSubFolders = [NSMutableDictionary new]; error = [self appendSubscribedSources]; diff --git a/SoObjects/SOGo/SOGoPermissions.h b/SoObjects/SOGo/SOGoPermissions.h index 6c0a72483..6b1a4990a 100644 --- a/SoObjects/SOGo/SOGoPermissions.h +++ b/SoObjects/SOGo/SOGoPermissions.h @@ -38,8 +38,6 @@ extern NSString *SOGoRole_FolderViewer; extern NSString *SOGoRole_AuthorizedSubscriber; extern NSString *SOGoRole_None; -extern NSString *SOGoRole_FreeBusy; -extern NSString *SOGoRole_FreeBusyLookup; extern NSString *SOGoMailRole_SeenKeeper; extern NSString *SOGoMailRole_Writer; @@ -51,6 +49,8 @@ extern NSString *SOGoMailRole_Administrator; extern NSString *SOGoCalendarRole_Organizer; extern NSString *SOGoCalendarRole_Participant; +extern NSString *SOGoCalendarRole_FreeBusyReader; + extern NSString *SOGoCalendarRole_PublicViewer; extern NSString *SOGoCalendarRole_PublicDAndTViewer; extern NSString *SOGoCalendarRole_PublicModifier; @@ -74,6 +74,8 @@ extern NSString *SOGoPerm_DeleteObject; extern NSString *SOGoPerm_ReadAcls; extern NSString *SOGoPerm_FreeBusyLookup; +extern NSString *SOGoCalendarPerm_ReadFreeBusy; + extern NSString *SOGoCalendarPerm_ViewWholePublicRecords; extern NSString *SOGoCalendarPerm_ViewDAndTOfPublicRecords; extern NSString *SOGoCalendarPerm_ModifyPublicRecords; diff --git a/SoObjects/SOGo/SOGoPermissions.m b/SoObjects/SOGo/SOGoPermissions.m index ada69d997..b6e0bb4f8 100644 --- a/SoObjects/SOGo/SOGoPermissions.m +++ b/SoObjects/SOGo/SOGoPermissions.m @@ -34,18 +34,13 @@ NSString *SOGoRole_FolderEraser = @"FolderEraser"; NSString *SOGoRole_AuthorizedSubscriber = @"AuthorizedSubscriber"; NSString *SOGoRole_None = @"None"; -NSString *SOGoRole_FreeBusy = @"FreeBusy"; /* for the "freebusy" special user - */ -NSString *SOGoRole_FreeBusyLookup = @"FreeBusyLookup"; /* for users that have - access to the - freebusy information - from a specific - calendar */ - /* Calendar */ NSString *SOGoCalendarRole_Organizer = @"Organizer"; NSString *SOGoCalendarRole_Participant = @"Participant"; +/* a special role that is given to all subscribed users */ +NSString *SOGoCalendarRole_FreeBusyReader = @"FreeBusyReader"; + NSString *SOGoCalendarRole_PublicViewer = @"PublicViewer"; NSString *SOGoCalendarRole_PublicDAndTViewer = @"PublicDAndTViewer"; NSString *SOGoCalendarRole_PublicModifier = @"PublicModifier"; @@ -73,10 +68,13 @@ NSString *SOGoMailRole_Administrator = @"MailAdministrator"; /* permissions */ NSString *SOGoPerm_AccessObject= @"Access Object"; NSString *SOGoPerm_DeleteObject= @"Delete Object"; -NSString *SOGoPerm_ReadAcls = @"ReadAcls"; /* the equivalent of "read-acl" in - the WebDAV acls spec, which is - currently missing from SOPE */ -NSString *SOGoPerm_FreeBusyLookup = @"FreeBusyLookup"; + +/* the equivalent of "read-acl" in the WebDAV acls spec, which is currently + missing from SOPE */ + +NSString *SOGoPerm_ReadAcls = @"ReadAcls"; + +NSString *SOGoCalendarPerm_ReadFreeBusy = @"Read FreeBusy"; NSString *SOGoCalendarPerm_ViewWholePublicRecords = @"ViewWholePublicRecords"; NSString *SOGoCalendarPerm_ViewDAndTOfPublicRecords = @"ViewDAndTOfPublicRecords"; diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 0ce6f1fda..bfea17b87 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -340,53 +340,6 @@ return _domainDefaults; } -// - (SOGoUserDefaults *) userDefaults -// { -// if (!_defaults) -// { -// [SOGoUserDefaults defaultsForUser: login -// inDomain: [self domainName]] -// if (!SOGoUserProfileKlass) -// SOGoUserProfileKlass -// = NSClassFromString ([self userProfileClassName]); -// _defaults = [SOGoUserProfileKlass -// userProfileWithType: SOGoUserProfileTypeDefaults -// forUID: login]; -// if (_defaults) -// { -// [_defaults retain]; -// [_defaults fetchProfile]; -// if ([_defaults values]) -// { -// BOOL b; -// b = NO; - -// if (![[_defaults stringForKey: @"MessageCheck"] length]) -// { -// [_defaults setObject: defaultMessageCheck forKey: @"MessageCheck"]; -// b = YES; -// } -// if (![[_defaults stringForKey: @"TimeZone"] length]) -// { -// [_defaults setObject: [serverTimeZone name] forKey: @"TimeZone"]; -// b = YES; -// } - -// if (b) -// [_defaults synchronize]; - - -// // See explanation in -language -// [self invalidateLanguage]; -// } -// } -// } -// //else -// // NSLog(@"User defaults cache hit for %@", login); - -// return _defaults; -// } - - (SOGoUserSettings *) userSettings { if (!_settings) @@ -398,28 +351,6 @@ return _settings; } -// - (void) invalidateLanguage -// { -// DESTROY(language); -// } - -// - (NSString *) language -// { -// if (![language length]) -// { -// language = [[self userDefaults] stringForKey: @"Language"]; -// // This is a workaround until we handle the connection errors to the db -// // in a better way. It enables us to avoid retrieving the userDefaults -// // too many times when the DB is down, causing a huge delay. -// if (![language length]) -// language = [SOGoUser language]; - -// [language retain]; -// } - -// return language; -// } - - (NSCalendarDate *) firstDayOfWeekForDate: (NSCalendarDate *) date { int offset; @@ -650,9 +581,8 @@ - (SOGoUserFolder *) homeFolderInContext: (id) context { - return [[WOApplication application] lookupName: [self login] - inContext: context - acquire: NO]; + return [SOGoUserFolder objectWithName: login + inContainer: [WOApplication application]]; } - (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context diff --git a/SoObjects/SOGo/SOGoUserFolder.h b/SoObjects/SOGo/SOGoUserFolder.h index 2de151a56..f02379643 100644 --- a/SoObjects/SOGo/SOGoUserFolder.h +++ b/SoObjects/SOGo/SOGoUserFolder.h @@ -58,6 +58,8 @@ - (id) mailAccountsFolder: (NSString *) _key inContext: (WOContext *) _ctx; +- (NSArray *) davPrincipalURL; + @end #endif /* __SOGo_SOGoUserFolder_H__ */ diff --git a/SoObjects/SOGo/SOGoUserFolder.m b/SoObjects/SOGo/SOGoUserFolder.m index b3427772e..1ce18856c 100644 --- a/SoObjects/SOGo/SOGoUserFolder.m +++ b/SoObjects/SOGo/SOGoUserFolder.m @@ -57,6 +57,7 @@ #import "SOGoSystemDefaults.h" #import "SOGoUser.h" #import "WORequest+SOGo.h" +#import "WOResponse+SOGo.h" #import "SOGoUserFolder.h" @@ -366,12 +367,7 @@ 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 prepareDAVResponse]; [r appendContentString: @""]; @@ -473,10 +469,7 @@ NSString *content; r = [queryContext response]; - [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 prepareDAVResponse]; document = [[context request] contentAsDOMDocument]; content = [self _davUsersFromQuery: document]; @@ -484,10 +477,7 @@ { [r setStatus: 207]; if ([content length]) - { - [r appendContentString: @""]; - [r appendContentString: content]; - } + [r appendContentString: content]; } else [r setStatus: 400]; diff --git a/SoObjects/SOGo/SOGoUserSettings.h b/SoObjects/SOGo/SOGoUserSettings.h index 2778c58bd..b250c8118 100644 --- a/SoObjects/SOGo/SOGoUserSettings.h +++ b/SoObjects/SOGo/SOGoUserSettings.h @@ -23,14 +23,29 @@ #ifndef SOGOUSERSETTINGS_H #define SOGOUSERSETTINGS_H -@class NSString; - #import "SOGoDefaultsSource.h" +@class NSArray; +@class NSString; + @interface SOGoUserSettings : SOGoDefaultsSource + (SOGoUserSettings *) settingsForUser: (NSString *) userId; +/* the calendars that we publish to our proxy subscribers */ +- (void) setProxiedCalendars: (NSArray *) proxiedCalendars; +- (NSArray *) proxiedCalendars; + +/* the users that we have subscribed us as a proxy to our calendars */ +- (void) setCalendarProxyUsers: (NSArray *) proxyUsers + withWriteAccess: (BOOL) writeAccess; +- (NSArray *) calendarProxyUsersWithWriteAccess: (BOOL) writeAccess; + +/* the users that have subscribed us as a proxy to their calendars */ +- (void) setCalendarProxySubscriptionUsers: (NSArray *) subscriptionUsers + withWriteAccess: (BOOL) writeAccess; +- (NSArray *) calendarProxySubscriptionUsersWithWriteAccess: (BOOL) writeAccess; + @end #endif /* SOGOUSERSETTINGS_H */ diff --git a/SoObjects/SOGo/SOGoUserSettings.m b/SoObjects/SOGo/SOGoUserSettings.m index af1bc5a51..916df8326 100644 --- a/SoObjects/SOGo/SOGoUserSettings.m +++ b/SoObjects/SOGo/SOGoUserSettings.m @@ -20,6 +20,7 @@ * Boston, MA 02111-1307, USA. */ +#import #import #import "SOGoUserProfile.h" @@ -54,4 +55,65 @@ static Class SOGoUserProfileKlass = Nil; return ud; } +/* the calendars that we publish to our proxy subscribers */ +- (void) setProxiedCalendars: (NSArray *) proxiedCalendars +{ + [self setObject: proxiedCalendars forKey: @"ProxiedCalendars"]; +} + +- (NSArray *) proxiedCalendars +{ + NSArray *proxiedCalendars; + + proxiedCalendars = [self arrayForKey: @"ProxiedCalendars"]; + if (!proxiedCalendars) + proxiedCalendars = [NSArray arrayWithObject: @"personal"]; + + return proxiedCalendars; +} + +/* the users that we have subscribed us as a proxy to our calendars */ +- (void) setCalendarProxyUsers: (NSArray *) proxyUsers + withWriteAccess: (BOOL) writeAccess +{ + NSString *key; + + key = [NSString stringWithFormat: @"CalendarProxy%@Users", + (writeAccess ? @"Read" : @"Write")]; + + [self setObject: proxyUsers forKey: key]; +} + +- (NSArray *) calendarProxyUsersWithWriteAccess: (BOOL) writeAccess +{ + NSString *key; + + key = [NSString stringWithFormat: @"CalendarProxy%@Users", + (writeAccess ? @"Read" : @"Write")]; + + return [self arrayForKey: key]; +} + +/* the users that have subscribed us as a proxy to their calendars */ +- (void) setCalendarProxySubscriptionUsers: (NSArray *) subscriptionUsers + withWriteAccess: (BOOL) writeAccess +{ + NSString *key; + + key = [NSString stringWithFormat: @"CalendarProxy%@SubscriptionUsers", + (writeAccess ? @"Read" : @"Write")]; + + [self setObject: subscriptionUsers forKey: key]; +} + +- (NSArray *) calendarProxySubscriptionUsersWithWriteAccess: (BOOL) writeAccess +{ + NSString *key; + + key = [NSString stringWithFormat: @"CalendarProxy%@SubscriptionUsers", + (writeAccess ? @"Read" : @"Write")]; + + return [self arrayForKey: key]; +} + @end diff --git a/SoObjects/SOGo/SOGoWebDAVAclManager.m b/SoObjects/SOGo/SOGoWebDAVAclManager.m index 446157430..1c410512a 100644 --- a/SoObjects/SOGo/SOGoWebDAVAclManager.m +++ b/SoObjects/SOGo/SOGoWebDAVAclManager.m @@ -116,18 +116,20 @@ static NSNumber *yesObject = nil; identifier = [parentPermission keysWithFormat: @"{%{ns}}%{method}"]; parentEntry = [aclTree objectForKey: identifier]; - if (!parentEntry) - [self warnWithFormat: @"parent entry '%@' does not exist in DAV" - @" permissions table", identifier]; - children = [parentEntry objectForKey: @"children"]; - if (!children) + if (parentEntry) { - children = [NSMutableArray new]; - [parentEntry setObject: children forKey: @"children"]; - [children release]; + children = [parentEntry objectForKey: @"children"]; + if (!children) + { + children = [NSMutableArray array]; + [parentEntry setObject: children forKey: @"children"]; + } + [children addObject: newEntry]; + [newEntry setObject: parentEntry forKey: @"parent"]; } - [children addObject: newEntry]; - [newEntry setObject: parentEntry forKey: @"parent"]; + else + [self errorWithFormat: @"parent entry '%@' does not exist in DAV" + @" permissions table", identifier]; } - (void) registerDAVPermission: (NSDictionary *) davPermission diff --git a/SoObjects/SOGo/WOResponse+SOGo.h b/SoObjects/SOGo/WOResponse+SOGo.h index cc9c56a9e..6ae690dc3 100644 --- a/SoObjects/SOGo/WOResponse+SOGo.h +++ b/SoObjects/SOGo/WOResponse+SOGo.h @@ -29,6 +29,8 @@ @interface WOResponse (SOGoSOPEUtilities) +- (void) prepareDAVResponse; + - (void) appendDAVError: (NSDictionary *) errorCondition; @end diff --git a/SoObjects/SOGo/WOResponse+SOGo.m b/SoObjects/SOGo/WOResponse+SOGo.m index 737577769..b9768a29d 100644 --- a/SoObjects/SOGo/WOResponse+SOGo.m +++ b/SoObjects/SOGo/WOResponse+SOGo.m @@ -31,6 +31,16 @@ @implementation WOResponse (SOGoSOPEUtilities) +- (void) prepareDAVResponse +{ + [self setStatus: 207]; + [self setContentEncoding: NSUTF8StringEncoding]; + [self setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"]; + [self setHeader: @"no-cache" forKey: @"pragma"]; + [self setHeader: @"no-cache" forKey: @"cache-control"]; + [self appendContentString:@"\r\n"]; +} + - (void) appendDAVError: (NSDictionary *) errorCondition { NSDictionary *error; @@ -38,8 +48,6 @@ error = davElementWithContent (@"error", XMLNS_WEBDAV, errorCondition); [self setStatus: 403]; - [self appendContentString: @"\r\n"]; [self appendContentString: [error asWebDavStringWithNamespaces: nil]]; } diff --git a/Tests/test-ical.py b/Tests/test-ical.py new file mode 100755 index 000000000..29401f06b --- /dev/null +++ b/Tests/test-ical.py @@ -0,0 +1,124 @@ +#!/usr/bin/python + +from config import hostname, port, username, password, subscriber_username + +import unittest +import webdavlib + +class iCalTest(unittest.TestCase): + def testPrincipalCollectionSet(self): + """principal-collection-set: 'DAV' header must be returned with iCal 4""" + client = webdavlib.WebDAVClient(hostname, port, username, password) + resource = '/SOGo/dav/%s/' % username + + # NOT iCal4 + propfind = webdavlib.WebDAVPROPFIND(resource, + ["{DAV:}principal-collection-set"], + 0) + propfind.xpath_namespace = { "D": "DAV:" } + client.execute(propfind) + self.assertEquals(propfind.response["status"], 207) + headers = propfind.response["headers"] + self.assertFalse(headers.has_key("dav"), + "DAV header must not be returned when user-agent is NOT iCal 4") + + # iCal4 + propfind = webdavlib.WebDAVPROPFIND(resource, + ["{DAV:}principal-collection-set"], + 0) + client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)" + propfind.xpath_namespace = { "D": "DAV:" } + client.execute(propfind) + self.assertEquals(propfind.response["status"], 207) + headers = propfind.response["headers"] + self.assertTrue(headers.has_key("dav"), + "DAV header must be returned when user-agent is iCal 4") + + expectedDAVClasses = ["1", "2", "access-control", "calendar-access", + "calendar-schedule", "calendar-proxy"] + davClasses = [x.strip() for x in headers["dav"].split(",")] + for davClass in expectedDAVClasses: + self.assertTrue(davClass in davClasses, + "DAV class '%s' not found" % davClass) + + def _setMemberSet(self, owner, members, perm): + resource = '/SOGo/dav/%s/calendar-proxy-%s/' % (owner, perm) + membersHref = [ { "{DAV:}href": '/SOGo/dav/%s/' % x } + for x in members ] + props = { "{DAV:}group-member-set": membersHref } + proppatch = webdavlib.WebDAVPROPPATCH(resource, props) + client = webdavlib.WebDAVClient(hostname, port, username, password) + client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)" + proppatch.xpath_namespace = { "D": "DAV:" } + client.execute(proppatch) + self.assertEquals(proppatch.response["status"], 207, + "failure (%s) setting '%s' permission for '%s' on %s's calendars" + % (proppatch.response["status"], perm, + "', '".join(members), owner)) + + def _getMembership(self, user): + resource = '/SOGo/dav/%s/' % user + propfind = webdavlib.WebDAVPROPFIND(resource, + ["{DAV:}group-membership"], 0) + client = webdavlib.WebDAVClient(hostname, port, username, password) + client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)" + propfind.xpath_namespace = { "D": "DAV:" } + client.execute(propfind) + + hrefs = propfind.xpath_evaluate("/D:multistatus/D:response/D:propstat/D:prop/D:group-membership/D:href") + members = [x.childNodes[0].nodeValue for x in hrefs] + + return members + + def _getProxyFor(self, user, perm): + resource = '/SOGo/dav/%s/' % user + prop = "{http://calendarserver.org/ns/}calendar-proxy-%s-for" % perm + propfind = webdavlib.WebDAVPROPFIND(resource, [prop], 0) + client = webdavlib.WebDAVClient(hostname, port, username, password) + client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)" + propfind.xpath_namespace = { "D": "DAV:", "n1": "http://calendarserver.org/ns/" } + client.execute(propfind) + + hrefs = propfind.xpath_evaluate("/D:multistatus/D:response/D:propstat/D:prop/n1:calendar-proxy-%s-for/D:href" + % perm) + members = [x.childNodes[0].nodeValue[len("/SOGo/dav/"):-1] for x in hrefs] + + return members + + def testCalendarProxy(self): + self._setMemberSet(username, [], "read") + self._setMemberSet(username, [], "write") + self._setMemberSet(subscriber_username, [], "read") + self._setMemberSet(subscriber_username, [], "write") + self.assertEquals([], self._getMembership(username), + "'%s' must have no membership" + % username) + self.assertEquals([], self._getMembership(subscriber_username), + "'%s' must have no membership" + % subscriber_username) + self.assertEquals([], self._getProxyFor(username, "read"), + "'%s' must not be a proxy for anyone" % username) + self.assertEquals([], self._getProxyFor(username, "write"), + "'%s' must not be a proxy for anyone" % username) + self.assertEquals([], self._getProxyFor(subscriber_username, "read"), + "'%s' must not be a proxy for anyone" % subscriber_username) + self.assertEquals([], self._getProxyFor(subscriber_username, "write"), + "'%s' must not be a proxy for anyone" % subscriber_username) + + for perm in ("read", "write"): + for users in ((username, subscriber_username), + (subscriber_username, username)): + self._setMemberSet(users[0], [users[1]], perm) + membership = self._getMembership(users[1]) + self.assertEquals(['/SOGo/dav/%s/calendar-proxy-%s/' + % (users[0], perm)], + membership, + "'%s' must have %s access to %s's calendars" + % (users[1], perm, users[0])) + proxyFor = self._getProxyFor(users[1], perm) + self.assertEquals([users[0]], proxyFor, + "'%s' expected to be %s proxy for %s: %s" + % (users[1], perm, users[0], proxyFor)) + +if __name__ == "__main__": + unittest.main() diff --git a/Tests/test-webdav.py b/Tests/test-webdav.py index 14e45cf70..19340f976 100755 --- a/Tests/test-webdav.py +++ b/Tests/test-webdav.py @@ -5,6 +5,24 @@ from config import hostname, port, username, password import unittest import webdavlib +def fetchUserInfo(login): + client = webdavlib.WebDAVClient(hostname, port, username, password) + resource = "/SOGo/dav/%s/" % login + propfind = webdavlib.WebDAVPROPFIND(resource, + ["displayname", + "{urn:ietf:params:xml:ns:caldav}calendar-user-address-set"], + 0) + propfind.xpath_namespace = { "D": "DAV:", + "C": "urn:ietf:params:xml:ns:caldav" } + client.execute(propfind) + assert(propfind.response["status"] == 207) + name_nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/D:displayname', + None) + email_nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/C:calendar-user-address-set/D:href', + None) + + return (name_nodes[0].childNodes[0].nodeValue, email_nodes[0].childNodes[0].nodeValue) + class WebDAVTest(unittest.TestCase): def testPrincipalCollectionSet(self): """property: 'principal-collection-set' on collection object""" @@ -15,18 +33,18 @@ class WebDAVTest(unittest.TestCase): 0) propfind.xpath_namespace = { "D": "DAV:" } client.execute(propfind) - assert(propfind.response["status"] == 207) + self.assertEquals(propfind.response["status"], 207) nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/D:principal-collection-set/D:href', None) responseHref = nodes[0].childNodes[0].nodeValue if responseHref[0:4] == "http": - self.assertEquals("http://%s%s" % (hostname, resource), responseHref, - "{DAV:}principal-collection-set returned %s instead of '%s'" + self.assertEquals("http://%s/SOGo/dav/" % hostname, responseHref, + "{DAV:}principal-collection-set returned %s instead of 'http../SOGo/dav/'" % ( responseHref, resource )) else: - self.assertEquals(resource, responseHref, - "{DAV:}principal-collection-set returned %s instead of '%s'" - % ( responseHref, resource )) + self.assertEquals("/SOGo/dav/", responseHref, + "{DAV:}principal-collection-set returned %s instead of '/SOGo/dav/'" + % responseHref) def testPrincipalCollectionSet2(self): """property: 'principal-collection-set' on non-collection object""" @@ -37,11 +55,11 @@ class WebDAVTest(unittest.TestCase): 0) propfind.xpath_namespace = { "D": "DAV:" } client.execute(propfind) - assert(propfind.response["status"] == 207) + self.assertEquals(propfind.response["status"], 207) nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/D:principal-collection-set/D:href', None) responseHref = nodes[0].childNodes[0].nodeValue - expectedHref = '/SOGo/dav/%s/' % username + expectedHref = '/SOGo/dav/' if responseHref[0:4] == "http": self.assertEquals("http://%s%s" % (hostname, expectedHref), responseHref, "{DAV:}principal-collection-set returned %s instead of '%s'" @@ -51,5 +69,101 @@ class WebDAVTest(unittest.TestCase): "{DAV:}principal-collection-set returned %s instead of '%s'" % ( responseHref, expectedHref )) + def _testPropfindURL(self, resource): + resourceWithSlash = resource[-1] == '/' + client = webdavlib.WebDAVClient(hostname, port, username, password) + propfind = webdavlib.WebDAVPROPFIND(resource, + ["{DAV:}displayname", "{DAV:}resourcetype"], + 1) + propfind.xpath_namespace = { "D": "DAV:" } + client.execute(propfind) + self.assertEquals(propfind.response["status"], 207) + + nodes = propfind.xpath_evaluate('/D:multistatus/D:response', + None) + for node in nodes: + responseHref = propfind.xpath_evaluate('D:href', node)[0].childNodes[0].nodeValue + hasSlash = responseHref[-1] == '/' + resourcetypes = \ + propfind.xpath_evaluate('D:propstat/D:prop/D:resourcetype', + node)[0].childNodes + isCollection = len(resourcetypes) > 0 + if isCollection: + self.assertEquals(hasSlash, resourceWithSlash, + "failure with href '%s' while querying '%s'" + % (responseHref, resource)) + else: + self.assertEquals(hasSlash, False, + "failure with href '%s' while querying '%s'" + % (responseHref, resource)) + + def testPropfindURL(self): + """propfind: ensure various NSURL work-arounds""" + # a collection without / + self._testPropfindURL('/SOGo/dav/%s' % username) + # a collection with / + self._testPropfindURL('/SOGo/dav/%s/' % username) + # a non-collection + self._testPropfindURL('/SOGo/dav/%s/freebusy.ifb' % username) + + ## REPORT + # http://tools.ietf.org/html/rfc3253.html#section-3.8 + def testExpandProperty(self): + """expand-property""" + client = webdavlib.WebDAVClient(hostname, port, username, password) + resource = '/SOGo/dav/%s/' % username + userInfo = fetchUserInfo(username) + + query_props = {"owner": { "href": resource, + "displayname": userInfo[0]}, + "principal-collection-set": { "href": "/SOGo/dav/", + "displayname": "SOGo"}} + query = webdavlib.WebDAVExpandProperty(resource, query_props.keys(), + ["displayname"]) + client.execute(query) + self.assertEquals(query.response["status"], 207) + + topResponse = query.xpath_evaluate('/D:multistatus/D:response')[0] + topHref = query.xpath_evaluate('D:href', topResponse)[0] + self.assertEquals(resource, topHref.childNodes[0].nodeValue) + for query_prop in query_props.keys(): + propResponse = query.xpath_evaluate('D:propstat/D:prop/D:%s' + % query_prop, topResponse)[0] + + +# +# +# +# /SOGo/dav/wsourdeau/ +# +# +# +# +# /SOGo/dav/wsourdeau/ +# +# +# Wolfgang Sourdeau +# +# HTTP/1.1 200 OK +# +# +# + propHref = query.xpath_evaluate('D:response/D:href', + propResponse)[0] + self.assertEquals(query_props[query_prop]["href"], + propHref.childNodes[0].nodeValue, + "'%s', href mismatch: exp. '%s', got '%s'" + % (query_prop, + query_props[query_prop]["href"], + propHref.childNodes[0].nodeValue)) + propDisplayname = query.xpath_evaluate('D:response/D:propstat/D:prop/D:displayname', + propResponse)[0] + self.assertEquals(query_props[query_prop]["displayname"], + propDisplayname.childNodes[0].nodeValue, + "'%s', displayname mismatch: exp. '%s', got '%s'" + % (query_prop, + query_props[query_prop]["displayname"], + propDisplayname.nodeValue)) + if __name__ == "__main__": unittest.main() diff --git a/Tests/webdavlib.py b/Tests/webdavlib.py index 6d834d1d2..2cca5d156 100644 --- a/Tests/webdavlib.py +++ b/Tests/webdavlib.py @@ -210,6 +210,21 @@ class WebDAVPROPFIND(WebDAVQuery): if properties is not None and len(properties) > 0: self._initProperties(properties) +class WebDAVPROPPATCH(WebDAVQuery): + method = "PROPPATCH" + +# + + def __init__(self, url, properties): + WebDAVQuery.__init__(self, url, None) + self.top_node = _WD_XMLTreeElement("propertyupdate") + set_node = _WD_XMLTreeElement("set") + self.top_node.append(set_node) + prop_node = _WD_XMLTreeElement("prop") + set_node.append(prop_node) + + prop_node.appendSubtree(self, properties) + class WebDAVMOVE(WebDAVQuery): method = "MOVE" destination = None @@ -314,6 +329,39 @@ class WebDAVSyncQuery(WebDAVREPORT): if properties is not None and len(properties) > 0: self._initProperties(properties) +class WebDAVExpandProperty(WebDAVREPORT): + def _parseTag(self, tag): + result = [] + + cb = tag.find("}") + if cb > -1: + result.append(tag[cb+1:]) + result.append(tag[1:cb]) + else: + result.append(tag) + result.append("DAV:") + + return result; + + def _propElement(self, tag): + parsedTag = self._parseTag(tag) + parameters = { "name": parsedTag[0] } + if len(parsedTag) > 1: + parameters["namespace"] = parsedTag[1] + + return _WD_XMLTreeElement("property", parameters) + + def __init__(self, url, query_properties, properties): + WebDAVQuery.__init__(self, url) + self.top_node = _WD_XMLTreeElement("expand-property") + + for query_tag in query_properties: + property_query = self._propElement(query_tag) + self.top_node.append(property_query) + for tag in properties: + property = self._propElement(tag) + property_query.append(property) + class MailDAVMailQuery(WebDAVREPORT): def __init__(self, url, properties, filters = None, sort = None, ascending = True): @@ -392,6 +440,11 @@ class _WD_XMLNS_MGR: return newTag class _WD_XMLTreeElement: + typeNum = type(0) + typeStr = type("") + typeList = type([]) + typeDict = type({}) + def __init__(self, tag, attributes = {}): self.tag = tag self.children = [] @@ -400,6 +453,26 @@ class _WD_XMLTreeElement: def append(self, child): self.children.append(child) + def appendSubtree(self, query, subtree): + if type(subtree) == self.typeNum: + strValue = "%d" % subtree + textNode = _WD_XMLTreeTextNode(strValue) + self.append(textNode) + elif type(subtree) == self.typeStr: + textNode = _WD_XMLTreeTextNode(subtree) + self.append(textNode) + elif type(subtree) == self.typeList: + for x in subtree: + self.appendSubtree(query, x) + elif type(subtree) == self.typeDict: + for x in subtree.keys(): + tag = query.render_tag(x) + node = _WD_XMLTreeElement(tag) + node.appendSubtree(query, subtree[x]) + self.append(node) + else: + None + def render(self, ns_text = None): text = "<" + self.tag diff --git a/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings b/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings index 6c44772b9..c99554057 100644 --- a/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings +++ b/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings @@ -512,6 +512,9 @@ vtodo_class2 = "(Tarefa Confidencial)"; "Synchronize" = "Synchronize"; "Tag:" = "Marca:"; +"iCal Delegation" = "iCal Delegation"; +"Shared when account is delegated" = "Shared when account is delegated"; + "Display" = "Display"; "Show alarms" = "Show alarms"; "Show tasks" = "Show tasks"; diff --git a/UI/Scheduler/Czech.lproj/Localizable.strings b/UI/Scheduler/Czech.lproj/Localizable.strings index 2289512ab..7e1e0eec0 100644 --- a/UI/Scheduler/Czech.lproj/Localizable.strings +++ b/UI/Scheduler/Czech.lproj/Localizable.strings @@ -512,6 +512,9 @@ vtodo_class2 = "(Důvěrný úkol)"; "Synchronize" = "Synchronize"; "Tag:" = "Štítek:"; +"iCal Delegation" = "iCal Delegation"; +"Shared when account is delegated" = "Shared when account is delegated"; + "Display" = "Display"; "Show alarms" = "Show alarms"; "Show tasks" = "Show tasks"; diff --git a/UI/Scheduler/Dutch.lproj/Localizable.strings b/UI/Scheduler/Dutch.lproj/Localizable.strings index 4f649a120..f5afd5945 100644 --- a/UI/Scheduler/Dutch.lproj/Localizable.strings +++ b/UI/Scheduler/Dutch.lproj/Localizable.strings @@ -512,6 +512,9 @@ vtodo_class2 = "(Vertrouwelijke taak)"; "Synchronize" = "Synchronize"; "Tag:" = "Markering:"; +"iCal Delegation" = "iCal Delegation"; +"Shared when account is delegated" = "Shared when account is delegated"; + "Display" = "Display"; "Show alarms" = "Show alarms"; "Show tasks" = "Show tasks"; diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings index 888eaca9d..2db0d97fa 100644 --- a/UI/Scheduler/English.lproj/Localizable.strings +++ b/UI/Scheduler/English.lproj/Localizable.strings @@ -512,6 +512,9 @@ vtodo_class2 = "(Confidential task)"; "Synchronize" = "Synchronize"; "Tag:" = "Tag:"; +"iCal Delegation" = "iCal Delegation"; +"Shared when account is delegated" = "Shared when account is delegated"; + "Display" = "Display"; "Show alarms" = "Show alarms"; "Show tasks" = "Show tasks"; diff --git a/UI/Scheduler/French.lproj/Localizable.strings b/UI/Scheduler/French.lproj/Localizable.strings index 0a0972554..f67a87040 100644 --- a/UI/Scheduler/French.lproj/Localizable.strings +++ b/UI/Scheduler/French.lproj/Localizable.strings @@ -512,6 +512,9 @@ vtodo_class2 = "(Tâche confidentielle)"; "Synchronize" = "Synchroniser"; "Tag:" = "Label :"; +"iCal Delegation" = "Délégation iCal"; +"Shared when account is delegated" = "Inclure cet agenda lors de la délégation"; + "Display" = "Affichage"; "Show alarms" = "Afficher les alarmes"; "Show tasks" = "Afficher les tâches"; diff --git a/UI/Scheduler/GNUmakefile b/UI/Scheduler/GNUmakefile index f79e4bd83..640e4da96 100644 --- a/UI/Scheduler/GNUmakefile +++ b/UI/Scheduler/GNUmakefile @@ -38,7 +38,6 @@ SchedulerUI_OBJC_FILES = \ UIxCalendarSelector.m \ UIxAppointmentEditor.m \ UIxTaskEditor.m \ - UIxCalDateLabel.m \ UIxDatePicker.m \ UIxTimeDateControl.m \ UIxCalParticipationStatusView.m \ diff --git a/UI/Scheduler/German.lproj/Localizable.strings b/UI/Scheduler/German.lproj/Localizable.strings index 44fcb7ba0..7a4f2e20b 100644 --- a/UI/Scheduler/German.lproj/Localizable.strings +++ b/UI/Scheduler/German.lproj/Localizable.strings @@ -512,6 +512,9 @@ vtodo_class2 = "(Vertrauliche Aufgabe)"; "Synchronize" = "Synchronize"; "Tag:" = "Tag:"; +"iCal Delegation" = "iCal Delegation"; +"Shared when account is delegated" = "Shared when account is delegated"; + "Display" = "Display"; "Show alarms" = "Show alarms"; "Show tasks" = "Show tasks"; diff --git a/UI/Scheduler/Hungarian.lproj/Localizable.strings b/UI/Scheduler/Hungarian.lproj/Localizable.strings index 18ea56ba7..7b8986a5f 100644 --- a/UI/Scheduler/Hungarian.lproj/Localizable.strings +++ b/UI/Scheduler/Hungarian.lproj/Localizable.strings @@ -512,6 +512,9 @@ vtodo_class2 = "(Bizalmas feladat)"; "Synchronize" = "Synchronize"; "Tag:" = "Cimke:"; +"iCal Delegation" = "iCal Delegation"; +"Shared when account is delegated" = "Shared when account is delegated"; + "Display" = "Display"; "Show alarms" = "Show alarms"; "Show tasks" = "Show tasks"; diff --git a/UI/Scheduler/Italian.lproj/Localizable.strings b/UI/Scheduler/Italian.lproj/Localizable.strings index 39b7de04f..456aac743 100644 --- a/UI/Scheduler/Italian.lproj/Localizable.strings +++ b/UI/Scheduler/Italian.lproj/Localizable.strings @@ -512,6 +512,9 @@ vtodo_class2 = "(Attività confidenziale)"; "Synchronize" = "Synchronize"; "Tag:" = "Etichetta:"; +"iCal Delegation" = "iCal Delegation"; +"Shared when account is delegated" = "Shared when account is delegated"; + "Display" = "Display"; "Show alarms" = "Show alarms"; "Show tasks" = "Show tasks"; diff --git a/UI/Scheduler/Russian.lproj/Localizable.strings b/UI/Scheduler/Russian.lproj/Localizable.strings index 099b74190..bc2f9940f 100644 --- a/UI/Scheduler/Russian.lproj/Localizable.strings +++ b/UI/Scheduler/Russian.lproj/Localizable.strings @@ -512,6 +512,9 @@ vtodo_class2 = "(Confidential task)"; "Synchronize" = "Synchronize"; "Tag:" = "Tag:"; +"iCal Delegation" = "iCal Delegation"; +"Shared when account is delegated" = "Shared when account is delegated"; + "Display" = "Display"; "Show alarms" = "Show alarms"; "Show tasks" = "Show tasks"; diff --git a/UI/Scheduler/Spanish.lproj/Localizable.strings b/UI/Scheduler/Spanish.lproj/Localizable.strings index 8cf76c1d1..d6e41c99a 100644 --- a/UI/Scheduler/Spanish.lproj/Localizable.strings +++ b/UI/Scheduler/Spanish.lproj/Localizable.strings @@ -512,6 +512,9 @@ vtodo_class2 = "(Tarea confidencial)"; "Synchronize" = "Synchronize"; "Tag:" = "Redacción:"; +"iCal Delegation" = "iCal Delegation"; +"Shared when account is delegated" = "Shared when account is delegated"; + "Display" = "Display"; "Show alarms" = "Show alarms"; "Show tasks" = "Show tasks"; diff --git a/UI/Scheduler/Swedish.lproj/Localizable.strings b/UI/Scheduler/Swedish.lproj/Localizable.strings index 693f363e5..1802fa24a 100644 --- a/UI/Scheduler/Swedish.lproj/Localizable.strings +++ b/UI/Scheduler/Swedish.lproj/Localizable.strings @@ -512,6 +512,9 @@ vtodo_class2 = "(Konfidentiell uppgift)"; "Synchronize" = "Synkronisera"; "Tag:" = "Etikett:"; +"iCal Delegation" = "iCal Delegation"; +"Shared when account is delegated" = "Shared when account is delegated"; + "Display" = "Visa"; "Show alarms" = "Visa alarm"; "Show tasks" = "Visa uppgifter"; diff --git a/UI/Scheduler/UIxCalendarProperties.h b/UI/Scheduler/UIxCalendarProperties.h index 0cc80609a..456c9eb4c 100644 --- a/UI/Scheduler/UIxCalendarProperties.h +++ b/UI/Scheduler/UIxCalendarProperties.h @@ -51,4 +51,7 @@ - (NSString *) calendarSyncTag; - (void) setCalendarSyncTag: (NSString *) newTag; +- (BOOL) isProxied; +- (void) setIsProxied: (BOOL) isProxied; + @end diff --git a/UI/Scheduler/UIxCalendarProperties.m b/UI/Scheduler/UIxCalendarProperties.m index 841faf414..3f4166927 100644 --- a/UI/Scheduler/UIxCalendarProperties.m +++ b/UI/Scheduler/UIxCalendarProperties.m @@ -149,6 +149,17 @@ [calendar setSyncTag: newTag]; } +/* DAV: calendar-proxy protocol */ +- (BOOL) isProxied +{ + return [calendar isProxied]; +} + +- (void) setIsProxied: (BOOL) isProxied +{ + [calendar setIsProxied: isProxied]; +} + - (BOOL) showCalendarAlarms { return [calendar showCalendarAlarms]; diff --git a/UI/Scheduler/Welsh.lproj/Localizable.strings b/UI/Scheduler/Welsh.lproj/Localizable.strings index 55f0d03fc..30be0f128 100644 --- a/UI/Scheduler/Welsh.lproj/Localizable.strings +++ b/UI/Scheduler/Welsh.lproj/Localizable.strings @@ -512,6 +512,9 @@ vtodo_class2 = "(Tasg gyhoeddus)"; "Synchronize" = "Synchronize"; "Tag:" = "Tag:"; +"iCal Delegation" = "iCal Delegation"; +"Shared when account is delegated" = "Shared when account is delegated"; + "Display" = "Display"; "Show alarms" = "Show alarms"; "Show tasks" = "Show tasks"; diff --git a/UI/Templates/SchedulerUI/UIxCalendarProperties.wox b/UI/Templates/SchedulerUI/UIxCalendarProperties.wox index cef0c4716..6fdf00220 100644 --- a/UI/Templates/SchedulerUI/UIxCalendarProperties.wox +++ b/UI/Templates/SchedulerUI/UIxCalendarProperties.wox @@ -15,7 +15,7 @@ var:value="calendarID"/>
- +
- -
+ +
-
+ +
+ />
+ +
+ +
+
+
- -
-
+ +
+
+
diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index 59f9452e1..d35d88d88 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -1891,16 +1891,17 @@ function onCalendarModify(event) { var url = ApplicationBaseURL + calendarID + "/properties"; var windowID = sanitizeWindowName(calendarID + " properties"); var width = 310; - var height = 270; + var height = 310; + var isWebCalendar = false; if (UserSettings['Calendar'] && UserSettings['Calendar']['WebCalendars']) { var webCalendars = UserSettings['Calendar']['WebCalendars']; var realID = calendarID.substr (1, calendarID.length - 1); if (webCalendars[realID]) - height = 290; + isWebCalendar = true; } - if (calendarID == "/personal") - height = 250; + if (isWebCalendar || calendarID == "/personal") + height -= 25; var properties = window.open(url, windowID, "width="+width+",height="+height+",resizable=0");