1 /*
2 * libwebsockets - small server side websockets and web server implementation
3 *
4 * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 */
24
25 #if !defined(_GNU_SOURCE)
26 #define _GNU_SOURCE
27 #endif
28
29 #include "private-lib-core.h"
30
31 #if defined(WIN32) || defined(_WIN32)
32 #else
33 #include <sys/wait.h>
34 #endif
35
36 static const char *hex = "0123456789ABCDEF";
37
38 static int
urlencode(const char * in,int inlen,char * out,int outlen)39 urlencode(const char *in, int inlen, char *out, int outlen)
40 {
41 char *start = out, *end = out + outlen;
42
43 while (inlen-- && out < end - 4) {
44 if ((*in >= 'A' && *in <= 'Z') ||
45 (*in >= 'a' && *in <= 'z') ||
46 (*in >= '0' && *in <= '9') ||
47 *in == '-' ||
48 *in == '_' ||
49 *in == '.' ||
50 *in == '~') {
51 *out++ = *in++;
52 continue;
53 }
54 if (*in == ' ') {
55 *out++ = '+';
56 in++;
57 continue;
58 }
59 *out++ = '%';
60 *out++ = hex[(*in) >> 4];
61 *out++ = hex[(*in++) & 15];
62 }
63 *out = '\0';
64
65 if (out >= end - 4)
66 return -1;
67
68 return out - start;
69 }
70
71 int
lws_cgi(struct lws * wsi,const char * const * exec_array,int script_uri_path_len,int timeout_secs,const struct lws_protocol_vhost_options * mp_cgienv)72 lws_cgi(struct lws *wsi, const char * const *exec_array,
73 int script_uri_path_len, int timeout_secs,
74 const struct lws_protocol_vhost_options *mp_cgienv)
75 {
76 struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
77 struct lws_spawn_piped_info info;
78 char *env_array[30], cgi_path[500], e[1024], *p = e,
79 *end = p + sizeof(e) - 1, tok[256], *t, *sum, *sumend;
80 struct lws_cgi *cgi;
81 int n, m = 0, i, uritok = -1, c;
82
83 /*
84 * give the master wsi a cgi struct
85 */
86
87 wsi->http.cgi = lws_zalloc(sizeof(*wsi->http.cgi), "new cgi");
88 if (!wsi->http.cgi) {
89 lwsl_err("%s: OOM\n", __func__);
90 return -1;
91 }
92
93 wsi->http.cgi->response_code = HTTP_STATUS_OK;
94
95 cgi = wsi->http.cgi;
96 cgi->wsi = wsi; /* set cgi's owning wsi */
97 sum = cgi->summary;
98 sumend = sum + strlen(cgi->summary) - 1;
99
100 if (timeout_secs)
101 lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, timeout_secs);
102
103 /* the cgi stdout is always sending us http1.x header data first */
104 wsi->hdr_state = LCHS_HEADER;
105
106 /* add us to the pt list of active cgis */
107 lwsl_debug("%s: adding cgi %p to list\n", __func__, wsi->http.cgi);
108 cgi->cgi_list = pt->http.cgi_list;
109 pt->http.cgi_list = cgi;
110
111 sum += lws_snprintf(sum, sumend - sum, "%s ", exec_array[0]);
112
113 if (0) {
114 char *pct = lws_hdr_simple_ptr(wsi,
115 WSI_TOKEN_HTTP_CONTENT_ENCODING);
116
117 if (pct && !strcmp(pct, "gzip"))
118 wsi->http.cgi->gzip_inflate = 1;
119 }
120
121 /* prepare his CGI env */
122
123 n = 0;
124
125 if (lws_is_ssl(wsi)) {
126 env_array[n++] = p;
127 p += lws_snprintf(p, end - p, "HTTPS=ON");
128 p++;
129 }
130
131 if (wsi->http.ah) {
132 static const unsigned char meths[] = {
133 WSI_TOKEN_GET_URI,
134 WSI_TOKEN_POST_URI,
135 #if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS)
136 WSI_TOKEN_OPTIONS_URI,
137 WSI_TOKEN_PUT_URI,
138 WSI_TOKEN_PATCH_URI,
139 WSI_TOKEN_DELETE_URI,
140 #endif
141 WSI_TOKEN_CONNECT,
142 WSI_TOKEN_HEAD_URI,
143 #ifdef LWS_WITH_HTTP2
144 WSI_TOKEN_HTTP_COLON_PATH,
145 #endif
146 };
147 static const char * const meth_names[] = {
148 "GET", "POST",
149 #if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS)
150 "OPTIONS", "PUT", "PATCH", "DELETE",
151 #endif
152 "CONNECT", "HEAD", ":path"
153 };
154
155 if (script_uri_path_len >= 0)
156 for (m = 0; m < (int)LWS_ARRAY_SIZE(meths); m++)
157 if (lws_hdr_total_length(wsi, meths[m]) >=
158 script_uri_path_len) {
159 uritok = meths[m];
160 break;
161 }
162
163 if (script_uri_path_len < 0 && uritok < 0)
164 goto bail;
165 // if (script_uri_path_len < 0)
166 // uritok = 0;
167
168 if (m >= 0) {
169 env_array[n++] = p;
170 if (m < (int)LWS_ARRAY_SIZE(meths) - 1) {
171 p += lws_snprintf(p, end - p,
172 "REQUEST_METHOD=%s",
173 meth_names[m]);
174 sum += lws_snprintf(sum, sumend - sum, "%s ",
175 meth_names[m]);
176 #if defined(LWS_ROLE_H2)
177 } else {
178 p += lws_snprintf(p, end - p,
179 "REQUEST_METHOD=%s",
180 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD));
181 sum += lws_snprintf(sum, sumend - sum, "%s ",
182 lws_hdr_simple_ptr(wsi,
183 WSI_TOKEN_HTTP_COLON_METHOD));
184 #endif
185 }
186 p++;
187 }
188
189 if (uritok >= 0)
190 sum += lws_snprintf(sum, sumend - sum, "%s ",
191 lws_hdr_simple_ptr(wsi, uritok));
192
193 env_array[n++] = p;
194 p += lws_snprintf(p, end - p, "QUERY_STRING=");
195 /* dump the individual URI Arg parameters */
196 m = 0;
197 while (script_uri_path_len >= 0) {
198 i = lws_hdr_copy_fragment(wsi, tok, sizeof(tok),
199 WSI_TOKEN_HTTP_URI_ARGS, m);
200 if (i < 0)
201 break;
202 t = tok;
203 while (*t && *t != '=' && p < end - 4)
204 *p++ = *t++;
205 if (*t == '=')
206 *p++ = *t++;
207 i = urlencode(t, i- (t - tok), p, end - p);
208 if (i > 0) {
209 p += i;
210 *p++ = '&';
211 }
212 m++;
213 }
214 if (m)
215 p--;
216 *p++ = '\0';
217
218 if (uritok >= 0) {
219 strcpy(cgi_path, "REQUEST_URI=");
220 c = lws_hdr_copy(wsi, cgi_path + 12,
221 sizeof(cgi_path) - 12, uritok);
222 if (c < 0)
223 goto bail;
224
225 cgi_path[sizeof(cgi_path) - 1] = '\0';
226 env_array[n++] = cgi_path;
227 }
228
229 sum += lws_snprintf(sum, sumend - sum, "%s", env_array[n - 1]);
230
231 if (script_uri_path_len >= 0) {
232 env_array[n++] = p;
233 p += lws_snprintf(p, end - p, "PATH_INFO=%s",
234 cgi_path + 12 + script_uri_path_len);
235 p++;
236 }
237 }
238 #if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS)
239 if (script_uri_path_len >= 0 &&
240 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER)) {
241 env_array[n++] = p;
242 p += lws_snprintf(p, end - p, "HTTP_REFERER=%s",
243 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_REFERER));
244 p++;
245 }
246 #endif
247 if (script_uri_path_len >= 0 &&
248 lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) {
249 env_array[n++] = p;
250 p += lws_snprintf(p, end - p, "HTTP_HOST=%s",
251 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST));
252 p++;
253 }
254 if (script_uri_path_len >= 0 &&
255 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
256 env_array[n++] = p;
257 p += lws_snprintf(p, end - p, "HTTP_COOKIE=");
258 m = lws_hdr_copy(wsi, p, end - p, WSI_TOKEN_HTTP_COOKIE);
259 if (m > 0)
260 p += lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE);
261 *p++ = '\0';
262 }
263 #if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS)
264 if (script_uri_path_len >= 0 &&
265 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT)) {
266 env_array[n++] = p;
267 p += lws_snprintf(p, end - p, "HTTP_USER_AGENT=%s",
268 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_USER_AGENT));
269 p++;
270 }
271 #endif
272 if (script_uri_path_len >= 0 &&
273 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING)) {
274 env_array[n++] = p;
275 p += lws_snprintf(p, end - p, "HTTP_CONTENT_ENCODING=%s",
276 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING));
277 p++;
278 }
279 if (script_uri_path_len >= 0 &&
280 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT)) {
281 env_array[n++] = p;
282 p += lws_snprintf(p, end - p, "HTTP_ACCEPT=%s",
283 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT));
284 p++;
285 }
286 if (script_uri_path_len >= 0 &&
287 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING)) {
288 env_array[n++] = p;
289 p += lws_snprintf(p, end - p, "HTTP_ACCEPT_ENCODING=%s",
290 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING));
291 p++;
292 }
293 if (script_uri_path_len >= 0 &&
294 uritok == WSI_TOKEN_POST_URI) {
295 if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
296 env_array[n++] = p;
297 p += lws_snprintf(p, end - p, "CONTENT_TYPE=%s",
298 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE));
299 p++;
300 }
301 if (!wsi->http.cgi->gzip_inflate &&
302 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
303 env_array[n++] = p;
304 p += lws_snprintf(p, end - p, "CONTENT_LENGTH=%s",
305 lws_hdr_simple_ptr(wsi,
306 WSI_TOKEN_HTTP_CONTENT_LENGTH));
307 p++;
308 }
309
310 if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH))
311 wsi->http.cgi->post_in_expected =
312 atoll(lws_hdr_simple_ptr(wsi,
313 WSI_TOKEN_HTTP_CONTENT_LENGTH));
314 }
315
316
317 env_array[n++] = p;
318 p += lws_snprintf(p, end - p, "PATH=/bin:/usr/bin:/usr/local/bin:/var/www/cgi-bin");
319 p++;
320
321 env_array[n++] = p;
322 p += lws_snprintf(p, end - p, "SCRIPT_PATH=%s", exec_array[0]);
323 p++;
324
325 while (mp_cgienv) {
326 env_array[n++] = p;
327 p += lws_snprintf(p, end - p, "%s=%s", mp_cgienv->name,
328 mp_cgienv->value);
329 if (!strcmp(mp_cgienv->name, "GIT_PROJECT_ROOT")) {
330 wsi->http.cgi->implied_chunked = 1;
331 wsi->http.cgi->explicitly_chunked = 1;
332 }
333 lwsl_info(" Applying mount-specific cgi env '%s'\n",
334 env_array[n - 1]);
335 p++;
336 mp_cgienv = mp_cgienv->next;
337 }
338
339 env_array[n++] = p;
340 p += lws_snprintf(p, end - p, "SERVER_SOFTWARE=lws");
341 p++;
342
343 env_array[n] = NULL;
344
345 #if 0
346 for (m = 0; m < n; m++)
347 lwsl_notice(" %s\n", env_array[m]);
348 #endif
349
350 memset(&info, 0, sizeof(info));
351 info.env_array = env_array;
352 info.exec_array = exec_array;
353 info.max_log_lines = 20000;
354 info.opt_parent = wsi;
355 info.timeout_us = 5 * 60 * LWS_US_PER_SEC;
356 info.tsi = wsi->tsi;
357 info.vh = wsi->vhost;
358 info.ops = &role_ops_cgi;
359 info.plsp = &wsi->http.cgi->lsp;
360
361 /*
362 * Actually having made the env, as a cgi we don't need the ah
363 * any more
364 */
365 if (script_uri_path_len >= 0) {
366 lws_header_table_detach(wsi, 0);
367 info.disable_ctrlc = 1;
368 }
369
370 wsi->http.cgi->lsp = lws_spawn_piped(&info);
371 if (!wsi->http.cgi->lsp) {
372 lwsl_err("%s: spawn failed\n", __func__);
373 goto bail;
374 }
375
376 /* we are the parent process */
377
378 wsi->context->count_cgi_spawned++;
379
380 /* inform cgi owner of the child PID */
381 n = user_callback_handle_rxflow(wsi->protocol->callback, wsi,
382 LWS_CALLBACK_CGI_PROCESS_ATTACH,
383 wsi->user_space, NULL, cgi->lsp->child_pid);
384 (void)n;
385
386 return 0;
387
388 bail:
389 lws_free_set_NULL(wsi->http.cgi);
390
391 lwsl_err("%s: failed\n", __func__);
392
393 return -1;
394 }
395
396 /* we have to parse out these headers in the CGI output */
397
398 static const char * const significant_hdr[SIGNIFICANT_HDR_COUNT] = {
399 "content-length: ",
400 "location: ",
401 "status: ",
402 "transfer-encoding: chunked",
403 "content-encoding: gzip",
404 };
405
406 enum header_recode {
407 HR_NAME,
408 HR_WHITESPACE,
409 HR_ARG,
410 HR_CRLF,
411 };
412
413 int
lws_cgi_write_split_stdout_headers(struct lws * wsi)414 lws_cgi_write_split_stdout_headers(struct lws *wsi)
415 {
416 int n, m, cmd;
417 unsigned char buf[LWS_PRE + 4096], *start = &buf[LWS_PRE], *p = start,
418 *end = &buf[sizeof(buf) - 1 - LWS_PRE], *name,
419 *value = NULL;
420 char c, hrs;
421
422 if (!wsi->http.cgi)
423 return -1;
424
425 while (wsi->hdr_state != LHCS_PAYLOAD) {
426 /*
427 * We have to separate header / finalize and payload chunks,
428 * since they need to be handled separately
429 */
430 switch (wsi->hdr_state) {
431 case LHCS_RESPONSE:
432 lwsl_debug("LHCS_RESPONSE: issuing response %d\n",
433 wsi->http.cgi->response_code);
434 if (lws_add_http_header_status(wsi,
435 wsi->http.cgi->response_code,
436 &p, end))
437 return 1;
438 if (!wsi->http.cgi->explicitly_chunked &&
439 !wsi->http.cgi->content_length &&
440 lws_add_http_header_by_token(wsi,
441 WSI_TOKEN_HTTP_TRANSFER_ENCODING,
442 (unsigned char *)"chunked", 7, &p, end))
443 return 1;
444 if (!(wsi->mux_substream))
445 if (lws_add_http_header_by_token(wsi,
446 WSI_TOKEN_CONNECTION,
447 (unsigned char *)"close", 5,
448 &p, end))
449 return 1;
450 n = lws_write(wsi, start, p - start,
451 LWS_WRITE_HTTP_HEADERS | LWS_WRITE_NO_FIN);
452
453 /*
454 * so we have a bunch of http/1 style ascii headers
455 * starting from wsi->http.cgi->headers_buf through
456 * wsi->http.cgi->headers_pos. These are OK for http/1
457 * connections, but they're no good for http/2 conns.
458 *
459 * Let's redo them at headers_pos forward using the
460 * correct coding for http/1 or http/2
461 */
462 if (!wsi->mux_substream)
463 goto post_hpack_recode;
464
465 p = wsi->http.cgi->headers_start;
466 wsi->http.cgi->headers_start =
467 wsi->http.cgi->headers_pos;
468 wsi->http.cgi->headers_dumped =
469 wsi->http.cgi->headers_start;
470 hrs = HR_NAME;
471 name = buf;
472
473 while (p < wsi->http.cgi->headers_start) {
474 switch (hrs) {
475 case HR_NAME:
476 /*
477 * in http/2 upper-case header names
478 * are illegal. So convert to lower-
479 * case.
480 */
481 if (name - buf > 64)
482 return -1;
483 if (*p != ':') {
484 if (*p >= 'A' && *p <= 'Z')
485 *name++ = (*p++) +
486 ('a' - 'A');
487 else
488 *name++ = *p++;
489 } else {
490 p++;
491 *name++ = '\0';
492 value = name;
493 hrs = HR_WHITESPACE;
494 }
495 break;
496 case HR_WHITESPACE:
497 if (*p == ' ') {
498 p++;
499 break;
500 }
501 hrs = HR_ARG;
502 /* fallthru */
503 case HR_ARG:
504 if (name > end - 64)
505 return -1;
506
507 if (*p != '\x0a' && *p != '\x0d') {
508 *name++ = *p++;
509 break;
510 }
511 hrs = HR_CRLF;
512 /* fallthru */
513 case HR_CRLF:
514 if ((*p != '\x0a' && *p != '\x0d') ||
515 p + 1 == wsi->http.cgi->headers_start) {
516 *name = '\0';
517 if ((strcmp((const char *)buf,
518 "transfer-encoding")
519 )) {
520 lwsl_debug("+ %s: %s\n",
521 buf, value);
522 if (
523 lws_add_http_header_by_name(wsi, buf,
524 (unsigned char *)value, name - value,
525 (unsigned char **)&wsi->http.cgi->headers_pos,
526 (unsigned char *)wsi->http.cgi->headers_end))
527 return 1;
528 hrs = HR_NAME;
529 name = buf;
530 break;
531 }
532 }
533 p++;
534 break;
535 }
536 }
537 post_hpack_recode:
538 /* finalize cached headers before dumping them */
539 if (lws_finalize_http_header(wsi,
540 (unsigned char **)&wsi->http.cgi->headers_pos,
541 (unsigned char *)wsi->http.cgi->headers_end)) {
542
543 lwsl_notice("finalize failed\n");
544 return -1;
545 }
546
547 wsi->hdr_state = LHCS_DUMP_HEADERS;
548 wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_HEADERS;
549 lws_callback_on_writable(wsi);
550 /* back to the loop for writeability again */
551 return 0;
552
553 case LHCS_DUMP_HEADERS:
554
555 n = wsi->http.cgi->headers_pos -
556 wsi->http.cgi->headers_dumped;
557 if (n > 512)
558 n = 512;
559
560 lwsl_debug("LHCS_DUMP_HEADERS: %d\n", n);
561
562 cmd = LWS_WRITE_HTTP_HEADERS_CONTINUATION;
563 if (wsi->http.cgi->headers_dumped + n !=
564 wsi->http.cgi->headers_pos) {
565 lwsl_notice("adding no fin flag\n");
566 cmd |= LWS_WRITE_NO_FIN;
567 }
568
569 m = lws_write(wsi,
570 (unsigned char *)wsi->http.cgi->headers_dumped,
571 n, cmd);
572 if (m < 0) {
573 lwsl_debug("%s: write says %d\n", __func__, m);
574 return -1;
575 }
576 wsi->http.cgi->headers_dumped += n;
577 if (wsi->http.cgi->headers_dumped ==
578 wsi->http.cgi->headers_pos) {
579 wsi->hdr_state = LHCS_PAYLOAD;
580 lws_free_set_NULL(wsi->http.cgi->headers_buf);
581 lwsl_debug("freed cgi headers\n");
582 } else {
583 wsi->reason_bf |=
584 LWS_CB_REASON_AUX_BF__CGI_HEADERS;
585 lws_callback_on_writable(wsi);
586 }
587
588 /* writeability becomes uncertain now we wrote
589 * something, we must return to the event loop
590 */
591 return 0;
592 }
593
594 if (!wsi->http.cgi->headers_buf) {
595 /* if we don't already have a headers buf, cook one */
596 n = 2048;
597 if (wsi->mux_substream)
598 n = 4096;
599 wsi->http.cgi->headers_buf = lws_malloc(n + LWS_PRE,
600 "cgi hdr buf");
601 if (!wsi->http.cgi->headers_buf) {
602 lwsl_err("OOM\n");
603 return -1;
604 }
605
606 lwsl_debug("allocated cgi hdrs\n");
607 wsi->http.cgi->headers_start =
608 wsi->http.cgi->headers_buf + LWS_PRE;
609 wsi->http.cgi->headers_pos = wsi->http.cgi->headers_start;
610 wsi->http.cgi->headers_dumped = wsi->http.cgi->headers_pos;
611 wsi->http.cgi->headers_end =
612 wsi->http.cgi->headers_buf + n - 1;
613
614 for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) {
615 wsi->http.cgi->match[n] = 0;
616 wsi->http.cgi->lp = 0;
617 }
618 }
619
620 n = lws_get_socket_fd(wsi->http.cgi->lsp->stdwsi[LWS_STDOUT]);
621 if (n < 0)
622 return -1;
623 n = read(n, &c, 1);
624 if (n < 0) {
625 if (errno != EAGAIN) {
626 lwsl_debug("%s: read says %d\n", __func__, n);
627 return -1;
628 }
629 else
630 n = 0;
631
632 if (wsi->http.cgi->headers_pos >=
633 wsi->http.cgi->headers_end - 4) {
634 lwsl_notice("CGI hdrs > buf size\n");
635
636 return -1;
637 }
638 }
639 if (!n)
640 goto agin;
641
642 lwsl_debug("-- 0x%02X %c %d %d\n", (unsigned char)c, c,
643 wsi->http.cgi->match[1], wsi->hdr_state);
644 if (!c)
645 return -1;
646 switch (wsi->hdr_state) {
647 case LCHS_HEADER:
648 hdr:
649 for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) {
650 /*
651 * significant headers with
652 * numeric decimal payloads
653 */
654 if (!significant_hdr[n][wsi->http.cgi->match[n]] &&
655 (c >= '0' && c <= '9') &&
656 wsi->http.cgi->lp < (int)sizeof(wsi->http.cgi->l) - 1) {
657 wsi->http.cgi->l[wsi->http.cgi->lp++] = c;
658 wsi->http.cgi->l[wsi->http.cgi->lp] = '\0';
659 switch (n) {
660 case SIGNIFICANT_HDR_CONTENT_LENGTH:
661 wsi->http.cgi->content_length =
662 atoll(wsi->http.cgi->l);
663 break;
664 case SIGNIFICANT_HDR_STATUS:
665 wsi->http.cgi->response_code =
666 atol(wsi->http.cgi->l);
667 lwsl_debug("Status set to %d\n",
668 wsi->http.cgi->response_code);
669 break;
670 default:
671 break;
672 }
673 }
674 /* hits up to the NUL are sticky until next hdr */
675 if (significant_hdr[n][wsi->http.cgi->match[n]]) {
676 if (tolower(c) ==
677 significant_hdr[n][wsi->http.cgi->match[n]])
678 wsi->http.cgi->match[n]++;
679 else
680 wsi->http.cgi->match[n] = 0;
681 }
682 }
683
684 /* some cgi only send us \x0a for EOL */
685 if (c == '\x0a') {
686 wsi->hdr_state = LCHS_SINGLE_0A;
687 *wsi->http.cgi->headers_pos++ = '\x0d';
688 }
689 *wsi->http.cgi->headers_pos++ = c;
690 if (c == '\x0d')
691 wsi->hdr_state = LCHS_LF1;
692
693 if (wsi->hdr_state != LCHS_HEADER &&
694 !significant_hdr[SIGNIFICANT_HDR_TRANSFER_ENCODING]
695 [wsi->http.cgi->match[
696 SIGNIFICANT_HDR_TRANSFER_ENCODING]]) {
697 lwsl_info("cgi produced chunked\n");
698 wsi->http.cgi->explicitly_chunked = 1;
699 }
700
701 /* presence of Location: mandates 302 retcode */
702 if (wsi->hdr_state != LCHS_HEADER &&
703 !significant_hdr[SIGNIFICANT_HDR_LOCATION][
704 wsi->http.cgi->match[SIGNIFICANT_HDR_LOCATION]]) {
705 lwsl_debug("CGI: Location hdr seen\n");
706 wsi->http.cgi->response_code = 302;
707 }
708 break;
709 case LCHS_LF1:
710 *wsi->http.cgi->headers_pos++ = c;
711 if (c == '\x0a') {
712 wsi->hdr_state = LCHS_CR2;
713 break;
714 }
715 /* we got \r[^\n]... it's unreasonable */
716 lwsl_debug("%s: funny CRLF 0x%02X\n", __func__,
717 (unsigned char)c);
718 return -1;
719
720 case LCHS_CR2:
721 if (c == '\x0d') {
722 /* drop the \x0d */
723 wsi->hdr_state = LCHS_LF2;
724 break;
725 }
726 wsi->hdr_state = LCHS_HEADER;
727 for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++)
728 wsi->http.cgi->match[n] = 0;
729 wsi->http.cgi->lp = 0;
730 goto hdr;
731
732 case LCHS_LF2:
733 case LCHS_SINGLE_0A:
734 m = wsi->hdr_state;
735 if (c == '\x0a') {
736 lwsl_debug("Content-Length: %lld\n",
737 (unsigned long long)
738 wsi->http.cgi->content_length);
739 wsi->hdr_state = LHCS_RESPONSE;
740 /*
741 * drop the \0xa ... finalize
742 * will add it if needed (HTTP/1)
743 */
744 break;
745 }
746 if (m == LCHS_LF2)
747 /* we got \r\n\r[^\n]... unreasonable */
748 return -1;
749 /* we got \x0anext header, it's reasonable */
750 *wsi->http.cgi->headers_pos++ = c;
751 wsi->hdr_state = LCHS_HEADER;
752 for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++)
753 wsi->http.cgi->match[n] = 0;
754 wsi->http.cgi->lp = 0;
755 break;
756 case LHCS_PAYLOAD:
757 break;
758 }
759
760 agin:
761 /* ran out of input, ended the hdrs, or filled up the hdrs buf */
762 if (!n || wsi->hdr_state == LHCS_PAYLOAD)
763 return 0;
764 }
765
766 /* payload processing */
767
768 m = !wsi->http.cgi->implied_chunked && !wsi->mux_substream &&
769 // !wsi->http.cgi->explicitly_chunked &&
770 !wsi->http.cgi->content_length;
771 n = lws_get_socket_fd(wsi->http.cgi->lsp->stdwsi[LWS_STDOUT]);
772 if (n < 0)
773 return -1;
774 n = read(n, start, sizeof(buf) - LWS_PRE);
775
776 if (n < 0 && errno != EAGAIN) {
777 lwsl_debug("%s: stdout read says %d\n", __func__, n);
778 return -1;
779 }
780 if (n > 0) {
781 // lwsl_hexdump_notice(buf, n);
782
783 if (!wsi->mux_substream && m) {
784 char chdr[LWS_HTTP_CHUNK_HDR_SIZE];
785 m = lws_snprintf(chdr, LWS_HTTP_CHUNK_HDR_SIZE - 3,
786 "%X\x0d\x0a", n);
787 memmove(start + m, start, n);
788 memcpy(start, chdr, m);
789 memcpy(start + m + n, "\x0d\x0a", 2);
790 n += m + 2;
791 }
792
793
794 #if defined(LWS_WITH_HTTP2)
795 if (wsi->mux_substream) {
796 struct lws *nwsi = lws_get_network_wsi(wsi);
797
798 __lws_set_timeout(wsi,
799 PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31);
800
801 if (!nwsi->immortal_substream_count)
802 __lws_set_timeout(nwsi,
803 PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31);
804 }
805 #endif
806
807 cmd = LWS_WRITE_HTTP;
808 if (wsi->http.cgi->content_length_seen + n ==
809 wsi->http.cgi->content_length)
810 cmd = LWS_WRITE_HTTP_FINAL;
811
812 m = lws_write(wsi, (unsigned char *)start, n, cmd);
813 //lwsl_notice("write %d\n", m);
814 if (m < 0) {
815 lwsl_debug("%s: stdout write says %d\n", __func__, m);
816 return -1;
817 }
818 wsi->http.cgi->content_length_seen += n;
819 } else {
820
821 if (!wsi->mux_substream && m) {
822 uint8_t term[LWS_PRE + 6];
823
824 lwsl_notice("%s: sent trailer\n", __func__);
825 memcpy(term + LWS_PRE, (uint8_t *)"0\x0d\x0a\x0d\x0a", 5);
826
827 if (lws_write(wsi, term + LWS_PRE, 5,
828 LWS_WRITE_HTTP_FINAL) != 5)
829 return -1;
830
831 wsi->http.cgi->cgi_transaction_over = 1;
832
833 return 0;
834 }
835
836 if (wsi->cgi_stdout_zero_length) {
837 lwsl_debug("%s: stdout is POLLHUP'd\n", __func__);
838 if (wsi->mux_substream)
839 m = lws_write(wsi, (unsigned char *)start, 0,
840 LWS_WRITE_HTTP_FINAL);
841 else
842 return -1;
843 return 1;
844 }
845 wsi->cgi_stdout_zero_length = 1;
846 }
847 return 0;
848 }
849
850 int
lws_cgi_kill(struct lws * wsi)851 lws_cgi_kill(struct lws *wsi)
852 {
853 struct lws_cgi_args args;
854 pid_t pid;
855 int n, m;
856
857 lwsl_debug("%s: %p\n", __func__, wsi);
858
859 if (!wsi->http.cgi || !wsi->http.cgi->lsp)
860 return 0;
861
862 pid = wsi->http.cgi->lsp->child_pid;
863
864 args.stdwsi = &wsi->http.cgi->lsp->stdwsi[0];
865 lws_spawn_piped_kill_child_process(wsi->http.cgi->lsp);
866 /* that has invalidated and NULL'd wsi->http.cgi->lsp */
867
868 if (pid != -1) {
869 m = wsi->http.cgi->being_closed;
870 n = user_callback_handle_rxflow(wsi->protocol->callback, wsi,
871 LWS_CALLBACK_CGI_TERMINATED,
872 wsi->user_space, (void *)&args,
873 pid);
874 if (n && !m)
875 lws_close_free_wsi(wsi, 0, "lws_cgi_kill");
876 }
877
878 return 0;
879 }
880
881 int
lws_cgi_kill_terminated(struct lws_context_per_thread * pt)882 lws_cgi_kill_terminated(struct lws_context_per_thread *pt)
883 {
884 struct lws_cgi **pcgi, *cgi = NULL;
885 int status, n = 1;
886
887 while (n > 0) {
888 /* find finished guys but don't reap yet */
889 n = waitpid(-1, &status, WNOHANG);
890 if (n <= 0)
891 continue;
892 lwsl_debug("%s: observed PID %d terminated\n", __func__, n);
893
894 pcgi = &pt->http.cgi_list;
895
896 /* check all the subprocesses on the cgi list */
897 while (*pcgi) {
898 /* get the next one first as list may change */
899 cgi = *pcgi;
900 pcgi = &(*pcgi)->cgi_list;
901
902 if (cgi->lsp->child_pid <= 0)
903 continue;
904
905 /* finish sending cached headers */
906 if (cgi->headers_buf)
907 continue;
908
909 /* wait for stdout to be drained */
910 if (cgi->content_length > cgi->content_length_seen)
911 continue;
912
913 if (cgi->content_length) {
914 lwsl_debug("%s: wsi %p: expected content "
915 "length seen: %lld\n", __func__,
916 cgi->wsi,
917 (unsigned long long)cgi->content_length_seen);
918 }
919
920 /* reap it */
921 waitpid(n, &status, WNOHANG);
922 /*
923 * he's already terminated so no need for kill()
924 * but we should do the terminated cgi callback
925 * and close him if he's not already closing
926 */
927 if (n == cgi->lsp->child_pid) {
928 lwsl_debug("%s: found PID %d on cgi list\n",
929 __func__, n);
930
931 if (!cgi->content_length) {
932 /*
933 * well, if he sends chunked...
934 * give him 2s after the
935 * cgi terminated to send buffered
936 */
937 cgi->chunked_grace++;
938 continue;
939 }
940
941 /* defeat kill() */
942 cgi->lsp->child_pid = 0;
943 lws_cgi_kill(cgi->wsi);
944
945 break;
946 }
947 cgi = NULL;
948 }
949 /* if not found on the cgi list, as he's one of ours, reap */
950 if (!cgi) {
951 lwsl_debug("%s: reading PID %d although no cgi match\n",
952 __func__, n);
953 waitpid(n, &status, WNOHANG);
954 }
955 }
956
957 pcgi = &pt->http.cgi_list;
958
959 /* check all the subprocesses on the cgi list */
960 while (*pcgi) {
961 /* get the next one first as list may change */
962 cgi = *pcgi;
963 pcgi = &(*pcgi)->cgi_list;
964
965 if (cgi->lsp->child_pid <= 0)
966 continue;
967
968 /* we deferred killing him after reaping his PID */
969 if (cgi->chunked_grace) {
970 cgi->chunked_grace++;
971 if (cgi->chunked_grace < 2)
972 continue;
973 goto finish_him;
974 }
975
976 /* finish sending cached headers */
977 if (cgi->headers_buf)
978 continue;
979
980 /* wait for stdout to be drained */
981 if (cgi->content_length > cgi->content_length_seen)
982 continue;
983
984 if (cgi->content_length)
985 lwsl_debug("%s: wsi %p: expected "
986 "content len seen: %lld\n", __func__,
987 cgi->wsi,
988 (unsigned long long)cgi->content_length_seen);
989
990 /* reap it */
991 if (waitpid(cgi->lsp->child_pid, &status, WNOHANG) > 0) {
992
993 if (!cgi->content_length) {
994 /*
995 * well, if he sends chunked...
996 * give him 2s after the
997 * cgi terminated to send buffered
998 */
999 cgi->chunked_grace++;
1000 continue;
1001 }
1002 finish_him:
1003 lwsl_debug("%s: found PID %d on cgi list\n",
1004 __func__, cgi->lsp->child_pid);
1005
1006 /* defeat kill() */
1007 cgi->lsp->child_pid = 0;
1008 lws_cgi_kill(cgi->wsi);
1009
1010 break;
1011 }
1012 }
1013
1014 return 0;
1015 }
1016
1017 struct lws *
lws_cgi_get_stdwsi(struct lws * wsi,enum lws_enum_stdinouterr ch)1018 lws_cgi_get_stdwsi(struct lws *wsi, enum lws_enum_stdinouterr ch)
1019 {
1020 if (!wsi->http.cgi)
1021 return NULL;
1022
1023 return wsi->http.cgi->lsp->stdwsi[ch];
1024 }
1025
1026 void
lws_cgi_remove_and_kill(struct lws * wsi)1027 lws_cgi_remove_and_kill(struct lws *wsi)
1028 {
1029 struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
1030 struct lws_cgi **pcgi = &pt->http.cgi_list;
1031
1032 /* remove us from the cgi list */
1033 lwsl_debug("%s: remove cgi %p from list\n", __func__, wsi->http.cgi);
1034 while (*pcgi) {
1035 if (*pcgi == wsi->http.cgi) {
1036 /* drop us from the pt cgi list */
1037 *pcgi = (*pcgi)->cgi_list;
1038 break;
1039 }
1040 pcgi = &(*pcgi)->cgi_list;
1041 }
1042 if (wsi->http.cgi->headers_buf) {
1043 lwsl_debug("close: freed cgi headers\n");
1044 lws_free_set_NULL(wsi->http.cgi->headers_buf);
1045 }
1046 /* we have a cgi going, we must kill it */
1047 wsi->http.cgi->being_closed = 1;
1048 lws_cgi_kill(wsi);
1049 }
1050