2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
22 #include "WORequestHandler+private.h"
23 #include "WOApplication+private.h"
24 #include "WOContext+private.h"
25 #include <NGObjWeb/WOApplication.h>
26 #include <NGObjWeb/WOStatisticsStore.h>
27 #include <NGObjWeb/WOContext.h>
28 #include <NGObjWeb/WOCookie.h>
29 #include <NGObjWeb/WOComponent.h>
30 #include <NGObjWeb/WORequest.h>
31 #include <NGObjWeb/WOResponse.h>
32 #include <NGObjWeb/WOSession.h>
37 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY
38 @interface NSObject(Miss)
39 - (id)subclassResponsibility:(SEL)cmd;
43 @interface WOApplication(Privates)
44 - (void)_setCurrentContext:(WOContext *)_ctx;
47 @implementation WORequestHandler
49 static BOOL perflog = NO;
50 static Class NSDateClass = Nil;
56 NSDateClass = [NSDate class];
57 perflog = [[NSUserDefaults standardUserDefaults]
58 boolForKey:@"WOProfileRequestHandler"];
62 if ((self = [super init])) {
63 if ([[[NSUserDefaults standardUserDefaults]
64 objectForKey:@"WORunMultithreaded"]
66 self->lock = [[NSRecursiveLock alloc] init];
77 /* request handling */
79 - (BOOL)restoreSessionUsingIDs {
80 /* restore a session if an ID was given */
83 - (BOOL)autocreateSessionForRequest:(WORequest *)_request {
84 /* autocreate a session if none was restored */
87 - (BOOL)requiresSessionForRequest:(WORequest *)_request {
88 /* _ensure_ that a session is available */
92 - (NSString *)sessionIDFromRequest:(WORequest *)_request
93 application:(WOApplication *)_app
97 if ((sid = [_app sessionIDFromRequest:_request]) == nil)
101 NSAssert1([sid isKindOfClass:[NSString class]],
102 @"invalid session ID: %@", sid);
107 - (WOResponse *)handleRequest:(WORequest *)_request
108 inContext:(WOContext *)context
109 session:(WOSession *)session
110 application:(WOApplication *)app
112 return [self subclassResponsibility:_cmd];
115 - (BOOL)doesRejectFavicon {
119 - (WOResponse *)handleRequest:(WORequest *)_request {
120 NSTimeInterval startHandling = 0.0;
122 NSAutoreleasePool *pool = nil;
125 WOResponse *response = nil;
126 WOContext *context = nil;
128 NSString *sessionId = nil;
129 WOSession *session = nil;
132 /* first check URI for favicon requests ... */
133 uri = [_request uri];
134 if ([self doesRejectFavicon] && uri != nil) {
135 if ([@"/favicon.ico" isEqualToString:uri]) {
136 response = [WOResponse responseWithRequest:_request];
137 [response setStatus:404 /* not found */];
138 [self debugWithFormat:@"rejected favicon request: %@", uri];
144 startHandling = [[NSDateClass date] timeIntervalSince1970];
146 thread = [NSThread currentThread];
147 NSAssert(thread, @"missing current thread ...");
149 if (_request == nil) return nil;
152 app = [WOApplication application];
155 *(&pool) = [[NSAutoreleasePool alloc] init];
159 context = [WOContext contextWithRequest:_request];
160 NSAssert(context, @"no context assigned ..");
161 [app _setCurrentContext:context];
163 /* check session id */
165 *(&sessionId) = [self sessionIDFromRequest:_request application:app];
167 if ([sessionId length] == 0)
169 else if ([sessionId isEqualToString:@"nil"])
175 /* retrieve session */
176 if ([self restoreSessionUsingIDs]) {
179 session = [app restoreSessionWithID:sessionId
181 if (session == nil) {
182 response = [app handleSessionRestorationErrorInContext:context];
189 [[session retain] autorelease];
192 /* some kind of error response from above ... */
195 /* check double click browser cache ... */
196 if ((response = [self doubleClickResponseForContext:context]))
199 if (session == nil) {
200 /* session autocreation .. */
201 if ([self autocreateSessionForRequest:_request]) {
202 if (![app isRefusingNewSessions]) {
203 session = [app _initializeSessionInContext:context];
205 [self logWithFormat:@"autocreated session: %@", session];
208 response =[app handleSessionRestorationErrorInContext:context];
210 else { /* app refuses new sessions */
211 [self logWithFormat:@"app is refusing new sessions ..."];
212 response = [app handleSessionRestorationErrorInContext:context];
216 /* some kind of error response from above ... */
219 /* check whether session is required ... */
220 if ([self requiresSessionForRequest:_request] && (session == nil)) {
221 response = [app handleSessionCreationErrorInContext:context];
230 response = [self handleRequest:_request
235 session = ([context hasSession])
240 if ([session storesIDsInCookies]) {
241 [self addCookiesForSession:session
246 [self saveSession:session
248 withResponse:response
253 response = [app handleException:localException inContext:context];
260 [session _sleepWithContext:context];
261 response = [response retain];
266 response = [app handleException:localException inContext:context];
267 response = [response retain];
271 [app _setCurrentContext:nil];
274 [pool release]; pool = nil;
278 if ([app isRefusingNewSessions] &&
279 ([app activeSessionsCount] < [app minimumActiveSessionsCount])) {
280 [self debugWithFormat:
281 @"application terminates because it refuses new sessions and "
282 @"the active session count (%i) is below the minimum (%i).",
283 [app activeSessionsCount], [app minimumActiveSessionsCount]];
290 rt = [[NSDateClass date] timeIntervalSince1970] - startHandling;
291 [self debugWithFormat:@"handleRequest took %4.3fs.", rt < 0.0 ? -1.0 : rt];
294 return [response autorelease];
308 - (id)valueForUndefinedKey:(NSString *)_key {
309 [self debugWithFormat:@"queried undefined KVC key (returning nil): '%@'",
314 @end /* WORequestHandler */
316 @implementation WORequestHandler(Cookies)
318 - (void)addCookiesForSession:(WOSession *)_sn
319 toResponse:(WOResponse *)_response
320 inContext:(WOContext *)_ctx
322 if ([_sn storesIDsInCookies]) {
324 NSString *cookieName = nil;
325 WOCookie *cookie = nil;
329 app = [WOApplication application];
330 cookieName = [app name];
335 if ((uri = [[_ctx request] applicationName]) == nil)
337 uri = [@"/" stringByAppendingString:uri];
339 if ((tmp = [[_ctx request] adaptorPrefix]))
340 uri = [tmp stringByAppendingString:uri];
344 uri = [_ctx urlSessionPrefix];
345 uri = [_ctx urlWithRequestHandlerKey:
346 [WOApplication componentRequestHandlerKey]
351 value = [_sn isTerminating]
355 cookie = [WOCookie cookieWithName:cookieName
358 domain:[_sn domainForIDCookies]
359 expires:[_sn expirationDateForIDCookies]
362 [_response addCookie:cookie];
366 @end /* WORequestHandler(Cookies) */
368 @implementation WORequest(DblClickBrowser)
370 - (BOOL)isDoubleClickBrowser {
373 if ((ua = [self headerForKey:@"user-agent"]) == nil)
376 if ([ua rangeOfString:@"Konqueror"].length > 0)
378 else if ([ua rangeOfString:@"MSIE"].length > 0)
379 return [ua rangeOfString:@"Mac"].length > 0 ? YES : NO;
384 @end /* WORequest(DblClickBrowser) */
386 @implementation WORequestHandler(Support)
388 - (WOResponse *)doubleClickResponseForContext:(WOContext *)_ctx {
389 /* HACK check for duplicate requests send out by Konqueror and MacIE */
391 NSArray *hack; /* 0: uri, 1: response */
395 if (![rq isDoubleClickBrowser])
398 if (![_ctx hasSession])
401 /* look into page cache */
402 hack = [(WOSession *)[_ctx session] objectForKey:@"_lastResponseCacheHack"];
406 if ([[hack objectAtIndex:0] isEqualToString:[rq uri]]) {
407 [[WOApplication application]
409 @"using response from dblclick cache hack ..."];
410 return [hack objectAtIndex:1];
416 - (void)saveSession:(WOSession *)_session
417 inContext:(WOContext *)_ctx
418 withResponse:(WOResponse *)_response
419 application:(WOApplication *)_app
421 static BOOL perflog = NO;
422 NSTimeInterval startSaveSn = 0.0;
424 if (_session == nil) return;
427 startSaveSn = [[NSDate date] timeIntervalSince1970];
429 [_app saveSessionForContext:_ctx];
431 /* store response if strange double-click browser */
433 if ([[_ctx request] isDoubleClickBrowser]) {
434 NSArray *hack; /* 0: uri, 1: response */
436 hack = [NSArray arrayWithObjects:[[_ctx request] uri], _response, nil];
437 [_session setObject:hack forKey:@"_lastResponseCacheHack"];
443 rt = [[NSDate date] timeIntervalSince1970] - startSaveSn;
444 NSLog(@"[rq]: saving of session took %4.3fs.",
445 rt < 0.0 ? -1.0 : rt);
449 - (void)_fixupResponse:(WOResponse *)_response {
451 NSString *cntype = nil;
453 if ((ctype = [_response headerForKey:@"content-type"]) == nil) {
456 ctype = @"text/html";
458 body = [_response content];
459 if ([body length] > 6) {
460 const unsigned char *bytes;
462 if ((bytes = [body bytes])) {
463 if ((bytes[0] == '<') && (bytes[1] == '?')) {
464 if ((bytes[2] == 'x') && (bytes[3] == 'm') && (bytes[4] == 'l'))
470 [_response setHeader:ctype forKey:@"content-type"];
473 if ([ctype isEqualToString:@"text/html"]) {
474 switch ([_response contentEncoding]) {
475 case NSISOLatin1StringEncoding:
476 cntype = [ctype stringByAppendingString:@"; charset=iso-8859-1"];
478 case NSUTF8StringEncoding:
479 cntype = [ctype stringByAppendingString:@"; charset=utf-8"];
487 [_response setHeader:cntype forKey:@"content-type"];
490 - (WOResponse *)generateResponseForComponent:(WOComponent *)_component
491 inContext:(WOContext *)_ctx
492 application:(WOApplication *)_app
494 WOResponse *response;
496 if (_component == nil) return nil;
498 /* make the component the "response page" */
499 [_ctx setPage:_component];
501 if ([_ctx hasSession]) {
502 response = [_ctx response];
503 [_app appendToResponse:response inContext:_ctx];
506 //[self logWithFormat:@"generating component using -generateResponse"];
507 response = [_component generateResponse];
509 /* generate statistics */
510 [[_app statisticsStore]
511 recordStatisticsForResponse:response
515 [self _fixupResponse:response];
519 @end /* WORequestHandler(Support) */