Index: src/OFHTTPClient.m ================================================================== --- src/OFHTTPClient.m +++ src/OFHTTPClient.m @@ -311,17 +311,17 @@ OFURL *URL = [request URL]; OFString *scheme = [URL scheme]; of_http_request_method_t method = [request method]; OFMutableString *requestString; OFString *user, *password; - OFDictionary *headers = [request headers]; + OFMutableDictionary OF_GENERIC(OFString*, OFString*) *headers; OFDataArray *body = [request body]; OFTCPSocket *socket; OFHTTPClientResponse *response; OFString *line, *version, *redirect, *connectionHeader; bool keepAlive; - OFMutableDictionary *serverHeaders; + OFMutableDictionary OF_GENERIC(OFString*, OFString*) *serverHeaders; OFEnumerator *keyEnumerator, *objectEnumerator; OFString *key, *object; int status; if (![scheme isEqual: @"http"] && ![scheme isEqual: @"https"]) @@ -375,63 +375,78 @@ else requestString = [OFMutableString stringWithFormat: @"%s /%@ HTTP/%@\r\n", of_http_request_method_to_string(method), [URL path], [request protocolVersionString]]; + + headers = [[[request headers] mutableCopy] autorelease]; + if (headers == nil) + headers = [OFMutableDictionary dictionary]; if (([scheme isEqual: @"http"] && [URL port] != 80) || - ([scheme isEqual: @"https"] && [URL port] != 443)) - [requestString appendFormat: @"Host: %@:%d\r\n", - [URL host], [URL port]]; - else - [requestString appendFormat: @"Host: %@\r\n", [URL host]]; + ([scheme isEqual: @"https"] && [URL port] != 443)) { + OFString *host = [OFString stringWithFormat: + @"%@:%d", [URL host], [URL port]]; + + [headers setObject: host + forKey: @"Host"]; + } else + [headers setObject: [URL host] + forKey: @"Host"]; user = [URL user]; password = [URL password]; if ([user length] > 0 || [password length] > 0) { - OFDataArray *authorization = [OFDataArray dataArray]; - - [authorization addItems: [user UTF8String] - count: [user UTF8StringLength]]; - [authorization addItem: ":"]; - [authorization addItems: [password UTF8String] - count: [password UTF8StringLength]]; - - [requestString appendFormat: - @"Authorization: Basic %@\r\n", - [authorization stringByBase64Encoding]]; + OFDataArray *authorizationData = [OFDataArray dataArray]; + OFString *authorization; + + [authorizationData addItems: [user UTF8String] + count: [user UTF8StringLength]]; + [authorizationData addItem: ":"]; + [authorizationData addItems: [password UTF8String] + count: [password UTF8StringLength]]; + + authorization = [OFString stringWithFormat: + @"Basic %@", [authorizationData stringByBase64Encoding]]; + + [headers setObject: authorization + forKey: @"Authorization"]; } if ([headers objectForKey: @"User-Agent"] == nil) - [requestString appendString: - @"User-Agent: Something using ObjFW " - @"\r\n"]; + [headers setObject: @"Something using ObjFW " + @"" + forKey: @"User-Agent"]; if (body != nil) { - if ([headers objectForKey: @"Content-Length"] == nil) - [requestString appendFormat: - @"Content-Length: %zd\r\n", - [body itemSize] * [body count]]; + if ([headers objectForKey: @"Content-Length"] == nil) { + OFString *contentLength = [OFString stringWithFormat: + @"%zd", [body itemSize] * [body count]]; + + [headers setObject: contentLength + forKey: @"Content-Length"]; + } if ([headers objectForKey: @"Content-Type"] == nil) - [requestString appendString: - @"Content-Type: application/x-www-form-urlencoded; " - @"charset=UTF-8\r\n"]; + [headers setObject: @"application/x-www-form-" + @"urlencoded; charset=UTF-8" + forKey: @"Content-Type"]; } + + if ([request protocolVersion].major == 1 && + [request protocolVersion].minor == 0) + [headers setObject: @"keep-alive" + forKey: @"Connection"]; keyEnumerator = [headers keyEnumerator]; objectEnumerator = [headers objectEnumerator]; while ((key = [keyEnumerator nextObject]) != nil && (object = [objectEnumerator nextObject]) != nil) [requestString appendFormat: @"%@: %@\r\n", key, object]; - if ([request protocolVersion].major == 1 && - [request protocolVersion].minor == 0) - [requestString appendString: @"Connection: keep-alive\r\n"]; - [requestString appendString: @"\r\n"]; @try { [socket writeString: requestString]; } @catch (OFWriteFailedException *e) { Index: src/OFHTTPServer.m ================================================================== --- src/OFHTTPServer.m +++ src/OFHTTPServer.m @@ -205,33 +205,42 @@ } - (void)OF_sendHeaders { void *pool = objc_autoreleasePoolPush(); - OFString *date = [[OFDate date] - dateStringWithFormat: @"%a, %d %b %Y %H:%M:%S GMT"]; + OFMutableDictionary OF_GENERIC(OFString*, OFString*) *headers; OFEnumerator *keyEnumerator, *valueEnumerator; OFString *key, *value; - [_socket writeFormat: @"HTTP/%@ %d %s\r\n" - @"Server: %@\r\n" - @"Date: %@\r\n", + [_socket writeFormat: @"HTTP/%@ %d %s\r\n", [self protocolVersionString], _statusCode, - statusCodeToString(_statusCode), - [_server name], date]; + statusCodeToString(_statusCode)]; - keyEnumerator = [_headers keyEnumerator]; - valueEnumerator = [_headers objectEnumerator]; + headers = [[_headers mutableCopy] autorelease]; + + if ([headers objectForKey: @"Date"] == nil) { + OFString *date = [[OFDate date] + dateStringWithFormat: @"%a, %d %b %Y %H:%M:%S GMT"]; + + [headers setObject: date + forKey: @"Date"]; + } + + if ([headers objectForKey: @"Server"] == nil) + [headers setObject: [_server name] + forKey: @"Server"]; + + keyEnumerator = [headers keyEnumerator]; + valueEnumerator = [headers objectEnumerator]; while ((key = [keyEnumerator nextObject]) != nil && (value = [valueEnumerator nextObject]) != nil) - if (![key isEqual: @"Server"] && ![key isEqual: @"Date"]) - [_socket writeFormat: @"%@: %@\r\n", key, value]; + [_socket writeFormat: @"%@: %@\r\n", key, value]; [_socket writeString: @"\r\n"]; _headersSent = true; - _chunked = [[_headers objectForKey: @"Transfer-Encoding"] + _chunked = [[headers objectForKey: @"Transfer-Encoding"] isEqual: @"chunked"]; objc_autoreleasePoolPop(pool); } @@ -656,11 +665,11 @@ - init { self = [super init]; _name = @"OFHTTPServer (ObjFW's HTTP server class " - @")"; + @")"; return self; } - (void)dealloc Index: tests/OFHTTPClientTests.m ================================================================== --- tests/OFHTTPClientTests.m +++ tests/OFHTTPClientTests.m @@ -60,15 +60,15 @@ client = [listener accept]; if (![[client readLine] isEqual: @"GET /foo HTTP/1.1"]) OF_ENSURE(0); - if (![[client readLine] isEqual: - [OFString stringWithFormat: @"Host: 127.0.0.1:%" @PRIu16, _port]]) + if (![[client readLine] hasPrefix: @"User-Agent:"]) OF_ENSURE(0); - if (![[client readLine] hasPrefix: @"User-Agent:"]) + if (![[client readLine] isEqual: + [OFString stringWithFormat: @"Host: 127.0.0.1:%" @PRIu16, _port]]) OF_ENSURE(0); if (![[client readLine] isEqual: @""]) OF_ENSURE(0); @@ -86,11 +86,11 @@ @implementation TestsAppDelegate (OFHTTPClientTests) - (void)HTTPClientTests { OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; HTTPClientTestsServer *server; - OFURL *url; + OFURL *URL; OFHTTPClient *client; OFHTTPRequest *request; OFHTTPResponse *response = nil; OFDataArray *data; @@ -101,17 +101,17 @@ [server start]; [cond wait]; [cond unlock]; - url = [OFURL URLWithString: + URL = [OFURL URLWithString: [OFString stringWithFormat: @"http://127.0.0.1:%" @PRIu16 "/foo", server->_port]]; TEST(@"-[performRequest:]", (client = [OFHTTPClient client]) && - R(request = [OFHTTPRequest requestWithURL: url]) && + R(request = [OFHTTPRequest requestWithURL: URL]) && R(response = [client performRequest: request])) TEST(@"Normalization of server header keys", [[response headers] objectForKey: @"Content-Length"] != nil)