Coverage Report

Created: 2026-10-22 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/http_chunks.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#ifndef CURL_DISABLE_HTTP
27
28
#include "urldata.h" /* it includes http_chunks.h */
29
#include "curl_trc.h"
30
#include "http.h"    /* for Curl_verify_header */
31
#include "sendf.h"   /* for the client write stuff */
32
#include "curlx/dynbuf.h"
33
#include "multiif.h"
34
#include "curlx/strparse.h"
35
36
/*
37
   Chunk format (simplified):
38
39
   <HEX SIZE>[ chunk extension ] CRLF
40
   <DATA> CRLF
41
42
   Highlights from RFC2616 section 3.6 say:
43
44
   The chunked encoding modifies the body of a message in order to
45
   transfer it as a series of chunks, each with its own size indicator,
46
   followed by an OPTIONAL trailer containing entity-header fields. This
47
   allows dynamically produced content to be transferred along with the
48
   information necessary for the recipient to verify that it has
49
   received the full message.
50
51
       Chunked-Body   = *chunk
52
                        last-chunk
53
                        trailer
54
                        CRLF
55
56
       chunk          = chunk-size [ chunk-extension ] CRLF
57
                        chunk-data CRLF
58
       chunk-size     = 1*HEX
59
       last-chunk     = 1*("0") [ chunk-extension ] CRLF
60
61
       chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
62
       chunk-ext-name = token
63
       chunk-ext-val  = token | quoted-string
64
       chunk-data     = chunk-size(OCTET)
65
       trailer        = *(entity-header CRLF)
66
67
   The chunk-size field is a string of hex digits indicating the size of
68
   the chunk. The chunked encoding is ended by any chunk whose size is
69
   zero, followed by the trailer, which is terminated by an empty line.
70
71
 */
72
73
void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch,
74
                         bool ignore_body, bool in_connect)
75
32.5k
{
76
32.5k
  (void)data;
77
32.5k
  ch->hexindex = 0;      /* start at 0 */
78
32.5k
  ch->state = CHUNK_HEX; /* we get hex first! */
79
32.5k
  ch->last_code = CHUNKE_OK;
80
32.5k
  curlx_dyn_init(&ch->trailer, DYN_H1_TRAILER);
81
32.5k
  ch->ignore_body = ignore_body;
82
32.5k
  ch->in_connect = in_connect;
83
32.5k
}
84
85
void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch,
86
                          bool ignore_body)
87
4.61k
{
88
4.61k
  (void)data;
89
4.61k
  ch->hexindex = 0;      /* start at 0 */
90
4.61k
  ch->state = CHUNK_HEX; /* we get hex first! */
91
4.61k
  ch->last_code = CHUNKE_OK;
92
4.61k
  curlx_dyn_reset(&ch->trailer);
93
4.61k
  ch->ignore_body = ignore_body;
94
4.61k
}
95
96
void Curl_httpchunk_free(struct Curl_easy *data, struct Curl_chunker *ch)
97
32.5k
{
98
32.5k
  (void)data;
99
32.5k
  curlx_dyn_free(&ch->trailer);
100
32.5k
}
101
102
bool Curl_httpchunk_is_done(struct Curl_easy *data, struct Curl_chunker *ch)
103
266k
{
104
266k
  (void)data;
105
266k
  return ch->state == CHUNK_DONE;
106
266k
}
107
108
static CURLcode httpchunk_readwrite(struct Curl_easy *data,
109
                                    struct Curl_chunker *ch,
110
                                    struct Curl_cwriter *cw_next,
111
                                    const char *buf, size_t blen,
112
                                    size_t *pconsumed)
113
279k
{
114
279k
  CURLcode result = CURLE_OK;
115
279k
  size_t piece;
116
117
279k
  *pconsumed = 0; /* nothing's written yet */
118
  /* first check terminal states that will not progress anywhere */
119
279k
  if(ch->state == CHUNK_DONE)
120
0
    return CURLE_OK;
121
279k
  if(ch->state == CHUNK_FAILED)
122
0
    return CURLE_RECV_ERROR;
123
124
  /* the original data is written to the client, but we go on with the
125
     chunk read process, to properly calculate the content length */
126
279k
  if(data->set.http_te_skip && !ch->ignore_body) {
127
1.62k
    if(cw_next)
128
1.62k
      result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY, buf, blen);
129
0
    else
130
0
      result = Curl_client_write(data, CLIENTWRITE_BODY, buf, blen);
131
1.62k
    if(result) {
132
15
      ch->state = CHUNK_FAILED;
133
15
      ch->last_code = CHUNKE_PASSTHRU_ERROR;
134
15
      return result;
135
15
    }
136
1.62k
  }
