* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * 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
/* 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
CURLcode result;
struct connectdata *conn;
struct Curl_easy *data;
+ unsigned int i;
DEBUGASSERT(*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
#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"
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;
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)
if(0 == len)
len = strlen(ptr);
+ DEBUGASSERT(type <= 3);
+
/* FTP data may need conversion. */
if((type & CLIENTWRITE_BODY) &&
(conn->handler->protocol & PROTO_FAMILY_FTP) &&
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 */
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
\
test1525 test1526 test1527 test1528 test1529 test1530 test1531 test1532 \
test1533 test1534 test1535 test1536 \
+test1540 \
\
test1600 test1601 test1602 test1603 test1604 test1605 \
\
--- /dev/null
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+CURLPAUSE_RECV
+chunked encoding
+Trailer:
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK swsclose\r
+Transfer-Encoding: chunked\r
+Trailer: MyCoolTrailerHeader\r
+\r
+4\r
+data\r
+5\r
+d474
+\r
+0\r
+MyCoolTrailerHeader: amazingtrailer\r
+\r
+</data>
+<datacheck>
+HTTP/1.1 200 OK swsclose\r
+Transfer-Encoding: chunked\r
+Trailer: MyCoolTrailerHeader\r
+\r
+Got 4 bytes but pausing!
+datad474
+MyCoolTrailerHeader: amazingtrailer\r
+</datacheck>
+
+</reply>
+# Client-side
+<client>
+<server>
+http
+</server>
+<tool>
+lib1540
+</tool>
+ <name>
+chunked with trailers and pausing the receive
+ </name>
+ <command>
+http://%HOSTIP:%HTTPPORT/1540
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+GET /1540 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+</protocol>
+</verify>
+</testcase>
lib1520 \
lib1525 lib1526 lib1527 lib1528 lib1529 lib1530 lib1531 lib1532 lib1533 \
lib1534 lib1535 lib1536 \
+ lib1540 \
lib1900 \
lib2033
lib1536_LDADD = $(TESTUTIL_LIBS)
lib1536_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1536
+lib1540_SOURCES = lib1540.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
+lib1540_LDADD = $(TESTUTIL_LIBS)
+lib1540_CPPFLAGS = $(AM_CPPFLAGS)
+
lib1900_SOURCES = lib1900.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib1900_LDADD = $(TESTUTIL_LIBS)
lib1900_CPPFLAGS = $(AM_CPPFLAGS)
--- /dev/null
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * 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
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+#include "test.h"
+
+#include "testutil.h"
+#include "warnless.h"
+#include "memdebug.h"
+
+struct transfer_status {
+ CURL *easy;
+ int halted;
+ int counter; /* count write callback invokes */
+ int please; /* number of times xferinfo is called while halted */
+};
+
+static int please_continue(void *userp,
+ curl_off_t dltotal,
+ curl_off_t dlnow,
+ curl_off_t ultotal,
+ curl_off_t ulnow)
+{
+ struct transfer_status *st = (struct transfer_status *)userp;
+ (void)dltotal;
+ (void)dlnow;
+ (void)ultotal;
+ (void)ulnow;
+ if(st->halted) {
+ st->please++;
+ if(st->please == 2) {
+ /* waited enough, unpause! */
+ curl_easy_pause(st->easy, CURLPAUSE_CONT);
+ }
+ }
+ fprintf(stderr, "xferinfo: paused %d\n", st->halted);
+ return 0; /* go on */
+}
+
+static size_t header_callback(void *ptr, size_t size, size_t nmemb,
+ void *userp)
+{
+ size_t len = size * nmemb;
+ (void)userp;
+ (void)fwrite(ptr, size, nmemb, stdout);
+ return len;
+}
+
+static size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userp)
+{
+ struct transfer_status *st = (struct transfer_status *)userp;
+ size_t len = size * nmemb;
+ st->counter++;
+ if(st->counter > 1) {
+ /* the first call puts us on pause, so subsequent calls are after
+ unpause */
+ fwrite(ptr, size, nmemb, stdout);
+ return len;
+ }
+ printf("Got %d bytes but pausing!\n", (int)len);
+ st->halted = 1;
+ return CURL_WRITEFUNC_PAUSE;
+}
+
+#define TEST_HANG_TIMEOUT 60 * 1000
+
+int test(char *URL)
+{
+ CURL *curls = NULL;
+ int i = 0;
+ int res = 0;
+ struct transfer_status st;
+
+ start_test_timing();
+
+ memset(&st, 0, sizeof(st));
+
+ global_init(CURL_GLOBAL_ALL);
+
+ easy_init(curls);
+ st.easy = curls; /* to allow callbacks access */
+
+ easy_setopt(curls, CURLOPT_URL, URL);
+ easy_setopt(curls, CURLOPT_WRITEFUNCTION, write_callback);
+ easy_setopt(curls, CURLOPT_WRITEDATA, &st);
+ easy_setopt(curls, CURLOPT_HEADERFUNCTION, header_callback);
+ easy_setopt(curls, CURLOPT_HEADERDATA, &st);
+
+ easy_setopt(curls, CURLOPT_XFERINFOFUNCTION, please_continue);
+ easy_setopt(curls, CURLOPT_XFERINFODATA, &st);
+ easy_setopt(curls, CURLOPT_NOPROGRESS, 0L);
+
+ res = curl_easy_perform(curls);
+
+test_cleanup:
+
+ curl_easy_cleanup(curls);
+ curl_global_cleanup();
+
+ if(res)
+ i = res;
+
+ return i; /* return the final return code */
+}