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
47 [content release]; content = nil;
57 - (void)setOCSPath:(NSString *)_path {
58 if ([ocsPath isEqualToString:_path])
62 [self warnWithFormat:@"GCS path is already set! '%@'", _path];
64 ASSIGNCOPY(ocsPath, _path);
67 - (NSString *) ocsPath
73 p = [self ocsPathOfContainer];
76 if (![p hasSuffix:@"/"])
77 p = [p stringByAppendingString: @"/"];
78 ocsPath = [p stringByAppendingString: [self nameInContainer]];
86 - (NSString *)ocsPathOfContainer {
87 if (![[self container] respondsToSelector:@selector(ocsPath)])
90 return [[self container] ocsPath];
93 - (GCSFolder *) ocsFolder
95 return [container ocsFolder];
100 - (NSString *) contentAsString
104 content = [[self ocsFolder] fetchContentWithName: nameInContainer];
111 - (NSException *) saveContentString: (NSString *) _str
112 baseVersion: (unsigned int) _baseVersion
114 /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
118 if ((folder = [self ocsFolder]) == nil) {
119 [self errorWithFormat:@"Did not find folder of content object."];
123 ex = [folder writeContent:_str toName:[self nameInContainer]
124 baseVersion:_baseVersion];
126 [self errorWithFormat:@"write failed: %@", ex];
131 - (NSException *)saveContentString:(NSString *)_str {
132 return [self saveContentString:_str baseVersion:0 /* don't check */];
135 - (NSException *)delete {
136 /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
140 // TODO: add precondition check? (or add DELETEAction?)
142 if ((folder = [self ocsFolder]) == nil) {
143 [self errorWithFormat:@"Did not find folder of content object."];
147 if ((ex = [folder deleteContentWithName:[self nameInContainer]])) {
148 [self errorWithFormat:@"delete failed: %@", ex];
156 - (id)PUTAction:(WOContext *)_ctx {
159 unsigned int baseVersion;
163 if ((error = [self matchesRequestConditionInContext:_ctx]) != nil)
168 /* check whether its a request to the 'special' 'new' location */
170 Note: this is kinda hack. The OGo ZideStore detects writes to 'new' as
171 object creations and will assign a server side identifier. Most
172 current GroupDAV clients rely on this behaviour, so we reproduce it
174 A correct client would loop until it has a name which doesn't not
175 yet exist (by using if-none-match).
178 tmp = [[self nameInContainer] stringByDeletingPathExtension];
179 if ([tmp isEqualToString:@"new"]) {
180 tmp = [[[self container] class] globallyUniqueObjectId];
183 [self debugWithFormat:
184 @"reassigned a new location for special new-location: %@", tmp];
186 /* kinda dangerous */
187 ASSIGNCOPY(nameInContainer, tmp);
188 ASSIGN(ocsPath, nil);
191 /* determine base version from etag in if-match header */
193 Note: The -matchesRequestConditionInContext: already checks whether the
194 etag matches and returns an HTTP exception in case it doesn't.
195 We retrieve the etag again here to _ensure_ a transactionally save
197 (between the check and the update a change could have been done)
199 tmp = [rq headerForKey:@"if-match"];
200 tmp = [self parseETagList:tmp];
202 if ([tmp count] > 0) {
203 if ([tmp count] > 1) {
205 Note: we would have to attempt a save for _each_ of the etags being
206 passed in! In practice most WebDAV clients submit exactly one
209 [self warnWithFormat:
210 @"Got multiple if-match etags from client, only attempting to "
211 @"save with the first: %@", tmp];
214 etag = [tmp objectAtIndex:0];
216 baseVersion = ([etag length] > 0)
217 ? [etag unsignedIntValue]
218 : 0 /* 0 means 'do not check' */;
222 if ((error = [self saveContentString:[rq contentAsString]
223 baseVersion:baseVersion]) != nil)
228 // TODO: this should be automatic in the SoDispatcher if we return nil?
229 [[_ctx response] setStatus:201 /* Created */];
231 if ((etag = [self davEntityTag]) != nil)
232 [[_ctx response] setHeader:etag forKey:@"etag"];
235 [[_ctx response] setHeader:[self baseURLInContext:_ctx]
239 return [_ctx response];
246 // TODO: cache tag in ivar? => if you do, remember to flush after PUT
250 NSNumber *versionValue;
252 folder = [self ocsFolder];
255 versionValue = [folder versionOfContentWithName: [self nameInContainer]];
256 sprintf (buf, "\"gcs%08d\"", [versionValue unsignedIntValue]);
257 entityTag = [NSString stringWithCString: buf];
261 [self errorWithFormat:@"Did not find folder of content object."];
270 - (NSException *) davMoveToTargetObject: (id) _target
271 newName: (NSString *) _name
275 Note: even for new objects we won't get a new name but a preinstantiated
276 object representing the new one.
279 @"TODO: move not implemented:\n target: %@\n new name: %@",
281 return [NSException exceptionWithHTTPStatus:405 /* not allowed */
282 reason:@"this object cannot be copied via WebDAV"];
285 - (NSException *) davCopyToTargetObject: (id)_target
286 newName: (NSString *) _name
290 Note: even for new objects we won't get a new name but a preinstantiated
291 object representing the new one.
294 @"TODO: copy not implemented:\n target: %@\n new name: %@",
296 return [NSException exceptionWithHTTPStatus:405 /* not allowed */
297 reason:@"this object cannot be copied via WebDAV"];
300 - (BOOL)davIsCollection {
301 return [self isFolderish];
308 return [container aclsForObjectAtPath: [self pathArrayToSoObject]];
311 - (NSArray *) aclsForUser: (NSString *) uid
313 NSMutableArray *acls;
314 NSArray *ownAcls, *containerAcls;
316 acls = [NSMutableArray array];
317 ownAcls = [container aclsForUser: uid
318 forObjectAtPath: [self pathArrayToSoObject]];
319 [acls addObjectsFromArray: ownAcls];
320 containerAcls = [container aclsForUser: uid];
321 if ([containerAcls count] > 0)
323 if ([containerAcls containsObject: SOGoRole_ObjectCreator])
324 [acls addObject: SOGoRole_ObjectCreator];
325 if ([containerAcls containsObject: SOGoRole_ObjectEraser])
326 [acls addObject: SOGoRole_ObjectEraser];
332 - (void) setRoles: (NSArray *) roles
333 forUser: (NSString *) uid
335 return [container setRoles: roles
337 forObjectAtPath: [self pathArrayToSoObject]];
340 - (void) removeAclsForUsers: (NSArray *) users
342 return [container removeAclsForUsers: users
343 forObjectAtPath: [self pathArrayToSoObject]];
348 - (NSString *) outlookMessageClass
355 - (void) appendAttributesToDescription: (NSMutableString *) _ms
357 [super appendAttributesToDescription:_ms];
359 [_ms appendFormat:@" ocs=%@", [self ocsPath]];
362 @end /* SOGoContentObject */