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 doNotSetCookiePath = NO;
50 static Class NSDateClass = Nil;
51 static NGLogger *logger = nil;
52 static NGLogger *perfLogger = nil;
60 static BOOL didInit = NO;
66 NSDateClass = [NSDate class];
68 lm = [NGLoggerManager defaultLoggerManager];
69 logger = [lm loggerForDefaultKey:@"WODebuggingEnabled"];
70 perfLogger = [lm loggerForDefaultKey:@"WOProfileRequestHandler"];
72 ud = [NSUserDefaults standardUserDefaults];
73 doNotSetCookiePath = [ud boolForKey:@"WOUseGlobalCookiePath"];
77 if ((self = [super init])) {
78 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
80 if ([ud boolForKey:@"WORunMultithreaded"])
81 self->lock = [[NSRecursiveLock alloc] init];
91 /* request handling */
93 - (BOOL)restoreSessionUsingIDs {
94 /* restore a session if an ID was given */
97 - (BOOL)autocreateSessionForRequest:(WORequest *)_request {
98 /* autocreate a session if none was restored */
101 - (BOOL)requiresSessionForRequest:(WORequest *)_request {
102 /* _ensure_ that a session is available */
106 - (NSString *)sessionIDFromRequest:(WORequest *)_request
107 application:(WOApplication *)_app
111 if ((sid = [_app sessionIDFromRequest:_request]) == nil)
115 NSAssert1([sid isKindOfClass:[NSString class]],
116 @"invalid session ID: %@", sid);
121 - (WOResponse *)handleRequest:(WORequest *)_request
122 inContext:(WOContext *)context
123 session:(WOSession *)session
124 application:(WOApplication *)app
126 return [self subclassResponsibility:_cmd];
129 - (BOOL)doesRejectFavicon {
133 - (WOResponse *)handleRequest:(WORequest *)_request {
134 NSTimeInterval startHandling = 0.0;
136 NSAutoreleasePool *pool = nil;
139 WOResponse *response = nil;
140 WOContext *context = nil;
142 NSString *sessionId = nil;
143 WOSession *session = nil;
146 /* first check URI for favicon requests ... */
147 uri = [_request uri];
148 if ([self doesRejectFavicon] && uri != nil) {
149 if ([@"/favicon.ico" isEqualToString:uri]) {
150 response = [WOResponse responseWithRequest:_request];
151 [response setStatus:404 /* not found */];
152 [self debugWithFormat:@"rejected favicon request: %@", uri];
158 startHandling = [[NSDateClass date] timeIntervalSince1970];
160 thread = [NSThread currentThread];
161 NSAssert(thread, @"missing current thread ...");
163 if (_request == nil) return nil;
166 app = [WOApplication application];
169 *(&pool) = [[NSAutoreleasePool alloc] init];
173 context = [WOContext contextWithRequest:_request];
174 NSAssert(context, @"no context assigned ..");
175 [app _setCurrentContext:context];
177 /* check session id */
179 *(&sessionId) = [self sessionIDFromRequest:_request application:app];
181 if ([sessionId length] == 0)
183 else if ([sessionId isEqualToString:@"nil"])
189 /* retrieve session */
190 if ([self restoreSessionUsingIDs]) {
193 session = [app restoreSessionWithID:sessionId
195 if (session == nil) {
196 response = [app handleSessionRestorationErrorInContext:context];
203 [[session retain] autorelease];
206 /* some kind of error response from above ... */
209 if (session == nil) {
210 /* session autocreation .. */
211 if ([self autocreateSessionForRequest:_request]) {
212 if (![app isRefusingNewSessions]) {
213 session = [app _initializeSessionInContext:context];
215 [self debugWithFormat:@"autocreated session: %@", session];
218 response =[app handleSessionRestorationErrorInContext:context];
220 else { /* app refuses new sessions */
221 [self logWithFormat:@"app is refusing new sessions ..."];
222 response = [app handleSessionRestorationErrorInContext:context];
226 /* some kind of error response from above ... */
229 /* check whether session is required ... */
230 if ([self requiresSessionForRequest:_request] && (session == nil)) {
231 response = [app handleSessionCreationErrorInContext:context];
240 response = [self handleRequest:_request
245 session = [context hasSession]
249 if (session != nil) {
250 if ([session storesIDsInCookies]) {
251 [self debugWithFormat:@"add cookie to session: %@", session];
252 [self addCookiesForSession:session
257 [self saveSession:session
259 withResponse:response
263 [self debugWithFormat:@"no session to store."];
266 response = [app handleException:localException inContext:context];
273 [session _sleepWithContext:context];
274 response = [response retain];
279 response = [app handleException:localException inContext:context];
280 response = [response retain];
284 [app _setCurrentContext:nil];
287 [pool release]; pool = nil;
291 if ([app isRefusingNewSessions] &&
292 ([app activeSessionsCount] < [app minimumActiveSessionsCount])) {
294 @"application terminates because it refuses new sessions and "
295 @"the active session count (%i) is below the minimum (%i).",
296 [app activeSessionsCount], [app minimumActiveSessionsCount]];
303 rt = [[NSDateClass date] timeIntervalSince1970] - startHandling;
304 [perfLogger logWithFormat:@"handleRequest took %4.3fs.",
305 rt < 0.0 ? -1.0 : rt];
308 return [response autorelease];
322 - (id)valueForUndefinedKey:(NSString *)_key {
323 [self debugWithFormat:@"queried undefined KVC key (returning nil): '%@'",
334 @end /* WORequestHandler */
336 @implementation WORequestHandler(Cookies)
338 - (void)addCookiesForSession:(WOSession *)_sn
339 toResponse:(WOResponse *)_response
340 inContext:(WOContext *)_ctx
343 WOCookie *cookie = nil;
347 if (![_sn storesIDsInCookies])
350 app = [WOApplication application];
352 // TODO: there is a DUP of this section in OpenGroupware.m to set an
354 if (!doNotSetCookiePath) {
357 if ((uri = [[_ctx request] applicationName]) == nil)
359 uri = [@"/" stringByAppendingString:uri];
360 if ((tmp = [[_ctx request] adaptorPrefix]))
361 uri = [tmp stringByAppendingString:uri];
366 #if 0 // TODO: explain!
367 uri = [_ctx urlSessionPrefix];
368 uri = [_ctx urlWithRequestHandlerKey:
369 [WOApplication componentRequestHandlerKey]
374 value = [_sn isTerminating]
378 cookie = [WOCookie cookieWithName:[app name]
381 domain:[_sn domainForIDCookies]
382 expires:[_sn expirationDateForIDCookies]
385 [_response addCookie:cookie];
388 @end /* WORequestHandler(Cookies) */
390 @implementation WORequest(DblClickBrowser)
392 - (BOOL)isDoubleClickBrowser {
396 @end /* WORequest(DblClickBrowser) */
398 @implementation WORequestHandler(Support)
400 - (WOResponse *)doubleClickResponseForContext:(WOContext *)_ctx {
405 - (void)saveSession:(WOSession *)_session
406 inContext:(WOContext *)_ctx
407 withResponse:(WOResponse *)_response
408 application:(WOApplication *)_app
410 static BOOL perflog = NO;
411 NSTimeInterval startSaveSn = 0.0;
413 if (_session == nil) return;
416 startSaveSn = [[NSDate date] timeIntervalSince1970];
418 [_app saveSessionForContext:_ctx];
422 rt = [[NSDate date] timeIntervalSince1970] - startSaveSn;
423 NSLog(@"[rq]: saving of session took %4.3fs.",
424 rt < 0.0 ? -1.0 : rt);
428 - (void)_fixupResponse:(WOResponse *)_response {
430 NSString *cntype = nil;
432 if ((ctype = [_response headerForKey:@"content-type"]) == nil) {
435 ctype = @"text/html";
437 body = [_response content];
438 if ([body length] > 6) {
439 const unsigned char *bytes;
441 if ((bytes = [body bytes])) {
442 if ((bytes[0] == '<') && (bytes[1] == '?')) {
443 if ((bytes[2] == 'x') && (bytes[3] == 'm') && (bytes[4] == 'l'))
449 [_response setHeader:ctype forKey:@"content-type"];
452 if ([ctype isEqualToString:@"text/html"]) {
453 switch ([_response contentEncoding]) {
454 case NSISOLatin1StringEncoding:
455 cntype = [ctype stringByAppendingString:@"; charset=iso-8859-1"];
457 case NSUTF8StringEncoding:
458 cntype = [ctype stringByAppendingString:@"; charset=utf-8"];
466 [_response setHeader:cntype forKey:@"content-type"];
469 - (WOResponse *)generateResponseForComponent:(WOComponent *)_component
470 inContext:(WOContext *)_ctx
471 application:(WOApplication *)_app
473 WOResponse *response;
475 if (_component == nil) return nil;
477 /* make the component the "response page" */
478 [_ctx setPage:_component];
480 if ([_ctx hasSession]) {
481 response = [_ctx response];
482 [_app appendToResponse:response inContext:_ctx];
485 //[self logWithFormat:@"generating component using -generateResponse"];
486 response = [_component generateResponse];
488 /* generate statistics */
489 [[_app statisticsStore]
490 recordStatisticsForResponse:response
494 [self _fixupResponse:response];
498 @end /* WORequestHandler(Support) */