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 <Foundation/NSArray.h>
23 #import <Foundation/NSString.h>
24 #import <Foundation/NSValue.h>
26 #import <NGObjWeb/NSException+HTTP.h>
27 #import <NGObjWeb/WOContext.h>
28 #import <NGObjWeb/WOResponse.h>
29 #import <NGExtensions/NSObject+Logs.h>
30 #import <GDLContentStore/GCSFolder.h>
32 #import "SOGoFolder.h"
34 #import "SOGoPermissions.h"
35 #import "SOGoContentObject.h"
37 @interface SOGoContentObject(ETag)
38 - (NSArray *)parseETagList:(NSString *)_c;
41 @implementation SOGoContentObject
43 // TODO: check superclass version
45 - (id) initWithName: (NSString *) newName
46 inContainer: (id) newContainer
48 if ((self = [super initWithName: newName inContainer: newContainer]))
51 content = [[self ocsFolder] fetchContentWithName: newName];
70 [content release]; content = nil;
81 - (void) setOCSPath: (NSString *) newOCSPath
83 if (![ocsPath isEqualToString: newOCSPath])
86 [self warnWithFormat:@"GCS path is already set! '%@'", newOCSPath];
88 ASSIGNCOPY (ocsPath, newOCSPath);
92 - (NSString *) ocsPath
94 NSMutableString *newOCSPath;
98 newOCSPath = [NSMutableString new];
99 [newOCSPath appendString: [self ocsPathOfContainer]];
100 if ([newOCSPath length] > 0)
102 if (![newOCSPath hasSuffix:@"/"])
103 [newOCSPath appendString: @"/"];
104 [newOCSPath appendString: nameInContainer];
105 ocsPath = newOCSPath;
112 - (NSString *) ocsPathOfContainer
114 NSString *ocsPathOfContainer;
116 if ([container respondsToSelector: @selector (ocsPath)])
117 ocsPathOfContainer = [container ocsPath];
119 ocsPathOfContainer = nil;
124 - (GCSFolder *) ocsFolder
126 return [container ocsFolder];
136 - (NSString *) contentAsString
141 - (NSException *) saveContentString: (NSString *) newContent
142 baseVersion: (unsigned int) newBaseVersion
144 /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
150 ASSIGN (content, newContent);
151 folder = [container ocsFolder];
154 ex = [folder writeContent: newContent
155 toName: nameInContainer
156 baseVersion: newBaseVersion];
158 [self errorWithFormat:@"write failed: %@", ex];
161 [self errorWithFormat:@"Did not find folder of content object."];
166 - (NSException *) saveContentString: (NSString *) newContent
168 return [self saveContentString: newContent baseVersion: 0];
171 - (NSException *) delete
173 /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
177 // TODO: add precondition check? (or add DELETEAction?)
179 if ((folder = [self ocsFolder]) == nil) {
180 [self errorWithFormat:@"Did not find folder of content object."];
184 if ((ex = [folder deleteContentWithName:[self nameInContainer]])) {
185 [self errorWithFormat:@"delete failed: %@", ex];
193 // - (id) lookupName:
195 // SoSelectorInvocation *invocation;
198 // name = [NSString stringWithFormat: @"%@:", [_key davMethodToObjC]];
200 // invocation = [[SoSelectorInvocation alloc]
201 // initWithSelectorNamed: name
202 // addContextParameter: YES];
203 // [invocation autorelease];
205 // return invocation;
209 - (id) PUTAction: (WOContext *) _ctx
213 unsigned int baseVersion;
217 if ((error = [self matchesRequestConditionInContext:_ctx]) != nil)
222 /* check whether its a request to the 'special' 'new' location */
224 Note: this is kinda hack. The OGo ZideStore detects writes to 'new' as
225 object creations and will assign a server side identifier. Most
226 current GroupDAV clients rely on this behaviour, so we reproduce it
228 A correct client would loop until it has a name which doesn't not
229 yet exist (by using if-none-match).
232 tmp = [[self nameInContainer] stringByDeletingPathExtension];
233 if ([tmp isEqualToString:@"new"]) {
234 tmp = [[[self container] class] globallyUniqueObjectId];
237 [self debugWithFormat:
238 @"reassigned a new location for special new-location: %@", tmp];
240 /* kinda dangerous */
241 ASSIGNCOPY(nameInContainer, tmp);
242 ASSIGN(ocsPath, nil);
245 /* determine base version from etag in if-match header */
247 Note: The -matchesRequestConditionInContext: already checks whether the
248 etag matches and returns an HTTP exception in case it doesn't.
249 We retrieve the etag again here to _ensure_ a transactionally save
251 (between the check and the update a change could have been done)
253 tmp = [rq headerForKey:@"if-match"];
254 tmp = [self parseETagList:tmp];
256 if ([tmp count] > 0) {
257 if ([tmp count] > 1) {
259 Note: we would have to attempt a save for _each_ of the etags being
260 passed in! In practice most WebDAV clients submit exactly one
263 [self warnWithFormat:
264 @"Got multiple if-match etags from client, only attempting to "
265 @"save with the first: %@", tmp];
268 etag = [tmp objectAtIndex:0];
270 baseVersion = ([etag length] > 0)
271 ? [etag unsignedIntValue]
272 : 0 /* 0 means 'do not check' */;
276 if ((error = [self saveContentString: [rq contentAsString]
277 baseVersion: baseVersion]) != nil)
282 // TODO: this should be automatic in the SoDispatcher if we return nil?
283 [[_ctx response] setStatus: 201 /* Created */];
285 if ((etag = [self davEntityTag]) != nil)
286 [[_ctx response] setHeader:etag forKey:@"etag"];
289 [[_ctx response] setHeader:[self baseURLInContext:_ctx]
293 return [_ctx response];
300 // TODO: cache tag in ivar? => if you do, remember to flush after PUT
304 NSNumber *versionValue;
306 folder = [self ocsFolder];
309 versionValue = [folder versionOfContentWithName: [self nameInContainer]];
310 sprintf (buf, "\"gcs%08d\"", [versionValue unsignedIntValue]);
311 entityTag = [NSString stringWithCString: buf];
315 [self errorWithFormat:@"Did not find folder of content object."];
324 - (NSException *) davMoveToTargetObject: (id) _target
325 newName: (NSString *) _name
329 Note: even for new objects we won't get a new name but a preinstantiated
330 object representing the new one.
333 @"TODO: move not implemented:\n target: %@\n new name: %@",
335 return [NSException exceptionWithHTTPStatus:405 /* not allowed */
336 reason:@"this object cannot be copied via WebDAV"];
339 - (NSException *) davCopyToTargetObject: (id)_target
340 newName: (NSString *) _name
344 Note: even for new objects we won't get a new name but a preinstantiated
345 object representing the new one.
348 @"TODO: copy not implemented:\n target: %@\n new name: %@",
350 return [NSException exceptionWithHTTPStatus:405 /* not allowed */
351 reason:@"this object cannot be copied via WebDAV"];
354 - (BOOL)davIsCollection {
355 return [self isFolderish];
360 - (NSArray *) aclUsers
362 return [container aclUsersForObjectAtPath: [self pathArrayToSoObject]];
365 - (NSArray *) aclsForUser: (NSString *) uid
367 NSMutableArray *acls;
368 NSArray *ownAcls, *containerAcls;
370 acls = [NSMutableArray array];
371 ownAcls = [container aclsForUser: uid
372 forObjectAtPath: [self pathArrayToSoObject]];
373 [acls addObjectsFromArray: ownAcls];
374 containerAcls = [container aclsForUser: uid];
375 if ([containerAcls count] > 0)
377 if ([containerAcls containsObject: SOGoRole_ObjectCreator])
379 [acls addObject: SOGoRole_ObjectCreator];
381 [acls addObject: SOGoRole_ObjectEditor];
383 if ([containerAcls containsObject: SOGoRole_ObjectReader])
384 [acls addObject: SOGoRole_ObjectViewer];
390 - (void) setRoles: (NSArray *) roles
391 forUser: (NSString *) uid
393 return [container setRoles: roles
395 forObjectAtPath: [self pathArrayToSoObject]];
398 - (void) removeAclsForUsers: (NSArray *) users
400 return [container removeAclsForUsers: users
401 forObjectAtPath: [self pathArrayToSoObject]];
404 - (NSString *) defaultUserID
411 - (NSString *) outlookMessageClass
418 - (void) appendAttributesToDescription: (NSMutableString *) _ms
420 [super appendAttributesToDescription:_ms];
422 [_ms appendFormat:@" ocs=%@", [self ocsPath]];
425 @end /* SOGoContentObject */