1/* 2 Copyright (C) 2000-2007 SKYRIX Software AG 3 Copyright (C) 2007 Helge Hess 4 5 This file is part of SOPE. 6 7 SOPE is free software; you can redistribute it and/or modify it under 8 the terms of the GNU Lesser General Public License as published by the 9 Free Software Foundation; either version 2, or (at your option) any 10 later version. 11 12 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY 13 WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 15 License for more details. 16 17 You should have received a copy of the GNU Lesser General Public 18 License along with SOPE; see the file COPYING. If not, write to the 19 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 20 02111-1307, USA. 21*/ 22 23#include "WOSimpleHTTPParser.h" 24#include <NGObjWeb/WOResponse.h> 25#include <NGObjWeb/WORequest.h> 26#include <NGMime/NGMimeType.h> 27#include "common.h" 28#include <string.h> 29 30@implementation WOSimpleHTTPParser 31 32static Class NSStringClass = Nil; 33static BOOL debugOn = NO; 34static BOOL heavyDebugOn = NO; 35static int fileIOBoundary = 0; 36static int maxUploadSize = 0; 37 38+ (void)initialize { 39 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; 40 41 debugOn = [ud boolForKey:@"WOSimpleHTTPParserDebugEnabled"]; 42 heavyDebugOn = [ud boolForKey:@"WOSimpleHTTPParserHeavyDebugEnabled"]; 43 fileIOBoundary = [ud integerForKey:@"WOSimpleHTTPParserFileIOBoundary"]; 44 maxUploadSize = [ud integerForKey:@"WOSimpleHTTPParserMaxUploadSizeInKB"]; 45 46 if (maxUploadSize == 0) 47 maxUploadSize = 256 * 1024; /* 256MB */ 48 if (fileIOBoundary == 0) 49 fileIOBoundary = 16384; 50 51 if (debugOn) { 52 NSLog(@"WOSimpleHTTPParser: max-upload-size: %dKB", maxUploadSize); 53 NSLog(@"WOSimpleHTTPParser: file-IO boundary: %d", fileIOBoundary); 54 } 55} 56 57- (id)initWithStream:(id<NGStream>)_stream { 58 if (NSStringClass == Nil) NSStringClass = [NSString class]; 59 60 if ((self = [super init])) { 61 if ((self->io = [_stream retain]) == nil) { 62 [self release]; 63 return nil; 64 } 65 66 self->readBytes = (void *) 67 [(NSObject *)self->io methodForSelector:@selector(readBytes:count:)]; 68 if (self->readBytes == NULL) { 69 [self warnWithFormat:@"(%s): got invalid stream object: %@", 70 __PRETTY_FUNCTION__, 71 self->io]; 72 [self release]; 73 return nil; 74 } 75 } 76 return self; 77} 78- (void)dealloc { 79 [self reset]; 80 [self->io release]; 81 [super dealloc]; 82} 83 84/* transient state */ 85 86- (void)reset { 87 self->clen = -1; 88 89 [self->content release]; self->content = nil; 90 [self->lastException release]; self->lastException = nil; 91 [self->httpVersion release]; self->httpVersion = nil; 92 [self->headers removeAllObjects]; 93 94 if (self->lineBuffer) { 95 free(self->lineBuffer); 96 self->lineBuffer = NULL; 97 } 98 self->lineBufSize = 0; 99} 100 101/* low-level reading */ 102 103- (unsigned int)defaultLineSize { 104 return 512; 105} 106 107- (NSException *)readNextLine { 108 unsigned i; 109 110 if (self->lineBuffer == NULL) { 111 self->lineBufSize = [self defaultLineSize]; 112 self->lineBuffer = malloc(self->lineBufSize + 10); 113 } 114 115 for (i = 0; YES; i++) { 116 register unsigned rc; 117 unsigned char c; 118 119 rc = self->readBytes(self->io, @selector(readBytes:count:), &c, 1); 120 if (rc != 1) { 121 if (debugOn) { 122 [self debugWithFormat:@"got result %u, exception: %@", 123 rc, [self->io lastException]]; 124 } 125 return [self->io lastException]; 126 } 127 128 /* check buffer capacity */ 129 if ((i + 2) > self->lineBufSize) { 130 static int reallocCount = 0; 131 reallocCount++; 132 if (reallocCount > 1000) { 133 static BOOL didLog = NO; 134 if (!didLog) { 135 didLog = YES; 136 [self warnWithFormat:@"(%s): reallocated the HTTP line buffer %i times, " 137 @"consider increasing the default line buffer size!", 138 __PRETTY_FUNCTION__, reallocCount]; 139 } 140 } 141 142 if (self->lineBufSize > (56 * 1024)) { 143 /* to avoid DOS attacks ... */ 144 return [NSException exceptionWithName:@"HTTPParserHeaderSizeExceeded" 145 reason: 146 @"got a HTTP line of 100KB+ (DoS attack?)!" 147 userInfo:nil]; 148 } 149 150 self->lineBufSize *= 2; 151 self->lineBuffer = realloc(self->lineBuffer, self->lineBufSize + 10); 152 } 153 154 if (c == '\n') { 155 /* found EOL */ 156 break; 157 } 158 else if (c == '\r') { 159 /* skip CR */ 160 i--; 161 continue; 162 } 163 else { 164 /* store byte */ 165 self->lineBuffer[i] = c; 166 } 167 } 168 self->lineBuffer[i] = 0; /* 0-terminate buffer */ 169 170 return nil /* nil means: everything OK */; 171} 172 173/* common HTTP parsing */ 174 175static NSString *ContentLengthHeaderName = @"content-length"; 176 177static NSString *stringForHeaderName(char *p) { /* Note: arg is _not_ const */ 178 /* 179 process header name 180 181 we try to be smart to avoid creation of NSString objects ... 182 */ 183 register unsigned len; 184 register char c1; 185 186 if ((len = strlen(p)) == 0) 187 return @""; 188 c1 = *p; 189 190 switch (len) { 191 case 0: 192 case 1: 193 break; 194 case 2: 195 if (strcasecmp(p, "te") == 0) return @"te"; 196 if (strcasecmp(p, "if") == 0) return @"if"; 197 break; 198 case 3: 199 if (strcasecmp(p, "via") == 0) return @"via"; 200 if (strcasecmp(p, "age") == 0) return @"age"; 201 if (strcasecmp(p, "p3p") == 0) return @"p3p"; 202 break; 203 case 4: 204 switch (c1) { 205 case 'd': case 'D': 206 if (strcasecmp(p, "date") == 0) return @"date"; 207 break; 208 case 'e': case 'E': 209 if (strcasecmp(p, "etag") == 0) return @"etag"; 210 break; 211 case 'f': case 'F': 212 if (strcasecmp(p, "from") == 0) return @"from"; 213 break; 214 case 'h': case 'H': 215 if (strcasecmp(p, "host") == 0) return @"host"; 216 break; 217 case 'v': case 'V': 218 if (strcasecmp(p, "vary") == 0) return @"vary"; 219 break; 220 } 221 break; 222 case 5: 223 if (strcasecmp(p, "allow") == 0) return @"allow"; 224 if (strcasecmp(p, "brief") == 0) return @"brief"; 225 if (strcasecmp(p, "range") == 0) return @"range"; 226 if (strcasecmp(p, "depth") == 0) return @"depth"; 227 if (strcasecmp(p, "ua-os") == 0) return @"ua-os"; /* Entourage */ 228 break; 229 case 6: 230 switch (c1) { 231 case 'a': case 'A': 232 if (strcasecmp(p, "accept") == 0) return @"accept"; 233 break; 234 case 'c': case 'C': 235 if (strcasecmp(p, "cookie") == 0) return @"cookie"; 236 break; 237 case 'e': case 'E': 238 if (strcasecmp(p, "expect") == 0) return @"expect"; 239 break; 240 case 'p': case 'P': 241 if (strcasecmp(p, "pragma") == 0) return @"pragma"; 242 break; 243 case 's': case 'S': 244 if (strcasecmp(p, "server") == 0) return @"server"; 245 break; 246 case 'u': case 'U': 247 if (strcasecmp(p, "ua-cpu") == 0) return @"ua-cpu"; /* Entourage */ 248 break; 249 } 250 break; 251 252 default: 253 switch (c1) { 254 case 'a': case 'A': 255 if (len > 10) { 256 if (p[6] == '-') { 257 if (strcasecmp(p, "accept-charset") == 0) return @"accept-charset"; 258 if (strcasecmp(p, "accept-encoding") == 0) return @"accept-encoding"; 259 if (strcasecmp(p, "accept-language") == 0) return @"accept-language"; 260 if (strcasecmp(p, "accept-ranges") == 0) return @"accept-ranges"; 261 } 262 else if (strcasecmp(p, "authorization") == 0) 263 return @"authorization"; 264 } 265 break; 266 267 case 'c': case 'C': 268 if (len > 8) { 269 if (p[7] == '-') { 270 if (strcasecmp(p, "content-length") == 0) 271 return ContentLengthHeaderName; 272 273 if (strcasecmp(p, "content-type") == 0) return @"content-type"; 274 if (strcasecmp(p, "content-md5") == 0) return @"content-md5"; 275 if (strcasecmp(p, "content-range") == 0) return @"content-range"; 276 277 if (strcasecmp(p, "content-encoding") == 0) 278 return @"content-encoding"; 279 if (strcasecmp(p, "content-language") == 0) 280 return @"content-language"; 281 282 if (strcasecmp(p, "content-location") == 0) 283 return @"content-location"; 284 if (strcasecmp(p, "content-class") == 0) /* Entourage */ 285 return @"content-class"; 286 } 287 else if (strcasecmp(p, "call-back") == 0) 288 return @"call-back"; 289 } 290 291 if (strcasecmp(p, "connection") == 0) return @"connection"; 292 if (strcasecmp(p, "cache-control") == 0) return @"cache-control"; 293 294 break; 295 296 case 'd': case 'D': 297 if (strcasecmp(p, "destination") == 0) return @"destination"; 298 if (strcasecmp(p, "destroy") == 0) return @"destroy"; 299 break; 300 301 case 'e': case 'E': 302 if (strcasecmp(p, "expires") == 0) return @"expires"; 303 if (strcasecmp(p, "extension") == 0) return @"extension"; /* Entourage */ 304 break; 305 306 case 'i': case 'I': 307 if (strcasecmp(p, "if-modified-since") == 0) 308 return @"if-modified-since"; 309 if (strcasecmp(p, "if-none-match") == 0) /* Entourage */ 310 return @"if-none-match"; 311 if (strcasecmp(p, "if-match") == 0) 312 return @"if-match"; 313 break; 314 315 case 'k': case 'K': 316 if (strcasecmp(p, "keep-alive") == 0) return @"keep-alive"; 317 break; 318 319 case 'l': case 'L': 320 if (strcasecmp(p, "last-modified") == 0) return @"last-modified"; 321 if (strcasecmp(p, "location") == 0) return @"location"; 322 if (strcasecmp(p, "lock-token") == 0) return @"lock-token"; 323 break; 324 325 case 'm': case 'M': 326 if (strcasecmp(p, "ms-webstorage") == 0) return @"ms-webstorage"; 327 if (strcasecmp(p, "max-forwards") == 0) return @"max-forwards"; 328 break; 329 330 case 'n': case 'N': 331 if (len > 16) { 332 if (p[12] == '-') { 333 if (strcasecmp(p, "notification-delay") == 0) 334 return @"notification-delay"; 335 if (strcasecmp(p, "notification-type") == 0) 336 return @"notification-type"; 337 } 338 } 339 break; 340 341 case 'o': case 'O': 342 if (len == 9) { 343 if (strcasecmp(p, "overwrite") == 0) 344 return @"overwrite"; 345 } 346 break; 347 348 case 'p': case 'P': 349 if (len == 16) { 350 if (strcasecmp(p, "proxy-connection") == 0) 351 return @"proxy-connection"; 352 } 353 break; 354 355 case 'r': case 'R': 356 if (len == 7) { 357 if (strcasecmp(p, "referer") == 0) return @"referer"; 358 } 359 break; 360 361 case 's': case 'S': 362 switch (len) { 363 case 21: 364 if (strcasecmp(p, "subscription-lifetime") == 0) 365 return @"subscription-lifetime"; 366 break; 367 case 15: 368 if (strcasecmp(p, "subscription-id") == 0) 369 return @"subscription-id"; 370 break; 371 case 10: 372 if (strcasecmp(p, "set-cookie") == 0) 373 return @"set-cookie"; 374 break; 375 } 376 break; 377 378 case 't': case 'T': 379 if (strcasecmp(p, "transfer-encoding") == 0) return @"transfer-encoding"; 380 if (strcasecmp(p, "translate") == 0) return @"translate"; 381 if (strcasecmp(p, "trailer") == 0) return @"trailer"; 382 if (strcasecmp(p, "timeout") == 0) return @"timeout"; 383 break; 384 385 case 'u': case 'U': 386 if (strcasecmp(p, "user-agent") == 0) return @"user-agent"; 387 break; 388 389 case 'w': case 'W': 390 if (strcasecmp(p, "www-authenticate") == 0) return @"www-authenticate"; 391 if (strcasecmp(p, "warning") == 0) return @"warning"; 392 break; 393 394 case 'x': case 'X': 395 if ((p[2] == 'w') && (len > 22)) { 396 if (strstr(p, "x-webobjects-") == (void *)p) { 397 p += 13; /* skip x-webobjects- */ 398 if (strcmp(p, "server-protocol") == 0) 399 return @"x-webobjects-server-protocol"; 400 else if (strcmp(p, "server-protocol") == 0) 401 return @"x-webobjects-server-protocol"; 402 else if (strcmp(p, "remote-addr") == 0) 403 return @"x-webobjects-remote-addr"; 404 else if (strcmp(p, "remote-host") == 0) 405 return @"x-webobjects-remote-host"; 406 else if (strcmp(p, "server-name") == 0) 407 return @"x-webobjects-server-name"; 408 else if (strcmp(p, "server-port") == 0) 409 return @"x-webobjects-server-port"; 410 else if (strcmp(p, "server-url") == 0) 411 return @"x-webobjects-server-url"; 412 } 413 } 414 if (len == 7) { 415 if (strcasecmp(p, "x-cache") == 0) 416 return @"x-cache"; 417 } 418 else if (len == 12) { 419 if (strcasecmp(p, "x-powered-by") == 0) 420 return @"x-powered-by"; 421 } 422 if (strcasecmp(p, "x-zidestore-name") == 0) 423 return @"x-zidestore-name"; 424 if (strcasecmp(p, "x-forwarded-for") == 0) 425 return @"x-forwarded-for"; 426 if (strcasecmp(p, "x-forwarded-host") == 0) 427 return @"x-forwarded-host"; 428 if (strcasecmp(p, "x-forwarded-server") == 0) 429 return @"x-forwarded-server"; 430 break; 431 } 432 } 433 434 if (debugOn) 435 NSLog(@"making custom header name '%s'!", p); 436 437 /* make name lowercase (we own the buffer, so we can work on it) */ 438 { 439 unsigned char *t; 440 441 for (t = (unsigned char *)p; *t != '\0'; t++) 442 *t = tolower(*t); 443 } 444 return [[NSString alloc] initWithCString:p]; 445} 446 447- (NSException *)parseHeader { 448 NSException *e = nil; 449 450 while ((e = [self readNextLine]) == nil) { 451 unsigned char *p, *v; 452 int idx; 453 NSString *headerName; 454 NSString *headerValue; 455 456 if (heavyDebugOn) 457 printf("read header line: '%s'\n", self->lineBuffer); 458 459 if (strlen((char *)self->lineBuffer) == 0) { 460 /* found end of header */ 461 break; 462 } 463 464 p = self->lineBuffer; 465 466 if (*p == ' ' || *p == '\t') { 467 // TODO: implement folding (remember last header-key, add string) 468 [self errorWithFormat: 469 @"(%s): got a folded HTTP header line, cannot process!", 470 __PRETTY_FUNCTION__]; 471 continue; 472 } 473 474 /* find key/value separator */ 475 if ((v = (unsigned char *)index((char *)p, ':')) == NULL) { 476 [self warnWithFormat:@"got malformed header line: '%s'", 477 self->lineBuffer]; 478 continue; 479 } 480 481 *v = '\0'; v++; /* now 'p' points to name and 'v' to value */ 482 483 /* skip leading spaces */ 484 while (*v != '\0' && (*v == ' ' || *v == '\t')) 485 v++; 486 487 if (*v != '\0') { 488 /* trim trailing spaces */ 489 for (idx = strlen((char *)v) - 1; idx >= 0; idx--) { 490 if ((v[idx] != ' ' && v[idx] != '\t')) 491 break; 492 493 v[idx] = '\0'; 494 } 495 } 496 497 headerName = stringForHeaderName((char *)p); 498 headerValue = [[NSStringClass alloc] initWithCString:(char *)v]; 499 500 if (headerName == ContentLengthHeaderName) 501 self->clen = atoi((char *)v); 502 503 if (headerName != nil || headerValue != nil) { 504 if (self->headers == nil) 505 self->headers = [[NSMutableDictionary alloc] initWithCapacity:32]; 506 507 [self->headers setObject:headerValue forKey:headerName]; 508 } 509 510 [headerValue release]; 511 [headerName release]; 512 } 513 514 return e; 515} 516 517- (NSException *)parseEntityOfMethod:(NSString *)_method { 518 /* 519 TODO: several cases are caught: 520 a) content-length = 0 => empty data 521 b) content-length small => read into memory 522 c) content-length large => streamed into the filesystem to safe RAM 523 d) content-length unknown => ?? 524 */ 525 526 if (self->clen == 0) { 527 /* nothing to do */ 528 } 529 else if (self->clen < 0) { 530 /* I think HTTP/1.1 requires a content-length header to be present ? */ 531 532 if ([self->httpVersion isEqualToString:@"HTTP/1.0"] || 533 [self->httpVersion isEqualToString:@"HTTP/0.9"]) { 534 /* content-length unknown, read till EOF */ 535 BOOL readToEOF = YES; 536 537 if ([_method isEqualToString:@"HEAD"]) 538 readToEOF = NO; 539 else if ([_method isEqualToString:@"GET"]) 540 readToEOF = NO; 541 else if ([_method isEqualToString:@"DELETE"]) 542 readToEOF = NO; 543 544 if (readToEOF) { 545 [self warnWithFormat: 546 @"not processing entity of request without contentlen!"]; 547 } 548 } 549 } 550 else if (self->clen > maxUploadSize*1024) { 551 /* entity is too large */ 552 NSString *s; 553 554 s = [NSString stringWithFormat:@"The maximum HTTP transaction size was " 555 @"exceeded (%d vs %d)", self->clen, maxUploadSize * 1024]; 556 return [NSException exceptionWithName:@"LimitException" 557 reason:s userInfo:nil]; 558 } 559 else if (self->clen > fileIOBoundary) { 560 /* we are streaming the content to a file and use a memory mapped data */ 561 unsigned toGo; 562 NSString *fn; 563 char buf[4096]; 564 BOOL ok = YES; 565 int writeError = 0; 566 FILE *t; 567 568 [self debugWithFormat:@"streaming %i bytes into file ...", self->clen]; 569 570 fn = [[NSProcessInfo processInfo] temporaryFileName]; 571 572 if ((t = fopen([fn cString], "w")) == NULL) { 573 [self errorWithFormat:@"could not open temporary file '%@'!", fn]; 574 575 /* read into memory as a fallback ... */ 576 577 self->content = 578 [[(NGStream *)self->io safeReadDataOfLength:self->clen] retain]; 579 if (self->content == nil) 580 return [self->io lastException]; 581 return nil; 582 } 583 584 for (toGo = self->clen; toGo > 0; ) { 585 unsigned readCount, writeCount; 586 587 /* read from socket */ 588 readCount = [self->io readBytes:buf count:sizeof(buf)]; 589 if (readCount == NGStreamError) { 590 /* an error */ 591 ok = NO; 592 break; 593 } 594 toGo -= readCount; 595 596 /* write to file */ 597 if ((writeCount = fwrite(buf, readCount, 1, t)) != 1) { 598 /* an error */ 599 ok = NO; 600 writeError = ferror(t); 601 break; 602 } 603 } 604 fclose(t); 605 606 if (!ok) { 607 unlink([fn cString]); /* delete temporary file */ 608 609 if (writeError == 0) { 610 return [NSException exceptionWithName:@"SystemWriteError" 611 reason:@"failed to write data to upload file" 612 userInfo:nil]; 613 } 614 615 return [self->io lastException]; 616 } 617 618 self->content = [[NSData alloc] initWithContentsOfMappedFile:fn]; 619 unlink([fn cString]); /* if the mmap disappears, the storage is freed */ 620 } 621 else { 622 /* content-length known and small */ 623 //[self logWithFormat:@"reading %i bytes of the entity", self->clen]; 624 625 self->content = 626 [[(NGStream *)self->io safeReadDataOfLength:self->clen] retain]; 627 if (self->content == nil) 628 return [self->io lastException]; 629 630 //[self logWithFormat:@"read %i bytes.", [self->content length]]; 631 } 632 633 return nil; 634} 635 636/* handling expectations */ 637 638- (BOOL)processContinueExpectation { 639 // TODO: this should check the credentials of a request before accepting the 640 // body. The current implementation is far from optimal and only added 641 // for Mono compatibility (and actually produces the same behaviour 642 // like with HTTP/1.0 ...) 643 static char *contStatLine = 644 "HTTP/1.0 100 Continue\r\n" 645 "content-length: 0\r\n" 646 "\r\n"; 647 static char *failStatLine = 648 "HTTP/1.0 417 Expectation Failed\r\n" 649 "content-length: 0\r\n" 650 "\r\n"; 651 char *respline = NULL; 652 BOOL ok = YES; 653 654 [self debugWithFormat:@"process 100 continue on IO: %@", self->io]; 655 656 if (self->clen > 0 && (self->clen > (maxUploadSize * 1024))) { 657 // TODO: return a 417 expectation failed 658 ok = NO; 659 respline = failStatLine; 660 } 661 else { 662 ok = YES; 663 respline = contStatLine; 664 } 665 666 if (![self->io safeWriteBytes:respline count:strlen(respline)]) { 667 ASSIGN(self->lastException, [self->io lastException]); 668 return NO; 669 } 670 if (![self->io flush]) { 671 ASSIGN(self->lastException, [self->io lastException]); 672 return NO; 673 } 674 675 return ok; 676} 677 678/* parsing */ 679 680- (void)_fixupContentEncodingOfMessageBasedOnContentType:(WOMessage *)_msg { 681 // DUP: NGHttp+WO.m 682 NSStringEncoding enc = 0; 683 NSString *ctype; 684 NGMimeType *rqContentType; 685 NSString *charset; 686 687 if (![(ctype = [_msg headerForKey:@"content-type"]) isNotEmpty]) 688 /* an HTTP message w/o a content type? */ 689 return; 690 691 if ((rqContentType = [NGMimeType mimeType:ctype]) == nil) { 692 [self warnWithFormat:@"could not parse MIME type: '%@'", ctype]; 693 return; 694 } 695 696 charset = [rqContentType valueOfParameter:@"charset"]; 697 698 if ([charset isNotEmpty]) { 699 enc = [NSString stringEncodingForEncodingNamed:charset]; 700 } 701 else if (rqContentType != nil) { 702 /* process default charsets for content types */ 703 NSString *majorType = [rqContentType type]; 704 705 if ([majorType isEqualToString:@"text"]) { 706 NSString *subType = [rqContentType subType]; 707 708 if ([subType isEqualToString:@"calendar"]) { 709 /* RFC2445, section 4.1.4 */ 710 enc = NSUTF8StringEncoding; 711 } 712 } 713 else if ([majorType isEqualToString:@"application"]) { 714 NSString *subType = [rqContentType subType]; 715 716 if ([subType isEqualToString:@"xml"]) { 717 // TBD: we should look at the actual content! (<?xml declaration 718 // and BOM 719 enc = NSUTF8StringEncoding; 720 } 721 } 722 } 723 724 if (enc != 0) 725 [_msg setContentEncoding:enc]; 726} 727 728- (WORequest *)parseRequest { 729 NSException *e = nil; 730 WORequest *r = nil; 731 NSString *uri = @"/"; 732 NSString *method = @"GET"; 733 NSString *expect; 734 735 [self reset]; 736 if (heavyDebugOn) 737 [self logWithFormat:@"HeavyDebug: parsing response ..."]; 738 739 /* process request line */ 740 741 if ((e = [self readNextLine])) { 742 ASSIGN(self->lastException, e); 743 return nil; 744 } 745 if (heavyDebugOn) 746 printf("read request line: '%s'\n", self->lineBuffer); 747 748 { 749 /* sample line: "GET / HTTP/1.0" */ 750 char *p, *t; 751 752 /* parse method */ 753 754 p = (char *)self->lineBuffer; 755 if ((t = index(p, ' ')) == NULL) { 756 [self logWithFormat:@"got broken request line '%s'", self->lineBuffer]; 757 return nil; 758 } 759 *t = '\0'; 760 761 switch (*p) { 762 /* intended fall-throughs ! */ 763 case 'b': case 'B': 764 if (strcasecmp(p, "BPROPFIND") == 0) { method = @"BPROPFIND"; break; } 765 if (strcasecmp(p, "BPROPPATCH") == 0) { method = @"BPROPPATCH"; break; } 766 case 'c': case 'C': 767 if (strcasecmp(p, "COPY") == 0) { method = @"COPY"; break; } 768 if (strcasecmp(p, "CHECKOUT") == 0) { method = @"CHECKOUT"; break; } 769 if (strcasecmp(p, "CHECKIN") == 0) { method = @"CHECKIN"; break; } 770 case 'd': case 'D': 771 if (strcasecmp(p, "DELETE") == 0) { method = @"DELETE"; break; } 772 case 'h': case 'H': 773 if (strcasecmp(p, "HEAD") == 0) { method = @"HEAD"; break; } 774 case 'l': case 'L': 775 if (strcasecmp(p, "LOCK") == 0) { method = @"LOCK"; break; } 776 case 'g': case 'G': 777 if (strcasecmp(p, "GET") == 0) { method = @"GET"; break; } 778 case 'm': case 'M': 779 if (strcasecmp(p, "MKCOL") == 0) { method = @"MKCOL"; break; } 780 if (strcasecmp(p, "MOVE") == 0) { method = @"MOVE"; break; } 781 case 'n': case 'N': 782 if (strcasecmp(p, "NOTIFY") == 0) { method = @"NOTIFY"; break; } 783 case 'o': case 'O': 784 if (strcasecmp(p, "OPTIONS") == 0) { method = @"OPTIONS"; break; } 785 case 'p': case 'P': 786 if (strcasecmp(p, "PUT") == 0) { method = @"PUT"; break; } 787 if (strcasecmp(p, "POST") == 0) { method = @"POST"; break; } 788 if (strcasecmp(p, "PROPFIND") == 0) { method = @"PROPFIND"; break; } 789 if (strcasecmp(p, "PROPPATCH") == 0) { method = @"PROPPATCH"; break; } 790 if (strcasecmp(p, "POLL") == 0) { method = @"POLL"; break; } 791 case 'r': case 'R': 792 if (strcasecmp(p, "REPORT") == 0) { method = @"REPORT"; break; } 793 case 's': case 'S': 794 if (strcasecmp(p, "SEARCH") == 0) { method = @"SEARCH"; break; } 795 if (strcasecmp(p, "SUBSCRIBE") == 0) { method = @"SUBSCRIBE"; break; } 796 case 'u': case 'U': 797 if (strcasecmp(p, "UNLOCK") == 0) { method = @"UNLOCK"; break; } 798 if (strcasecmp(p, "UNSUBSCRIBE")== 0) { method = @"UNSUBSCRIBE"; break; } 799 if (strcasecmp(p, "UNCHECKOUT") == 0) { method = @"UNCHECKOUT"; break; } 800 case 'v': case 'V': 801 if (strcasecmp(p, "VERSION-CONTROL") == 0) { 802 method = @"VERSION-CONTROL"; 803 break; 804 } 805 806 default: 807 if (debugOn) 808 [self debugWithFormat:@"making custom HTTP method name: '%s'", p]; 809 method = [NSString stringWithCString:p]; 810 break; 811 } 812 813 /* parse URI */ 814 815 p = t + 1; /* skip space */ 816 while (*p != '\0' && (*p == ' ' || *p == '\t')) /* skip spaces */ 817 p++; 818 819 if (*p == '\0') { 820 [self logWithFormat:@"got broken request line '%s'", self->lineBuffer]; 821 return nil; 822 } 823 824 if ((t = index(p, ' ')) == NULL) { 825 /* the URI isn't followed by a HTTP version */ 826 self->httpVersion = @"HTTP/0.9"; 827 /* TODO: strip trailing spaces for better compliance */ 828 uri = [NSString stringWithCString:p]; 829 } 830 else { 831 *t = '\0'; 832 uri = [NSString stringWithCString:p]; 833 834 /* parse version */ 835 836 p = t + 1; /* skip space */ 837 while (*p != '\0' && (*p == ' ' || *p == '\t')) /* skip spaces */ 838 p++; 839 840 if (*p == '\0') 841 self->httpVersion = @"HTTP/0.9"; 842 else if (strcasecmp(p, "http/1.0") == 0) 843 self->httpVersion = @"HTTP/1.0"; 844 else if (strcasecmp(p, "http/1.1") == 0) 845 self->httpVersion = @"HTTP/1.1"; 846 else { 847 /* TODO: strip trailing spaces */ 848 self->httpVersion = [[NSString alloc] initWithCString:p]; 849 } 850 } 851 } 852 853 /* process header */ 854 855 if ((e = [self parseHeader]) != nil) { 856 ASSIGN(self->lastException, e); 857 return nil; 858 } 859 if (heavyDebugOn) 860 [self logWithFormat:@"parsed header: %@", self->headers]; 861 862 /* check for expectations */ 863 864 if ((expect = [self->headers objectForKey:@"expect"]) != nil) { 865 if ([expect rangeOfString:@"100-continue" 866 options:NSCaseInsensitiveSearch].length > 0) { 867 if (![self processContinueExpectation]) 868 return nil; 869 } 870 } 871 872 /* process body */ 873 874 if (clen != 0) { 875 if ((e = [self parseEntityOfMethod:method])) { 876 ASSIGN(self->lastException, e); 877 return nil; 878 } 879 } 880 881 if (heavyDebugOn) 882 [self logWithFormat:@"HeavyDebug: got all .."]; 883 884 r = [[WORequest alloc] initWithMethod:method 885 uri:uri 886 httpVersion:self->httpVersion 887 headers:self->headers 888 content:self->content 889 userInfo:nil]; 890 [self _fixupContentEncodingOfMessageBasedOnContentType:r]; 891 [self reset]; 892 893 if (heavyDebugOn) 894 [self logWithFormat:@"HeavyDebug: request: %@", r]; 895 896 return [r autorelease]; 897} 898 899- (WOResponse *)parseResponse { 900 NSException *e = nil; 901 int code = 200; 902 WOResponse *r = nil; 903 904 [self reset]; 905 if (heavyDebugOn) 906 [self logWithFormat:@"HeavyDebug: parsing response ..."]; 907 908 /* process response line */ 909 910 if ((e = [self readNextLine])) { 911 ASSIGN(self->lastException, e); 912 return nil; 913 } 914 if (heavyDebugOn) 915 printf("read response line: '%s'\n", self->lineBuffer); 916 917 { 918 /* sample line: "HTTP/1.0 200 OK" */ 919 char *p, *t; 920 921 /* version */ 922 923 p = (char *)self->lineBuffer; 924 if ((t = index(p, ' ')) == NULL) { 925 [self logWithFormat:@"got broken response line '%s'", self->lineBuffer]; 926 return nil; 927 } 928 929 *t = '\0'; 930 if (strcasecmp(p, "http/1.0") == 0) 931 self->httpVersion = @"HTTP/1.0"; 932 else if (strcasecmp(p, "http/1.1") == 0) 933 self->httpVersion = @"HTTP/1.1"; 934 else 935 self->httpVersion = [[NSString alloc] initWithCString:p]; 936 937 /* code */ 938 939 p = t + 1; /* skip space */ 940 while (*p != '\0' && (*p == ' ' || *p == '\t')) /* skip spaces */ 941 p++; 942 if (*p == '\0') { 943 [self logWithFormat:@"got broken response line '%s'", self->lineBuffer]; 944 return nil; 945 } 946 code = atoi(p); 947 948 /* we don't need to parse a reason ... */ 949 } 950 951 /* process header */ 952 953 if ((e = [self parseHeader])) { 954 ASSIGN(self->lastException, e); 955 return nil; 956 } 957 if (heavyDebugOn) 958 [self logWithFormat:@"parsed header: %@", self->headers]; 959 960 /* process body */ 961 962 if (clen != 0) { 963 if ((e = [self parseEntityOfMethod:nil /* parsing a response */])) { 964 ASSIGN(self->lastException, e); 965 return nil; 966 } 967 } 968 969 if (heavyDebugOn) 970 [self logWithFormat:@"HeavyDebug: got all .."]; 971 972 r = [[[WOResponse alloc] init] autorelease]; 973 [r setStatus:code]; 974 [r setHTTPVersion:self->httpVersion]; 975 [r setHeaders:self->headers]; 976 [r setContent:self->content]; 977 [self _fixupContentEncodingOfMessageBasedOnContentType:r]; 978 979 [self reset]; 980 981 if (heavyDebugOn) 982 [self logWithFormat:@"HeavyDebug: response: %@", r]; 983 984 return r; 985} 986 987- (NSException *)lastException { 988 return self->lastException; 989} 990 991/* debugging */ 992 993- (BOOL)isDebuggingEnabled { 994 return debugOn; 995} 996 997@end /* WOSimpleHTTPParser */ 998