2 * libwebsockets - CGI management
4 * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation:
9 * version 2.1 of the License.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
24 #include "core/private.h"
26 #if defined(WIN32) || defined(_WIN32)
31 static const char *hex = "0123456789ABCDEF";
34 urlencode(const char *in, int inlen, char *out, int outlen)
36 char *start = out, *end = out + outlen;
38 while (inlen-- && out < end - 4) {
39 if ((*in >= 'A' && *in <= 'Z') ||
40 (*in >= 'a' && *in <= 'z') ||
41 (*in >= '0' && *in <= '9') ||
55 *out++ = hex[(*in) >> 4];
56 *out++ = hex[(*in++) & 15];
67 lws_create_basic_wsi(struct lws_context *context, int tsi)
71 if (!context->vhost_list)
74 if ((unsigned int)context->pt[tsi].fds_count ==
75 context->fd_limit_per_thread - 1) {
76 lwsl_err("no space for new conn\n");
80 new_wsi = lws_zalloc(sizeof(struct lws), "new wsi");
81 if (new_wsi == NULL) {
82 lwsl_err("Out of memory for new connection\n");
87 new_wsi->context = context;
88 new_wsi->pending_timeout = NO_PENDING_TIMEOUT;
89 new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
91 /* initialize the instance struct */
93 lws_role_transition(new_wsi, 0, LRS_ESTABLISHED, &role_ops_cgi);
95 new_wsi->hdr_parsing_completed = 0;
96 new_wsi->position_in_fds_table = LWS_NO_FDS_POS;
99 * these can only be set once the protocol is known
100 * we set an unestablished connection's protocol pointer
101 * to the start of the defauly vhost supported list, so it can look
102 * for matching ones during the handshake
104 new_wsi->protocol = context->vhost_list->protocols;
105 new_wsi->user_space = NULL;
106 new_wsi->desc.sockfd = LWS_SOCK_INVALID;
107 context->count_wsi_allocated++;
112 LWS_VISIBLE LWS_EXTERN int
113 lws_cgi(struct lws *wsi, const char * const *exec_array,
114 int script_uri_path_len, int timeout_secs,
115 const struct lws_protocol_vhost_options *mp_cgienv)
117 struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
118 char *env_array[30], cgi_path[500], e[1024], *p = e,
119 *end = p + sizeof(e) - 1, tok[256], *t, *sum, *sumend;
121 int n, m = 0, i, uritok = -1, c;
124 * give the master wsi a cgi struct
127 wsi->http.cgi = lws_zalloc(sizeof(*wsi->http.cgi), "new cgi");
128 if (!wsi->http.cgi) {
129 lwsl_err("%s: OOM\n", __func__);
133 wsi->http.cgi->response_code = HTTP_STATUS_OK;
136 cgi->wsi = wsi; /* set cgi's owning wsi */
138 sumend = sum + strlen(cgi->summary) - 1;
140 for (n = 0; n < 3; n++) {
141 cgi->pipe_fds[n][0] = -1;
142 cgi->pipe_fds[n][1] = -1;
145 /* create pipes for [stdin|stdout] and [stderr] */
147 for (n = 0; n < 3; n++)
148 if (pipe(cgi->pipe_fds[n]) == -1)
151 /* create cgi wsis for each stdin/out/err fd */
153 for (n = 0; n < 3; n++) {
154 cgi->stdwsi[n] = lws_create_basic_wsi(wsi->context, wsi->tsi);
155 if (!cgi->stdwsi[n]) {
156 lwsl_err("%s: unable to create cgi stdwsi\n", __func__);
159 cgi->stdwsi[n]->cgi_channel = n;
160 lws_vhost_bind_wsi(wsi->vhost, cgi->stdwsi[n]);
162 lwsl_debug("%s: cgi stdwsi %p: pipe idx %d -> fd %d / %d\n", __func__,
163 cgi->stdwsi[n], n, cgi->pipe_fds[n][!!(n == 0)],
164 cgi->pipe_fds[n][!(n == 0)]);
166 /* read side is 0, stdin we want the write side, others read */
167 cgi->stdwsi[n]->desc.sockfd = cgi->pipe_fds[n][!!(n == 0)];
168 if (fcntl(cgi->pipe_fds[n][!!(n == 0)], F_SETFL,
170 lwsl_err("%s: setting NONBLOCK failed\n", __func__);
175 for (n = 0; n < 3; n++) {
176 if (wsi->context->event_loop_ops->accept)
177 if (wsi->context->event_loop_ops->accept(cgi->stdwsi[n]))
180 if (__insert_wsi_socket_into_fds(wsi->context, cgi->stdwsi[n]))
182 cgi->stdwsi[n]->parent = wsi;
183 cgi->stdwsi[n]->sibling_list = wsi->child_list;
184 wsi->child_list = cgi->stdwsi[n];
187 if (lws_change_pollfd(cgi->stdwsi[LWS_STDIN], LWS_POLLIN, LWS_POLLOUT))
189 if (lws_change_pollfd(cgi->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN))
191 if (lws_change_pollfd(cgi->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN))
194 lwsl_debug("%s: fds in %d, out %d, err %d\n", __func__,
195 cgi->stdwsi[LWS_STDIN]->desc.sockfd,
196 cgi->stdwsi[LWS_STDOUT]->desc.sockfd,
197 cgi->stdwsi[LWS_STDERR]->desc.sockfd);
200 lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, timeout_secs);
202 /* the cgi stdout is always sending us http1.x header data first */
203 wsi->hdr_state = LCHS_HEADER;
205 /* add us to the pt list of active cgis */
206 lwsl_debug("%s: adding cgi %p to list\n", __func__, wsi->http.cgi);
207 cgi->cgi_list = pt->http.cgi_list;
208 pt->http.cgi_list = cgi;
210 sum += lws_snprintf(sum, sumend - sum, "%s ", exec_array[0]);
213 char *pct = lws_hdr_simple_ptr(wsi,
214 WSI_TOKEN_HTTP_CONTENT_ENCODING);
216 if (pct && !strcmp(pct, "gzip"))
217 wsi->http.cgi->gzip_inflate = 1;
220 /* prepare his CGI env */
225 env_array[n++] = "HTTPS=ON";
227 static const unsigned char meths[] = {
230 WSI_TOKEN_OPTIONS_URI,
233 WSI_TOKEN_DELETE_URI,
236 #ifdef LWS_WITH_HTTP2
237 WSI_TOKEN_HTTP_COLON_PATH,
240 static const char * const meth_names[] = {
241 "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE",
242 "CONNECT", "HEAD", ":path"
245 if (script_uri_path_len >= 0)
246 for (m = 0; m < (int)LWS_ARRAY_SIZE(meths); m++)
247 if (lws_hdr_total_length(wsi, meths[m]) >=
248 script_uri_path_len) {
253 if (script_uri_path_len < 0 && uritok < 0)
255 // if (script_uri_path_len < 0)
261 p += lws_snprintf(p, end - p,
264 sum += lws_snprintf(sum, sumend - sum, "%s ",
267 p += lws_snprintf(p, end - p,
269 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD));
270 sum += lws_snprintf(sum, sumend - sum, "%s ",
271 lws_hdr_simple_ptr(wsi,
272 WSI_TOKEN_HTTP_COLON_METHOD));
278 sum += lws_snprintf(sum, sumend - sum, "%s ",
279 lws_hdr_simple_ptr(wsi, uritok));
282 p += lws_snprintf(p, end - p, "QUERY_STRING=");
283 /* dump the individual URI Arg parameters */
285 while (script_uri_path_len >= 0) {
286 i = lws_hdr_copy_fragment(wsi, tok, sizeof(tok),
287 WSI_TOKEN_HTTP_URI_ARGS, m);
291 while (*t && *t != '=' && p < end - 4)
295 i = urlencode(t, i- (t - tok), p, end - p);
307 strcpy(cgi_path, "REQUEST_URI=");
308 c = lws_hdr_copy(wsi, cgi_path + 12,
309 sizeof(cgi_path) - 12, uritok);
313 cgi_path[sizeof(cgi_path) - 1] = '\0';
314 env_array[n++] = cgi_path;
317 sum += lws_snprintf(sum, sumend - sum, "%s", env_array[n - 1]);
319 if (script_uri_path_len >= 0) {
321 p += lws_snprintf(p, end - p, "PATH_INFO=%s",
322 cgi_path + 12 + script_uri_path_len);
326 if (script_uri_path_len >= 0 &&
327 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER)) {
329 p += lws_snprintf(p, end - p, "HTTP_REFERER=%s",
330 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_REFERER));
333 if (script_uri_path_len >= 0 &&
334 lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) {
336 p += lws_snprintf(p, end - p, "HTTP_HOST=%s",
337 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST));
340 if (script_uri_path_len >= 0 &&
341 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
343 p += lws_snprintf(p, end - p, "HTTP_COOKIE=");
344 m = lws_hdr_copy(wsi, p, end - p, WSI_TOKEN_HTTP_COOKIE);
346 p += lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE);
349 if (script_uri_path_len >= 0 &&
350 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT)) {
352 p += lws_snprintf(p, end - p, "HTTP_USER_AGENT=%s",
353 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_USER_AGENT));
356 if (script_uri_path_len >= 0 &&
357 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING)) {
359 p += lws_snprintf(p, end - p, "HTTP_CONTENT_ENCODING=%s",
360 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING));
363 if (script_uri_path_len >= 0 &&
364 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT)) {
366 p += lws_snprintf(p, end - p, "HTTP_ACCEPT=%s",
367 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT));
370 if (script_uri_path_len >= 0 &&
371 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING)) {
373 p += lws_snprintf(p, end - p, "HTTP_ACCEPT_ENCODING=%s",
374 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING));
377 if (script_uri_path_len >= 0 &&
378 uritok == WSI_TOKEN_POST_URI) {
379 if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
381 p += lws_snprintf(p, end - p, "CONTENT_TYPE=%s",
382 lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE));
385 if (!wsi->http.cgi->gzip_inflate &&
386 lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
388 p += lws_snprintf(p, end - p, "CONTENT_LENGTH=%s",
389 lws_hdr_simple_ptr(wsi,
390 WSI_TOKEN_HTTP_CONTENT_LENGTH));
394 if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH))
395 wsi->http.cgi->post_in_expected =
396 atoll(lws_hdr_simple_ptr(wsi,
397 WSI_TOKEN_HTTP_CONTENT_LENGTH));
401 env_array[n++] = "PATH=/bin:/usr/bin:/usr/local/bin:/var/www/cgi-bin";
404 p += lws_snprintf(p, end - p, "SCRIPT_PATH=%s", exec_array[0]) + 1;
408 p += lws_snprintf(p, end - p, "%s=%s", mp_cgienv->name,
410 if (!strcmp(mp_cgienv->name, "GIT_PROJECT_ROOT")) {
411 wsi->http.cgi->implied_chunked = 1;
412 wsi->http.cgi->explicitly_chunked = 1;
414 lwsl_info(" Applying mount-specific cgi env '%s'\n",
417 mp_cgienv = mp_cgienv->next;
420 env_array[n++] = "SERVER_SOFTWARE=libwebsockets";
424 for (m = 0; m < n; m++)
425 lwsl_notice(" %s\n", env_array[m]);
429 * Actually having made the env, as a cgi we don't need the ah
432 if (script_uri_path_len >= 0)
433 lws_header_table_detach(wsi, 0);
435 /* we are ready with the redirection pipes... run the thing */
436 #if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
442 lwsl_err("fork failed, errno %d", errno);
446 #if defined(__linux__)
447 prctl(PR_SET_PDEATHSIG, SIGTERM);
449 if (script_uri_path_len >= 0)
450 /* stops non-daemonized main processess getting SIGINT
455 /* we are the parent process */
456 wsi->context->count_cgi_spawned++;
457 lwsl_info("%s: cgi %p spawned PID %d\n", __func__,
461 * close: stdin:r, stdout:w, stderr:w
462 * hide from other forks: stdin:w, stdout:r, stderr:r
464 for (n = 0; n < 3; n++) {
465 lws_plat_apply_FD_CLOEXEC(cgi->pipe_fds[n][!!(n == 0)]);
466 close(cgi->pipe_fds[n][!(n == 0)]);
469 /* inform cgi owner of the child PID */
470 n = user_callback_handle_rxflow(wsi->protocol->callback, wsi,
471 LWS_CALLBACK_CGI_PROCESS_ATTACH,
472 wsi->user_space, NULL, cgi->pid);
478 /* somewhere we can at least read things and enter it */
480 lwsl_notice("%s: Failed to chdir\n", __func__);
482 /* We are the forked process, redirect and kill inherited things.
484 * Because of vfork(), we cannot do anything that changes pages in
485 * the parent environment. Stuff that changes kernel state for the
486 * process is OK. Stuff that happens after the execvpe() is OK.
489 for (n = 0; n < 3; n++) {
490 if (dup2(cgi->pipe_fds[n][!(n == 0)], n) < 0) {
491 lwsl_err("%s: stdin dup2 failed\n", __func__);
494 close(cgi->pipe_fds[n][0]);
495 close(cgi->pipe_fds[n][1]);
498 #if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
499 for (m = 0; m < n; m++) {
500 p = strchr(env_array[m], '=');
502 setenv(env_array[m], p, 1);
504 execvp(exec_array[0], (char * const *)&exec_array[0]);
506 execvpe(exec_array[0], (char * const *)&exec_array[0], &env_array[0]);
512 /* drop us from the pt cgi list */
513 pt->http.cgi_list = cgi->cgi_list;
516 __remove_wsi_socket_from_fds(wsi->http.cgi->stdwsi[n]);
518 for (n = 0; n < 3; n++)
519 if (wsi->http.cgi->stdwsi[n])
520 __lws_free_wsi(cgi->stdwsi[n]);
523 for (n = 0; n < 3; n++) {
524 if (cgi->pipe_fds[n][0] >= 0)
525 close(cgi->pipe_fds[n][0]);
526 if (cgi->pipe_fds[n][1] >= 0)
527 close(cgi->pipe_fds[n][1]);
530 lws_free_set_NULL(wsi->http.cgi);
532 lwsl_err("%s: failed\n", __func__);
537 /* we have to parse out these headers in the CGI output */
539 static const char * const significant_hdr[SIGNIFICANT_HDR_COUNT] = {
543 "transfer-encoding: chunked",
544 "content-encoding: gzip",
554 LWS_VISIBLE LWS_EXTERN int
555 lws_cgi_write_split_stdout_headers(struct lws *wsi)
558 unsigned char buf[LWS_PRE + 4096], *start = &buf[LWS_PRE], *p = start,
559 *end = &buf[sizeof(buf) - 1 - LWS_PRE], *name,
566 while (wsi->hdr_state != LHCS_PAYLOAD) {
568 * We have to separate header / finalize and payload chunks,
569 * since they need to be handled separately
571 switch (wsi->hdr_state) {
573 lwsl_debug("LHCS_RESPONSE: issuing response %d\n",
574 wsi->http.cgi->response_code);
575 if (lws_add_http_header_status(wsi,
576 wsi->http.cgi->response_code,
579 if (!wsi->http.cgi->explicitly_chunked &&
580 !wsi->http.cgi->content_length &&
581 lws_add_http_header_by_token(wsi,
582 WSI_TOKEN_HTTP_TRANSFER_ENCODING,
583 (unsigned char *)"chunked", 7, &p, end))
585 if (!(wsi->http2_substream))
586 if (lws_add_http_header_by_token(wsi,
587 WSI_TOKEN_CONNECTION,
588 (unsigned char *)"close", 5,
591 n = lws_write(wsi, start, p - start,
592 LWS_WRITE_HTTP_HEADERS | LWS_WRITE_NO_FIN);
595 * so we have a bunch of http/1 style ascii headers
596 * starting from wsi->http.cgi->headers_buf through
597 * wsi->http.cgi->headers_pos. These are OK for http/1
598 * connections, but they're no good for http/2 conns.
600 * Let's redo them at headers_pos forward using the
601 * correct coding for http/1 or http/2
603 if (!wsi->http2_substream)
604 goto post_hpack_recode;
606 p = wsi->http.cgi->headers_start;
607 wsi->http.cgi->headers_start =
608 wsi->http.cgi->headers_pos;
609 wsi->http.cgi->headers_dumped =
610 wsi->http.cgi->headers_start;
614 while (p < wsi->http.cgi->headers_start) {
618 * in http/2 upper-case header names
619 * are illegal. So convert to lower-
625 if (*p >= 'A' && *p <= 'Z')
648 if (*p != '\x0a' && *p != '\x0d') {
655 if ((*p != '\x0a' && *p != '\x0d') ||
656 p + 1 == wsi->http.cgi->headers_start) {
658 if ((strcmp((const char *)buf,
661 lwsl_debug("+ %s: %s\n",
664 lws_add_http_header_by_name(wsi, buf,
665 (unsigned char *)value, name - value,
666 (unsigned char **)&wsi->http.cgi->headers_pos,
667 (unsigned char *)wsi->http.cgi->headers_end))
679 /* finalize cached headers before dumping them */
680 if (lws_finalize_http_header(wsi,
681 (unsigned char **)&wsi->http.cgi->headers_pos,
682 (unsigned char *)wsi->http.cgi->headers_end)) {
684 lwsl_notice("finalize failed\n");
688 wsi->hdr_state = LHCS_DUMP_HEADERS;
689 wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_HEADERS;
690 lws_callback_on_writable(wsi);
691 /* back to the loop for writeability again */
694 case LHCS_DUMP_HEADERS:
696 n = wsi->http.cgi->headers_pos -
697 wsi->http.cgi->headers_dumped;
701 lwsl_debug("LHCS_DUMP_HEADERS: %d\n", n);
703 cmd = LWS_WRITE_HTTP_HEADERS_CONTINUATION;
704 if (wsi->http.cgi->headers_dumped + n !=
705 wsi->http.cgi->headers_pos) {
706 lwsl_notice("adding no fin flag\n");
707 cmd |= LWS_WRITE_NO_FIN;
711 (unsigned char *)wsi->http.cgi->headers_dumped,
714 lwsl_debug("%s: write says %d\n", __func__, m);
717 wsi->http.cgi->headers_dumped += n;
718 if (wsi->http.cgi->headers_dumped ==
719 wsi->http.cgi->headers_pos) {
720 wsi->hdr_state = LHCS_PAYLOAD;
721 lws_free_set_NULL(wsi->http.cgi->headers_buf);
722 lwsl_debug("freed cgi headers\n");
725 LWS_CB_REASON_AUX_BF__CGI_HEADERS;
726 lws_callback_on_writable(wsi);
729 /* writeability becomes uncertain now we wrote
730 * something, we must return to the event loop
735 if (!wsi->http.cgi->headers_buf) {
736 /* if we don't already have a headers buf, cook one */
738 if (wsi->http2_substream)
740 wsi->http.cgi->headers_buf = lws_malloc(n + LWS_PRE,
742 if (!wsi->http.cgi->headers_buf) {
747 lwsl_debug("allocated cgi hdrs\n");
748 wsi->http.cgi->headers_start =
749 wsi->http.cgi->headers_buf + LWS_PRE;
750 wsi->http.cgi->headers_pos = wsi->http.cgi->headers_start;
751 wsi->http.cgi->headers_dumped = wsi->http.cgi->headers_pos;
752 wsi->http.cgi->headers_end =
753 wsi->http.cgi->headers_buf + n - 1;
755 for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) {
756 wsi->http.cgi->match[n] = 0;
757 wsi->http.cgi->lp = 0;
761 n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]);
766 if (errno != EAGAIN) {
767 lwsl_debug("%s: read says %d\n", __func__, n);
773 if (wsi->http.cgi->headers_pos >=
774 wsi->http.cgi->headers_end - 4) {
775 lwsl_notice("CGI hdrs > buf size\n");
783 lwsl_debug("-- 0x%02X %c %d %d\n", (unsigned char)c, c,
784 wsi->http.cgi->match[1], wsi->hdr_state);
787 switch (wsi->hdr_state) {
790 for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) {
792 * significant headers with
793 * numeric decimal payloads
795 if (!significant_hdr[n][wsi->http.cgi->match[n]] &&
796 (c >= '0' && c <= '9') &&
797 wsi->http.cgi->lp < (int)sizeof(wsi->http.cgi->l) - 1) {
798 wsi->http.cgi->l[wsi->http.cgi->lp++] = c;
799 wsi->http.cgi->l[wsi->http.cgi->lp] = '\0';
801 case SIGNIFICANT_HDR_CONTENT_LENGTH:
802 wsi->http.cgi->content_length =
803 atoll(wsi->http.cgi->l);
805 case SIGNIFICANT_HDR_STATUS:
806 wsi->http.cgi->response_code =
807 atol(wsi->http.cgi->l);
808 lwsl_debug("Status set to %d\n",
809 wsi->http.cgi->response_code);
815 /* hits up to the NUL are sticky until next hdr */
816 if (significant_hdr[n][wsi->http.cgi->match[n]]) {
818 significant_hdr[n][wsi->http.cgi->match[n]])
819 wsi->http.cgi->match[n]++;
821 wsi->http.cgi->match[n] = 0;
825 /* some cgi only send us \x0a for EOL */
827 wsi->hdr_state = LCHS_SINGLE_0A;
828 *wsi->http.cgi->headers_pos++ = '\x0d';
830 *wsi->http.cgi->headers_pos++ = c;
832 wsi->hdr_state = LCHS_LF1;
834 if (wsi->hdr_state != LCHS_HEADER &&
835 !significant_hdr[SIGNIFICANT_HDR_TRANSFER_ENCODING]
836 [wsi->http.cgi->match[
837 SIGNIFICANT_HDR_TRANSFER_ENCODING]]) {
838 lwsl_info("cgi produced chunked\n");
839 wsi->http.cgi->explicitly_chunked = 1;
842 /* presence of Location: mandates 302 retcode */
843 if (wsi->hdr_state != LCHS_HEADER &&
844 !significant_hdr[SIGNIFICANT_HDR_LOCATION][
845 wsi->http.cgi->match[SIGNIFICANT_HDR_LOCATION]]) {
846 lwsl_debug("CGI: Location hdr seen\n");
847 wsi->http.cgi->response_code = 302;
851 *wsi->http.cgi->headers_pos++ = c;
853 wsi->hdr_state = LCHS_CR2;
856 /* we got \r[^\n]... it's unreasonable */
857 lwsl_debug("%s: funny CRLF 0x%02X\n", __func__,
864 wsi->hdr_state = LCHS_LF2;
867 wsi->hdr_state = LCHS_HEADER;
868 for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++)
869 wsi->http.cgi->match[n] = 0;
870 wsi->http.cgi->lp = 0;
877 lwsl_debug("Content-Length: %lld\n",
879 wsi->http.cgi->content_length);
880 wsi->hdr_state = LHCS_RESPONSE;
882 * drop the \0xa ... finalize
883 * will add it if needed (HTTP/1)
888 /* we got \r\n\r[^\n]... unreasonable */
890 /* we got \x0anext header, it's reasonable */
891 *wsi->http.cgi->headers_pos++ = c;
892 wsi->hdr_state = LCHS_HEADER;
893 for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++)
894 wsi->http.cgi->match[n] = 0;
895 wsi->http.cgi->lp = 0;
902 /* ran out of input, ended the hdrs, or filled up the hdrs buf */
903 if (!n || wsi->hdr_state == LHCS_PAYLOAD)
907 /* payload processing */
909 m = !wsi->http.cgi->implied_chunked && !wsi->http2_substream &&
910 !wsi->http.cgi->explicitly_chunked &&
911 !wsi->http.cgi->content_length;
912 n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]);
916 uint8_t term[LWS_PRE + 6];
918 lwsl_info("%s: zero chunk\n", __func__);
920 memcpy(term + LWS_PRE, (uint8_t *)"0\x0d\x0a\x0d\x0a", 5);
922 if (lws_write(wsi, term + LWS_PRE, 5,
923 LWS_WRITE_HTTP_FINAL) != 5)
926 wsi->http.cgi->cgi_transaction_over = 1;
931 n = read(n, start, sizeof(buf) - LWS_PRE);
933 if (n < 0 && errno != EAGAIN) {
934 lwsl_debug("%s: stdout read says %d\n", __func__, n);
939 if (!wsi->http2_substream && m) {
940 char chdr[LWS_HTTP_CHUNK_HDR_SIZE];
941 m = lws_snprintf(chdr, LWS_HTTP_CHUNK_HDR_SIZE - 3,
943 memmove(start + m, start, n);
944 memcpy(start, chdr, m);
945 memcpy(start + m + n, "\x0d\x0a", 2);
950 #if defined(LWS_WITH_HTTP2)
951 if (wsi->http2_substream) {
952 struct lws *nwsi = lws_get_network_wsi(wsi);
954 __lws_set_timeout(wsi,
955 PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31);
957 if (!nwsi->immortal_substream_count)
958 __lws_set_timeout(nwsi,
959 PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31);
963 cmd = LWS_WRITE_HTTP;
964 if (wsi->http.cgi->content_length_seen + n ==
965 wsi->http.cgi->content_length)
966 cmd = LWS_WRITE_HTTP_FINAL;
968 m = lws_write(wsi, (unsigned char *)start, n, cmd);
969 //lwsl_notice("write %d\n", m);
971 lwsl_debug("%s: stdout write says %d\n", __func__, m);
974 wsi->http.cgi->content_length_seen += n;
976 if (wsi->cgi_stdout_zero_length) {
977 lwsl_debug("%s: stdout is POLLHUP'd\n", __func__);
978 if (wsi->http2_substream)
979 m = lws_write(wsi, (unsigned char *)start, 0,
980 LWS_WRITE_HTTP_FINAL);
985 wsi->cgi_stdout_zero_length = 1;
990 LWS_VISIBLE LWS_EXTERN int
991 lws_cgi_kill(struct lws *wsi)
993 struct lws_cgi_args args;
996 lwsl_debug("%s: %p\n", __func__, wsi);
1001 if (wsi->http.cgi->pid > 0) {
1002 n = waitpid(wsi->http.cgi->pid, &status, WNOHANG);
1004 lwsl_debug("%s: PID %d reaped\n", __func__,
1005 wsi->http.cgi->pid);
1008 /* kill the process group */
1009 n = kill(-wsi->http.cgi->pid, SIGTERM);
1010 lwsl_debug("%s: SIGTERM child PID %d says %d (errno %d)\n",
1011 __func__, wsi->http.cgi->pid, n, errno);
1014 * hum seen errno=3 when process is listed in ps,
1015 * it seems we don't always retain process grouping
1017 * Direct these fallback attempt to the exact child
1019 n = kill(wsi->http.cgi->pid, SIGTERM);
1021 n = kill(wsi->http.cgi->pid, SIGPIPE);
1023 n = kill(wsi->http.cgi->pid, SIGKILL);
1025 lwsl_info("%s: SIGKILL PID %d "
1029 wsi->http.cgi->pid, errno);
1033 /* He could be unkillable because he's a zombie */
1036 n = waitpid(-wsi->http.cgi->pid, &status, WNOHANG);
1038 lwsl_debug("%s: reaped PID %d\n", __func__, n);
1040 n = waitpid(wsi->http.cgi->pid, &status, WNOHANG);
1042 lwsl_debug("%s: reaped PID %d\n",
1049 args.stdwsi = &wsi->http.cgi->stdwsi[0];
1051 if (wsi->http.cgi->pid != -1) {
1052 n = user_callback_handle_rxflow(wsi->protocol->callback, wsi,
1053 LWS_CALLBACK_CGI_TERMINATED,
1054 wsi->user_space, (void *)&args,
1055 wsi->http.cgi->pid);
1056 wsi->http.cgi->pid = -1;
1057 if (n && !wsi->http.cgi->being_closed)
1058 lws_close_free_wsi(wsi, 0, "lws_cgi_kill");
1065 lws_cgi_kill_terminated(struct lws_context_per_thread *pt)
1067 struct lws_cgi **pcgi, *cgi = NULL;
1071 /* find finished guys but don't reap yet */
1072 n = waitpid(-1, &status, WNOHANG);
1075 lwsl_debug("%s: observed PID %d terminated\n", __func__, n);
1077 pcgi = &pt->http.cgi_list;
1079 /* check all the subprocesses on the cgi list */
1081 /* get the next one first as list may change */
1083 pcgi = &(*pcgi)->cgi_list;
1088 /* finish sending cached headers */
1089 if (cgi->headers_buf)
1092 /* wait for stdout to be drained */
1093 if (cgi->content_length > cgi->content_length_seen)
1096 if (cgi->content_length) {
1097 lwsl_debug("%s: wsi %p: expected content "
1098 "length seen: %lld\n", __func__,
1100 (unsigned long long)cgi->content_length_seen);
1104 waitpid(n, &status, WNOHANG);
1106 * he's already terminated so no need for kill()
1107 * but we should do the terminated cgi callback
1108 * and close him if he's not already closing
1110 if (n == cgi->pid) {
1111 lwsl_debug("%s: found PID %d on cgi list\n",
1114 if (!cgi->content_length) {
1116 * well, if he sends chunked...
1117 * give him 2s after the
1118 * cgi terminated to send buffered
1120 cgi->chunked_grace++;
1126 lws_cgi_kill(cgi->wsi);
1132 /* if not found on the cgi list, as he's one of ours, reap */
1134 lwsl_debug("%s: reading PID %d although no cgi match\n",
1136 waitpid(n, &status, WNOHANG);
1140 pcgi = &pt->http.cgi_list;
1142 /* check all the subprocesses on the cgi list */
1144 /* get the next one first as list may change */
1146 pcgi = &(*pcgi)->cgi_list;
1151 /* we deferred killing him after reaping his PID */
1152 if (cgi->chunked_grace) {
1153 cgi->chunked_grace++;
1154 if (cgi->chunked_grace < 2)
1159 /* finish sending cached headers */
1160 if (cgi->headers_buf)
1163 /* wait for stdout to be drained */
1164 if (cgi->content_length > cgi->content_length_seen)
1167 if (cgi->content_length)
1168 lwsl_debug("%s: wsi %p: expected "
1169 "content len seen: %lld\n", __func__,
1171 (unsigned long long)cgi->content_length_seen);
1174 if (waitpid(cgi->pid, &status, WNOHANG) > 0) {
1176 if (!cgi->content_length) {
1178 * well, if he sends chunked...
1179 * give him 2s after the
1180 * cgi terminated to send buffered
1182 cgi->chunked_grace++;
1186 lwsl_debug("%s: found PID %d on cgi list\n",
1187 __func__, cgi->pid);
1191 lws_cgi_kill(cgi->wsi);
1200 LWS_VISIBLE LWS_EXTERN struct lws *
1201 lws_cgi_get_stdwsi(struct lws *wsi, enum lws_enum_stdinouterr ch)
1206 return wsi->http.cgi->stdwsi[ch];
1210 lws_cgi_remove_and_kill(struct lws *wsi)
1212 struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
1213 struct lws_cgi **pcgi = &pt->http.cgi_list;
1215 /* remove us from the cgi list */
1216 lwsl_debug("%s: remove cgi %p from list\n", __func__, wsi->http.cgi);
1218 if (*pcgi == wsi->http.cgi) {
1219 /* drop us from the pt cgi list */
1220 *pcgi = (*pcgi)->cgi_list;
1223 pcgi = &(*pcgi)->cgi_list;
1225 if (wsi->http.cgi->headers_buf) {
1226 lwsl_debug("close: freed cgi headers\n");
1227 lws_free_set_NULL(wsi->http.cgi->headers_buf);
1229 /* we have a cgi going, we must kill it */
1230 wsi->http.cgi->being_closed = 1;