Coverage Report

Created: 2024-02-25 06:14

/src/PROJ/curl/lib/http_chunks.c
Line
Count
Source (jump to first uncovered line)
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
25
#include "curl_setup.h"
26
27
#ifndef CURL_DISABLE_HTTP
28
29
#include "urldata.h" /* it includes http_chunks.h */
30
#include "sendf.h"   /* for the client write stuff */
31
#include "dynbuf.h"
32
#include "content_encoding.h"
33
#include "http.h"
34
#include "strtoofft.h"
35
#include "warnless.h"
36
37
/* The last #include files should be: */
38
#include "curl_memory.h"
39
#include "memdebug.h"
40
41
/*
42
 * Chunk format (simplified):
43
 *
44
 * <HEX SIZE>[ chunk extension ] CRLF
45
 * <DATA> CRLF
46
 *
47
 * Highlights from RFC2616 section 3.6 say:
48
49
   The chunked encoding modifies the body of a message in order to
50
   transfer it as a series of chunks, each with its own size indicator,
51
   followed by an OPTIONAL trailer containing entity-header fields. This
52
   allows dynamically produced content to be transferred along with the
53
   information necessary for the recipient to verify that it has
54
   received the full message.
55
56
       Chunked-Body   = *chunk
57
                        last-chunk
58
                        trailer
59
                        CRLF
60
61
       chunk          = chunk-size [ chunk-extension ] CRLF
62
                        chunk-data CRLF
63
       chunk-size     = 1*HEX
64
       last-chunk     = 1*("0") [ chunk-extension ] CRLF
65
66
       chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
67
       chunk-ext-name = token
68
       chunk-ext-val  = token | quoted-string
69
       chunk-data     = chunk-size(OCTET)
70
       trailer        = *(entity-header CRLF)
71
72
   The chunk-size field is a string of hex digits indicating the size of
73
   the chunk. The chunked encoding is ended by any chunk whose size is
74
   zero, followed by the trailer, which is terminated by an empty line.
75
76
 */
77
78
void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch,
79
                         bool ignore_body)
80
0
{
81
0
  (void)data;
82
0
  ch->hexindex = 0;      /* start at 0 */
83
0
  ch->state = CHUNK_HEX; /* we get hex first! */
84
0
  ch->last_code = CHUNKE_OK;
85
0
  Curl_dyn_init(&ch->trailer, DYN_H1_TRAILER);
86
0
  ch->ignore_body = ignore_body;
87
0
}
88
89
void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch,
90
                          bool ignore_body)
91
0
{
92
0
  (void)data;
93
0
  ch->hexindex = 0;      /* start at 0 */
94
0
  ch->state = CHUNK_HEX; /* we get hex first! */
95
0
  ch->last_code = CHUNKE_OK;
96
0
  Curl_dyn_reset(&ch->trailer);
97
0
  ch->ignore_body = ignore_body;
98
0
}
99
100
void Curl_httpchunk_free(struct Curl_easy *data, struct Curl_chunker *ch)
101
0
{
102
0
  (void)data;
103
0
  Curl_dyn_free(&ch->trailer);
104
0
}
105
106
bool Curl_httpchunk_is_done(struct Curl_easy *data, struct Curl_chunker *ch)
107
0
{
108
0
  (void)data;
109
0
  return ch->state == CHUNK_DONE;
110
0
}
111
112
static CURLcode httpchunk_readwrite(struct Curl_easy *data,
113
                                    struct Curl_chunker *ch,
114
                                    struct Curl_cwriter *cw_next,
115
                                    const char *buf, size_t blen,
116
                                    size_t *pconsumed)
117
0
{
118
0
  CURLcode result = CURLE_OK;
119
0
  size_t piece;
120
121
0
  *pconsumed = 0; /* nothing's written yet */
122
  /* first check terminal states that will not progress anywhere */
123
0
  if(ch->state == CHUNK_DONE)
124
0
    return CURLE_OK;
125
0
  if(ch->state == CHUNK_FAILED)
126
0
    return CURLE_RECV_ERROR;
127
128
  /* the original data is written to the client, but we go on with the
129
     chunk read process, to properly calculate the content length */
130
0
  if(data->set.http_te_skip && !ch->ignore_body) {
131
0
    if(cw_next)
132
0
      result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY, buf, blen);
133
0
    else
134
0
      result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)buf, blen);
