]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WORequest.m
fixed superclass versions
[sope] / sope-appserver / NGObjWeb / WORequest.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 <NGObjWeb/WORequest.h>
23 #include <NGObjWeb/WOSession.h>
24 #include <NGObjWeb/WOContext.h>
25 #include <NGObjWeb/WOApplication.h>
26 #include <NGObjWeb/WOSession.h>
27 #include <NGObjWeb/WOCookie.h>
28 #include <NGHttp/NGHttp.h>
29 #include "NGHttp+WO.h"
30 #include <NGExtensions/NSString+Ext.h>
31 #include <time.h>
32 #include "common.h"
33
34 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY
35 @interface NSObject(Miss)
36 - (id)notImplemented:(SEL)cmd;
37 @end
38 #endif
39
40 NGObjWeb_DECLARE NSString *WORequestValueData       = @"wodata";
41 NGObjWeb_DECLARE NSString *WORequestValueInstance   = @"woinst";
42 NGObjWeb_DECLARE NSString *WORequestValuePageName   = @"wopage";
43 NGObjWeb_DECLARE NSString *WORequestValueContextID  = @"_c";
44 NGObjWeb_DECLARE NSString *WORequestValueSenderID   = @"_i";
45 NGObjWeb_DECLARE NSString *WORequestValueSessionID  = @"wosid";
46 NGObjWeb_DECLARE NSString *WONoSelectionString      = @"WONoSelectionString";
47
48 @implementation WORequest
49
50 static BOOL debugOn = NO;
51
52 + (int)version {
53   return [super version] + 2 /* v7 */;
54 }
55
56 + (NSString *)lookupLanguagesPlist {
57   NSString *apath;
58 #if !COMPILE_AS_FRAMEWORK
59   NSFileManager *fm = [NSFileManager defaultManager];
60   NSDictionary  *env;
61   NSString      *relPath;
62 #else
63   NSBundle *bundle;
64 #endif
65   
66 #if COMPILE_AS_FRAMEWORK
67   bundle = [NSBundle bundleForClass:self];
68   apath  = [bundle pathForResource:@"Languages" ofType:@"plist"];
69 #else
70   env  = [[NSProcessInfo processInfo] environment];
71
72   /* 
73        TODO: the following is a dirty hack because GNUstep people decided
74              to change the directory structure. SIGH!
75   */
76 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY || GNUSTEP_BASE_LIBRARY
77   relPath = @"Library/Libraries";
78 #else    
79   relPath = @"Libraries";
80 #endif
81   relPath = [relPath stringByAppendingPathComponent:@"Resources"];
82   relPath = [relPath stringByAppendingPathComponent:@"NGObjWeb"];
83   relPath = [relPath stringByAppendingPathComponent:@"Languages.plist"];
84     
85   apath = [env objectForKey:@"GNUSTEP_USER_ROOT"];
86   apath = [apath stringByAppendingPathComponent:relPath];
87   if (![fm fileExistsAtPath:apath]) {
88     apath = [env objectForKey:@"GNUSTEP_LOCAL_ROOT"];
89     apath = [apath stringByAppendingPathComponent:relPath];
90   }
91   if (![fm fileExistsAtPath:apath]) {
92     apath = [env objectForKey:@"GNUSTEP_SYSTEM_ROOT"];
93     apath = [apath stringByAppendingPathComponent:relPath];
94   }
95   if (![fm fileExistsAtPath:apath]) {
96     apath = relPath;
97   }
98   if (![fm fileExistsAtPath:apath]) {
99     NSLog(@"ERROR: cannot find Languages.plist resource "
100           @"of NGObjWeb library !");
101   }
102 #endif
103   return apath;
104 }
105
106 + (void)initialize {
107   static BOOL isInitialized = NO;
108   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
109   NSDictionary   *langMap;
110   NSString       *apath;
111
112   if (isInitialized) return;
113   isInitialized = YES;
114   
115   NSAssert2([super version] == 5,
116             @"invalid superclass (%@) version %i !",
117             NSStringFromClass([self superclass]), [super version]);
118   
119   debugOn = [WOApplication isDebuggingEnabled];
120     
121   /* apply defaults on some globals ... */
122     
123   apath = [ud stringForKey:@"WORequestValueSessionID"];
124   if ([apath length] > 0)
125     WORequestValueSessionID = [apath copy];
126   apath = [ud stringForKey:@"WORequestValueInstance"];
127   if ([apath length] > 0)
128     WORequestValueInstance = [apath copy];
129   apath = [ud stringForKey:@"WONoSelectionString"];
130   if ([apath length] > 0)
131     WONoSelectionString = [apath copy];
132   
133   /* load language mappings */
134     
135   apath = [self lookupLanguagesPlist];
136   langMap = [NSDictionary dictionaryWithContentsOfFile:apath];
137   
138   if (langMap) {
139     NSDictionary *defs;
140     
141     defs = [NSDictionary dictionaryWithObject:langMap
142                          forKey:@"WOBrowserLanguageMappings"];
143     [ud registerDefaults:defs];
144   }
145   else
146     NSLog(@"WARNING: did not register browser language mappings: %@", apath);
147 }
148
149 /* parse URI */
150
151 - (void)_parseURI {
152   unsigned uriLen;
153   char     *uriBuf;
154   char     *uri;
155   NSString *serverUrl;
156
157   uriLen = [self->_uri cStringLength];
158   
159   uriBuf = uri = malloc(uriLen + 1);
160   [self->_uri getCString:uriBuf]; uriBuf[uriLen] = '\0';
161   
162   /* determine adaptor prefix */
163
164   if ((serverUrl = [self headerForKey:@"x-webobjects-adaptor-prefix"]))
165     self->adaptorPrefix = [serverUrl copyWithZone:[self zone]];
166   
167   if (self->adaptorPrefix == nil)
168     self->adaptorPrefix = @"";
169
170     /* new parse */
171   if (uri) {
172     const char *start = NULL;
173       
174     /* skip adaptor prefix */
175     if (self->adaptorPrefix)
176       uri += [self->adaptorPrefix cStringLength];
177     if (*uri == '\0') goto done;
178
179     /* parse application name */
180       
181     uri++; // skip '/'
182     start = uri;
183     while ((*uri != '\0') && (*uri != '/') && (*uri != '.'))
184       uri++;
185
186     if (*uri == '\0') {
187       self->appName =
188         [[NSString alloc] initWithCString:start length:(uri - start)];
189       goto done;
190     }
191     else if (*uri == '.') {
192       self->appName =
193         [[NSString alloc] initWithCString:start length:(uri - start)];
194
195       // skip appname trailer (eg .woa)
196       while ((*uri != '\0') && (*uri != '/'))
197         uri++;
198       if (*uri == '\0') goto done;
199       uri++; // skip '/'
200     }
201     else if (*uri == '/') {
202       self->appName =
203         [[NSString alloc] initWithCString:start length:(uri - start)];
204       uri++; // skip '/'
205     }
206     else
207       goto done; // invalid state !
208
209     if (*uri == '\0') goto done;
210     
211     /* parse request handler key */
212     
213     start = uri;
214     while ((*uri != '\0') && (*uri != '/') && (*uri != '?'))
215       uri++;
216     self->requestHandlerKey =
217       [[NSString alloc] initWithCString:start length:(uri - start)];
218     if (*uri == '\0') goto done;
219     if(*uri == '/'){
220       uri++; // skip '/'
221       /* parse request handler path */
222       
223       start = uri;
224       while (*uri != '\0' && (*uri != '?'))
225         uri++;
226       self->requestHandlerPath =
227         [[NSString alloc] initWithCString:start length:(uri - start)];
228     }
229     
230     /* parsing done (found '\0') */
231   done:
232     ; // required for MacOSX-S
233     if (uriBuf) free(uriBuf);
234   }
235 }
236
237 - (id)initWithMethod:(NSString *)_method
238   uri:(NSString *)__uri
239   httpVersion:(NSString *)_version
240   headers:(NSDictionary *)_headers
241   content:(NSData *)_body
242   userInfo:(NSDictionary *)_userInfo
243 {
244   if ((self = [super init])) {
245     self->_uri   = [__uri   copy];
246     self->method = [_method copy];
247     [self _parseURI];
248     
249     /* WOMessage */
250     [self setHTTPVersion:_version];
251     [self setContent:_body];
252     [self setUserInfo:_userInfo];
253     [self setHeaders:_headers];
254   }
255   return self;
256 }
257
258 - (void)dealloc {
259   [self->startDate          release];
260   [self->startStatistics    release];
261   [self->method             release];
262   [self->_uri               release];
263   [self->adaptorPrefix      release];
264   [self->requestHandlerKey  release];
265   [self->requestHandlerPath release];
266   [self->appName            release];
267   [self->formContent        release];
268   [self->request            release];
269   [super dealloc];
270 }
271
272 /* privates */
273
274 - (void)_setHttpRequest:(NGHttpRequest *)_request {
275   ASSIGN(self->request, _request);
276 }
277 - (NGHttpRequest *)httpRequest {
278   if (self->request == nil) {
279     /* construct request 'on-demand' */
280     self->request =
281       [[NSClassFromString(@"NGHttpRequest") alloc] initWithWORequest:self];
282   }
283   return self->request;
284 }
285
286 /* request handler */
287
288 - (void)setRequestHandlerKey:(NSString *)_key {
289   ASSIGNCOPY(self->requestHandlerKey, _key);
290 }
291 - (NSString *)requestHandlerKey { // new in WO4
292   if ([self isProxyRequest])
293     return @"proxy";
294   return self->requestHandlerKey;
295 }
296
297 - (void)setRequestHandlerPath:(NSString *)_path {
298   ASSIGNCOPY(self->requestHandlerPath, _path);
299 }
300 - (NSString *)requestHandlerPath { // new in WO4
301   return self->requestHandlerPath;
302 }
303
304 - (NSArray *)requestHandlerPathArray { // new in WO4
305   NSMutableArray *array = nil;
306   unsigned       clen;
307   char           *cstrBuf;
308   register char  *cstr;
309   
310   clen   = [self->requestHandlerPath cStringLength];
311   if (clen == 0)
312     return nil;
313   
314   cstrBuf = cstr = malloc(clen + 1);
315   [self->requestHandlerPath getCString:cstrBuf]; cstrBuf[clen] = '\0';
316   
317   do {
318     NSString *component = nil;
319     register char *tmp = cstr;
320
321     while ((*tmp != '\0') && (*tmp != '?') && (*tmp != '/'))
322       tmp++;
323     
324     component = ((tmp - cstr) == 0)
325       ? @""
326       : [[NSString alloc] initWithCString:cstr length:(tmp - cstr)];
327
328     if (component) {
329       if (array == nil) array = [NSMutableArray arrayWithCapacity:64];
330       [array addObject:component];
331       [component release]; component = nil;
332     }
333
334     cstr = tmp;
335     if (*cstr == '/') cstr++; // skip '/'
336   }
337   while ((*cstr != '\0') && (*cstr != '?'));
338
339   free(cstrBuf);
340   return [[array copy] autorelease];
341 }
342
343 /* WO methods */
344
345 - (BOOL)isFromClientComponent {
346   return NO;
347 }
348
349 - (NSString *)sessionID { // deprecated in WO4
350   return [self cookieValueForKey:self->appName];
351 }
352 - (NSString *)senderID { // deprecated in WO4
353   IS_DEPRECATED;
354   return [[[WOApplication application] context] senderID];
355 }
356
357 - (NSString *)contextID {
358   return [[[WOApplication application] context] contextID];
359   //return self->contextID;
360 }
361
362 - (NSString *)applicationName {
363   return self->appName;
364 }
365 - (NSString *)applicationHost {
366   return [[NSHost currentHost] name];
367 }
368
369 - (NSString *)adaptorPrefix {
370   return self->adaptorPrefix;
371 }
372
373 - (NSString *)method {
374   return self->method;
375 }
376 - (void)_hackSetURI:(NSString *)_vuri {
377   /* be careful, used by the WebDAV dispatcher for ZideLook range queries */
378   ASSIGNCOPY(self->_uri, _vuri);
379 }
380 - (NSString *)uri {
381   return self->_uri;
382 }
383 - (BOOL)isProxyRequest {
384   return [[self uri] isAbsoluteURL];
385 }
386
387 - (void)setStartDate:(NSCalendarDate *)_startDate {
388   ASSIGNCOPY(self->startDate, _startDate);
389 }
390 - (NSCalendarDate *)startDate {
391   return self->startDate;
392 }
393 - (id)startStatistics {
394   return self->startStatistics;
395 }
396
397 /* forms */
398
399 - (NSStringEncoding)formValueEncoding {
400   return NSUTF8StringEncoding;
401 }
402
403 - (void)setDefaultFormValueEncoding:(NSStringEncoding)_enc {
404   if (_enc != NSUTF8StringEncoding || _enc != NSASCIIStringEncoding)
405     [self notImplemented:_cmd];
406 }
407 - (NSStringEncoding)defaultFormValueEncoding {
408   return NSUTF8StringEncoding;
409 }
410
411 - (void)setFormValueEncodingDetectionEnabled:(BOOL)_flag {
412   if (_flag) [self notImplemented:_cmd];
413 }
414 - (BOOL)isFormValueEncodingDetectionEnabled {
415   return NO;
416 }
417
418 - (void)_parseQueryParameters:(NSString *)_s intoMap:(NGMutableHashMap *)_map {
419   NSEnumerator *e;
420   NSString *part;
421   
422   e = [[_s componentsSeparatedByString:@"&"] objectEnumerator];
423   while ((part = [e nextObject])) {
424     NSRange  r;
425     NSString *key, *value;
426           
427     r = [part rangeOfString:@"="];
428     if (r.length == 0) {
429       /* missing value of query parameter */
430       key   = [part stringByUnescapingURL];
431       value = @"1";
432     }
433     else {
434       key   = [[part substringToIndex:r.location] stringByUnescapingURL];
435       value = [[part substringFromIndex:(r.location + r.length)] 
436                      stringByUnescapingURL];
437     }
438     
439     [self->formContent addObject:value forKey:key];
440   }
441 }
442
443 - (NGHashMap *)_getFormParameters {
444   if (self->formContent) 
445     return self->formContent;
446   
447   if (self->request == nil) {
448     /*
449       TODO: add parsing of form values
450       
451       contained in URL:
452         a/blah?name=login&pwd=j
453       
454       contained in body:
455         Content-Type: application/x-www-form-urlencoded
456         browserconfig=%7BisJavaScriptEnabled%3DYES%3B%7D&login=r&button=login
457     */
458     NSRange  r;
459     NSString *query;
460     NSString *ctype;
461     BOOL     isMultiPartContent = NO, isFormContent = NO;
462     
463     r = [self->_uri rangeOfString:@"?"];
464     query = (r.length > 0)
465       ? [self->_uri substringFromIndex:(r.location + r.length)]
466       : nil;
467     
468     if ((ctype = [self headerForKey:@"content-type"]) != nil) {
469       isFormContent = [ctype hasPrefix:@"application/x-www-form-urlencoded"];
470       if (!isFormContent)
471         isMultiPartContent = [ctype hasPrefix:@"multipart/form-data"];
472     }
473     
474     if (query != nil || isFormContent || isMultiPartContent) {
475       NSAutoreleasePool *pool;
476       
477       pool = [[NSAutoreleasePool alloc] init];
478       self->formContent = [[NGMutableHashMap alloc] init];
479       
480       /* parse query string */
481       if (query)
482         [self _parseQueryParameters:query intoMap:self->formContent];
483       
484       /* parse content (if form content) */
485       if (isFormContent) {
486         [self _parseQueryParameters:[self contentAsString]
487               intoMap:self->formContent];
488       }
489       else if (isMultiPartContent) {
490         [self logWithFormat:
491                 @"ERROR: missing NGHttpRequest, cannot parse multipart"];
492       }
493       
494       [pool release];
495     }
496     else
497       self->formContent = [[NGHashMap alloc] init];
498   }
499   else
500     self->formContent = [[self->request formParameters] retain];
501   return self->formContent;
502 }
503
504 - (NSArray *)formValueKeys {
505   id paras = [self _getFormParameters];
506   
507   if ([paras respondsToSelector:@selector(allKeys)])
508     return [paras allKeys];
509   
510   return nil;
511 }
512
513 - (NSString *)formValueForKey:(NSString *)_key {
514   NSString *value;
515   id paras;
516   
517   value = nil;
518   paras = [self _getFormParameters];
519   if ([paras respondsToSelector:@selector(objectForKey:)])
520     value = [(NSDictionary *)paras objectForKey:_key];
521   
522   return value;
523 }
524 - (NSArray *)formValuesForKey:(NSString *)_key {
525   id paras = [self _getFormParameters];
526   return [paras respondsToSelector:@selector(objectsForKey:)]
527     ? [paras objectsForKey:_key]
528     : nil;
529 }
530
531 - (NSDictionary *)formValues {
532   id paras;
533   
534   if ((paras = [self _getFormParameters]) == nil)
535     return nil;
536   
537   /* check class, could change with different HTTP adaptor */
538   
539   if ([paras isKindOfClass:[NGHashMap class]])
540     return [paras asDictionaryWithArraysForValues];
541   if ([paras isKindOfClass:[NSDictionary class]])
542     return paras;
543   
544   [self logWithFormat:
545           @"ERROR(%s): don't know how to deal with form object: %@", paras];
546   return nil;
547 }
548
549 // ******************** Headers ******************
550
551 - (NSString *)languageForBrowserLanguageCode:(NSString *)_e {
552   static NSDictionary *langMap = nil;
553   NSString *lang;
554   
555   if (_e == nil) return nil;
556   
557   if (langMap == nil) {
558     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
559     
560     langMap = [[ud dictionaryForKey:@"WOBrowserLanguageMappings"] copy];
561     if (langMap == nil) {
562       [self debugWithFormat:
563               @"WARNING: did not find browser language mappings!"];
564     }
565   }
566   
567   _e = [_e lowercaseString];
568   
569   lang = [langMap objectForKey:_e];
570   if (lang == nil && [_e length] > 2) {
571     /* process constructs like 'de-ch' */
572     if ([_e characterAtIndex:2] == '-') {
573       NSString *ek;
574       
575       ek = [_e substringToIndex:2];
576       lang = [langMap objectForKey:ek];
577     }
578   }
579   if (lang == nil && ![_e isEqualToString:@"*"]) {
580     [self debugWithFormat:@"did not find '%@' in map: %@", 
581             _e, [[langMap allKeys] componentsJoinedByString:@", "]];
582   }
583   return lang;
584 }
585
586 - (NSString *)_languageFromUserAgent {
587   /*
588     user-agent sometimes stores the browser-language,
589     eg: Opera/5.0 (Linux 2.2.18 i686; U)  [en]
590   */
591   NSString *ua;
592   NSRange  rng;
593   NSString *tmp;
594   
595   if ((ua = [self headerForKey:@"user-agent"]) == nil)
596     return nil;
597
598   rng = [ua rangeOfString:@"["];
599   if (rng.length == 0)
600     return nil;
601       
602   tmp = [ua substringFromIndex:(rng.location + rng.length)];
603   rng = [tmp rangeOfString:@"]"];
604   if (rng.length > 0)
605     tmp = [tmp substringToIndex:rng.location];
606
607   return [self languageForBrowserLanguageCode:tmp];
608 }
609
610 - (NSArray *)browserLanguages { /* new in WO4 */
611   static NSArray *defLangs = nil;
612   NSString       *hheader;
613   NSEnumerator   *e;
614   NSMutableArray *languages;
615   NSString       *language;
616   NSString       *tmp;
617   
618   languages = [NSMutableArray arrayWithCapacity:8];
619   
620   e = [[self headersForKey:@"accept-language"] objectEnumerator];
621   while ((hheader = [e nextObject])) {
622     NSEnumerator *le;
623     
624     le = [[hheader componentsSeparatedByString:@","] objectEnumerator];
625     while ((language = [le nextObject])) {
626       NSString *tmp;
627       NSRange  r;
628       
629       /* split off the quality (eg 'en;0.96') */
630       r = [language rangeOfString:@";"];
631       if (r.length > 0)
632         language = [language substringToIndex:r.location];
633       language = [language stringByTrimmingSpaces];
634       
635       /* check in map */
636       if ((tmp = [self languageForBrowserLanguageCode:language]))
637         language = tmp;
638       
639       if ([languages containsObject:language])
640         continue;
641       
642       [languages addObject:language];
643     }
644   }
645   
646   if ((tmp = [self _languageFromUserAgent]))
647     [languages addObject:tmp];
648   
649   if (defLangs == nil) {
650     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
651     defLangs = [[ud arrayForKey:@"WODefaultLanguages"] copy];
652   }
653   [languages addObjectsFromArray:defLangs];
654   
655   //[self debugWithFormat:@"languages: %@", languages];
656   return [[languages copy] autorelease];
657 }
658
659 /* cookies */
660
661 - (NSArray *)cookieValuesForKey:(NSString *)_key {
662   NSEnumerator   *ecookies;
663   NSMutableArray *values;
664   WOCookie       *cookie;
665   
666   values  = [NSMutableArray arrayWithCapacity:8];
667   
668   ecookies = [[self cookies] objectEnumerator];
669   while ((cookie = [ecookies nextObject])) {
670     if ([_key isEqualToString:[cookie name]])
671       [values addObject:[cookie value]];
672   }
673   
674   return values;
675 }
676
677 - (NSString *)cookieValueForKey:(NSString *)_key {
678   NSEnumerator *ecookies;
679   WOCookie     *cookie;
680   
681   ecookies = [[self cookies] objectEnumerator];
682   while ((cookie = [ecookies nextObject])) {
683     if ([_key isEqualToString:[cookie name]])
684       return [cookie value];
685   }
686   return nil;
687 }
688
689 - (NSDictionary *)cookieValues {
690   NSEnumerator        *ecookies;
691   NSMutableDictionary *values;
692   WOCookie            *cookie;
693   
694   values  = [NSMutableDictionary dictionaryWithCapacity:8];
695   
696   ecookies = [[self cookies] objectEnumerator];
697   while ((cookie = [ecookies nextObject])) {
698     NSString       *name;
699     NSMutableArray *vArray;
700     
701     name   = [cookie name];
702     vArray = [values objectForKey:name];
703     
704     if (vArray == nil) {
705       vArray = [[NSMutableArray alloc] initWithCapacity:8];
706       [values setObject:vArray forKey:name];
707       [vArray release];
708     }
709     
710     [vArray addObject:[cookie value]];
711   }
712   
713   return values;
714 }
715
716 /* logging */
717
718 - (BOOL)isDebuggingEnabled {
719   return debugOn;
720 }
721 - (NSString *)loggingPrefix {
722   return [NSString stringWithFormat:@"|Rq:%@ 0x%08X|", 
723                      [self method], self];
724 }
725
726 /* description */
727
728 - (NSString *)description {
729   NSMutableString *str;
730
731   str = [NSMutableString stringWithCapacity:256];
732   [str appendFormat:@"<%@[0x%08X]:", NSStringFromClass([self class]), self];
733   [str appendFormat:@" method=%@",   [self method]];
734   [str appendFormat:@" uri=%@",      [self uri]];
735   [str appendFormat:@" app=%@",      self->appName];
736   [str appendFormat:@" rqKey=%@",    [self requestHandlerKey]];
737   [str appendFormat:@" rqPath=%@",   [self requestHandlerPath]];
738   [str appendString:@">"];
739   return str;
740 }
741
742 @end /* WORequest */