/src/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) |
79 | 0 | { |
80 | 0 | struct connectdata *conn = data->conn; |
81 | 0 | struct Curl_chunker *chunk = &conn->chunk; |
82 | 0 | chunk->hexindex = 0; /* start at 0 */ |
83 | 0 | chunk->state = CHUNK_HEX; /* we get hex first! */ |
84 | 0 | Curl_dyn_init(&conn->trailer, DYN_H1_TRAILER); |
85 | 0 | } |
86 | | |
87 | | /* |
88 | | * chunk_read() returns a OK for normal operations, or a positive return code |
89 | | * for errors. STOP means this sequence of chunks is complete. The 'wrote' |
90 | | * argument is set to tell the caller how many bytes we actually passed to the |
91 | | * client (for byte-counting and whatever). |
92 | | * |
93 | | * The states and the state-machine is further explained in the header file. |
94 | | * |
95 | | * This function always uses ASCII hex values to accommodate non-ASCII hosts. |
96 | | * For example, 0x0d and 0x0a are used instead of '\r' and '\n'. |
97 | | */ |
98 | | CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, |
99 | | char *buf, |
100 | | size_t blen, |
101 | | size_t *pconsumed, |
102 | | CURLcode *extrap) |
103 | 0 | { |
104 | 0 | CURLcode result = CURLE_OK; |
105 | 0 | struct connectdata *conn = data->conn; |
106 | 0 | struct Curl_chunker *ch = &conn->chunk; |
107 | 0 | struct SingleRequest *k = &data->req; |
108 | 0 | size_t piece; |
109 | |
|
110 | 0 | *pconsumed = 0; /* nothing's written yet */ |
111 | | |
112 | | /* the original data is written to the client, but we go on with the |
113 | | chunk read process, to properly calculate the content length */ |
114 | 0 | if(data->set.http_te_skip && !k->ignorebody) { |
115 | 0 | result = Curl_client_write(data, CLIENTWRITE_BODY, buf, blen); |
116 | 0 | if(result) { |
117 | 0 | *extrap = result; |
118 | 0 | return CHUNKE_PASSTHRU_ERROR; |
119 | 0 | } |
120 | 0 | } |
121 | | |
122 | 0 | while(blen) { |
123 | 0 | switch(ch->state) { |
124 | 0 | case CHUNK_HEX: |
125 | 0 | if(ISXDIGIT(*buf)) { |
126 | 0 | if(ch->hexindex < CHUNK_MAXNUM_LEN) { |
127 | 0 | ch->hexbuffer[ch->hexindex] = *buf; |
128 | 0 | buf++; |
129 | 0 | blen--; |
130 | 0 | ch->hexindex++; |
131 | 0 | } |
132 | 0 | else { |
133 | 0 | return CHUNKE_TOO_LONG_HEX; /* longer hex than we support */ |
134 | 0 | } |
135 | 0 | } |
136 | 0 | else { |
137 | 0 | char *endptr; |
138 | 0 | if(0 == ch->hexindex) |
139 | | /* This is illegal data, we received junk where we expected |
140 | | a hexadecimal digit. */ |
141 | 0 | return CHUNKE_ILLEGAL_HEX; |
142 | | |
143 | | /* blen and buf are unmodified */ |
144 | 0 | ch->hexbuffer[ch->hexindex] = 0; |
145 | |
|
146 | 0 | if(curlx_strtoofft(ch->hexbuffer, &endptr, 16, &ch->datasize)) |
147 | 0 | return CHUNKE_ILLEGAL_HEX; |
148 | 0 | ch->state = CHUNK_LF; /* now wait for the CRLF */ |
149 | 0 | } |
150 | 0 | break; |
151 | | |
152 | 0 | case CHUNK_LF: |
153 | | /* waiting for the LF after a chunk size */ |
154 | 0 | if(*buf == 0x0a) { |
155 | | /* we're now expecting data to come, unless size was zero! */ |
156 | 0 | if(0 == ch->datasize) { |
157 | 0 | ch->state = CHUNK_TRAILER; /* now check for trailers */ |
158 | 0 | } |
159 | 0 | else |
160 | 0 | ch->state = CHUNK_DATA; |
161 | 0 | } |
162 | |
|
163 | 0 | buf++; |
164 | 0 | blen--; |
165 | 0 | break; |
166 | | |
167 | 0 | case CHUNK_DATA: |
168 | | /* We expect 'datasize' of data. We have 'blen' right now, it can be |
169 | | more or less than 'datasize'. Get the smallest piece. |
170 | | */ |
171 | 0 | piece = blen; |
172 | 0 | if(ch->datasize < (curl_off_t)blen) |
173 | 0 | piece = curlx_sotouz(ch->datasize); |
174 | | |
175 | | /* Write the data portion available */ |
176 | 0 | if(!data->set.http_te_skip && !k->ignorebody) { |
177 | 0 | result = Curl_client_write(data, CLIENTWRITE_BODY, buf, piece); |
178 | |
|
179 | 0 | if(result) { |
180 | 0 | *extrap = result; |
181 | 0 | return CHUNKE_PASSTHRU_ERROR; |
182 | 0 | } |
183 | 0 | } |
184 | | |
185 | 0 | *pconsumed += piece; |
186 | 0 | ch->datasize -= piece; /* decrease amount left to expect */ |
187 | 0 | buf += piece; /* move read pointer forward */ |
188 | 0 | blen -= piece; /* decrease space left in this round */ |
189 | |
|
190 | 0 | if(0 == ch->datasize) |
191 | | /* end of data this round, we now expect a trailing CRLF */ |
192 | 0 | ch->state = CHUNK_POSTLF; |
193 | 0 | break; |
194 | | |
195 | 0 | case CHUNK_POSTLF: |
196 | 0 | if(*buf == 0x0a) { |
197 | | /* The last one before we go back to hex state and start all over. */ |
198 | 0 | Curl_httpchunk_init(data); /* sets state back to CHUNK_HEX */ |
199 | 0 | } |
200 | 0 | else if(*buf != 0x0d) |
201 | 0 | return CHUNKE_BAD_CHUNK; |
202 | 0 | buf++; |
203 | 0 | blen--; |
204 | 0 | break; |
205 | | |
206 | 0 | case CHUNK_TRAILER: |
207 | 0 | if((*buf == 0x0d) || (*buf == 0x0a)) { |
208 | 0 | char *tr = Curl_dyn_ptr(&conn->trailer); |
209 | | /* this is the end of a trailer, but if the trailer was zero bytes |
210 | | there was no trailer and we move on */ |
211 | |
|
212 | 0 | if(tr) { |
213 | 0 | size_t trlen; |
214 | 0 | result = Curl_dyn_addn(&conn->trailer, (char *)STRCONST("\x0d\x0a")); |
215 | 0 | if(result) |
216 | 0 | return CHUNKE_OUT_OF_MEMORY; |
217 | | |
218 | 0 | tr = Curl_dyn_ptr(&conn->trailer); |
219 | 0 | trlen = Curl_dyn_len(&conn->trailer); |
220 | 0 | if(!data->set.http_te_skip) { |
221 | 0 | result = Curl_client_write(data, |
222 | 0 | CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER, |
223 | 0 | tr, trlen); |
224 | 0 | if(result) { |
225 | 0 | *extrap = result; |
226 | 0 | return CHUNKE_PASSTHRU_ERROR; |
227 | 0 | } |
228 | 0 | } |
229 | 0 | Curl_dyn_reset(&conn->trailer); |
230 | 0 | ch->state = CHUNK_TRAILER_CR; |
231 | 0 | if(*buf == 0x0a) |
232 | | /* already on the LF */ |
233 | 0 | break; |
234 | 0 | } |
235 | 0 | else { |
236 | | /* no trailer, we're on the final CRLF pair */ |
237 | 0 | ch->state = CHUNK_TRAILER_POSTCR; |
238 | 0 | break; /* don't advance the pointer */ |
239 | 0 | } |
240 | 0 | } |
241 | 0 | else { |
242 | 0 | result = Curl_dyn_addn(&conn->trailer, buf, 1); |
243 | 0 | if(result) |
244 | 0 | return CHUNKE_OUT_OF_MEMORY; |
245 | 0 | } |
246 | 0 | buf++; |
247 | 0 | blen--; |
248 | 0 | break; |
249 | | |
250 | 0 | case CHUNK_TRAILER_CR: |
251 | 0 | if(*buf == 0x0a) { |
252 | 0 | ch->state = CHUNK_TRAILER_POSTCR; |
253 | 0 | buf++; |
254 | 0 | blen--; |
255 | 0 | } |
256 | 0 | else |
257 | 0 | return CHUNKE_BAD_CHUNK; |
258 | 0 | break; |
259 | | |
260 | 0 | case CHUNK_TRAILER_POSTCR: |
261 | | /* We enter this state when a CR should arrive so we expect to |
262 | | have to first pass a CR before we wait for LF */ |
263 | 0 | if((*buf != 0x0d) && (*buf != 0x0a)) { |
264 | | /* not a CR then it must be another header in the trailer */ |
265 | 0 | ch->state = CHUNK_TRAILER; |
266 | 0 | break; |
267 | 0 | } |
268 | 0 | if(*buf == 0x0d) { |
269 | | /* skip if CR */ |
270 | 0 | buf++; |
271 | 0 | blen--; |
272 | 0 | } |
273 | | /* now wait for the final LF */ |
274 | 0 | ch->state = CHUNK_STOP; |
275 | 0 | break; |
276 | | |
277 | 0 | case CHUNK_STOP: |
278 | 0 | if(*buf == 0x0a) { |
279 | 0 | blen--; |
280 | | |
281 | | /* Record the length of any data left in the end of the buffer |
282 | | even if there's no more chunks to read */ |
283 | 0 | ch->datasize = blen; |
284 | |
|
285 | 0 | return CHUNKE_STOP; /* return stop */ |
286 | 0 | } |
287 | 0 | else |
288 | 0 | return CHUNKE_BAD_CHUNK; |
289 | 0 | } |
290 | 0 | } |
291 | 0 | return CHUNKE_OK; |
292 | 0 | } |
293 | | |
294 | | const char *Curl_chunked_strerror(CHUNKcode code) |
295 | 0 | { |
296 | 0 | switch(code) { |
297 | 0 | default: |
298 | 0 | return "OK"; |
299 | 0 | case CHUNKE_TOO_LONG_HEX: |
300 | 0 | return "Too long hexadecimal number"; |
301 | 0 | case CHUNKE_ILLEGAL_HEX: |
302 | 0 | return "Illegal or missing hexadecimal sequence"; |
303 | 0 | case CHUNKE_BAD_CHUNK: |
304 | 0 | return "Malformed encoding found"; |
305 | 0 | case CHUNKE_PASSTHRU_ERROR: |
306 | 0 | DEBUGASSERT(0); /* never used */ |
307 | 0 | return ""; |
308 | 0 | case CHUNKE_BAD_ENCODING: |
309 | 0 | return "Bad content-encoding found"; |
310 | 0 | case CHUNKE_OUT_OF_MEMORY: |
311 | 0 | return "Out of memory"; |
312 | 0 | } |
313 | 0 | } |
314 | | |
315 | | #endif /* CURL_DISABLE_HTTP */ |