@@ -14,56 +14,18 @@ * file. */ #include "config.h" -#define OF_HTTP_REQUEST_M - -#include -#include - #import "OFHTTPRequest.h" #import "OFString.h" #import "OFURL.h" -#import "OFTCPSocket.h" #import "OFDictionary.h" #import "OFDataArray.h" -#import "OFHTTPRequestFailedException.h" -#import "OFInvalidEncodingException.h" -#import "OFInvalidFormatException.h" -#import "OFInvalidServerReplyException.h" -#import "OFOutOfRangeException.h" -#import "OFTruncatedDataException.h" -#import "OFUnsupportedProtocolException.h" -#import "OFUnsupportedVersionException.h" - -#import "autorelease.h" #import "macros.h" -Class of_http_request_tls_socket_class = Nil; - -static OF_INLINE void -normalizeKey(OFString *key) -{ - uint8_t *str = (uint8_t*)[key UTF8String]; - BOOL firstLetter = YES; - - while (*str != '\0') { - if (!isalnum(*str)) { - firstLetter = YES; - str++; - continue; - } - - *str = (firstLetter ? toupper(*str) : tolower(*str)); - - firstLetter = NO; - str++; - } -} - @implementation OFHTTPRequest + (instancetype)request { return [[[self alloc] init] autorelease]; } @@ -76,15 +38,10 @@ - init { self = [super init]; requestType = OF_HTTP_REQUEST_TYPE_GET; - headers = [[OFDictionary alloc] - initWithObject: @"Something using ObjFW " - @"" - forKey: @"User-Agent"]; - storesData = YES; return self; } - initWithURL: (OFURL*)URL_ @@ -102,12 +59,13 @@ } - (void)dealloc { [URL release]; - [queryString release]; [headers release]; + [postData release]; + [MIMEType release]; [super dealloc]; } - (void)setURL: (OFURL*)URL_ @@ -128,20 +86,10 @@ - (of_http_request_type_t)requestType { return requestType; } -- (void)setQueryString: (OFString*)queryString_ -{ - OF_SETTER(queryString, queryString_, YES, 1) -} - -- (OFString*)queryString -{ - OF_GETTER(queryString, YES) -} - - (void)setHeaders: (OFDictionary*)headers_ { OF_SETTER(headers, headers_, YES, 1) } @@ -148,397 +96,35 @@ - (OFDictionary*)headers { OF_GETTER(headers, YES) } -- (void)setRedirectsFromHTTPSToHTTPAllowed: (BOOL)allowed -{ - redirectsFromHTTPSToHTTPAllowed = allowed; -} - -- (BOOL)redirectsFromHTTPSToHTTPAllowed -{ - return redirectsFromHTTPSToHTTPAllowed; -} - -- (void)setDelegate: (id )delegate_ -{ - delegate = delegate_; -} - -- (id )delegate -{ - return delegate; -} - -- (void)setStoresData: (BOOL)storesData_ -{ - storesData = storesData_; -} - -- (BOOL)storesData -{ - return storesData; -} - -- (OFHTTPRequestResult*)perform -{ - return [self performWithRedirects: 10]; -} - -- (OFHTTPRequestResult*)performWithRedirects: (size_t)redirects -{ - void *pool = objc_autoreleasePoolPush(); - OFString *scheme = [URL scheme]; - OFTCPSocket *sock; - OFHTTPRequestResult *result; - OFString *line, *path, *version; - OFMutableDictionary *serverHeaders; - OFDataArray *data; - OFEnumerator *keyEnumerator, *objectEnumerator; - OFString *key, *object, *contentLengthHeader; - int status; - const char *type = NULL; - size_t contentLength = 0; - BOOL chunked; - char *buffer; - size_t bytesReceived; - - if (![scheme isEqual: @"http"] && ![scheme isEqual: @"https"]) - @throw [OFUnsupportedProtocolException - exceptionWithClass: [self class] - URL: URL]; - - if ([scheme isEqual: @"http"]) - sock = [OFTCPSocket socket]; - else { - if (of_http_request_tls_socket_class == Nil) - @throw [OFUnsupportedProtocolException - exceptionWithClass: [self class] - URL: URL]; - - sock = [[[of_http_request_tls_socket_class alloc] init] - autorelease]; - } - - [delegate request: self - didCreateSocket: sock]; - - [sock connectToHost: [URL host] - port: [URL port]]; - - /* - * Work around a bug with packet bisection in lighttpd when using - * HTTPS. - */ - [sock setWriteBufferEnabled: YES]; - - if (requestType == OF_HTTP_REQUEST_TYPE_GET) - type = "GET"; - if (requestType == OF_HTTP_REQUEST_TYPE_HEAD) - type = "HEAD"; - if (requestType == OF_HTTP_REQUEST_TYPE_POST) - type = "POST"; - - if ([(path = [URL path]) isEqual: @""]) - path = @"/"; - - if ([URL query] != nil) - [sock writeFormat: @"%s %@?%@ HTTP/1.1\r\n", - type, path, [URL query]]; - else - [sock writeFormat: @"%s %@ HTTP/1.1\r\n", type, path]; - - if ([URL port] == 80) - [sock writeFormat: @"Host: %@\r\n", [URL host]]; - else - [sock writeFormat: @"Host: %@:%d\r\n", [URL host], - [URL port]]; - - [sock writeString: @"Connection: close\r\n"]; - - keyEnumerator = [headers keyEnumerator]; - objectEnumerator = [headers objectEnumerator]; - - while ((key = [keyEnumerator nextObject]) != nil && - (object = [objectEnumerator nextObject]) != nil) - [sock writeFormat: @"%@: %@\r\n", key, object]; - - if (requestType == OF_HTTP_REQUEST_TYPE_POST) { - if ([headers objectForKey: @"Content-Type"] == nil) - [sock writeString: @"Content-Type: " - @"application/x-www-form-urlencoded; " - @"charset=UTF-8\r\n"]; - - if ([headers objectForKey: @"Content-Length"] == nil) - [sock writeFormat: @"Content-Length: %d\r\n", - [queryString UTF8StringLength]]; - } - - [sock writeString: @"\r\n"]; - - /* Work around a bug in lighttpd, see above */ - [sock flushWriteBuffer]; - [sock setWriteBufferEnabled: NO]; - - if (requestType == OF_HTTP_REQUEST_TYPE_POST) - [sock writeString: queryString]; - - @try { - line = [sock readLine]; - } @catch (OFInvalidEncodingException *e) { - @throw [OFInvalidServerReplyException - exceptionWithClass: [self class]]; - } - - if (![line hasPrefix: @"HTTP/"] || [line characterAtIndex: 8] != ' ') - @throw [OFInvalidServerReplyException - exceptionWithClass: [self class]]; - - version = [line substringWithRange: of_range(5, 3)]; - if (![version isEqual: @"1.0"] && ![version isEqual: @"1.1"]) - @throw [OFUnsupportedVersionException - exceptionWithClass: [self class] - version: version]; - - status = (int)[[line substringWithRange: of_range(9, 3)] decimalValue]; - - serverHeaders = [OFMutableDictionary dictionary]; - - for (;;) { - OFString *key, *value; - const char *line_c, *tmp; - - @try { - line = [sock readLine]; - } @catch (OFInvalidEncodingException *e) { - @throw [OFInvalidServerReplyException - exceptionWithClass: [self class]]; - } - - if (line == nil) - @throw [OFInvalidServerReplyException - exceptionWithClass: [self class]]; - - if ([line isEqual: @""]) - break; - - line_c = [line UTF8String]; - - if ((tmp = strchr(line_c, ':')) == NULL) - @throw [OFInvalidServerReplyException - exceptionWithClass: [self class]]; - - key = [OFString stringWithUTF8String: line_c - length: tmp - line_c]; - normalizeKey(key); - - do { - tmp++; - } while (*tmp == ' '); - - value = [OFString stringWithUTF8String: tmp]; - - if ((redirects > 0 && (status == 301 || status == 302 || - status == 303 || status == 307) && - [key isEqual: @"Location"]) && - (redirectsFromHTTPSToHTTPAllowed || - [scheme isEqual: @"http"] || - ![value hasPrefix: @"http://"])) { - OFURL *new; - BOOL follow; - - new = [OFURL URLWithString: value - relativeToURL: URL]; - - follow = [delegate request: self - shouldFollowRedirectTo: new]; - - if (!follow && delegate != nil) { - [serverHeaders setObject: value - forKey: key]; - continue; - } - - new = [new retain]; - [URL release]; - URL = new; - - if (status == 303) { - requestType = OF_HTTP_REQUEST_TYPE_GET; - [queryString release]; - queryString = nil; - } - - objc_autoreleasePoolPop(pool); - - return [self performWithRedirects: redirects - 1]; - } - - [serverHeaders setObject: value - forKey: key]; - } - - [delegate request: self - didReceiveHeaders: serverHeaders - withStatusCode: status]; - - data = (storesData ? [OFDataArray dataArray] : nil); - chunked = [[serverHeaders objectForKey: @"Transfer-Encoding"] - isEqual: @"chunked"]; - - contentLengthHeader = [serverHeaders objectForKey: @"Content-Length"]; - - if (contentLengthHeader != nil) { - contentLength = (size_t)[contentLengthHeader decimalValue]; - - if (contentLength > SIZE_MAX) - @throw [OFOutOfRangeException - exceptionWithClass: [self class]]; - } - - buffer = [self allocMemoryWithSize: of_pagesize]; - bytesReceived = 0; - @try { - if (chunked) { - for (;;) { - void *pool2 = objc_autoreleasePoolPush(); - size_t toRead; - of_range_t range; - - @try { - line = [sock readLine]; - } @catch (OFInvalidEncodingException *e) { - @throw [OFInvalidServerReplyException - exceptionWithClass: [self class]]; - } - - range = [line rangeOfString: @";"]; - if (range.location != OF_NOT_FOUND) - line = [line substringWithRange: - of_range(0, range.location)]; - - @try { - toRead = - (size_t)[line hexadecimalValue]; - } @catch (OFInvalidFormatException *e) { - @throw [OFInvalidServerReplyException - exceptionWithClass: [self class]]; - } - - if (toRead == 0 || - (contentLengthHeader != nil && - contentLength >= bytesReceived)) - break; - - while (toRead > 0) { - size_t length = (toRead < of_pagesize - ? toRead : of_pagesize); - - length = [sock readIntoBuffer: buffer - length: length]; - - [delegate request: self - didReceiveData: buffer - withLength: length]; - - objc_autoreleasePoolPop(pool2); - pool2 = objc_autoreleasePoolPush(); - - bytesReceived += length; - [data addItemsFromCArray: buffer - count: length]; - - toRead -= length; - } - - @try { - line = [sock readLine]; - } @catch (OFInvalidEncodingException *e) { - @throw [OFInvalidServerReplyException - exceptionWithClass: [self class]]; - } - - if (![line isEqual: @""]) - @throw [OFInvalidServerReplyException - exceptionWithClass: [self class]]; - - objc_autoreleasePoolPop(pool2); - } - } else { - size_t length; - - while (![sock isAtEndOfStream]) { - void *pool2; - - length = [sock readIntoBuffer: buffer - length: of_pagesize]; - - pool2 = objc_autoreleasePoolPush(); - - [delegate request: self - didReceiveData: buffer - withLength: length]; - - objc_autoreleasePoolPop(pool2); - - bytesReceived += length; - [data addItemsFromCArray: buffer - count: length]; - - if (contentLengthHeader != nil && - bytesReceived >= contentLength) - break; - } - } - } @finally { - [self freeMemory: buffer]; - } - - [sock close]; - - /* - * We only want to throw on these status codes as we will throw an - * OFHTTPRequestFailedException for all other status codes later. - */ - if (contentLengthHeader != nil && contentLength != bytesReceived && - (status == 200 || status == 301 || status == 302 || status == 303 || - status == 307)) - @throw [OFTruncatedDataException - exceptionWithClass: [self class]]; - - [serverHeaders makeImmutable]; - - result = [[OFHTTPRequestResult alloc] initWithStatusCode: status - headers: serverHeaders - data: data]; - - switch (status) { - case 200: - case 301: - case 302: - case 303: - case 307: - break; - default: - [result autorelease]; - @throw [OFHTTPRequestFailedException - exceptionWithClass: [self class] - request: self - result: result]; - } - - objc_autoreleasePoolPop(pool); - - return [result autorelease]; +- (void)setPostData: (OFDataArray*)postData_ +{ + OF_SETTER(postData, postData_, YES, 0) +} + +- (OFDataArray*)postData +{ + OF_GETTER(postData, YES) +} + +- (void)setMIMEType: (OFString*)MIMEType_ +{ + OF_SETTER(MIMEType, MIMEType_, YES, 1) +} + +- (OFString*)MIMEType +{ + OF_GETTER(MIMEType, YES) } @end @implementation OFHTTPRequestResult -- initWithStatusCode: (short)status - headers: (OFDictionary*)headers_ - data: (OFDataArray*)data_ +- OF_initWithStatusCode: (short)status + headers: (OFDictionary*)headers_ + data: (OFDataArray*)data_ { self = [super init]; statusCode = status; data = [data_ retain]; @@ -567,31 +153,6 @@ - (OFDataArray*)data { OF_GETTER(data, YES) } -@end - -@implementation OFObject (OFHTTPRequestDelegate) -- (void)request: (OFHTTPRequest*)request - didCreateSocket: (OFTCPSocket*)socket -{ -} - -- (void)request: (OFHTTPRequest*)request - didReceiveHeaders: (OFDictionary*)headers - withStatusCode: (int)statusCode -{ -} - -- (void)request: (OFHTTPRequest*)request - didReceiveData: (const char*)data - withLength: (size_t)len -{ -} - -- (BOOL)request: (OFHTTPRequest*)request - shouldFollowRedirectTo: (OFURL*)url -{ - return YES; -} @end