2 Copyright (C) 2000-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 "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 if (logger != nil) /* Note: required! do not remove */
252 [self debugWithFormat:@"add cookie to session: %@", session];
253 [self addCookiesForSession:session
258 [self saveSession:session
260 withResponse:response
264 [self debugWithFormat:@"no session to store."];
267 response = [app handleException:localException inContext:context];
274 [session _sleepWithContext:context];
275 response = [response retain];
280 response = [app handleException:localException inContext:context];
281 response = [response retain];
285 [app _setCurrentContext:nil];
288 [pool release]; pool = nil;
292 if ([app isRefusingNewSessions] &&
293 ([app activeSessionsCount] < [app minimumActiveSessionsCount])) {
295 @"application terminates because it refuses new sessions and "
296 @"the active session count (%i) is below the minimum (%i).",
297 [app activeSessionsCount], [app minimumActiveSessionsCount]];
304 rt = [[NSDateClass date] timeIntervalSince1970] - startHandling;
305 [perfLogger logWithFormat:@"handleRequest took %4.3fs.",
306 rt < 0.0 ? -1.0 : rt];
309 return [response autorelease];
323 - (id)valueForUndefinedKey:(NSString *)_key {
324 [self debugWithFormat:@"queried undefined KVC key (returning nil): '%@'",
335 @end /* WORequestHandler */
337 @implementation WORequestHandler(Cookies)
339 - (void)addCookiesForSession:(WOSession *)_sn
340 toResponse:(WOResponse *)_response
341 inContext:(WOContext *)_ctx
344 WOCookie *cookie = nil;
348 if (![_sn storesIDsInCookies])
351 app = [WOApplication application];
353 // TODO: there is a DUP of this section in OpenGroupware.m to set an
355 if (!doNotSetCookiePath) {
358 if ((uri = [[_ctx request] applicationName]) == nil)
360 uri = [@"/" stringByAppendingString:uri];
361 if ((tmp = [[_ctx request] adaptorPrefix]))
362 uri = [tmp stringByAppendingString:uri];
367 #if 0 // TODO: explain!
368 uri = [_ctx urlSessionPrefix];
369 uri = [_ctx urlWithRequestHandlerKey:
370 [WOApplication componentRequestHandlerKey]
375 value = [_sn isTerminating]
379 cookie = [WOCookie cookieWithName:[app name]
382 domain:[_sn domainForIDCookies]
383 expires:[_sn expirationDateForIDCookies]
386 [_response addCookie:cookie];
389 @end /* WORequestHandler(Cookies) */
391 @implementation WORequest(DblClickBrowser)
393 - (BOOL)isDoubleClickBrowser {
397 @end /* WORequest(DblClickBrowser) */
399 @implementation WORequestHandler(Support)
401 - (WOResponse *)doubleClickResponseForContext:(WOContext *)_ctx {
406 - (void)saveSession:(WOSession *)_session
407 inContext:(WOContext *)_ctx
408 withResponse:(WOResponse *)_response
409 application:(WOApplication *)_app
411 static BOOL perflog = NO;
412 NSTimeInterval startSaveSn = 0.0;
414 if (_session == nil) return;
417 startSaveSn = [[NSDate date] timeIntervalSince1970];
419 [_app saveSessionForContext:_ctx];
423 rt = [[NSDate date] timeIntervalSince1970] - startSaveSn;
424 NSLog(@"[rq]: saving of session took %4.3fs.",
425 rt < 0.0 ? -1.0 : rt);
429 - (void)_fixupResponse:(WOResponse *)_response {
431 NSString *cntype = nil;
433 if ((ctype = [_response headerForKey:@"content-type"]) == nil) {
436 ctype = @"text/html";
438 body = [_response content];
439 if ([body length] > 6) {
440 const unsigned char *bytes;
442 if ((bytes = [body bytes])) {
443 if ((bytes[0] == '<') && (bytes[1] == '?')) {
444 if ((bytes[2] == 'x') && (bytes[3] == 'm') && (bytes[4] == 'l'))
450 [_response setHeader:ctype forKey:@"content-type"];
453 if ([ctype isEqualToString:@"text/html"]) {
454 switch ([_response contentEncoding]) {
455 case NSISOLatin1StringEncoding:
456 cntype = [ctype stringByAppendingString:@"; charset=iso-8859-1"];
458 case NSUTF8StringEncoding:
459 cntype = [ctype stringByAppendingString:@"; charset=utf-8"];
467 [_response setHeader:cntype forKey:@"content-type"];
470 - (WOResponse *)generateResponseForComponent:(WOComponent *)_component
471 inContext:(WOContext *)_ctx
472 application:(WOApplication *)_app
474 WOResponse *response;
476 if (_component == nil) return nil;
478 /* make the component the "response page" */
479 [_ctx setPage:_component];
481 if ([_ctx hasSession]) {
482 response = [_ctx response];
483 [_app appendToResponse:response inContext:_ctx];
486 //[self logWithFormat:@"generating component using -generateResponse"];
487 response = [_component generateResponse];
489 /* generate statistics */
490 [[_app statisticsStore]
491 recordStatisticsForResponse:response
495 [self _fixupResponse:response];
499 @end /* WORequestHandler(Support) */