]> err.no Git - sope/blob - skyrix-sope/NGObjWeb/SoObjects/SoObject+Traversal.m
added svn:keywords and svn:ignore where appropriate. removed CVS artifacts.
[sope] / skyrix-sope / NGObjWeb / SoObjects / SoObject+Traversal.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21 // $Id$
22
23 #include "SoObjectRequestHandler.h"
24 #include "SoObject.h"
25 #include "SoSecurityManager.h"
26 #include "WOContext+SoObjects.h"
27 #include <NGObjWeb/WORequest.h>
28 #include "common.h"
29
30 /*
31   The implementation for HTTP path traversion, just uses lookupKey
32   of SoObject.
33
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 ?).
37   
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)
42 */
43
44 @implementation NSObject(SoObjectLookup)
45
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;
51   }
52   return debugTraversal;
53 }
54
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];
58 }
59
60 /* path traversion */
61
62 - (id)handleValidationError:(NSException *)_error 
63   duringTraveralOfKey:(NSString *)_key
64   inContext:(id)_ctx 
65 {
66   if (_isDebugOn()) {
67     [self debugWithFormat:@"traversal validation error for key '%@':", _key];
68     [self debugWithFormat:@"  error:  %@", [_error name]];
69     [self debugWithFormat:@"  reason: %@", [_error reason]];
70   }
71   return nil;
72 }
73
74 - (id)traverseKey:(NSString *)_name
75   inContext:(id)_ctx
76   error:(NSException **)_error
77   acquire:(BOOL)_acquire
78 {
79   SoSecurityManager *sm;
80   id obj;
81   
82   if (_isDebugOn()) {
83     [self debugWithFormat:@"traverse key '%@' (acquire=%s) ..",
84             _name, _acquire ? "yes" : "no"];
85   }
86   
87   /* check access right */
88   
89   if ((*_error = [self validateName:_name inContext:_ctx])) {
90     /* not allowed ! */
91     if (debugTraversal)
92       [self debugWithFormat:@"  key '%@' did not validate !", _name];
93     return nil;
94   }
95   
96   /* lookup in object (and acquire from strict parents) */
97   
98   sm = [_ctx soSecurityManager];
99   if ((obj = [self traverseKey:_name inContext:_ctx])) {
100     *_error = [sm validateValue:obj forName:_name ofObject:self inContext:_ctx];
101     if (*_error) {
102       /* not allowed ! */
103       if (debugTraversal)
104         [self debugWithFormat:@"  value of key '%@' did not validate !",_name];
105       return nil;
106     }
107     
108     if (debugTraversal)
109       [self debugWithFormat:@"  key '%@' resolved: %@", _name, obj];
110     return obj;
111   }
112   
113   if (_acquire) {
114     /* now try to acquire from parents in URL path */
115     NSEnumerator *e;
116
117     if (debugTraversal) {
118       [self debugWithFormat:@"  try to acquire key '%@' from traversal stack",
119               _name];
120     }
121     
122     e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
123     while ((obj = [e nextObject])) {
124       NSException *e;
125       
126       if ((e = [obj validateName:_name inContext:_ctx])) {
127         /* access restriction */
128         *_error = e;
129         return nil;
130       }
131       
132       if ((obj = [obj traverseKey:_name inContext:_ctx])) {
133         /* found .. */
134         e = [sm validateValue:obj forName:_name 
135                 ofObject:self inContext:_ctx];
136         if (e) {
137           *_error = e;
138           return nil;
139         }
140         return obj;
141       }
142     }
143   }
144   else {
145     if (debugTraversal)
146       [self debugWithFormat:@"  acquisition disabled."];
147   }
148   
149   /* did not find object ... */
150   if (_isDebugOn())
151     [self debugWithFormat:@"  lookup of key '%@' failed.", _name];
152   return nil;
153 }
154
155 - (id)traversePathArray:(NSArray *)traversalPath
156   inContext:(id)_ctx
157   error:(NSException **)_error
158   acquire:(BOOL)_acquire
159 {
160   register BOOL doDebug = _isDebugOn();
161   WORequest *rq;
162   BOOL      isCreateIfMissingMethod = NO;
163   BOOL      isCreateMethod = NO;
164   unsigned  i, count;
165   id        root, currentObject, clientObject;
166   
167   if (doDebug) {
168     [self logWithFormat:@"traverse%s: %@",
169             _acquire ? "(acquire)" : "",
170             [traversalPath componentsJoinedByString:@" => "]];
171   }
172   
173   /* reset error */
174   if (_error) *_error = nil;
175   
176   if ((rq = [(id <WOPageGenerationContext>)_ctx request])) {
177     /* isn't that somewhat hackish, directly accessing the HTTP method? */
178     NSString *m;
179     
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"] 
193                               boolValue];
194       }
195     }
196   }
197   
198   root          = self;
199   currentObject = self;
200   clientObject  = nil;
201   [_ctx addObjectToTraversalStack:currentObject];
202   
203   /* do traversion */
204   
205   for (i = 0, count = [traversalPath count]; i < count; i++) {
206     NSException *error = nil;
207     NSString    *name;
208     id          nextObject = nil;
209     
210     /* get next name */
211     name = [traversalPath objectAtIndex:i];
212     if (doDebug) [self logWithFormat:@"  do traverse name: '%@'", name];
213     if ([name length] == 0)
214       /* empty name ?, ignore */
215       continue;
216     
217     if ([name isEqualToString:@"/"])
218       /* ignore root */
219       continue;
220     
221     if (i == (count - 1)) {
222       /* 
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 ...
226       */
227       // TODO: should check whether the resource exists, but access is denied
228       if (isCreateMethod) {
229         [_ctx setPathInfo:name];
230         if (doDebug)
231           [self logWithFormat:@"create-method: PATH_INFO: %@", name];
232         break;
233       }
234     }
235     
236     /* lookup next object */
237     
238     nextObject = [currentObject traverseKey:name inContext:_ctx error:&error
239                                 acquire:_acquire];
240     if (nextObject == nil) {
241       if (doDebug) {
242         [self logWithFormat:@"  traverse miss: name=%@%s: i=%i,count=%i", 
243                 name, _acquire ? ", acquire" : "", i, count];
244       }
245       if (i == (count - 1)) {
246         /* 
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.
251         */
252         // should check whether the resource exists, but access is denied
253         if (isCreateIfMissingMethod) {
254           [_ctx setPathInfo:name];
255           if (doDebug)
256             [self logWithFormat:@"create-if-missing: PATH_INFO: %@", name];
257           break;
258         }
259         if (doDebug) [self logWithFormat:@"    miss is last object."];
260       }
261       
262       if (error) {
263         if (doDebug) [self logWithFormat:@"    handle miss error: %@", error];
264         currentObject = [currentObject handleValidationError:error 
265                                        duringTraveralOfKey:name
266                                        inContext:_ctx];
267         if (currentObject == nil) {
268           if (_error) 
269             *_error = error;
270           else
271             currentObject = error;
272           break;
273         }
274         if (doDebug) [self logWithFormat:@"    miss error continues ..."];
275       }
276       if (doDebug) [self logWithFormat:@"    got no error for miss."];
277     }
278     
279     /* check whether the current object is executable */
280     /*
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!
285     */
286     
287     if (nextObject == nil && [currentObject isCallable]) {
288       NSArray *piArray;
289       NSRange r;
290       
291       r.location = i;
292       r.length   = (count - i);
293       piArray = [traversalPath subarrayWithRange:r];
294       if (doDebug) [self logWithFormat:@"PATH_INFO: %@", piArray];
295       [_ctx setPathInfo:[piArray componentsJoinedByString:@"/"]];
296       break;
297     }
298     else if (nextObject == nil && doDebug) {
299       [self logWithFormat:
300               @"Note: next object is nil, but currentObject "
301               @"is not callable: %@",
302               currentObject];
303     }
304     
305     /* found an object */
306     currentObject = nextObject;
307     [_ctx addObjectToTraversalStack:currentObject];
308   }
309   
310   /* fill clientObject */
311   
312   if ([currentObject isCallable]) {
313     unsigned count;
314     NSArray  *tstack;
315     
316     tstack = [_ctx objectTraversalStack];
317     count  = [tstack count];
318     if (count > 2)
319       clientObject = [tstack objectAtIndex:(count - 2)];
320   }
321   else
322     clientObject = currentObject;
323   
324   if (clientObject) {
325     if (doDebug)
326       [self logWithFormat:@"set clientObject: %@", clientObject];
327     [_ctx setClientObject:clientObject];
328   }
329   
330   /* return result */
331   return currentObject;
332 }
333
334 - (id)traversePathArray:(NSArray *)_tp acquire:(BOOL)_acquire {
335   NSAutoreleasePool *pool;
336   NSException *error = nil;
337   WOContext   *context;
338   id          result;
339   
340   pool = [[NSAutoreleasePool alloc] init];
341   {
342     // TODO: shouldn't we use a "subcontext"?
343     context = [WOContext context];
344   
345     result = [self traversePathArray:_tp
346                    inContext:context
347                    error:&error
348                    acquire:_acquire];
349     result = error ? [error retain] : [result retain];
350   }
351   [pool release];
352
353   return [result autorelease];
354 }
355
356 - (id)traversePath:(id)_tp acquire:(BOOL)_acquire {
357   if (![_tp isNotNull]) return nil;
358   
359   if ([_tp isKindOfClass:[NSArray class]])
360     return [self traversePathArray:_tp acquire:_acquire];
361   
362   if ([_tp isKindOfClass:[NSString class]])
363     return [self traversePathArray:[_tp pathComponents] acquire:_acquire];
364   
365   if ([_tp respondsToSelector:@selector(objectEnumerator)]) {
366     _tp = [[[NSArray alloc] initWithObjectsFromEnumerator:_tp] autorelease];
367     return [self traversePathArray:_tp acquire:_acquire];
368   }
369   
370   [self logWithFormat:
371           @"ERROR(%s): don't know how to turn path object %@ into an array",
372           __PRETTY_FUNCTION__, _tp];
373   return nil;
374 }
375
376 @end /* NSObject(SoObjectLookup) */