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