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 "SOGoGCSFolder.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 - (void) setContentString: (NSString *) newContent
143 ASSIGN (content, newContent);
146 - (NSException *) saveContentString: (NSString *) newContent
147 baseVersion: (unsigned int) newBaseVersion
149 /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
155 [self setContentString: newContent];
156 folder = [container ocsFolder];
159 ex = [folder writeContent: content toName: nameInContainer
160 baseVersion: newBaseVersion];
162 [self errorWithFormat:@"write failed: %@", ex];
165 [self errorWithFormat:@"Did not find folder of content object."];
170 - (NSException *) saveContentString: (NSString *) newContent
172 return [self saveContentString: newContent baseVersion: 0];
175 - (NSException *) delete
177 /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
181 // TODO: add precondition check? (or add DELETEAction?)
183 if ((folder = [self ocsFolder]) == nil) {
184 [self errorWithFormat:@"Did not find folder of content object."];
188 if ((ex = [folder deleteContentWithName:[self nameInContainer]])) {
189 [self errorWithFormat:@"delete failed: %@", ex];
197 // - (id) lookupName:
199 // SoSelectorInvocation *invocation;
202 // name = [NSString stringWithFormat: @"%@:", [_key davMethodToObjC]];
204 // invocation = [[SoSelectorInvocation alloc]
205 // initWithSelectorNamed: name
206 // addContextParameter: YES];
207 // [invocation autorelease];
209 // return invocation;
213 - (id) PUTAction: (WOContext *) _ctx
217 unsigned int baseVersion;
221 if ((error = [self matchesRequestConditionInContext:_ctx]) != nil)
226 /* check whether its a request to the 'special' 'new' location */
228 Note: this is kinda hack. The OGo ZideStore detects writes to 'new' as
229 object creations and will assign a server side identifier. Most
230 current GroupDAV clients rely on this behaviour, so we reproduce it
232 A correct client would loop until it has a name which doesn't not
233 yet exist (by using if-none-match).
236 tmp = [[self nameInContainer] stringByDeletingPathExtension];
237 if ([tmp isEqualToString:@"new"]) {
238 tmp = [self globallyUniqueObjectId];
241 [self debugWithFormat:
242 @"reassigned a new location for special new-location: %@", tmp];
244 /* kinda dangerous */
245 ASSIGNCOPY(nameInContainer, tmp);
246 ASSIGN(ocsPath, nil);
249 /* determine base version from etag in if-match header */
251 Note: The -matchesRequestConditionInContext: already checks whether the
252 etag matches and returns an HTTP exception in case it doesn't.
253 We retrieve the etag again here to _ensure_ a transactionally save
255 (between the check and the update a change could have been done)
257 tmp = [rq headerForKey:@"if-match"];
258 tmp = [self parseETagList:tmp];
260 if ([tmp count] > 0) {
261 if ([tmp count] > 1) {
263 Note: we would have to attempt a save for _each_ of the etags being
264 passed in! In practice most WebDAV clients submit exactly one
267 [self warnWithFormat:
268 @"Got multiple if-match etags from client, only attempting to "
269 @"save with the first: %@", tmp];
272 etag = [tmp objectAtIndex:0];
274 baseVersion = ([etag length] > 0)
275 ? [etag unsignedIntValue]
276 : 0 /* 0 means 'do not check' */;
280 if ((error = [self saveContentString: [rq contentAsString]
281 baseVersion: baseVersion]) != nil)
286 // TODO: this should be automatic in the SoDispatcher if we return nil?
287 [[_ctx response] setStatus: 201 /* Created */];
289 if ((etag = [self davEntityTag]) != nil)
290 [[_ctx response] setHeader:etag forKey:@"etag"];
293 [[_ctx response] setHeader:[self baseURLInContext:_ctx]
297 return [_ctx response];
304 // TODO: cache tag in ivar? => if you do, remember to flush after PUT
308 NSNumber *versionValue;
310 folder = [self ocsFolder];
313 versionValue = [folder versionOfContentWithName: [self nameInContainer]];
314 sprintf (buf, "\"gcs%08d\"", [versionValue unsignedIntValue]);
315 entityTag = [NSString stringWithCString: buf];
319 [self errorWithFormat:@"Did not find folder of content object."];
328 - (NSException *) davMoveToTargetObject: (id) _target
329 newName: (NSString *) _name
333 Note: even for new objects we won't get a new name but a preinstantiated
334 object representing the new one.
337 @"TODO: move not implemented:\n target: %@\n new name: %@",
339 return [NSException exceptionWithHTTPStatus:405 /* not allowed */
340 reason:@"this object cannot be copied via WebDAV"];
343 - (NSException *) davCopyToTargetObject: (id)_target
344 newName: (NSString *) _name
348 Note: even for new objects we won't get a new name but a preinstantiated
349 object representing the new one.
352 @"TODO: copy not implemented:\n target: %@\n new name: %@",
354 return [NSException exceptionWithHTTPStatus:405 /* not allowed */
355 reason:@"this object cannot be copied via WebDAV"];
358 - (BOOL)davIsCollection {
359 return [self isFolderish];
364 - (NSArray *) aclUsers
366 return [container aclUsersForObjectAtPath: [self pathArrayToSOGoObject]];
369 - (NSArray *) aclsForUser: (NSString *) uid
371 NSMutableArray *acls;
372 NSArray *ownAcls, *containerAcls;
374 acls = [NSMutableArray array];
375 ownAcls = [container aclsForUser: uid
376 forObjectAtPath: [self pathArrayToSOGoObject]];
377 [acls addObjectsFromArray: ownAcls];
378 containerAcls = [container aclsForUser: uid];
379 if ([containerAcls count] > 0)
381 if ([containerAcls containsObject: SOGoRole_ObjectCreator])
383 [acls addObject: SOGoRole_ObjectCreator];
385 [acls addObject: SOGoRole_ObjectEditor];
387 if ([containerAcls containsObject: SOGoRole_ObjectReader])
388 [acls addObject: SOGoRole_ObjectViewer];
389 if ([containerAcls containsObject: SOGoRole_ObjectEditor])
390 [acls addObject: SOGoRole_ObjectEditor];
396 - (void) setRoles: (NSArray *) roles
397 forUser: (NSString *) uid
399 return [container setRoles: roles
401 forObjectAtPath: [self pathArrayToSoObject]];
404 - (void) removeAclsForUsers: (NSArray *) users
406 return [container removeAclsForUsers: users
407 forObjectAtPath: [self pathArrayToSoObject]];
410 - (NSString *) defaultUserID
417 - (NSString *) outlookMessageClass
424 - (void) appendAttributesToDescription: (NSMutableString *) _ms
426 [super appendAttributesToDescription:_ms];
428 [_ms appendFormat:@" ocs=%@", [self ocsPath]];
431 @end /* SOGoContentObject */