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