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