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