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