diff options
author | Daniel Stenberg <daniel@haxx.se> | 2017-03-27 12:14:57 +0200 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2017-03-28 13:27:49 +0200 |
commit | 452203341d6e97b8f52f9a886046d9fdb96346a9 (patch) | |
tree | c5aa7fa5e29da5a1799123703be46f28d0491fc1 /lib | |
parent | 7975d10cf8a3fe9b35867509b9ef10d06614eb3a (diff) |
pause: handle mixed types of data when paused
When receiving chunked encoded data with trailers, and the write
callback returns PAUSE, there might be both body and header to store to
resend on unpause. Previously libcurl returned error for that case.
Added test case 1540 to verify.
Reported-by: Stephen Toub
Fixes #1354
Closes #1357
Diffstat (limited to 'lib')
-rw-r--r-- | lib/easy.c | 41 | ||||
-rw-r--r-- | lib/multi.c | 9 | ||||
-rw-r--r-- | lib/sendf.c | 87 | ||||
-rw-r--r-- | lib/urldata.h | 20 |
4 files changed, 101 insertions, 56 deletions
diff --git a/lib/easy.c b/lib/easy.c index cf65af911..b3159d1a2 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -1011,19 +1011,32 @@ CURLcode curl_easy_pause(struct Curl_easy *data, int action) /* put it back in the keepon */ k->keepon = newstate; - if(!(newstate & KEEP_RECV_PAUSE) && data->state.tempwrite) { - /* we have a buffer for sending that we now seem to be able to deliver - since the receive pausing is lifted! */ - - /* get the pointer in local copy since the function may return PAUSE - again and then we'll get a new copy allocted and stored in - the tempwrite variables */ - char *tempwrite = data->state.tempwrite; - - data->state.tempwrite = NULL; - result = Curl_client_chop_write(data->easy_conn, data->state.tempwritetype, - tempwrite, data->state.tempwritesize); - free(tempwrite); + if(!(newstate & KEEP_RECV_PAUSE) && data->state.tempcount) { + /* there are buffers for sending that can be delivered as the receive + pausing is lifted! */ + unsigned int i; + unsigned int count = data->state.tempcount; + struct tempbuf writebuf[3]; /* there can only be three */ + + /* copy the structs to allow for immediate re-pausing */ + for(i=0; i < data->state.tempcount; i++) { + writebuf[i] = data->state.tempwrite[i]; + data->state.tempwrite[i].buf = NULL; + } + data->state.tempcount = 0; + + for(i=0; i < count; i++) { + /* even if one function returns error, this loops through and frees all + buffers */ + if(!result) + result = Curl_client_chop_write(data->easy_conn, + writebuf[i].type, + writebuf[i].buf, + writebuf[i].len); + free(writebuf[i].buf); + } + if(result) + return result; } /* if there's no error and we're not pausing both directions, we want diff --git a/lib/multi.c b/lib/multi.c index 88ce00566..47e24005a 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -532,6 +532,7 @@ static CURLcode multi_done(struct connectdata **connp, CURLcode result; struct connectdata *conn; struct Curl_easy *data; + unsigned int i; DEBUGASSERT(*connp); @@ -598,9 +599,11 @@ static CURLcode multi_done(struct connectdata **connp, } /* if the transfer was completed in a paused state there can be buffered - data left to write and then kill */ - free(data->state.tempwrite); - data->state.tempwrite = NULL; + data left to free */ + for(i=0; i < data->state.tempcount; i++) { + free(data->state.tempwrite[i].buf); + } + data->state.tempcount = 0; /* if data->set.reuse_forbid is TRUE, it means the libcurl client has forced us to close this connection. This is ignored for requests taking diff --git a/lib/sendf.c b/lib/sendf.c index e03241f0d..84b6b4b2f 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -33,6 +33,7 @@ #include "non-ascii.h" #include "strerror.h" #include "select.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -474,21 +475,58 @@ static CURLcode pausewrite(struct Curl_easy *data, we want to send we need to dup it to save a copy for when the sending is again enabled */ struct SingleRequest *k = &data->req; - char *dupl = malloc(len); - if(!dupl) - return CURLE_OUT_OF_MEMORY; + struct UrlState *s = &data->state; + char *dupl; + unsigned int i; + bool newtype = TRUE; + + if(s->tempcount) { + for(i=0; i< s->tempcount; i++) { + if(s->tempwrite[i].type == type) { + /* data for this type exists */ + newtype = FALSE; + break; + } + } + DEBUGASSERT(i < 3); + } + else + i = 0; + + if(!newtype) { + /* append new data to old data */ + + /* figure out the new size of the data to save */ + size_t newlen = len + s->tempwrite[i].len; + /* allocate the new memory area */ + char *newptr = realloc(s->tempwrite[i].buf, newlen); + if(!newptr) + return CURLE_OUT_OF_MEMORY; + /* copy the new data to the end of the new area */ + memcpy(newptr + s->tempwrite[i].len, ptr, len); + + /* update the pointer and the size */ + s->tempwrite[i].buf = newptr; + s->tempwrite[i].len = newlen; + } + else { + dupl = Curl_memdup(ptr, len); + if(!dupl) + return CURLE_OUT_OF_MEMORY; - memcpy(dupl, ptr, len); + /* store this information in the state struct for later use */ + s->tempwrite[i].buf = dupl; + s->tempwrite[i].len = len; + s->tempwrite[i].type = type; - /* store this information in the state struct for later use */ - data->state.tempwrite = dupl; - data->state.tempwritesize = len; - data->state.tempwritetype = type; + if(newtype) + s->tempcount++; + } /* mark the connection as RECV paused */ k->keepon |= KEEP_RECV_PAUSE; - DEBUGF(infof(data, "Pausing with %zu bytes in buffer for type %02x\n", + DEBUGF(infof(data, "Paused %zu bytes in buffer for type %02x\n", len, type)); return CURLE_OK; @@ -511,31 +549,10 @@ CURLcode Curl_client_chop_write(struct connectdata *conn, if(!len) return CURLE_OK; - /* If reading is actually paused, we're forced to append this chunk of data - to the already held data, but only if it is the same type as otherwise it - can't work and it'll return error instead. */ - if(data->req.keepon & KEEP_RECV_PAUSE) { - size_t newlen; - char *newptr; - if(type != data->state.tempwritetype) - /* major internal confusion */ - return CURLE_RECV_ERROR; - - DEBUGASSERT(data->state.tempwrite); - - /* figure out the new size of the data to save */ - newlen = len + data->state.tempwritesize; - /* allocate the new memory area */ - newptr = realloc(data->state.tempwrite, newlen); - if(!newptr) - return CURLE_OUT_OF_MEMORY; - /* copy the new data to the end of the new area */ - memcpy(newptr + data->state.tempwritesize, ptr, len); - /* update the pointer and the size */ - data->state.tempwrite = newptr; - data->state.tempwritesize = newlen; - return CURLE_OK; - } + /* If reading is paused, append this data to the already held data for this + type. */ + if(data->req.keepon & KEEP_RECV_PAUSE) + return pausewrite(data, type, ptr, len); /* Determine the callback(s) to use. */ if(type & CLIENTWRITE_BODY) @@ -615,6 +632,8 @@ CURLcode Curl_client_write(struct connectdata *conn, if(0 == len) len = strlen(ptr); + DEBUGASSERT(type <= 3); + /* FTP data may need conversion. */ if((type & CLIENTWRITE_BODY) && (conn->handler->protocol & PROTO_FAMILY_FTP) && diff --git a/lib/urldata.h b/lib/urldata.h index 3ec7e0f16..9b3849117 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1294,6 +1294,19 @@ struct Curl_http2_dep { struct Curl_easy *data; }; +/* + * This struct is for holding data that was attemped to get sent to the user's + * callback but is held due to pausing. One instance per type (BOTH, HEADER, + * BODY). + */ +struct tempbuf { + char *buf; /* allocated buffer to keep data in when a write callback + returns to make the connection paused */ + size_t len; /* size of the 'tempwrite' allocated buffer */ + int type; /* type of the 'tempwrite' buffer as a bitmask that is used with + Curl_client_write() */ +}; + struct UrlState { /* Points to the connection cache */ @@ -1327,11 +1340,8 @@ struct UrlState { int first_remote_port; /* remote port of the first (not followed) request */ struct curl_ssl_session *session; /* array of 'max_ssl_sessions' size */ long sessionage; /* number of the most recent session */ - char *tempwrite; /* allocated buffer to keep data in when a write - callback returns to make the connection paused */ - size_t tempwritesize; /* size of the 'tempwrite' allocated buffer */ - int tempwritetype; /* type of the 'tempwrite' buffer as a bitmask that is - used with Curl_client_write() */ + unsigned int tempcount; /* number of entries in use in tempwrite, 0 - 3 */ + struct tempbuf tempwrite[3]; /* BOTH, HEADER, BODY */ char *scratch; /* huge buffer[BUFSIZE*2] when doing upload CRLF replacing */ bool errorbuf; /* Set to TRUE if the error buffer is already filled in. This must be set to FALSE every time _easy_perform() is |