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