]> err.no Git - sope/blobdiff - sope-appserver/NGObjWeb/WebDAV/SoObjectWebDAVDispatcher.m
reverted gstep-base workaround (fixed in gstep-base svn trunk), bumped framework...
[sope] / sope-appserver / NGObjWeb / WebDAV / SoObjectWebDAVDispatcher.m
index ab731f5c01d8115f9def920d0db5575bf69cc23e..82f813d35ab3e41d64698622ef18e2c7cef78fa4 100644 (file)
@@ -1,24 +1,23 @@
 /*
-  Copyright (C) 2002-2004 SKYRIX Software AG
+  Copyright (C) 2002-2005 SKYRIX Software AG
 
-  This file is part of OpenGroupware.org.
+  This file is part of SOPE.
 
-  OGo is free software; you can redistribute it and/or modify it under
+  SOPE is free software; you can redistribute it and/or modify it under
   the terms of the GNU Lesser General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.
 
-  OGo is distributed in the hope that it will be useful, but WITHOUT ANY
+  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
   License for more details.
 
   You should have received a copy of the GNU Lesser General Public
-  License along with OGo; see the file COPYING.  If not, write to the
+  License along with SOPE; see the file COPYING.  If not, write to the
   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
   02111-1307, USA.
 */
-// $Id$
 
 #include "SoObjectWebDAVDispatcher.h"
 #include "SoObject.h"
@@ -31,6 +30,8 @@
 #include "SoDAVLockManager.h"
 #include "EOFetchSpecification+SoDAV.h"
 #include "WOContext+SoObjects.h"
+#include "NSException+HTTP.h"
+#include <NGObjWeb/WOApplication.h>
 #include <NGObjWeb/WORequest.h>
 #include <NGObjWeb/WOResponse.h>
 #include <NGObjWeb/WOContext.h>
@@ -105,55 +106,12 @@ static NSTimeZone                *gmt      = nil;
                      userInfo:ui];
 }
 
