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