@@ -74,10 +74,11 @@ @interface OFHTTPClientRequestBodyStream: OFStream { OFHTTPClientRequestHandler *_handler; OFTCPSocket *_socket; + bool _chunked; uintmax_t _toWrite; bool _atEndOfStream; } - (instancetype)initWithHandler: (OFHTTPClientRequestHandler *)handler @@ -548,10 +549,13 @@ didWriteString: (OFString *)string encoding: (of_string_encoding_t)encoding bytesWritten: (size_t)bytesWritten exception: (id)exception { + OFDictionary OF_GENERIC(OFString *, OFString *) *headers; + OFString *transferEncoding; + if (exception != nil) { if ([exception isKindOfClass: [OFWriteFailedException class]] && ([exception errNo] == ECONNRESET || [exception errNo] == EPIPE)) { /* In case a keep-alive connection timed out */ @@ -563,11 +567,15 @@ return nil; } _firstLine = true; - if ([_request.headers objectForKey: @"Content-Length"] != nil) { + headers = _request.headers; + transferEncoding = [headers objectForKey: @"Transfer-Encoding"]; + + if ([transferEncoding isEqual: @"chunked"] || + [headers objectForKey: @"Content-Length"] != nil) { stream.delegate = nil; OFStream *requestBody = [[[OFHTTPClientRequestBodyStream alloc] initWithHandler: self socket: (OFTCPSocket *)stream] autorelease]; @@ -702,28 +710,32 @@ self = [super init]; @try { OFDictionary OF_GENERIC(OFString *, OFString *) *headers; intmax_t contentLength; - OFString *contentLengthString; + OFString *transferEncoding, *contentLengthString; _handler = [handler retain]; _socket = [sock retain]; headers = _handler->_request.headers; + + transferEncoding = [headers objectForKey: @"Transfer-Encoding"]; + _chunked = [transferEncoding isEqual: @"chunked"]; contentLengthString = [headers objectForKey: @"Content-Length"]; - if (contentLengthString == nil) - @throw [OFInvalidArgumentException exception]; - - contentLength = contentLengthString.decimalValue; - if (contentLength < 0) - @throw [OFOutOfRangeException exception]; - - _toWrite = contentLength; - - if ([headers objectForKey: @"Transfer-Encoding"] != nil) + if (contentLengthString != nil) { + if (_chunked) + @throw [OFInvalidArgumentException + exception]; + + contentLength = contentLengthString.decimalValue; + if (contentLength < 0) + @throw [OFOutOfRangeException exception]; + + _toWrite = contentLength; + } else if (!_chunked) @throw [OFInvalidArgumentException exception]; } @catch (id e) { [self release]; @throw e; } @@ -748,30 +760,42 @@ size_t ret; if (_socket == nil) @throw [OFNotOpenException exceptionWithObject: self]; + /* + * We must not send a chunk of size 0, as that would end the body. We + * always ignore writing 0 bytes to still allow writing 0 bytes after + * the end of stream. + */ + if (length == 0) + return 0; + if (_atEndOfStream) @throw [OFWriteFailedException exceptionWithObject: self requestedLength: requestedLength bytesWritten: 0 errNo: 0]; - if (length > _toWrite) + if (_chunked) + [_socket writeFormat: @"%zX\r\n", length]; + else if (length > _toWrite) length = (size_t)_toWrite; ret = [_socket writeBuffer: buffer length: length]; if (ret > length) @throw [OFOutOfRangeException exception]; - _toWrite -= ret; + if (!_chunked) { + _toWrite -= ret; - if (_toWrite == 0) - _atEndOfStream = true; + if (_toWrite == 0) + _atEndOfStream = true; + } if (requestedLength > length) @throw [OFWriteFailedException exceptionWithObject: self requestedLength: requestedLength @@ -789,11 +813,13 @@ - (void)close { if (_socket == nil) @throw [OFNotOpenException exceptionWithObject: self]; - if (_toWrite > 0) + if (_chunked) + [_socket writeString: @"0\r\n"]; + else if (_toWrite > 0) @throw [OFTruncatedDataException exception]; _socket.delegate = _handler; [_socket asyncReadLine]; @@ -838,10 +864,13 @@ _chunked = [[headers objectForKey: @"Transfer-Encoding"] isEqual: @"chunked"]; contentLength = [headers objectForKey: @"Content-Length"]; if (contentLength != nil) { + if (_chunked) + @throw [OFInvalidServerReplyException exception]; + _hasContentLength = true; @try { intmax_t toRead = contentLength.decimalValue;