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