diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index b8e89745..0e914f33 100755 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -302,6 +302,8 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) } pcgi = &(*pcgi)->cgi_list; } + if (wsi->cgi->headers_buf) + lws_free_set_NULL(wsi->cgi->headers_buf); /* we have a cgi going, we must kill it */ wsi->cgi->being_closed = 1; lws_cgi_kill(wsi); @@ -2272,6 +2274,8 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len return -1; } + wsi->cgi->response_code = HTTP_STATUS_OK; + cgi = wsi->cgi; cgi->wsi = wsi; /* set cgi's owning wsi */ @@ -2545,13 +2549,21 @@ bail1: return -1; } +/* we have to parse out these headers in the CGI output */ + +static const char * const significant_hdr[SIGNIFICANT_HDR_COUNT] = { + "content-length: ", + "location: ", + "status: ", +}; + LWS_VISIBLE LWS_EXTERN int lws_cgi_write_split_stdout_headers(struct lws *wsi) { - int n, m, match = 0, lp = 0; - static const char * const content_length = "content-length: "; - char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start, - *end = &buf[sizeof(buf) - 1 - LWS_PRE], c, l[12]; + int n, m; + unsigned char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start, + *end = &buf[sizeof(buf) - 1 - LWS_PRE]; + char c; if (!wsi->cgi) return -1; @@ -2561,6 +2573,72 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi) * payload chunks, since they need to be * handled separately */ + + switch (wsi->hdr_state) { + + case LHCS_RESPONSE: + lwsl_info("LHCS_RESPONSE: issuing response %d\n", + wsi->cgi->response_code); + if (lws_add_http_header_status(wsi, wsi->cgi->response_code, &p, end)) + return 1; + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, + (unsigned char *)"close", 5, &p, end)) + return 1; + n = lws_write(wsi, start, p - start, + LWS_WRITE_HTTP_HEADERS); + + /* finalize cached headers before dumping them */ + if (lws_finalize_http_header(wsi, + (unsigned char **)&wsi->cgi->headers_pos, + (unsigned char *)wsi->cgi->headers_end)) + return -1; + + wsi->hdr_state = LHCS_DUMP_HEADERS; + /* back to the loop for writeability again */ + return 0; + + case LHCS_DUMP_HEADERS: + + n = wsi->cgi->headers_pos - wsi->cgi->headers_dumped; + if (n > 512) + n = 512; + + m = lws_write(wsi, (unsigned char *)wsi->cgi->headers_dumped, + n, LWS_WRITE_HTTP_HEADERS); + if (m < 0) { + lwsl_debug("%s: write says %d\n", __func__, m); + return -1; + } + wsi->cgi->headers_dumped += n; + if (wsi->cgi->headers_dumped == wsi->cgi->headers_pos) { + wsi->hdr_state = LHCS_PAYLOAD; + lws_free_set_NULL(wsi->cgi->headers_buf); + } + + /* writeability becomes uncertain now we wrote + * something, we must return to the event loop + */ + return 0; + } + + if (!wsi->cgi->headers_buf) { + /* if we don't already have a headers buf, cook one up */ + n = 2048; + wsi->cgi->headers_buf = malloc(n); + if (!wsi->cgi->headers_buf) { + lwsl_err("OOM\n"); + return -1; + } + wsi->cgi->headers_pos = wsi->cgi->headers_buf; + wsi->cgi->headers_dumped = wsi->cgi->headers_pos; + wsi->cgi->headers_end = wsi->cgi->headers_buf + n - 1; + + for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) { + wsi->cgi->match[n] = 0; + wsi->cgi->lp = 0; + } + } + n = read(lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]), &c, 1); if (n < 0) { if (errno != EAGAIN) { @@ -2569,37 +2647,67 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi) } else n = 0; + + if (wsi->cgi->headers_pos >= wsi->cgi->headers_end - 4) { + lwsl_notice("CGI headers larger than buffer size\n"); + + return -1; + } } if (n) { - lwsl_debug("-- 0x%02X %c\n", (unsigned char)c, c); + lwsl_debug("-- 0x%02X %c %d %d\n", (unsigned char)c, c, wsi->cgi->match[1], wsi->hdr_state); + if (!c) + return -1; switch (wsi->hdr_state) { case LCHS_HEADER: - if (!content_length[match] && - (c >= '0' && c <= '9') && - lp < sizeof(l) - 1) { - l[lp++] = c; - l[lp] = '\0'; - wsi->cgi->content_length = atol(l); + hdr: + for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) { + /* significant headers with numeric decimal payloads */ + if (!significant_hdr[n][wsi->cgi->match[n]] && + (c >= '0' && c <= '9') && + wsi->cgi->lp < sizeof(wsi->cgi->l) - 1) { + wsi->cgi->l[wsi->cgi->lp++] = c; + wsi->cgi->l[wsi->cgi->lp] = '\0'; + switch (n) { + case SIGNIFICANT_HDR_CONTENT_LENGTH: + wsi->cgi->content_length = atol(wsi->cgi->l); + break; + case SIGNIFICANT_HDR_STATUS: + wsi->cgi->response_code = atol(wsi->cgi->l); + lwsl_debug("Status set to %d\n", wsi->cgi->response_code); + break; + default: + break; + } + } + /* hits up to the NUL are sticky until next hdr */ + if (significant_hdr[n][wsi->cgi->match[n]]) { + if (tolower(c) == significant_hdr[n][wsi->cgi->match[n]]) + wsi->cgi->match[n]++; + else + wsi->cgi->match[n] = 0; + } } - if (tolower(c) == content_length[match]) - match++; - else - match = 0; /* some cgi only send us \x0a for EOL */ if (c == '\x0a') { wsi->hdr_state = LCHS_SINGLE_0A; - *p++ = '\x0d'; + *wsi->cgi->headers_pos++ = '\x0d'; } - *p++ = c; - if (c == '\x0d') { + *wsi->cgi->headers_pos++ = c; + if (c == '\x0d') wsi->hdr_state = LCHS_LF1; - break; + + /* presence of Location: mandates 302 retcode */ + if (wsi->hdr_state != LCHS_HEADER && + !significant_hdr[SIGNIFICANT_HDR_LOCATION][wsi->cgi->match[SIGNIFICANT_HDR_LOCATION]]) { + lwsl_debug("CGI: Location hdr seen\n"); + wsi->cgi->response_code = 302; } break; case LCHS_LF1: - *p++ = c; + *wsi->cgi->headers_pos++ = c; if (c == '\x0a') { wsi->hdr_state = LCHS_CR2; break; @@ -2615,28 +2723,29 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi) break; } wsi->hdr_state = LCHS_HEADER; - match = 0; - *p++ = c; - break; + for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) + wsi->cgi->match[n] = 0; + wsi->cgi->lp = 0; + goto hdr; + case LCHS_LF2: case LCHS_SINGLE_0A: m = wsi->hdr_state; if (c == '\x0a') { lwsl_debug("Content-Length: %ld\n", wsi->cgi->content_length); - wsi->hdr_state = LHCS_PAYLOAD; + wsi->hdr_state = LHCS_RESPONSE; /* drop the \0xa ... finalize will add it if needed */ - if (lws_finalize_http_header(wsi, - (unsigned char **)&p, - (unsigned char *)end)) - return -1; break; } if (m == LCHS_LF2) /* we got \r\n\r[^\n]... it's unreasonable */ return -1; /* we got \x0anext header, it's reasonable */ - *p++ = c; + *wsi->cgi->headers_pos++ = c; wsi->hdr_state = LCHS_HEADER; + for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) + wsi->cgi->match[n] = 0; + wsi->cgi->lp = 0; break; case LHCS_PAYLOAD: break; @@ -2644,22 +2753,12 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi) } /* ran out of input, ended the headers, or filled up the headers buf */ - if (!n || wsi->hdr_state == LHCS_PAYLOAD || (p + 4) == end) { - - m = lws_write(wsi, (unsigned char *)start, - p - start, LWS_WRITE_HTTP_HEADERS); - if (m < 0) { - lwsl_debug("%s: write says %d\n", __func__, m); - return -1; - } - /* writeability becomes uncertain now we wrote - * something, we must return to the event loop - */ - + if (!n || wsi->hdr_state == LHCS_PAYLOAD) return 0; - } } + /* payload processing */ + n = read(lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]), start, sizeof(buf) - LWS_PRE); diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 185a5690..860eeb1a 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -4399,6 +4399,8 @@ enum lws_cgi_hdr_state { LCHS_LF1, LCHS_CR2, LCHS_LF2, + LHCS_RESPONSE, + LHCS_DUMP_HEADERS, LHCS_PAYLOAD, LCHS_SINGLE_0A, }; diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index ad61015f..78d1ceb3 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -1442,16 +1442,32 @@ struct _lws_websocket_related { #ifdef LWS_WITH_CGI +enum { + SIGNIFICANT_HDR_CONTENT_LENGTH, + SIGNIFICANT_HDR_LOCATION, + SIGNIFICANT_HDR_STATUS, + + SIGNIFICANT_HDR_COUNT +}; + /* wsi who is master of the cgi points to an lws_cgi */ struct lws_cgi { struct lws_cgi *cgi_list; struct lws *stdwsi[3]; /* points to the associated stdin/out/err wsis */ struct lws *wsi; /* owner */ + unsigned char *headers_buf; + unsigned char *headers_pos; + unsigned char *headers_dumped; + unsigned char *headers_end; unsigned long content_length; unsigned long content_length_seen; int pipe_fds[3][2]; + int match[SIGNIFICANT_HDR_COUNT]; int pid; + int response_code; + int lp; + char l[12]; unsigned int being_closed:1; diff --git a/lib/server.c b/lib/server.c index acadcd6b..db214bd9 100644 --- a/lib/server.c +++ b/lib/server.c @@ -1058,7 +1058,6 @@ lws_http_action(struct lws *wsi) NULL, /* replace with cgi path */ NULL }; - unsigned char *p, *end, buffer[1024]; lwsl_debug("%s: cgi\n", __func__); cmd[0] = hit->origin; @@ -1073,17 +1072,6 @@ lws_http_action(struct lws *wsi) lwsl_err("%s: cgi failed\n", __func__); return -1; } - p = buffer + LWS_PRE; - end = p + sizeof(buffer) - LWS_PRE; - - if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end)) - return 1; - if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, - (unsigned char *)"close", 5, &p, end)) - return 1; - n = lws_write(wsi, buffer + LWS_PRE, - p - (buffer + LWS_PRE), - LWS_WRITE_HTTP_HEADERS); goto deal_body; }