Index: src/OFHTTPClient.m ================================================================== --- src/OFHTTPClient.m +++ src/OFHTTPClient.m @@ -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; Index: utils/ofhttp/OFHTTP.m ================================================================== --- utils/ofhttp/OFHTTP.m +++ utils/ofhttp/OFHTTP.m @@ -90,10 +90,12 @@ [stream writeString: @"\n"]; [stream writeLine: OF_LOCALIZED(@"full_usage", @"Options:\n " @"-b --body " @" Specify the file to send as body\n " + @" " + @" (- for standard input)\n " @"-c --continue " @" Continue download of existing file\n " @"-f --force " @" Force / overwrite existing file\n " @"-h --help " @@ -318,20 +320,36 @@ forKey: name]; } - (void)setBody: (OFString *)path { - uintmax_t bodySize; + OFString *contentLength = nil; [_body release]; - _body = [[OFFile alloc] initWithPath: path - mode: @"r"]; + _body = nil; - bodySize = [[OFFileManager defaultManager] attributesOfItemAtPath: path] - .fileSize; - [_clientHeaders setObject: [OFString stringWithFormat: @"%ju", bodySize] - forKey: @"Content-Length"]; + if ([path isEqual: @"-"]) + _body = [of_stdin copy]; + else { + _body = [[OFFile alloc] initWithPath: path + mode: @"r"]; + + @try { + uintmax_t fileSize = [[OFFileManager defaultManager] + attributesOfItemAtPath: path].fileSize; + + contentLength = + [OFString stringWithFormat: @"%ju", fileSize]; + [_clientHeaders setObject: contentLength + forKey: @"Content-Length"]; + } @catch (OFRetrieveItemAttributesFailedException *e) { + } + } + + if (contentLength == nil) + [_clientHeaders setObject: @"chunked" + forKey: @"Transfer-Encoding"]; } - (void)setMethod: (OFString *)method { void *pool = objc_autoreleasePoolPush(); Index: utils/ofhttp/lang/de.json ================================================================== --- utils/ofhttp/lang/de.json +++ utils/ofhttp/lang/de.json @@ -1,10 +1,11 @@ { "usage": "Benutzung: %[prog] -[cehHmoOPqv] url1 [url2 ...]", "full_usage": [ "Optionen:\n", " -b --body Angegebene Datei als Body übergeben\n", + " (- für Standard-Eingabe)\n", " -c --continue Download von existierender Datei ", "fortsetzen\n", " -f --force Existierende Datei überschreiben\n", " -h --help Diese Hilfe anzeigen\n", " -H --header Einen Header (z.B. X-Foo:Bar) hinzufügen\n",