137
138
1.22M
  while(blen) {
139
942k
    switch(ch->state) {
140
28.8k
    case CHUNK_HEX:
141
28.8k
      if(ISXDIGIT(*buf)) {
142
22.6k
        if(ch->hexindex >= CHUNK_MAXNUM_LEN) {
143
36
          failf(data, "chunk hex-length longer than %d", CHUNK_MAXNUM_LEN);
144
36
          ch->state = CHUNK_FAILED;
145
36
          ch->last_code = CHUNKE_TOO_LONG_HEX; /* longer than we support */
146
36
          return CURLE_RECV_ERROR;
147
36
        }
148
22.6k
        ch->hexbuffer[ch->hexindex++] = *buf;
149
22.6k
        buf++;
150
22.6k
        blen--;
151
22.6k
        (*pconsumed)++;
152
22.6k
      }
153
6.18k
      else {
154
6.18k
        const char *p;
155
6.18k
        if(ch->hexindex == 0) {
156
          /* This is illegal data, we received junk where we expected
157
             a hexadecimal digit. */
158
148
          failf(data, "chunk hex-length char not a hex digit: 0x%x",
159
148
                (unsigned int)*buf);
160
148
          ch->state = CHUNK_FAILED;
161
148
          ch->last_code = CHUNKE_ILLEGAL_HEX;
162
148
          return CURLE_RECV_ERROR;
163
148
        }
164
        /* blen and buf are unmodified */
165
6.03k
        ch->hexbuffer[ch->hexindex] = 0;
166
6.03k
        p = &ch->hexbuffer[0];
167
6.03k
        if(curlx_str_hex(&p, &ch->datasize, CURL_OFF_T_MAX)) {
168
72
          failf(data, "invalid chunk size: '%s'", ch->hexbuffer);
169
72
          ch->state = CHUNK_FAILED;
170
72
          ch->last_code = CHUNKE_ILLEGAL_HEX;
171
72
          return CURLE_RECV_ERROR;
172
72
        }
173
5.96k
        ch->state = CHUNK_LF; /* now wait for the CRLF */
174
5.96k
      }
175
28.6k
      break;
176
177
154k
    case CHUNK_LF:
178
      /* waiting for the LF after a chunk size */
179
154k
      if(*buf == 0x0a) {
180
        /* we are now expecting data to come, unless size was zero! */
181
5.61k
        if(ch->datasize == 0) {
182
1.01k
          ch->state = CHUNK_TRAILER; /* now check for trailers */
183
1.01k
        }
184
4.60k
        else {
185
4.60k
          ch->state = CHUNK_DATA;
186
4.60k
          CURL_TRC_WRITE(data, "http_chunked, chunk start of %"
187
4.60k
                         FMT_OFF_T " bytes", ch->datasize);
188
4.60k
        }
189
5.61k
      }
190
191
154k
      buf++;
192
154k
      blen--;
193
154k
      (*pconsumed)++;
194
154k
      break;
195
196
221k
    case CHUNK_DATA:
197
      /* We expect 'datasize' of data. We have 'blen' right now, it can be
198
         more or less than 'datasize'. Get the smallest piece. */
199
221k
      piece = blen;
200
221k
      if(ch->datasize < (curl_off_t)blen)
201
2.98k
        piece = curlx_sotouz(ch->datasize);
202
203
      /* Write the data portion available */
204
221k
      if(!data->set.http_te_skip && !ch->ignore_body) {
205
5.23k
        if(cw_next)
206
5.23k
          result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY,
207
5.23k
                                      buf, piece);
208
0
        else
209
0
          result = Curl_client_write(data, CLIENTWRITE_BODY, buf, piece);
210
5.23k
        if(result) {
211
39
          ch->state = CHUNK_FAILED;
212
39
          ch->last_code = CHUNKE_PASSTHRU_ERROR;
213
39
          return result;
214
39
        }
215
5.23k
      }
216
217
221k
      *pconsumed += piece;
218
221k
      ch->datasize -= piece; /* decrease amount left to expect */
219
221k
      buf += piece;    /* move read pointer forward */
220
221k
      blen -= piece;   /* decrease space left in this round */
221
221k
      CURL_TRC_WRITE(data, "http_chunked, write %zu body bytes, %"
222
221k
                     FMT_OFF_T " bytes in chunk remain",
223
221k
                     piece, ch->datasize);
224
225
221k
      if(ch->datasize == 0)
226
        /* end of data this round, we now expect a trailing CRLF */
227
3.17k
        ch->state = CHUNK_POSTLF;
228
221k
      break;
229
230
6.42k
    case CHUNK_POSTLF:
231
6.42k
      if(*buf == 0x0a) {
232
        /* The last one before we go back to hex state and start all over. */
233
2.95k
        Curl_httpchunk_reset(data, ch, (bool)ch->ignore_body);
234
2.95k
      }
235
3.47k
      else if(*buf != 0x0d) {
236
164
        ch->state = CHUNK_FAILED;
237
164
        ch->last_code = CHUNKE_BAD_CHUNK;
238
164
        return CURLE_RECV_ERROR;
239
164
      }
240
6.25k
      buf++;
241
6.25k
      blen--;
242
6.25k
      (*pconsumed)++;
243
6.25k
      break;
244
245
493k
    case CHUNK_TRAILER:
246
493k
      if((*buf == 0x0d) || (*buf == 0x0a)) {
247
19.5k
        const char *tr = curlx_dyn_ptr(&ch->trailer);
248
        /* this is the end of a trailer, but if the trailer was zero bytes
249
           there was no trailer and we move on */
250
251
19.5k
        if(tr) {
252
19.4k
          size_t trlen;
253
19.4k
          result = curlx_dyn_addn(&ch->trailer, STRCONST("\x0d\x0a"));
254
19.4k
          if(result) {
255
10
            ch->state = CHUNK_FAILED;
256
10
            ch->last_code = CHUNKE_OUT_OF_MEMORY;
257
10
            return result;
258
10
          }
259
19.4k
          tr = curlx_dyn_ptr(&ch->trailer);
260
19.4k
          trlen = curlx_dyn_len(&ch->trailer);
261
262
          /* a trailer is delivered to the client as a header, so it must pass
263
             the same checks as a regular response header */
264
19.4k
          result = Curl_verify_header(data, tr, trlen);
265
19.4k
          if(result) {
266
186
            ch->state = CHUNK_FAILED;
267
186
            ch->last_code = CHUNKE_BAD_CHUNK;
268
186
            return result;
269
186
          }
270
271
19.2k
          if(!data->set.http_te_skip) {
272
14.6k
            int hd_type = CLIENTWRITE_HEADER | CLIENTWRITE_TRAILER;
273
14.6k
            if(ch->in_connect)
274
2.80k
              hd_type |= CLIENTWRITE_CONNECT;
275
14.6k
            if(cw_next)
276
11.8k
              result = Curl_cwriter_write(data, cw_next, hd_type, tr, trlen);
277
2.80k
            else
278
2.80k
              result = Curl_client_write(data, hd_type, tr, trlen);
279
14.6k
            CURL_TRC_WRITE(data, "wrote trailer '%s'", tr);
280
14.6k
            if(result) {
281
52
              ch->state = CHUNK_FAILED;
282
52
              ch->last_code = CHUNKE_PASSTHRU_ERROR;
283
52
              return result;
284
52
            }
285
14.5k
            result = Curl_bump_headersize(data, trlen, FALSE);
286
14.5k
            if(result) {
287
0
              ch->state = CHUNK_FAILED;
288
0
              ch->last_code = CHUNKE_TRAILER_LIMIT;
289
0
              return result;
290
0
            }
291
14.5k
          }
292
19.2k
          curlx_dyn_reset(&ch->trailer);
293
19.2k
          ch->state = CHUNK_TRAILER_CR;
294
19.2k
          if(*buf == 0x0a)
295
            /* already on the LF */
296
12.5k
            break;
297
19.2k
        }
298
80
        else {
299
          /* no trailer, we are on the final CRLF pair */
300
80
          ch->state = CHUNK_TRAILER_POSTCR;
301
80
          break; /* do not advance the pointer */
302
80
        }
303
19.5k
      }
304
473k
      else {
305
473k
        result = curlx_dyn_addn(&ch->trailer, buf, 1);
306
473k
        if(result) {
307
17
          ch->state = CHUNK_FAILED;
308
17
          ch->last_code = CHUNKE_OUT_OF_MEMORY;
309
17
          return result;
310
17
        }
311
473k
      }
312
480k
      buf++;
313
480k
      blen--;
314
480k
      (*pconsumed)++;
315
480k
      break;
316
317
19.1k
    case CHUNK_TRAILER_CR:
318
19.1k
      if(*buf == 0x0a) {
319
19.1k
        ch->state = CHUNK_TRAILER_POSTCR;
320
19.1k
        buf++;
321
19.1k
        blen--;
322
19.1k
        (*pconsumed)++;
323
19.1k
      }
324
33
      else {
325
33
        ch->state = CHUNK_FAILED;
326
33
        ch->last_code = CHUNKE_BAD_CHUNK;
327
33
        return CURLE_RECV_ERROR;
328
33
      }
329
19.1k
      break;
330
331
19.1k
    case CHUNK_TRAILER_POSTCR:
332
      /* We enter this state when a CR should arrive so we expect to
333
         have to first pass a CR before we wait for LF */
334
19.1k
      if((*buf != 0x0d) && (*buf != 0x0a)) {
335
        /* not a CR then it must be another header in the trailer */
336
18.9k
        ch->state = CHUNK_TRAILER;
337
18.9k
        break;
338
18.9k
      }
339
193
      if(*buf == 0x0d) {
340
        /* skip if CR */
341
130
        buf++;
342
130
        blen--;
343
130
        (*pconsumed)++;
344
130
      }
345
      /* now wait for the final LF */
346
193
      ch->state = CHUNK_STOP;
347
193
      break;
348
349
192
    case CHUNK_STOP:
350
192
      if(*buf == 0x0a) {
351
163
        blen--;
352
163
        (*pconsumed)++;
353
        /* Record the length of any data left in the end of the buffer
354
           even if there is no more chunks to read */
355
163
        ch->datasize = blen;
356
163
        ch->state = CHUNK_DONE;
357
163
        CURL_TRC_WRITE(data, "http_chunk, response complete");
358
163
        return CURLE_OK;
359
163
      }
360
29
      else {
361
29
        ch->state = CHUNK_FAILED;
362
29
        ch->last_code = CHUNKE_BAD_CHUNK;
363
29
        CURL_TRC_WRITE(data, "http_chunk error, expected 0x0a, seeing 0x%ux",
364
29
                       (unsigned int)*buf);
365
29
        return CURLE_RECV_ERROR;
366
29
      }
367
0
    case CHUNK_DONE:
368
0
      return CURLE_OK;
369
370
0
    case CHUNK_FAILED:
371
0
      return CURLE_RECV_ERROR;
372
942k
    }
