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