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 NGLogger *debugLogger = nil;
53 static NGLogger *logger = nil;
54 static BOOL debugRulesOn = NO;
55 static BOOL disableZLHack = NO;
57 static Class WOTemplateClass = Nil;
58 static NSString *rapidTurnAroundPath = nil;
60 static NSString *redirectURISafetySuffix = nil;
63 return [super version] + 0 /* 2 */;
66 static BOOL didInit = NO;
73 NSAssert2([super version] == 2,
74 @"invalid superclass (%@) version %i !",
75 NSStringFromClass([self superclass]), [super version]);
77 lm = [NGLoggerManager defaultLoggerManager];
78 logger = [lm loggerForClass:self];
79 debugLogger = [lm loggerForDefaultKey:@"SoObjectRequestHandlerDebugEnabled"];
81 ud = [NSUserDefaults standardUserDefaults];
82 debugRulesOn = [ud boolForKey:@"SoObjectRequestHandlerRulesDebugEnabled"];
83 disableZLHack = [ud boolForKey:@"DisableZideLookHack"];
85 WOTemplateClass = [WOTemplate class];
86 rapidTurnAroundPath = [[ud stringForKey:@"WOProjectDirectory"] copy];
88 redirectURISafetySuffix =
89 [[ud stringForKey:@"WORedirectURISafetySuffix"] copy];
93 if ((self = [super init])) {
94 self->dispatcherRules =
95 [[NGRuleContext ruleContextWithModelInUserDefault:
96 @"SoRequestDispatcherRules"] retain];
97 if (debugRulesOn) [self->dispatcherRules setDebugEnabled:YES];
102 [self->dispatcherRules release];
103 [self->rootObject release];
107 /* type the request */
109 - (BOOL)isObjectPublishingContext:(WOContext *)_ctx {
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
117 value = [self->dispatcherRules valueForKey:@"useAcquisition"];
118 if (debugRulesOn) [self debugWithFormat:@"acquision: %@", value];
119 return [value boolValue];
122 /* request path acquisition */
124 - (BOOL)enableZideLookHack {
125 /* Temporary Hack for ZideLook */
126 return disableZLHack ? NO : YES;
129 - (BOOL)skipApplicationName {
130 /* is the application name path of a URI part of the traversal path ? */
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"];
139 else if ([m hasPrefix:@"Suchpfad"]) {
140 m = [m stringByReplacingString:@"Suchpfad"
141 withString:@"helge"];
143 else if ([m hasPrefix:@"public"]) {
144 /* Evolution query on "/public" */
145 ; // keep it completly
147 else if ([self skipApplicationName]) {
150 r = [m rangeOfString:@"/"];
151 m = [m substringFromIndex:(r.location + r.length)];
155 - (NSString *)hackZideLookName:(NSString *)_p {
156 if ([_p isEqualToString:@"Gel_schte_Objekte"])
161 - (NSMutableArray *)addSpecialFormValuesInRequest:(WORequest *)_rq
162 toTraversalPath:(NSMutableArray *)_traversalPath
167 keys = [_rq formValueKeys];
168 if ((count = [keys count]) == 0)
169 return _traversalPath;
171 for (i = 0; i < count; i++) {
176 key = [keys objectAtIndex:i];
178 if (klen != 3 && klen < 7)
181 /* calculate method name */
184 if (klen == 3 && [key isEqualToString:@"Cmd"]) {
186 check for ASP style ?Cmd query parameter (required in ZideStore),
187 the value is the additional path we need to add
189 m = [_rq formValueForKey:key]; // need to unescape somehow ?
191 else if (klen == 7 && [key isEqualToString:@":method"]) {
193 check for ":method" form value, the value is the additional path we
196 m = [_rq formValueForKey:key]; // need to unescape somehow ?
198 else if ([key hasSuffix:@":method"]) {
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
206 m = [key substringToIndex:klen];
209 /* check calculated method */
213 else if ([m length] == 0) {
214 [self debugWithFormat:@"empty '%@' query parameter !", key];
219 [_traversalPath addObject:m];
221 return _traversalPath;
224 - (NSArray *)traversalPathFromRequest:(WORequest *)_rq {
225 static NSArray *rqKeys = nil; /* cache of request handlers */
226 NSMutableArray *traversalPath;
232 /* cache set of registered request handlers */
233 rqKeys = [[[WOApplication application] registeredRequestHandlerKeys] copy];
235 /* check request handler keys */
237 m = [_rq requestHandlerKey];
238 if ([rqKeys containsObject:m]) {
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 !
243 /MyApp/wo/... => match
244 /MyApp/bla/... => fail
245 /blah/wa/... => match
247 a = [_rq requestHandlerPathArray];
250 /* TODO: more options, eg allow the appname to be part of the path */
253 /* get URI, cut of leading slash */
255 m = [m substringFromIndex:1];
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)];
265 /* cut of query parameters */
266 r = [m rangeOfString:@"?"];
268 m = [m substringToIndex:r.location];
270 /* split into path components */
271 a = [m componentsSeparatedByString:@"/"];
275 traversalPath = [NSMutableArray arrayWithCapacity:(count + 1)];
276 for (i = 0; i < count; i++) {
279 p = [a objectAtIndex:i];
281 if ([p hasPrefix:@"_range"])
282 /* a ZideLook range query, further handled by WebDAV dispatcher */
285 p = [p stringByUnescapingURL];
287 if ([self enableZideLookHack])
288 p = [self hackZideLookName:p];
291 [traversalPath addObject:p];
294 traversalPath = [self addSpecialFormValuesInRequest:_rq
295 toTraversalPath:traversalPath];
297 return traversalPath;
300 - (id)rootObjectForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
303 if (self->rootObject != nil)
304 return self->rootObject;
306 if ((object = [_ctx application]) == nil)
307 object = [WOApplication application];
311 If we resolve in this location, we won't be able to resolve
312 application names like "Control_Panel".
314 TODO: explain better!
316 if ([object respondsToSelector:@selector(rootObjectInContext:)])
317 object = [object rootObjectInContext:_ctx];
323 - (id)lookupObjectForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
324 NSAutoreleasePool *pool;
325 NSArray *traversalPath;
328 pool = [[NSAutoreleasePool alloc] init];
330 NSException *error = nil;
334 /* build traversal path */
336 traversalPath = [self traversalPathFromRequest:_rq];
337 if (traversalPath != nil)
338 [_ctx setSoRequestTraversalPath:traversalPath];
340 /* setup root object */
342 root = [self rootObjectForRequest:_rq inContext:_ctx];
344 doAcquire = self->doesNoRequestPathAcquisition
346 : [self isObjectPublishingContext:_ctx];
348 [self debugWithFormat:@"traverse (%@): %@ %@",
350 [traversalPath componentsJoinedByString:@" => "],
351 doAcquire ? @"[acquires]" : @"(no acquisition)"];
353 currentObject = [root traversePathArray:traversalPath
358 currentObject = error;
361 currentObject = [currentObject retain];
364 return [currentObject autorelease];
367 /* object invocation */
369 - (id)dispatcherForObject:(id)_object inContext:(WOContext *)_ctx {
370 NSString *dpClass, *rqType;
373 /* ask object for dispatcher */
375 if ([_object respondsToSelector:@selector(dispatcherForContext:)]) {
376 if ((dispatcher = [_object dispatcherForContext:_ctx]))
381 [self debugWithFormat:@"select dispatcher using rules: %@",
382 self->dispatcherRules];
386 dpClass = [self->dispatcherRules valueForKey:@"dispatcher"];
387 rqType = [self->dispatcherRules valueForKey:@"requestType"];
389 [self debugWithFormat:@"selected dispatcher: %@", dpClass];
390 [self debugWithFormat:@"selected rq-type: %@", rqType];
393 /* create dispatcher */
395 if (rqType != nil) [_ctx setSoRequestType:rqType];
396 if ((dispatcher = NSClassFromString(dpClass)) == nil) {
397 [self errorWithFormat:@"did not find dispatcher class '%@'", dpClass];
401 if ((dispatcher = [[dispatcher alloc] initWithObject:_object]))
402 [_ctx setObjectDispatcher:dispatcher];
404 return [dispatcher autorelease];
407 /* object rendering */
409 - (WOResponse *)renderObject:(id)_object inContext:(WOContext *)_ctx {
410 SoDefaultRenderer *renderer;
415 [self debugWithFormat:@"render in ctx: %@", _ctx];
417 if ([_object isKindOfClass:[WOResponse class]])
418 /* already rendered ... */
421 /* check whether a container on the traversal stack provides a renderer */
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 ... */
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];
439 /* if we didn't find a renderer, determine using rules */
441 if (renderer == nil) {
442 NSString *rendererClass;
444 rendererClass = [self->dispatcherRules valueForKey:@"renderer"];
448 if ((clazz = NSClassFromString(rendererClass)) == Nil) {
449 [self errorWithFormat:@"did not find class of selected renderer %@",
452 else if ((renderer = [clazz sharedRenderer]) == nil) {
453 [self errorWithFormat:@"could not get renderer of class %@",
456 else if (![renderer canRenderObject:_object inContext:_ctx]) {
457 [self debugWithFormat:@"renderer %@ rejected rendering of object %@",
459 renderer = [SoDefaultRenderer sharedRenderer];
464 [self debugWithFormat:@"use rule-selected renderer: %@", renderer];
468 [self debugWithFormat:@"found no renderer for object: %@", _object];
470 if ((error = [renderer renderObject:_object inContext:_ctx])) {
471 if (renderer != [SoDefaultRenderer sharedRenderer]) {
474 e2 = [(SoDefaultRenderer *)[SoDefaultRenderer sharedRenderer]
475 renderObject:error inContext:_ctx];
477 [self errorWithFormat:
478 @"default renderer could not render error %@: %@", error, e2];
483 [self errorWithFormat:@"default renderer returned error: %@", error];
487 return [_ctx response];
490 - (BOOL)doesRejectFavicon {
494 - (WOResponse *)handleRequest:(WORequest *)_rq
495 inContext:(WOContext *)_ctx
496 session:(WOSession *)_sn
497 application:(WOApplication *)app
499 /* split up this big method */
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];
511 /* first check safety marker */
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];
520 [r setStatus:403 /* Forbidden */];
521 [r appendContentString:
522 @"Request forbidden, a server side safety limit was reached."];
526 /* setup rule context */
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"];
534 /* preprocess authentication credentials with global auth handler */
536 if ((authenticator = [app authenticatorInContext:_ctx])) {
537 [_ctx setObject:authenticator forKey:@"SoAuthenticator"];
539 /* give authenticator the chance to reject invalid credentials */
541 if ((r = [authenticator preprocessCredentialsInContext:_ctx])) {
542 [self->dispatcherRules reset];
546 [self debugWithFormat:@"authenticator allowed request."];
549 [self warnWithFormat:@"no authenticator available."];
555 object = [self lookupObjectForRequest:_rq inContext:_ctx];
558 [self->dispatcherRules
559 takeValue:[_ctx clientObject] forKey:@"clientObject"];
560 [self->dispatcherRules takeValue:object forKey:@"object"];
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:@" => "]];
573 /* dispatch object */
575 if ([object isKindOfClass:[NSException class]]) {
576 /* exceptions are not called ... */
577 [self debugWithFormat:@"not calling exception: %@", object];
584 dispatcher = [self dispatcherForObject:object inContext:_ctx];
585 [self debugWithFormat:@"dispatcher: %@", dispatcher];
587 [self debugWithFormat:@"dispatch object: %@", object];
588 object = [dispatcher dispatchInContext:_ctx];
590 if (object) [self->dispatcherRules takeValue:object forKey:@"result"];
596 [self debugWithFormat:@"got an empty result !"];
599 [r appendContentString:@"the called object returned no result"];
601 else if ([object isKindOfClass:[WOResponse class]]) {
603 [self debugWithFormat:
604 @"got response: 0x%08X (status=%i,len=%@,type=%@)",
606 [r headerForKey:@"content-length"],
607 [r headerForKey:@"content-type"]];
611 if ([object isKindOfClass:[NSData class]]) {
612 [self debugWithFormat:@"render data 0x%08X[len=%i]",
613 object, [object length]];
616 [self debugWithFormat:@"render object: %@", object];
619 [self->dispatcherRules takeValue:object forKey:@"result"];
620 r = [self renderObject:object inContext:_ctx];
623 [self debugWithFormat:
624 @"made response: 0x%08X (status=%i,len=%@,type=%@)",
626 [r headerForKey:@"content-length"],
627 [r headerForKey:@"content-type"]];
631 /* add header with primary key of new objects (for ZideLook) */
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];
642 /* rapid turnaround */
643 if (rapidTurnAroundPath != nil) {
645 NSString *_path = nil;
647 if ((page = [_ctx page])) {
650 template = [page _woComponentTemplate];
651 if ([template isKindOfClass:WOTemplateClass])
652 _path = [[(WOTemplate *)template url] path];
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];
661 [r setHeader:_path forKey:@"x-sope-template-path"];
664 /* sleep traversal stack */
669 e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
670 while ((obj = [e nextObject])) {
671 if (![obj isNotNull])
674 if ([obj respondsToSelector:@selector(_sleepWithContext:)])
675 [obj _sleepWithContext:_ctx];
676 else if ([obj respondsToSelector:@selector(sleep)])
681 [self->dispatcherRules reset];
686 @end /* SoObjectRequestHandler */
689 @implementation WOCoreApplication(RendererSelection)
691 - (id)rendererForObject:(id)_object inContext:(WOContext *)_ctx {
695 @end /* WOCoreApplication(RendererSelection) */
698 @implementation SoObjectRequestHandler(Logging)
700 - (NSString *)loggingPrefix {
701 return @"[object-handler]";
703 - (BOOL)isDebuggingEnabled {
704 return debugLogger ? YES : NO;
713 @end /* SoObjectRequestHandler(Logging) */