cgi: allow time travelling headers to decide response code

https://github.com/warmcat/libwebsockets/issues/899
This commit is contained in:
Andy Green 2017-05-18 21:19:57 +08:00
parent ed92b6dfe7
commit de12c860db
4 changed files with 159 additions and 54 deletions

View file

@ -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);

View file

@ -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,
};

View file

@ -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;

View file

@ -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;
}