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 // TODO: this already failed once, will it return null again?
222 [self logWithFormat:@"app is refusing new sessions ..."];
223 response = [app handleSessionRestorationErrorInContext:context];
227 /* some kind of error response from above ... */
230 /* check whether session is required ... */
231 if ([self requiresSessionForRequest:_request] && (session == nil)) {
232 response = [app handleSessionCreationErrorInContext:context];
241 response = [self handleRequest:_request
246 session = [context hasSession]
250 if (session != nil) {
251 if ([session storesIDsInCookies]) {
252 if (logger != nil) /* Note: required! do not remove */
253 [self debugWithFormat:@"add cookie to session: %@", session];
254 [self addCookiesForSession:session
259 [self saveSession:session
261 withResponse:response
265 [self debugWithFormat:@"no session to store."];
268 response = [app handleException:localException inContext:context];
275 [session _sleepWithContext:context];
276 response = [response retain];
281 response = [app handleException:localException inContext:context];
282 response = [response retain];
286 [app _setCurrentContext:nil];
289 [pool release]; pool = nil;
293 if ([app isRefusingNewSessions] &&
294 ([app activeSessionsCount] < [app minimumActiveSessionsCount])) {
296 @"application terminates because it refuses new sessions and "
297 @"the active session count (%i) is below the minimum (%i).",
298 [app activeSessionsCount], [app minimumActiveSessionsCount]];
305 rt = [[NSDateClass date] timeIntervalSince1970] - startHandling;
306 [perfLogger logWithFormat:@"handleRequest took %4.3fs.",
307 rt < 0.0 ? -1.0 : rt];
310 return [response autorelease];
324 - (id)valueForUndefinedKey:(NSString *)_key {
325 [self debugWithFormat:@"queried undefined KVC key (returning nil): '%@'",
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 */
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) */