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