135
0
    if(result) {
136
0
      ch->state = CHUNK_FAILED;
137
0
      ch->last_code = CHUNKE_PASSTHRU_ERROR;
138
0
      return result;
139
0
    }
140
0
  }
141
142
0
  while(blen) {
143
0
    switch(ch->state) {
144
0
    case CHUNK_HEX:
145
0
      if(ISXDIGIT(*buf)) {
146
0
        if(ch->hexindex >= CHUNK_MAXNUM_LEN) {
147
0
          failf(data, "chunk hex-length longer than %d", CHUNK_MAXNUM_LEN);
148
0
          ch->state = CHUNK_FAILED;
149
0
          ch->last_code = CHUNKE_TOO_LONG_HEX; /* longer than we support */
150
0
          return CURLE_RECV_ERROR;
151
0
        }
152
0
        ch->hexbuffer[ch->hexindex++] = *buf;
153
0
        buf++;
154
0
        blen--;
155
0
        (*pconsumed)++;
156
0
      }
157
0
      else {
158
0
        char *endptr;
159
0
        if(0 == ch->hexindex) {
160
          /* This is illegal data, we received junk where we expected
161
             a hexadecimal digit. */
162
0
          failf(data, "chunk hex-length char not a hex digit: 0x%x", *buf);
163
0
          ch->state = CHUNK_FAILED;
164
0
          ch->last_code = CHUNKE_ILLEGAL_HEX;
165
0
          return CURLE_RECV_ERROR;
166
0
        }
167
168
        /* blen and buf are unmodified */
169
0
        ch->hexbuffer[ch->hexindex] = 0;
170
0
        if(curlx_strtoofft(ch->hexbuffer, &endptr, 16, &ch->datasize)) {
171
0
          failf(data, "chunk hex-length not valid: '%s'", ch->hexbuffer);
172
0
          ch->state = CHUNK_FAILED;
173
0
          ch->last_code = CHUNKE_ILLEGAL_HEX;
174
0
          return CURLE_RECV_ERROR;
175
0
        }
176
0
        ch->state = CHUNK_LF; /* now wait for the CRLF */
177
0
      }
178
0
      break;
179
180
0
    case CHUNK_LF:
181
      /* waiting for the LF after a chunk size */
182
0
      if(*buf == 0x0a) {
183
        /* we're now expecting data to come, unless size was zero! */
184
0
        if(0 == ch->datasize) {
185
0
          ch->state = CHUNK_TRAILER; /* now check for trailers */
186
0
        }
187
0
        else
188
0
          ch->state = CHUNK_DATA;
189
0
      }
190
191
0
      buf++;
192
0
      blen--;
193
0
      (*pconsumed)++;
194
0
      break;
195
196
0
    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
      */
200
0
      piece = blen;
201
0
      if(ch->datasize < (curl_off_t)blen)
202
0
        piece = curlx_sotouz(ch->datasize);
203
204
      /* Write the data portion available */
205
0
      if(!data->set.http_te_skip && !ch->ignore_body) {
206
0
        if(cw_next)
207
0
          result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY,
208
0
                                      buf, piece);
209
0
        else
210
0
          result = Curl_client_write(data, CLIENTWRITE_BODY,
211
0
                                    (char *)buf, piece);
212
0
        if(result) {
213
0
          ch->state = CHUNK_FAILED;
214
0
          ch->last_code = CHUNKE_PASSTHRU_ERROR;
215
0
          return result;
216
0
        }
217
0
      }
218
219
0
      *pconsumed += piece;
220
0
      ch->datasize -= piece; /* decrease amount left to expect */
221
0
      buf += piece;    /* move read pointer forward */
222
0
      blen -= piece;   /* decrease space left in this round */
223
224
0
      if(0 == ch->datasize)
225
        /* end of data this round, we now expect a trailing CRLF */
226
0
        ch->state = CHUNK_POSTLF;
227
0
      break;
228
229
0
    case CHUNK_POSTLF:
230
0
      if(*buf == 0x0a) {
231
        /* The last one before we go back to hex state and start all over. */
232
0
        Curl_httpchunk_reset(data, ch, ch->ignore_body);
233
0
      }
234
0
      else if(*buf != 0x0d) {
235
0
        ch->state = CHUNK_FAILED;
236
0
        ch->last_code = CHUNKE_BAD_CHUNK;
237
0
        return CURLE_RECV_ERROR;
238
0
      }
239
0
      buf++;
240
0
      blen--;
241
0
      (*pconsumed)++;
242
0
      break;
243
244
0
    case CHUNK_TRAILER:
245
0
      if((*buf == 0x0d) || (*buf == 0x0a)) {
246
0
        char *tr = Curl_dyn_ptr(&ch->trailer);
247
        /* this is the end of a trailer, but if the trailer was zero bytes
248
           there was no trailer and we move on */
249
250
0
        if(tr) {
251
0
          size_t trlen;
252
0
          result = Curl_dyn_addn(&ch->trailer, (char *)STRCONST("\x0d\x0a"));
253
0
          if(result) {
254
0
            ch->state = CHUNK_FAILED;
255
0
            ch->last_code = CHUNKE_OUT_OF_MEMORY;
256
0
            return result;
257
0
          }
258
0
          tr = Curl_dyn_ptr(&ch->trailer);
259
0
          trlen = Curl_dyn_len(&ch->trailer);
260
0
          if(!data->set.http_te_skip) {
261
0
            if(cw_next)
262
0
              result = Curl_cwriter_write(data, cw_next,
263
0
                                          CLIENTWRITE_HEADER|
264
0
                                          CLIENTWRITE_TRAILER,
265
0
                                          tr, trlen);
266
0
            else
267
0
              result = Curl_client_write(data,
268
0
                                         CLIENTWRITE_HEADER|
269
0
                                         CLIENTWRITE_TRAILER,
270
0
                                         tr, trlen);
271
0
            if(result) {
272
0
              ch->state = CHUNK_FAILED;
273
0
              ch->last_code = CHUNKE_PASSTHRU_ERROR;
274
0
              return result;
275
0
            }
276
0
          }
277
0
          Curl_dyn_reset(&ch->trailer);
278
0
          ch->state = CHUNK_TRAILER_CR;
279
0
          if(*buf == 0x0a)
280
            /* already on the LF */
281
0
            break;
282
0
        }
283
0
        else {
284
          /* no trailer, we're on the final CRLF pair */
285
0
          ch->state = CHUNK_TRAILER_POSTCR;
286
0
          break; /* don't advance the pointer */
287
0
        }
288
0
      }
289
0
      else {
290
0
        result = Curl_dyn_addn(&ch->trailer, buf, 1);
291
0
        if(result) {
292
0
          ch->state = CHUNK_FAILED;
293
0
          ch->last_code = CHUNKE_OUT_OF_MEMORY;
294
0
          return result;
295
0
        }
296
0
      }
297
0
      buf++;
298
0
      blen--;
299
0
      (*pconsumed)++;
300
0
      break;
301
302
0
    case CHUNK_TRAILER_CR:
303
0
      if(*buf == 0x0a) {
304
0
        ch->state = CHUNK_TRAILER_POSTCR;
305
0
        buf++;
306
0
        blen--;
307
0
        (*pconsumed)++;
308
0
      }
309
0
      else {
310
0
        ch->state = CHUNK_FAILED;
311
0
        ch->last_code = CHUNKE_BAD_CHUNK;
312
0
        return CURLE_RECV_ERROR;
313
0
      }
314
0
      break;
315
316
0
    case CHUNK_TRAILER_POSTCR:
317
      /* We enter this state when a CR should arrive so we expect to
318
         have to first pass a CR before we wait for LF */
319
0
      if((*buf != 0x0d) && (*buf != 0x0a)) {
320
        /* not a CR then it must be another header in the trailer */
321
0
        ch->state = CHUNK_TRAILER;
322
0
        break;
323
0
      }
324
0
      if(*buf == 0x0d) {
325
        /* skip if CR */
326
0
        buf++;
327
0
        blen--;
328
0
        (*pconsumed)++;
329
0
      }
330
      /* now wait for the final LF */
331
0
      ch->state = CHUNK_STOP;
332
0
      break;
333
334
0
    case CHUNK_STOP:
335
0
      if(*buf == 0x0a) {
336
0
        blen--;
337
0
        (*pconsumed)++;
338
        /* Record the length of any data left in the end of the buffer
339
           even if there's no more chunks to read */
340
0
        ch->datasize = blen;
341
0
        ch->state = CHUNK_DONE;
342
0
        return CURLE_OK;
343
0
      }
344
0
      else {
345
0
        ch->state = CHUNK_FAILED;
346
0
        ch->last_code = CHUNKE_BAD_CHUNK;
347
0
        return CURLE_RECV_ERROR;
348
0
      }
349
0
    case CHUNK_DONE:
350
0
      return CURLE_OK;
351
352
0
    case CHUNK_FAILED:
353
0
      return CURLE_RECV_ERROR;
354
0
    }
