/*
- 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"
#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>
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 {
/* 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;
}
: 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;
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:
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"
}
- (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 {
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) {
e = [sm validatePermission:SoPerm_AccessContentsInformation
onObject:self->object
inContext:_ctx];
- if (e) return e;
+ if (e != nil) return e;
/* perform search */
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 */
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];
{
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];
}
}
: SoPerm_ChangeImagesAndFiles
onObject:self->object
inContext:_ctx];
- if (e) return e;
+ if (e != nil) return e;
/* check for conflicts */
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 !"];
}
davSetProperties:setProps
removePropertiesNamed:delProps
inContext:_ctx];
- if (e) return e;
+ if (e != nil) return e;
}
else {
/* create an object */
e = [sm validatePermission:SoPerm_WebDAVLockItems
onObject:self->object
inContext:_ctx];
- if (e) return e;
+ if (e != nil) return e;
/* check lock manager */
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
e = [sm validatePermission:SoPerm_WebDAVUnlockItems
onObject:self->object
inContext:_ctx];
- if (e) return e;
+ if (e != nil) return e;
/* check lock manager */
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]] ||
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."];
}
[self debugWithFormat:@"SOURCE: %@", self->object];
- [self debugWithFormat:@"TARGET: %@ (PI %@)", *target_, [_ctx pathInfo]];
+ [self debugWithFormat:@"TARGET: %@ (pathinfo %@)",
+ *target_, [_ctx pathInfo]];
return nil;
}
e = [sm validatePermission:SoPerm_AccessContentsInformation
onObject:self->object
inContext:_ctx];
- if (e) return e;
+ if (e != nil) return e;
/* perform search */
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 !"];
if (debugBulkTarget) [self logWithFormat:@"path: %@", target];
}
else {
- [self logWithFormat:@"ERROR: could not parse BPROPFIND target '%@' !",
- target];
+ [self errorWithFormat:@"could not parse BPROPFIND target '%@' !",
+ target];
}
}
e = [sm validatePermission:SoPerm_AccessContentsInformation
onObject:self->object
inContext:_ctx];
- if (e) return e;
+ if (e != nil) return e;
/* perform search */
/* 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"
content:nil
userInfo:nil];
localContext =
- [[[WOContext alloc] initWithRequest:localRequest] autorelease];
+ [[[WOContext alloc] initWithRequest:localRequest] autorelease];
[localRequest autorelease];
/* resetup fetchspec */
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];
}
}
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 {
e = [sm validatePermission:SoPerm_WebDAVAccess
onObject:self->object
inContext:_ctx];
- if (e) return e;
+ if (e != nil) return e;
/* perform search */