static CURLcode httpchunk_readwrite()

in libs/curl/lib/http_chunks.c [115:369]


static CURLcode httpchunk_readwrite(struct Curl_easy *data,
                                    struct Curl_chunker *ch,
                                    struct Curl_cwriter *cw_next,
                                    const char *buf, size_t blen,
                                    size_t *pconsumed)
{
  CURLcode result = CURLE_OK;
  size_t piece;

  *pconsumed = 0; /* nothing's written yet */
  /* first check terminal states that will not progress anywhere */
  if(ch->state == CHUNK_DONE)
    return CURLE_OK;
  if(ch->state == CHUNK_FAILED)
    return CURLE_RECV_ERROR;

  /* the original data is written to the client, but we go on with the
     chunk read process, to properly calculate the content length */
  if(data->set.http_te_skip && !ch->ignore_body) {
    if(cw_next)
      result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY, buf, blen);
    else
      result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)buf, blen);
    if(result) {
      ch->state = CHUNK_FAILED;
      ch->last_code = CHUNKE_PASSTHRU_ERROR;
      return result;
    }
  }

  while(blen) {
    switch(ch->state) {
    case CHUNK_HEX:
      if(ISXDIGIT(*buf)) {
        if(ch->hexindex >= CHUNK_MAXNUM_LEN) {
          failf(data, "chunk hex-length longer than %d", CHUNK_MAXNUM_LEN);
          ch->state = CHUNK_FAILED;
          ch->last_code = CHUNKE_TOO_LONG_HEX; /* longer than we support */
          return CURLE_RECV_ERROR;
        }
        ch->hexbuffer[ch->hexindex++] = *buf;
        buf++;
        blen--;
        (*pconsumed)++;
      }
      else {
        if(0 == ch->hexindex) {
          /* This is illegal data, we received junk where we expected
             a hexadecimal digit. */
          failf(data, "chunk hex-length char not a hex digit: 0x%x", *buf);
          ch->state = CHUNK_FAILED;
          ch->last_code = CHUNKE_ILLEGAL_HEX;
          return CURLE_RECV_ERROR;
        }

        /* blen and buf are unmodified */
        ch->hexbuffer[ch->hexindex] = 0;
        if(curlx_strtoofft(ch->hexbuffer, NULL, 16, &ch->datasize)) {
          failf(data, "chunk hex-length not valid: '%s'", ch->hexbuffer);
          ch->state = CHUNK_FAILED;
          ch->last_code = CHUNKE_ILLEGAL_HEX;
          return CURLE_RECV_ERROR;
        }
        ch->state = CHUNK_LF; /* now wait for the CRLF */
      }
      break;

    case CHUNK_LF:
      /* waiting for the LF after a chunk size */
      if(*buf == 0x0a) {
        /* we are now expecting data to come, unless size was zero! */
        if(0 == ch->datasize) {
          ch->state = CHUNK_TRAILER; /* now check for trailers */
        }
        else {
          ch->state = CHUNK_DATA;
          CURL_TRC_WRITE(data, "http_chunked, chunk start of %"
                         CURL_FORMAT_CURL_OFF_T " bytes", ch->datasize);
        }
      }

      buf++;
      blen--;
      (*pconsumed)++;
      break;

    case CHUNK_DATA:
      /* We expect 'datasize' of data. We have 'blen' right now, it can be
         more or less than 'datasize'. Get the smallest piece.
      */
      piece = blen;
      if(ch->datasize < (curl_off_t)blen)
        piece = curlx_sotouz(ch->datasize);

      /* Write the data portion available */
      if(!data->set.http_te_skip && !ch->ignore_body) {
        if(cw_next)
          result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY,
                                      buf, piece);
        else
          result = Curl_client_write(data, CLIENTWRITE_BODY,
                                    (char *)buf, piece);
        if(result) {
          ch->state = CHUNK_FAILED;
          ch->last_code = CHUNKE_PASSTHRU_ERROR;
          return result;
        }
      }

      *pconsumed += piece;
      ch->datasize -= piece; /* decrease amount left to expect */
      buf += piece;    /* move read pointer forward */
      blen -= piece;   /* decrease space left in this round */
      CURL_TRC_WRITE(data, "http_chunked, write %zu body bytes, %"
                     CURL_FORMAT_CURL_OFF_T " bytes in chunk remain",
                     piece, ch->datasize);

      if(0 == ch->datasize)
        /* end of data this round, we now expect a trailing CRLF */
        ch->state = CHUNK_POSTLF;
      break;

    case CHUNK_POSTLF:
      if(*buf == 0x0a) {
        /* The last one before we go back to hex state and start all over. */
        Curl_httpchunk_reset(data, ch, ch->ignore_body);
      }
      else if(*buf != 0x0d) {
        ch->state = CHUNK_FAILED;
        ch->last_code = CHUNKE_BAD_CHUNK;
        return CURLE_RECV_ERROR;
      }
      buf++;
      blen--;
      (*pconsumed)++;
      break;

    case CHUNK_TRAILER:
      if((*buf == 0x0d) || (*buf == 0x0a)) {
        char *tr = Curl_dyn_ptr(&ch->trailer);
        /* this is the end of a trailer, but if the trailer was zero bytes
           there was no trailer and we move on */

        if(tr) {
          size_t trlen;
          result = Curl_dyn_addn(&ch->trailer, (char *)STRCONST("\x0d\x0a"));
          if(result) {
            ch->state = CHUNK_FAILED;
            ch->last_code = CHUNKE_OUT_OF_MEMORY;
            return result;
          }
          tr = Curl_dyn_ptr(&ch->trailer);
          trlen = Curl_dyn_len(&ch->trailer);
          if(!data->set.http_te_skip) {
            if(cw_next)
              result = Curl_cwriter_write(data, cw_next,
                                          CLIENTWRITE_HEADER|
                                          CLIENTWRITE_TRAILER,
                                          tr, trlen);
            else
              result = Curl_client_write(data,
                                         CLIENTWRITE_HEADER|
                                         CLIENTWRITE_TRAILER,
                                         tr, trlen);
            if(result) {
              ch->state = CHUNK_FAILED;
              ch->last_code = CHUNKE_PASSTHRU_ERROR;
              return result;
            }
          }
          Curl_dyn_reset(&ch->trailer);
          ch->state = CHUNK_TRAILER_CR;
          if(*buf == 0x0a)
            /* already on the LF */
            break;
        }
        else {
          /* no trailer, we are on the final CRLF pair */
          ch->state = CHUNK_TRAILER_POSTCR;
          break; /* do not advance the pointer */
        }
      }
      else {
        result = Curl_dyn_addn(&ch->trailer, buf, 1);
        if(result) {
          ch->state = CHUNK_FAILED;
          ch->last_code = CHUNKE_OUT_OF_MEMORY;
          return result;
        }
      }
      buf++;
      blen--;
      (*pconsumed)++;
      break;

    case CHUNK_TRAILER_CR:
      if(*buf == 0x0a) {
        ch->state = CHUNK_TRAILER_POSTCR;
        buf++;
        blen--;
        (*pconsumed)++;
      }
      else {
        ch->state = CHUNK_FAILED;
        ch->last_code = CHUNKE_BAD_CHUNK;
        return CURLE_RECV_ERROR;
      }
      break;

    case CHUNK_TRAILER_POSTCR:
      /* We enter this state when a CR should arrive so we expect to
         have to first pass a CR before we wait for LF */
      if((*buf != 0x0d) && (*buf != 0x0a)) {
        /* not a CR then it must be another header in the trailer */
        ch->state = CHUNK_TRAILER;
        break;
      }
      if(*buf == 0x0d) {
        /* skip if CR */
        buf++;
        blen--;
        (*pconsumed)++;
      }
      /* now wait for the final LF */
      ch->state = CHUNK_STOP;
      break;

    case CHUNK_STOP:
      if(*buf == 0x0a) {
        blen--;
        (*pconsumed)++;
        /* Record the length of any data left in the end of the buffer
           even if there is no more chunks to read */
        ch->datasize = blen;
        ch->state = CHUNK_DONE;
        CURL_TRC_WRITE(data, "http_chunk, response complete");
        return CURLE_OK;
      }
      else {
        ch->state = CHUNK_FAILED;
        ch->last_code = CHUNKE_BAD_CHUNK;
        CURL_TRC_WRITE(data, "http_chunk error, expected 0x0a, seeing 0x%ux",
                       (unsigned int)*buf);
        return CURLE_RECV_ERROR;
      }
    case CHUNK_DONE:
      return CURLE_OK;

    case CHUNK_FAILED:
      return CURLE_RECV_ERROR;
    }

  }
  return CURLE_OK;
}