chiark / gitweb /
Import curl_7.56.1.orig.tar.gz
[curl.git] / lib / http_chunks.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2017, 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.haxx.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  ***************************************************************************/
22
23 #include "curl_setup.h"
24
25 #ifndef CURL_DISABLE_HTTP
26
27 #include "urldata.h" /* it includes http_chunks.h */
28 #include "sendf.h"   /* for the client write stuff */
29
30 #include "content_encoding.h"
31 #include "http.h"
32 #include "non-ascii.h" /* for Curl_convert_to_network prototype */
33 #include "strtoofft.h"
34 #include "warnless.h"
35
36 /* The last #include files should be: */
37 #include "curl_memory.h"
38 #include "memdebug.h"
39
40 /*
41  * Chunk format (simplified):
42  *
43  * <HEX SIZE>[ chunk extension ] CRLF
44  * <DATA> CRLF
45  *
46  * Highlights from RFC2616 section 3.6 say:
47
48    The chunked encoding modifies the body of a message in order to
49    transfer it as a series of chunks, each with its own size indicator,
50    followed by an OPTIONAL trailer containing entity-header fields. This
51    allows dynamically produced content to be transferred along with the
52    information necessary for the recipient to verify that it has
53    received the full message.
54
55        Chunked-Body   = *chunk
56                         last-chunk
57                         trailer
58                         CRLF
59
60        chunk          = chunk-size [ chunk-extension ] CRLF
61                         chunk-data CRLF
62        chunk-size     = 1*HEX
63        last-chunk     = 1*("0") [ chunk-extension ] CRLF
64
65        chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
66        chunk-ext-name = token
67        chunk-ext-val  = token | quoted-string
68        chunk-data     = chunk-size(OCTET)
69        trailer        = *(entity-header CRLF)
70
71    The chunk-size field is a string of hex digits indicating the size of
72    the chunk. The chunked encoding is ended by any chunk whose size is
73    zero, followed by the trailer, which is terminated by an empty line.
74
75  */
76
77 /* Check for an ASCII hex digit.
78  We avoid the use of isxdigit to accommodate non-ASCII hosts. */
79 static bool Curl_isxdigit(char digit)
80 {
81   return ( (digit >= 0x30 && digit <= 0x39) /* 0-9 */
82         || (digit >= 0x41 && digit <= 0x46) /* A-F */
83         || (digit >= 0x61 && digit <= 0x66) /* a-f */) ? TRUE : FALSE;
84 }
85
86 void Curl_httpchunk_init(struct connectdata *conn)
87 {
88   struct Curl_chunker *chunk = &conn->chunk;
89   chunk->hexindex = 0;      /* start at 0 */
90   chunk->dataleft = 0;      /* no data left yet! */
91   chunk->state = CHUNK_HEX; /* we get hex first! */
92 }
93
94 /*
95  * chunk_read() returns a OK for normal operations, or a positive return code
96  * for errors. STOP means this sequence of chunks is complete.  The 'wrote'
97  * argument is set to tell the caller how many bytes we actually passed to the
98  * client (for byte-counting and whatever).
99  *
100  * The states and the state-machine is further explained in the header file.
101  *
102  * This function always uses ASCII hex values to accommodate non-ASCII hosts.
103  * For example, 0x0d and 0x0a are used instead of '\r' and '\n'.
104  */
105 CHUNKcode Curl_httpchunk_read(struct connectdata *conn,
106                               char *datap,
107                               ssize_t datalen,
108                               ssize_t *wrotep)
109 {
110   CURLcode result = CURLE_OK;
111   struct Curl_easy *data = conn->data;
112   struct Curl_chunker *ch = &conn->chunk;
113   struct SingleRequest *k = &data->req;
114   size_t piece;
115   curl_off_t length = (curl_off_t)datalen;
116   size_t *wrote = (size_t *)wrotep;
117
118   *wrote = 0; /* nothing's written yet */
119
120   /* the original data is written to the client, but we go on with the
121      chunk read process, to properly calculate the content length*/
122   if(data->set.http_te_skip && !k->ignorebody) {
123     result = Curl_client_write(conn, CLIENTWRITE_BODY, datap, datalen);
124     if(result)
125       return CHUNKE_WRITE_ERROR;
126   }
127
128   while(length) {
129     switch(ch->state) {
130     case CHUNK_HEX:
131       if(Curl_isxdigit(*datap)) {
132         if(ch->hexindex < MAXNUM_SIZE) {
133           ch->hexbuffer[ch->hexindex] = *datap;
134           datap++;
135           length--;
136           ch->hexindex++;
137         }
138         else {
139           return CHUNKE_TOO_LONG_HEX; /* longer hex than we support */
140         }
141       }
142       else {
143         char *endptr;
144         if(0 == ch->hexindex)
145           /* This is illegal data, we received junk where we expected
146              a hexadecimal digit. */
147           return CHUNKE_ILLEGAL_HEX;
148
149         /* length and datap are unmodified */
150         ch->hexbuffer[ch->hexindex] = 0;
151
152         /* convert to host encoding before calling strtoul */
153         result = Curl_convert_from_network(conn->data, ch->hexbuffer,
154                                            ch->hexindex);
155         if(result) {
156           /* Curl_convert_from_network calls failf if unsuccessful */
157           /* Treat it as a bad hex character */
158           return CHUNKE_ILLEGAL_HEX;
159         }
160
161         if(curlx_strtoofft(ch->hexbuffer, &endptr, 16, &ch->datasize))
162           return CHUNKE_ILLEGAL_HEX;
163         ch->state = CHUNK_LF; /* now wait for the CRLF */
164       }
165       break;
166
167     case CHUNK_LF:
168       /* waiting for the LF after a chunk size */
169       if(*datap == 0x0a) {
170         /* we're now expecting data to come, unless size was zero! */
171         if(0 == ch->datasize) {
172           ch->state = CHUNK_TRAILER; /* now check for trailers */
173           conn->trlPos = 0;
174         }
175         else
176           ch->state = CHUNK_DATA;
177       }
178
179       datap++;
180       length--;
181       break;
182
183     case CHUNK_DATA:
184       /* We expect 'datasize' of data. We have 'length' right now, it can be
185          more or less than 'datasize'. Get the smallest piece.
186       */
187       piece = curlx_sotouz((ch->datasize >= length)?length:ch->datasize);
188
189       /* Write the data portion available */
190 #ifdef HAVE_LIBZ
191       switch(conn->data->set.http_ce_skip?
192              IDENTITY : data->req.auto_decoding) {
193       case IDENTITY:
194 #endif
195         if(!k->ignorebody) {
196           if(!data->set.http_te_skip)
197             result = Curl_client_write(conn, CLIENTWRITE_BODY, datap,
198                                        piece);
199           else
200             result = CURLE_OK;
201         }
202 #ifdef HAVE_LIBZ
203         break;
204
205       case DEFLATE:
206         /* update data->req.keep.str to point to the chunk data. */
207         data->req.str = datap;
208         result = Curl_unencode_deflate_write(conn, &data->req,
209                                              (ssize_t)piece);
210         break;
211
212       case GZIP:
213         /* update data->req.keep.str to point to the chunk data. */
214         data->req.str = datap;
215         result = Curl_unencode_gzip_write(conn, &data->req,
216                                           (ssize_t)piece);
217         break;
218
219       default:
220         failf(conn->data,
221               "Unrecognized content encoding type. "
222               "libcurl understands `identity', `deflate' and `gzip' "
223               "content encodings.");
224         return CHUNKE_BAD_ENCODING;
225       }
226 #endif
227
228       if(result)
229         return CHUNKE_WRITE_ERROR;
230
231       *wrote += piece;
232
233       ch->datasize -= piece; /* decrease amount left to expect */
234       datap += piece;    /* move read pointer forward */
235       length -= piece;   /* decrease space left in this round */
236
237       if(0 == ch->datasize)
238         /* end of data this round, we now expect a trailing CRLF */
239         ch->state = CHUNK_POSTLF;
240       break;
241
242     case CHUNK_POSTLF:
243       if(*datap == 0x0a) {
244         /* The last one before we go back to hex state and start all over. */
245         Curl_httpchunk_init(conn); /* sets state back to CHUNK_HEX */
246       }
247       else if(*datap != 0x0d)
248         return CHUNKE_BAD_CHUNK;
249       datap++;
250       length--;
251       break;
252
253     case CHUNK_TRAILER:
254       if((*datap == 0x0d) || (*datap == 0x0a)) {
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(conn->trlPos) {
259           /* we allocate trailer with 3 bytes extra room to fit this */
260           conn->trailer[conn->trlPos++] = 0x0d;
261           conn->trailer[conn->trlPos++] = 0x0a;
262           conn->trailer[conn->trlPos] = 0;
263
264           /* Convert to host encoding before calling Curl_client_write */
265           result = Curl_convert_from_network(conn->data, conn->trailer,
266                                              conn->trlPos);
267           if(result)
268             /* Curl_convert_from_network calls failf if unsuccessful */
269             /* Treat it as a bad chunk */
270             return CHUNKE_BAD_CHUNK;
271
272           if(!data->set.http_te_skip) {
273             result = Curl_client_write(conn, CLIENTWRITE_HEADER,
274                                        conn->trailer, conn->trlPos);
275             if(result)
276               return CHUNKE_WRITE_ERROR;
277           }
278           conn->trlPos = 0;
279           ch->state = CHUNK_TRAILER_CR;
280           if(*datap == 0x0a)
281             /* already on the LF */
282             break;
283         }
284         else {
285           /* no trailer, we're on the final CRLF pair */
286           ch->state = CHUNK_TRAILER_POSTCR;
287           break; /* don't advance the pointer */
288         }
289       }
290       else {
291         /* conn->trailer is assumed to be freed in url.c on a
292            connection basis */
293         if(conn->trlPos >= conn->trlMax) {
294           /* we always allocate three extra bytes, just because when the full
295              header has been received we append CRLF\0 */
296           char *ptr;
297           if(conn->trlMax) {
298             conn->trlMax *= 2;
299             ptr = realloc(conn->trailer, conn->trlMax + 3);
300           }
301           else {
302             conn->trlMax = 128;
303             ptr = malloc(conn->trlMax + 3);
304           }
305           if(!ptr)
306             return CHUNKE_OUT_OF_MEMORY;
307           conn->trailer = ptr;
308         }
309         conn->trailer[conn->trlPos++]=*datap;
310       }
311       datap++;
312       length--;
313       break;
314
315     case CHUNK_TRAILER_CR:
316       if(*datap == 0x0a) {
317         ch->state = CHUNK_TRAILER_POSTCR;
318         datap++;
319         length--;
320       }
321       else
322         return CHUNKE_BAD_CHUNK;
323       break;
324
325     case CHUNK_TRAILER_POSTCR:
326       /* We enter this state when a CR should arrive so we expect to
327          have to first pass a CR before we wait for LF */
328       if((*datap != 0x0d) && (*datap != 0x0a)) {
329         /* not a CR then it must be another header in the trailer */
330         ch->state = CHUNK_TRAILER;
331         break;
332       }
333       if(*datap == 0x0d) {
334         /* skip if CR */
335         datap++;
336         length--;
337       }
338       /* now wait for the final LF */
339       ch->state = CHUNK_STOP;
340       break;
341
342     case CHUNK_STOP:
343       if(*datap == 0x0a) {
344         length--;
345
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->dataleft = curlx_sotouz(length);
349
350         return CHUNKE_STOP; /* return stop */
351       }
352       else
353         return CHUNKE_BAD_CHUNK;
354     }
355   }
356   return CHUNKE_OK;
357 }
358
359 const char *Curl_chunked_strerror(CHUNKcode code)
360 {
361   switch(code) {
362   default:
363     return "OK";
364   case CHUNKE_TOO_LONG_HEX:
365     return "Too long hexadecimal number";
366   case CHUNKE_ILLEGAL_HEX:
367     return "Illegal or missing hexadecimal sequence";
368   case CHUNKE_BAD_CHUNK:
369     return "Malformed encoding found";
370   case CHUNKE_WRITE_ERROR:
371     return "Write error";
372   case CHUNKE_BAD_ENCODING:
373     return "Bad content-encoding found";
374   case CHUNKE_OUT_OF_MEMORY:
375     return "Out of memory";
376   }
377 }
378
379 #endif /* CURL_DISABLE_HTTP */