Commit 45220334 authored by Daniel Stenberg's avatar Daniel Stenberg
Browse files

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
parent 7975d10c
Loading
Loading
Loading
Loading
+27 −14
Original line number Diff line number Diff line
@@ -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
+6 −3
Original line number Diff line number Diff line
@@ -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
+53 −34
Original line number Diff line number Diff line
@@ -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)
  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);

  memcpy(dupl, 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;

    /* store this information in the state struct for later use */
  data->state.tempwrite = dupl;
  data->state.tempwritesize = len;
  data->state.tempwritetype = type;
    s->tempwrite[i].buf = dupl;
    s->tempwrite[i].len = len;
    s->tempwrite[i].type = 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) &&
+15 −5
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -163,6 +163,7 @@ test1520 \
\
test1525 test1526 test1527 test1528 test1529 test1530 test1531 test1532 \
test1533 test1534 test1535 test1536 \
test1540 \
\
test1600 test1601 test1602 test1603 test1604 test1605 \
\
Loading