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