1 
2 /*
3  * Copyright (C) Igor Sysoev
4  * Copyright (C) NGINX, Inc.
5  */
6 
7 #include <nxt_main.h>
8 
9 
10 #define NXT_FASTCGI_DATA_MIDDLE         0
11 #define NXT_FASTCGI_DATA_END_ON_BORDER  1
12 #define NXT_FASTCGI_DATA_END            2
13 
14 
15 static nxt_int_t nxt_fastcgi_buffer(nxt_fastcgi_parse_t *fp, nxt_buf_t ***tail,
16     nxt_buf_t *in);
17 
18 
19 void
nxt_fastcgi_record_parse(nxt_task_t * task,nxt_fastcgi_parse_t * fp,nxt_buf_t * in)20 nxt_fastcgi_record_parse(nxt_task_t *task, nxt_fastcgi_parse_t *fp,
21     nxt_buf_t *in)
22 {
23     u_char        ch;
24     nxt_int_t     ret, stream;
25     nxt_buf_t     *b, *nb, **tail[2];
26     const char    *msg;
27     enum {
28         sw_fastcgi_version = 0,
29         sw_fastcgi_type,
30         sw_fastcgi_request_id_high,
31         sw_fastcgi_request_id_low,
32         sw_fastcgi_content_length_high,
33         sw_fastcgi_content_length_low,
34         sw_fastcgi_padding_length,
35         sw_fastcgi_reserved,
36         sw_fastcgi_data,
37         sw_fastcgi_padding,
38         sw_fastcgi_end_request,
39     } state;
40 
41     fp->out[0] = NULL;
42     fp->out[1] = NULL;
43 
44     tail[0] = &fp->out[0];
45     tail[1] = &fp->out[1];
46 
47     state = fp->state;
48 
49     for (b = in; b != NULL; b = b->next) {
50 
51         if (nxt_buf_is_sync(b)) {
52             **tail = b;
53             *tail = &b->next;
54             continue;
55         }
56 
57         fp->pos = b->mem.pos;
58 
59         while (fp->pos < b->mem.free) {
60             /*
61              * The sw_fastcgi_data state is tested outside the
62              * switch to preserve fp->pos and to not touch memory.
63              */
64             if (state == sw_fastcgi_data) {
65 
66                 /*
67                  * fp->type here can be only NXT_FASTCGI_STDOUT
68                  * or NXT_FASTCGI_STDERR.  NXT_FASTCGI_END_REQUEST
69                  * is tested in sw_fastcgi_reserved.
70                  */
71                 stream = fp->type - NXT_FASTCGI_STDOUT;
72 
73                 ret = nxt_fastcgi_buffer(fp, &tail[stream], b);
74 
75                 if (ret == NXT_FASTCGI_DATA_MIDDLE) {
76                     goto next;
77                 }
78 
79                 if (nxt_slow_path(ret == NXT_ERROR)) {
80                     fp->error = 1;
81                     goto done;
82                 }
83 
84                 if (fp->padding == 0) {
85                     state = sw_fastcgi_version;
86 
87                 } else {
88                     state = sw_fastcgi_padding;
89                 }
90 
91                 if (ret == NXT_FASTCGI_DATA_END_ON_BORDER) {
92                     goto next;
93                 }
94 
95                 /* ret == NXT_FASTCGI_DATA_END */
96             }
97 
98             ch = *fp->pos++;
99 
100             nxt_thread_log_debug("fastcgi record byte: %02Xd", ch);
101 
102             switch (state) {
103 
104             case sw_fastcgi_version:
105                 if (nxt_fast_path(ch == 1)) {
106                     state = sw_fastcgi_type;
107                     continue;
108                 }
109 
110                 msg = "unsupported FastCGI protocol version";
111                 goto fastcgi_error;
112 
113             case sw_fastcgi_type:
114                 switch (ch) {
115                 case NXT_FASTCGI_STDOUT:
116                 case NXT_FASTCGI_STDERR:
117                 case NXT_FASTCGI_END_REQUEST:
118                     fp->type = ch;
119                     state = sw_fastcgi_request_id_high;
120                     continue;
121                 default:
122                     msg = "invalid FastCGI record type";
123                     goto fastcgi_error;
124                 }
125 
126             case sw_fastcgi_request_id_high:
127                 /* FastCGI multiplexing is not supported. */
128                 if (nxt_fast_path(ch == 0)) {
129                     state = sw_fastcgi_request_id_low;
130                     continue;
131                 }
132 
133                 msg = "unexpected FastCGI request ID high byte";
134                 goto fastcgi_error;
135 
136             case sw_fastcgi_request_id_low:
137                 if (nxt_fast_path(ch == 1)) {
138                     state = sw_fastcgi_content_length_high;
139                     continue;
140                 }
141 
142                 msg = "unexpected FastCGI request ID low byte";
143                 goto fastcgi_error;
144 
145             case sw_fastcgi_content_length_high:
146                 fp->length = ch << 8;
147                 state = sw_fastcgi_content_length_low;
148                 continue;
149 
150             case sw_fastcgi_content_length_low:
151                 fp->length |= ch;
152                 state = sw_fastcgi_padding_length;
153                 continue;
154 
155             case sw_fastcgi_padding_length:
156                 fp->padding = ch;
157                 state = sw_fastcgi_reserved;
158                 continue;
159 
160             case sw_fastcgi_reserved:
161                 nxt_thread_log_debug("fastcgi record type:%d "
162                                      "length:%uz padding:%d",
163                                      fp->type, fp->length, fp->padding);
164 
165                 if (nxt_fast_path(fp->type != NXT_FASTCGI_END_REQUEST)) {
166                     state = sw_fastcgi_data;
167                     continue;
168                 }
169 
170                 state = sw_fastcgi_end_request;
171                 continue;
172 
173             case sw_fastcgi_data:
174                 /*
175                  * This state is processed before the switch.
176                  * It added here just to suppress a warning.
177                  */
178                 continue;
179 
180             case sw_fastcgi_padding:
181                 /*
182                  * No special fast processing of padding
183                  * because it usually takes just 1-7 bytes.
184                  */
185                 fp->padding--;
186 
187                 if (fp->padding == 0) {
188                     nxt_thread_log_debug("fastcgi record end");
189                     state = sw_fastcgi_version;
190                 }
191                 continue;
192 
193             case sw_fastcgi_end_request:
194                 /* Just skip 8 bytes of END_REQUEST. */
195                 fp->length--;
196 
197                 if (fp->length != 0) {
198                     continue;
199                 }
200 
201                 fp->done = 1;
202 
203                 nxt_thread_log_debug("fastcgi end request");
204 
205                 goto done;
206             }
207         }
208 
209         if (b->retain == 0) {
210             /* No record data was found in a buffer. */
211             nxt_thread_current_work_queue_add(task->thread,
212                                               b->completion_handler,
213                                               task, b, b->parent);
214         }
215 
216     next:
217 
218         continue;
219     }
220 
221     fp->state = state;
222 
223     return;
224 
225 fastcgi_error:
226 
227     nxt_thread_log_error(NXT_LOG_ERR, "upstream sent %s: %d", msg, ch);
228 
229     fp->fastcgi_error = 1;
230 
231 done:
232 
233     nb = fp->last_buf(fp);
234 
235     if (nxt_fast_path(nb != NULL)) {
236         *tail[0] = nb;
237 
238     } else {
239         fp->error = 1;
240     }
241 
242     // STUB: fp->fastcgi_error = 1;
243     // STUB: fp->error = 1;
244 
245     return;
246 }
247 
248 
249 static nxt_int_t
nxt_fastcgi_buffer(nxt_fastcgi_parse_t * fp,nxt_buf_t *** tail,nxt_buf_t * in)250 nxt_fastcgi_buffer(nxt_fastcgi_parse_t *fp, nxt_buf_t ***tail, nxt_buf_t *in)
251 {
252     u_char     *p;
253     size_t     size;
254     nxt_buf_t  *b;
255 
256     if (fp->length == 0) {
257         return NXT_FASTCGI_DATA_END;
258     }
259 
260     p = fp->pos;
261     size = in->mem.free - p;
262 
263     if (fp->length >= size && in->retain == 0) {
264         /*
265          * Use original buffer if the buffer is lesser than or equal to
266          * FastCGI record size and this is the first record in the buffer.
267          */
268         in->mem.pos = p;
269         **tail = in;
270         *tail = &in->next;
271 
272     } else {
273         b = nxt_buf_mem_alloc(fp->mem_pool, 0, 0);
274         if (nxt_slow_path(b == NULL)) {
275             return NXT_ERROR;
276         }
277 
278         **tail = b;
279         *tail = &b->next;
280 
281         b->parent = in;
282         in->retain++;
283         b->mem.pos = p;
284         b->mem.start = p;
285 
286         if (fp->length < size) {
287             p += fp->length;
288             fp->pos = p;
289 
290             b->mem.free = p;
291             b->mem.end = p;
292 
293             return NXT_FASTCGI_DATA_END;
294         }
295 
296         b->mem.free = in->mem.free;
297         b->mem.end = in->mem.free;
298     }
299 
300     fp->length -= size;
301 
302     if (fp->length == 0) {
303         return NXT_FASTCGI_DATA_END_ON_BORDER;
304     }
305 
306     return NXT_FASTCGI_DATA_MIDDLE;
307 }
308