Avoid typecasting a signed char to an int when using is*() functions, as that
[platform/upstream/curl.git] / lib / http_chunks.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2006, 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 http://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  * $Id$
22  ***************************************************************************/
23 #include "setup.h"
24
25 #ifndef CURL_DISABLE_HTTP
26 /* -- WIN32 approved -- */
27 #include <stdio.h>
28 #include <string.h>
29 #include <stdarg.h>
30 #include <stdlib.h>
31 #include <ctype.h>
32
33 #include "urldata.h" /* it includes http_chunks.h */
34 #include "sendf.h"   /* for the client write stuff */
35
36 #include "content_encoding.h"
37 #include "http.h"
38 #include "memory.h"
39
40 #define _MPRINTF_REPLACE /* use our functions only */
41 #include <curl/mprintf.h>
42
43 /* The last #include file should be: */
44 #include "memdebug.h"
45
46 /*
47  * Chunk format (simplified):
48  *
49  * <HEX SIZE>[ chunk extension ] CRLF
50  * <DATA> CRLF
51  *
52  * Highlights from RFC2616 section 3.6 say:
53
54    The chunked encoding modifies the body of a message in order to
55    transfer it as a series of chunks, each with its own size indicator,
56    followed by an OPTIONAL trailer containing entity-header fields. This
57    allows dynamically produced content to be transferred along with the
58    information necessary for the recipient to verify that it has
59    received the full message.
60
61        Chunked-Body   = *chunk
62                         last-chunk
63                         trailer
64                         CRLF
65
66        chunk          = chunk-size [ chunk-extension ] CRLF
67                         chunk-data CRLF
68        chunk-size     = 1*HEX
69        last-chunk     = 1*("0") [ chunk-extension ] CRLF
70
71        chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
72        chunk-ext-name = token
73        chunk-ext-val  = token | quoted-string
74        chunk-data     = chunk-size(OCTET)
75        trailer        = *(entity-header CRLF)
76
77    The chunk-size field is a string of hex digits indicating the size of
78    the chunk. The chunked encoding is ended by any chunk whose size is
79    zero, followed by the trailer, which is terminated by an empty line.
80
81  */
82
83
84 void Curl_httpchunk_init(struct connectdata *conn)
85 {
86   struct Curl_chunker *chunk = &conn->data->reqdata.proto.http->chunk;
87   chunk->hexindex=0; /* start at 0 */
88   chunk->dataleft=0; /* no data left yet! */
89   chunk->state = CHUNK_HEX; /* we get hex first! */
90 }
91
92 /*
93  * chunk_read() returns a OK for normal operations, or a positive return code
94  * for errors. STOP means this sequence of chunks is complete.  The 'wrote'
95  * argument is set to tell the caller how many bytes we actually passed to the
96  * client (for byte-counting and whatever).
97  *
98  * The states and the state-machine is further explained in the header file.
99  */
100 CHUNKcode Curl_httpchunk_read(struct connectdata *conn,
101                               char *datap,
102                               ssize_t datalen,
103                               ssize_t *wrotep)
104 {
105   CURLcode result=CURLE_OK;
106   struct SessionHandle *data = conn->data;
107   struct Curl_chunker *ch = &data->reqdata.proto.http->chunk;
108   struct Curl_transfer_keeper *k = &data->reqdata.keep;
109   size_t piece;
110   size_t length = (size_t)datalen;
111   size_t *wrote = (size_t *)wrotep;
112
113   *wrote = 0; /* nothing's written yet */
114
115   while(length) {
116     switch(ch->state) {
117     case CHUNK_HEX:
118       if(ISXDIGIT(*datap)) {
119         if(ch->hexindex < MAXNUM_SIZE) {
120           ch->hexbuffer[ch->hexindex] = *datap;
121           datap++;
122           length--;
123           ch->hexindex++;
124         }
125         else {
126           return CHUNKE_TOO_LONG_HEX; /* longer hex than we support */
127         }
128       }
129       else {
130         if(0 == ch->hexindex) {
131           /* This is illegal data, we received junk where we expected
132              a hexadecimal digit. */
133           return CHUNKE_ILLEGAL_HEX;
134         }
135         /* length and datap are unmodified */
136         ch->hexbuffer[ch->hexindex]=0;
137         ch->datasize=strtoul(ch->hexbuffer, NULL, 16);
138         ch->state = CHUNK_POSTHEX;
139       }
140       break;
141
142     case CHUNK_POSTHEX:
143       /* In this state, we're waiting for CRLF to arrive. We support
144          this to allow so called chunk-extensions to show up here
145          before the CRLF comes. */
146       if(*datap == '\r')
147         ch->state = CHUNK_CR;
148       length--;
149       datap++;
150       break;
151
152     case CHUNK_CR:
153       /* waiting for the LF */
154       if(*datap == '\n') {
155         /* we're now expecting data to come, unless size was zero! */
156         if(0 == ch->datasize) {
157           if (conn->bits.trailerHdrPresent!=TRUE) {
158             /* No Trailer: header found - revert to original Curl processing */
159             ch->state = CHUNK_STOP;
160             if (1 == length) {
161                /* This is the final byte, return right now */
162                return CHUNKE_STOP;
163             }
164           }
165           else {
166             ch->state = CHUNK_TRAILER; /* attempt to read trailers */
167             conn->trlPos=0;
168           }
169         }
170         else
171           ch->state = CHUNK_DATA;
172       }
173       else
174         /* previously we got a fake CR, go back to CR waiting! */
175         ch->state = CHUNK_CR;
176       datap++;
177       length--;
178       break;
179
180     case CHUNK_DATA:
181       /* we get pure and fine data
182
183          We expect another 'datasize' of data. We have 'length' right now,
184          it can be more or less than 'datasize'. Get the smallest piece.
185       */
186       piece = (ch->datasize >= length)?length:ch->datasize;
187
188       /* Write the data portion available */
189 #ifdef HAVE_LIBZ
190       switch (data->reqdata.keep.content_encoding) {
191         case IDENTITY:
192 #endif
193           if(!k->ignorebody)
194             result = Curl_client_write(conn, CLIENTWRITE_BODY, datap,
195                                        piece);
196 #ifdef HAVE_LIBZ
197           break;
198
199         case DEFLATE:
200           /* update data->reqdata.keep.str to point to the chunk data. */
201           data->reqdata.keep.str = datap;
202           result = Curl_unencode_deflate_write(conn, &data->reqdata.keep,
203                                                (ssize_t)piece);
204           break;
205
206         case GZIP:
207           /* update data->reqdata.keep.str to point to the chunk data. */
208           data->reqdata.keep.str = datap;
209           result = Curl_unencode_gzip_write(conn, &data->reqdata.keep,
210                                             (ssize_t)piece);
211           break;
212
213         case COMPRESS:
214         default:
215           failf (conn->data,
216                  "Unrecognized content encoding type. "
217                  "libcurl understands `identity', `deflate' and `gzip' "
218                  "content encodings.");
219           return CHUNKE_BAD_ENCODING;
220       }
221 #endif
222
223       if(result)
224         return CHUNKE_WRITE_ERROR;
225
226       *wrote += piece;
227
228       ch->datasize -= piece; /* decrease amount left to expect */
229       datap += piece;    /* move read pointer forward */
230       length -= piece;   /* decrease space left in this round */
231
232       if(0 == ch->datasize)
233         /* end of data this round, we now expect a trailing CRLF */
234         ch->state = CHUNK_POSTCR;
235       break;
236
237     case CHUNK_POSTCR:
238       if(*datap == '\r') {
239         ch->state = CHUNK_POSTLF;
240         datap++;
241         length--;
242       }
243       else
244         return CHUNKE_BAD_CHUNK;
245       break;
246
247     case CHUNK_POSTLF:
248       if(*datap == '\n') {
249         /*
250          * The last one before we go back to hex state and start all
251          * over.
252          */
253         Curl_httpchunk_init(conn);
254         datap++;
255         length--;
256       }
257       else
258         return CHUNKE_BAD_CHUNK;
259       break;
260
261     case CHUNK_TRAILER:
262       /* conn->trailer is assumed to be freed in url.c on a
263          connection basis */
264       if (conn->trlPos >= conn->trlMax) {
265         char *ptr;
266         if(conn->trlMax) {
267           conn->trlMax *= 2;
268           ptr = (char*)realloc(conn->trailer,conn->trlMax);
269         }
270         else {
271           conn->trlMax=128;
272           ptr = (char*)malloc(conn->trlMax);
273         }
274         if(!ptr)
275           return CHUNKE_OUT_OF_MEMORY;
276         conn->trailer = ptr;
277       }
278       conn->trailer[conn->trlPos++]=*datap;
279
280       if(*datap == '\r')
281         ch->state = CHUNK_TRAILER_CR;
282       else {
283         datap++;
284         length--;
285      }
286       break;
287
288     case CHUNK_TRAILER_CR:
289       if(*datap == '\r') {
290         ch->state = CHUNK_TRAILER_POSTCR;
291         datap++;
292         length--;
293       }
294       else
295         return CHUNKE_BAD_CHUNK;
296       break;
297
298     case CHUNK_TRAILER_POSTCR:
299       if (*datap == '\n') {
300         conn->trailer[conn->trlPos++]='\n';
301         conn->trailer[conn->trlPos]=0;
302         if (conn->trlPos==2) {
303           ch->state = CHUNK_STOP;
304           return CHUNKE_STOP;
305         }
306         else {
307           Curl_client_write(conn, CLIENTWRITE_HEADER,
308                             conn->trailer, conn->trlPos);
309         }
310         ch->state = CHUNK_TRAILER;
311         conn->trlPos=0;
312         datap++;
313         length--;
314       }
315       else
316         return CHUNKE_BAD_CHUNK;
317       break;
318
319     case CHUNK_STOP:
320       /* If we arrive here, there is data left in the end of the buffer
321          even if there's no more chunks to read */
322       ch->dataleft = length;
323       return CHUNKE_STOP; /* return stop */
324     default:
325       return CHUNKE_STATE_ERROR;
326     }
327   }
328   return CHUNKE_OK;
329 }
330 #endif /* CURL_DISABLE_HTTP */