373
942k
  }
374
278k
  return CURLE_OK;
375
279k
}
376
377
static const char *Curl_chunked_strerror(CHUNKcode code)
378
606
{
379
606
  switch(code) {
380
0
  default:
381
0
    return "OK";
382
30
  case CHUNKE_TOO_LONG_HEX:
383
30
    return "Too long hexadecimal number";
384
200
  case CHUNKE_ILLEGAL_HEX:
385
200
    return "Illegal or missing hexadecimal sequence";
386
353
  case CHUNKE_BAD_CHUNK:
387
353
    return "Malformed encoding found";
388
0
  case CHUNKE_PASSTHRU_ERROR:
389
0
    return "Error writing data to client";
390
0
  case CHUNKE_BAD_ENCODING:
391
0
    return "Bad content-encoding found";
392
23
  case CHUNKE_OUT_OF_MEMORY:
393
23
    return "Out of memory";
394
0
  case CHUNKE_TRAILER_LIMIT:
395
0
    return "trailers exceeded header limit";
396
606
  }
397
606
}
398
399
CURLcode Curl_httpchunk_read(struct Curl_easy *data,
400
                             struct Curl_chunker *ch,
401
                             char *buf, size_t blen,
402
                             size_t *pconsumed)
403
266k
{
404
266k
  return httpchunk_readwrite(data, ch, NULL, buf, blen, pconsumed);
405
266k
}
406
407
struct chunked_writer {
408
  struct Curl_cwriter super;
409
  struct Curl_chunker ch;
410
};
411
412
static CURLcode cw_chunked_init(struct Curl_easy *data,
413
                                struct Curl_cwriter *writer)
