]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOMessage.m
added support for 'disabled' binding as in WO
[sope] / sope-appserver / NGObjWeb / WOMessage.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/WOMessage.h>
23 #include <NGExtensions/NGHashMap.h>
24 #include <NGExtensions/NSString+misc.h>
25 #include "common.h"
26
27 // #define STRIP_MULTIPLE_SPACES // this doesn't work with <pre> tags !
28
29 @implementation WOMessage
30
31 typedef struct _WOMessageProfileInfo {
32   unsigned append;
33   unsigned appendC;
34   unsigned appendChr;
35   unsigned appendXML;
36   unsigned appendHTML;
37 } WOMessageProfileInfo;
38
39 static Class            NSStringClass    = Nil;
40 static BOOL             printProfile     = NO;
41 static int              DEF_CONTENT_SIZE = 20000;
42 static NSStringEncoding defaultEncoding  = 0;
43
44 static WOMessageProfileInfo profile    = { 0, 0, 0, 0, 0 };
45 static WOMessageProfileInfo profilemax = { 0, 0, 0, 0, 0 };
46 static WOMessageProfileInfo profiletot = { 0, 0, 0, 0, 0 };
47
48 + (void)initialize {
49   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
50   
51   if (NSStringClass == Nil)
52     NSStringClass = [NSString class];
53   
54   printProfile = [ud boolForKey:@"WOProfileResponse"];
55
56   if ([ud boolForKey:@"WOMessageUseUTF8"]) {
57     defaultEncoding = NSUTF8StringEncoding;
58   }
59   else {
60 #ifdef __APPLE__
61     //#warning default encoding is ISO Latin 1 ...
62     // TODO: why are we doing this?
63     defaultEncoding = NSISOLatin1StringEncoding;
64 #else
65     defaultEncoding = [NSStringClass defaultCStringEncoding];
66 #endif
67   }
68 }
69
70 static inline void _ensureBody(WOMessage *self) {
71   if (self->content == nil) {
72     self->content = [[NSMutableData alloc] initWithCapacity:DEF_CONTENT_SIZE];
73     self->addBytes = (void *)
74       [self->content methodForSelector:@selector(appendBytes:length:)];
75   }
76 }
77
78 static __inline__ NSMutableData *_checkBody(WOMessage *self) {
79   if (self->content == nil) {
80     self->content = [[NSMutableData alloc] initWithCapacity:DEF_CONTENT_SIZE];
81     self->addBytes = (void *)
82       [self->content methodForSelector:@selector(appendBytes:length:)];
83   }
84   return self->content;
85 }
86
87 + (int)version {
88   return 5;
89 }
90
91 + (void)setDefaultEncoding:(NSStringEncoding)_encoding {
92   defaultEncoding = _encoding;
93 }
94 + (NSStringEncoding)defaultEncoding {
95   return defaultEncoding;
96 }
97
98 - (id)init {
99   if ((self = [super init])) {
100     self->contentEncoding = [[self class] defaultEncoding];
101     
102     self->addChar = (void*)
103       [self methodForSelector:@selector(appendContentCharacter:)];
104     self->addStr  = (void *)
105       [self methodForSelector:@selector(appendContentString:)];
106     self->addHStr = (void *)
107       [self methodForSelector:@selector(appendContentHTMLString:)];
108     self->addCStr = (void *)
109       [self methodForSelector:@selector(appendContentCString:)];
110     
111     self->header  = [[NGMutableHashMap allocWithZone:[self zone]] init];
112     self->version = @"HTTP/1.0";
113   }
114   return self;
115 }
116
117 - (void)dealloc {
118   [self->domCache      release];
119   [self->contentStream release];
120   [self->cookies  release];
121   [self->version  release];
122   [self->content  release];
123   [self->header   release];
124   [self->userInfo release];
125   [super dealloc];
126 }
127
128 /* accessors */
129
130 - (void)setUserInfo:(NSDictionary *)_userInfo {
131   ASSIGN(self->userInfo, _userInfo);
132 }
133 - (NSDictionary *)userInfo {
134   return self->userInfo;
135 }
136
137 /* HTTP */
138
139 - (void)setHTTPVersion:(NSString *)_httpVersion {
140   id old;
141   if (self->version == _httpVersion)
142     return;
143   old = self->version;
144   self->version = [_httpVersion copy];
145   [old release];
146   
147   if (self->version != nil && ![_httpVersion hasPrefix:@"HTTP/"]) {
148     [self warnWithFormat:
149             @"you apparently passed in an invalid HTTP version: '%@'",
150             _httpVersion];
151   }
152 }
153 - (void)setHttpVersion:(NSString *)_httpVersion {
154   // deprecated
155   [self setHTTPVersion:_httpVersion];
156 }
157 - (NSString *)httpVersion {
158   return self->version;
159 }
160
161 /* cookies (new in WO4) */
162
163 - (void)addCookie:(WOCookie *)_cookie {
164   if (self->cookies == nil)
165     self->cookies = [[NSMutableArray allocWithZone:[self zone]] init];
166   [self->cookies addObject:_cookie];
167 }
168
169 - (void)removeCookie:(WOCookie *)_cookie {
170   [self->cookies removeObject:_cookie];
171 }
172
173 - (NSArray *)cookies {
174   return self->cookies;
175 }
176
177 /* header */
178
179 - (void)setHeaders:(NSDictionary *)_headers {
180   NSEnumerator *keys;
181   NSString *key;
182
183   keys = [_headers keyEnumerator];
184   while ((key = [keys nextObject])) {
185     id value;
186     
187     value = [_headers objectForKey:key];
188     if ([value isKindOfClass:[NSArray class]]) {
189       NSEnumerator *e = [value objectEnumerator];
190
191       while ((value = [e nextObject]))
192         [self appendHeader:value forKey:key];
193     }
194     else
195       [self appendHeader:value forKey:key];
196   }
197 }
198
199 - (void)setHeader:(NSString *)_header forKey:(NSString *)_key {
200   [self->header setObject:[_header stringValue] forKey:_key];
201 }
202 - (NSString *)headerForKey:(NSString *)_key {
203   return [[self->header objectEnumeratorForKey:_key] nextObject];
204 }
205
206 - (void)appendHeader:(NSString *)_header forKey:(NSString *)_key {
207   [self->header addObject:_header forKey:_key];
208 }
209 - (void)appendHeaders:(NSArray *)_headers forKey:(NSString *)_key {
210   [self->header addObjects:_headers forKey:_key];
211 }
212
213 - (void)setHeaders:(NSArray *)_headers forKey:(NSString *)_key {
214   NSEnumerator *e;
215   id value;
216
217   e = [_headers objectEnumerator];
218
219   [self->header removeAllObjectsForKey:_key];
220   
221   while ((value = [e nextObject]))
222     [self->header addObject:value forKey:_key];
223 }
224 - (NSArray *)headersForKey:(NSString *)_key {
225   NSEnumerator *values;
226
227   if ((values = [self->header objectEnumeratorForKey:_key])) {
228     NSMutableArray *array = nil;
229     id value = nil;
230     
231     array = [[NSMutableArray allocWithZone:[self zone]] init];
232
233     while ((value = [values nextObject]))
234       [array addObject:value];
235
236     return [array autorelease];
237   }
238   return nil;
239 }
240
241 - (NSArray *)headerKeys {
242   NSEnumerator *values;
243
244   if ((values = [self->header keyEnumerator])) {
245     NSMutableArray *array  = nil;
246     id name = nil;
247     array = [[NSMutableArray alloc] init];
248     
249     while ((name = [values nextObject]))
250       [array addObject:name];
251
252     name = [array copy];
253     [array release];
254     
255     return [name autorelease];
256   }
257   return nil;
258 }
259
260 - (NSDictionary *)headers {
261   return [self->header asDictionary];
262 }
263
264 - (NSString *)headersAsString {
265   NSMutableString *ms;
266   NSEnumerator *keys;
267   NSString     *key;
268   
269   ms = [NSMutableString stringWithCapacity:1024];
270   
271   /* headers */
272   keys = [[self headerKeys] objectEnumerator];
273   while ((key = [keys nextObject])) {
274     NSEnumerator *vals;
275     id val;
276     
277     vals = [[self headersForKey:key] objectEnumerator];
278     while ((val = [vals nextObject])) {
279       [ms appendString:key];
280       [ms appendString:@": "];
281       [ms appendString:[val stringValue]];
282       [ms appendString:@"\r\n"];
283     }
284   }
285   return ms;
286 }
287
288 /* profiling */
289
290 - (void)_printProfile {
291   if (profile.append + profile.appendC + profile.appendChr +
292       profile.appendXML + profile.appendHTML == 0)
293     return;
294   
295   /* calc max */
296   if (profile.append > profilemax.append)
297     profilemax.append = profile.append;
298   if (profile.appendC > profilemax.appendC) 
299     profilemax.appendC = profile.appendC;
300   if (profile.appendHTML > profilemax.appendHTML) 
301     profilemax.appendHTML = profile.appendHTML;
302   
303   /* calc total */
304   profiletot.append     += profile.append;
305   profiletot.appendC    += profile.appendC;
306   profiletot.appendChr  += profile.appendChr;
307   profiletot.appendHTML += profile.appendHTML;
308   profiletot.appendXML  += profile.appendXML;
309   
310   /* print */
311   
312   [self logWithFormat:@"PROFILE: WOResponse:\n"
313         @"  appendContentString:     %8i max %8i total %8i\n"
314         @"  appendContentCString:    %8i max %8i total %8i\n"
315         @"  appendContentCharacter:  %8i max %8i total %8i\n"
316         @"  appendContentXMLString:  %8i max %8i total %8i\n"
317         @"  appendContentHTMLString: %8i max %8i total %8i\n",
318         profile.append,     profilemax.append,     profiletot.append,
319         profile.appendC,    profilemax.appendC,    profiletot.appendC,
320         profile.appendChr,  profilemax.appendChr,  profiletot.appendChr,
321         profile.appendXML,  profilemax.appendXML,  profiletot.appendXML,
322         profile.appendHTML, profilemax.appendHTML, profiletot.appendHTML];
323   
324   /* reset profile */
325   memset(&profile, 0, sizeof(profile));
326 }
327
328 /* generic content */
329
330 - (void)setContent:(NSData *)_data {
331   id old;
332   
333   if (self->content == (id)_data) 
334     return;
335   
336   old = self->content;
337   self->content = [_data mutableCopy];
338   [old release];
339 }
340 - (NSData *)content {
341   if (printProfile) [self _printProfile];
342   return self->content;
343 }
344 - (NSString *)contentAsString {
345   NSString *s;
346   NSData   *c;
347   
348   if ((c = [self content]) == nil)
349     return nil;
350
351   s = [[NSString alloc] initWithData:c encoding:[self contentEncoding]];
352   return [s autorelease];
353 }
354 - (BOOL)doesStreamContent {
355   return self->contentStream != nil ? YES : NO;
356 }
357
358 - (void)setContentEncoding:(NSStringEncoding)_encoding {
359   self->contentEncoding = _encoding;
360 }  
361 - (NSStringEncoding)contentEncoding {
362   return self->contentEncoding;
363 }
364
365 /* structured content */
366
367 - (void)appendContentBytes:(const void *)_bytes length:(unsigned)_l {
368   if (_bytes == NULL || _l == 0) return;
369   if (self->content == nil) _ensureBody(self);
370   self->addBytes(self->content, @selector(appendBytes:length:), _bytes, _l);
371 }
372
373 - (void)appendContentCharacter:(unichar)_c {
374   unsigned char bc[2] = {0, 0};
375   
376   profile.appendChr++;
377   
378   *(&bc[0]) = _c;
379   if (self->content == nil) _ensureBody(self);
380   
381   switch (self->contentEncoding) {
382     case NSISOLatin1StringEncoding:
383     case NSASCIIStringEncoding:
384       /* those two encodings are == Unicode ... */
385       self->addBytes(self->content, @selector(appendBytes:length:), &(bc[0]), 1);
386       break;
387       
388     case NSUnicodeStringEncoding:
389       /* directly add 16-byte char ... */
390       self->addBytes(self->content, @selector(appendBytes:length:), 
391                      &_c, sizeof(_c));
392       break;
393       
394     case NSUTF8StringEncoding:
395       /* directly add a byte if 1-byte char (<127 in UTF-8) */
396       if (_c < 127) {
397         self->addBytes(self->content, @selector(appendBytes:length:), &(bc[0]), 1);
398         break;
399       }
400       /* *intended* fall-through !!! */
401       
402     default: {
403       /* otherwise create string for char and ask string to convert to data */
404       NSString *s;
405     
406 #if DEBUG
407       [self warnWithFormat:
408               @"using NSString to add a character %i,0x%08X"
409               @"(slow, encoding=%i).", _c, _c, self->contentEncoding];
410 #endif
411       
412       if ((s = [[NSStringClass alloc] initWithCharacters:&_c length:1])) {
413         self->addStr(self, @selector(appendContentString:), s);
414         [s release];
415       }
416       break;
417     }
418   }
419 }
420 - (void)appendContentData:(NSData *)_data {
421   if (_data == nil) return;
422   [_checkBody(self) appendData:_data];
423 }
424
425 - (void)appendContentHTMLAttributeValue:(NSString *)_value {
426   self->addStr(self, @selector(appendContentString:), 
427                [_value stringByEscapingHTMLAttributeValue]);
428   profile.appendHTML++;
429 }
430 - (void)appendContentHTMLString:(NSString *)_value {
431   self->addStr(self, @selector(appendContentString:), 
432                [_value stringByEscapingHTMLString]);
433   profile.appendHTML++;
434 }
435
436 - (void)appendContentXMLAttributeValue:(NSString *)_value {
437   self->addStr(self, @selector(appendContentString:), 
438                [_value stringByEscapingXMLAttributeValue]);
439   profile.appendXML++;
440 }
441 - (void)appendContentXMLString:(NSString *)_value {
442   if (_value == nil) return;
443   self->addStr(self, @selector(appendContentString:), 
444                [_value stringByEscapingXMLString]);
445   profile.appendXML++;
446 }
447
448 - (void)appendContentCString:(const unsigned char *)_value {
449   /* we assume that cString == ASCII !!! */
450   register unsigned len;
451
452   profile.appendC++;
453
454   if (self->content == nil) _ensureBody(self);
455   if ((len = _value ? strlen(_value) : 0) == 0)
456     return;
457   
458   switch (self->contentEncoding) {
459     case NSISOLatin1StringEncoding:
460     case NSASCIIStringEncoding:
461     case NSUTF8StringEncoding:
462       self->addBytes(self->content, @selector(appendBytes:length:), 
463                      _value, len);
464       return;
465     
466     case NSUnicodeStringEncoding:
467     default: {
468       /* worst case ... */
469       NSString *s;
470       
471       if ((s = [[NSString alloc] initWithCString:_value])) {
472         self->addStr(self, @selector(appendContentString:), s);
473         [s release];
474       }
475     }
476   }
477 }
478
479 - (void)appendContentString:(NSString *)_value {
480   NSData *cdata;
481   
482   profile.append++;
483   
484   if ([_value length] == 0)
485     return;
486   
487   cdata = [_value dataUsingEncoding:self->contentEncoding
488                   allowLossyConversion:NO];
489 #if 0
490   if ([_value length] > 9000) {
491     char *cstr;
492     unsigned i, len;
493
494 #if 0
495     cstr = [cdata bytes];
496     len  = [cdata length];
497 #else
498     cstr = [_value cString];
499     len  = [_value cStringLength];
500 #endif
501
502     printf("\n\n*** add contentstring (value-enc=%i,%i,%i) "
503            "(len=%i, dlen=%i): '",
504            [_value smallestEncoding],
505            [_value fastestEncoding],
506            self->contentEncoding,
507            [_value length], len);
508     fwrite(cstr, 1, len, stdout);
509     printf("'\n");
510     
511     for (i = len - 20; i < len; i++)
512       printf("%5i: 0x%08X %4i\n", i, cstr[i], cstr[i]);
513     fflush(stdout);
514   }
515 #endif
516   if (cdata == NULL) {
517     [self errorWithFormat:
518             @"(%s): could not convert string non-lossy to encoding %i !",
519             __PRETTY_FUNCTION__, self->contentEncoding];
520     cdata = [_value dataUsingEncoding:self->contentEncoding
521                     allowLossyConversion:YES];
522   }
523   [self appendContentData:cdata];
524 }
525
526 @end /* WOMessage */
527
528 @implementation WOMessage(Escaping)
529
530 static inline void
531 _escapeHtmlValue(unsigned char c, unsigned char *buf, int *pos)
532 {
533   int j = *pos;
534   switch (c) {
535     case '&':
536       buf[j] = '&'; j++; buf[j] = 'a'; j++; buf[j] = 'm'; j++;
537       buf[j] = 'p'; j++; buf[j] = ';';
538       break;
539     case '"':
540       buf[j] = '&'; j++; buf[j] = 'q'; j++; buf[j] = 'u'; j++;
541       buf[j] = 'o'; j++; buf[j] = 't'; j++; buf[j] = ';';
542       break;
543     case '<':
544       buf[j] = '&'; j++; buf[j] = 'l'; j++; buf[j] = 't'; j++;
545       buf[j] = ';';
546       break;
547     case '>':
548       buf[j] = '&'; j++; buf[j] = 'g'; j++; buf[j] = 't'; j++;
549       buf[j] = ';';
550       break;
551
552     default:
553       buf[j] = c;
554       break;
555   }
556   *pos = j;
557 }
558
559 static inline void
560 _escapeAttrValue(unsigned char c, unsigned char *buf, int *pos)
561 {
562   int j = *pos;
563   switch (c) {
564     case '&':
565       buf[j] = '&'; j++; buf[j] = 'a'; j++; buf[j] = 'm'; j++;
566       buf[j] = 'p'; j++; buf[j] = ';';
567       break;
568     case '"':
569       buf[j] = '&'; j++; buf[j] = 'q'; j++; buf[j] = 'u'; j++;
570       buf[j] = 'o'; j++; buf[j] = 't'; j++; buf[j] = ';';
571       break;
572     case '<':
573       buf[j] = '&'; j++; buf[j] = 'l'; j++; buf[j] = 't'; j++;
574       buf[j] = ';';
575       break;
576     case '>':
577       buf[j] = '&'; j++; buf[j] = 'g'; j++; buf[j] = 't'; j++;
578       buf[j] = ';';
579       break;
580
581     case '\t':
582       buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '9'; j++;
583       buf[j] = ';';
584       break;
585     case '\n':
586       buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '1'; j++;
587       buf[j] = '0'; j++; buf[j] = ';';
588       break;
589     case '\r':
590       buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '1'; j++;
591       buf[j] = '3'; j++; buf[j] = ';';
592       break;
593           
594     default:
595       buf[j] = c;
596       break;
597   }
598   *pos = j;
599 }
600
601
602 + (NSString *)stringByEscapingHTMLString:(NSString *)_string {
603   return [_string stringByEscapingHTMLString];
604 }
605
606 + (NSString *)stringByEscapingHTMLAttributeValue:(NSString *)_string {
607   return [_string stringByEscapingHTMLAttributeValue];
608 }
609
610 @end /* WOMessage(Escaping) */