xref: /unit/src/nxt_http_chunk_parse.c (revision 1627:821b47dde68e)
1 
2 /*
3  * Copyright (C) Igor Sysoev
4  * Copyright (C) NGINX, Inc.
5  */
6 
7 #include <nxt_main.h>
8 
9 
10 #define NXT_HTTP_CHUNK_MIDDLE         0
11 #define NXT_HTTP_CHUNK_END_ON_BORDER  1
12 #define NXT_HTTP_CHUNK_END            2
13 
14 
15 #define                                                                       \
16 nxt_size_is_sufficient(cs)                                                    \
17     (cs < ((__typeof__(cs)) 1 << (sizeof(cs) * 8 - 4)))
18 
19 
20 static nxt_int_t nxt_http_chunk_buffer(nxt_http_chunk_parse_t *hcp,
21     nxt_buf_t ***tail, nxt_buf_t *in);
22 
23 
24 static void nxt_http_chunk_buf_completion(nxt_task_t *task, void *obj,
25     void *data);
26 
27 
28 nxt_buf_t *
29 nxt_http_chunk_parse(nxt_task_t *task, nxt_http_chunk_parse_t *hcp,
30     nxt_buf_t *in)
31 {
32     u_char        c, ch;
33     nxt_int_t     ret;
34     nxt_buf_t     *b, *out, *next, **tail;
35     enum {
36         sw_start = 0,
37         sw_chunk_size,
38         sw_chunk_size_linefeed,
39         sw_chunk_end_newline,
40         sw_chunk_end_linefeed,
41         sw_chunk,
42     } state;
43 
44     next = NULL;
45     out = NULL;
46     tail = &out;
47 
48     state = hcp->state;
49 
50     for (b = in; b != NULL; b = next) {
51 
52         hcp->pos = b->mem.pos;
53 
54         while (hcp->pos < b->mem.free) {
55             /*
56              * The sw_chunk state is tested outside the switch
57              * to preserve hcp->pos and to not touch memory.
58              */
59             if (state == sw_chunk) {
60                 ret = nxt_http_chunk_buffer(hcp, &tail, b);
61 
62                 if (ret == NXT_HTTP_CHUNK_MIDDLE) {
63                     goto next;
64                 }
65 
66                 if (nxt_slow_path(ret == NXT_ERROR)) {
67                     hcp->error = 1;
68                     return out;
69                 }
70 
71                 state = sw_chunk_end_newline;
72 
73                 if (ret == NXT_HTTP_CHUNK_END_ON_BORDER) {
74                     goto next;
75                 }
76 
77                 /* ret == NXT_HTTP_CHUNK_END */
78             }
79 
80             ch = *hcp->pos++;
81 
82             switch (state) {
83 
84             case sw_start:
85                 state = sw_chunk_size;
86 
87                 c = ch - '0';
88 
89                 if (c <= 9) {
90                     hcp->chunk_size = c;
91                     continue;
92                 }
93 
94                 c = (ch | 0x20) - 'a';
95 
96                 if (c <= 5) {
97                     hcp->chunk_size = 0x0A + c;
98                     continue;
99                 }
100 
101                 goto chunk_error;
102 
103             case sw_chunk_size:
104 
105                 c = ch - '0';
106 
107                 if (c > 9) {
108                     c = (ch | 0x20) - 'a';
109 
110                     if (nxt_fast_path(c <= 5)) {
111                         c += 0x0A;
112 
113                     } else if (nxt_fast_path(ch == '\r')) {
114                         state = sw_chunk_size_linefeed;
115                         continue;
116 
117                     } else {
118                         goto chunk_error;
119                     }
120                 }
121 
122                 if (nxt_fast_path(nxt_size_is_sufficient(hcp->chunk_size))) {
123                     hcp->chunk_size = (hcp->chunk_size << 4) + c;
124                     continue;
125                 }
126 
127                 goto chunk_error;
128 
129             case sw_chunk_size_linefeed:
130                 if (nxt_fast_path(ch == '\n')) {
131 
132                     if (hcp->chunk_size != 0) {
133                         state = sw_chunk;
134                         continue;
135                     }
136 
137                     hcp->last = 1;
138                     state = sw_chunk_end_newline;
139                     continue;
140                 }
141 
142                 goto chunk_error;
143 
144             case sw_chunk_end_newline:
145                 if (nxt_fast_path(ch == '\r')) {
146                     state = sw_chunk_end_linefeed;
147                     continue;
148                 }
149 
150                 goto chunk_error;
151 
152             case sw_chunk_end_linefeed:
153                 if (nxt_fast_path(ch == '\n')) {
154 
155                     if (!hcp->last) {
156                         state = sw_start;
157                         continue;
158                     }
159 
160                     return out;
161                 }
162 
163                 goto chunk_error;
164 
165             case sw_chunk:
166                 /*
167                  * This state is processed before the switch.
168                  * It added here just to suppress a warning.
169                  */
170                 continue;
171             }
172         }
173 
174         if (b->retain == 0) {
175             /* No chunk data was found in a buffer. */
176             nxt_work_queue_add(&task->thread->engine->fast_work_queue,
177                                b->completion_handler, task, b, b->parent);
178 
179         }
180 
181     next:
182 
183         next = b->next;
184         b->next = NULL;
185     }
186 
187     hcp->state = state;
188 
189     return out;
190 
191 chunk_error:
192 
193     hcp->chunk_error = 1;
194 
195     return out;
196 }
197 
198 
199 static nxt_int_t
200 nxt_http_chunk_buffer(nxt_http_chunk_parse_t *hcp, nxt_buf_t ***tail,
201     nxt_buf_t *in)
202 {
203     u_char     *p;
204     size_t     size;
205     nxt_buf_t  *b;
206 
207     p = hcp->pos;
208     size = in->mem.free - p;
209 
210     b = nxt_buf_mem_alloc(hcp->mem_pool, 0, 0);
211     if (nxt_slow_path(b == NULL)) {
212         return NXT_ERROR;
213     }
214 
215     **tail = b;
216     *tail = &b->next;
217 
218     nxt_mp_retain(hcp->mem_pool);
219     b->completion_handler = nxt_http_chunk_buf_completion;
220 
221     b->parent = in;
222     in->retain++;
223     b->mem.pos = p;
224     b->mem.start = p;
225 
226     if (hcp->chunk_size < size) {
227         p += hcp->chunk_size;
228         hcp->pos = p;
229 
230         b->mem.free = p;
231         b->mem.end = p;
232 
233         return NXT_HTTP_CHUNK_END;
234     }
235 
236     b->mem.free = in->mem.free;
237     b->mem.end = in->mem.free;
238 
239     hcp->chunk_size -= size;
240 
241     if (hcp->chunk_size == 0) {
242         return NXT_HTTP_CHUNK_END_ON_BORDER;
243     }
244 
245     return NXT_HTTP_CHUNK_MIDDLE;
246 }
247 
248 
249 static void
250 nxt_http_chunk_buf_completion(nxt_task_t *task, void *obj, void *data)
251 {
252     nxt_mp_t   *mp;
253     nxt_buf_t  *b, *next, *parent;
254 
255     b = obj;
256     parent = data;
257 
258     nxt_debug(task, "buf completion: %p %p", b, b->mem.start);
259 
260     nxt_assert(data == b->parent);
261 
262     do {
263         next = b->next;
264         parent = b->parent;
265         mp = b->data;
266 
267         nxt_mp_free(mp, b);
268         nxt_mp_release(mp);
269 
270         nxt_buf_parent_completion(task, parent);
271 
272         b = next;
273     } while (b != NULL);
274 }
275