414
3.51k
{
415
3.51k
  struct chunked_writer *ctx = writer->ctx;
416
417
3.51k
  data->req.chunk = TRUE;      /* chunks coming our way. */
418
3.51k
  Curl_httpchunk_init(data, &ctx->ch, FALSE, FALSE);
419
3.51k
  return CURLE_OK;
420
3.51k
}
421
422
static void cw_chunked_close(struct Curl_easy *data,
423
                             struct Curl_cwriter *writer)
424
3.51k
{
425
3.51k
  struct chunked_writer *ctx = writer->ctx;
426
3.51k
  Curl_httpchunk_free(data, &ctx->ch);
427
3.51k
}
428
429
static CURLcode cw_chunked_write(struct Curl_easy *data,
430
                                 struct Curl_cwriter *writer, int type,
431
                                 const char *buf, size_t blen)
432
37.1k
{
433
37.1k
  struct chunked_writer *ctx = writer->ctx;
434
37.1k
  CURLcode result;
435
37.1k
  size_t consumed;
436
437
37.1k
  if(!(type & CLIENTWRITE_BODY))
438
24.8k
    return Curl_cwriter_write(data, writer->next, type, buf, blen);
439
440
12.3k
  consumed = 0;
441
12.3k
  result = httpchunk_readwrite(data, &ctx->ch, writer->next, buf, blen,
442
12.3k
                               &consumed);
443
444
12.3k
  if(result) {
445
702
    if(CHUNKE_PASSTHRU_ERROR == ctx->ch.last_code) {
446
96
      failf(data, "Failed reading the chunked-encoded stream");
447
96
    }
448
606
    else {
449
606
      failf(data, "%s in chunked-encoding",
450
606
            Curl_chunked_strerror(ctx->ch.last_code));
451
606
    }
452
702
    return result;
453
702
  }
454
455
11.6k
  blen -= consumed;
456
11.6k
  if(CHUNK_DONE == ctx->ch.state) {
457
    /* chunks read successfully, download is complete */
458
149
    data->req.download_done = TRUE;
459
149
    if(blen) {
460
81
      infof(data, "Leftovers after chunking: %zu bytes", blen);
461
81
    }
462
149
  }
463
11.4k
  else if((type & CLIENTWRITE_EOS) && !data->req.no_body) {
464
2.17k
    failf(data, "transfer closed with outstanding read data remaining");
465
2.17k
    return CURLE_PARTIAL_FILE;
466
2.17k
  }
467
468
9.47k
  return CURLE_OK;
469
11.6k
}
470
471
/* HTTP chunked Transfer-Encoding decoder */
472
const struct Curl_cwtype Curl_httpchunk_unencoder = {
473
  "chunked",
474
  NULL,
475
  0,
476
  cw_chunked_init,
477
  cw_chunked_write,
478
  Curl_cwriter_def_flush,
479
  cw_chunked_close,
480
  sizeof(struct chunked_writer)
481
};
482
483
/* max length of an HTTP chunk that we want to generate */
484
#define CURL_CHUNKED_MINLEN   1024
485
18.8k
#define CURL_CHUNKED_MAXLEN   (64 * 1024)
486
487
struct chunked_reader {
488
  struct Curl_creader super;
489
  struct bufq chunkbuf;
490
  BIT(read_eos);  /* we read an EOS from the next reader */
491
  BIT(eos);       /* we have returned an EOS */
492
};
493
494
static CURLcode cr_chunked_init(struct Curl_easy *data,
495
                                struct Curl_creader *reader)
