/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 */ |