updated the chunked state-machine to deal with the trailing CRLF that comes
[platform/upstream/curl.git] / lib / http_chunks.c
1 /*****************************************************************************
2  *                                  _   _ ____  _     
3  *  Project                     ___| | | |  _ \| |    
4  *                             / __| | | | |_) | |    
5  *                            | (__| |_| |  _ <| |___ 
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 2001, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * In order to be useful for every potential user, curl and libcurl are
11  * dual-licensed under the MPL and the MIT/X-derivate licenses.
12  *
13  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
14  * copies of the Software, and permit persons to whom the Software is
15  * furnished to do so, under the terms of the MPL or the MIT/X-derivate
16  * licenses. You may pick one of these licenses.
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 /* -- WIN32 approved -- */
26 #include <stdio.h>
27 #include <string.h>
28 #include <stdarg.h>
29 #include <stdlib.h>
30 #include <ctype.h>
31
32 #include "urldata.h" /* it includes http_chunks.h */
33 #include "sendf.h"   /* for the client write stuff */
34
35 #define _MPRINTF_REPLACE /* use our functions only */
36 #include <curl/mprintf.h>
37
38 /* The last #include file should be: */
39 #ifdef MALLOCDEBUG
40 #include "memdebug.h"
41 #endif
42
43 /* 
44  * Chunk format (simplified):
45  *
46  * <HEX SIZE>[ chunk extension ] CRLF
47  * <DATA>
48  *
49  * Highlights from RFC2616 section 3.6 say:
50
51    The chunked encoding modifies the body of a message in order to
52    transfer it as a series of chunks, each with its own size indicator,
53    followed by an OPTIONAL trailer containing entity-header fields. This
54    allows dynamically produced content to be transferred along with the
55    information necessary for the recipient to verify that it has
56    received the full message.
57
58        Chunked-Body   = *chunk
59                         last-chunk
60                         trailer
61                         CRLF
62
63        chunk          = chunk-size [ chunk-extension ] CRLF
64                         chunk-data CRLF
65        chunk-size     = 1*HEX
66        last-chunk     = 1*("0") [ chunk-extension ] CRLF
67
68        chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
69        chunk-ext-name = token
70        chunk-ext-val  = token | quoted-string
71        chunk-data     = chunk-size(OCTET)
72        trailer        = *(entity-header CRLF)
73
74    The chunk-size field is a string of hex digits indicating the size of
75    the chunk. The chunked encoding is ended by any chunk whose size is
76    zero, followed by the trailer, which is terminated by an empty line.
77
78  */
79
80
81 void Curl_httpchunk_init(struct connectdata *conn)
82 {
83   struct Curl_chunker *chunk = &conn->proto.http->chunk;
84   chunk->hexindex=0; /* start at 0 */
85   chunk->dataleft=0; /* no data left yet! */
86   chunk->state = CHUNK_HEX; /* we get hex first! */
87 }
88
89 /*
90  * chunk_read() returns a OK for normal operations, or a positive return code
91  * for errors. STOP means this sequence of chunks is complete.  The 'wrote'
92  * argument is set to tell the caller how many bytes we actually passed to the
93  * client (for byte-counting and whatever).
94  *
95  * The states and the state-machine is further explained in the header file.
96  */
97 CHUNKcode Curl_httpchunk_read(struct connectdata *conn,
98                               char *datap,
99                               ssize_t length,
100                               ssize_t *wrote)
101 {
102   CURLcode result;
103   struct Curl_chunker *ch = &conn->proto.http->chunk;
104   int piece;
105   *wrote = 0; /* nothing yet */
106
107   while(length) {
108     switch(ch->state) {
109     case CHUNK_HEX:
110       if(isxdigit((int)*datap)) {
111         if(ch->hexindex < MAXNUM_SIZE) {
112           ch->hexbuffer[ch->hexindex] = *datap;
113           datap++;
114           length--;
115           ch->hexindex++;
116         }
117         else {
118           return CHUNKE_TOO_LONG_HEX; /* longer hex than we support */
119         }
120       }
121       else {
122         if(0 == ch->hexindex) {
123           /* This is illegal data, we received junk where we expected
124              a hexadecimal digit. */
125           return CHUNKE_ILLEGAL_HEX;
126         }
127         /* length and datap are unmodified */
128         ch->hexbuffer[ch->hexindex]=0;
129         ch->datasize=strtoul(ch->hexbuffer, NULL, 16);
130         ch->state = CHUNK_POSTHEX;
131       }
132       break;
133
134     case CHUNK_POSTHEX:
135       /* In this state, we're waiting for CRLF to arrive. We support
136          this to allow so called chunk-extensions to show up here
137          before the CRLF comes. */
138       if(*datap == '\r')
139         ch->state = CHUNK_CR;
140       length--;
141       datap++;
142       break;
143
144     case CHUNK_CR:
145       /* waiting for the LF */
146       if(*datap == '\n') {
147         /* we're now expecting data to come, unless size was zero! */
148         if(0 == ch->datasize) {
149           ch->state = CHUNK_STOP; /* stop reading! */
150           if(1 == length) {
151             /* This was the final byte, return right now */
152             return CHUNKE_STOP;
153           }
154         }
155         else
156           ch->state = CHUNK_DATA;
157       }
158       else
159         /* previously we got a fake CR, go back to CR waiting! */
160         ch->state = CHUNK_CR;
161       datap++;
162       length--;
163       break;
164
165     case CHUNK_DATA:
166       /* we get pure and fine data
167
168          We expect another 'datasize' of data. We have 'length' right now,
169          it can be more or less than 'datasize'. Get the smallest piece.
170       */
171       piece = (ch->datasize >= length)?length:ch->datasize;
172
173       /* Write the data portion available */
174       result = Curl_client_write(conn->data, CLIENTWRITE_BODY, datap, piece);
175       if(result)
176         return CHUNKE_WRITE_ERROR;
177       *wrote += piece;
178
179       ch->datasize -= piece; /* decrease amount left to expect */
180       datap += piece;    /* move read pointer forward */
181       length -= piece;   /* decrease space left in this round */
182
183       if(0 == ch->datasize)
184         /* end of data this round, we now expect a trailing CRLF */
185         ch->state = CHUNK_POSTCR;
186       break;
187
188     case CHUNK_POSTCR:
189       if(*datap == '\r') {
190         ch->state = CHUNK_POSTLF;
191         datap++;
192         length--;
193       }
194       else
195         return CHUNKE_BAD_CHUNK;
196       break;
197
198     case CHUNK_POSTLF:
199       if(*datap == '\n') {
200         /*
201          * The last one before we go back to hex state and start all
202          * over.
203          */
204         Curl_httpchunk_init(conn);
205         datap++;
206         length--;
207       }
208       else
209         return CHUNKE_BAD_CHUNK;
210       break;
211
212     case CHUNK_STOP:
213       /* If we arrive here, there is data left in the end of the buffer
214          even if there's no more chunks to read */
215       ch->dataleft = length;
216       return CHUNKE_STOP; /* return stop */
217 #if 0
218     default:
219       return CHUNKE_STATE_ERROR;
220 #endif
221     }
222   }
223   return CHUNKE_OK;
224 }