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