496
18.8k
{
497
18.8k
  struct chunked_reader *ctx = reader->ctx;
498
18.8k
  (void)data;
499
18.8k
  Curl_bufq_init2(&ctx->chunkbuf, CURL_CHUNKED_MAXLEN, 2, BUFQ_OPT_SOFT_LIMIT);
500
18.8k
  return CURLE_OK;
501
18.8k
}
502
503
static void cr_chunked_close(struct Curl_easy *data,
504
                             struct Curl_creader *reader)
505
18.8k
{
506
18.8k
  struct chunked_reader *ctx = reader->ctx;
507
18.8k
  (void)data;
508
18.8k
  Curl_bufq_free(&ctx->chunkbuf);
509
18.8k
}
510
511
static CURLcode add_last_chunk(struct Curl_easy *data,
512
                               struct Curl_creader *reader)
513
11.7k
{
514
11.7k
  struct chunked_reader *ctx = reader->ctx;
515
11.7k
  struct curl_slist *trailers = NULL, *tr;
516
11.7k
  CURLcode result;
517
11.7k
  size_t n;
518
11.7k
  int rc;
519
520
11.7k
  if(!data->set.trailer_callback) {
521
11.7k
    CURL_TRC_READ(data, "http_chunk, added last, empty chunk");
522
11.7k
    return Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("0\r\n\r\n"), &n);
523
11.7k
  }
