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