File Coverage

c/fcgi.c
Criterion Covered Total %
statement 283 286 98.9
branch 166 204 81.3
condition n/a
subroutine n/a
pod n/a
total 449 490 91.6


line stmt bran cond sub pod time code
1             #define FCGI_BEGIN_REQUEST 1
2             #define FCGI_ABORT_REQUEST 2
3             #define FCGI_END_REQUEST 3
4             #define FCGI_PARAMS 4
5             #define FCGI_STDIN 5
6             #define FCGI_STDOUT 6
7             #define FCGI_STDERR 7
8             #define FCGI_DATA 8
9             #define FCGI_GET_VALUES 9
10             #define FCGI_GET_VALUES_RESULT 10
11             #define FCGI_UNKNOWN_TYPE 11
12              
13             #define FUFE_OK 0
14             #define FUFE_EOF -1 /* unexpected protocol-level EOF */
15             #define FUFE_IO -2
16             #define FUFE_PROTO -3
17             #define FUFE_PLEN -4
18             #define FUFE_CLEN -5
19             #define FUFE_ABORT -6 /* explicit abort or client-level EOF */
20             #define FUFE_NOREQ -7 /* protocol-level EOF before we received anything */
21             #define FUFE_SEND -8 /* error in send() */
22              
23             #define FUFCGI_MAX_DATA 65535
24              
25             typedef struct {
26             SV *self;
27             int fd;
28             int maxproc;
29             int keepconn;
30              
31             int reqid;
32             HV *headers;
33             HV *params;
34              
35             /* Single buffer for reading & writing, we only do one thing at a time */
36             char buf[8 + FUFCGI_MAX_DATA + 255]; /* fits a maximum-length fcgi record */
37             int len; /* total number of bytes in the buffer */
38             int off; /* number of bytes consumed */
39             } fufcgi;
40              
41             typedef struct {
42             unsigned char type;
43             unsigned short id;
44             int len;
45             char *data;
46             } fufcgi_rec;
47              
48              
49             /* Incremental param length & name parser */
50             typedef enum {
51             FUFC_INIT, FUFC_L1, FUFC_L2, FUFC_L3,
52             FUFC_V0, FUFC_V1, FUFC_V2, FUFC_V3,
53             FUFC_N0, FUFC_NX
54             } fufcgi_paramstate;
55              
56             typedef struct {
57             int namelen;
58             int vallen;
59             int state;
60             int namerd;
61             char *name;
62             char namebuf[128]; /* We don't support longer param names */
63             } fufcgi_param;
64              
65             /* Returns NULL on error or ptr to value (or 'end' if !done) */
66 95           static char *fufcgi_param_parse(fufcgi_param *p, char *buf, char *end) {
67 384 100         while (buf < end) {
68 357           switch (p->state) {
69 77           case FUFC_INIT:
70 77           p->vallen = p->namerd = 0;
71 77 100         if (*buf & 0x80) {
72 6           p->namelen = (*buf & 0x1f) << 24;
73 6           p->state = FUFC_L1;
74             } else {
75 71           p->namelen = *buf;
76 71           p->state = FUFC_V0;
77             }
78 77           break;
79 6           case FUFC_L1:
80 6           p->namelen |= ((unsigned char)*buf) << 16;
81 6           p->state = FUFC_L2;
82 6           break;
83 6           case FUFC_L2:
84 6           p->namelen |= ((unsigned char)*buf) << 8;
85 6           p->state = FUFC_L3;
86 6           break;
87 6           case FUFC_L3:
88 6           p->namelen |= (unsigned char)*buf;
89 6           p->state = FUFC_V0;
90 6 100         if (p->namelen > (int)sizeof(p->namebuf)) return NULL;
91 3           break;
92 74           case FUFC_V0:
93 74 100         if (*buf & 0x80) {
94 6           p->vallen = (*buf & 0x1f) << 24;
95 6           p->state = FUFC_V1;
96             } else {
97 68           p->vallen = *buf;
98 68 100         p->state = p->namelen ? FUFC_N0 : FUFC_INIT;
99             }
100 74           break;
101 6           case FUFC_V1:
102 6           p->vallen |= ((unsigned char)*buf) << 16;
103 6 100         if (p->vallen) return NULL; /* Let's just disallow param values > 64 KiB */
104 3           p->state = FUFC_V2;
105 3           break;
106 3           case FUFC_V2:
107 3           p->vallen |= ((unsigned char)*buf) << 8;
108 3           p->state = FUFC_V3;
109 3           break;
110 3           case FUFC_V3:
111 3           p->vallen |= (unsigned char)*buf;
112 3           p->state = FUFC_N0;
113 3           break;
114 65           case FUFC_N0:
115 65 100         if (p->namelen <= end - buf) {
116 56           p->name = buf;
117 56           p->state = FUFC_INIT;
118 56           return buf + p->namelen;
119             } else {
120 9           p->name = p->namebuf;
121 9           p->name[0] = *buf;
122 9           p->namerd = 1;
123 9           p->state = FUFC_NX;
124             }
125 9           break;
126 111           case FUFC_NX:
127 111           p->name[p->namerd++] = *buf;
128 111 100         if (p->namerd == p->namelen) {
129 6           p->state = FUFC_INIT;
130 6           return buf + 1;
131             }
132 105           break;
133             }
134 289           buf++;
135             }
136 27           return buf;
137             }
138              
139 447           static int fufcgi_fill(fufcgi *ctx, int len) {
140 447 100         if ((int)sizeof(ctx->buf) - ctx->off < len) {
141 4           memmove(ctx->buf, ctx->buf+ctx->off, ctx->len - ctx->off);
142 4           ctx->len -= ctx->off;
143 4           ctx->off = 0;
144             }
145 506 100         while (ctx->len - ctx->off < len) {
146 65           ssize_t r = read(ctx->fd, ctx->buf+ctx->len, sizeof(ctx->buf) - ctx->len);
147 65 100         if (r <= 0) return r == 0 ? FUFE_EOF : FUFE_IO;
    50          
148 59           ctx->len += r;
149             }
150 441           return FUFE_OK;
151             }
152              
153 228           static int fufcgi_read_record(fufcgi *ctx, fufcgi_rec *rec) {
154             int r;
155 228 100         if ((r = fufcgi_fill(ctx, 8)) != FUFE_OK) return r;
156              
157 222 100         if (ctx->buf[ctx->off] != 1) return FUFE_PROTO; /* version */
158 219           rec->type = ctx->buf[ctx->off+1];
159 219           rec->id = fu_frombeU(16, ctx->buf+ctx->off+2);
160 219           rec->len = fu_frombeU(16, ctx->buf+ctx->off+4);
161 219           int pad = (unsigned char)ctx->buf[ctx->off+6];
162 219           ctx->off += 8;
163              
164 219 50         if ((r = fufcgi_fill(ctx, rec->len + pad)) != FUFE_OK) return r;
165 219           rec->data = ctx->buf + ctx->off;
166 219           ctx->off += rec->len + pad;
167 219           return FUFE_OK;
168             }
169              
170             /* Unbuffered write of a single record, first 8 bytes of 'buf' are filled out
171             * by this function, record contents must come after. */
172 31           static int fufcgi_write_record(fufcgi *ctx, fufcgi_rec *hdr, char *buf) {
173 31           buf[0] = 1;
174 31           buf[1] = hdr->type;
175 31           fu_tobeU(16, buf+2, hdr->id);
176 31           fu_tobeU(16, buf+4, hdr->len);
177 31           buf[6] = 0;
178 31           buf[7] = 0;
179 31           int len = hdr->len + 8;
180 56 100         while (len > 0) {
181 31           int r = send(ctx->fd, buf, len, MSG_NOSIGNAL);
182 31 100         if (r <= 0) return FUFE_SEND;
183 25           buf += r;
184 25           len -= r;
185             }
186 25           return FUFE_OK;
187             }
188              
189 3           static int fufcgi_handle_values(fufcgi *ctx, fufcgi_rec *rec, char *buf) {
190 3           int reslen = 8;
191 3           char *param = rec->data;
192 3           char *end = rec->data + rec->len;
193             fufcgi_param p;
194 3           p.state = FUFC_INIT;
195              
196 15 100         while (param < end) {
197 12 50         if ((param = fufcgi_param_parse(&p, param, end)) == NULL) return FUFE_PLEN;
198 12 50         if (p.state != FUFC_INIT) return FUFE_PROTO;
199 12 50         if (p.vallen > end - param) return FUFE_PROTO;
200 12 50         if (reslen >= 100) return FUFE_PROTO; /* implies requested params were duplicated */
201              
202 12 100         if (p.namelen == 14 && memcmp(p.name, "FCGI_MAX_CONNS", 14) == 0) {
    50          
203 3           memcpy(buf+reslen, "\x0e\0FCGI_MAX_CONNS", 16);
204 3           int l = sprintf(buf+reslen+16, "%d", ctx->maxproc);
205 3           buf[reslen+1] = l;
206 3           reslen += 16 + l;
207              
208 9 100         } else if (p.namelen == 13 && memcmp(p.name, "FCGI_MAX_REQS", 13) == 0) {
    50          
209 3           memcpy(buf+reslen, "\x0d\0FCGI_MAX_REQS", 15);
210 3           int l = sprintf(buf+reslen+15, "%d", ctx->maxproc);
211 3           buf[reslen+1] = l;
212 3           reslen += 15 + l;
213              
214 6 100         } else if (p.namelen == 15 && memcmp(p.name, "FCGI_MPXS_CONNS", 15) == 0) {
    50          
215 3           memcpy(buf+reslen, "\x0f\1FCGI_MPXS_CONNS0", 18);
216 3           reslen += 18;
217             }
218              
219 12           param += p.vallen;
220             }
221 3           rec->type = FCGI_GET_VALUES_RESULT;
222 3           rec->len = reslen - 8;
223 3           return fufcgi_write_record(ctx, rec, buf);
224             }
225              
226             /* Read a PARAMS/STDIN/ABORT record corresponding to the current id, starts
227             * reading a new request if id=0. */
228 169           static int fufcgi_read_req_record(fufcgi *ctx, fufcgi_rec *rec) {
229             int r;
230             char tmp[128]; /* Large enough for a FCGI_GET_VALUES_RESULT */
231             while (1) {
232 228 100         if ((r = fufcgi_read_record(ctx, rec)) != FUFE_OK) return r == FUFE_EOF && ctx->len == 0 ? FUFE_NOREQ : r;
    100          
    100          
233              
234 219           switch (rec->type) {
235 157           case FCGI_PARAMS:
236             case FCGI_STDIN:
237             case FCGI_ABORT_REQUEST:
238 157 50         if (rec->id != ctx->reqid) return FUFE_PROTO;
239 157           return FUFE_OK;
240 56           case FCGI_BEGIN_REQUEST:
241 56 50         if (!rec->id || rec->id == ctx->reqid) return FUFE_PROTO;
    50          
242 56 50         if (rec->len != 8) return FUFE_PROTO;
243 56           ctx->keepconn = rec->data[2] & 1;
244 56 50         if (rec->data[0] != 0 || rec->data[1] != 1) { /* FCGI_RESPONDER */
    100          
245 6           memcpy(tmp+8, "\0\0\0\0\3\0\0\0", 8); /* FCGI_UNKNOWN_ROLE */
246 6           rec->type = FCGI_END_REQUEST;
247 6           rec->len = 8;
248 6 100         if ((r = fufcgi_write_record(ctx, rec, tmp)) != FUFE_OK) return r;
249 3 50         if (!ctx->keepconn) return FUFE_EOF;
250 50 100         } else if (ctx->reqid) {
251 3           memcpy(tmp+8, "\0\0\0\0\1\0\0\0", 8); /* FCGI_CANT_MPX_CONN */
252 3           rec->type = FCGI_END_REQUEST;
253 3           rec->len = 8;
254 3 50         if ((r = fufcgi_write_record(ctx, rec, tmp)) != FUFE_OK) return r;
255 3 50         if (!ctx->keepconn) return FUFE_EOF;
256             } else {
257 47           ctx->reqid = rec->id;
258             }
259 53           break;
260 3           case FCGI_GET_VALUES:
261 3 50         if (rec->id) return FUFE_PROTO;
262 3 50         if ((r = fufcgi_handle_values(ctx, rec, tmp)) != FUFE_OK) return r;
263 3           break;
264 3           default:
265 3           memset(tmp+8, 0, 8);
266 3           tmp[8] = rec->type;
267 3           rec->type = FCGI_UNKNOWN_TYPE;
268 3           rec->len = 8;
269 3           rec->id = 0;
270 3 50         if ((r = fufcgi_write_record(ctx, rec, tmp)) != FUFE_OK) return r;
271 3           break;
272             }
273             }
274             }
275              
276 56           static int fufcgi_read_params(pTHX_ fufcgi *ctx, fufcgi_rec *rec) {
277             int r;
278             fufcgi_param p;
279 56           p.state = FUFC_INIT;
280              
281 56           SV *valsv = NULL;
282 56           char *val = NULL;
283 56           int valleft = 0;
284              
285 68           while (1) {
286 124 100         if ((r = fufcgi_read_req_record(ctx, rec)) != FUFE_OK) return r;
287 112 100         if (rec->type == FCGI_ABORT_REQUEST) return FUFE_OK;
288 109 100         if (rec->type != FCGI_PARAMS) return FUFE_PROTO;
289 106 100         if (rec->len == 0) return p.state != FUFC_INIT || valleft ? FUFE_PROTO : FUFE_OK;
    100          
    100          
290              
291 74           char *buf = rec->data;
292 74           char *end = rec->data + rec->len;
293 177 100         while (buf < end) {
294 133 100         if (valleft) {
295 50           r = valleft > end - buf ? end - buf : valleft;
296 50 100         if (val) {
297 41           memcpy(val, buf, r);
298 41           val += r;
299             }
300 50           valleft -= r;
301 50           buf += r;
302 50 100         if (val && !valleft) {
    100          
303 35           *val = 0;
304 35           SvCUR_set(valsv, p.vallen);
305             }
306 50           continue;
307             }
308 83 100         if ((buf = fufcgi_param_parse(&p, buf, end)) == NULL) return FUFE_PLEN;
309 77 100         if (p.state != FUFC_INIT) break;
310              
311 53           valsv = NULL;
312 53           val = NULL;
313 53           valleft = p.vallen;
314              
315             /* https://www.rfc-editor.org/rfc/rfc3875 */
316              
317             /* Request header */
318 53 100         if (p.namelen > 5 && memcmp(p.name, "HTTP_", 5) == 0) {
    100          
319 12           p.namelen -= 5;
320 12           p.name += 5;
321 111 100         for (r=0; r
322 99 100         p.name[r] = p.name[r] == '_' ? '-' : p.name[r] >= 'A' && p.name[r] <= 'Z' ? p.name[r] | 0x20 : p.name[r];
    50          
    100          
323 12 100         if (!(p.namelen == 14 && memcmp(p.name, "content-length", 14) == 0)
    50          
324 6 50         && !(p.namelen == 12 && memcmp(p.name, "content-type", 12) == 0)) {
    0          
325 6           valsv = newSV(p.vallen+1);
326 6           hv_store(ctx->headers, p.name, p.namelen, valsv, 0);
327             }
328              
329 41 100         } else if (p.namelen == 14 && memcmp(p.name, "CONTENT_LENGTH", 14) == 0) {
    100          
330 17           valsv = newSV(p.vallen+1);
331 17           hv_stores(ctx->headers, "content-length", valsv);
332              
333 24 100         } else if (p.namelen == 12 && memcmp(p.name, "CONTENT_TYPE", 12) == 0) {
    100          
334 6           valsv = newSV(p.vallen+1);
335 6           hv_stores(ctx->headers, "content-type", valsv);
336              
337 18 100         } else if (p.namelen == 11 && memcmp(p.name, "REMOTE_ADDR", 11) == 0) {
    100          
338 3           valsv = newSV(p.vallen+1);
339 3           hv_stores(ctx->params, "ip", valsv);
340              
341 15 100         } else if (p.namelen == 12 && memcmp(p.name, "QUERY_STRING", 12) == 0) {
    50          
342 3           valsv = newSV(p.vallen+1);
343 3           hv_stores(ctx->params, "qs", valsv);
344              
345 12 100         } else if (p.namelen == 14 && memcmp(p.name, "REQUEST_METHOD", 14) == 0) {
    50          
346 3           valsv = newSV(p.vallen+1);
347 3           hv_stores(ctx->params, "method", valsv);
348              
349             /* Not in rfc3875; there's no standardized parameter for the URI,
350             * but every FastCGI-capable web server includes this one */
351 9 100         } else if (p.namelen == 11 && memcmp(p.name, "REQUEST_URI", 11) == 0) {
    50          
352 3           valsv = newSV(p.vallen+1);
353 3           hv_stores(ctx->params, "path", valsv);
354              
355             } else { /* ignore */ }
356              
357 53 100         if (valsv) {
358 41           SvPOK_only(valsv);
359 41           val = SvPVX(valsv);
360 41           *val = 0; /* in case vallen = 0 */
361             }
362             }
363             }
364             }
365              
366 56           static int fufcgi_read_req(pTHX_ fufcgi *ctx, SV *headers, SV *params) {
367 56 50         if (ctx->reqid) fu_confess("Invalid attempt to read FastCGI request before finishing the previous one");
368             fufcgi_rec rec;
369             int r;
370              
371 56           ctx->off = ctx->len = 0;
372 56           ctx->headers = (HV *)SvRV(headers);
373 56           ctx->params = (HV *)SvRV(params);
374 56 100         if ((r = fufcgi_read_params(aTHX_ ctx, &rec)) != FUFE_OK) return r;
375              
376 29           int stdinlen = 0;
377 29           SV **contentlength = hv_fetchs(ctx->headers, "content-length", 0);
378 29 100         if (contentlength && *contentlength) {
    50          
379 17           UV uv = 0;
380 17           char *v = SvPV_nolen(*contentlength);
381 17 100         if (*v && !grok_atoUV(v, &uv, NULL)) return FUFE_CLEN;
    50          
382 17 50         if (uv >= INT_MAX) return FUFE_CLEN;
383 17           stdinlen = uv;
384             }
385              
386 29           SV *sv = newSV(stdinlen+1);
387 29           hv_stores(ctx->params, "body", sv);
388 29           SvPOK_only(sv);
389 29           char *stdinbuf = SvPVX(sv);
390 29           int stdinleft = stdinlen;
391              
392             while (1) {
393 74 100         if (rec.type == FCGI_ABORT_REQUEST) return FUFE_ABORT;
394 71 100         else if (rec.type == FCGI_PARAMS) {
395 26 50         if (rec.len != 0) return FUFE_PROTO;
396 45 50         } else if (rec.type == FCGI_STDIN) {
397 45 100         if (rec.len == 0) {
398 26           *stdinbuf = 0;
399 26           SvCUR_set(sv, stdinlen - stdinleft);
400 26 100         return stdinleft == 0 ? FUFE_OK : FUFE_ABORT;
401             }
402 19 50         if (rec.len > stdinleft) return FUFE_PROTO;
403 19           memcpy(stdinbuf, rec.data, rec.len);
404 19           stdinbuf += rec.len;
405 19           stdinleft -= rec.len;
406             } else {
407 0           return FUFE_PROTO;
408             }
409 45 50         if ((r = fufcgi_read_req_record(ctx, &rec)) != FUFE_OK) return r;
410             }
411             }
412              
413 8           static void fufcgi_flush(pTHX_ fufcgi *ctx) {
414             fufcgi_rec hdr;
415 8 100         if (ctx->len > 0) {
416 5           hdr.len = ctx->len;
417 5           hdr.type = FCGI_STDOUT;
418 5           hdr.id = ctx->reqid;
419 5 50         if (fufcgi_write_record(ctx, &hdr, ctx->buf) != FUFE_OK)
420 0           croak("%s\n", strerror(errno));
421 5           ctx->len = 0;
422             }
423 8           }
424              
425 11           static void fufcgi_print(pTHX_ fufcgi *ctx, const char *buf, int len) {
426             int r;
427 23 100         while (len > 0) {
428 12           r = len > FUFCGI_MAX_DATA - ctx->len ? FUFCGI_MAX_DATA - ctx->len : len;
429 12           memcpy(ctx->buf+8+ctx->len, buf, r);
430 12           ctx->len += r;
431 12           len -= r;
432 12           buf += r;
433 12 100         if (ctx->len >= FUFCGI_MAX_DATA) fufcgi_flush(aTHX_ ctx);
434             }
435 11           }
436              
437 7           static void fufcgi_done(pTHX_ fufcgi *ctx) {
438             fufcgi_rec hdr;
439 7           fufcgi_flush(aTHX_ ctx);
440              
441 7           hdr.len = 0;
442 7           hdr.type = FCGI_STDOUT;
443 7           hdr.id = ctx->reqid;
444 7 100         if (fufcgi_write_record(ctx, &hdr, ctx->buf) != FUFE_OK)
445 3           croak("%s\n", strerror(errno));
446              
447 4           memcpy(ctx->buf+8, "\0\0\0\0\0\0\0\0", 8); /* FCGI_REQUEST_COMPLETE */
448 4           hdr.type = FCGI_END_REQUEST;
449 4           hdr.len = 8;
450 4 50         if (fufcgi_write_record(ctx, &hdr, ctx->buf) != FUFE_OK)
451 0           croak("%s\n", strerror(errno));
452              
453 4           ctx->reqid = ctx->len = ctx->off = 0;
454 4           }