From 7fdd12719a50fb0d080cd4a8193aaa4fe32f6aa7 Mon Sep 17 00:00:00 2001 From: joe Date: Fri, 26 Jul 2013 17:51:23 +0000 Subject: Support chunked bodies; patch by Julien Reichel. * src/ne_request.c (send_request_body): Support chunked bodies. (set_body_length): Negative length implies chunked, not C-L. (send_request): Adjust for chunked. * src/ne_request.h: Update ne_set_request_body_provider API. git-svn-id: http://svn.webdav.org/repos/projects/neon/trunk@1905 61a7d7f5-40b7-0310-9c16-bb0ea8cb1845 --- src/ne_request.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++-------- src/ne_request.h | 6 +++-- 2 files changed, 72 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/ne_request.c b/src/ne_request.c index 9a47843..65730b7 100644 --- a/src/ne_request.c +++ b/src/ne_request.c @@ -357,6 +357,14 @@ static ssize_t body_fd_send(void *userdata, char *buffer, size_t count) ((((code) == NE_SOCK_CLOSED || (code) == NE_SOCK_RESET || \ (code) == NE_SOCK_TRUNC) && retry) ? NE_RETRY : (acode)) +/* For sending chunks, an 8-byte prefix is reserved at the beginning + * of the buffer. This is large enough for a trailing \r\n for the + * previous chunk, the chunk size, and the \r\n following the + * chunk-size. */ +#define CHUNK_OFFSET (8) +#define CHUNK_TERM "\r\n0\r\n\r\n" +#define CHUNK_NULL_TERM "0\r\n\r\n" + /* Sends the request body; returns 0 on success or an NE_* error code. * If retry is non-zero; will return NE_RETRY on persistent connection * timeout. On error, the session error string is set and the @@ -364,13 +372,28 @@ static ssize_t body_fd_send(void *userdata, char *buffer, size_t count) static int send_request_body(ne_request *req, int retry) { ne_session *const sess = req->session; - char buffer[NE_BUFSIZ]; + char buffer[NE_BUFSIZ], *start; ssize_t bytes; + size_t buflen; + int chunked = req->body_length < 0, chunknum = 0; + int ret; NE_DEBUG(NE_DBG_HTTP, "Sending request body:\n"); + /* Set up status union and (start, buflen) as the buffer to be + * passed the supplied callback. */ + if (chunked) { + start = buffer + CHUNK_OFFSET; + buflen = sizeof(buffer) - CHUNK_OFFSET; + req->session->status.sr.total = -1; + } + else { + start = buffer; + buflen = sizeof buffer; + req->session->status.sr.total = req->body_length; + } + req->session->status.sr.progress = 0; - req->session->status.sr.total = req->body_length; notify_status(sess, ne_status_sending); /* tell the source to start again from the beginning. */ @@ -379,8 +402,24 @@ static int send_request_body(ne_request *req, int retry) return NE_ERROR; } - while ((bytes = req->body_cb(req->body_ud, buffer, sizeof buffer)) > 0) { - int ret = ne_sock_fullwrite(sess->socket, buffer, bytes); + while ((bytes = req->body_cb(req->body_ud, start, buflen)) > 0) { + req->session->status.sr.progress += bytes; + + if (chunked) { + /* Overwrite the buffer prefix with the appropriate chunk + * size; since ne_snprintf always NUL-terminates, the \n + * is omitted and placed over the NUL afterwards. */ + if (chunknum++ == 0) + ne_snprintf(buffer, CHUNK_OFFSET, + "%06x\r", (unsigned)bytes); + else + ne_snprintf(buffer, CHUNK_OFFSET, + "\r\n%04x\r", (unsigned)bytes); + buffer[CHUNK_OFFSET - 1] = '\n'; + bytes += CHUNK_OFFSET; + } + ret = ne_sock_fullwrite(sess->socket, buffer, bytes); + if (ret < 0) { int aret = aborted(req, _("Could not send request body"), ret); return RETRY_RET(retry, ret, aret); @@ -395,14 +434,28 @@ static int send_request_body(ne_request *req, int retry) notify_status(sess, ne_status_sending); } - if (bytes == 0) { - return NE_OK; - } else { + if (bytes) { NE_DEBUG(NE_DBG_HTTP, "Request body provider failed with " "%" NE_FMT_SSIZE_T "\n", bytes); ne_close_connection(sess); return NE_ERROR; } + + if (chunked) { + if (chunknum == 0) + ret = ne_sock_fullwrite(sess->socket, CHUNK_NULL_TERM, + sizeof(CHUNK_NULL_TERM) - 1); + else + ret = ne_sock_fullwrite(sess->socket, CHUNK_TERM, + sizeof(CHUNK_TERM) - 1); + if (ret < 0) { + int aret = aborted(req, _("Could not send chunked " + "request terminator"), ret); + return RETRY_RET(retry, ret, aret); + } + } + + return NE_OK; } /* Lob the User-Agent, connection and host headers in to the request @@ -495,7 +548,12 @@ ne_request *ne_request_create(ne_session *sess, static void set_body_length(ne_request *req, ne_off_t length) { req->body_length = length; - ne_print_request_header(req, "Content-Length", "%" FMT_NE_OFF_T, length); + + if (length >= 0) + ne_print_request_header(req, "Content-Length", "%" FMT_NE_OFF_T, length); + else /* length < 0 => chunked body */ + ne_add_request_header(req, "Transfer-Encoding", "chunked"); + } void ne_set_request_body_buffer(ne_request *req, const char *buffer, @@ -965,7 +1023,7 @@ static int send_request(ne_request *req, const ne_buffer *request) return RETRY_RET(retry, sret, aret); } - if (!req->flags[NE_REQFLAG_EXPECT100] && req->body_length > 0) { + if (!req->flags[NE_REQFLAG_EXPECT100] && req->body_length) { /* Send request body, if not using 100-continue. */ ret = send_request_body(req, retry); if (ret) { @@ -985,7 +1043,7 @@ static int send_request(ne_request *req, const ne_buffer *request) if ((ret = discard_headers(req)) != NE_OK) break; if (req->flags[NE_REQFLAG_EXPECT100] && (status->code == 100) - && req->body_length > 0 && !sentbody) { + && req->body_length && !sentbody) { /* Send the body after receiving the first 100 Continue */ if ((ret = send_request_body(req, 0)) != NE_OK) break; sentbody = 1; diff --git a/src/ne_request.h b/src/ne_request.h index bfe5cdc..be6f3a1 100644 --- a/src/ne_request.h +++ b/src/ne_request.h @@ -78,9 +78,11 @@ typedef ssize_t (*ne_provide_body)(void *userdata, /* Install a callback which is invoked as needed to provide the * request body, a block at a time. The total size of the request * body is 'length'; the callback must ensure that it returns no more - * than 'length' bytes in total. */ + * than 'length' bytes in total. If 'length' is set to -1, then the + * total size of the request is unknown by the caller and chunked + * tranfer will be used. */ void ne_set_request_body_provider(ne_request *req, ne_off_t length, - ne_provide_body provider, void *userdata); + ne_provide_body provider, void *userdata); /* Handling response bodies; two callbacks must be provided: * -- cgit v1.2.1