355
356
0
  }
357
0
  return CURLE_OK;
358
0
}
359
360
static const char *Curl_chunked_strerror(CHUNKcode code)
361
0
{
362
0
  switch(code) {
363
0
  default:
364
0
    return "OK";
365
0
  case CHUNKE_TOO_LONG_HEX:
366
0
    return "Too long hexadecimal number";
367
0
  case CHUNKE_ILLEGAL_HEX:
368
0
    return "Illegal or missing hexadecimal sequence";
369
0
  case CHUNKE_BAD_CHUNK:
370
0
    return "Malformed encoding found";
371
0
  case CHUNKE_PASSTHRU_ERROR:
372
0
    return "Error writing data to client";
373
0
  case CHUNKE_BAD_ENCODING:
374
0
    return "Bad content-encoding found";
375
0
  case CHUNKE_OUT_OF_MEMORY:
376
0
    return "Out of memory";
377
0
  }
378
0
}
379
380
CURLcode Curl_httpchunk_read(struct Curl_easy *data,
381
                             struct Curl_chunker *ch,
382
                             char *buf, size_t blen,
383
                             size_t *pconsumed)
384
0
{
385
0
  return httpchunk_readwrite(data, ch, NULL, buf, blen, pconsumed);
386
0
}
387
388
struct chunked_writer {
389
  struct Curl_cwriter super;
390
  struct Curl_chunker ch;
391
};
392
393
static CURLcode cw_chunked_init(struct Curl_easy *data,
394
                                struct Curl_cwriter *writer)
