From 1621d6c888f624bbf21fb420a7f201b780e29a40 Mon Sep 17 00:00:00 2001 From: helge Date: Wed, 13 Jul 2005 13:18:29 +0000 Subject: [PATCH] added etag support git-svn-id: http://svn.opengroupware.org/SOGo/trunk@736 d1b88da0-ebda-0310-925b-ed51d893ca5b --- SOGo/SoObjects/SOGo/ChangeLog | 8 ++ SOGo/SoObjects/SOGo/SOGoContentObject.h | 5 + SOGo/SoObjects/SOGo/SOGoContentObject.m | 157 +++++++++++++++++++++++- SOGo/SoObjects/SOGo/SOGoObject.m | 15 ++- SOGo/SoObjects/SOGo/Version | 2 +- 5 files changed, 177 insertions(+), 10 deletions(-) diff --git a/SOGo/SoObjects/SOGo/ChangeLog b/SOGo/SoObjects/SOGo/ChangeLog index 4635c188..27c77a68 100644 --- a/SOGo/SoObjects/SOGo/ChangeLog +++ b/SOGo/SoObjects/SOGo/ChangeLog @@ -1,5 +1,13 @@ 2005-07-13 Helge Hess + * v0.9.52 + + * SOGoObject.m: properly add etag during a GET (if available) + + * SOGoContentObject.m: generate etag from content object version, added + methods to check request preconditions, check preconditions prior + running a PUT, added new etag after running a PUT + * SOGoObject.m, SOGoFolder.m: added +version methods to detect fragile base class issues (v0.9.51) diff --git a/SOGo/SoObjects/SOGo/SOGoContentObject.h b/SOGo/SoObjects/SOGo/SOGoContentObject.h index 77e4ddaf..5c2443d1 100644 --- a/SOGo/SoObjects/SOGo/SOGoContentObject.h +++ b/SOGo/SoObjects/SOGo/SOGoContentObject.h @@ -49,6 +49,11 @@ - (NSException *)saveContentString:(NSString *)_str; - (NSException *)delete; +/* etag support */ + +- (id)davEntityTag; +- (NSException *)matchesRequestConditionInContext:(id)_ctx; + /* message type */ - (NSString *)outlookMessageClass; diff --git a/SOGo/SoObjects/SOGo/SOGoContentObject.m b/SOGo/SoObjects/SOGo/SOGoContentObject.m index 0f958327..e942dc4f 100644 --- a/SOGo/SoObjects/SOGo/SOGoContentObject.m +++ b/SOGo/SoObjects/SOGo/SOGoContentObject.m @@ -20,11 +20,21 @@ */ #include "SOGoContentObject.h" +#include #include "common.h" #include @implementation SOGoContentObject +static BOOL kontactGroupDAV = YES; + ++ (void)initialize { + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + + kontactGroupDAV = + [ud boolForKey:@"SOGoDisableKontact34GroupDAVHack"] ? NO : YES; +} + - (void)dealloc { [self->content release]; [self->ocsPath release]; @@ -90,7 +100,7 @@ return self->content; if ((folder = [self ocsFolder]) == nil) { - [self logWithFormat:@"did not find folder of appointment."]; + [self errorWithFormat:@"Did not find folder of content object."]; return nil; } @@ -102,13 +112,13 @@ /* Note: "iCal multifolder saves" are implemented in the apt subclass! */ GCSFolder *folder; NSException *ex; - + if ((folder = [self ocsFolder]) == nil) { - [self logWithFormat:@"did not find folder of appointment."]; + [self errorWithFormat:@"Did not find folder of content object."]; return nil; } if ((ex = [folder writeContent:_str toName:[self nameInContainer]])) { - [self logWithFormat:@"write failed: %@", ex]; + [self errorWithFormat:@"write failed: %@", ex]; return ex; } return nil; @@ -119,13 +129,15 @@ GCSFolder *folder; NSException *ex; + // TODO: add precondition check? (or add DELETEAction?) + if ((folder = [self ocsFolder]) == nil) { - [self logWithFormat:@"did not find folder of appointment."]; + [self errorWithFormat:@"Did not find folder of content object."]; return nil; } if ((ex = [folder deleteContentWithName:[self nameInContainer]])) { - [self logWithFormat:@"delete failed: %@", ex]; + [self errorWithFormat:@"delete failed: %@", ex]; return ex; } return nil; @@ -136,6 +148,10 @@ - (id)PUTAction:(WOContext *)_ctx { WORequest *rq; NSException *error; + id etag; + + if ((error = [self matchesRequestConditionInContext:_ctx]) != nil) + return error; rq = [_ctx request]; if ((error = [self saveContentString:[rq contentAsString]]) != nil) @@ -143,9 +159,138 @@ // TODO: this should be automatic if we return nil? [[_ctx response] setStatus:201 /* Created */]; + + if ((etag = [self davEntityTag]) != nil) + [[_ctx response] setHeader:etag forKey:@"etag"]; return [_ctx response]; } +/* E-Tags */ + +- (id)davEntityTag { + // TODO: cache tag in ivar? => if you do, remember to flush after PUT + GCSFolder *folder; + + if ((folder = [self ocsFolder]) == nil) { + [self errorWithFormat:@"Did not find folder of content object."]; + return nil; + } + + return [folder versionOfContentWithName:[self nameInContainer]]; +} + +- (NSArray *)parseETagList:(NSString *)_c { + NSMutableArray *ma; + NSArray *etags; + unsigned i, count; + + if ([_c length] == 0) + return nil; + if ([_c isEqualToString:@"*"]) + return nil; + + etags = [_c componentsSeparatedByString:@","]; + count = [etags count]; + ma = [NSMutableArray arrayWithCapacity:count]; + for (i = 0; i < count; i++) { + NSString *etag; + + etag = [[etags objectAtIndex:i] stringByTrimmingSpaces]; + if ([etag hasPrefix:@"\""] && [etag hasSuffix:@"\""]) + etag = [etag substringWithRange:NSMakeRange(1, [etag length] - 2)]; + + if (etag != nil) [ma addObject:etag]; + } + return ma; +} + +- (NSException *)checkIfMatchCondition:(NSString *)_c inContext:(id)_ctx { + /* only run the request if one of the etags matches the resource etag */ + NSArray *etags; + NSString *etag; + + if ([_c isEqualToString:@"*"]) + /* to ensure that the resource exists! */ + return nil; + + if ((etags = [self parseETagList:_c]) == nil) + return nil; + if ([etags count] == 0) /* no etags to check for? */ + return nil; + + etag = [self davEntityTag]; + if ([etag length] == 0) /* has no etag, ignore */ + return nil; + + if ([etags containsObject:etag]) { + [self debugWithFormat:@"etag '%@' matches: %@", etag, + [etags componentsJoinedByString:@","]]; + return nil; /* one etag matches, so continue with request */ + } + + /* hack for Kontact 3.4 */ + + if (kontactGroupDAV) { + WEClientCapabilities *cc; + + cc = [[_ctx request] clientCapabilities]; + if ([[cc userAgentType] isEqualToString:@"Konqueror"]) { + if ([cc majorVersion] == 3 && [cc minorVersion] == 4) { + [self logWithFormat: + @"WARNING: applying Kontact 3.4 GroupDAV hack" + @" - etag check is disabled!" + @" (can be enabled using 'ZSDisableKontact34GroupDAVHack')"]; + return nil; + } + } + } + + // TODO: we might want to return the davEntityTag in the response + [self debugWithFormat:@"etag '%@' does not match: %@", etag, + [etags componentsJoinedByString:@","]]; + return [NSException exceptionWithHTTPStatus:412 /* Precondition Failed */ + reason:@"Precondition Failed"]; +} + +- (NSException *)checkIfNoneMatchCondition:(NSString *)_c inContext:(id)_ctx { + /* + If one of the etags is still the same, we can ignore the request. + + Can be used for PUT to ensure that the object does not exist in the store + and for GET to retrieve the content only if if the etag changed. + */ +#if 0 + if ([_c isEqualToString:@"*"]) + return nil; + + if ((a = [self parseETagList:_c]) == nil) + return nil; +#else + [self logWithFormat:@"TODO: implement if-none-match for etag: '%@'", _c]; +#endif + return nil; +} + +- (NSException *)matchesRequestConditionInContext:(id)_ctx { + NSException *error; + WORequest *rq; + NSString *c; + + if ((rq = [(WOContext *)_ctx request]) == nil) + return nil; /* be tolerant - no request, no condition */ + + if ((c = [rq headerForKey:@"if-match"]) != nil) { + if ((error = [self checkIfMatchCondition:c inContext:_ctx]) != nil) + return error; + } + if ((c = [rq headerForKey:@"if-none-match"]) != nil) { + if ((error = [self checkIfNoneMatchCondition:c inContext:_ctx]) != nil) + return error; + } + + return nil; +} + /* WebDAV */ - (BOOL)davIsCollection { diff --git a/SOGo/SoObjects/SOGo/SOGoObject.m b/SOGo/SoObjects/SOGo/SOGoObject.m index c597beb2..77fbf6a5 100644 --- a/SOGo/SoObjects/SOGo/SOGoObject.m +++ b/SOGo/SoObjects/SOGo/SOGoObject.m @@ -21,6 +21,7 @@ #include "SOGoObject.h" #include "SOGoUserFolder.h" +#include #include "common.h" @interface SOGoObject(Content) @@ -154,10 +155,19 @@ WOResponse *r; NSString *uri; + r = [(WOContext *)_ctx response]; rq = [(WOContext *)_ctx request]; + if ([rq isSoWebDAVRequest]) { - if ([self respondsToSelector:@selector(contentAsString)]) - return [self contentAsString]; + if ([self respondsToSelector:@selector(contentAsString)]) { + id etag; + + [r appendContentString:[self contentAsString]]; + + if ((etag = [self davEntityTag]) != nil) + [r setHeader:etag forKey:@"etag"]; + return r; + } return [NSException exceptionWithHTTPStatus:501 /* not implemented */ reason:@"no WebDAV GET support?!"]; @@ -167,7 +177,6 @@ if (![uri hasSuffix:@"/"]) uri = [uri stringByAppendingString:@"/"]; uri = [uri stringByAppendingString:@"view"]; - r = [(WOContext *)_ctx response]; [r setStatus:302 /* moved */]; [r setHeader:uri forKey:@"location"]; return r; diff --git a/SOGo/SoObjects/SOGo/Version b/SOGo/SoObjects/SOGo/Version index fae7b092..1a25ad08 100644 --- a/SOGo/SoObjects/SOGo/Version +++ b/SOGo/SoObjects/SOGo/Version @@ -1,6 +1,6 @@ # version file -SUBMINOR_VERSION:=51 +SUBMINOR_VERSION:=52 # v0.9.50 requires libGDLContentStore v4.5.30 # v0.9.34 requires libGDLContentStore v4.5.26 -- 2.39.5