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