2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #import <GDLContentStore/GCSFolder.h>
25 #import "SOGoFolder.h"
27 #import "SOGoPermissions.h"
28 #import "SOGoContentObject.h"
30 @interface SOGoContentObject(ETag)
31 - (NSArray *)parseETagList:(NSString *)_c;
34 @implementation SOGoContentObject
36 // TODO: check superclass version
38 - (id) initWithName: (NSString *) newName
39 inContainer: (id) newContainer
41 if ((self = [super initWithName: newName inContainer: newContainer]))
44 content = [[self ocsFolder] fetchContentWithName: newName];
63 [content release]; content = nil;
74 - (void) setOCSPath: (NSString *) newOCSPath
76 if (![ocsPath isEqualToString: newOCSPath])
79 [self warnWithFormat:@"GCS path is already set! '%@'", newOCSPath];
81 ASSIGNCOPY (ocsPath, newOCSPath);
85 - (NSString *) ocsPath
87 NSMutableString *newOCSPath;
91 newOCSPath = [NSMutableString new];
92 [newOCSPath appendString: [self ocsPathOfContainer]];
93 if ([newOCSPath length] > 0)
95 if (![newOCSPath hasSuffix:@"/"])
96 [newOCSPath appendString: @"/"];
97 [newOCSPath appendString: nameInContainer];
105 - (NSString *) ocsPathOfContainer
107 NSString *ocsPathOfContainer;
109 if ([container respondsToSelector: @selector (ocsPath)])
110 ocsPathOfContainer = [container ocsPath];
112 ocsPathOfContainer = nil;
117 - (GCSFolder *) ocsFolder
119 return [container ocsFolder];
129 - (NSString *) contentAsString
134 - (NSException *) saveContentString: (NSString *) newContent
135 baseVersion: (unsigned int) newBaseVersion
137 /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
143 ASSIGN (content, newContent);
144 folder = [container ocsFolder];
147 ex = [folder writeContent: newContent
148 toName: nameInContainer
149 baseVersion: newBaseVersion];
151 [self errorWithFormat:@"write failed: %@", ex];
154 [self errorWithFormat:@"Did not find folder of content object."];
159 - (NSException *) saveContentString: (NSString *) newContent
161 return [self saveContentString: newContent baseVersion: 0];
164 - (NSException *) delete
166 /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
170 // TODO: add precondition check? (or add DELETEAction?)
172 if ((folder = [self ocsFolder]) == nil) {
173 [self errorWithFormat:@"Did not find folder of content object."];
177 if ((ex = [folder deleteContentWithName:[self nameInContainer]])) {
178 [self errorWithFormat:@"delete failed: %@", ex];
186 // - (id) lookupName:
188 // SoSelectorInvocation *invocation;
191 // name = [NSString stringWithFormat: @"%@:", [_key davMethodToObjC]];
193 // invocation = [[SoSelectorInvocation alloc]
194 // initWithSelectorNamed: name
195 // addContextParameter: YES];
196 // [invocation autorelease];
198 // return invocation;
202 - (id) PUTAction: (WOContext *) _ctx
206 unsigned int baseVersion;
210 if ((error = [self matchesRequestConditionInContext:_ctx]) != nil)
215 /* check whether its a request to the 'special' 'new' location */
217 Note: this is kinda hack. The OGo ZideStore detects writes to 'new' as
218 object creations and will assign a server side identifier. Most
219 current GroupDAV clients rely on this behaviour, so we reproduce it
221 A correct client would loop until it has a name which doesn't not
222 yet exist (by using if-none-match).
225 tmp = [[self nameInContainer] stringByDeletingPathExtension];
226 if ([tmp isEqualToString:@"new"]) {
227 tmp = [[[self container] class] globallyUniqueObjectId];
230 [self debugWithFormat:
231 @"reassigned a new location for special new-location: %@", tmp];
233 /* kinda dangerous */
234 ASSIGNCOPY(nameInContainer, tmp);
235 ASSIGN(ocsPath, nil);
238 /* determine base version from etag in if-match header */
240 Note: The -matchesRequestConditionInContext: already checks whether the
241 etag matches and returns an HTTP exception in case it doesn't.
242 We retrieve the etag again here to _ensure_ a transactionally save
244 (between the check and the update a change could have been done)
246 tmp = [rq headerForKey:@"if-match"];
247 tmp = [self parseETagList:tmp];
249 if ([tmp count] > 0) {
250 if ([tmp count] > 1) {
252 Note: we would have to attempt a save for _each_ of the etags being
253 passed in! In practice most WebDAV clients submit exactly one
256 [self warnWithFormat:
257 @"Got multiple if-match etags from client, only attempting to "
258 @"save with the first: %@", tmp];
261 etag = [tmp objectAtIndex:0];
263 baseVersion = ([etag length] > 0)
264 ? [etag unsignedIntValue]
265 : 0 /* 0 means 'do not check' */;
269 if ((error = [self saveContentString: [rq contentAsString]
270 baseVersion: baseVersion]) != nil)
275 // TODO: this should be automatic in the SoDispatcher if we return nil?
276 [[_ctx response] setStatus: 201 /* Created */];
278 if ((etag = [self davEntityTag]) != nil)
279 [[_ctx response] setHeader:etag forKey:@"etag"];
282 [[_ctx response] setHeader:[self baseURLInContext:_ctx]
286 return [_ctx response];
293 // TODO: cache tag in ivar? => if you do, remember to flush after PUT
297 NSNumber *versionValue;
299 folder = [self ocsFolder];
302 versionValue = [folder versionOfContentWithName: [self nameInContainer]];
303 sprintf (buf, "\"gcs%08d\"", [versionValue unsignedIntValue]);
304 entityTag = [NSString stringWithCString: buf];
308 [self errorWithFormat:@"Did not find folder of content object."];
317 - (NSException *) davMoveToTargetObject: (id) _target
318 newName: (NSString *) _name
322 Note: even for new objects we won't get a new name but a preinstantiated
323 object representing the new one.
326 @"TODO: move not implemented:\n target: %@\n new name: %@",
328 return [NSException exceptionWithHTTPStatus:405 /* not allowed */
329 reason:@"this object cannot be copied via WebDAV"];
332 - (NSException *) davCopyToTargetObject: (id)_target
333 newName: (NSString *) _name
337 Note: even for new objects we won't get a new name but a preinstantiated
338 object representing the new one.
341 @"TODO: copy not implemented:\n target: %@\n new name: %@",
343 return [NSException exceptionWithHTTPStatus:405 /* not allowed */
344 reason:@"this object cannot be copied via WebDAV"];
347 - (BOOL)davIsCollection {
348 return [self isFolderish];
353 - (NSArray *) aclUsers
355 return [container aclUsersForObjectAtPath: [self pathArrayToSoObject]];
358 - (NSArray *) aclsForUser: (NSString *) uid
360 NSMutableArray *acls;
361 NSArray *ownAcls, *containerAcls;
363 acls = [NSMutableArray array];
364 ownAcls = [container aclsForUser: uid
365 forObjectAtPath: [self pathArrayToSoObject]];
366 [acls addObjectsFromArray: ownAcls];
367 containerAcls = [container aclsForUser: uid];
368 if ([containerAcls count] > 0)
370 if ([containerAcls containsObject: SOGoRole_ObjectCreator])
372 [acls addObject: SOGoRole_ObjectCreator];
374 [acls addObject: SOGoRole_ObjectEditor];
376 if ([containerAcls containsObject: SOGoRole_ObjectReader])
377 [acls addObject: SOGoRole_ObjectViewer];
383 - (void) setRoles: (NSArray *) roles
384 forUser: (NSString *) uid
386 return [container setRoles: roles
388 forObjectAtPath: [self pathArrayToSoObject]];
391 - (void) removeAclsForUsers: (NSArray *) users
393 return [container removeAclsForUsers: users
394 forObjectAtPath: [self pathArrayToSoObject]];
397 - (NSString *) defaultUserID
404 - (NSString *) outlookMessageClass
411 - (void) appendAttributesToDescription: (NSMutableString *) _ms
413 [super appendAttributesToDescription:_ms];
415 [_ms appendFormat:@" ocs=%@", [self ocsPath]];
418 @end /* SOGoContentObject */