524
525
0
  result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("0\r\n"), &n);
526
0
  if(result)
527
0
    goto out;
528
529
0
  {
530
0
    struct Curl_mapi_guard guard;
531
0
    CURL_CBAPI_START(&guard, data, easy_trailer_callback);
532
0
    rc = data->set.trailer_callback(&trailers, data->set.trailer_data);
533
0
    CURL_CBAPI_END(&guard);
534
0
  }
535
536
0
  if(rc != CURL_TRAILERFUNC_OK) {
537
0
    failf(data, "operation aborted by trailing headers callback");
538
0
    result = CURLE_ABORTED_BY_CALLBACK;
539
0
    goto out;
540
0
  }
541
542
0
  for(tr = trailers; tr; tr = tr->next) {
543
    /* only add correctly formatted trailers */
544
0
    const char *ptr = strchr(tr->data, ':');
545
0
    if(!ptr || *(ptr + 1) != ' ') {
546
0
      infof(data, "Malformatted trailing header, skipping trailer");
547
0
      continue;
548
0
    }
549
550
0
    result = Curl_bufq_cwrite(&ctx->chunkbuf, tr->data, strlen(tr->data), &n);
551
0
    if(!result)
552
0
      result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("\r\n"), &n);
553
0
    if(result)
554
0
      goto out;
555
0
  }
556
557
0
  result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("\r\n"), &n);
558
559
0
out:
560
0
  curl_slist_free_all(trailers);
561
0
  CURL_TRC_READ(data, "http_chunk, added last chunk with trailers "
562
0
                "from client -> %d", (int)result);
563
0
  return result;
564
0
}
565
566
static CURLcode add_chunk(struct Curl_easy *data,
567
                          struct Curl_creader *reader,
568
                          char *buf, size_t blen)
569
87.3k
{
570
87.3k
  struct chunked_reader *ctx = reader->ctx;
571
87.3k
  CURLcode result;
572
87.3k
  char tmp[CURL_CHUNKED_MINLEN];
573
87.3k
  size_t nread;
574
87.3k
  bool eos;
575
576
87.3k
  DEBUGASSERT(!ctx->read_eos);
577
87.3k
  blen = CURLMIN(blen, CURL_CHUNKED_MAXLEN); /* respect our buffer pref */
578
87.3k
  if(blen < sizeof(tmp)) {
579
    /* small read, make a chunk of decent size */
580
1.85k
    buf = tmp;
581
1.85k
    blen = sizeof(tmp);
582
1.85k
  }
583
85.5k
  else {
584
    /* larger read, make a chunk that will fit when read back */
585
85.5k
    blen -= (8 + 2 + 2); /* deduct max overhead, 8 hex + 2*crlf */
586
85.5k
  }
587
588
87.3k
  result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos);
589
87.3k
  if(result)
590
144
    return result;
591
87.2k
  if(eos)
592
11.7k
    ctx->read_eos = TRUE;
