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>
27 // #define STRIP_MULTIPLE_SPACES // this doesn't work with <pre> tags !
29 @implementation WOMessage
31 typedef struct _WOMessageProfileInfo {
37 } WOMessageProfileInfo;
39 static Class NSStringClass = Nil;
40 static BOOL printProfile = NO;
41 static int DEF_CONTENT_SIZE = 20000;
42 static NSStringEncoding defaultEncoding = 0;
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 };
49 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
51 if (NSStringClass == Nil)
52 NSStringClass = [NSString class];
54 printProfile = [ud boolForKey:@"WOProfileResponse"];
56 if ([ud boolForKey:@"WOMessageUseUTF8"]) {
57 defaultEncoding = NSUTF8StringEncoding;
61 //#warning default encoding is ISO Latin 1 ...
62 // TODO: why are we doing this?
63 defaultEncoding = NSISOLatin1StringEncoding;
65 defaultEncoding = [NSStringClass defaultCStringEncoding];
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:)];
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:)];
91 + (void)setDefaultEncoding:(NSStringEncoding)_encoding {
92 defaultEncoding = _encoding;
94 + (NSStringEncoding)defaultEncoding {
95 return defaultEncoding;
99 if ((self = [super init])) {
100 self->contentEncoding = [[self class] defaultEncoding];
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:)];
111 self->header = [[NGMutableHashMap allocWithZone:[self zone]] init];
112 self->version = @"HTTP/1.0";
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];
130 - (void)setUserInfo:(NSDictionary *)_userInfo {
131 ASSIGN(self->userInfo, _userInfo);
133 - (NSDictionary *)userInfo {
134 return self->userInfo;
139 - (void)setHTTPVersion:(NSString *)_httpVersion {
141 if (self->version == _httpVersion)
144 self->version = [_httpVersion copy];
147 if (self->version != nil && ![_httpVersion hasPrefix:@"HTTP/"]) {
148 [self warnWithFormat:
149 @"you apparently passed in an invalid HTTP version: '%@'",
153 - (void)setHttpVersion:(NSString *)_httpVersion {
155 [self setHTTPVersion:_httpVersion];
157 - (NSString *)httpVersion {
158 return self->version;
161 /* cookies (new in WO4) */
163 - (void)addCookie:(WOCookie *)_cookie {
164 if (self->cookies == nil)
165 self->cookies = [[NSMutableArray allocWithZone:[self zone]] init];
166 [self->cookies addObject:_cookie];
169 - (void)removeCookie:(WOCookie *)_cookie {
170 [self->cookies removeObject:_cookie];
173 - (NSArray *)cookies {
174 return self->cookies;
179 - (void)setHeaders:(NSDictionary *)_headers {
183 keys = [_headers keyEnumerator];
184 while ((key = [keys nextObject])) {
187 value = [_headers objectForKey:key];
188 if ([value isKindOfClass:[NSArray class]]) {
189 NSEnumerator *e = [value objectEnumerator];
191 while ((value = [e nextObject]))
192 [self appendHeader:value forKey:key];
195 [self appendHeader:value forKey:key];
199 - (void)setHeader:(NSString *)_header forKey:(NSString *)_key {
200 [self->header setObject:[_header stringValue] forKey:_key];
202 - (NSString *)headerForKey:(NSString *)_key {
203 return [[self->header objectEnumeratorForKey:_key] nextObject];
206 - (void)appendHeader:(NSString *)_header forKey:(NSString *)_key {
207 [self->header addObject:_header forKey:_key];
209 - (void)appendHeaders:(NSArray *)_headers forKey:(NSString *)_key {
210 [self->header addObjects:_headers forKey:_key];
213 - (void)setHeaders:(NSArray *)_headers forKey:(NSString *)_key {
217 e = [_headers objectEnumerator];
219 [self->header removeAllObjectsForKey:_key];
221 while ((value = [e nextObject]))
222 [self->header addObject:value forKey:_key];
224 - (NSArray *)headersForKey:(NSString *)_key {
225 NSEnumerator *values;
227 if ((values = [self->header objectEnumeratorForKey:_key])) {
228 NSMutableArray *array = nil;
231 array = [[NSMutableArray allocWithZone:[self zone]] init];
233 while ((value = [values nextObject]))
234 [array addObject:value];
236 return [array autorelease];
241 - (NSArray *)headerKeys {
242 NSEnumerator *values;
244 if ((values = [self->header keyEnumerator])) {
245 NSMutableArray *array = nil;
247 array = [[NSMutableArray alloc] init];
249 while ((name = [values nextObject]))
250 [array addObject:name];
255 return [name autorelease];
260 - (NSDictionary *)headers {
261 return [self->header asDictionary];
264 - (NSString *)headersAsString {
269 ms = [NSMutableString stringWithCapacity:1024];
272 keys = [[self headerKeys] objectEnumerator];
273 while ((key = [keys nextObject])) {
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"];
290 - (void)_printProfile {
291 if (profile.append + profile.appendC + profile.appendChr +
292 profile.appendXML + profile.appendHTML == 0)
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;
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;
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];
325 memset(&profile, 0, sizeof(profile));
328 /* generic content */
330 - (void)setContent:(NSData *)_data {
333 if (self->content == (id)_data)
337 self->content = [_data mutableCopy];
340 - (NSData *)content {
341 if (printProfile) [self _printProfile];
342 return self->content;
344 - (NSString *)contentAsString {
348 if ((c = [self content]) == nil)
351 s = [[NSString alloc] initWithData:c encoding:[self contentEncoding]];
352 return [s autorelease];
354 - (BOOL)doesStreamContent {
355 return self->contentStream != nil ? YES : NO;
358 - (void)setContentEncoding:(NSStringEncoding)_encoding {
359 self->contentEncoding = _encoding;
361 - (NSStringEncoding)contentEncoding {
362 return self->contentEncoding;
365 /* structured content */
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);
373 - (void)appendContentCharacter:(unichar)_c {
374 unsigned char bc[2] = {0, 0};
379 if (self->content == nil) _ensureBody(self);
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);
388 case NSUnicodeStringEncoding:
389 /* directly add 16-byte char ... */
390 self->addBytes(self->content, @selector(appendBytes:length:),
394 case NSUTF8StringEncoding:
395 /* directly add a byte if 1-byte char (<127 in UTF-8) */
397 self->addBytes(self->content, @selector(appendBytes:length:), &(bc[0]), 1);
400 /* *intended* fall-through !!! */
403 /* otherwise create string for char and ask string to convert to data */
407 [self warnWithFormat:
408 @"using NSString to add a character %i,0x%08X"
409 @"(slow, encoding=%i).", _c, _c, self->contentEncoding];
412 if ((s = [[NSStringClass alloc] initWithCharacters:&_c length:1])) {
413 self->addStr(self, @selector(appendContentString:), s);
420 - (void)appendContentData:(NSData *)_data {
421 if (_data == nil) return;
422 [_checkBody(self) appendData:_data];
425 - (void)appendContentHTMLAttributeValue:(NSString *)_value {
426 self->addStr(self, @selector(appendContentString:),
427 [_value stringByEscapingHTMLAttributeValue]);
428 profile.appendHTML++;
430 - (void)appendContentHTMLString:(NSString *)_value {
431 self->addStr(self, @selector(appendContentString:),
432 [_value stringByEscapingHTMLString]);
433 profile.appendHTML++;
436 - (void)appendContentXMLAttributeValue:(NSString *)_value {
437 self->addStr(self, @selector(appendContentString:),
438 [_value stringByEscapingXMLAttributeValue]);
441 - (void)appendContentXMLString:(NSString *)_value {
442 if (_value == nil) return;
443 self->addStr(self, @selector(appendContentString:),
444 [_value stringByEscapingXMLString]);
448 - (void)appendContentCString:(const unsigned char *)_value {
449 /* we assume that cString == ASCII !!! */
450 register unsigned len;
454 if (self->content == nil) _ensureBody(self);
455 if ((len = _value ? strlen(_value) : 0) == 0)
458 switch (self->contentEncoding) {
459 case NSISOLatin1StringEncoding:
460 case NSASCIIStringEncoding:
461 case NSUTF8StringEncoding:
462 self->addBytes(self->content, @selector(appendBytes:length:),
466 case NSUnicodeStringEncoding:
471 if ((s = [[NSString alloc] initWithCString:_value])) {
472 self->addStr(self, @selector(appendContentString:), s);
479 - (void)appendContentString:(NSString *)_value {
484 if ([_value length] == 0)
487 cdata = [_value dataUsingEncoding:self->contentEncoding
488 allowLossyConversion:NO];
490 if ([_value length] > 9000) {
495 cstr = [cdata bytes];
496 len = [cdata length];
498 cstr = [_value cString];
499 len = [_value cStringLength];
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);
511 for (i = len - 20; i < len; i++)
512 printf("%5i: 0x%08X %4i\n", i, cstr[i], cstr[i]);
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];
523 [self appendContentData:cdata];
528 @implementation WOMessage(Escaping)
531 _escapeHtmlValue(unsigned char c, unsigned char *buf, int *pos)
536 buf[j] = '&'; j++; buf[j] = 'a'; j++; buf[j] = 'm'; j++;
537 buf[j] = 'p'; j++; buf[j] = ';';
540 buf[j] = '&'; j++; buf[j] = 'q'; j++; buf[j] = 'u'; j++;
541 buf[j] = 'o'; j++; buf[j] = 't'; j++; buf[j] = ';';
544 buf[j] = '&'; j++; buf[j] = 'l'; j++; buf[j] = 't'; j++;
548 buf[j] = '&'; j++; buf[j] = 'g'; j++; buf[j] = 't'; j++;
560 _escapeAttrValue(unsigned char c, unsigned char *buf, int *pos)
565 buf[j] = '&'; j++; buf[j] = 'a'; j++; buf[j] = 'm'; j++;
566 buf[j] = 'p'; j++; buf[j] = ';';
569 buf[j] = '&'; j++; buf[j] = 'q'; j++; buf[j] = 'u'; j++;
570 buf[j] = 'o'; j++; buf[j] = 't'; j++; buf[j] = ';';
573 buf[j] = '&'; j++; buf[j] = 'l'; j++; buf[j] = 't'; j++;
577 buf[j] = '&'; j++; buf[j] = 'g'; j++; buf[j] = 't'; j++;
582 buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '9'; j++;
586 buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '1'; j++;
587 buf[j] = '0'; j++; buf[j] = ';';
590 buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '1'; j++;
591 buf[j] = '3'; j++; buf[j] = ';';
602 + (NSString *)stringByEscapingHTMLString:(NSString *)_string {
603 return [_string stringByEscapingHTMLString];
606 + (NSString *)stringByEscapingHTMLAttributeValue:(NSString *)_string {
607 return [_string stringByEscapingHTMLAttributeValue];
610 @end /* WOMessage(Escaping) */