2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
23 #include "SoObjectRequestHandler.h"
25 #include "SoSecurityManager.h"
26 #include "WOContext+SoObjects.h"
27 #include <NGObjWeb/WORequest.h>
31 The implementation for HTTP path traversion, just uses lookupKey
34 The traverseKey:inContext: basically reflects Zope's __bobo_traverse__()
35 method. But __bobo_traverse__ can return a tuple with a set of objects
36 to be inserted in the traversion path (what is that good for ?).
38 Zope has an additional __before_publishing_traverse__() which is called
39 before traversion. This is used to change requests and supposed to be
40 useful for virtual hosting and special authentication controls. (need a
41 specific example why a special method is required for that)
44 @implementation NSObject(SoObjectLookup)
46 static int debugTraversal = -1;
47 static BOOL _isDebugOn(void) {
48 if (debugTraversal == -1) {
49 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
50 debugTraversal = [ud boolForKey:@"SoDebugObjectTraversal"] ? 1 : 0;
52 return debugTraversal;
55 - (id)traverseKey:(NSString *)_key inContext:(id)_ctx {
56 /* this corresponds to Zope's __bobo_traverse__() */
57 return [self lookupName:_key inContext:_ctx acquire:NO];
62 - (id)handleValidationError:(NSException *)_error
63 duringTraveralOfKey:(NSString *)_key
67 [self debugWithFormat:@"traversal validation error for key '%@':", _key];
68 [self debugWithFormat:@" error: %@", [_error name]];
69 [self debugWithFormat:@" reason: %@", [_error reason]];
74 - (id)traverseKey:(NSString *)_name
76 error:(NSException **)_error
77 acquire:(BOOL)_acquire
79 SoSecurityManager *sm;
83 [self debugWithFormat:@"traverse key '%@' (acquire=%s) ..",
84 _name, _acquire ? "yes" : "no"];
87 /* check access right */
89 if ((*_error = [self validateName:_name inContext:_ctx])) {
92 [self debugWithFormat:@" key '%@' did not validate !", _name];
96 /* lookup in object (and acquire from strict parents) */
98 sm = [_ctx soSecurityManager];
99 if ((obj = [self traverseKey:_name inContext:_ctx])) {
100 *_error = [sm validateValue:obj forName:_name ofObject:self inContext:_ctx];
104 [self debugWithFormat:@" value of key '%@' did not validate !",_name];
109 [self debugWithFormat:@" key '%@' resolved: %@", _name, obj];
114 /* now try to acquire from parents in URL path */
117 if (debugTraversal) {
118 [self debugWithFormat:@" try to acquire key '%@' from traversal stack",
122 e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
123 while ((obj = [e nextObject])) {
126 if ((e = [obj validateName:_name inContext:_ctx])) {
127 /* access restriction */
132 if ((obj = [obj traverseKey:_name inContext:_ctx])) {
134 e = [sm validateValue:obj forName:_name
135 ofObject:self inContext:_ctx];
146 [self debugWithFormat:@" acquisition disabled."];
149 /* did not find object ... */
151 [self debugWithFormat:@" lookup of key '%@' failed.", _name];
155 - (id)traversePathArray:(NSArray *)traversalPath
157 error:(NSException **)_error
158 acquire:(BOOL)_acquire
160 register BOOL doDebug = _isDebugOn();
162 BOOL isCreateIfMissingMethod = NO;
163 BOOL isCreateMethod = NO;
165 id root, currentObject, clientObject;
168 [self logWithFormat:@"traverse%s: %@",
169 _acquire ? "(acquire)" : "",
170 [traversalPath componentsJoinedByString:@" => "]];
174 if (_error) *_error = nil;
176 if ((rq = [(id <WOPageGenerationContext>)_ctx request])) {
177 /* isn't that somewhat hackish, directly accessing the HTTP method? */
180 if ((m = [rq method])) {
181 if ([m isEqualToString:@"PUT"])
182 isCreateIfMissingMethod = YES;
183 else if ([m isEqualToString:@"PROPPATCH"])
184 isCreateIfMissingMethod = YES;
185 else if ([m isEqualToString:@"MKCOL"])
186 /* this one is strictly creating */
187 isCreateMethod = YES;
188 // TODO: the following are only create-if-missing on the target!
189 else if ([m isEqualToString:@"MOVE"] ||
190 [m isEqualToString:@"COPY"]) {
191 isCreateIfMissingMethod =
192 [[(WOContext *)_ctx objectForKey:@"isDestinationPathLookup"]
199 currentObject = self;
201 [_ctx addObjectToTraversalStack:currentObject];
205 for (i = 0, count = [traversalPath count]; i < count; i++) {
206 NSException *error = nil;
211 name = [traversalPath objectAtIndex:i];
212 if (doDebug) [self logWithFormat:@" do traverse name: '%@'", name];
213 if ([name length] == 0)
214 /* empty name ?, ignore */
217 if ([name isEqualToString:@"/"])
221 if (i == (count - 1)) {
223 is last object, special handling for MKCOL, with MKCOL queries
224 the last part of the URI is a collection to be created and not
225 yet in the object tree ...
227 // TODO: should check whether the resource exists, but access is denied
228 if (isCreateMethod) {
229 [_ctx setPathInfo:name];
231 [self logWithFormat:@"create-method: PATH_INFO: %@", name];
236 /* lookup next object */
238 nextObject = [currentObject traverseKey:name inContext:_ctx error:&error
240 if (nextObject == nil) {
242 [self logWithFormat:@" traverse miss: name=%@%s: i=%i,count=%i",
243 name, _acquire ? ", acquire" : "", i, count];
245 if (i == (count - 1)) {
247 Is last object, special handling for PUT, with PUT queries
248 the last part of the URI is allowed to be missing. If this
249 is the case, the PUT is actually a "creation" operation.
250 The same goes for PROPPATCH.
252 // should check whether the resource exists, but access is denied
253 if (isCreateIfMissingMethod) {
254 [_ctx setPathInfo:name];
256 [self logWithFormat:@"create-if-missing: PATH_INFO: %@", name];
259 if (doDebug) [self logWithFormat:@" miss is last object."];
263 if (doDebug) [self logWithFormat:@" handle miss error: %@", error];
264 currentObject = [currentObject handleValidationError:error
265 duringTraveralOfKey:name
267 if (currentObject == nil) {
271 currentObject = error;
274 if (doDebug) [self logWithFormat:@" miss error continues ..."];
276 if (doDebug) [self logWithFormat:@" got no error for miss."];
279 /* check whether the current object is executable */
281 TODO: why did I add this check?, we cannot break on the first
282 executable, otherwise we cannot use methods on methods!
283 So: but we can break if the nextObject could not be found, so
284 that we can generate a proper pathinfo!
287 if (nextObject == nil && [currentObject isCallable]) {
292 r.length = (count - i);
293 piArray = [traversalPath subarrayWithRange:r];
294 if (doDebug) [self logWithFormat:@"PATH_INFO: %@", piArray];
295 [_ctx setPathInfo:[piArray componentsJoinedByString:@"/"]];
298 else if (nextObject == nil && doDebug) {
300 @"Note: next object is nil, but currentObject "
301 @"is not callable: %@",
305 /* found an object */
306 currentObject = nextObject;
307 [_ctx addObjectToTraversalStack:currentObject];
310 /* fill clientObject */
312 if ([currentObject isCallable]) {
316 tstack = [_ctx objectTraversalStack];
317 count = [tstack count];
319 clientObject = [tstack objectAtIndex:(count - 2)];
322 clientObject = currentObject;
326 [self logWithFormat:@"set clientObject: %@", clientObject];
327 [_ctx setClientObject:clientObject];
331 return currentObject;
334 - (id)traversePathArray:(NSArray *)_tp acquire:(BOOL)_acquire {
335 NSAutoreleasePool *pool;
336 NSException *error = nil;
340 pool = [[NSAutoreleasePool alloc] init];
342 // TODO: shouldn't we use a "subcontext"?
343 context = [WOContext context];
345 result = [self traversePathArray:_tp
349 result = error ? [error retain] : [result retain];
353 return [result autorelease];
356 - (id)traversePath:(id)_tp acquire:(BOOL)_acquire {
357 if (![_tp isNotNull]) return nil;
359 if ([_tp isKindOfClass:[NSArray class]])
360 return [self traversePathArray:_tp acquire:_acquire];
362 if ([_tp isKindOfClass:[NSString class]])
363 return [self traversePathArray:[_tp pathComponents] acquire:_acquire];
365 if ([_tp respondsToSelector:@selector(objectEnumerator)]) {
366 _tp = [[[NSArray alloc] initWithObjectsFromEnumerator:_tp] autorelease];
367 return [self traversePathArray:_tp acquire:_acquire];
371 @"ERROR(%s): don't know how to turn path object %@ into an array",
372 __PRETTY_FUNCTION__, _tp];
376 @end /* NSObject(SoObjectLookup) */