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