-- (NSArray *)allowedMethods {
-  static NSArray *defMethods = nil;
-  NSMutableArray *allow;
-
-  if (defMethods == nil) {
-    defMethods = [[[NSUserDefaults standardUserDefaults] 
-                                   arrayForKey:@"SoWebDAVDefaultAllowMethods"] 
-                                   copy];
-  }
-  
-  allow = [NSMutableArray arrayWithCapacity:16];
-  if (defMethods) [allow addObjectsFromArray:defMethods];
-  
-  if ([self->object 
-           respondsToSelector:@selector(performWebDAVQuery:inContext:)]) {
-    [allow addObject:@"PROPFIND"];
-    [allow addObject:@"SEARCH"];
-  }
-  if ([self->object respondsToSelector:
-            @selector(davSetProperties:removePropertiesNamed:)])
-    [allow addObject:@"PROPPATCH"];
-  
-  return allow;
-}
-
 - (NSString *)baseURLForContext:(WOContext *)_ctx {
-  /*
-    Note: Evolution doesn't correctly transfer the "Host:" header, it
-    misses the port argument :-(
-  */
-  NSString  *baseURL;
-  WORequest *rq;
-  NSString  *hostport;
-  id tmp;
+  extern NSString *SoObjectRootURLInContext(WOContext *_ctx, id logobj, BOOL withAppPart);
+  NSString *rootURL;
   
-  rq = [_ctx request];
-  
-  if ((tmp = [rq headerForKey:@"x-webobjects-server-name"])) {
-    hostport = tmp;
-    if ((tmp = [rq headerForKey:@"x-webobjects-server-port"]))
-      hostport = [NSString stringWithFormat:@"%@:%@", hostport, tmp];
-  }
-  else if ((tmp = [rq headerForKey:@"host"]))
-    hostport = tmp;
-  else
-    hostport = [[NSHost currentHost] name];
-  
-  baseURL = [NSString stringWithFormat:@"http://%@%@", hostport, [rq uri]];
-  return baseURL;
+  rootURL = SoObjectRootURLInContext(_ctx, self, NO);
+  return [rootURL stringByAppendingString:[[_ctx request] uri]];
 }
 
 - (id)primaryCallWebDAVMethod:(NSString *)_name inContext:(WOContext *)_ctx {
@@ -175,16 +133,43 @@ static NSTimeZone                *gmt      = nil;
 
 /* core HTTP methods */
 
-- (id)doGET:(WOContext *)_ctx {
+- (id)_callObjectMethod:(NSString *)_method inContext:(WOContext *)_ctx {
+  /* returns 'nil' if the object had no such method */
   NSException *e;
   id methodObject;
+  id result;
   
+  methodObject =
+    [self->object lookupName:_method inContext:_ctx acquire:NO];
+  if (![methodObject isNotNull])
+    return nil;
+  if ([methodObject isKindOfClass:[NSException class]]) {
+    if ([(NSException *)methodObject httpStatus] == 404 /* Not Found */) {
+      /* not found */
+      return nil;
+    }
+    return methodObject; /* the exception */
+  }
+  if ((e = [self->object validateName:_method inContext:_ctx]) != nil)
+    return e;
   
-  if ((methodObject = 
-       [self->object lookupName:@"GET" inContext:_ctx acquire:NO]) == nil)
+  if ([methodObject respondsToSelector:
+                     @selector(takeValuesFromRequest:inContext:)])
+    [methodObject takeValuesFromRequest:[_ctx request] inContext:_ctx];
+  
+  result = [methodObject callOnObject:self->object inContext:_ctx];
+  return (result != nil) ? result : [NSNull null];
+}
+
+- (id)doGET:(WOContext *)_ctx {
+  NSException *e;
+  id methodObject;
+  
+  methodObject = [self->object lookupName:@"GET" inContext:_ctx acquire:NO];
+  if (methodObject == nil)
     methodObject = [self->object lookupDefaultMethod];
   else {
-    if ((e = [self->object validateName:@"GET" inContext:_ctx]))
+    if ((e = [self->object validateName:@"GET" inContext:_ctx]) != nil)
       return e;
   }
   
@@ -217,7 +202,7 @@ static NSTimeZone                *gmt      = nil;
             : SoPerm_ChangeImagesAndFiles
           onObject:self->object
           inContext:_ctx];
-  if (e) return e;
+  if (e != nil) return e;
   
   if ((e = [self->object validateName:@"PUT" inContext:_ctx]))
     return e;
@@ -226,6 +211,7 @@ static NSTimeZone                *gmt      = nil;
   
   if ([pathInfo length] > 0) {
     /* check whether all the parent collections are available */
+    // TODO: we might also want to check for a 'create' permission
     if ([pathInfo rangeOfString:@"/"].length > 0) {
       return [self httpException:409 /* Conflict */
                   reason:
@@ -256,10 +242,11 @@ static NSTimeZone                *gmt      = nil;
   e  = [sm validatePermission:SoPerm_DeleteObjects
           onObject:self->object
           inContext:_ctx];
-  if (e) return e;
-  if ((e = [self->object validateName:@"DELETE" inContext:_ctx]))
+  if (e != nil) 
     return e;
-
+  if ((e = [self->object validateName:@"DELETE" inContext:_ctx]) != nil)
+    return e;
+  
   // TODO: IE WebFolders sent a "Destroy" header together with the
   //       DELETE request, eg:
   //       "Destroy: NoUndelete"
@@ -268,7 +255,35 @@ static NSTimeZone                *gmt      = nil;
 }
 
 - (id)doOPTIONS:(WOContext *)_ctx {
-  return [self allowedMethods];
+  WOResponse *response;
+  NSArray    *tmp;
+  id         result;
+  
+  /* this checks whether the object provides a specific OPTIONS method */
+  if ((result = [self _callObjectMethod:@"OPTIONS" inContext:_ctx]) != nil)
+    return result;
+  
+  response = [_ctx response];
+  [response setStatus:200 /* OK */];
+  
+  if ((tmp = [self->object davAllowedMethodsInContext:_ctx]) != nil) 
+    [response setHeader:[tmp componentsJoinedByString:@", "] forKey:@"allow"];
+  
+  if ([[[_ctx request] clientCapabilities] isWebFolder]) {
+    /*
+       As described over here:
+         http://teyc.editthispage.com/2005/06/02
+       
+       This page also says that: "MS-Auth-Via header is not required to work
+       with Web Folders".
+    */
+    [response setHeader:[tmp componentsJoinedByString:@", "] forKey:@"public"];
+  }
+  
+  if ((tmp = [self->object davComplianceClassesInContext:_ctx]) != nil) 
+    [response setHeader:[tmp componentsJoinedByString:@", "] forKey:@"dav"];
+  
+  return response;
 }
 
 - (id)doHEAD:(WOContext *)_ctx {
@@ -301,7 +316,7 @@ static NSTimeZone                *gmt      = nil;
   e  = [sm validatePermission:SoPerm_AddFolders 
           onObject:self->object
           inContext:_ctx];
-  if (e) return e;
+  if (e != nil) return e;
 
   /* check whether all the parent collections are available */
   if ([pathInfo rangeOfString:@"/"].length > 0) {
@@ -401,7 +416,7 @@ static NSTimeZone                *gmt      = nil;
   e  = [sm validatePermission:SoPerm_AccessContentsInformation 
           onObject:self->object
           inContext:_ctx];
-  if (e) return e;
+  if (e != nil) return e;
   
   /* perform search */
   
@@ -417,15 +432,29 @@ static NSTimeZone                *gmt      = nil;
   
   if ([depth length] == 0) depth = @"infinity";
   
-  [self lockParser:davsax];
-  {
-    [xmlParser parseFromSource:[rq content]];
-    propNames = [[davsax propFindQueriedNames] copy];
-    findAll   = [davsax  propFindAllProperties];
-    findNames = [davsax  propFindPropertyNames];
+  if ([[rq content] length] > 0) {
+    [self lockParser:davsax];
+    {
+      [xmlParser parseFromSource:[rq content]];
+      propNames = [[davsax propFindQueriedNames] copy];
+      findAll   = [davsax  propFindAllProperties];
+      findNames = [davsax  propFindPropertyNames];
+    }
+    [self unlockParser:davsax];
+    propNames = [propNames autorelease];
+  }
+  else {
+    /*
+      8.1 PROPFIND
+      "A client may choose not to submit a request body.  An empty PROPFIND 
+       request body MUST be treated as a request for the names and values of
+       all properties."
+      TODO: means, an empty request is to be treated as allprop?
+    */
+    propNames = nil;
+    findAll   = YES;
+    findNames = NO;
   }
-  [self unlockParser:davsax];
-  propNames = [propNames autorelease];
   
   /* check query all properties */
   
@@ -472,7 +501,7 @@ static NSTimeZone                *gmt      = nil;
     
     hints = [self hintsWithScope:[self scopeForDepth:depth inContext:_ctx]
                  propNames:propNames findAll:findAll namesOnly:findNames];
-    if (rtargets) /* range-query keys */
+    if (rtargets != nil) /* range-query keys */
       [hints setObject:rtargets forKey:@"bulkTargetKeys"];
     
     fs = [EOFetchSpecification alloc];
@@ -491,10 +520,11 @@ static NSTimeZone                *gmt      = nil;
   {
     NSDictionary *map;
     
-    if ((map = [self->object davAttributeMapInContext:_ctx])) {
+    if ((map = [self->object davAttributeMapInContext:_ctx]) != nil) {
       [_ctx setObject:map forKey:@"DAVPropertyMap"];
       fs = [fs fetchSpecificationByApplyingKeyMap:map];
       [_ctx setObject:fs  forKey:@"DAVMappedFetchSpecification"];
+      if (debugOn) [self logWithFormat:@"    remapped fetchspec: %@", fs];
     }
   }
   
@@ -546,7 +576,7 @@ static NSTimeZone                *gmt      = nil;
             : SoPerm_ChangeImagesAndFiles
           onObject:self->object
           inContext:_ctx];
-  if (e) return e;
+  if (e != nil) return e;
   
   /* check for conflicts */
 
@@ -595,7 +625,7 @@ static NSTimeZone                *gmt      = nil;
   setProps = [setProps autorelease];
   
   if (delProps == nil && setProps == nil) {
-    [self logWithFormat:@"WARNING: got no properties in PROPPATCH !"];
+    [self warnWithFormat:@"got no properties in PROPPATCH !"];
     return [self httpException:400 /* bad request */
                 reason:@"got no properties in PROPPATCH !"];
   }
@@ -674,7 +704,7 @@ static NSTimeZone                *gmt      = nil;
             davSetProperties:setProps
             removePropertiesNamed:delProps
             inContext:_ctx];
-    if (e) return e;
+    if (e != nil) return e;
   }
   else {
     /* create an object */
@@ -709,7 +739,7 @@ static NSTimeZone                *gmt      = nil;
   e  = [sm validatePermission:SoPerm_WebDAVLockItems
           onObject:self->object
           inContext:_ctx];
-  if (e) return e;
+  if (e != nil) return e;
   
   /* check lock manager */
   
@@ -725,13 +755,11 @@ static NSTimeZone                *gmt      = nil;
   ifValue   = [rq headerForKey:@"if"];
   
   if (lockDepth != nil && ![lockDepth isEqualToString:@"0"]) {
-    [self logWithFormat:
-           @"WARNING: 'depth' locking not supported yet (depth=%@)!", 
-           lockDepth];
+    [self warnWithFormat:@"'depth' locking not supported yet (depth=%@)!", 
+            lockDepth];
   }
   if (ifValue) {
-    [self logWithFormat:
-           @"WARNING: 'if' locking not supported yet, if: '%@'", ifValue];
+    [self warnWithFormat:@"'if' locking not supported yet, if: '%@'", ifValue];
   }
   
   // need to parse lockinfo
@@ -763,7 +791,7 @@ static NSTimeZone                *gmt      = nil;
   e  = [sm validatePermission:SoPerm_WebDAVUnlockItems
           onObject:self->object
           inContext:_ctx];
-  if (e) return e;
+  if (e != nil) return e;
   
   /* check lock manager */
   
@@ -809,8 +837,9 @@ static NSTimeZone                *gmt      = nil;
   
   srvURL = [_ctx serverURL];
   
-  [self debugWithFormat:@"move/copy:\n  to:    %@\n  server: %@)", 
-         [destURL absoluteString], [srvURL absoluteString]];
+  [self debugWithFormat:@"move/copy:\n  to:    %@ (%@)\n  server: %@)", 
+         [destURL absoluteString], absDestURL,
+         [srvURL absoluteString]];
   
   /* check whether URL is on the same server ... */
   if (![[srvURL host] isEqualToString:[destURL host]] ||
@@ -876,14 +905,16 @@ static NSTimeZone                *gmt      = nil;
                   inContext:_ctx
                   error:&error
                   acquire:NO];
-  if (error) {
+  if ([*target_ isKindOfClass:[NSException class]])
+    error = *target_;
+  if (error != nil) {
     [self logWithFormat:@"could not resolve destination object (%@): %@",
            [targetPath componentsJoinedByString:@" => "],
            error];
     return error;
   }
-
-  if (name_) *name_ = [[[_ctx pathInfo] copy] autorelease];
+  
+  if (name_ != NULL) *name_ = [[[_ctx pathInfo] copy] autorelease];
   
   if (*target_ == nil) {
     [self debugWithFormat:@"MOVE/COPY destination could not be found."];
@@ -892,7 +923,8 @@ static NSTimeZone                *gmt      = nil;
   }
   
   [self debugWithFormat:@"SOURCE: %@", self->object];
-  [self debugWithFormat:@"TARGET: %@ (PI %@)", *target_, [_ctx pathInfo]];
+  [self debugWithFormat:@"TARGET: %@ (pathinfo %@)", 
+       *target_, [_ctx pathInfo]];
   return nil;
 }
 
@@ -971,7 +1003,7 @@ static NSTimeZone                *gmt      = nil;
   e  = [sm validatePermission:SoPerm_AccessContentsInformation 
           onObject:self->object
           inContext:_ctx];
-  if (e) return e;
+  if (e != nil) return e;
 
   /* perform search */
   
@@ -1117,7 +1149,7 @@ static NSTimeZone                *gmt      = nil;
     NSURL *url;
     
     if ((url = [NSURL URLWithString:[callback stringValue]]) == nil) {
-      [self debugWithFormat:@"ERROR: could not parse callback URL '%@'", 
+      [self errorWithFormat:@"could not parse callback URL '%@'", 
               callback];
       return [self httpException:400 /* Bad Request */
                    reason:@"missing valid callback URL !"];
@@ -1211,8 +1243,8 @@ static NSTimeZone                *gmt      = nil;
        if (debugBulkTarget) [self logWithFormat:@"path: %@", target];
       }
       else {
-       [self logWithFormat:@"ERROR: could not parse BPROPFIND target '%@' !",
-               target];
+        [self errorWithFormat:@"could not parse BPROPFIND target '%@' !",
+                target];
       }
     }
     
@@ -1267,7 +1299,7 @@ static NSTimeZone                *gmt      = nil;
   e  = [sm validatePermission:SoPerm_AccessContentsInformation 
           onObject:self->object
           inContext:_ctx];
-  if (e) return e;
+  if (e != nil) return e;
 
   /* perform search */
   
@@ -1365,14 +1397,13 @@ static NSTimeZone                *gmt      = nil;
       /* setup the "subrequest" */
       
       if ([targetURL isAbsoluteURL]) {
-       NSURL *url;
-       
-       if ((url = [NSURL URLWithString:targetURL]))
-         targetURL = [url path];
-       else {
-         [self logWithFormat:@"ERROR: could not parse target-url '%@'",
-                 targetURL];
-       }
+        NSURL *url;
+        
+        if ((url = [NSURL URLWithString:targetURL]))
+          targetURL = [url path];
+        else {
+          [self errorWithFormat:@"could not parse target-url '%@'", targetURL];
+        }
       }
       
       localRequest = [[WORequest alloc] initWithMethod:@"PROPFIND"
@@ -1382,7 +1413,7 @@ static NSTimeZone                *gmt      = nil;
                                        content:nil
                                        userInfo:nil];
       localContext = 
-       [[[WOContext alloc] initWithRequest:localRequest] autorelease];
+        [[[WOContext alloc] initWithRequest:localRequest] autorelease];
       [localRequest autorelease];
       
       /* resetup fetchspec */
@@ -1397,32 +1428,32 @@ static NSTimeZone                *gmt      = nil;
                                   error:&e
                                   acquire:NO];
       if (targetObject == nil) {
-       [self logWithFormat:@"did not find BPROPFIND target: %@", targetURL];
-       [self logWithFormat:@"  root:   %@", [_ctx traversalRoot]];
-       [self logWithFormat:@"  path:   %@", 
+        [self logWithFormat:@"did not find BPROPFIND target: %@", targetURL];
+        [self logWithFormat:@"  root:   %@", [_ctx traversalRoot]];
+        [self logWithFormat:@"  path:   %@", 
                [[localRequest requestHandlerPathArray] 
                               componentsJoinedByString:@"/"]];
-       [self logWithFormat:@"  error:  %@", e];
-       targetResult = e;
+        [self logWithFormat:@"  error:  %@", e];
+        targetResult = e;
       }
       else {
-       /* perform query */
+        /* perform query */
        
-       targetResult = [targetObject performWebDAVQuery:fs 
-                                    inContext:localContext];
-       if (targetResult == nil) {
-         targetResult = 
-           [self httpException:500 /* Server Error */
-                 reason:@"could not perform query (object returned nil)"];
-       }
+        targetResult = [targetObject performWebDAVQuery:fs 
+                                              inContext:localContext];
+        if (targetResult == nil) {
+          targetResult = 
+          [self httpException:500 /* Server Error */
+                reason:@"could not perform query (object returned nil)"];
+        }
       }
       
       // do we need to distinguish the queries somehow ? (href generation)
       if ([targetResult isKindOfClass:[NSArray class]])
-       [result addObjectsFromArray:targetResult];
+        [result addObjectsFromArray:targetResult];
       else if (targetResult)
-       [result addObject:targetResult];
-      
+        [result addObject:targetResult];
+
       [pool release];
     }
   }
@@ -1451,6 +1482,43 @@ static NSTimeZone                *gmt      = nil;
               reason:@"WebDAV operation not yet implemented."];
 }
 
+/* DAV reports */
+
+- (id)doREPORT:(WOContext *)_ctx {
+  WORequest *rq;
+  id domDocument;
+  
+  rq = [_ctx request];
+  
+  /* ensure XML */
+
+  if (![[rq headerForKey:@"content-type"] hasPrefix:@"text/xml"]) {
+    return [self httpException:400 /* invalid request */
+                reason:@"XML entity expected for WebDAV REPORT."];
+  }
+
+  /* retrieve XML */
+
+  if ((domDocument = [rq contentAsDOMDocument]) == nil) {
+    return [self httpException:400 /* invalid request */
+                reason:@"Could not parse XML of WebDAV REPORT."];
+  }
+  
+  /* process DOM */
+
+  [self logWithFormat:@"TODO: process REPORT: %@", domDocument];
+  
+  return [self httpException:405 /* method not allowed */
+              reason:@"WebDAV reports not yet implemented."];
+}
+
+/* CalDAV */
+
+- (id)doMKCALENDAR:(WOContext *)_ctx {
+  return [self httpException:405 /* method not allowed */
+              reason:@"CalDAV calendar creation not yet implemented."];
+}
+
 /* DAV access control lists */
 
 - (id)doACL:(WOContext *)_ctx {
@@ -1517,7 +1585,7 @@ static NSTimeZone                *gmt      = nil;
   e  = [sm validatePermission:SoPerm_WebDAVAccess
           onObject:self->object
           inContext:_ctx];
-  if (e) return e;
+  if (e != nil) return e;
   
   /* perform search */