#include "curl_setup.h"
#ifndef CURL_DISABLE_HTTP
#include "urldata.h"
#include "curl_trc.h"
#include "sendf.h"
#include "curlx/dynbuf.h"
#include "content_encoding.h"
#include "http.h"
#include "multiif.h"
#include "curlx/strparse.h"
#include "curlx/warnless.h"
#include "curl_memory.h"
#include "memdebug.h"
void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch,
bool ignore_body)
{
(void)data;
ch->hexindex = 0;
ch->state = CHUNK_HEX;
ch->last_code = CHUNKE_OK;
curlx_dyn_init(&ch->trailer, DYN_H1_TRAILER);
ch->ignore_body = ignore_body;
}
void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch,
bool ignore_body)
{
(void)data;
ch->hexindex = 0;
ch->state = CHUNK_HEX;
ch->last_code = CHUNKE_OK;
curlx_dyn_reset(&ch->trailer);
ch->ignore_body = ignore_body;
}
void Curl_httpchunk_free(struct Curl_easy *data, struct Curl_chunker *ch)
{
(void)data;
curlx_dyn_free(&ch->trailer);
}
bool Curl_httpchunk_is_done(struct Curl_easy *data, struct Curl_chunker *ch)
{
(void)data;
return ch->state == CHUNK_DONE;
}
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;
if(ch->state == CHUNK_DONE)
return CURLE_OK;
if(ch->state == CHUNK_FAILED)
return CURLE_RECV_ERROR;
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, 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;
return CURLE_RECV_ERROR;
}
ch->hexbuffer[ch->hexindex++] = *buf;
buf++;
blen--;
(*pconsumed)++;
}
else {
const char *p;
if(ch->hexindex == 0) {
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;
}
ch->hexbuffer[ch->hexindex] = 0;
p = &ch->hexbuffer[0];
if(curlx_str_hex(&p, &ch->datasize, CURL_OFF_T_MAX)) {
failf(data, "invalid chunk size: '%s'", ch->hexbuffer);
ch->state = CHUNK_FAILED;
ch->last_code = CHUNKE_ILLEGAL_HEX;
return CURLE_RECV_ERROR;
}
ch->state = CHUNK_LF;
}
break;
case CHUNK_LF:
if(*buf == 0x0a) {
if(ch->datasize == 0) {
ch->state = CHUNK_TRAILER;
}
else {
ch->state = CHUNK_DATA;
CURL_TRC_WRITE(data, "http_chunked, chunk start of %"
FMT_OFF_T " bytes", ch->datasize);
}
}
buf++;
blen--;
(*pconsumed)++;
break;
case CHUNK_DATA:
piece = blen;
if(ch->datasize < (curl_off_t)blen)
piece = curlx_sotouz(ch->datasize);
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, buf, piece);
if(result) {
ch->state = CHUNK_FAILED;
ch->last_code = CHUNKE_PASSTHRU_ERROR;
return result;
}
}
*pconsumed += piece;
ch->datasize -= piece;
buf += piece;
blen -= piece;
CURL_TRC_WRITE(data, "http_chunked, write %zu body bytes, %"
FMT_OFF_T " bytes in chunk remain",
piece, ch->datasize);
if(ch->datasize == 0)
ch->state = CHUNK_POSTLF;
break;
case CHUNK_POSTLF:
if(*buf == 0x0a) {
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 = curlx_dyn_ptr(&ch->trailer);
if(tr) {
result = curlx_dyn_addn(&ch->trailer, STRCONST("\x0d\x0a"));
if(result) {
ch->state = CHUNK_FAILED;
ch->last_code = CHUNKE_OUT_OF_MEMORY;
return result;
}
tr = curlx_dyn_ptr(&ch->trailer);
if(!data->set.http_te_skip) {
size_t trlen = curlx_dyn_len(&ch->trailer);
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;
}
}
curlx_dyn_reset(&ch->trailer);
ch->state = CHUNK_TRAILER_CR;
if(*buf == 0x0a)
break;
}
else {
ch->state = CHUNK_TRAILER_POSTCR;
break;
}
}
else {
result = curlx_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:
if((*buf != 0x0d) && (*buf != 0x0a)) {
ch->state = CHUNK_TRAILER;
break;
}
if(*buf == 0x0d) {
buf++;
blen--;
(*pconsumed)++;
}
ch->state = CHUNK_STOP;
break;
case CHUNK_STOP:
if(*buf == 0x0a) {
blen--;
(*pconsumed)++;
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;
}
static const char *Curl_chunked_strerror(CHUNKcode code)
{
switch(code) {
default:
return "OK";
case CHUNKE_TOO_LONG_HEX:
return "Too long hexadecimal number";
case CHUNKE_ILLEGAL_HEX:
return "Illegal or missing hexadecimal sequence";
case CHUNKE_BAD_CHUNK:
return "Malformed encoding found";
case CHUNKE_PASSTHRU_ERROR:
return "Error writing data to client";
case CHUNKE_BAD_ENCODING:
return "Bad content-encoding found";
case CHUNKE_OUT_OF_MEMORY:
return "Out of memory";
}
}
CURLcode Curl_httpchunk_read(struct Curl_easy *data,
struct Curl_chunker *ch,
char *buf, size_t blen,
size_t *pconsumed)
{
return httpchunk_readwrite(data, ch, NULL, buf, blen, pconsumed);
}
struct chunked_writer {
struct Curl_cwriter super;
struct Curl_chunker ch;
};
static CURLcode cw_chunked_init(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
struct chunked_writer *ctx = writer->ctx;
data->req.chunk = TRUE;
Curl_httpchunk_init(data, &ctx->ch, FALSE);
return CURLE_OK;
}
static void cw_chunked_close(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
struct chunked_writer *ctx = writer->ctx;
Curl_httpchunk_free(data, &ctx->ch);
}
static CURLcode cw_chunked_write(struct Curl_easy *data,
struct Curl_cwriter *writer, int type,
const char *buf, size_t blen)
{
struct chunked_writer *ctx = writer->ctx;
CURLcode result;
size_t consumed;
if(!(type & CLIENTWRITE_BODY))
return Curl_cwriter_write(data, writer->next, type, buf, blen);
consumed = 0;
result = httpchunk_readwrite(data, &ctx->ch, writer->next, buf, blen,
&consumed);
if(result) {
if(CHUNKE_PASSTHRU_ERROR == ctx->ch.last_code) {
failf(data, "Failed reading the chunked-encoded stream");
}
else {
failf(data, "%s in chunked-encoding",
Curl_chunked_strerror(ctx->ch.last_code));
}
return result;
}
blen -= consumed;
if(CHUNK_DONE == ctx->ch.state) {
data->req.download_done = TRUE;
if(blen) {
infof(data, "Leftovers after chunking: %zu bytes", blen);
}
}
else if((type & CLIENTWRITE_EOS) && !data->req.no_body) {
failf(data, "transfer closed with outstanding read data remaining");
return CURLE_PARTIAL_FILE;
}
return CURLE_OK;
}
const struct Curl_cwtype Curl_httpchunk_unencoder = {
"chunked",
NULL,
cw_chunked_init,
cw_chunked_write,
cw_chunked_close,
sizeof(struct chunked_writer)
};
#define CURL_CHUNKED_MINLEN (1024)
#define CURL_CHUNKED_MAXLEN (64 * 1024)
struct chunked_reader {
struct Curl_creader super;
struct bufq chunkbuf;
BIT(read_eos);
BIT(eos);
};
static CURLcode cr_chunked_init(struct Curl_easy *data,
struct Curl_creader *reader)
{
struct chunked_reader *ctx = reader->ctx;
(void)data;
Curl_bufq_init2(&ctx->chunkbuf, CURL_CHUNKED_MAXLEN, 2, BUFQ_OPT_SOFT_LIMIT);
return CURLE_OK;
}
static void cr_chunked_close(struct Curl_easy *data,
struct Curl_creader *reader)
{
struct chunked_reader *ctx = reader->ctx;
(void)data;
Curl_bufq_free(&ctx->chunkbuf);
}
static CURLcode add_last_chunk(struct Curl_easy *data,
struct Curl_creader *reader)
{
struct chunked_reader *ctx = reader->ctx;
struct curl_slist *trailers = NULL, *tr;
CURLcode result;
size_t n;
int rc;
if(!data->set.trailer_callback) {
CURL_TRC_READ(data, "http_chunk, added last, empty chunk");
return Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("0\r\n\r\n"), &n);
}
result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("0\r\n"), &n);
if(result)
goto out;
Curl_set_in_callback(data, TRUE);
rc = data->set.trailer_callback(&trailers, data->set.trailer_data);
Curl_set_in_callback(data, FALSE);
if(rc != CURL_TRAILERFUNC_OK) {
failf(data, "operation aborted by trailing headers callback");
result = CURLE_ABORTED_BY_CALLBACK;
goto out;
}
for(tr = trailers; tr; tr = tr->next) {
char *ptr = strchr(tr->data, ':');
if(!ptr || *(ptr + 1) != ' ') {
infof(data, "Malformatted trailing header, skipping trailer");
continue;
}
result = Curl_bufq_cwrite(&ctx->chunkbuf, tr->data,
strlen(tr->data), &n);
if(!result)
result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("\r\n"), &n);
if(result)
goto out;
}
result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("\r\n"), &n);
out:
curl_slist_free_all(trailers);
CURL_TRC_READ(data, "http_chunk, added last chunk with trailers "
"from client -> %d", result);
return result;
}
static CURLcode add_chunk(struct Curl_easy *data,
struct Curl_creader *reader,
char *buf, size_t blen)
{
struct chunked_reader *ctx = reader->ctx;
CURLcode result;
char tmp[CURL_CHUNKED_MINLEN];
size_t nread;
bool eos;
DEBUGASSERT(!ctx->read_eos);
blen = CURLMIN(blen, CURL_CHUNKED_MAXLEN);
if(blen < sizeof(tmp)) {
buf = tmp;
blen = sizeof(tmp);
}
else {
blen -= (8 + 2 + 2);
}
result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos);
if(result)
return result;
if(eos)
ctx->read_eos = TRUE;
if(nread) {
char hd[11] = "";
int hdlen;
size_t n;
hdlen = curl_msnprintf(hd, sizeof(hd), "%zx\r\n", nread);
if(hdlen <= 0)
return CURLE_READ_ERROR;
result = Curl_bufq_cwrite(&ctx->chunkbuf, hd, hdlen, &n);
if(!result)
result = Curl_bufq_cwrite(&ctx->chunkbuf, buf, nread, &n);
if(!result)
result = Curl_bufq_cwrite(&ctx->chunkbuf, "\r\n", 2, &n);
CURL_TRC_READ(data, "http_chunk, made chunk of %zu bytes -> %d",
nread, result);
if(result)
return result;
}
if(ctx->read_eos)
return add_last_chunk(data, reader);
return CURLE_OK;
}
static CURLcode cr_chunked_read(struct Curl_easy *data,
struct Curl_creader *reader,
char *buf, size_t blen,
size_t *pnread, bool *peos)
{
struct chunked_reader *ctx = reader->ctx;
CURLcode result = CURLE_READ_ERROR;
*pnread = 0;
*peos = ctx->eos;
if(!ctx->eos) {
if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->chunkbuf)) {
result = add_chunk(data, reader, buf, blen);
if(result)
return result;
}
if(!Curl_bufq_is_empty(&ctx->chunkbuf)) {
result = Curl_bufq_cread(&ctx->chunkbuf, buf, blen, pnread);
if(!result && ctx->read_eos && Curl_bufq_is_empty(&ctx->chunkbuf)) {
ctx->eos = TRUE;
*peos = TRUE;
}
return result;
}
}
DEBUGASSERT(ctx->eos || !ctx->read_eos);
return CURLE_OK;
}
static curl_off_t cr_chunked_total_length(struct Curl_easy *data,
struct Curl_creader *reader)
{
(void)data;
(void)reader;
return -1;
}
const struct Curl_crtype Curl_httpchunk_encoder = {
"chunked",
cr_chunked_init,
cr_chunked_read,
cr_chunked_close,
Curl_creader_def_needs_rewind,
cr_chunked_total_length,
Curl_creader_def_resume_from,
Curl_creader_def_cntrl,
Curl_creader_def_is_paused,
Curl_creader_def_done,
sizeof(struct chunked_reader)
};
CURLcode Curl_httpchunk_add_reader(struct Curl_easy *data)
{
struct Curl_creader *reader = NULL;
CURLcode result;
result = Curl_creader_create(&reader, data, &Curl_httpchunk_encoder,
CURL_CR_TRANSFER_ENCODE);
if(!result)
result = Curl_creader_add(data, reader);
if(result && reader)
Curl_creader_free(data, reader);
return result;
}
#endif