/* SOGoObject.m - this file is part of SOGo * * Copyright (C) 2004-2005 SKYRIX Software AG * Copyright (C) 2006-2022 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * 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 "NSArray+Utilities.h" #import "NSCalendarDate+SOGo.h" #import "NSDictionary+Utilities.h" #import "NSObject+DAV.h" #import "NSObject+Utilities.h" #import "NSString+DAV.h" #import "NSString+Utilities.h" #import "SOGoCache.h" #import "SOGoSystemDefaults.h" #import "SOGoUser.h" #import "SOGoWebDAVAclManager.h" #import "WORequest+SOGo.h" #import "WOResponse+SOGo.h" @implementation SOGoObject + (SOGoWebDAVAclManager *) webdavAclManager { static SOGoWebDAVAclManager *aclManager = nil; if (!aclManager) aclManager = [SOGoWebDAVAclManager new]; return aclManager; } /* + (id) WebDAVPermissionsMap { static NSDictionary *permissions = nil; if (!permissions) { permissions = [NSDictionary dictionaryWithObjectsAndKeys: davElement (@"read", XMLNS_WEBDAV), SoPerm_AccessContentsInformation, davElement (@"bind", XMLNS_WEBDAV), SoPerm_AddDocumentsImagesAndFiles, davElement (@"unbind", XMLNS_WEBDAV), SoPerm_DeleteObjects, davElement (@"write-acl", XMLNS_WEBDAV), SoPerm_ChangePermissions, davElement (@"write-content", XMLNS_WEBDAV), SoPerm_ChangeImagesAndFiles, NULL]; [permissions retain]; } return permissions; } */ + (NSString *) globallyUniqueObjectId { /* 4C08AE1A-A808-11D8-AC5A-000393BBAFF6 SOGo-Web-28273-18283-288182 printf( "%x", *(int *) &f); */ static int pid = 0; static int sequence = 0; static float rndm = 0; float f; if (pid == 0) { pid = getpid(); rndm = random(); } if(pid<255) pid+=255; //Because of a native spamassassin rule -> header TVD_RATWARE_MSGID_01 rule on message-id like ') document { NSString *command; command = [[document firstChild] localName]; return [NSString stringWithFormat: @"%@:", command]; } - (id) davPOSTRequest: (WORequest *) request withContentType: (NSString *) cType inContext: (WOContext *) localContext { id obj; id document; SEL commandSel; NSString *command; obj = nil; if ([cType hasPrefix: @"application/xml"] || [cType hasPrefix: @"text/xml"]) { document = [request contentAsDOMDocument]; command = [[self _parseXMLCommand: document] davMethodToObjC]; commandSel = NSSelectorFromString (command); if ([self respondsToSelector: commandSel]) obj = [self performSelector: commandSel withObject: localContext]; } return obj; } - (id) POSTAction: (id) localContext { WORequest *rq; id obj; rq = [localContext request]; if ([rq isSoWebDAVRequest]) obj = [self davPOSTRequest: rq withContentType: [rq headerForKey: @"content-type"] inContext: localContext]; else obj = nil; return obj; } - (id) GETAction: (id) localContext { // TODO: I guess this should really be done by SOPE (redirect to // default method) WORequest *request; NSString *uri; NSException *error; id value; request = [localContext request]; if ([request isSoWebDAVRequest]) { if ([self respondsToSelector: @selector (contentAsString)]) { error = [self matchesRequestConditionInContext: localContext]; if (error) value = error; else value = [self _webDAVResponse: localContext]; } else value = [NSException exceptionWithDAVStatus: 501 /* not implemented */ reason: @"no WebDAV GET support?!"]; } else { value = [localContext response]; uri = [[request uri] composeURLWithAction: @"view" parameters: [request formValues] andHash: NO]; [value setStatus: 302 /* moved */]; [value setHeader: uri forKey: @"location"]; } return value; } /* etag support */ - (NSArray *) parseETagList: (NSString *) list { NSArray *etags; if (![list length] || [list isEqualToString:@"*"]) etags = nil; else etags = [[list componentsSeparatedByString: @","] trimmedComponents]; return etags; } - (NSException *)checkIfMatchCondition:(NSString *)_c inContext:(id)_ctx { /* Only run the request if one of the etags matches the resource etag, usually used to ensure consistent PUTs. */ NSArray *etags; NSString *etag; if ([_c isEqualToString:@"*"]) /* to ensure that the resource exists! */ return nil; if ((etags = [self parseETagList:_c]) == nil) return nil; if ([etags count] == 0) /* no etags to check for? */ return nil; etag = [self davEntityTag]; if ([etag length] == 0) /* has no etag, ignore */ return nil; if ([etags containsObject:etag]) { [self debugWithFormat:@"etag '%@' matches: %@", etag, [etags componentsJoinedByString:@","]]; return nil; /* one etag matches, so continue with request */ } // TODO: we might want to return the davEntityTag in the response [self debugWithFormat:@"etag '%@' does not match: %@", etag, [etags componentsJoinedByString:@","]]; return [NSException exceptionWithDAVStatus: 412 /* Precondition Failed */ reason:@"Precondition Failed"]; } - (NSException *)checkIfNoneMatchCondition:(NSString *)_c inContext:(id)_ctx { /* If one of the etags is still the same, we can ignore the request. Can be used for PUT to ensure that the object does not exist in the store and for GET to retrieve the content only if if the etag changed. */ if (![_c isEqualToString:@"*"] && [[[_ctx request] method] isEqualToString:@"GET"]) { NSString *etag; NSArray *etags; if ((etags = [self parseETagList:_c]) == nil) return nil; if ([etags count] == 0) /* no etags to check for? */ return nil; etag = [self davEntityTag]; if ([etag length] == 0) /* has no etag, ignore */ return nil; if ([etags containsObject:etag]) { [self debugWithFormat:@"etag '%@' matches: %@", etag, [etags componentsJoinedByString:@","]]; /* one etag matches, so stop the request */ return [NSException exceptionWithDAVStatus: 304 /* Not Modified */ reason: @"object was not modified"]; } return nil; } #if 0 if ([_c isEqualToString:@"*"]) return nil; if ((a = [self parseETagList:_c]) == nil) return nil; #else [self logWithFormat:@"TODO: implement if-none-match for etag: '%@'", _c]; #endif return nil; } - (NSException *)matchesRequestConditionInContext:(id)_ctx { NSException *error; WORequest *rq; NSString *c; if ((rq = [(WOContext *)_ctx request]) == nil) return nil; /* be tolerant - no request, no condition */ if ((c = [rq headerForKey:@"if-match"]) != nil) { if ((error = [self checkIfMatchCondition:c inContext:_ctx]) != nil) return error; } if ((c = [rq headerForKey:@"if-none-match"]) != nil) { if ((error = [self checkIfNoneMatchCondition:c inContext:_ctx]) != nil) return error; } return nil; } /* acls */ /* roles required to obtain the "authorized subscriber" role */ - (NSArray *) subscriptionRoles { return [container subscriptionRoles]; } - (BOOL) addUserInAcls: (NSString *) uid { BOOL result; SOGoDomainDefaults *dd; if ([uid length] && ![uid isEqualToString: [self ownerInContext: nil]]) { [self setRoles: [self aclsForUser: uid] forUser: uid]; dd = [[context activeUser] domainDefaults]; if ([dd aclSendEMailNotifications]) [self sendACLAdditionAdvisoryToUser: uid]; result = YES; } else result = NO; return result; } - (BOOL) removeUserFromAcls: (NSString *) uid { BOOL result; SOGoDomainDefaults *dd; if ([uid length]) { [self removeAclsForUsers: [NSArray arrayWithObject: uid]]; dd = [[context activeUser] domainDefaults]; if ([dd aclSendEMailNotifications]) [self sendACLRemovalAdvisoryToUser: uid]; result = YES; } else result = NO; return result; } - (NSArray *) aclUsers { [self subclassResponsibility: _cmd]; return nil; } - (NSArray *) aclsForUser: (NSString *) uid { [self subclassResponsibility: _cmd]; return nil; } - (void) setRoles: (NSArray *) roles forUser: (NSString *) uid { [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]; } - (NSString *) defaultUserID { [self subclassResponsibility: _cmd]; return nil; } - (void) sendACLAdvisoryTemplate: (NSString *) template toUser: (NSString *) uid { NSString *language, *pageName; SOGoUserDefaults *userDefaults; SOGoACLAdvisory *page; WOApplication *app; userDefaults = [[SOGoUser userWithLogin: uid roles: nil] userDefaults]; language = [userDefaults language]; pageName = [NSString stringWithFormat: @"SOGoACL%@%@Advisory", language, template]; app = [WOApplication application]; page = [app pageWithName: pageName inContext: context]; if (page == nil) [self errorWithFormat: @"Template %@ doesn't exist.", pageName]; [page setACLObject: self]; [page setRecipientUID: uid]; [page send]; } - (void) sendACLAdditionAdvisoryToUser: (NSString *) uid { return [self sendACLAdvisoryTemplate: @"Addition" toUser: uid]; } - (void) sendACLRemovalAdvisoryToUser: (NSString *) uid { return [self sendACLAdvisoryTemplate: @"Removal" toUser: uid]; } - (NSURL *) _urlPreferringParticle: (NSString *) expected overThisOne: (NSString *) possible { NSURL *serverURL, *url; NSMutableArray *path; NSString *baseURL, *urlMethod, *fullHost; NSNumber *port; int i; serverURL = [context serverURL]; baseURL = [[self baseURLInContext: context] stringByUnescapingURL]; path = [NSMutableArray arrayWithArray: [baseURL componentsSeparatedByString: @"/"]]; if ([baseURL hasPrefix: @"http"]) { [path removeObjectAtIndex: 1]; [path removeObjectAtIndex: 0]; [path replaceObjectAtIndex: 0 withObject: @""]; } urlMethod = [path objectAtIndex: 2]; if (![urlMethod isEqualToString: expected]) { if ([urlMethod isEqualToString: possible]) [path replaceObjectAtIndex: 2 withObject: expected]; else [path insertObject: expected atIndex: 2]; } port = [serverURL port]; if (port) fullHost = [NSString stringWithFormat: @"%@:%@", [serverURL host], port]; else fullHost = [serverURL host]; for (i = 0 ; i < [path count] ; i++) { // For DAV url, the username must be decrypted // Username is placed afted dav in url components if ([[[path objectAtIndex: i] lowercaseString] isEqualToString:@"dav"]) { if ([path count] > (i + 1)) { [path replaceObjectAtIndex: (i +1) withObject: [SOGoUser getDecryptedUsernameIfNeeded: [path objectAtIndex: (i + 1)] request: [context request]]]; } } } url = [[NSURL alloc] initWithScheme: [serverURL scheme] host: fullHost path: [path componentsJoinedByString: @"/"]]; [url autorelease]; return url; } - (NSURL *) davURL { return [self _urlPreferringParticle: @"dav" overThisOne: @"so"]; } - (NSString *) davURLAsString { return [[container davURLAsString] stringByAppendingFormat: @"%@", nameInContainer]; } - (NSURL *) soURL { return [self _urlPreferringParticle: @"so" overThisOne: @"dav"]; } - (NSURL *) soURLToBaseContainerForUser: (NSString *) uid { NSURL *soURL, *baseSoURL; NSArray *basePath; NSMutableArray *newPath; soURL = [self soURL]; basePath = [[soURL path] componentsSeparatedByString: @"/"]; newPath = [NSMutableArray arrayWithArray: [basePath subarrayWithRange: NSMakeRange (0, 5)]]; [newPath replaceObjectAtIndex: 3 withObject: uid]; baseSoURL = [[NSURL alloc] initWithScheme: [soURL scheme] host: [soURL host] path: [newPath componentsJoinedByString: @"/"]]; [baseSoURL autorelease]; return baseSoURL; } - (NSURL *) soURLToBaseContainerForCurrentUser { NSString *currentLogin; currentLogin = [[context activeUser] login]; return [self soURLToBaseContainerForUser: currentLogin]; } - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid { [self subclassResponsibility: _cmd]; return nil; } - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid { [self subclassResponsibility: _cmd]; return nil; } /* description */ - (void) appendAttributesToDescription: (NSMutableString *) _ms { if (nameInContainer) [_ms appendFormat:@" name=%@", nameInContainer]; if (container) [_ms appendFormat:@" container=%p/%@", container, [container valueForKey:@"nameInContainer"]]; } - (NSString *) description { NSMutableString *ms; ms = [NSMutableString stringWithCapacity:64]; [ms appendFormat:@"<%p[%@]:", self, NSStringFromClass([self class])]; [self appendAttributesToDescription:ms]; [ms appendString:@">"]; return ms; } - (NSString *) loggingPrefix { return [NSString stringWithFormat:@"<%p[%@]:%@>", self, NSStringFromClass([self class]), [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 *classes; static NSArray *calendarClasses = nil, *contactsClasses = nil, *upperClasses = nil; NSArray *caldavClasses; BOOL found = NO, needCalDAVClasses = NO, needCardDAVClasses = NO; NSUInteger count, max; /* popuplate static arrays */ if (!upperClasses) { upperClasses = [NSArray arrayWithObjects: NSClassFromString (@"SOGoPublicBaseFolder"), NSClassFromString (@"SOGoUserFolder"), nil]; [upperClasses retain]; } if (!calendarClasses) { calendarClasses = [NSArray arrayWithObjects: NSClassFromString (@"SOGoAppointmentFolders"), NSClassFromString (@"SOGoAppointmentFolder"), nil]; [calendarClasses retain]; } if (!contactsClasses) { contactsClasses = [NSArray arrayWithObjects: NSClassFromString (@"SOGoContactFolders"), NSClassFromString (@"SOGoContactGCSFolder"), NSClassFromString (@"SOGoContactSourceFolder"), nil]; [contactsClasses retain]; } /* determine which kind of object we are dealing with */ if ([self isFolderish]) { max = [upperClasses count]; for (count = 0; !found && count < max; count++) { if ([self isKindOfClass: [upperClasses objectAtIndex: count]]) { found = YES; needCalDAVClasses = YES; needCardDAVClasses = YES; } } max = [calendarClasses count]; for (count = 0; !found && count < max; count++) { if ([self isKindOfClass: [calendarClasses objectAtIndex: count]]) { found = YES; needCalDAVClasses = YES; } } max = [contactsClasses count]; for (count = 0; !found && count < max; count++) { if ([self isKindOfClass: [contactsClasses objectAtIndex: count]]) { found = YES; needCardDAVClasses = YES; } } } /* prepare the array */ classes = [[super davComplianceClassesInContext: localContext] mutableCopy]; if (!classes) classes = [NSMutableArray new]; [classes autorelease]; /* generic */ [classes addObject: @"access-control"]; /* CardDAV */ if (needCardDAVClasses) [classes addObject: @"addressbook"]; /* CalDAV */ if (needCalDAVClasses) { caldavClasses = [NSArray arrayWithObjects: @"calendar-access", @"calendar-schedule", @"calendar-auto-schedule", @"calendar-proxy", // @"calendarserver-private-events", // @"calendarserver-private-comments", // @"calendarserver-sharing", // @"calendarserver-sharing-no-scheduling", @"calendar-query-extended", @"extended-mkcol", @"calendarserver-principal-property-search", nil]; [classes addObjectsFromArray: caldavClasses]; } return classes; } - (SOGoWebDAVValue *) davCurrentUserPrincipal { NSDictionary *userHREF; NSString *login, *s; SOGoUser *activeUser; SOGoWebDAVValue *davCurrentUserPrincipal; activeUser = [[self context] activeUser]; login = [activeUser login]; if ([login isEqualToString: @"anonymous"]) davCurrentUserPrincipal = nil; else { s = [NSString stringWithFormat: @"/SOGo/dav/%@", login]; userHREF = davElementWithContent (@"href", XMLNS_WEBDAV, s); davCurrentUserPrincipal = [davElementWithContent (@"current-user-principal", XMLNS_WEBDAV, userHREF) asWebDAVValue]; } return davCurrentUserPrincipal; } /* dav acls */ - (NSString *) davRecordForUser: (NSString *) user parameters: (NSArray *) params { NSMutableString *userRecord; userRecord = [NSMutableString string]; [userRecord appendFormat: @"%@", [user stringByEscapingXMLString]]; // Make sure to not call [SOGoUser userWithLogin...] here if nocn AND noemail // is specified. We'll avoid generating LDAP calls by doing so. if (![params containsObject: @"nocn"]) { NSString *cn; cn = [[SOGoUser userWithLogin: user roles: nil] cn]; if (!cn) cn = user; [userRecord appendFormat: @"%@", [cn safeStringByEscapingXMLString]]; } if (![params containsObject: @"noemail"]) { NSString *email; email = [[[SOGoUser userWithLogin: user roles: nil] allEmails] objectAtIndex: 0]; if (email) [userRecord appendFormat: @"%@", [email stringByEscapingXMLString]]; } return userRecord; } - (NSString *) _davAclUserListQuery: (NSString *) theParameters { NSMutableString *userList; NSString *defaultUserID, *currentUserID; NSEnumerator *users; NSArray *params; if (theParameters && [theParameters length]) params = [[theParameters lowercaseString] componentsSeparatedByString: @","]; else params = [NSArray array]; userList = [NSMutableString string]; defaultUserID = [self defaultUserID]; if ([defaultUserID length]) [userList appendFormat: @"%@", [defaultUserID stringByEscapingXMLString]]; users = [[self aclUsers] objectEnumerator]; while ((currentUserID = [users nextObject])) { if (![currentUserID isEqualToString: defaultUserID]) { [userList appendFormat: @"%@", [self davRecordForUser: currentUserID parameters: params]]; } } return userList; } - (NSString *) _davAclUserRoles: (NSString *) userName { NSArray *roles; roles = [[self aclsForUser: userName] stringsWithFormat: @"<%@/>"]; return [roles componentsJoinedByString: @""]; } - (NSArray *) _davGetRolesFromRequest: (id ) node { NSMutableArray *roles; NSArray *childNodes; NSString *currentRole; unsigned int count, max; roles = [NSMutableArray array]; childNodes = [self domNode: node getChildNodesByType: DOM_ELEMENT_NODE]; max = [childNodes count]; for (count = 0; count < max; count++) { currentRole = [[childNodes objectAtIndex: count] localName]; [roles addObject: currentRole]; } return roles; } - (NSString *) _davAclActionFromQuery: (id ) document { id node, userAttr; id attrs; NSString *nodeName, *result, *response, *user; NSArray *childNodes, *allUsers, *allRoles; int i; result = nil; childNodes = [self domNode: [document documentElement] getChildNodesByType: DOM_ELEMENT_NODE]; if ([childNodes count]) { node = [childNodes objectAtIndex: 0]; nodeName = [node localName]; // // We support parameters during the user-list REPORT. // We do that in order to avoid looking up from the LDAP // the CN and the EMAIL address of the users that are // returned in the REPORT's response. That can be slow if // the LDAP server used for authentication is slow and this // information might not be necessary for scripting purposes. // // The following parameters are supported: // // nocn - avoid returning the CN // noemail - avoid returning the EMAIL address // // Both can be specified using: // // params=nocn,noemail // if ([nodeName isEqualToString: @"user-list"]) { result = [self _davAclUserListQuery: [[[node attributes] namedItem: @"params"] nodeValue]]; } else if ([nodeName isEqualToString: @"roles"]) { attrs = [node attributes]; userAttr = [attrs namedItem: @"user"]; user = [userAttr nodeValue]; if ([user length]) result = [self _davAclUserRoles: user]; } else if ([nodeName isEqualToString: @"set-roles"]) { // Disable Acl modifications if this is not the owner if (![self ignoreRights]) return nil; // We support two ways of setting roles. The first one is, for example: // // // // // // while the second one, for mutliple users at the same time, is: // // // // // attrs = [node attributes]; userAttr = [attrs namedItem: @"user"]; user = [userAttr nodeValue]; if ([user length]) allUsers = [NSArray arrayWithObject: user]; else { userAttr = [attrs namedItem: @"users"]; allUsers = [[userAttr nodeValue] componentsSeparatedByString: @","]; } allRoles = [self _davGetRolesFromRequest: node]; for (i = 0; i < [allUsers count]; i++) { [self setRoles: allRoles forUser: [allUsers objectAtIndex: i]]; } result = @""; } // // Here again, we support two ways of adding users. The first one is, for example: // // // // // // while the second one, for mutliple users at the same time, is: // // // // // else if ([nodeName isEqualToString: @"add-user"]) { // Disable Acl modifications if this is not the owner if (![self ignoreRights]) return nil; attrs = [node attributes]; userAttr = [attrs namedItem: @"user"]; user = [userAttr nodeValue]; if ([self addUserInAcls: user]) result = @""; } else if ([nodeName isEqualToString: @"add-users"]) { // Disable Acl modifications if this is not the owner if (![self ignoreRights]) return nil; attrs = [node attributes]; userAttr = [attrs namedItem: @"users"]; allUsers = [[userAttr nodeValue] componentsSeparatedByString: @","]; for (i = 0; i < [allUsers count]; i++) { if ([self addUserInAcls: [allUsers objectAtIndex: i]]) result = @""; else { result = nil; break; } } } // // See the comment for add-user / add-users // else if ([nodeName isEqualToString: @"remove-user"]) { // Disable Acl modifications if this is not the owner if (![self ignoreRights]) return nil; attrs = [node attributes]; userAttr = [attrs namedItem: @"user"]; user = [userAttr nodeValue]; if ([self removeUserFromAcls: user]) result = @""; } else if ([nodeName isEqualToString: @"remove-users"]) { attrs = [node attributes]; userAttr = [attrs namedItem: @"users"]; allUsers = [[userAttr nodeValue] componentsSeparatedByString: @","]; for (i = 0; i < [allUsers count]; i++) { if ([self removeUserFromAcls: [allUsers objectAtIndex: i]]) result = @""; else { result = nil; break; } } } } if (result) { if ([result length]) response = [NSString stringWithFormat: @"<%@>%@", nodeName, result, nodeName]; else response = @""; } else response = nil; return response; } - (id) davAclQuery: (WOContext *) queryContext { WOResponse *r; id document; NSString *content; r = [queryContext response]; [r setContentEncoding: NSUTF8StringEncoding]; [r setHeader: @"no-cache" forKey: @"pragma"]; [r setHeader: @"no-cache" forKey: @"cache-control"]; document = [[context request] contentAsDOMDocument]; content = [self _davAclActionFromQuery: document]; if (content) { if ([content length]) { [r setStatus: 207]; [r setHeader: @"application/xml; charset=\"utf-8\"" forKey: @"content-type"]; [r appendContentString: @"\r\n"]; [r appendContentString: content]; } else [r setStatus: 204]; } else [r setStatus: 400]; return r; } - (NSException *) davSetProperties: (NSDictionary *) setProps removePropertiesNamed: (NSArray *) removedProps inContext: (WOContext *) localContext { NSString *currentProp; NSException *exception; NSEnumerator *properties; id currentValue; SEL methodSel; properties = [[setProps allKeys] objectEnumerator]; exception = nil; while (!exception && (currentProp = [properties nextObject])) { methodSel = NSSelectorFromString ([currentProp davSetterName]); if ([self respondsToSelector: methodSel]) { currentValue = [setProps objectForKey: currentProp]; exception = [self performSelector: methodSel withObject: currentValue]; if (![exception isKindOfClass: [NSException class]]) { exception = nil; } } else exception = [NSException exceptionWithDAVStatus: 403 reason: [NSString stringWithFormat: @"Property '%@' cannot be set.", currentProp]]; } return exception; } - (NSString *) davBooleanForResult: (BOOL) result { return (result ? @"true" : @"false"); } - (BOOL) isValidDAVBoolean: (NSString *) davBoolean { static NSSet *validBooleans = nil; if (!validBooleans) { validBooleans = [NSSet setWithObjects: @"true", @"false", @"1", @"0", nil]; [validBooleans retain]; } return [validBooleans containsObject: davBoolean]; } - (BOOL) resultForDAVBoolean: (NSString *) davBoolean { BOOL result; result = ([davBoolean isEqualToString: @"true"] || [davBoolean isEqualToString: @"1"]); return result; } - (NSString *) labelForKey: (NSString *) key { return [self labelForKey: key inContext: context]; } - (id) exceptionWithHTTPStatus: (unsigned short) theStatus { if ([[context request] handledByDefaultHandler]) return [NSException exceptionWithHTTPStatus: theStatus]; else return [NSException exceptionWithDAVStatus: theStatus]; } - (id) exceptionWithHTTPStatus: (unsigned short) theStatus reason: (NSString *) theReason { if ([[context request] handledByDefaultHandler]) return [NSException exceptionWithHTTPStatus: theStatus reason: theReason]; else return [NSException exceptionWithDAVStatus: theStatus reason: theReason]; } @end /* SOGoObject */