]> err.no Git - sope/blob - skyrix-sope/NGObjWeb/SoObjects/SoObjectRequestHandler.m
added svn:keywords and svn:ignore where appropriate. removed CVS artifacts.
[sope] / skyrix-sope / NGObjWeb / SoObjects / SoObjectRequestHandler.m
1 /*
2   Copyright (C) 2002-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 "SoObjectMethodDispatcher.h"
26 #include "SoSecurityManager.h"
27 #include "SoDefaultRenderer.h"
28 #include "WOContext+SoObjects.h"
29 #include "WORequest+So.h"
30 #include <NGObjWeb/WOApplication.h>
31 #include <NGObjWeb/WORequest.h>
32 #include <NGObjWeb/WOResponse.h>
33 #include <NGObjWeb/WOElement.h>
34 #include <NGObjWeb/WOTemplate.h>
35 #include <NGObjWeb/WEClientCapabilities.h>
36 #include <SoOFS/OFSBaseObject.h>
37 #include <NGExtensions/NGRuleContext.h>
38 #include "WOComponent+private.h"
39 #include "common.h"
40
41 @interface NSObject(ObjectDispatcher)
42 - (id)dispatcherForContext:(WOContext *)_ctx;
43 - (WOResponse *)preprocessCredentialsInContext:(WOContext *)_ctx;
44 - (void)_sleepWithContext:(WOContext *)_ctx;
45 @end
46
47 @implementation SoObjectRequestHandler
48
49 static BOOL debugOn       = NO;
50 static BOOL debugRulesOn  = NO;
51 static BOOL disableZLHack = NO;
52
53 static Class WOTemplateClass = Nil;
54 static NSString *rapidTurnAroundPath = nil;
55
56 + (int)version {
57   return [super version] + 0 /* 2 */;
58 }
59 + (void)initialize {
60   static BOOL didInit = NO;
61   if (!didInit) {
62     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
63     didInit = YES;
64     NSAssert2([super version] == 2,
65               @"invalid superclass (%@) version %i !",
66               NSStringFromClass([self superclass]), [super version]);
67     debugOn       = [ud boolForKey:@"SoObjectRequestHandlerDebugEnabled"];
68     debugRulesOn  = [ud boolForKey:@"SoObjectRequestHandlerRulesDebugEnabled"];
69     disableZLHack = [ud boolForKey:@"DisableZideLookHack"];
70
71     WOTemplateClass     = [WOTemplate class];
72     rapidTurnAroundPath = [[ud stringForKey:@"WOProjectDirectory"] copy];    
73   }
74 }
75
76 - (id)init {
77   if ((self = [super init])) {
78     self->dispatcherRules =
79       [[NGRuleContext ruleContextWithModelInUserDefault:
80                         @"SoRequestDispatcherRules"] retain];
81     if (debugRulesOn) [self->dispatcherRules setDebugEnabled:YES];
82   }
83   return self;
84 }
85 - (void)dealloc {
86   [self->dispatcherRules release];
87   [self->rootObject      release];
88   [super dealloc];
89 }
90
91 /* type the request */
92
93 - (BOOL)isObjectPublishingContext:(WOContext *)_ctx {
94   /* 
95      Find out, whether we should do acquisition and dynamic method publishing.
96      This is only appropriate for HEAD/GET/POST requests from non-WebDAV
97      clients ?
98   */
99   id value;
100   
101   value = [self->dispatcherRules valueForKey:@"useAcquisition"];
102   if (debugRulesOn) [self debugWithFormat:@"acquision: %@", value];
103   return [value boolValue];
104 }
105
106 /* request path acquisition */
107
108 - (BOOL)enableZideLookHack {
109   /* Temporary Hack for ZideLook */
110   return disableZLHack ? NO : YES;
111 }
112
113 - (BOOL)skipApplicationName {
114   /* is the application name path of a URI part of the traversal path ? */
115   return NO;
116 }
117
118 - (NSString *)hackZideLookURI:(NSString *)m {
119   if ([m hasPrefix:@"H_chste_Ebene_der_Pers_nlichen_Ordner"]) {
120     m = [m stringByReplacingString:@"H_chste_Ebene_der_Pers_nlichen_Ordner"
121            withString:@"helge"];
122   }
123   else if ([m hasPrefix:@"Suchpfad"]) {
124     m = [m stringByReplacingString:@"Suchpfad"
125            withString:@"helge"];
126   }
127   else if ([m hasPrefix:@"public"]) {
128     /* Evolution query on "/public" */
129     ; // keep it completly
130   }
131   else if ([self skipApplicationName]) {
132     /* cut of appname */
133     NSRange r;
134     r = [m rangeOfString:@"/"];
135     m = [m substringFromIndex:(r.location + r.length)];
136   }
137   return m;
138 }
139 - (NSString *)hackZideLookName:(NSString *)_p {
140   if ([_p isEqualToString:@"Gel_schte_Objekte"])
141     return @"Trash";
142   return _p;
143 }
144
145 - (NSMutableArray *)addSpecialFormValuesInRequest:(WORequest *)_rq
146   toTraversalPath:(NSMutableArray *)_traversalPath
147 {
148   NSArray  *keys;
149   unsigned i, count;
150   
151   keys = [_rq formValueKeys];
152   if ((count = [keys count]) == 0)
153     return _traversalPath;
154   
155   for (i = 0; i < count; i++) {
156     NSString *key;
157     unsigned klen;
158     NSString *m;
159     
160     key  = [keys objectAtIndex:i];
161     klen = [key length];
162     if (klen != 3 && klen < 7)
163       continue;
164     
165     /* calculate method name */
166     
167     m = nil;
168     if (klen == 3 && [key isEqualToString:@"Cmd"]) {
169       /* 
170          check for ASP style ?Cmd query parameter (required in ZideStore),
171          the value is the additional path we need to add
172       */
173       m = [_rq formValueForKey:key]; // need to unescape somehow ?
174     }
175     else if (klen == 7 && [key isEqualToString:@":method"]) {
176       /* 
177          check for ":method" form value, the value is the additional path we 
178          need to add
179       */
180       m = [_rq formValueForKey:key]; // need to unescape somehow ?
181     }
182     else if ([key hasSuffix:@":method"]) {
183       /*
184         Check for XXX:method form-keys, the value is ignored and the
185         XXX is added to the path. This is useful for binding actions
186         to submit buttons, since the value of a submit button is 
187         displayed as it's label in the browser
188       */
189       klen = klen - 7;
190       m = [key substringToIndex:klen];
191     }
192     
193     /* check calculated method */
194     
195     if (m == nil)
196       continue;
197     else if ([m length] == 0) {
198       [self debugWithFormat:@"empty '%@' query parameter !", key];
199       continue;
200     }
201     
202     /* add to path */
203     [_traversalPath addObject:m];
204   }
205   return _traversalPath;
206 }
207
208 - (NSArray *)traversalPathFromRequest:(WORequest *)_rq {
209   static NSArray *rqKeys = nil;
210   NSMutableArray *traversalPath;
211   unsigned i, count;
212   NSString *m;
213   NSArray  *a;
214
215   if (rqKeys == nil)
216     /* cache set of registered request handlers */
217     rqKeys = [[[WOApplication application] registeredRequestHandlerKeys] copy];
218   
219   m = [_rq requestHandlerKey];
220   if ([rqKeys containsObject:m]) {
221     /* 
222        If the request-handler-key parsed by WORequest is valid, we'll consider
223        it a "usual" NGObjWeb query. Note that the appname is *not* processed !
224        Example:
225          /MyApp/wo/...   => match
226          /MyApp/bla/...  => fail
227          /blah/wa/...    => match
228     */
229     a = [_rq requestHandlerPathArray];
230   }
231   else {
232     /* TODO: more options, eg allow the appname to be part of the path */
233     NSRange r;
234
235     /* get URI, cut of leading slash */
236     m = [_rq uri];
237     m = [m substringFromIndex:1];
238     
239     if ([self enableZideLookHack]) 
240       m = [self hackZideLookURI:m];
241     else if ([self skipApplicationName]) {
242       /* cut of application name */
243       r = [m rangeOfString:@"/"];
244       m = [m substringFromIndex:(r.location + r.length)];
245     }
246     
247     /* cut of query parameters */
248     r = [m rangeOfString:@"?"];
249     if (r.length > 0)
250       m = [m substringToIndex:r.location];
251     
252     /* split into path components */
253     a = [m componentsSeparatedByString:@"/"];
254   }
255   
256   count = [a count];
257   traversalPath = [NSMutableArray arrayWithCapacity:(count + 1)];
258   for (i = 0; i < count; i++) {
259     NSString *p;
260     
261     p = [a objectAtIndex:i];
262     
263     if ([p hasPrefix:@"_range"])
264       /* a ZideLook range query, further handled by WebDAV dispatcher */
265       continue;
266     
267     p = [p stringByUnescapingURL];
268     
269     if ([self enableZideLookHack])
270       p = [self hackZideLookName:p];
271     
272     if ([p length] > 0)
273       [traversalPath addObject:p];
274   }
275   
276   traversalPath = [self addSpecialFormValuesInRequest:_rq
277                         toTraversalPath:traversalPath];
278   
279   return traversalPath;
280 }
281
282 - (id)rootObjectForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
283   id object;
284   
285   if (self->rootObject)
286     return self->rootObject;
287     
288   if ((object = [_ctx application]) == nil)
289     object = [WOApplication application];
290   
291 #if 0
292   /* 
293     if we resolve in this location, we won't be able to resolve
294     application names like "Control_Panel".
295   */
296   if ([object respondsToSelector:@selector(rootObjectInContext:)])
297     object = [object rootObjectInContext:_ctx];
298 #endif
299   
300   return object;
301 }
302
303 - (id)lookupObjectForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
304   NSAutoreleasePool *pool;
305   NSArray     *traversalPath;
306   id currentObject;
307   
308   pool = [[NSAutoreleasePool alloc] init];
309   {
310     NSException *error = nil;
311     id   root;
312     BOOL doAcquire;
313     
314     /* build traversal path */
315     
316     traversalPath = [self traversalPathFromRequest:_rq];
317     if (traversalPath)
318       [_ctx setSoRequestTraversalPath:traversalPath];
319     
320     /* setup root object */
321     
322     root = [self rootObjectForRequest:_rq inContext:_ctx];
323     
324     doAcquire = self->doesNoRequestPathAcquisition
325       ? NO
326       : [self isObjectPublishingContext:_ctx];
327
328     [self debugWithFormat:@"traverse (%@): %@ %@", 
329             [_rq uri], 
330             [traversalPath componentsJoinedByString:@" => "],
331             doAcquire ? @"[acquires]" : @"(no acquisition)"];
332     
333     currentObject = [root traversePathArray:traversalPath
334                           inContext:_ctx
335                           error:&error
336                           acquire:doAcquire];
337     if (error)
338       currentObject = error;
339     
340     /* retain result */
341     currentObject = [currentObject retain];
342   }
343   [pool release];
344   return [currentObject autorelease];
345 }
346
347 /* object invocation */
348
349 - (id)dispatcherForObject:(id)_object inContext:(WOContext *)_ctx {
350   NSString *dpClass, *rqType;
351   id dispatcher = nil;
352   
353   /* ask object for dispatcher */
354   
355   if ([_object respondsToSelector:@selector(dispatcherForContext:)]) {
356     if ((dispatcher = [_object dispatcherForContext:_ctx]))
357       return dispatcher;
358   }
359   
360   if (debugRulesOn) {
361     [self debugWithFormat:@"select dispatcher using rules: %@", 
362             self->dispatcherRules];
363   }
364   
365   /* query */
366   dpClass = [self->dispatcherRules valueForKey:@"dispatcher"];
367   rqType  = [self->dispatcherRules valueForKey:@"requestType"];
368   if (debugRulesOn) {
369     [self debugWithFormat:@"  selected dispatcher: %@", dpClass];
370     [self debugWithFormat:@"  selected rq-type:    %@", rqType];
371   }
372   
373   /* create dispatcher */
374   
375   if (rqType != nil) [_ctx setSoRequestType:rqType];
376   if ((dispatcher = NSClassFromString(dpClass)) == nil) {
377     [self logWithFormat:@"ERROR: did not find dispatcher class '%@'", dpClass];
378     return nil;
379   }
380   
381   if ((dispatcher = [[dispatcher alloc] initWithObject:_object]))
382     [_ctx setObjectDispatcher:dispatcher];
383   
384   return [dispatcher autorelease];
385 }
386
387 /* object rendering */
388
389 - (WOResponse *)renderObject:(id)_object inContext:(WOContext *)_ctx {
390   SoDefaultRenderer *renderer;
391   NSException  *error;
392   NSEnumerator *e;
393   id container;
394   
395   [self debugWithFormat:@"    render in ctx: %@", _ctx];
396
397   if ([_object isKindOfClass:[WOResponse class]])
398     /* already rendered ... */
399     return _object;
400   
401   /* check whether a container on the traversal stack provides a renderer */
402   
403   renderer = nil;
404   e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
405   while ((container = [e nextObject])) {
406     if (![container respondsToSelector:
407                       @selector(rendererForObject:inContext:)]) {
408       /* does not provide a renderer factory ... */
409       continue;
410     }
411     
412     if ((renderer = [container rendererForObject:_object inContext:_ctx])) {
413       /* the container provided an own renderer for the object */
414       [self debugWithFormat:@"    use container renderer: %@", renderer];
415       break;
416     }
417   }
418   
419   /* if we didn't find a renderer, determine using rules */
420   
421   if (renderer == nil) {
422     NSString *rendererClass;
423     
424     rendererClass = [self->dispatcherRules valueForKey:@"renderer"];
425     if (rendererClass) {
426       Class clazz;
427       
428       if ((clazz = NSClassFromString(rendererClass)) == Nil) {
429         [self logWithFormat:@"did not find class of selected renderer %@", 
430                 rendererClass];
431       }
432       else if ((renderer = [clazz sharedRenderer]) == nil) {
433         [self logWithFormat:@"could not get renderer of class %@", 
434                 rendererClass];
435       }
436       else if (![renderer canRenderObject:_object inContext:_ctx]) {
437         [self debugWithFormat:@"renderer %@ rejected rendering of object %@", 
438                 renderer, _object];
439         renderer = [SoDefaultRenderer sharedRenderer];
440       }
441     }
442     
443     if (renderer)
444       [self debugWithFormat:@"    use rule-selected renderer: %@", renderer];
445   }
446   
447   if (renderer == nil)
448     [self debugWithFormat:@"    found no renderer for object: %@", _object];
449   
450   if ((error = [renderer renderObject:_object inContext:_ctx])) {
451     if (renderer != [SoDefaultRenderer sharedRenderer]) {
452       NSException *e2;
453       
454       e2 = [(SoDefaultRenderer *)[SoDefaultRenderer sharedRenderer] 
455                                  renderObject:error inContext:_ctx];
456       if (e2) {
457         [self logWithFormat:@"default renderer could not render error %@: %@",
458                 error, e2];
459         return nil;
460       }
461     }
462     else {
463       [self logWithFormat:@"default renderer returned error: %@", error];
464       return nil;
465     }
466   }
467   return [_ctx response];
468 }
469
470 - (BOOL)doesRejectFavicon {
471   return NO;
472 }
473
474 - (WOResponse *)handleRequest:(WORequest *)_rq
475   inContext:(WOContext *)_ctx
476   session:(WOSession *)_sn
477   application:(WOApplication *)app
478 {
479   /* split up this big method */
480   WOResponse *r;
481   id object;
482   id authenticator;
483   BOOL doDispatch;
484   
485   if (debugOn) {
486     [self debugWithFormat:@"request 0x%08X: %@ %@ (ctx=0x%08X)", _rq, 
487             [_rq method], [_rq uri], _ctx];
488     if (_sn) [self debugWithFormat:@"  session 0x%08X: %@", _sn, _sn];
489   }
490   
491   /* setup rule context */
492   
493   [self->dispatcherRules reset];
494   [self->dispatcherRules takeValue:_rq           forKey:@"request"];
495   [self->dispatcherRules takeValue:[_rq headers] forKey:@"headers"];
496   [self->dispatcherRules takeValue:[_rq method]  forKey:@"method"];
497   [self->dispatcherRules takeValue:_ctx          forKey:@"context"];
498   
499   /* preprocess authentication credentials with global auth handler */
500   
501   if ((authenticator = [app authenticatorInContext:_ctx])) {
502     [_ctx setObject:authenticator forKey:@"SoAuthenticator"];
503     
504     /* give authenticator the chance to reject invalid credentials */
505     
506     if ((r = [authenticator preprocessCredentialsInContext:_ctx])) {
507       [self->dispatcherRules reset];
508       return r;
509     }
510     
511     [self debugWithFormat:@"authenticator allowed request."];
512   }
513   else {
514     [self debugWithFormat:@"WARNING: no authenticator available."];
515   }
516   
517   /* lookup object */
518   
519   doDispatch = YES;
520   object = [self lookupObjectForRequest:_rq inContext:_ctx];
521   
522   if (object) {
523     [self->dispatcherRules 
524          takeValue:[_ctx clientObject] forKey:@"clientObject"];
525     [self->dispatcherRules takeValue:object forKey:@"object"];
526   }
527   else {
528     r = [_ctx response];
529     [r setStatus:404];
530     [r setHeader:@"text/html" forKey:@"content-type"];
531     [r appendContentString:@"object not found: "];
532     [r appendContentHTMLString:
533          [[_ctx soRequestTraversalPath] componentsJoinedByString:@" => "]];
534     doDispatch = NO;
535     object = r;
536   }
537   
538   /* dispatch object */
539   
540   if ([object isKindOfClass:[NSException class]]) {
541     /* exceptions are not called ... */
542     [self debugWithFormat:@"  not calling exception: %@", object];
543     doDispatch = NO;
544   }
545   
546   if (doDispatch) {
547     id dispatcher;
548     
549     dispatcher = [self dispatcherForObject:object inContext:_ctx];
550     [self debugWithFormat:@"  dispatcher: %@", dispatcher];
551     
552     [self debugWithFormat:@"  dispatch object: %@", object];
553     object = [dispatcher dispatchInContext:_ctx];
554
555     if (object) [self->dispatcherRules takeValue:object forKey:@"result"];
556   }
557   
558   /* render result */
559   
560   if (object == nil) {
561     [self debugWithFormat:@"  got an empty result !"];
562     r = [_ctx response];
563     [r setStatus:500];
564     [r appendContentString:@"the called object returned no result"];
565   }
566   else if ([object isKindOfClass:[WOResponse class]]) {
567     r = object;
568     [self debugWithFormat:
569             @"  got response: 0x%08X (status=%i,len=%@,type=%@)", 
570             r, [r status], 
571             [r headerForKey:@"content-length"],
572             [r headerForKey:@"content-type"]];
573   }
574   else {
575     if (debugOn) {
576       if ([object isKindOfClass:[NSData class]]) {
577         [self debugWithFormat:@"  render data 0x%08X[len=%i]",
578                 object, [object length]];
579       }
580       else
581         [self debugWithFormat:@"  render object: %@", object];
582     }
583     
584     [self->dispatcherRules takeValue:object forKey:@"result"];
585     r = [self renderObject:object inContext:_ctx];
586     
587     if (debugOn) {
588       [self debugWithFormat:
589             @"  made response: 0x%08X (status=%i,len=%@,type=%@)", 
590             r, [r status], 
591             [r headerForKey:@"content-length"],
592             [r headerForKey:@"content-type"]];
593     }
594   }
595   
596   /* add header with primary key of new objects (for ZideLook) */
597   if (r != nil) {
598     id key;
599     
600     if ((key = [_ctx objectForKey:@"SxNewObjectID"])) {
601       key = [NSString stringWithFormat:@"%@", key];
602       [r setHeader:key forKey:@"x-skyrix-newname"];
603       [self logWithFormat:@"added new key header to response: '%@'", key];
604     }
605   }
606
607   /* rapid turnaround */
608   if(rapidTurnAroundPath != nil) {
609     WOComponent *page;
610     NSString *_path = nil;
611
612     if((page = [_ctx page])) {
613       WOElement *template;
614           
615       template = [page _woComponentTemplate];
616       if([template isKindOfClass:WOTemplateClass])
617         _path = [[(WOTemplate *)template url] path];
618     }
619     else {
620       if([object isKindOfClass:[OFSBaseObject class]])
621         _path = [object storagePath];
622     }
623     if(_path != nil)
624       [r setHeader:_path forKey:@"x-sope-template-path"];
625   }
626   
627   /* sleep traversal stack */
628   {
629     NSEnumerator *e;
630     id obj;
631     
632     e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
633     while ((obj = [e nextObject])) {
634       if (![obj isNotNull])
635         continue;
636       
637       if ([obj respondsToSelector:@selector(_sleepWithContext:)])
638         [obj _sleepWithContext:_ctx];
639       else if ([obj respondsToSelector:@selector(sleep)])
640         [obj sleep];
641     }
642   }
643   
644   [self->dispatcherRules reset];
645   
646   return r;
647 }
648
649 @end /* SoObjectRequestHandler */
650
651 @implementation WOCoreApplication(RendererSelection)
652
653 - (id)rendererForObject:(id)_object inContext:(WOContext *)_ctx {
654   return nil;
655 }
656
657 @end /* WOCoreApplication(RendererSelection) */
658
659 @implementation SoObjectRequestHandler(Logging)
660
661 - (NSString *)loggingPrefix {
662   return @"[object-handler]";
663 }
664 - (BOOL)isDebuggingEnabled {
665   return debugOn ? YES : NO;
666 }
667
668 @end /* SoObjectRequestHandler(Logging) */