#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 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;
- if ((methodObject =
- [self->object lookupName:@"GET" inContext:_ctx acquire:NO]) == nil)
+ 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];
: SoPerm_ChangeImagesAndFiles
onObject:self->object
inContext:_ctx];
- if (e) return e;
+ if (e != nil) return e;
/* check for conflicts */
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 */
e = [sm validatePermission:SoPerm_WebDAVUnlockItems
onObject:self->object
inContext:_ctx];
- if (e) return e;
+ if (e != nil) return e;
/* check lock manager */
e = [sm validatePermission:SoPerm_AccessContentsInformation
onObject:self->object
inContext:_ctx];
- if (e) return e;
+ if (e != nil) return e;
/* perform search */
e = [sm validatePermission:SoPerm_AccessContentsInformation
onObject:self->object
inContext:_ctx];
- if (e) return e;
+ if (e != nil) return e;
/* perform search */
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 */