]> err.no Git - scalable-opengroupware.org/commitdiff
added etag support
authorhelge <helge@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Wed, 13 Jul 2005 13:18:29 +0000 (13:18 +0000)
committerhelge <helge@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Wed, 13 Jul 2005 13:18:29 +0000 (13:18 +0000)
git-svn-id: http://svn.opengroupware.org/SOGo/trunk@736 d1b88da0-ebda-0310-925b-ed51d893ca5b

SOGo/SoObjects/SOGo/ChangeLog
SOGo/SoObjects/SOGo/SOGoContentObject.h
SOGo/SoObjects/SOGo/SOGoContentObject.m
SOGo/SoObjects/SOGo/SOGoObject.m
SOGo/SoObjects/SOGo/Version

index 4635c188a0c2b402f7c911d5176b41b8bd2196d5..27c77a68586b2c459c1d25157b64e875104c04d8 100644 (file)
@@ -1,5 +1,13 @@
 2005-07-13  Helge Hess  <helge.hess@opengroupware.org>
 
+       * 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)
 
index 77e4ddaf04bb3e6449dec68e272914b77b788da3..5c2443d1e090cdb317e11a20f7fce56a6b1ee82f 100644 (file)
 - (NSException *)saveContentString:(NSString *)_str;
 - (NSException *)delete;
 
+/* etag support */
+
+- (id)davEntityTag;
+- (NSException *)matchesRequestConditionInContext:(id)_ctx;
+
 /* message type */
 
 - (NSString *)outlookMessageClass;
index 0f958327de2e44fe73b08922b17538e639cbaa8a..e942dc4facd911d08fe6bf9643bf37d4900b131e 100644 (file)
 */
 
 #include "SOGoContentObject.h"
+#include <NGObjWeb/WEClientCapabilities.h>
 #include "common.h"
 #include <GDLContentStore/GCSFolder.h>
 
 @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];
     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;
   }
   
   /* 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;
   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;
 - (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)
   
   // 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 {
index c597beb2af0c4ca3fa7eff486e3ffcd6aaac6ffe..77fbf6a586ca3a47e5461aa3d3f2fd8ab06a8239 100644 (file)
@@ -21,6 +21,7 @@
 
 #include "SOGoObject.h"
 #include "SOGoUserFolder.h"
+#include <NGObjWeb/SoObject+SoDAV.h>
 #include "common.h"
 
 @interface SOGoObject(Content)
   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?!"];
   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;
index fae7b092e7b52c6c3a471532eaf3c2fc5c07185e..1a25ad08848acb5a7596f41cb81648cd53f30fd7 100644 (file)
@@ -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