395
0
{
396
0
  struct chunked_writer *ctx = (struct chunked_writer *)writer;
397
398
0
  data->req.chunk = TRUE;      /* chunks coming our way. */
399
0
  Curl_httpchunk_init(data, &ctx->ch, FALSE);
400
0
  return CURLE_OK;
401
0
}
402
403
static void cw_chunked_close(struct Curl_easy *data,
404
                             struct Curl_cwriter *writer)
405
0
{
406
0
  struct chunked_writer *ctx = (struct chunked_writer *)writer;
407
0
  Curl_httpchunk_free(data, &ctx->ch);
408
0
}
409
410
static CURLcode cw_chunked_write(struct Curl_easy *data,
411
                                 struct Curl_cwriter *writer, int type,
412
                                 const char *buf, size_t blen)
413
0
{
414
0
  struct chunked_writer *ctx = (struct chunked_writer *)writer;
415
0
  CURLcode result;
416
0
  size_t consumed;
417
418
0
  if(!(type & CLIENTWRITE_BODY))
419
0
    return Curl_cwriter_write(data, writer->next, type, buf, blen);
420
421
0
  consumed = 0;
422
0
  result = httpchunk_readwrite(data, &ctx->ch, writer->next, buf, blen,
423
0
                               &consumed);
424
425
0
  if(result) {
426
0
    if(CHUNKE_PASSTHRU_ERROR == ctx->ch.last_code) {
427
0
      failf(data, "Failed reading the chunked-encoded stream");
428
0
    }
429
0
    else {
430
0
      failf(data, "%s in chunked-encoding",
431
0
            Curl_chunked_strerror(ctx->ch.last_code));
432
0
    }
433
0
    return result;
434
0
  }
435
436
0
  blen -= consumed;
437
0
  if(CHUNK_DONE == ctx->ch.state) {
438
    /* chunks read successfully, download is complete */
439
0
    data->req.download_done = TRUE;
440
0
    if(blen) {
441
0
      infof(data, "Leftovers after chunking: %zu bytes", blen);
442
0
    }
443
0
  }
444
0
  else if((type & CLIENTWRITE_EOS) && !data->req.no_body) {
445
0
    failf(data, "transfer closed with outstanding read data remaining");
446
0
    return CURLE_PARTIAL_FILE;
447
0
  }
448
449
0
  return CURLE_OK;
450
0
}
451
452
/* HTTP chunked Transfer-Encoding decoder */
453
const struct Curl_cwtype Curl_httpchunk_unencoder = {
454
  "chunked",
455
  NULL,
456
  cw_chunked_init,
457
  cw_chunked_write,
458
  cw_chunked_close,
459
  sizeof(struct chunked_writer)
460
};
461
462
#endif /* CURL_DISABLE_HTTP */