2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
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
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.
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
22 #include <NGObjWeb/WOMessage.h>
23 #include <NGExtensions/NGHashMap.h>
24 #include <NGExtensions/NSString+misc.h>
28 // #define STRIP_MULTIPLE_SPACES // this doesn't work with <pre> tags !
30 @implementation WOMessage
32 typedef struct _WOMessageProfileInfo {
38 } WOMessageProfileInfo;
40 static Class NSStringClass = Nil;
41 static BOOL printProfile = NO;
42 static int DEF_CONTENT_SIZE = 20000;
43 static NSStringEncoding defaultEncoding = 0;
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 };
50 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
52 if (NSStringClass == Nil)
53 NSStringClass = [NSString class];
55 printProfile = [ud boolForKey:@"WOProfileResponse"];
57 if ([ud boolForKey:@"WOMessageUseUTF8"]) {
58 defaultEncoding = NSUTF8StringEncoding;
62 //#warning default encoding is ISO Latin 1 ...
63 // TODO: why are we doing this?
64 defaultEncoding = NSISOLatin1StringEncoding;
66 defaultEncoding = [NSStringClass defaultCStringEncoding];
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:)];
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:)];
92 + (void)setDefaultEncoding:(NSStringEncoding)_encoding {
93 defaultEncoding = _encoding;
95 + (NSStringEncoding)defaultEncoding {
96 return defaultEncoding;
100 if ((self = [super init])) {
101 self->contentEncoding = [[self class] defaultEncoding];
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:)];
112 self->header = [[NGMutableHashMap allocWithZone:[self zone]] init];
113 self->version = @"HTTP/1.0";
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];
131 - (void)setUserInfo:(NSDictionary *)_userInfo {
132 ASSIGN(self->userInfo, _userInfo);
134 - (NSDictionary *)userInfo {
135 return self->userInfo;
140 - (void)setHTTPVersion:(NSString *)_httpVersion {
142 if (self->version == _httpVersion)
145 self->version = [_httpVersion copy];
148 if (self->version != nil && ![_httpVersion hasPrefix:@"HTTP/"]) {
149 [self warnWithFormat:
150 @"you apparently passed in an invalid HTTP version: '%@'",
154 - (void)setHttpVersion:(NSString *)_httpVersion {
156 [self setHTTPVersion:_httpVersion];
158 - (NSString *)httpVersion {
159 return self->version;
162 /* cookies (new in WO4) */
164 - (void)addCookie:(WOCookie *)_cookie {
165 if (self->cookies == nil)
166 self->cookies = [[NSMutableArray allocWithZone:[self zone]] init];
167 [self->cookies addObject:_cookie];
170 - (void)removeCookie:(WOCookie *)_cookie {
171 [self->cookies removeObject:_cookie];
174 - (NSArray *)cookies {
175 return self->cookies;
180 - (void)setHeaders:(NSDictionary *)_headers {
184 keys = [_headers keyEnumerator];
185 while ((key = [keys nextObject])) {
188 value = [_headers objectForKey:key];
189 if ([value isKindOfClass:[NSArray class]]) {
190 NSEnumerator *e = [value objectEnumerator];
192 while ((value = [e nextObject]))
193 [self appendHeader:value forKey:key];
196 [self appendHeader:value forKey:key];
200 - (void)setHeader:(NSString *)_header forKey:(NSString *)_key {
201 [self->header setObject:[_header stringValue] forKey:_key];
203 - (NSString *)headerForKey:(NSString *)_key {
204 return [[self->header objectEnumeratorForKey:_key] nextObject];
207 - (void)appendHeader:(NSString *)_header forKey:(NSString *)_key {
208 [self->header addObject:_header forKey:_key];
210 - (void)appendHeaders:(NSArray *)_headers forKey:(NSString *)_key {
211 [self->header addObjects:_headers forKey:_key];
214 - (void)setHeaders:(NSArray *)_headers forKey:(NSString *)_key {
218 e = [_headers objectEnumerator];
220 [self->header removeAllObjectsForKey:_key];
222 while ((value = [e nextObject]))
223 [self->header addObject:value forKey:_key];
225 - (NSArray *)headersForKey:(NSString *)_key {
226 NSEnumerator *values;
228 if ((values = [self->header objectEnumeratorForKey:_key])) {
229 NSMutableArray *array = nil;
232 array = [[NSMutableArray allocWithZone:[self zone]] init];
234 while ((value = [values nextObject]))
235 [array addObject:value];
237 return [array autorelease];
242 - (NSArray *)headerKeys {
243 NSEnumerator *values;
245 if ((values = [self->header keyEnumerator])) {
246 NSMutableArray *array = nil;
248 array = [[NSMutableArray alloc] init];
250 while ((name = [values nextObject]))
251 [array addObject:name];
256 return [name autorelease];
261 - (NSDictionary *)headers {
262 return [self->header asDictionary];
265 - (NSString *)headersAsString {
270 ms = [NSMutableString stringWithCapacity:1024];
273 keys = [[self headerKeys] objectEnumerator];
274 while ((key = [keys nextObject])) {
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"];
291 - (void)_printProfile {
292 if (profile.append + profile.appendC + profile.appendChr +
293 profile.appendXML + profile.appendHTML == 0)
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;
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;
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];
326 memset(&profile, 0, sizeof(profile));
329 /* generic content */
331 - (void)setContent:(NSData *)_data {
334 if (self->content == (id)_data)
338 self->content = [_data mutableCopy];
341 - (NSData *)content {
342 if (printProfile) [self _printProfile];
343 return self->content;
345 - (NSString *)contentAsString {
349 if ((c = [self content]) == nil)
352 s = [[NSString alloc] initWithData:c encoding:[self contentEncoding]];
353 return [s autorelease];
355 - (BOOL)doesStreamContent {
356 return self->contentStream != nil ? YES : NO;
359 - (void)setContentEncoding:(NSStringEncoding)_encoding {
360 self->contentEncoding = _encoding;
362 - (NSStringEncoding)contentEncoding {
363 return self->contentEncoding;
366 /* structured content */
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);
374 - (void)appendContentCharacter:(unichar)_c {
375 unsigned char bc[2] = {0, 0};
380 if (self->content == nil) _ensureBody(self);
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);
389 case NSUnicodeStringEncoding:
390 /* directly add 16-byte char ... */
391 self->addBytes(self->content, @selector(appendBytes:length:),
395 case NSUTF8StringEncoding:
396 /* directly add a byte if 1-byte char (<127 in UTF-8) */
398 self->addBytes(self->content, @selector(appendBytes:length:), &(bc[0]), 1);
401 /* *intended* fall-through !!! */
404 /* otherwise create string for char and ask string to convert to data */
408 [self warnWithFormat:
409 @"using NSString to add a character %i,0x%p"
410 @"(slow, encoding=%i).", _c, _c, self->contentEncoding];
413 if ((s = [[NSStringClass alloc] initWithCharacters:&_c length:1])) {
414 self->addStr(self, @selector(appendContentString:), s);
421 - (void)appendContentData:(NSData *)_data {
422 if (_data == nil) return;
423 [_checkBody(self) appendData:_data];
426 - (void)appendContentHTMLAttributeValue:(NSString *)_value {
427 self->addStr(self, @selector(appendContentString:),
428 [_value stringByEscapingHTMLAttributeValue]);
429 profile.appendHTML++;
431 - (void)appendContentHTMLString:(NSString *)_value {
432 self->addStr(self, @selector(appendContentString:),
433 [_value stringByEscapingHTMLString]);
434 profile.appendHTML++;
437 - (void)appendContentXMLAttributeValue:(NSString *)_value {
438 self->addStr(self, @selector(appendContentString:),
439 [_value stringByEscapingXMLAttributeValue]);
442 - (void)appendContentXMLString:(NSString *)_value {
443 if (_value == nil) return;
444 self->addStr(self, @selector(appendContentString:),
445 [_value stringByEscapingXMLString]);
449 - (void)appendContentCString:(const unsigned char *)_value {
450 /* we assume that cString == ASCII !!! */
451 register unsigned len;
455 if (self->content == nil) _ensureBody(self);
456 if ((len = _value ? strlen((char *)_value) : 0) == 0)
459 switch (self->contentEncoding) {
460 case NSISOLatin1StringEncoding:
461 case NSASCIIStringEncoding:
462 case NSUTF8StringEncoding:
463 self->addBytes(self->content, @selector(appendBytes:length:),
467 case NSUnicodeStringEncoding:
472 if ((s = [[NSString alloc] initWithCString:(char *)_value])) {
473 self->addStr(self, @selector(appendContentString:), s);
480 - (void)appendContentString:(NSString *)_value {
485 if ([_value length] == 0)
488 cdata = [_value dataUsingEncoding:self->contentEncoding
489 allowLossyConversion:NO];
491 if ([_value length] > 9000) {
496 cstr = [cdata bytes];
497 len = [cdata length];
499 cstr = [_value cString];
500 len = [_value cStringLength];
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);
512 for (i = len - 20; i < len; i++)
513 printf("%5i: 0x%p %4i\n", i, cstr[i], cstr[i]);
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];
524 [self appendContentData:cdata];
529 @implementation WOMessage(Escaping)
532 _escapeHtmlValue(unsigned char c, unsigned char *buf, int *pos)
537 buf[j] = '&'; j++; buf[j] = 'a'; j++; buf[j] = 'm'; j++;
538 buf[j] = 'p'; j++; buf[j] = ';';
541 buf[j] = '&'; j++; buf[j] = 'q'; j++; buf[j] = 'u'; j++;
542 buf[j] = 'o'; j++; buf[j] = 't'; j++; buf[j] = ';';
545 buf[j] = '&'; j++; buf[j] = 'l'; j++; buf[j] = 't'; j++;
549 buf[j] = '&'; j++; buf[j] = 'g'; j++; buf[j] = 't'; j++;
561 _escapeAttrValue(unsigned char c, unsigned char *buf, int *pos)
566 buf[j] = '&'; j++; buf[j] = 'a'; j++; buf[j] = 'm'; j++;
567 buf[j] = 'p'; j++; buf[j] = ';';
570 buf[j] = '&'; j++; buf[j] = 'q'; j++; buf[j] = 'u'; j++;
571 buf[j] = 'o'; j++; buf[j] = 't'; j++; buf[j] = ';';
574 buf[j] = '&'; j++; buf[j] = 'l'; j++; buf[j] = 't'; j++;
578 buf[j] = '&'; j++; buf[j] = 'g'; j++; buf[j] = 't'; j++;
583 buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '9'; j++;
587 buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '1'; j++;
588 buf[j] = '0'; j++; buf[j] = ';';
591 buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '1'; j++;
592 buf[j] = '3'; j++; buf[j] = ';';
603 + (NSString *)stringByEscapingHTMLString:(NSString *)_string {
604 return [_string stringByEscapingHTMLString];
607 + (NSString *)stringByEscapingHTMLAttributeValue:(NSString *)_string {
608 return [_string stringByEscapingHTMLAttributeValue];
611 @end /* WOMessage(Escaping) */