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