2 Copyright (C) 2000-2007 SKYRIX Software AG
3 Copyright (C) 2007 Helge Hess
5 This file is part of SOPE.
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
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.
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
23 #include "WOSimpleHTTPParser.h"
24 #include <NGObjWeb/WOResponse.h>
25 #include <NGObjWeb/WORequest.h>
26 #include <NGMime/NGMimeType.h>
30 @implementation WOSimpleHTTPParser
32 static Class NSStringClass = Nil;
33 static BOOL debugOn = NO;
34 static BOOL heavyDebugOn = NO;
35 static int fileIOBoundary = 0;
36 static int maxUploadSize = 0;
42 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
44 debugOn = [ud boolForKey:@"WOSimpleHTTPParserDebugEnabled"];
45 heavyDebugOn = [ud boolForKey:@"WOSimpleHTTPParserHeavyDebugEnabled"];
46 fileIOBoundary = [ud integerForKey:@"WOSimpleHTTPParserFileIOBoundary"];
47 maxUploadSize = [ud integerForKey:@"WOSimpleHTTPParserMaxUploadSizeInKB"];
49 if (maxUploadSize == 0)
50 maxUploadSize = 256 * 1024; /* 256MB */
51 if (fileIOBoundary == 0)
52 fileIOBoundary = 16384;
55 NSLog(@"WOSimpleHTTPParser: max-upload-size: %dKB", maxUploadSize);
56 NSLog(@"WOSimpleHTTPParser: file-IO boundary: %d", fileIOBoundary);
60 - (id)initWithStream:(id<NGStream>)_stream {
61 if (NSStringClass == Nil) NSStringClass = [NSString class];
63 if ((self = [super init])) {
64 if ((self->io = [_stream retain]) == nil) {
69 self->readBytes = (void *)
70 [(NSObject *)self->io methodForSelector:@selector(readBytes:count:)];
71 if (self->readBytes == NULL) {
72 [self warnWithFormat:@"(%s): got invalid stream object: %@",
92 [self->content release]; self->content = nil;
93 [self->lastException release]; self->lastException = nil;
94 [self->httpVersion release]; self->httpVersion = nil;
95 [self->headers removeAllObjects];
97 if (self->lineBuffer) {
98 free(self->lineBuffer);
99 self->lineBuffer = NULL;
101 self->lineBufSize = 0;
104 /* low-level reading */
106 - (unsigned int)defaultLineSize {
110 - (NSException *)readNextLine {
113 if (self->lineBuffer == NULL) {
114 self->lineBufSize = [self defaultLineSize];
115 self->lineBuffer = malloc(self->lineBufSize + 10);
118 for (i = 0; YES; i++) {
119 register unsigned rc;
122 rc = self->readBytes(self->io, @selector(readBytes:count:), &c, 1);
125 [self debugWithFormat:@"got result %u, exception: %@",
126 rc, [self->io lastException]];
128 return [self->io lastException];
131 /* check buffer capacity */
132 if ((i + 2) > self->lineBufSize) {
133 static int reallocCount = 0;
135 if (reallocCount > 1000) {
136 static BOOL didLog = NO;
139 [self warnWithFormat:@"(%s): reallocated the HTTP line buffer %i times, "
140 @"consider increasing the default line buffer size!",
141 __PRETTY_FUNCTION__, reallocCount];
145 if (self->lineBufSize > (56 * 1024)) {
146 /* to avoid DOS attacks ... */
147 return [NSException exceptionWithName:@"HTTPParserHeaderSizeExceeded"
149 @"got a HTTP line of 100KB+ (DoS attack?)!"
153 self->lineBufSize *= 2;
154 self->lineBuffer = realloc(self->lineBuffer, self->lineBufSize + 10);
161 else if (c == '\r') {
168 self->lineBuffer[i] = c;
171 self->lineBuffer[i] = 0; /* 0-terminate buffer */
173 return nil /* nil means: everything OK */;
176 /* common HTTP parsing */
178 static NSString *ContentLengthHeaderName = @"content-length";
180 static NSString *stringForHeaderName(char *p) { /* Note: arg is _not_ const */
184 we try to be smart to avoid creation of NSString objects ...
186 register unsigned len;
189 if ((len = strlen(p)) == 0)
198 if (strcasecmp(p, "te") == 0) return @"te";
199 if (strcasecmp(p, "if") == 0) return @"if";
202 if (strcasecmp(p, "via") == 0) return @"via";
203 if (strcasecmp(p, "age") == 0) return @"age";
204 if (strcasecmp(p, "p3p") == 0) return @"p3p";
209 if (strcasecmp(p, "date") == 0) return @"date";
212 if (strcasecmp(p, "etag") == 0) return @"etag";
215 if (strcasecmp(p, "from") == 0) return @"from";
218 if (strcasecmp(p, "host") == 0) return @"host";
221 if (strcasecmp(p, "vary") == 0) return @"vary";
226 if (strcasecmp(p, "allow") == 0) return @"allow";
227 if (strcasecmp(p, "brief") == 0) return @"brief";
228 if (strcasecmp(p, "range") == 0) return @"range";
229 if (strcasecmp(p, "depth") == 0) return @"depth";
230 if (strcasecmp(p, "ua-os") == 0) return @"ua-os"; /* Entourage */
235 if (strcasecmp(p, "accept") == 0) return @"accept";
238 if (strcasecmp(p, "cookie") == 0) return @"cookie";
241 if (strcasecmp(p, "expect") == 0) return @"expect";
244 if (strcasecmp(p, "pragma") == 0) return @"pragma";
247 if (strcasecmp(p, "server") == 0) return @"server";
250 if (strcasecmp(p, "ua-cpu") == 0) return @"ua-cpu"; /* Entourage */
260 if (strcasecmp(p, "accept-charset") == 0) return @"accept-charset";
261 if (strcasecmp(p, "accept-encoding") == 0) return @"accept-encoding";
262 if (strcasecmp(p, "accept-language") == 0) return @"accept-language";
263 if (strcasecmp(p, "accept-ranges") == 0) return @"accept-ranges";
265 else if (strcasecmp(p, "authorization") == 0)
266 return @"authorization";
273 if (strcasecmp(p, "content-length") == 0)
274 return ContentLengthHeaderName;
276 if (strcasecmp(p, "content-type") == 0) return @"content-type";
277 if (strcasecmp(p, "content-md5") == 0) return @"content-md5";
278 if (strcasecmp(p, "content-range") == 0) return @"content-range";
280 if (strcasecmp(p, "content-encoding") == 0)
281 return @"content-encoding";
282 if (strcasecmp(p, "content-language") == 0)
283 return @"content-language";
285 if (strcasecmp(p, "content-location") == 0)
286 return @"content-location";
287 if (strcasecmp(p, "content-class") == 0) /* Entourage */
288 return @"content-class";
290 else if (strcasecmp(p, "call-back") == 0)
294 if (strcasecmp(p, "connection") == 0) return @"connection";
295 if (strcasecmp(p, "cache-control") == 0) return @"cache-control";
300 if (strcasecmp(p, "destination") == 0) return @"destination";
301 if (strcasecmp(p, "destroy") == 0) return @"destroy";
305 if (strcasecmp(p, "expires") == 0) return @"expires";
306 if (strcasecmp(p, "extension") == 0) return @"extension"; /* Entourage */
310 if (strcasecmp(p, "if-modified-since") == 0)
311 return @"if-modified-since";
312 if (strcasecmp(p, "if-none-match") == 0) /* Entourage */
313 return @"if-none-match";
314 if (strcasecmp(p, "if-match") == 0)
319 if (strcasecmp(p, "keep-alive") == 0) return @"keep-alive";
323 if (strcasecmp(p, "last-modified") == 0) return @"last-modified";
324 if (strcasecmp(p, "location") == 0) return @"location";
325 if (strcasecmp(p, "lock-token") == 0) return @"lock-token";
329 if (strcasecmp(p, "ms-webstorage") == 0) return @"ms-webstorage";
330 if (strcasecmp(p, "max-forwards") == 0) return @"max-forwards";
336 if (strcasecmp(p, "notification-delay") == 0)
337 return @"notification-delay";
338 if (strcasecmp(p, "notification-type") == 0)
339 return @"notification-type";
346 if (strcasecmp(p, "overwrite") == 0)
353 if (strcasecmp(p, "proxy-connection") == 0)
354 return @"proxy-connection";
360 if (strcasecmp(p, "referer") == 0) return @"referer";
367 if (strcasecmp(p, "subscription-lifetime") == 0)
368 return @"subscription-lifetime";
371 if (strcasecmp(p, "subscription-id") == 0)
372 return @"subscription-id";
375 if (strcasecmp(p, "set-cookie") == 0)
376 return @"set-cookie";
382 if (strcasecmp(p, "transfer-encoding") == 0) return @"transfer-encoding";
383 if (strcasecmp(p, "translate") == 0) return @"translate";
384 if (strcasecmp(p, "trailer") == 0) return @"trailer";
385 if (strcasecmp(p, "timeout") == 0) return @"timeout";
389 if (strcasecmp(p, "user-agent") == 0) return @"user-agent";
393 if (strcasecmp(p, "www-authenticate") == 0) return @"www-authenticate";
394 if (strcasecmp(p, "warning") == 0) return @"warning";
398 if ((p[2] == 'w') && (len > 22)) {
399 if (strstr(p, "x-webobjects-") == (void *)p) {
400 p += 13; /* skip x-webobjects- */
401 if (strcmp(p, "server-protocol") == 0)
402 return @"x-webobjects-server-protocol";
403 else if (strcmp(p, "server-protocol") == 0)
404 return @"x-webobjects-server-protocol";
405 else if (strcmp(p, "remote-addr") == 0)
406 return @"x-webobjects-remote-addr";
407 else if (strcmp(p, "remote-host") == 0)
408 return @"x-webobjects-remote-host";
409 else if (strcmp(p, "server-name") == 0)
410 return @"x-webobjects-server-name";
411 else if (strcmp(p, "server-port") == 0)
412 return @"x-webobjects-server-port";
413 else if (strcmp(p, "server-url") == 0)
414 return @"x-webobjects-server-url";
418 if (strcasecmp(p, "x-cache") == 0)
421 else if (len == 12) {
422 if (strcasecmp(p, "x-powered-by") == 0)
423 return @"x-powered-by";
425 if (strcasecmp(p, "x-zidestore-name") == 0)
426 return @"x-zidestore-name";
427 if (strcasecmp(p, "x-forwarded-for") == 0)
428 return @"x-forwarded-for";
429 if (strcasecmp(p, "x-forwarded-host") == 0)
430 return @"x-forwarded-host";
431 if (strcasecmp(p, "x-forwarded-server") == 0)
432 return @"x-forwarded-server";
438 NSLog(@"making custom header name '%s'!", p);
440 /* make name lowercase (we own the buffer, so we can work on it) */
444 for (t = (unsigned char *)p; *t != '\0'; t++)
447 return [[NSString alloc] initWithCString:p];
450 - (NSException *)parseHeader {
451 NSException *e = nil;
453 while ((e = [self readNextLine]) == nil) {
454 unsigned char *p, *v;
456 NSString *headerName;
457 NSString *headerValue;
460 printf("read header line: '%s'\n", self->lineBuffer);
462 if (strlen((char *)self->lineBuffer) == 0) {
463 /* found end of header */
467 p = self->lineBuffer;
469 if (*p == ' ' || *p == '\t') {
470 // TODO: implement folding (remember last header-key, add string)
471 [self errorWithFormat:
472 @"(%s): got a folded HTTP header line, cannot process!",
473 __PRETTY_FUNCTION__];
477 /* find key/value separator */
478 if ((v = (unsigned char *)index((char *)p, ':')) == NULL) {
479 [self warnWithFormat:@"got malformed header line: '%s'",
484 *v = '\0'; v++; /* now 'p' points to name and 'v' to value */
486 /* skip leading spaces */
487 while (*v != '\0' && (*v == ' ' || *v == '\t'))
491 /* trim trailing spaces */
492 for (idx = strlen((char *)v) - 1; idx >= 0; idx--) {
493 if ((v[idx] != ' ' && v[idx] != '\t'))
500 headerName = stringForHeaderName((char *)p);
501 headerValue = [[NSStringClass alloc] initWithCString:(char *)v];
503 if (headerName == ContentLengthHeaderName)
504 self->clen = atoi((char *)v);
506 if (headerName != nil || headerValue != nil) {
507 if (self->headers == nil)
508 self->headers = [[NSMutableDictionary alloc] initWithCapacity:32];
510 [self->headers setObject:headerValue forKey:headerName];
513 [headerValue release];
514 [headerName release];
520 - (NSException *)parseEntityOfMethod:(NSString *)_method {
522 TODO: several cases are caught:
523 a) content-length = 0 => empty data
524 b) content-length small => read into memory
525 c) content-length large => streamed into the filesystem to safe RAM
526 d) content-length unknown => ??
529 if (self->clen == 0) {
532 else if (self->clen < 0) {
533 /* I think HTTP/1.1 requires a content-length header to be present ? */
535 if ([self->httpVersion isEqualToString:@"HTTP/1.0"] ||
536 [self->httpVersion isEqualToString:@"HTTP/0.9"]) {
537 /* content-length unknown, read till EOF */
538 BOOL readToEOF = YES;
540 if ([_method isEqualToString:@"HEAD"])
542 else if ([_method isEqualToString:@"GET"])
544 else if ([_method isEqualToString:@"DELETE"])
548 [self warnWithFormat:
549 @"not processing entity of request without contentlen!"];
553 else if (self->clen > maxUploadSize*1024) {
554 /* entity is too large */
557 s = [NSString stringWithFormat:@"The maximum HTTP transaction size was "
558 @"exceeded (%d vs %d)", self->clen, maxUploadSize * 1024];
559 return [NSException exceptionWithName:@"LimitException"
560 reason:s userInfo:nil];
562 else if (self->clen > fileIOBoundary) {
563 /* we are streaming the content to a file and use a memory mapped data */
571 [self debugWithFormat:@"streaming %i bytes into file ...", self->clen];
573 fn = [[NSProcessInfo processInfo] temporaryFileName];
575 if ((t = fopen([fn cString], "w")) == NULL) {
576 [self errorWithFormat:@"could not open temporary file '%@'!", fn];
578 /* read into memory as a fallback ... */
581 [[(NGStream *)self->io safeReadDataOfLength:self->clen] retain];
582 if (self->content == nil)
583 return [self->io lastException];
587 for (toGo = self->clen; toGo > 0; ) {
588 unsigned readCount, writeCount;
590 /* read from socket */
591 readCount = [self->io readBytes:buf count:sizeof(buf)];
592 if (readCount == NGStreamError) {
600 if ((writeCount = fwrite(buf, readCount, 1, t)) != 1) {
603 writeError = ferror(t);
610 unlink([fn cString]); /* delete temporary file */
612 if (writeError == 0) {
613 return [NSException exceptionWithName:@"SystemWriteError"
614 reason:@"failed to write data to upload file"
618 return [self->io lastException];
621 self->content = [[NSData alloc] initWithContentsOfMappedFile:fn];
622 unlink([fn cString]); /* if the mmap disappears, the storage is freed */
625 /* content-length known and small */
626 //[self logWithFormat:@"reading %i bytes of the entity", self->clen];
629 [[(NGStream *)self->io safeReadDataOfLength:self->clen] retain];
630 if (self->content == nil)
631 return [self->io lastException];
633 //[self logWithFormat:@"read %i bytes.", [self->content length]];
639 /* handling expectations */
641 - (BOOL)processContinueExpectation {
642 // TODO: this should check the credentials of a request before accepting the
643 // body. The current implementation is far from optimal and only added
644 // for Mono compatibility (and actually produces the same behaviour
645 // like with HTTP/1.0 ...)
646 static char *contStatLine =
647 "HTTP/1.0 100 Continue\r\n"
648 "content-length: 0\r\n"
650 static char *failStatLine =
651 "HTTP/1.0 417 Expectation Failed\r\n"
652 "content-length: 0\r\n"
654 char *respline = NULL;
657 [self debugWithFormat:@"process 100 continue on IO: %@", self->io];
659 if (self->clen > 0 && (self->clen > (maxUploadSize * 1024))) {
660 // TODO: return a 417 expectation failed
662 respline = failStatLine;
666 respline = contStatLine;
669 if (![self->io safeWriteBytes:respline count:strlen(respline)]) {
670 ASSIGN(self->lastException, [self->io lastException]);
673 if (![self->io flush]) {
674 ASSIGN(self->lastException, [self->io lastException]);
683 - (void)_fixupContentEncodingOfMessageBasedOnContentType:(WOMessage *)_msg {
685 NSStringEncoding enc = 0;
687 NGMimeType *rqContentType;
690 if (![(ctype = [_msg headerForKey:@"content-type"]) isNotEmpty])
691 /* an HTTP message w/o a content type? */
694 if ((rqContentType = [NGMimeType mimeType:ctype]) == nil) {
695 [self warnWithFormat:@"could not parse MIME type: '%@'", ctype];
699 charset = [rqContentType valueOfParameter:@"charset"];
701 if ([charset isNotEmpty]) {
702 enc = [NSString stringEncodingForEncodingNamed:charset];
704 else if (rqContentType != nil) {
705 /* process default charsets for content types */
706 NSString *majorType = [rqContentType type];
708 if ([majorType isEqualToString:@"text"]) {
709 NSString *subType = [rqContentType subType];
711 if ([subType isEqualToString:@"calendar"]) {
712 /* RFC2445, section 4.1.4 */
713 enc = NSUTF8StringEncoding;
716 else if ([majorType isEqualToString:@"application"]) {
717 NSString *subType = [rqContentType subType];
719 if ([subType isEqualToString:@"xml"]) {
720 // TBD: we should look at the actual content! (<?xml declaration
722 enc = NSUTF8StringEncoding;
728 [_msg setContentEncoding:enc];
731 - (WORequest *)parseRequest {
732 NSException *e = nil;
734 NSString *uri = @"/";
735 NSString *method = @"GET";
740 [self logWithFormat:@"HeavyDebug: parsing response ..."];
742 /* process request line */
744 if ((e = [self readNextLine])) {
745 ASSIGN(self->lastException, e);
749 printf("read request line: '%s'\n", self->lineBuffer);
752 /* sample line: "GET / HTTP/1.0" */
757 p = (char *)self->lineBuffer;
758 if ((t = index(p, ' ')) == NULL) {
759 [self logWithFormat:@"got broken request line '%s'", self->lineBuffer];
765 /* intended fall-throughs ! */
767 if (strcasecmp(p, "BPROPFIND") == 0) { method = @"BPROPFIND"; break; }
768 if (strcasecmp(p, "BPROPPATCH") == 0) { method = @"BPROPPATCH"; break; }
770 if (strcasecmp(p, "COPY") == 0) { method = @"COPY"; break; }
771 if (strcasecmp(p, "CHECKOUT") == 0) { method = @"CHECKOUT"; break; }
772 if (strcasecmp(p, "CHECKIN") == 0) { method = @"CHECKIN"; break; }
774 if (strcasecmp(p, "DELETE") == 0) { method = @"DELETE"; break; }
776 if (strcasecmp(p, "HEAD") == 0) { method = @"HEAD"; break; }
778 if (strcasecmp(p, "LOCK") == 0) { method = @"LOCK"; break; }
780 if (strcasecmp(p, "GET") == 0) { method = @"GET"; break; }
782 if (strcasecmp(p, "MKCOL") == 0) { method = @"MKCOL"; break; }
783 if (strcasecmp(p, "MOVE") == 0) { method = @"MOVE"; break; }
785 if (strcasecmp(p, "NOTIFY") == 0) { method = @"NOTIFY"; break; }
787 if (strcasecmp(p, "OPTIONS") == 0) { method = @"OPTIONS"; break; }
789 if (strcasecmp(p, "PUT") == 0) { method = @"PUT"; break; }
790 if (strcasecmp(p, "POST") == 0) { method = @"POST"; break; }
791 if (strcasecmp(p, "PROPFIND") == 0) { method = @"PROPFIND"; break; }
792 if (strcasecmp(p, "PROPPATCH") == 0) { method = @"PROPPATCH"; break; }
793 if (strcasecmp(p, "POLL") == 0) { method = @"POLL"; break; }
795 if (strcasecmp(p, "REPORT") == 0) { method = @"REPORT"; break; }
797 if (strcasecmp(p, "SEARCH") == 0) { method = @"SEARCH"; break; }
798 if (strcasecmp(p, "SUBSCRIBE") == 0) { method = @"SUBSCRIBE"; break; }
800 if (strcasecmp(p, "UNLOCK") == 0) { method = @"UNLOCK"; break; }
801 if (strcasecmp(p, "UNSUBSCRIBE")== 0) { method = @"UNSUBSCRIBE"; break; }
802 if (strcasecmp(p, "UNCHECKOUT") == 0) { method = @"UNCHECKOUT"; break; }
804 if (strcasecmp(p, "VERSION-CONTROL") == 0) {
805 method = @"VERSION-CONTROL";
811 [self debugWithFormat:@"making custom HTTP method name: '%s'", p];
812 method = [NSString stringWithCString:p];
818 p = t + 1; /* skip space */
819 while (*p != '\0' && (*p == ' ' || *p == '\t')) /* skip spaces */
823 [self logWithFormat:@"got broken request line '%s'", self->lineBuffer];
827 if ((t = index(p, ' ')) == NULL) {
828 /* the URI isn't followed by a HTTP version */
829 self->httpVersion = @"HTTP/0.9";
830 /* TODO: strip trailing spaces for better compliance */
831 uri = [NSString stringWithCString:p];
835 uri = [NSString stringWithCString:p];
839 p = t + 1; /* skip space */
840 while (*p != '\0' && (*p == ' ' || *p == '\t')) /* skip spaces */
844 self->httpVersion = @"HTTP/0.9";
845 else if (strcasecmp(p, "http/1.0") == 0)
846 self->httpVersion = @"HTTP/1.0";
847 else if (strcasecmp(p, "http/1.1") == 0)
848 self->httpVersion = @"HTTP/1.1";
850 /* TODO: strip trailing spaces */
851 self->httpVersion = [[NSString alloc] initWithCString:p];
858 if ((e = [self parseHeader]) != nil) {
859 ASSIGN(self->lastException, e);
863 [self logWithFormat:@"parsed header: %@", self->headers];
865 /* check for expectations */
867 if ((expect = [self->headers objectForKey:@"expect"]) != nil) {
868 if ([expect rangeOfString:@"100-continue"
869 options:NSCaseInsensitiveSearch].length > 0) {
870 if (![self processContinueExpectation])
878 if ((e = [self parseEntityOfMethod:method])) {
879 ASSIGN(self->lastException, e);
885 [self logWithFormat:@"HeavyDebug: got all .."];
887 r = [[WORequest alloc] initWithMethod:method
889 httpVersion:self->httpVersion
890 headers:self->headers
891 content:self->content
893 [self _fixupContentEncodingOfMessageBasedOnContentType:r];
897 [self logWithFormat:@"HeavyDebug: request: %@", r];
899 return [r autorelease];
902 - (WOResponse *)parseResponse {
903 NSException *e = nil;
909 [self logWithFormat:@"HeavyDebug: parsing response ..."];
911 /* process response line */
913 if ((e = [self readNextLine])) {
914 ASSIGN(self->lastException, e);
918 printf("read response line: '%s'\n", self->lineBuffer);
921 /* sample line: "HTTP/1.0 200 OK" */
926 p = (char *)self->lineBuffer;
927 if ((t = index(p, ' ')) == NULL) {
928 [self logWithFormat:@"got broken response line '%s'", self->lineBuffer];
933 if (strcasecmp(p, "http/1.0") == 0)
934 self->httpVersion = @"HTTP/1.0";
935 else if (strcasecmp(p, "http/1.1") == 0)
936 self->httpVersion = @"HTTP/1.1";
938 self->httpVersion = [[NSString alloc] initWithCString:p];
942 p = t + 1; /* skip space */
943 while (*p != '\0' && (*p == ' ' || *p == '\t')) /* skip spaces */
946 [self logWithFormat:@"got broken response line '%s'", self->lineBuffer];
951 /* we don't need to parse a reason ... */
956 if ((e = [self parseHeader])) {
957 ASSIGN(self->lastException, e);
961 [self logWithFormat:@"parsed header: %@", self->headers];
966 if ((e = [self parseEntityOfMethod:nil /* parsing a response */])) {
967 ASSIGN(self->lastException, e);
973 [self logWithFormat:@"HeavyDebug: got all .."];
975 r = [[[WOResponse alloc] init] autorelease];
977 [r setHTTPVersion:self->httpVersion];
978 [r setHeaders:self->headers];
979 [r setContent:self->content];
980 [self _fixupContentEncodingOfMessageBasedOnContentType:r];
985 [self logWithFormat:@"HeavyDebug: response: %@", r];
990 - (NSException *)lastException {
991 return self->lastException;
996 - (BOOL)isDebuggingEnabled {
1000 @end /* WOSimpleHTTPParser */