593
594
87.2k
  if(nread) {
595
    /* actually got bytes, wrap them into the chunkbuf */
596
24.0k
    char hd[11] = "";
597
24.0k
    int hdlen;
598
24.0k
    size_t n;
599
600
24.0k
    hdlen = curl_msnprintf(hd, sizeof(hd), "%zx\r\n", nread);
601
24.0k
    if(hdlen <= 0)
602
0
      return CURLE_READ_ERROR;
603
    /* On a soft-limited bufq, we do not need to check that all was written */
604
24.0k
    result = Curl_bufq_cwrite(&ctx->chunkbuf, hd, hdlen, &n);
605
24.0k
    if(!result)
606
24.0k
      result = Curl_bufq_cwrite(&ctx->chunkbuf, buf, nread, &n);
607
24.0k
    if(!result)
608
24.0k
      result = Curl_bufq_cwrite(&ctx->chunkbuf, "\r\n", 2, &n);
609
24.0k
    CURL_TRC_READ(data, "http_chunk, made chunk of %zu bytes -> %d",
610
24.0k
                  nread, (int)result);
611
24.0k
    if(result)
612
9
      return result;
613
24.0k
  }
614
615
87.2k
  if(ctx->read_eos)
616
11.7k
    return add_last_chunk(data, reader);
617
75.4k
  return CURLE_OK;
618
87.2k
}
619
620
static CURLcode cr_chunked_read(struct Curl_easy *data,
621
                                struct Curl_creader *reader,
622
                                char *buf, size_t blen,
623
                                size_t *pnread, bool *peos)
624
88.0k
{
625
88.0k
  struct chunked_reader *ctx = reader->ctx;
626
88.0k
  CURLcode result = CURLE_READ_ERROR;
627
628
88.0k
  *pnread = 0;
629
88.0k
  *peos = (bool)ctx->eos;
630
631
88.0k
  if(!ctx->eos) {
632
88.0k
    if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->chunkbuf)) {
633
      /* Still getting data form the next reader, buffer is empty */
634
87.3k
      result = add_chunk(data, reader, buf, blen);
635
87.3k
      if(result)
636
160
        return result;
637
87.3k
    }
638
639
87.9k
    if(!Curl_bufq_is_empty(&ctx->chunkbuf)) {
640
31.0k
      result = Curl_bufq_cread(&ctx->chunkbuf, buf, blen, pnread);
641
31.0k
      if(!result && ctx->read_eos && Curl_bufq_is_empty(&ctx->chunkbuf)) {
642
        /* no more data, read all, done. */
643
11.6k
        ctx->eos = TRUE;
644
11.6k
        *peos = TRUE;
645
11.6k
      }
646
31.0k
      return result;
647
31.0k
    }
648
87.9k
  }
649
  /* We may get here, because we are done or because callbacks paused */
650
56.8k
  DEBUGASSERT(ctx->eos || !ctx->read_eos);
651
56.8k
  return CURLE_OK;
652
56.8k
}
653
654
static curl_off_t cr_chunked_total_length(struct Curl_easy *data,
655
                                          struct Curl_creader *reader)
656
47.8k
{
657
  /* this reader changes length depending on input */
658
47.8k
  (void)data;
659
47.8k
  (void)reader;
660
47.8k
  return -1;
661
47.8k
}
662
663
/* HTTP chunked Transfer-Encoding encoder */
664
const struct Curl_crtype Curl_httpchunk_encoder = {
665
  "chunked",
666
  cr_chunked_init,
667
  cr_chunked_read,
668
  cr_chunked_close,
669
  Curl_creader_def_needs_rewind,
670
  cr_chunked_total_length,
671
  Curl_creader_def_resume_from,
672
  Curl_creader_def_cntrl,
673
  Curl_creader_def_is_paused,
674
  Curl_creader_def_done,
675
  sizeof(struct chunked_reader)
676
};
677
678
CURLcode Curl_httpchunk_add_reader(struct Curl_easy *data)
679
18.8k
{
680
18.8k
  struct Curl_creader *reader = NULL;
681
18.8k
  CURLcode result;
682
683
18.8k
  result = Curl_creader_create(&reader, data, &Curl_httpchunk_encoder,
684
18.8k
                               CURL_CR_TRANSFER_ENCODE);
685
18.8k
  if(!result)
686
18.8k
    result = Curl_creader_add(data, reader);
687
688
18.8k
  if(result && reader)
689
0
    Curl_creader_free(data, reader);
690
18.8k
  return result;
691
18.8k
}
692
693
#endif /* CURL_DISABLE_HTTP */