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>
24 #import <SOGo/SOGoUser.h>
27 #import "SOGoFolder.h"
28 #import "SOGoContentObject.h"
30 @interface SOGoContentObject(ETag)
31 - (NSArray *)parseETagList:(NSString *)_c;
34 @implementation SOGoContentObject
36 // TODO: check superclass version
39 [self->content release];
40 [self->ocsPath release];
47 [self->content release]; self->content = nil;
57 - (void)setOCSPath:(NSString *)_path {
58 if ([self->ocsPath isEqualToString:_path])
62 [self warnWithFormat:@"GCS path is already set! '%@'", _path];
64 ASSIGNCOPY(self->ocsPath, _path);
67 - (NSString *)ocsPath {
68 if (self->ocsPath == nil) {
71 if ((p = [self ocsPathOfContainer]) != nil) {
72 if (![p hasSuffix:@"/"]) p = [p stringByAppendingString:@"/"];
73 p = [p stringByAppendingString:[self nameInContainer]];
74 self->ocsPath = [p copy];
80 - (NSString *)ocsPathOfContainer {
81 if (![[self container] respondsToSelector:@selector(ocsPath)])
84 return [[self container] ocsPath];
87 - (GCSFolder *)ocsFolder {
88 if (![[self container] respondsToSelector:@selector(ocsFolder)])
91 return [[self container] ocsFolder];
96 - (NSString *)contentAsString {
99 if (self->content != nil)
100 return self->content;
102 if ((folder = [self ocsFolder]) == nil) {
103 [self errorWithFormat:@"Did not find folder of content object."];
107 self->content = [[folder fetchContentWithName:[self nameInContainer]] copy];
108 return self->content;
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(self->nameInContainer, tmp);
188 ASSIGN(self->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];
243 - (NSArray *) rolesOfUser: (NSString *) login
244 inContext: (WOContext *) context
246 NSMutableArray *sogoRoles;
249 sogoRoles = [NSMutableArray new];
250 [sogoRoles autorelease];
252 if (![container nameExistsInFolder: nameInContainer])
254 user = [[SOGoUser alloc] initWithLogin: login roles: nil];
255 [sogoRoles addObjectsFromArray: [user rolesForObject: container
256 inContext: context]];
266 // TODO: cache tag in ivar? => if you do, remember to flush after PUT
270 if ((folder = [self ocsFolder]) == nil) {
271 [self errorWithFormat:@"Did not find folder of content object."];
275 sprintf(buf, "\"gcs%08d\"",
276 [[folder versionOfContentWithName:[self nameInContainer]]
278 return [NSString stringWithCString:buf];
283 - (NSException *)davMoveToTargetObject:(id)_target newName:(NSString *)_name
287 Note: even for new objects we won't get a new name but a preinstantiated
288 object representing the new one.
291 @"TODO: move not implemented:\n target: %@\n new name: %@",
293 return [NSException exceptionWithHTTPStatus:405 /* not allowed */
294 reason:@"this object cannot be copied via WebDAV"];
297 - (NSException *)davCopyToTargetObject:(id)_target newName:(NSString *)_name
301 Note: even for new objects we won't get a new name but a preinstantiated
302 object representing the new one.
305 @"TODO: copy not implemented:\n target: %@\n new name: %@",
307 return [NSException exceptionWithHTTPStatus:405 /* not allowed */
308 reason:@"this object cannot be copied via WebDAV"];
311 - (BOOL)davIsCollection {
312 return [self isFolderish];
317 - (NSString *)outlookMessageClass {
323 - (void)appendAttributesToDescription:(NSMutableString *)_ms {
324 [super appendAttributesToDescription:_ms];
326 [_ms appendFormat:@" ocs=%@", [self ocsPath]];
329 @end /* SOGoContentObject */