@@ -33,10 +33,11 @@ #import "OFTCPSocket.h" #import "OFURL.h" #import "OFAlreadyConnectedException.h" #import "OFHTTPRequestFailedException.h" +#import "OFInvalidArgumentException.h" #import "OFInvalidEncodingException.h" #import "OFInvalidFormatException.h" #import "OFInvalidServerReplyException.h" #import "OFNotImplementedException.h" #import "OFNotOpenException.h" @@ -47,10 +48,11 @@ #import "OFUnsupportedVersionException.h" #import "OFWriteFailedException.h" @interface OFHTTPClientRequestHandler: OFObject { +@public OFHTTPClient *_client; OFHTTPRequest *_request; unsigned int _redirects; id _context; bool _firstLine; @@ -64,10 +66,22 @@ redirects: (unsigned int)redirects context: (id)context; - (void)start; - (void)closeAndReconnect; @end + +@interface OFHTTPClientRequestBodyStream: OFStream +{ + OFHTTPClientRequestHandler *_handler; + OFTCPSocket *_socket; + intmax_t _contentLength, _written; + bool _closed; +} + +- (instancetype)initWithHandler: (OFHTTPClientRequestHandler *)handler + socket: (OFTCPSocket *)sock; +@end @interface OFHTTPClientResponse: OFHTTPResponse { OFTCPSocket *_socket; bool _hasContentLength, _chunked, _keepAlive, _atEndOfStream; @@ -85,11 +99,10 @@ void *pool = objc_autoreleasePoolPush(); of_http_request_method_t method = [request method]; OFURL *URL = [request URL]; OFString *path; OFString *user = [URL user], *password = [URL password]; - OFData *body = [request body]; OFMutableString *requestString; OFMutableDictionary OF_GENERIC(OFString *, OFString *) *headers; OFEnumerator OF_GENERIC(OFString *) *keyEnumerator, *objectEnumerator; OFString *key, *object; @@ -149,30 +162,21 @@ if ([headers objectForKey: @"User-Agent"] == nil) [headers setObject: @"Something using ObjFW " @"" forKey: @"User-Agent"]; - if (body != nil) { - 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) - [headers setObject: @"application/x-www-form-" - @"urlencoded; charset=UTF-8" - forKey: @"Content-Type"]; - } - if ([request protocolVersion].major == 1 && [request protocolVersion].minor == 0 && [headers objectForKey: @"Connection"] == nil) [headers setObject: @"keep-alive" forKey: @"Connection"]; + + if ([headers objectForKey: @"Content-Length"] != nil && + [headers objectForKey: @"Content-Type"] == nil) + [headers setObject: @"application/x-www-form-" + @"urlencoded; charset=UTF-8" + forKey: @"Content-Type"]; keyEnumerator = [headers keyEnumerator]; objectEnumerator = [headers objectEnumerator]; while ((key = [keyEnumerator nextObject]) != nil && @@ -349,17 +353,17 @@ objectEnumerator = [headers objectEnumerator]; while ((key = [keyEnumerator nextObject]) != nil && (object = [objectEnumerator nextObject]) != nil) - if ([key hasPrefix: @"Content-"]) + if ([key hasPrefix: @"Content-"] || + [key hasPrefix: @"Transfer-"]) [newHeaders removeObjectForKey: key]; [newRequest setMethod: OF_HTTP_REQUEST_METHOD_GET]; - [newRequest setBody: nil]; } [newRequest setURL: newURL]; [newRequest setHeaders: newHeaders]; @@ -393,11 +397,11 @@ @try { [self createResponseWithSocketOrThrow: sock]; } @catch (id e) { [_client->_delegate client: _client didEncounterException: e - forRequest: _request + request: _request context: _context]; } } - (bool)handleFirstLine: (OFString *)line @@ -503,11 +507,11 @@ [OFInvalidEncodingException class]]) exception = [OFInvalidServerReplyException exception]; [_client->_delegate client: _client didEncounterException: exception - forRequest: _request + request: _request context: _context]; return false; } @try { @@ -518,48 +522,24 @@ ret = [self handleServerHeader: line socket: sock]; } @catch (id e) { [_client->_delegate client: _client didEncounterException: e - forRequest: _request + request: _request context: _context]; ret = false; } return ret; } -- (size_t)socket: (OFTCPSocket *)sock - didWriteBody: (const void **)body - length: (size_t)length - context: (id)context - exception: (id)exception -{ - if (exception != nil) { - [_client->_delegate client: _client - didEncounterException: exception - forRequest: _request - context: _context]; - return 0; - } - - _firstLine = true; - [sock asyncReadLineWithTarget: self - selector: @selector(socket:didReadLine:context: - exception:) - context: nil]; - return 0; -} - - (size_t)socket: (OFTCPSocket *)sock didWriteRequest: (const void **)request length: (size_t)length context: (id)context exception: (id)exception { - OFData *body; - if (exception != nil) { if ([exception isKindOfClass: [OFWriteFailedException class]] && ([exception errNo] == ECONNRESET || [exception errNo] == EPIPE)) { /* In case a keep-alive connection timed out */ @@ -567,29 +547,36 @@ return 0; } [_client->_delegate client: _client didEncounterException: exception - forRequest: _request + request: _request context: _context]; return 0; } - if ((body = [_request body]) != nil) { - [sock asyncWriteBuffer: [body items] - length: [body count] * [body itemSize] - target: self - selector: @selector(socket:didWriteBody:length: - context:exception:) - context: nil]; - return 0; + _firstLine = true; + + if ([[_request headers] objectForKey: @"Content-Length"] != nil) { + OFStream *stream = [[[OFHTTPClientRequestBodyStream alloc] + initWithHandler: self + socket: sock] autorelease]; + + if ([_client->_delegate respondsToSelector: + @selector(client:requestsBody:request:context:)]) + [_client->_delegate client: _client + requestsBody: stream + request: _request + context: _context]; + } else - return [self socket: sock - didWriteBody: NULL - length: 0 - context: nil - exception: nil]; + [sock asyncReadLineWithTarget: self + selector: @selector(socket:didReadLine: + context:exception:) + context: nil]; + + return 0; } - (void)handleSocket: (OFTCPSocket *)sock { /* @@ -617,11 +604,11 @@ length:context:exception:) context: requestString]; } @catch (id e) { [_client->_delegate client: _client didEncounterException: e - forRequest: _request + request: _request context: _context]; return; } } @@ -630,20 +617,20 @@ exception: (id)exception { if (exception != nil) { [_client->_delegate client: _client didEncounterException: exception - forRequest: _request + request: _request context: _context]; return; } if ([_client->_delegate respondsToSelector: - @selector(client:didCreateSocket:forRequest:context:)]) + @selector(client:didCreateSocket:request:context:)]) [_client->_delegate client: _client didCreateSocket: sock - forRequest: _request + request: _request context: _context]; [self performSelector: @selector(handleSocket:) withObject: sock afterDelay: 0]; @@ -656,11 +643,11 @@ exception: (id)exception { if (exception != nil) { [_client->_delegate client: _client didEncounterException: exception - forRequest: _request + request: _request context: _context]; return false; } if ([response isAtEndOfStream]) { @@ -756,15 +743,108 @@ exception:) context: nil]; } @catch (id e) { [_client->_delegate client: _client didEncounterException: e - forRequest: _request + request: _request context: _context]; } } @end + +@implementation OFHTTPClientRequestBodyStream +- (instancetype)initWithHandler: (OFHTTPClientRequestHandler *)handler + socket: (OFTCPSocket *)sock +{ + self = [super init]; + + @try { + OFDictionary OF_GENERIC(OFString *, OFString *) *headers; + OFString *contentLengthString; + + _handler = [handler retain]; + _socket = [sock retain]; + + headers = [_handler->_request headers]; + + contentLengthString = [headers objectForKey: @"Content-Length"]; + if (contentLengthString == nil) + @throw [OFInvalidArgumentException exception]; + + _contentLength = [contentLengthString decimalValue]; + if (_contentLength < 0) + @throw [OFOutOfRangeException exception]; + + if ([headers objectForKey: @"Transfer-Encoding"] != nil) + @throw [OFInvalidArgumentException exception]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [self close]; + + [_handler release]; + [_socket release]; + + [super dealloc]; +} + +- (size_t)lowlevelWriteBuffer: (const void *)buffer + length: (size_t)length +{ + size_t written; + +#if SIZE_MAX >= INTMAX_MAX + if (length > INTMAX_MAX) + @throw [OFOutOfRangeException exception]; +#endif + + if (INTMAX_MAX - _written < (intmax_t)length || + _written + (intmax_t)length > _contentLength) + @throw [OFOutOfRangeException exception]; + + written = [_socket writeBuffer: buffer + length: length]; + +#if SIZE_MAX >= INTMAX_MAX + if (written > INTMAX_MAX) + @throw [OFOutOfRangeException exception]; +#endif + + if (INTMAX_MAX - _written < (intmax_t)written) + @throw [OFOutOfRangeException exception]; + + _written += written; + + return written; +} + +- (void)close +{ + if (_closed) + return; + + if (_written < _contentLength) + @throw [OFTruncatedDataException exception]; + + [_socket asyncReadLineWithTarget: _handler + selector: @selector(socket:didReadLine:context: + exception:) + context: nil]; +} + +- (int)fileDescriptorForWriting +{ + return [_socket fileDescriptorForWriting]; +} +@end @implementation OFHTTPClientResponse @synthesize of_keepAlive = _keepAlive; - (instancetype)initWithSocket: (OFTCPSocket *)sock