1/* 2 Copyright (C) 2000-2005 SKYRIX Software AG 3 4 This file is part of SOPE. 5 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 9 later version. 10 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. 15 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 19 02111-1307, USA. 20*/ 21 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> 33#include "common.h" 34 35//#define USE_POOLS 1 36 37#if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY 38@interface NSObject(Miss) 39- (id)subclassResponsibility:(SEL)cmd; 40@end 41#endif 42 43@interface WOApplication(Privates) 44- (void)_setCurrentContext:(WOContext *)_ctx; 45@end 46 47@implementation WORequestHandler 48 49static BOOL doNotSetCookiePath = NO; 50static Class NSDateClass = Nil; 51static NGLogger *logger = nil; 52static NGLogger *perfLogger = nil; 53 54+ (void)initialize { 55 NSUserDefaults *ud; 56 NGLoggerManager *lm; 57 static BOOL didInit = NO; 58 59 if (didInit) 60 return; 61 didInit = YES; 62 NSDateClass = [NSDate class]; 63 64 lm = [NGLoggerManager defaultLoggerManager]; 65 logger = [lm loggerForDefaultKey:@"WODebuggingEnabled"]; 66 perfLogger = [lm loggerForDefaultKey:@"WOProfileRequestHandler"]; 67 68 ud = [NSUserDefaults standardUserDefaults]; 69 doNotSetCookiePath = [ud boolForKey:@"WOUseGlobalCookiePath"]; 70} 71 72- (id)init { 73 if ((self = [super init])) { 74 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; 75 76 if ([ud boolForKey:@"WORunMultithreaded"]) 77 self->lock = [[NSRecursiveLock alloc] init]; 78 } 79 return self; 80} 81 82- (void)dealloc { 83 [self->lock release]; 84 [super dealloc]; 85} 86 87/* request handling */ 88 89- (BOOL)restoreSessionUsingIDs { 90 /* restore a session if an ID was given */ 91 return YES; 92} 93- (BOOL)autocreateSessionForRequest:(WORequest *)_request { 94 /* autocreate a session if none was restored */ 95 return NO; 96} 97- (BOOL)requiresSessionForRequest:(WORequest *)_request { 98 /* _ensure_ that a session is available */ 99 return NO; 100} 101 102- (NSString *)sessionIDFromRequest:(WORequest *)_request 103 application:(WOApplication *)_app 104{ 105 NSString *sid; 106 107 if ((sid = [_app sessionIDFromRequest:_request]) == nil) 108 return nil; 109 110#if DEBUG 111 NSAssert1([sid isKindOfClass:[NSString class]], 112 @"invalid session ID: %@", sid); 113#endif 114 return sid; 115} 116 117- (WOResponse *)handleRequest:(WORequest *)_request 118 inContext:(WOContext *)context 119 session:(WOSession *)session 120 application:(WOApplication *)app 121{ 122 return [self subclassResponsibility:_cmd]; 123} 124 125- (BOOL)doesRejectFavicon { 126 return YES; 127} 128 129- (WOResponse *)handleRequest:(WORequest *)_request { 130 NSTimeInterval startHandling = 0.0; 131#if USE_POOLS 132 NSAutoreleasePool *pool = nil; 133#endif 134 WOApplication *app; 135 WOResponse *response = nil; 136 WOContext *context = nil; 137 NSThread *thread; 138 NSString *sessionId = nil; 139 WOSession *session = nil; 140 NSString *uri; 141 142 /* first check URI for favicon requests ... */ 143 uri = [_request uri]; 144 if ([self doesRejectFavicon] && uri != nil) { 145 if ([@"/favicon.ico" isEqualToString:uri]) { 146 response = [WOResponse responseWithRequest:_request]; 147 [response setStatus:404 /* not found */]; 148 [self debugWithFormat:@"rejected favicon request: %@", uri]; 149 return response; 150 } 151 } 152 153 if (perfLogger) 154 startHandling = [[NSDateClass date] timeIntervalSince1970]; 155 156 thread = [NSThread currentThread]; 157 NSAssert(thread, @"missing current thread ..."); 158 159 if (_request == nil) return nil; 160 161 *(&app) = nil; 162 app = [WOApplication application]; 163 164#if USE_POOLS 165 *(&pool) = [[NSAutoreleasePool alloc] init]; 166#endif 167 { 168 /* setup context */ 169 context = [WOContext contextWithRequest:_request]; 170 NSAssert(context, @"no context assigned .."); 171 [app _setCurrentContext:context]; 172 173 /* check session id */ 174 *(&session) = nil; 175 *(&sessionId) = [self sessionIDFromRequest:_request application:app]; 176 177 if ([sessionId length] == 0) 178 sessionId = nil; 179 else if ([sessionId isEqualToString:@"nil"]) 180 sessionId = nil; 181 182 NS_DURING { 183 [app awake]; 184 185 /* retrieve session */ 186 if ([self restoreSessionUsingIDs]) { 187 SYNCHRONIZED(app) { 188 if (sessionId) { 189 session = [app restoreSessionWithID:sessionId 190 inContext:context]; 191 if (session == nil) { 192 response = [app handleSessionRestorationErrorInContext:context]; 193 sessionId = nil; 194 } 195 } 196 } 197 END_SYNCHRONIZED; 198 199 [[session retain] autorelease]; 200 201 if (response != nil) 202 /* some kind of error response from above ... */ 203 goto responseDone; 204 205 if (session == nil) { 206 /* session autocreation .. */ 207 if ([self autocreateSessionForRequest:_request]) { 208 if (![app isRefusingNewSessions]) { 209 session = [app _initializeSessionInContext:context]; 210 211 [self debugWithFormat:@"autocreated session: %@", session]; 212 213 if (session == nil) 214 response =[app handleSessionRestorationErrorInContext:context]; 215 } 216 else { /* app refuses new sessions */ 217 // TODO: this already failed once, will it return null again? 218 [self logWithFormat:@"app is refusing new sessions ..."]; 219 response = [app handleSessionRestorationErrorInContext:context]; 220 } 221 } 222 if (response) 223 /* some kind of error response from above ... */ 224 goto responseDone; 225 226 /* check whether session is required ... */ 227 if ([self requiresSessionForRequest:_request] && (session == nil)) { 228 response = [app handleSessionCreationErrorInContext:context]; 229 goto responseDone; 230 } 231 } 232 } 233 234 [session lock]; 235 236 NS_DURING { 237 response = [self handleRequest:_request 238 inContext:context 239 session:session 240 application:app]; 241 242 session = [context hasSession] 243 ? [context session] 244 : nil; 245 246 if (session != nil) { 247 if ([session storesIDsInCookies]) { 248 if (logger != nil) /* Note: required! do not remove */ 249 [self debugWithFormat:@"add cookie to session: %@", session]; 250 [self addCookiesForSession:session 251 toResponse:response 252 inContext:context]; 253 } 254 255 [self saveSession:session 256 inContext:context 257 withResponse:response 258 application:app]; 259 } 260 else 261 [self debugWithFormat:@"no session to store."]; 262 } 263 NS_HANDLER { 264 response = [app handleException:localException inContext:context]; 265 } 266 NS_ENDHANDLER; 267 268 [session unlock]; 269 270 responseDone: 271 [session _sleepWithContext:context]; 272 response = [response retain]; 273 274 [app sleep]; 275 } 276 NS_HANDLER { 277 response = [app handleException:localException inContext:context]; 278 response = [response retain]; 279 } 280 NS_ENDHANDLER; 281 282 [app _setCurrentContext:nil]; 283 } 284#if USE_POOLS 285 [pool release]; pool = nil; 286#endif 287 288 [app lock]; 289 if ([app isRefusingNewSessions] && 290 ([app activeSessionsCount] < [app minimumActiveSessionsCount])) { 291 [self logWithFormat: 292 @"application terminates because it refuses new sessions and " 293 @"the active session count (%i) is below the minimum (%i).", 294 [app activeSessionsCount], [app minimumActiveSessionsCount]]; 295 [app terminate]; 296 } 297 [app unlock]; 298 299 if (perfLogger) { 300 NSTimeInterval rt; 301 rt = [[NSDateClass date] timeIntervalSince1970] - startHandling; 302 [perfLogger logWithFormat:@"handleRequest took %4.3fs.", 303 rt < 0.0 ? -1.0 : rt]; 304 } 305 306 return [response autorelease]; 307} 308 309/* locking */ 310 311- (void)lock { 312 [self->lock lock]; 313} 314- (void)unlock { 315 [self->lock unlock]; 316} 317 318/* KVC */ 319 320- (id)valueForUndefinedKey:(NSString *)_key { 321 [self debugWithFormat:@"queried undefined KVC key (returning nil): '%@'", 322 _key]; 323 return nil; 324} 325 326/* logging */ 327 328- (id)debugLogger { 329 return logger; 330} 331 332/* Cookies */ 333 334- (void)addCookiesForSession:(WOSession *)_sn 335 toResponse:(WOResponse *)_response 336 inContext:(WOContext *)_ctx 337{ 338 WOApplication *app; 339 WOCookie *cookie = nil; 340 NSString *uri; 341 NSString *value; 342 343 if (![_sn storesIDsInCookies]) 344 return; 345 346 app = [WOApplication application]; 347 348 // TODO: there is a DUP of this section in OpenGroupware.m to set an 349 // expiration cookie 350 if (!doNotSetCookiePath) { 351 NSString *tmp; 352 353 if ((uri = [[_ctx request] applicationName]) == nil) 354 uri = [app name]; 355 uri = [@"/" stringByAppendingString:uri]; 356 if ((tmp = [[_ctx request] adaptorPrefix])) 357 uri = [tmp stringByAppendingString:uri]; 358 } 359 else 360 uri = @"/"; 361 362#if 0 // TODO: explain! 363 uri = [_ctx urlSessionPrefix]; 364 uri = [_ctx urlWithRequestHandlerKey: 365 [WOApplication componentRequestHandlerKey] 366 path:@"/" 367 queryString:nil]; 368#endif 369 370 value = [_sn isTerminating] 371 ? (NSString *)@"nil" 372 : [_sn sessionID]; 373 374 cookie = [WOCookie cookieWithName:[app name] 375 value:value 376 path:uri 377 domain:[_sn domainForIDCookies] 378 expires:[_sn expirationDateForIDCookies] 379 isSecure:NO]; 380 if (cookie != nil) 381 [_response addCookie:cookie]; 382} 383 384@end /* WORequestHandler */ 385 386 387@implementation WORequest(DblClickBrowser) 388 389- (BOOL)isDoubleClickBrowser { 390 return NO; 391} 392 393@end /* WORequest(DblClickBrowser) */ 394 395@implementation WORequestHandler(Support) 396 397- (WOResponse *)doubleClickResponseForContext:(WOContext *)_ctx { 398 // DEPRECATED 399 return nil; 400} 401 402- (void)saveSession:(WOSession *)_session 403 inContext:(WOContext *)_ctx 404 withResponse:(WOResponse *)_response 405 application:(WOApplication *)_app 406{ 407 static BOOL perflog = NO; 408 NSTimeInterval startSaveSn = 0.0; 409 410 if (_session == nil) return; 411 412 if (perflog) 413 startSaveSn = [[NSDate date] timeIntervalSince1970]; 414 415 [_app saveSessionForContext:_ctx]; 416 417 if (perflog) { 418 NSTimeInterval rt; 419 rt = [[NSDate date] timeIntervalSince1970] - startSaveSn; 420 NSLog(@"[rq]: saving of session took %4.3fs.", 421 rt < 0.0 ? -1.0 : rt); 422 } 423} 424 425- (void)_fixupResponse:(WOResponse *)_response { 426 NSString *ctype; 427 NSString *cntype = nil; 428 429 if ((ctype = [_response headerForKey:@"content-type"]) == nil) { 430 NSData *body; 431 432 ctype = @"text/html"; 433 434 body = [_response content]; 435 if ([body length] > 6) { 436 const unsigned char *bytes; 437 438 if ((bytes = [body bytes])) { 439 if ((bytes[0] == '<') && (bytes[1] == '?')) { 440 if ((bytes[2] == 'x') && (bytes[3] == 'm') && (bytes[4] == 'l')) 441 ctype = @"text/xml"; 442 } 443 } 444 } 445 446 [_response setHeader:ctype forKey:@"content-type"]; 447 } 448 449 if ([ctype isEqualToString:@"text/html"]) { 450 switch ([_response contentEncoding]) { 451 case NSISOLatin1StringEncoding: 452 cntype = [ctype stringByAppendingString:@"; charset=iso-8859-1"]; 453 break; 454 case NSUTF8StringEncoding: 455 cntype = [ctype stringByAppendingString:@"; charset=utf-8"]; 456 break; 457 458 default: 459 break; 460 } 461 } 462 if (cntype) 463 [_response setHeader:cntype forKey:@"content-type"]; 464} 465 466- (WOResponse *)generateResponseForComponent:(WOComponent *)_component 467 inContext:(WOContext *)_ctx 468 application:(WOApplication *)_app 469{ 470 WOResponse *response; 471 472 if (_component == nil) return nil; 473 474 /* make the component the "response page" */ 475 [_ctx setPage:_component]; 476 477 if ([_ctx hasSession]) { 478 response = [_ctx response]; 479 [_app appendToResponse:response inContext:_ctx]; 480 } 481 else { 482 //[self logWithFormat:@"generating component using -generateResponse"]; 483 response = [_component generateResponse]; 484 485 /* generate statistics */ 486 [[_app statisticsStore] 487 recordStatisticsForResponse:response 488 inContext:_ctx]; 489 } 490 491 [self _fixupResponse:response]; 492 return response; 493} 494 495@end /* WORequestHandler(Support) */ 496