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