2 Copyright (C) 2002-2005 SKYRIX Software AG
4 This file is part of SOPE.
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
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.
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
22 #include "SoObjectRequestHandler.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"
40 @interface NSObject(ObjectDispatcher)
41 - (id)dispatcherForContext:(WOContext *)_ctx;
42 - (WOResponse *)preprocessCredentialsInContext:(WOContext *)_ctx;
43 - (void)_sleepWithContext:(WOContext *)_ctx;
46 @interface NSObject(SoOFS)
47 - (NSString *)storagePath;
50 @implementation SoObjectRequestHandler
52 static BOOL debugOn = NO;
53 static BOOL debugRulesOn = NO;
54 static BOOL disableZLHack = NO;
56 static Class WOTemplateClass = Nil;
57 static NSString *rapidTurnAroundPath = nil;
59 static NSString *redirectURISafetySuffix = nil;
62 return [super version] + 0 /* 2 */;
65 static BOOL didInit = NO;
66 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
71 NSAssert2([super version] == 2,
72 @"invalid superclass (%@) version %i !",
73 NSStringFromClass([self superclass]), [super version]);
74 debugOn = [ud boolForKey:@"SoObjectRequestHandlerDebugEnabled"];
75 debugRulesOn = [ud boolForKey:@"SoObjectRequestHandlerRulesDebugEnabled"];
76 disableZLHack = [ud boolForKey:@"DisableZideLookHack"];
78 WOTemplateClass = [WOTemplate class];
79 rapidTurnAroundPath = [[ud stringForKey:@"WOProjectDirectory"] copy];
81 redirectURISafetySuffix =
82 [[ud stringForKey:@"WORedirectURISafetySuffix"] copy];
86 if ((self = [super init])) {
87 self->dispatcherRules =
88 [[NGRuleContext ruleContextWithModelInUserDefault:
89 @"SoRequestDispatcherRules"] retain];
90 if (debugRulesOn) [self->dispatcherRules setDebugEnabled:YES];
95 [self->dispatcherRules release];
96 [self->rootObject release];
100 /* type the request */
102 - (BOOL)isObjectPublishingContext:(WOContext *)_ctx {
104 Find out, whether we should do acquisition and dynamic method publishing.
105 This is only appropriate for HEAD/GET/POST requests from non-WebDAV
110 value = [self->dispatcherRules valueForKey:@"useAcquisition"];
111 if (debugRulesOn) [self debugWithFormat:@"acquision: %@", value];
112 return [value boolValue];
115 /* request path acquisition */
117 - (BOOL)enableZideLookHack {
118 /* Temporary Hack for ZideLook */
119 return disableZLHack ? NO : YES;
122 - (BOOL)skipApplicationName {
123 /* is the application name path of a URI part of the traversal path ? */
127 - (NSString *)hackZideLookURI:(NSString *)m {
128 if ([m hasPrefix:@"H_chste_Ebene_der_Pers_nlichen_Ordner"]) {
129 m = [m stringByReplacingString:@"H_chste_Ebene_der_Pers_nlichen_Ordner"
130 withString:@"helge"];
132 else if ([m hasPrefix:@"Suchpfad"]) {
133 m = [m stringByReplacingString:@"Suchpfad"
134 withString:@"helge"];
136 else if ([m hasPrefix:@"public"]) {
137 /* Evolution query on "/public" */
138 ; // keep it completly
140 else if ([self skipApplicationName]) {
143 r = [m rangeOfString:@"/"];
144 m = [m substringFromIndex:(r.location + r.length)];
148 - (NSString *)hackZideLookName:(NSString *)_p {
149 if ([_p isEqualToString:@"Gel_schte_Objekte"])
154 - (NSMutableArray *)addSpecialFormValuesInRequest:(WORequest *)_rq
155 toTraversalPath:(NSMutableArray *)_traversalPath
160 keys = [_rq formValueKeys];
161 if ((count = [keys count]) == 0)
162 return _traversalPath;
164 for (i = 0; i < count; i++) {
169 key = [keys objectAtIndex:i];
171 if (klen != 3 && klen < 7)
174 /* calculate method name */
177 if (klen == 3 && [key isEqualToString:@"Cmd"]) {
179 check for ASP style ?Cmd query parameter (required in ZideStore),
180 the value is the additional path we need to add
182 m = [_rq formValueForKey:key]; // need to unescape somehow ?
184 else if (klen == 7 && [key isEqualToString:@":method"]) {
186 check for ":method" form value, the value is the additional path we
189 m = [_rq formValueForKey:key]; // need to unescape somehow ?
191 else if ([key hasSuffix:@":method"]) {
193 Check for XXX:method form-keys, the value is ignored and the
194 XXX is added to the path. This is useful for binding actions
195 to submit buttons, since the value of a submit button is
196 displayed as it's label in the browser
199 m = [key substringToIndex:klen];
202 /* check calculated method */
206 else if ([m length] == 0) {
207 [self debugWithFormat:@"empty '%@' query parameter !", key];
212 [_traversalPath addObject:m];
214 return _traversalPath;
217 - (NSArray *)traversalPathFromRequest:(WORequest *)_rq {
218 static NSArray *rqKeys = nil; /* cache of request handlers */
219 NSMutableArray *traversalPath;
225 /* cache set of registered request handlers */
226 rqKeys = [[[WOApplication application] registeredRequestHandlerKeys] copy];
228 m = [_rq requestHandlerKey];
229 if ([rqKeys containsObject:m]) {
231 If the request-handler-key parsed by WORequest is valid, we'll consider
232 it a "usual" NGObjWeb query. Note that the appname is *not* processed !
234 /MyApp/wo/... => match
235 /MyApp/bla/... => fail
236 /blah/wa/... => match
238 a = [_rq requestHandlerPathArray];
241 /* TODO: more options, eg allow the appname to be part of the path */
244 /* get URI, cut of leading slash */
246 m = [m substringFromIndex:1];
248 if ([self enableZideLookHack])
249 m = [self hackZideLookURI:m];
250 else if ([self skipApplicationName]) {
251 /* cut of application name */
252 r = [m rangeOfString:@"/"];
253 m = [m substringFromIndex:(r.location + r.length)];
256 /* cut of query parameters */
257 r = [m rangeOfString:@"?"];
259 m = [m substringToIndex:r.location];
261 /* split into path components */
262 a = [m componentsSeparatedByString:@"/"];
266 traversalPath = [NSMutableArray arrayWithCapacity:(count + 1)];
267 for (i = 0; i < count; i++) {
270 p = [a objectAtIndex:i];
272 if ([p hasPrefix:@"_range"])
273 /* a ZideLook range query, further handled by WebDAV dispatcher */
276 p = [p stringByUnescapingURL];
278 if ([self enableZideLookHack])
279 p = [self hackZideLookName:p];
282 [traversalPath addObject:p];
285 traversalPath = [self addSpecialFormValuesInRequest:_rq
286 toTraversalPath:traversalPath];
288 return traversalPath;
291 - (id)rootObjectForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
294 if (self->rootObject != nil)
295 return self->rootObject;
297 if ((object = [_ctx application]) == nil)
298 object = [WOApplication application];
302 If we resolve in this location, we won't be able to resolve
303 application names like "Control_Panel".
305 TODO: explain better!
307 if ([object respondsToSelector:@selector(rootObjectInContext:)])
308 object = [object rootObjectInContext:_ctx];
314 - (id)lookupObjectForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
315 NSAutoreleasePool *pool;
316 NSArray *traversalPath;
319 pool = [[NSAutoreleasePool alloc] init];
321 NSException *error = nil;
325 /* build traversal path */
327 traversalPath = [self traversalPathFromRequest:_rq];
329 [_ctx setSoRequestTraversalPath:traversalPath];
331 /* setup root object */
333 root = [self rootObjectForRequest:_rq inContext:_ctx];
335 doAcquire = self->doesNoRequestPathAcquisition
337 : [self isObjectPublishingContext:_ctx];
339 [self debugWithFormat:@"traverse (%@): %@ %@",
341 [traversalPath componentsJoinedByString:@" => "],
342 doAcquire ? @"[acquires]" : @"(no acquisition)"];
344 currentObject = [root traversePathArray:traversalPath
349 currentObject = error;
352 currentObject = [currentObject retain];
355 return [currentObject autorelease];
358 /* object invocation */
360 - (id)dispatcherForObject:(id)_object inContext:(WOContext *)_ctx {
361 NSString *dpClass, *rqType;
364 /* ask object for dispatcher */
366 if ([_object respondsToSelector:@selector(dispatcherForContext:)]) {
367 if ((dispatcher = [_object dispatcherForContext:_ctx]))
372 [self debugWithFormat:@"select dispatcher using rules: %@",
373 self->dispatcherRules];
377 dpClass = [self->dispatcherRules valueForKey:@"dispatcher"];
378 rqType = [self->dispatcherRules valueForKey:@"requestType"];
380 [self debugWithFormat:@"selected dispatcher: %@", dpClass];
381 [self debugWithFormat:@"selected rq-type: %@", rqType];
384 /* create dispatcher */
386 if (rqType != nil) [_ctx setSoRequestType:rqType];
387 if ((dispatcher = NSClassFromString(dpClass)) == nil) {
388 [self errorWithFormat:@"did not find dispatcher class '%@'", dpClass];
392 if ((dispatcher = [[dispatcher alloc] initWithObject:_object]))
393 [_ctx setObjectDispatcher:dispatcher];
395 return [dispatcher autorelease];
398 /* object rendering */
400 - (WOResponse *)renderObject:(id)_object inContext:(WOContext *)_ctx {
401 SoDefaultRenderer *renderer;
406 [self debugWithFormat:@"render in ctx: %@", _ctx];
408 if ([_object isKindOfClass:[WOResponse class]])
409 /* already rendered ... */
412 /* check whether a container on the traversal stack provides a renderer */
415 e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
416 while ((container = [e nextObject])) {
417 if (![container respondsToSelector:
418 @selector(rendererForObject:inContext:)]) {
419 /* does not provide a renderer factory ... */
423 if ((renderer = [container rendererForObject:_object inContext:_ctx])) {
424 /* the container provided an own renderer for the object */
425 [self debugWithFormat:@"use container renderer: %@", renderer];
430 /* if we didn't find a renderer, determine using rules */
432 if (renderer == nil) {
433 NSString *rendererClass;
435 rendererClass = [self->dispatcherRules valueForKey:@"renderer"];
439 if ((clazz = NSClassFromString(rendererClass)) == Nil) {
440 [self errorWithFormat:@"did not find class of selected renderer %@",
443 else if ((renderer = [clazz sharedRenderer]) == nil) {
444 [self errorWithFormat:@"could not get renderer of class %@",
447 else if (![renderer canRenderObject:_object inContext:_ctx]) {
448 [self debugWithFormat:@"renderer %@ rejected rendering of object %@",
450 renderer = [SoDefaultRenderer sharedRenderer];
455 [self debugWithFormat:@"use rule-selected renderer: %@", renderer];
459 [self debugWithFormat:@"found no renderer for object: %@", _object];
461 if ((error = [renderer renderObject:_object inContext:_ctx])) {
462 if (renderer != [SoDefaultRenderer sharedRenderer]) {
465 e2 = [(SoDefaultRenderer *)[SoDefaultRenderer sharedRenderer]
466 renderObject:error inContext:_ctx];
468 [self errorWithFormat:
469 @"default renderer could not render error %@: %@", error, e2];
474 [self errorWithFormat:@"default renderer returned error: %@", error];
478 return [_ctx response];
481 - (BOOL)doesRejectFavicon {
485 - (WOResponse *)handleRequest:(WORequest *)_rq
486 inContext:(WOContext *)_ctx
487 session:(WOSession *)_sn
488 application:(WOApplication *)app
490 /* split up this big method */
497 [self debugWithFormat:@"request 0x%08X: %@ %@ (ctx=0x%08X)", _rq,
498 [_rq method], [_rq uri], _ctx];
499 if (_sn) [self debugWithFormat:@"session 0x%08X: %@", _sn, _sn];
502 /* first check safety marker */
504 if ([[_rq uri] hasSuffix:redirectURISafetySuffix]) {
505 #if 0 // does not work => znek's logging framework
507 @"ERROR: stopping processing because redirect safety suffix was "
508 @"reached:\n uri=%@\n suffix=%@\n",
509 [_rq uri], redirectURISafetySuffix];
511 NSLog(@"ERROR: stopping processing because redirect safety suffix was "
512 @"reached:\n uri=%@\n suffix=%@\n",
513 [_rq uri], redirectURISafetySuffix);
517 [r setStatus:403 /* Forbidden */];
518 [r appendContentString:
519 @"Request forbidden, a server side safety limit was reached."];
523 /* setup rule context */
525 [self->dispatcherRules reset];
526 [self->dispatcherRules takeValue:_rq forKey:@"request"];
527 [self->dispatcherRules takeValue:[_rq headers] forKey:@"headers"];
528 [self->dispatcherRules takeValue:[_rq method] forKey:@"method"];
529 [self->dispatcherRules takeValue:_ctx forKey:@"context"];
531 /* preprocess authentication credentials with global auth handler */
533 if ((authenticator = [app authenticatorInContext:_ctx])) {
534 [_ctx setObject:authenticator forKey:@"SoAuthenticator"];
536 /* give authenticator the chance to reject invalid credentials */
538 if ((r = [authenticator preprocessCredentialsInContext:_ctx])) {
539 [self->dispatcherRules reset];
543 [self debugWithFormat:@"authenticator allowed request."];
546 [self warnWithFormat:@"no authenticator available."];
552 object = [self lookupObjectForRequest:_rq inContext:_ctx];
555 [self->dispatcherRules
556 takeValue:[_ctx clientObject] forKey:@"clientObject"];
557 [self->dispatcherRules takeValue:object forKey:@"object"];
562 [r setHeader:@"text/html" forKey:@"content-type"];
563 [r appendContentString:@"object not found: "];
564 [r appendContentHTMLString:
565 [[_ctx soRequestTraversalPath] componentsJoinedByString:@" => "]];
570 /* dispatch object */
572 if ([object isKindOfClass:[NSException class]]) {
573 /* exceptions are not called ... */
574 [self debugWithFormat:@"not calling exception: %@", object];
581 dispatcher = [self dispatcherForObject:object inContext:_ctx];
582 [self debugWithFormat:@"dispatcher: %@", dispatcher];
584 [self debugWithFormat:@"dispatch object: %@", object];
585 object = [dispatcher dispatchInContext:_ctx];
587 if (object) [self->dispatcherRules takeValue:object forKey:@"result"];
593 [self debugWithFormat:@"got an empty result !"];
596 [r appendContentString:@"the called object returned no result"];
598 else if ([object isKindOfClass:[WOResponse class]]) {
600 [self debugWithFormat:
601 @"got response: 0x%08X (status=%i,len=%@,type=%@)",
603 [r headerForKey:@"content-length"],
604 [r headerForKey:@"content-type"]];
608 if ([object isKindOfClass:[NSData class]]) {
609 [self debugWithFormat:@"render data 0x%08X[len=%i]",
610 object, [object length]];
613 [self debugWithFormat:@"render object: %@", object];
616 [self->dispatcherRules takeValue:object forKey:@"result"];
617 r = [self renderObject:object inContext:_ctx];
620 [self debugWithFormat:
621 @"made response: 0x%08X (status=%i,len=%@,type=%@)",
623 [r headerForKey:@"content-length"],
624 [r headerForKey:@"content-type"]];
628 /* add header with primary key of new objects (for ZideLook) */
632 if ((key = [_ctx objectForKey:@"SxNewObjectID"])) {
633 key = [NSString stringWithFormat:@"%@", key];
634 [r setHeader:key forKey:@"x-skyrix-newname"];
635 [self logWithFormat:@"added new key header to response: '%@'", key];
639 /* rapid turnaround */
640 if (rapidTurnAroundPath != nil) {
642 NSString *_path = nil;
644 if ((page = [_ctx page])) {
647 template = [page _woComponentTemplate];
648 if ([template isKindOfClass:WOTemplateClass])
649 _path = [[(WOTemplate *)template url] path];
652 // TODO: ZNeK: explain!
653 // I guess we need some generic method to retrieve a template path?
654 if ([object isKindOfClass:NSClassFromString(@"OFSBaseObject")])
655 _path = [object storagePath];
658 [r setHeader:_path forKey:@"x-sope-template-path"];
661 /* sleep traversal stack */
666 e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
667 while ((obj = [e nextObject])) {
668 if (![obj isNotNull])
671 if ([obj respondsToSelector:@selector(_sleepWithContext:)])
672 [obj _sleepWithContext:_ctx];
673 else if ([obj respondsToSelector:@selector(sleep)])
678 [self->dispatcherRules reset];
683 @end /* SoObjectRequestHandler */
686 @implementation WOCoreApplication(RendererSelection)
688 - (id)rendererForObject:(id)_object inContext:(WOContext *)_ctx {
692 @end /* WOCoreApplication(RendererSelection) */
695 @implementation SoObjectRequestHandler(Logging)
697 - (NSString *)loggingPrefix {
698 return @"[object-handler]";
700 - (BOOL)isDebuggingEnabled {
701 return debugOn ? YES : NO;
704 @end /* SoObjectRequestHandler(Logging) */