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