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 #include "SOGoContentObject.h"
23 #include <NGObjWeb/WEClientCapabilities.h>
25 #include <GDLContentStore/GCSFolder.h>
27 @implementation SOGoContentObject
29 static BOOL kontactGroupDAV = YES;
32 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
35 [ud boolForKey:@"SOGoDisableKontact34GroupDAVHack"] ? NO : YES;
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 /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
116 if ((folder = [self ocsFolder]) == nil) {
117 [self errorWithFormat:@"Did not find folder of content object."];
120 if ((ex = [folder writeContent:_str toName:[self nameInContainer]])) {
121 [self errorWithFormat:@"write failed: %@", ex];
127 - (NSException *)delete {
128 /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
132 // TODO: add precondition check? (or add DELETEAction?)
134 if ((folder = [self ocsFolder]) == nil) {
135 [self errorWithFormat:@"Did not find folder of content object."];
139 if ((ex = [folder deleteContentWithName:[self nameInContainer]])) {
140 [self errorWithFormat:@"delete failed: %@", ex];
148 - (id)PUTAction:(WOContext *)_ctx {
153 if ((error = [self matchesRequestConditionInContext:_ctx]) != nil)
157 if ((error = [self saveContentString:[rq contentAsString]]) != nil)
160 // TODO: this should be automatic if we return nil?
161 [[_ctx response] setStatus:201 /* Created */];
163 if ((etag = [self davEntityTag]) != nil)
164 [[_ctx response] setHeader:etag forKey:@"etag"];
165 return [_ctx response];
171 // TODO: cache tag in ivar? => if you do, remember to flush after PUT
174 if ((folder = [self ocsFolder]) == nil) {
175 [self errorWithFormat:@"Did not find folder of content object."];
179 return [folder versionOfContentWithName:[self nameInContainer]];
182 - (NSArray *)parseETagList:(NSString *)_c {
187 if ([_c length] == 0)
189 if ([_c isEqualToString:@"*"])
192 etags = [_c componentsSeparatedByString:@","];
193 count = [etags count];
194 ma = [NSMutableArray arrayWithCapacity:count];
195 for (i = 0; i < count; i++) {
198 etag = [[etags objectAtIndex:i] stringByTrimmingSpaces];
199 if ([etag hasPrefix:@"\""] && [etag hasSuffix:@"\""])
200 etag = [etag substringWithRange:NSMakeRange(1, [etag length] - 2)];
202 if (etag != nil) [ma addObject:etag];
207 - (NSException *)checkIfMatchCondition:(NSString *)_c inContext:(id)_ctx {
208 /* only run the request if one of the etags matches the resource etag */
212 if ([_c isEqualToString:@"*"])
213 /* to ensure that the resource exists! */
216 if ((etags = [self parseETagList:_c]) == nil)
218 if ([etags count] == 0) /* no etags to check for? */
221 etag = [self davEntityTag];
222 if ([etag length] == 0) /* has no etag, ignore */
225 if ([etags containsObject:etag]) {
226 [self debugWithFormat:@"etag '%@' matches: %@", etag,
227 [etags componentsJoinedByString:@","]];
228 return nil; /* one etag matches, so continue with request */
231 /* hack for Kontact 3.4 */
233 if (kontactGroupDAV) {
234 WEClientCapabilities *cc;
236 cc = [[_ctx request] clientCapabilities];
237 if ([[cc userAgentType] isEqualToString:@"Konqueror"]) {
238 if ([cc majorVersion] == 3 && [cc minorVersion] == 4) {
240 @"WARNING: applying Kontact 3.4 GroupDAV hack"
241 @" - etag check is disabled!"
242 @" (can be enabled using 'ZSDisableKontact34GroupDAVHack')"];
248 // TODO: we might want to return the davEntityTag in the response
249 [self debugWithFormat:@"etag '%@' does not match: %@", etag,
250 [etags componentsJoinedByString:@","]];
251 return [NSException exceptionWithHTTPStatus:412 /* Precondition Failed */
252 reason:@"Precondition Failed"];
255 - (NSException *)checkIfNoneMatchCondition:(NSString *)_c inContext:(id)_ctx {
257 If one of the etags is still the same, we can ignore the request.
259 Can be used for PUT to ensure that the object does not exist in the store
260 and for GET to retrieve the content only if if the etag changed.
263 if ([_c isEqualToString:@"*"])
266 if ((a = [self parseETagList:_c]) == nil)
269 [self logWithFormat:@"TODO: implement if-none-match for etag: '%@'", _c];
274 - (NSException *)matchesRequestConditionInContext:(id)_ctx {
279 if ((rq = [(WOContext *)_ctx request]) == nil)
280 return nil; /* be tolerant - no request, no condition */
282 if ((c = [rq headerForKey:@"if-match"]) != nil) {
283 if ((error = [self checkIfMatchCondition:c inContext:_ctx]) != nil)
286 if ((c = [rq headerForKey:@"if-none-match"]) != nil) {
287 if ((error = [self checkIfNoneMatchCondition:c inContext:_ctx]) != nil)
296 - (BOOL)davIsCollection {
297 return [self isFolderish];
302 - (NSString *)outlookMessageClass {
308 - (void)appendAttributesToDescription:(NSMutableString *)_ms {
309 [super appendAttributesToDescription:_ms];
311 [_ms appendFormat:@" ocs=%@", [self ocsPath]];
314 @end /* SOGoContentObject */