[core] consolidate dynamic handler response parse
- consolidate dynamic handler HTTP response parsing code - reduce string copies for CGI, FastCGI, SCGI, proxy response headers - let read() signal EOF or EAGAIN instead of ioctl FIONREAD 0-data-readypersonal/stbuehler/mod-csrf
parent
76ad82434f
commit
0a635fc8be
|
@ -670,12 +670,11 @@ void http_response_send_file (server *srv, connection *con, buffer *path) {
|
|||
}
|
||||
}
|
||||
|
||||
void http_response_xsendfile (server *srv, connection *con, buffer *path, const array *xdocroot) {
|
||||
|
||||
static void http_response_xsendfile (server *srv, connection *con, buffer *path, const array *xdocroot) {
|
||||
const int status = con->http_status;
|
||||
int valid = 1;
|
||||
|
||||
con->file_started = 1;
|
||||
|
||||
/* reset Content-Length, if set by backend
|
||||
* Content-Length might later be set to size of X-Sendfile static file,
|
||||
* determined by open(), fstat() to reduces race conditions if the file
|
||||
|
@ -727,6 +726,143 @@ void http_response_xsendfile (server *srv, connection *con, buffer *path, const
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void http_response_xsendfile2(server *srv, connection *con, const buffer *value, const array *xdocroot) {
|
||||
const char *pos = value->ptr;
|
||||
buffer *b = srv->tmp_buf;
|
||||
const int status = con->http_status;
|
||||
|
||||
/* reset Content-Length, if set by backend */
|
||||
if (con->parsed_response & HTTP_CONTENT_LENGTH) {
|
||||
data_string *ds = (data_string *)
|
||||
array_get_element(con->response.headers, "Content-Length");
|
||||
if (ds) buffer_reset(ds->value);
|
||||
con->parsed_response &= ~HTTP_CONTENT_LENGTH;
|
||||
con->response.content_length = -1;
|
||||
}
|
||||
|
||||
while (*pos) {
|
||||
const char *filename, *range;
|
||||
stat_cache_entry *sce;
|
||||
off_t begin_range, end_range, range_len;
|
||||
|
||||
while (' ' == *pos) pos++;
|
||||
if (!*pos) break;
|
||||
|
||||
filename = pos;
|
||||
if (NULL == (range = strchr(pos, ' '))) {
|
||||
/* missing range */
|
||||
log_error_write(srv, __FILE__, __LINE__, "ss",
|
||||
"Couldn't find range after filename:", filename);
|
||||
con->http_status = 502;
|
||||
break;
|
||||
}
|
||||
buffer_copy_string_len(b, filename, range - filename);
|
||||
|
||||
/* find end of range */
|
||||
for (pos = ++range; *pos && *pos != ' ' && *pos != ','; pos++) ;
|
||||
|
||||
buffer_urldecode_path(b);
|
||||
buffer_path_simplify(b, b);
|
||||
if (con->conf.force_lowercase_filenames) {
|
||||
buffer_to_lower(b);
|
||||
}
|
||||
if (xdocroot->used) {
|
||||
size_t i, xlen = buffer_string_length(b);
|
||||
for (i = 0; i < xdocroot->used; ++i) {
|
||||
data_string *ds = (data_string *)xdocroot->data[i];
|
||||
size_t dlen = buffer_string_length(ds->value);
|
||||
if (dlen <= xlen
|
||||
&& (!con->conf.force_lowercase_filenames
|
||||
? 0 == memcmp(b->ptr, ds->value->ptr, dlen)
|
||||
: 0 == strncasecmp(b->ptr, ds->value->ptr, dlen))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == xdocroot->used) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "SBs",
|
||||
"X-Sendfile2 (", b,
|
||||
") not under configured x-sendfile-docroot(s)");
|
||||
con->http_status = 403;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (HANDLER_ERROR == stat_cache_get_entry(srv, con, b, &sce)) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "sb", "send-file error: "
|
||||
"couldn't get stat_cache entry for X-Sendfile2:",
|
||||
b);
|
||||
con->http_status = 404;
|
||||
break;
|
||||
} else if (!S_ISREG(sce->st.st_mode)) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "sb",
|
||||
"send-file error: wrong filetype for X-Sendfile2:",
|
||||
b);
|
||||
con->http_status = 502;
|
||||
break;
|
||||
}
|
||||
/* found the file */
|
||||
|
||||
/* parse range */
|
||||
end_range = sce->st.st_size - 1;
|
||||
{
|
||||
char *rpos = NULL;
|
||||
errno = 0;
|
||||
begin_range = strtoll(range, &rpos, 10);
|
||||
if (errno != 0 || begin_range < 0 || rpos == range)
|
||||
goto range_failed;
|
||||
if ('-' != *rpos++) goto range_failed;
|
||||
if (rpos != pos) {
|
||||
range = rpos;
|
||||
end_range = strtoll(range, &rpos, 10);
|
||||
if (errno != 0 || end_range < 0 || rpos == range)
|
||||
goto range_failed;
|
||||
}
|
||||
if (rpos != pos) goto range_failed;
|
||||
|
||||
goto range_success;
|
||||
|
||||
range_failed:
|
||||
log_error_write(srv, __FILE__, __LINE__, "ss",
|
||||
"Couldn't decode range after filename:", filename);
|
||||
con->http_status = 502;
|
||||
break;
|
||||
|
||||
range_success: ;
|
||||
}
|
||||
|
||||
/* no parameters accepted */
|
||||
|
||||
while (*pos == ' ') pos++;
|
||||
if (*pos != '\0' && *pos != ',') {
|
||||
con->http_status = 502;
|
||||
break;
|
||||
}
|
||||
|
||||
range_len = end_range - begin_range + 1;
|
||||
if (range_len < 0) {
|
||||
con->http_status = 502;
|
||||
break;
|
||||
}
|
||||
if (range_len != 0) {
|
||||
if (0 != http_chunk_append_file_range(srv, con, b,
|
||||
begin_range, range_len)) {
|
||||
con->http_status = 502;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (*pos == ',') pos++;
|
||||
}
|
||||
|
||||
if (con->http_status >= 400 && status < 300) {
|
||||
con->mode = DIRECT;
|
||||
} else if (0 != status && 200 != status) {
|
||||
con->http_status = status;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void http_response_backend_error (server *srv, connection *con) {
|
||||
UNUSED(srv);
|
||||
if (con->file_started) {
|
||||
|
@ -762,6 +898,471 @@ void http_response_backend_done (server *srv, connection *con) {
|
|||
}
|
||||
|
||||
|
||||
static handler_t http_response_process_local_redir(server *srv, connection *con, size_t blen) {
|
||||
/* [RFC3875] The Common Gateway Interface (CGI) Version 1.1
|
||||
* [RFC3875] 6.2.2 Local Redirect Response
|
||||
*
|
||||
* The CGI script can return a URI path and query-string
|
||||
* ('local-pathquery') for a local resource in a Location header field.
|
||||
* This indicates to the server that it should reprocess the request
|
||||
* using the path specified.
|
||||
*
|
||||
* local-redir-response = local-Location NL
|
||||
*
|
||||
* The script MUST NOT return any other header fields or a message-body,
|
||||
* and the server MUST generate the response that it would have produced
|
||||
* in response to a request containing the URL
|
||||
*
|
||||
* scheme "://" server-name ":" server-port local-pathquery
|
||||
*
|
||||
* (Might not have begun to receive body yet, but do skip local-redir
|
||||
* if we already have started receiving a response body (blen > 0))
|
||||
* (Also, while not required by the RFC, do not send local-redir back
|
||||
* to same URL, since CGI should have handled it internally if it
|
||||
* really wanted to do that internally)
|
||||
*/
|
||||
|
||||
/* con->http_status >= 300 && con->http_status < 400) */
|
||||
size_t ulen = buffer_string_length(con->uri.path);
|
||||
data_string *ds = (data_string *)
|
||||
array_get_element(con->response.headers, "Location");
|
||||
if (NULL != ds
|
||||
&& ds->value->ptr[0] == '/'
|
||||
&& (0 != strncmp(ds->value->ptr, con->uri.path->ptr, ulen)
|
||||
|| (ds->value->ptr[ulen] != '\0'
|
||||
&& ds->value->ptr[ulen] != '/'
|
||||
&& ds->value->ptr[ulen] != '?'))
|
||||
&& 0 == blen
|
||||
&& !(con->parsed_response & HTTP_STATUS) /*no "Status" or NPH response*/
|
||||
&& 1 == con->response.headers->used) {
|
||||
if (++con->loops_per_request > 5) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "sb",
|
||||
"too many internal loops while processing request:",
|
||||
con->request.orig_uri);
|
||||
con->http_status = 500; /* Internal Server Error */
|
||||
con->mode = DIRECT;
|
||||
return HANDLER_FINISHED;
|
||||
}
|
||||
|
||||
buffer_copy_buffer(con->request.uri, ds->value);
|
||||
|
||||
if (con->request.content_length) {
|
||||
if (con->request.content_length
|
||||
!= con->request_content_queue->bytes_in) {
|
||||
con->keep_alive = 0;
|
||||
}
|
||||
con->request.content_length = 0;
|
||||
chunkqueue_reset(con->request_content_queue);
|
||||
}
|
||||
|
||||
if (con->http_status != 307 && con->http_status != 308) {
|
||||
/* Note: request body (if any) sent to initial dynamic handler
|
||||
* and is not available to the internal redirect */
|
||||
con->request.http_method = HTTP_METHOD_GET;
|
||||
}
|
||||
|
||||
/*(caller must reset request as follows)*/
|
||||
/*connection_response_reset(srv, con);*/ /*(sets con->http_status = 0)*/
|
||||
/*plugins_call_connection_reset(srv, con);*/
|
||||
|
||||
return HANDLER_COMEBACK;
|
||||
}
|
||||
|
||||
return HANDLER_GO_ON;
|
||||
}
|
||||
|
||||
|
||||
static int http_response_process_headers(server *srv, connection *con, http_response_opts *opts, buffer *hdrs) {
|
||||
char *ns;
|
||||
const char *s;
|
||||
int line = 0;
|
||||
|
||||
for (s = hdrs->ptr; NULL != (ns = strchr(s, '\n')); s = ns + 1, ++line) {
|
||||
const char *key, *value;
|
||||
int key_len;
|
||||
data_string *ds;
|
||||
|
||||
/* strip the \n */
|
||||
ns[0] = '\0';
|
||||
if (ns > s && ns[-1] == '\r') ns[-1] = '\0';
|
||||
|
||||
if (0 == line && 0 == strncmp(s, "HTTP/1.", 7)) {
|
||||
/* non-parsed headers ... we parse them anyway */
|
||||
if ((s[7] == '1' || s[7] == '0') && s[8] == ' ') {
|
||||
/* after the space should be a status code for us */
|
||||
int status = strtol(s+9, NULL, 10);
|
||||
if (status >= 100 && status < 1000) {
|
||||
con->parsed_response |= HTTP_STATUS;
|
||||
con->http_status = status;
|
||||
} /* else we expected 3 digits and didn't get them */
|
||||
}
|
||||
|
||||
if (0 == con->http_status) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "ss",
|
||||
"invalid HTTP status line:", s);
|
||||
con->http_status = 502; /* Bad Gateway */
|
||||
con->mode = DIRECT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* parse the headers */
|
||||
key = s;
|
||||
if (NULL == (value = strchr(s, ':'))) {
|
||||
/* we expect: "<key>: <value>\r\n" */
|
||||
continue;
|
||||
}
|
||||
|
||||
key_len = value - key;
|
||||
do { ++value; } while (*value == ' ' || *value == '\t'); /* skip LWS */
|
||||
|
||||
if (opts->authorizer) {
|
||||
if (0 == con->http_status || 200 == con->http_status) {
|
||||
if (key_len == 6 && 0 == strncasecmp(key, "Status", key_len)) {
|
||||
int status = strtol(value, NULL, 10);
|
||||
if (status >= 100 && status < 1000) {
|
||||
con->http_status = status;
|
||||
} else {
|
||||
con->http_status = 502; /* Bad Gateway */
|
||||
break;
|
||||
}
|
||||
} else if (key_len > 9
|
||||
&& 0==strncasecmp(key, CONST_STR_LEN("Variable-"))) {
|
||||
ds = (data_string *)
|
||||
array_get_unused_element(con->environment, TYPE_STRING);
|
||||
if (NULL == ds) ds = data_string_init();
|
||||
buffer_copy_string_len(ds->key, key + 9, key_len - 9);
|
||||
buffer_copy_string(ds->value, value);
|
||||
|
||||
array_insert_unique(con->environment, (data_unset *)ds);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
switch(key_len) {
|
||||
case 4:
|
||||
if (0 == strncasecmp(key, "Date", key_len)) {
|
||||
con->parsed_response |= HTTP_DATE;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if (0 == strncasecmp(key, "Status", key_len)) {
|
||||
int status;
|
||||
if (opts->backend == BACKEND_PROXY) break; /*(pass w/o parse)*/
|
||||
status = strtol(value, NULL, 10);
|
||||
if (status >= 100 && status < 1000) {
|
||||
con->http_status = status;
|
||||
con->parsed_response |= HTTP_STATUS;
|
||||
} else {
|
||||
con->http_status = 502;
|
||||
con->mode = DIRECT;
|
||||
}
|
||||
continue; /* do not send Status to client */
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
if (0 == strncasecmp(key, "Location", key_len)) {
|
||||
con->parsed_response |= HTTP_LOCATION;
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
if (0 == strncasecmp(key, "Connection", key_len)) {
|
||||
if (opts->backend == BACKEND_PROXY) continue;
|
||||
con->response.keep_alive =
|
||||
(0 == strcasecmp(value, "Keep-Alive")) ? 1 : 0;
|
||||
con->parsed_response |= HTTP_CONNECTION;
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
if (0 == strncasecmp(key, "Content-Length", key_len)) {
|
||||
con->response.content_length = strtoul(value, NULL, 10);
|
||||
con->parsed_response |= HTTP_CONTENT_LENGTH;
|
||||
}
|
||||
break;
|
||||
case 17:
|
||||
if (0 == strncasecmp(key, "Transfer-Encoding", key_len)) {
|
||||
if (opts->backend == BACKEND_PROXY) continue;
|
||||
con->parsed_response |= HTTP_TRANSFER_ENCODING;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ds = (data_string *)
|
||||
array_get_unused_element(con->response.headers, TYPE_STRING);
|
||||
if (NULL == ds) ds = data_response_init();
|
||||
buffer_copy_string_len(ds->key, key, key_len);
|
||||
buffer_copy_string(ds->value, value);
|
||||
|
||||
array_insert_unique(con->response.headers, (data_unset *)ds);
|
||||
}
|
||||
|
||||
/* CGI/1.1 rev 03 - 7.2.1.2 */
|
||||
/* (proxy requires Status-Line, so never true for proxy)*/
|
||||
if ((con->parsed_response & HTTP_LOCATION) &&
|
||||
!(con->parsed_response & HTTP_STATUS)) {
|
||||
con->http_status = 302;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
handler_t http_response_parse_headers(server *srv, connection *con, http_response_opts *opts, buffer *b) {
|
||||
/**
|
||||
* possible formats of response headers:
|
||||
*
|
||||
* proxy or NPH (non-parsed headers):
|
||||
*
|
||||
* HTTP/1.0 200 Ok\n
|
||||
* Header: Value\n
|
||||
* \n
|
||||
*
|
||||
* CGI:
|
||||
*
|
||||
* Header: Value\n
|
||||
* Status: 200\n
|
||||
* \n
|
||||
*
|
||||
* and different mixes of \n and \r\n combinations
|
||||
*
|
||||
* Some users also forget about CGI and just send a response
|
||||
* and hope we handle it. No headers, no header-content separator
|
||||
*/
|
||||
|
||||
int is_nph = (0 == strncmp(b->ptr, "HTTP/1.", 7)); /*nph (non-parsed hdrs)*/
|
||||
int is_header_end = 0;
|
||||
size_t last_eol = 0;
|
||||
size_t i = 0, header_len = buffer_string_length(b);
|
||||
const char *bstart;
|
||||
size_t blen;
|
||||
|
||||
if (b->ptr[0] == '\n' || (b->ptr[0] == '\r' && b->ptr[1] == '\n')) {
|
||||
/* no HTTP headers */
|
||||
i = (b->ptr[0] == '\n') ? 1 : 2;
|
||||
is_header_end = 1;
|
||||
} else if (is_nph || b->ptr[(i = strcspn(b->ptr, ":\n"))] == ':') {
|
||||
/* HTTP headers */
|
||||
++i;
|
||||
for (char *c; NULL != (c = strchr(b->ptr+i, '\n')); ++i) {
|
||||
i = (uintptr_t)(c - b->ptr);
|
||||
/**
|
||||
* check if we saw a \n(\r)?\n sequence
|
||||
*/
|
||||
if (last_eol > 0 &&
|
||||
((i - last_eol == 1) ||
|
||||
(i - last_eol == 2 && b->ptr[i - 1] == '\r'))) {
|
||||
is_header_end = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
last_eol = i;
|
||||
}
|
||||
} else if (opts->backend == BACKEND_CGI) {
|
||||
/* no HTTP headers, but a body (special-case for CGI compat) */
|
||||
/* no colon found; does not appear to be HTTP headers */
|
||||
if (0 != http_chunk_append_buffer(srv, con, b)) {
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
con->http_status = 200; /* OK */
|
||||
con->file_started = 1;
|
||||
return HANDLER_GO_ON;
|
||||
} else {
|
||||
/* invalid response headers */
|
||||
con->http_status = 502; /* Bad Gateway */
|
||||
con->mode = DIRECT;
|
||||
return HANDLER_FINISHED;
|
||||
}
|
||||
|
||||
if (!is_header_end) {
|
||||
/*(reuse MAX_HTTP_REQUEST_HEADER as max size
|
||||
* for response headers from backends)*/
|
||||
if (header_len > MAX_HTTP_REQUEST_HEADER) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "sb",
|
||||
"response headers too large for", con->uri.path);
|
||||
con->http_status = 502; /* Bad Gateway */
|
||||
con->mode = DIRECT;
|
||||
return HANDLER_FINISHED;
|
||||
}
|
||||
return HANDLER_GO_ON;
|
||||
}
|
||||
|
||||
/* the body starts after the EOL */
|
||||
bstart = b->ptr + (i + 1);
|
||||
blen = header_len - (i + 1);
|
||||
|
||||
/* strip the last \r?\n */
|
||||
if (i > 0 && (b->ptr[i - 1] == '\r')) {
|
||||
i--;
|
||||
}
|
||||
|
||||
buffer_string_set_length(b, i);
|
||||
|
||||
if (opts->backend == BACKEND_PROXY && !is_nph) {
|
||||
/* invalid response Status-Line from HTTP proxy */
|
||||
con->http_status = 502; /* Bad Gateway */
|
||||
con->mode = DIRECT;
|
||||
return HANDLER_FINISHED;
|
||||
}
|
||||
|
||||
if (0 != http_response_process_headers(srv, con, opts, b)) {
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
|
||||
con->file_started = 1;
|
||||
|
||||
if (opts->authorizer
|
||||
&& (con->http_status == 0 || con->http_status == 200)) {
|
||||
return HANDLER_GO_ON;
|
||||
}
|
||||
|
||||
if (con->mode == DIRECT) {
|
||||
return HANDLER_FINISHED;
|
||||
}
|
||||
|
||||
if (opts->local_redir && con->http_status >= 300 && con->http_status < 400){
|
||||
/*(con->parsed_response & HTTP_LOCATION)*/
|
||||
handler_t rc = http_response_process_local_redir(srv, con, blen);
|
||||
if (rc != HANDLER_GO_ON) return rc;
|
||||
}
|
||||
|
||||
if (opts->xsendfile_allow) {
|
||||
data_string *ds;
|
||||
/* X-Sendfile2 is deprecated; historical for fastcgi */
|
||||
if (opts->backend == BACKEND_FASTCGI
|
||||
&& NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-Sendfile2"))) {
|
||||
http_response_xsendfile2(srv, con, ds->value, opts->xsendfile_docroot);
|
||||
buffer_reset(ds->value); /*(do not send to client)*/
|
||||
return HANDLER_FINISHED;
|
||||
} else if (NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-Sendfile"))
|
||||
|| (opts->backend == BACKEND_FASTCGI /* X-LIGHTTPD-send-file is deprecated; historical for fastcgi */
|
||||
&& NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-LIGHTTPD-send-file")))) {
|
||||
http_response_xsendfile(srv, con, ds->value, opts->xsendfile_docroot);
|
||||
buffer_reset(ds->value); /*(do not send to client)*/
|
||||
return HANDLER_FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
if (blen > 0) {
|
||||
if (0 != http_chunk_append_mem(srv, con, bstart, blen)) {
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return HANDLER_GO_ON;
|
||||
}
|
||||
|
||||
|
||||
handler_t http_response_read(server *srv, connection *con, http_response_opts *opts, buffer *b, int fd, int *fde_ndx) {
|
||||
while (1) {
|
||||
ssize_t n;
|
||||
size_t avail = buffer_string_space(b);
|
||||
unsigned int toread = 4096;
|
||||
|
||||
#if !defined(_WIN32) && !defined(__CYGWIN__)
|
||||
if (0 == ioctl(fd, FIONREAD, (int *)&toread)) {
|
||||
if (avail < toread) {
|
||||
if (toread < 4096)
|
||||
toread = 4096;
|
||||
else if (toread > MAX_READ_LIMIT)
|
||||
toread = MAX_READ_LIMIT;
|
||||
}
|
||||
else if (0 == toread) {
|
||||
#if 0
|
||||
return (fdevent_event_get_interest(srv->ev, fd) & FDEVENT_IN)
|
||||
? HANDLER_FINISHED /* read finished */
|
||||
: HANDLER_GO_ON; /* optimistic read; data not ready */
|
||||
#else
|
||||
if (!(fdevent_event_get_interest(srv->ev, fd) & FDEVENT_IN))
|
||||
return HANDLER_GO_ON; /* optimistic read; data not ready */
|
||||
toread = 4096; /* let read() below indicate if EOF or EAGAIN */
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) {
|
||||
off_t cqlen = chunkqueue_length(con->write_queue);
|
||||
if (cqlen + (off_t)toread > 65536 - 4096) {
|
||||
if (!con->is_writable) {
|
||||
/*(defer removal of FDEVENT_IN interest since
|
||||
* connection_state_machine() might be able to send data
|
||||
* immediately, unless !con->is_writable, where
|
||||
* connection_state_machine() might not loop back to call
|
||||
* mod_proxy_handle_subrequest())*/
|
||||
fdevent_event_clr(srv->ev, fde_ndx, fd, FDEVENT_IN);
|
||||
}
|
||||
if (cqlen >= 65536-1) return HANDLER_GO_ON;
|
||||
toread = 65536 - 1 - (unsigned int)cqlen;
|
||||
}
|
||||
}
|
||||
|
||||
if (avail < toread) {
|
||||
avail = toread;
|
||||
buffer_string_prepare_append(b, avail);
|
||||
}
|
||||
|
||||
n = read(fd, b->ptr+buffer_string_length(b), avail);
|
||||
|
||||
if (0 == n) {
|
||||
return HANDLER_FINISHED; /* read finished */
|
||||
}
|
||||
else if (n < 0) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
#ifdef EWOULDBLOCK
|
||||
#if EWOULDBLOCK != EAGAIN
|
||||
case EWOULDBLOCK:
|
||||
#endif
|
||||
#endif
|
||||
case EINTR:
|
||||
return HANDLER_GO_ON;
|
||||
default:
|
||||
log_error_write(srv, __FILE__, __LINE__, "ssdd",
|
||||
"read():", strerror(errno), con->fd, fd);
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
buffer_commit(b, (size_t)n);
|
||||
|
||||
if (con->file_started == 0) {
|
||||
/* split header from body */
|
||||
handler_t rc = http_response_parse_headers(srv, con, opts, b);
|
||||
if (rc != HANDLER_GO_ON) return rc;
|
||||
/* accumulate response in b until headers completed (or error) */
|
||||
if (con->file_started) buffer_string_set_length(b, 0);
|
||||
} else {
|
||||
if (0 != http_chunk_append_buffer(srv, con, b)) {
|
||||
/* error writing to tempfile;
|
||||
* truncate response or send 500 if nothing sent yet */
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
buffer_string_set_length(b, 0);
|
||||
}
|
||||
|
||||
if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
|
||||
&& chunkqueue_length(con->write_queue) > 65536 - 4096) {
|
||||
if (!con->is_writable) {
|
||||
/*(defer removal of FDEVENT_IN interest since
|
||||
* connection_state_machine() might be able to send
|
||||
* data immediately, unless !con->is_writable, where
|
||||
* connection_state_machine() might not loop back to
|
||||
* call the subrequest handler)*/
|
||||
fdevent_event_clr(srv->ev, fde_ndx, fd, FDEVENT_IN);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return HANDLER_GO_ON;
|
||||
}
|
||||
|
||||
|
||||
int http_cgi_headers (server *srv, connection *con, http_cgi_opts *opts, http_cgi_header_append_cb cb, void *vdata) {
|
||||
|
||||
/* CGI-SPEC 6.1.2, FastCGI spec 6.3 and SCGI spec */
|
||||
|
|
423
src/mod_cgi.c
423
src/mod_cgi.c
|
@ -75,8 +75,6 @@ typedef struct {
|
|||
PLUGIN_DATA;
|
||||
buffer_pid_t cgi_pid;
|
||||
|
||||
buffer *parse_response;
|
||||
|
||||
plugin_config **config_storage;
|
||||
|
||||
plugin_config conf;
|
||||
|
@ -93,8 +91,8 @@ typedef struct {
|
|||
plugin_data *plugin_data; /* dumb pointer */
|
||||
|
||||
buffer *response;
|
||||
buffer *response_header;
|
||||
buffer *cgi_handler; /* dumb pointer */
|
||||
http_response_opts opts;
|
||||
plugin_config conf;
|
||||
} handler_ctx;
|
||||
|
||||
|
@ -104,7 +102,6 @@ static handler_ctx * cgi_handler_ctx_init(void) {
|
|||
force_assert(hctx);
|
||||
|
||||
hctx->response = buffer_init();
|
||||
hctx->response_header = buffer_init();
|
||||
hctx->fd = -1;
|
||||
hctx->fdtocgi = -1;
|
||||
|
||||
|
@ -113,13 +110,9 @@ static handler_ctx * cgi_handler_ctx_init(void) {
|
|||
|
||||
static void cgi_handler_ctx_free(handler_ctx *hctx) {
|
||||
buffer_free(hctx->response);
|
||||
buffer_free(hctx->response_header);
|
||||
|
||||
free(hctx);
|
||||
}
|
||||
|
||||
enum {FDEVENT_HANDLED_UNSET, FDEVENT_HANDLED_FINISHED, FDEVENT_HANDLED_NOT_FINISHED, FDEVENT_HANDLED_COMEBACK, FDEVENT_HANDLED_ERROR};
|
||||
|
||||
INIT_FUNC(mod_cgi_init) {
|
||||
plugin_data *p;
|
||||
|
||||
|
@ -127,8 +120,6 @@ INIT_FUNC(mod_cgi_init) {
|
|||
|
||||
force_assert(p);
|
||||
|
||||
p->parse_response = buffer_init();
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
@ -157,8 +148,6 @@ FREE_FUNC(mod_cgi_free) {
|
|||
|
||||
if (r->ptr) free(r->ptr);
|
||||
|
||||
buffer_free(p->parse_response);
|
||||
|
||||
free(p);
|
||||
|
||||
return HANDLER_GO_ON;
|
||||
|
@ -285,376 +274,6 @@ static int cgi_pid_del(server *srv, plugin_data *p, pid_t pid) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) {
|
||||
char *ns;
|
||||
const char *s;
|
||||
int line = 0;
|
||||
|
||||
UNUSED(srv);
|
||||
|
||||
buffer_copy_buffer(p->parse_response, in);
|
||||
|
||||
for (s = p->parse_response->ptr;
|
||||
NULL != (ns = strchr(s, '\n'));
|
||||
s = ns + 1, line++) {
|
||||
const char *key, *value;
|
||||
int key_len;
|
||||
data_string *ds;
|
||||
|
||||
/* strip the \n */
|
||||
ns[0] = '\0';
|
||||
|
||||
if (ns > s && ns[-1] == '\r') ns[-1] = '\0';
|
||||
|
||||
if (line == 0 &&
|
||||
0 == strncmp(s, "HTTP/1.", 7)) {
|
||||
/* non-parsed header ... we parse them anyway */
|
||||
|
||||
if ((s[7] == '1' ||
|
||||
s[7] == '0') &&
|
||||
s[8] == ' ') {
|
||||
int status;
|
||||
/* after the space should be a status code for us */
|
||||
|
||||
status = strtol(s+9, NULL, 10);
|
||||
|
||||
if (status >= 100 &&
|
||||
status < 1000) {
|
||||
/* we expected 3 digits and didn't got them */
|
||||
con->parsed_response |= HTTP_STATUS;
|
||||
con->http_status = status;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* parse the headers */
|
||||
key = s;
|
||||
if (NULL == (value = strchr(s, ':'))) {
|
||||
/* we expect: "<key>: <value>\r\n" */
|
||||
continue;
|
||||
}
|
||||
|
||||
key_len = value - key;
|
||||
value += 1;
|
||||
|
||||
/* skip LWS */
|
||||
while (*value == ' ' || *value == '\t') value++;
|
||||
|
||||
if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
|
||||
ds = data_response_init();
|
||||
}
|
||||
buffer_copy_string_len(ds->key, key, key_len);
|
||||
buffer_copy_string(ds->value, value);
|
||||
|
||||
array_insert_unique(con->response.headers, (data_unset *)ds);
|
||||
|
||||
switch(key_len) {
|
||||
case 4:
|
||||
if (0 == strncasecmp(key, "Date", key_len)) {
|
||||
con->parsed_response |= HTTP_DATE;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if (0 == strncasecmp(key, "Status", key_len)) {
|
||||
int status = strtol(value, NULL, 10);
|
||||
if (status >= 100 && status < 1000) {
|
||||
con->http_status = status;
|
||||
con->parsed_response |= HTTP_STATUS;
|
||||
} else {
|
||||
con->http_status = 502;
|
||||
}
|
||||
/* do not send Status to client */
|
||||
buffer_reset(ds->value);
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
if (0 == strncasecmp(key, "Location", key_len)) {
|
||||
con->parsed_response |= HTTP_LOCATION;
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
if (0 == strncasecmp(key, "Connection", key_len)) {
|
||||
con->response.keep_alive = (0 == strcasecmp(value, "Keep-Alive")) ? 1 : 0;
|
||||
con->parsed_response |= HTTP_CONNECTION;
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
if (0 == strncasecmp(key, "Content-Length", key_len)) {
|
||||
con->response.content_length = strtoul(value, NULL, 10);
|
||||
con->parsed_response |= HTTP_CONTENT_LENGTH;
|
||||
}
|
||||
break;
|
||||
case 17:
|
||||
if (0 == strncasecmp(key, "Transfer-Encoding", key_len)) {
|
||||
con->parsed_response |= HTTP_TRANSFER_ENCODING;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* CGI/1.1 rev 03 - 7.2.1.2 */
|
||||
if ((con->parsed_response & HTTP_LOCATION) &&
|
||||
!(con->parsed_response & HTTP_STATUS)) {
|
||||
con->http_status = 302;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int cgi_demux_response(server *srv, handler_ctx *hctx) {
|
||||
plugin_data *p = hctx->plugin_data;
|
||||
connection *con = hctx->remote_conn;
|
||||
|
||||
while(1) {
|
||||
int n;
|
||||
int toread;
|
||||
|
||||
#if defined(__WIN32)
|
||||
buffer_string_prepare_copy(hctx->response, 4 * 1024);
|
||||
#else
|
||||
if (ioctl(hctx->fd, FIONREAD, &toread) || toread <= 4*1024) {
|
||||
buffer_string_prepare_copy(hctx->response, 4 * 1024);
|
||||
} else {
|
||||
if (toread > MAX_READ_LIMIT) toread = MAX_READ_LIMIT;
|
||||
buffer_string_prepare_copy(hctx->response, toread);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) {
|
||||
if (errno == EAGAIN || errno == EINTR) {
|
||||
/* would block, wait for signal */
|
||||
return FDEVENT_HANDLED_NOT_FINISHED;
|
||||
}
|
||||
/* error */
|
||||
log_error_write(srv, __FILE__, __LINE__, "sdd", strerror(errno), con->fd, hctx->fd);
|
||||
return FDEVENT_HANDLED_ERROR;
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
/* read finished */
|
||||
return FDEVENT_HANDLED_FINISHED;
|
||||
}
|
||||
|
||||
buffer_commit(hctx->response, n);
|
||||
|
||||
/* split header from body */
|
||||
|
||||
if (con->file_started == 0) {
|
||||
int is_header = 0;
|
||||
int is_header_end = 0;
|
||||
size_t last_eol = 0;
|
||||
size_t i, header_len;
|
||||
|
||||
buffer_append_string_buffer(hctx->response_header, hctx->response);
|
||||
|
||||
/**
|
||||
* we have to handle a few cases:
|
||||
*
|
||||
* nph:
|
||||
*
|
||||
* HTTP/1.0 200 Ok\n
|
||||
* Header: Value\n
|
||||
* \n
|
||||
*
|
||||
* CGI:
|
||||
* Header: Value\n
|
||||
* Status: 200\n
|
||||
* \n
|
||||
*
|
||||
* and different mixes of \n and \r\n combinations
|
||||
*
|
||||
* Some users also forget about CGI and just send a response and hope
|
||||
* we handle it. No headers, no header-content seperator
|
||||
*
|
||||
*/
|
||||
|
||||
/* nph (non-parsed headers) */
|
||||
if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) is_header = 1;
|
||||
|
||||
header_len = buffer_string_length(hctx->response_header);
|
||||
for (i = 0; !is_header_end && i < header_len; i++) {
|
||||
char c = hctx->response_header->ptr[i];
|
||||
|
||||
switch (c) {
|
||||
case ':':
|
||||
/* we found a colon
|
||||
*
|
||||
* looks like we have a normal header
|
||||
*/
|
||||
is_header = 1;
|
||||
break;
|
||||
case '\n':
|
||||
/* EOL */
|
||||
if (is_header == 0) {
|
||||
/* we got a EOL but we don't seem to got a HTTP header */
|
||||
|
||||
is_header_end = 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if we saw a \n(\r)?\n sequence
|
||||
*/
|
||||
if (last_eol > 0 &&
|
||||
((i - last_eol == 1) ||
|
||||
(i - last_eol == 2 && hctx->response_header->ptr[i - 1] == '\r'))) {
|
||||
is_header_end = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
last_eol = i;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_header_end) {
|
||||
if (!is_header) {
|
||||
/* no header, but a body */
|
||||
if (0 != http_chunk_append_buffer(srv, con, hctx->response_header)) {
|
||||
return FDEVENT_HANDLED_ERROR;
|
||||
}
|
||||
if (0 == con->http_status) con->http_status = 200; /* OK */
|
||||
} else {
|
||||
const char *bstart;
|
||||
size_t blen;
|
||||
|
||||
/* the body starts after the EOL */
|
||||
bstart = hctx->response_header->ptr + i;
|
||||
blen = header_len - i;
|
||||
|
||||
/**
|
||||
* i still points to the char after the terminating EOL EOL
|
||||
*
|
||||
* put it on the last \n again
|
||||
*/
|
||||
i--;
|
||||
|
||||
/* string the last \r?\n */
|
||||
if (i > 0 && (hctx->response_header->ptr[i - 1] == '\r')) {
|
||||
i--;
|
||||
}
|
||||
|
||||
buffer_string_set_length(hctx->response_header, i);
|
||||
|
||||
/* parse the response header */
|
||||
cgi_response_parse(srv, con, p, hctx->response_header);
|
||||
|
||||
/* [RFC3875] 6.2.2 Local Redirect Response
|
||||
*
|
||||
* The CGI script can return a URI path and query-string
|
||||
* ('local-pathquery') for a local resource in a Location header field.
|
||||
* This indicates to the server that it should reprocess the request
|
||||
* using the path specified.
|
||||
*
|
||||
* local-redir-response = local-Location NL
|
||||
*
|
||||
* The script MUST NOT return any other header fields or a message-body,
|
||||
* and the server MUST generate the response that it would have produced
|
||||
* in response to a request containing the URL
|
||||
*
|
||||
* scheme "://" server-name ":" server-port local-pathquery
|
||||
*
|
||||
* (Might not have begun to receive body yet, but do skip local-redir
|
||||
* if we already have started receiving a response body (blen > 0))
|
||||
* (Also, while not required by the RFC, do not send local-redir back
|
||||
* to same URL, since CGI should have handled it internally if it
|
||||
* really wanted to do that internally)
|
||||
*/
|
||||
if (hctx->conf.local_redir && con->http_status >= 300 && con->http_status < 400) {
|
||||
/*(con->parsed_response & HTTP_LOCATION)*/
|
||||
size_t ulen = buffer_string_length(con->uri.path);
|
||||
data_string *ds;
|
||||
if (NULL != (ds = (data_string *) array_get_element(con->response.headers, "Location"))
|
||||
&& ds->value->ptr[0] == '/'
|
||||
&& (0 != strncmp(ds->value->ptr, con->uri.path->ptr, ulen)
|
||||
|| (ds->value->ptr[ulen] != '\0' && ds->value->ptr[ulen] != '/' && ds->value->ptr[ulen] != '?'))
|
||||
&& 0 == blen
|
||||
&& !(con->parsed_response & HTTP_STATUS) /* no "Status" or NPH response line */
|
||||
&& 1 == con->response.headers->used) {
|
||||
if (++con->loops_per_request > 5) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "sb", "too many internal loops while processing request:", con->request.orig_uri);
|
||||
con->http_status = 500; /* Internal Server Error */
|
||||
con->mode = DIRECT;
|
||||
return FDEVENT_HANDLED_FINISHED;
|
||||
}
|
||||
|
||||
buffer_copy_buffer(con->request.uri, ds->value);
|
||||
|
||||
if (con->request.content_length) {
|
||||
if (con->request.content_length != con->request_content_queue->bytes_in) {
|
||||
con->keep_alive = 0;
|
||||
}
|
||||
con->request.content_length = 0;
|
||||
chunkqueue_reset(con->request_content_queue);
|
||||
}
|
||||
|
||||
if (con->http_status != 307 && con->http_status != 308) {
|
||||
/* Note: request body (if any) sent to initial dynamic handler
|
||||
* and is not available to the internal redirect */
|
||||
con->request.http_method = HTTP_METHOD_GET;
|
||||
}
|
||||
|
||||
connection_response_reset(srv, con); /*(includes con->http_status = 0)*/
|
||||
plugins_call_connection_reset(srv, con);
|
||||
return FDEVENT_HANDLED_COMEBACK;
|
||||
}
|
||||
}
|
||||
|
||||
if (hctx->conf.xsendfile_allow) {
|
||||
data_string *ds;
|
||||
if (NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-Sendfile"))) {
|
||||
http_response_xsendfile(srv, con, ds->value, hctx->conf.xsendfile_docroot);
|
||||
return FDEVENT_HANDLED_FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
if (blen > 0) {
|
||||
if (0 != http_chunk_append_mem(srv, con, bstart, blen)) {
|
||||
return FDEVENT_HANDLED_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
con->file_started = 1;
|
||||
} else {
|
||||
/*(reuse MAX_HTTP_REQUEST_HEADER as max size for response headers from backends)*/
|
||||
if (header_len > MAX_HTTP_REQUEST_HEADER) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "sb", "response headers too large for", con->uri.path);
|
||||
con->http_status = 502; /* Bad Gateway */
|
||||
con->mode = DIRECT;
|
||||
return FDEVENT_HANDLED_FINISHED;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (0 != http_chunk_append_buffer(srv, con, hctx->response)) {
|
||||
return FDEVENT_HANDLED_ERROR;
|
||||
}
|
||||
if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
|
||||
&& chunkqueue_length(con->write_queue) > 65536 - 4096) {
|
||||
if (!con->is_writable) {
|
||||
/*(defer removal of FDEVENT_IN interest since
|
||||
* connection_state_machine() might be able to send data
|
||||
* immediately, unless !con->is_writable, where
|
||||
* connection_state_machine() might not loop back to call
|
||||
* mod_cgi_handle_subrequest())*/
|
||||
fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), b->ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
return FDEVENT_HANDLED_NOT_FINISHED;
|
||||
}
|
||||
|
||||
static void cgi_connection_close_fdtocgi(server *srv, handler_ctx *hctx) {
|
||||
/*(closes only hctx->fdtocgi)*/
|
||||
|
@ -805,30 +424,23 @@ static handler_t cgi_handle_fdevent_send (server *srv, void *ctx, int revents) {
|
|||
|
||||
|
||||
static int cgi_recv_response(server *srv, handler_ctx *hctx) {
|
||||
switch (cgi_demux_response(srv, hctx)) {
|
||||
case FDEVENT_HANDLED_NOT_FINISHED:
|
||||
break;
|
||||
case FDEVENT_HANDLED_FINISHED:
|
||||
/* we are done */
|
||||
|
||||
#if 0
|
||||
log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), "finished");
|
||||
#endif
|
||||
switch (http_response_read(srv, hctx->remote_conn, &hctx->opts,
|
||||
hctx->response, hctx->fd, &hctx->fde_ndx)) {
|
||||
default:
|
||||
return HANDLER_GO_ON;
|
||||
case HANDLER_ERROR:
|
||||
http_response_backend_error(srv, hctx->remote_conn);
|
||||
/* fall through */
|
||||
case HANDLER_FINISHED:
|
||||
cgi_connection_close(srv, hctx);
|
||||
|
||||
/* if we get a IN|HUP and have read everything don't exec the close twice */
|
||||
return HANDLER_FINISHED;
|
||||
case FDEVENT_HANDLED_COMEBACK:
|
||||
case HANDLER_COMEBACK:
|
||||
/* hctx->conf.local_redir */
|
||||
connection_response_reset(srv, hctx->remote_conn); /*(includes con->http_status = 0)*/
|
||||
plugins_call_connection_reset(srv, hctx->remote_conn);
|
||||
/*cgi_connection_close(srv, hctx);*//*(already cleaned up and hctx is now invalid)*/
|
||||
return HANDLER_COMEBACK;
|
||||
case FDEVENT_HANDLED_ERROR:
|
||||
log_error_write(srv, __FILE__, __LINE__, "s", "demuxer failed: ");
|
||||
|
||||
cgi_connection_close(srv, hctx);
|
||||
return HANDLER_FINISHED;
|
||||
}
|
||||
|
||||
return HANDLER_GO_ON;
|
||||
}
|
||||
|
||||
|
||||
|
@ -856,10 +468,10 @@ static handler_t cgi_handle_fdevent(server *srv, void *ctx, int revents) {
|
|||
rc = cgi_recv_response(srv,hctx);/*(might invalidate hctx)*/
|
||||
} while (rc == HANDLER_GO_ON); /*(unless HANDLER_GO_ON)*/
|
||||
return rc; /* HANDLER_FINISHED or HANDLER_COMEBACK or HANDLER_ERROR */
|
||||
} else if (!buffer_string_is_empty(hctx->response_header)) {
|
||||
} else if (!buffer_string_is_empty(hctx->response)) {
|
||||
/* unfinished header package which is a body in reality */
|
||||
con->file_started = 1;
|
||||
if (0 != http_chunk_append_buffer(srv, con, hctx->response_header)) {
|
||||
if (0 != http_chunk_append_buffer(srv, con, hctx->response)) {
|
||||
cgi_connection_close(srv, hctx);
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
|
@ -1403,6 +1015,11 @@ URIHANDLER_FUNC(cgi_is_handled) {
|
|||
hctx->plugin_data = p;
|
||||
hctx->cgi_handler = cgi_handler;
|
||||
memcpy(&hctx->conf, &p->conf, sizeof(plugin_config));
|
||||
hctx->opts.backend = BACKEND_CGI;
|
||||
hctx->opts.authorizer = 0;
|
||||
hctx->opts.local_redir = hctx->conf.local_redir;
|
||||
hctx->opts.xsendfile_allow = hctx->conf.xsendfile_allow;
|
||||
hctx->opts.xsendfile_docroot = hctx->conf.xsendfile_docroot;
|
||||
con->plugin_ctx[p->id] = hctx;
|
||||
con->mode = p->id;
|
||||
}
|
||||
|
|
|
@ -342,6 +342,7 @@ typedef struct {
|
|||
int request_id;
|
||||
int send_content_body;
|
||||
|
||||
http_response_opts opts;
|
||||
plugin_config conf;
|
||||
|
||||
connection *remote_conn; /* dumb pointer */
|
||||
|
@ -457,7 +458,7 @@ static handler_ctx * handler_ctx_init(void) {
|
|||
|
||||
hctx->fde_ndx = -1;
|
||||
|
||||
hctx->response_header = buffer_init();
|
||||
/*hctx->response_header = buffer_init();*//*(allocated when needed)*/
|
||||
|
||||
hctx->request_id = 0;
|
||||
hctx->fcgi_mode = FCGI_RESPONDER;
|
||||
|
@ -1751,6 +1752,8 @@ static handler_t fcgi_reconnect(server *srv, handler_ctx *hctx) {
|
|||
|
||||
fcgi_host_assign(srv, hctx, hctx->host);
|
||||
hctx->request_id = 0;
|
||||
hctx->opts.xsendfile_allow = hctx->host->xsendfile_allow;
|
||||
hctx->opts.xsendfile_docroot = hctx->host->xsendfile_docroot;
|
||||
fcgi_set_state(srv, hctx, FCGI_STATE_INIT);
|
||||
return HANDLER_COMEBACK;
|
||||
}
|
||||
|
@ -2054,257 +2057,6 @@ static int fcgi_create_env(server *srv, handler_ctx *hctx, int request_id) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int fcgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) {
|
||||
char *s, *ns;
|
||||
|
||||
handler_ctx *hctx = con->plugin_ctx[p->id];
|
||||
fcgi_extension_host *host= hctx->host;
|
||||
int have_sendfile2 = 0;
|
||||
off_t sendfile2_content_length = 0;
|
||||
|
||||
UNUSED(srv);
|
||||
|
||||
/* search for \n */
|
||||
for (s = in->ptr; NULL != (ns = strchr(s, '\n')); s = ns + 1) {
|
||||
char *key, *value;
|
||||
int key_len;
|
||||
|
||||
/* a good day. Someone has read the specs and is sending a \r\n to us */
|
||||
|
||||
if (ns > in->ptr &&
|
||||
*(ns-1) == '\r') {
|
||||
*(ns-1) = '\0';
|
||||
}
|
||||
|
||||
ns[0] = '\0';
|
||||
|
||||
key = s;
|
||||
if (NULL == (value = strchr(s, ':'))) {
|
||||
/* we expect: "<key>: <value>\n" */
|
||||
continue;
|
||||
}
|
||||
|
||||
key_len = value - key;
|
||||
|
||||
value++;
|
||||
/* strip WS */
|
||||
while (*value == ' ' || *value == '\t') value++;
|
||||
|
||||
if (hctx->fcgi_mode != FCGI_AUTHORIZER ||
|
||||
!(con->http_status == 0 ||
|
||||
con->http_status == 200)) {
|
||||
/* authorizers shouldn't affect the response headers sent back to the client */
|
||||
|
||||
/* don't forward Status: */
|
||||
if (key_len != sizeof("Status")-1 || 0 != strncasecmp(key, "Status", key_len)) {
|
||||
data_string *ds;
|
||||
if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
|
||||
ds = data_response_init();
|
||||
}
|
||||
buffer_copy_string_len(ds->key, key, key_len);
|
||||
buffer_copy_string(ds->value, value);
|
||||
|
||||
array_insert_unique(con->response.headers, (data_unset *)ds);
|
||||
}
|
||||
}
|
||||
|
||||
if (hctx->fcgi_mode == FCGI_AUTHORIZER &&
|
||||
key_len > 9 &&
|
||||
0 == strncasecmp(key, CONST_STR_LEN("Variable-"))) {
|
||||
data_string *ds;
|
||||
if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) {
|
||||
ds = data_response_init();
|
||||
}
|
||||
buffer_copy_string_len(ds->key, key + 9, key_len - 9);
|
||||
buffer_copy_string(ds->value, value);
|
||||
|
||||
array_insert_unique(con->environment, (data_unset *)ds);
|
||||
}
|
||||
|
||||
switch(key_len) {
|
||||
case 4:
|
||||
if (0 == strncasecmp(key, "Date", key_len)) {
|
||||
con->parsed_response |= HTTP_DATE;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if (0 == strncasecmp(key, "Status", key_len)) {
|
||||
int status = strtol(value, NULL, 10);
|
||||
if (status >= 100 && status < 1000) {
|
||||
con->http_status = status;
|
||||
con->parsed_response |= HTTP_STATUS;
|
||||
} else {
|
||||
con->http_status = 502;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
if (0 == strncasecmp(key, "Location", key_len)) {
|
||||
con->parsed_response |= HTTP_LOCATION;
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
if (0 == strncasecmp(key, "Connection", key_len)) {
|
||||
con->response.keep_alive = (0 == strcasecmp(value, "Keep-Alive")) ? 1 : 0;
|
||||
con->parsed_response |= HTTP_CONNECTION;
|
||||
}
|
||||
break;
|
||||
case 11:
|
||||
if (host->xsendfile_allow && 0 == strncasecmp(key, "X-Sendfile2", key_len) && hctx->send_content_body) {
|
||||
char *pos = value;
|
||||
have_sendfile2 = 1;
|
||||
|
||||
while (*pos) {
|
||||
char *filename, *range;
|
||||
stat_cache_entry *sce;
|
||||
off_t begin_range, end_range, range_len;
|
||||
|
||||
while (' ' == *pos) pos++;
|
||||
if (!*pos) break;
|
||||
|
||||
filename = pos;
|
||||
if (NULL == (range = strchr(pos, ' '))) {
|
||||
/* missing range */
|
||||
if (hctx->conf.debug) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "ss", "Couldn't find range after filename:", filename);
|
||||
}
|
||||
return 502;
|
||||
}
|
||||
buffer_copy_string_len(srv->tmp_buf, filename, range - filename);
|
||||
|
||||
/* find end of range */
|
||||
for (pos = ++range; *pos && *pos != ' ' && *pos != ','; pos++) ;
|
||||
|
||||
buffer_urldecode_path(srv->tmp_buf);
|
||||
buffer_path_simplify(srv->tmp_buf, srv->tmp_buf);
|
||||
if (con->conf.force_lowercase_filenames) {
|
||||
buffer_to_lower(srv->tmp_buf);
|
||||
}
|
||||
if (host->xsendfile_docroot->used) {
|
||||
size_t i, xlen = buffer_string_length(srv->tmp_buf);
|
||||
for (i = 0; i < host->xsendfile_docroot->used; ++i) {
|
||||
data_string *ds = (data_string *)host->xsendfile_docroot->data[i];
|
||||
size_t dlen = buffer_string_length(ds->value);
|
||||
if (dlen <= xlen
|
||||
&& (!con->conf.force_lowercase_filenames
|
||||
? 0 == memcmp(srv->tmp_buf->ptr, ds->value->ptr, dlen)
|
||||
: 0 == strncasecmp(srv->tmp_buf->ptr, ds->value->ptr, dlen))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == host->xsendfile_docroot->used) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "SBs",
|
||||
"X-Sendfile2 (", srv->tmp_buf,
|
||||
") not under configured x-sendfile-docroot(s)");
|
||||
return 403;
|
||||
}
|
||||
}
|
||||
|
||||
if (HANDLER_ERROR == stat_cache_get_entry(srv, con, srv->tmp_buf, &sce)) {
|
||||
if (hctx->conf.debug) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "sb",
|
||||
"send-file error: couldn't get stat_cache entry for X-Sendfile2:",
|
||||
srv->tmp_buf);
|
||||
}
|
||||
return 404;
|
||||
} else if (!S_ISREG(sce->st.st_mode)) {
|
||||
if (hctx->conf.debug) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "sb",
|
||||
"send-file error: wrong filetype for X-Sendfile2:",
|
||||
srv->tmp_buf);
|
||||
}
|
||||
return 502;
|
||||
}
|
||||
/* found the file */
|
||||
|
||||
/* parse range */
|
||||
end_range = sce->st.st_size - 1;
|
||||
{
|
||||
char *rpos = NULL;
|
||||
errno = 0;
|
||||
begin_range = strtoll(range, &rpos, 10);
|
||||
if (errno != 0 || begin_range < 0 || rpos == range) goto range_failed;
|
||||
if ('-' != *rpos++) goto range_failed;
|
||||
if (rpos != pos) {
|
||||
range = rpos;
|
||||
end_range = strtoll(range, &rpos, 10);
|
||||
if (errno != 0 || end_range < 0 || rpos == range) goto range_failed;
|
||||
}
|
||||
if (rpos != pos) goto range_failed;
|
||||
|
||||
goto range_success;
|
||||
|
||||
range_failed:
|
||||
if (hctx->conf.debug) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "ss", "Couldn't decode range after filename:", filename);
|
||||
}
|
||||
return 502;
|
||||
|
||||
range_success: ;
|
||||
}
|
||||
|
||||
/* no parameters accepted */
|
||||
|
||||
while (*pos == ' ') pos++;
|
||||
if (*pos != '\0' && *pos != ',') return 502;
|
||||
|
||||
range_len = end_range - begin_range + 1;
|
||||
if (range_len < 0) return 502;
|
||||
if (range_len != 0) {
|
||||
if (0 != http_chunk_append_file_range(srv, con, srv->tmp_buf, begin_range, range_len)) {
|
||||
return 502;
|
||||
}
|
||||
}
|
||||
sendfile2_content_length += range_len;
|
||||
|
||||
if (*pos == ',') pos++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
if (0 == strncasecmp(key, "Content-Length", key_len)) {
|
||||
con->response.content_length = strtoul(value, NULL, 10);
|
||||
con->parsed_response |= HTTP_CONTENT_LENGTH;
|
||||
|
||||
if (con->response.content_length < 0) con->response.content_length = 0;
|
||||
}
|
||||
break;
|
||||
case 17:
|
||||
if (0 == strncasecmp(key, "Transfer-Encoding", key_len)) {
|
||||
con->parsed_response |= HTTP_TRANSFER_ENCODING;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (have_sendfile2) {
|
||||
data_string *dcls;
|
||||
|
||||
/* fix content-length */
|
||||
if (NULL == (dcls = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
|
||||
dcls = data_response_init();
|
||||
}
|
||||
|
||||
buffer_copy_string_len(dcls->key, "Content-Length", sizeof("Content-Length")-1);
|
||||
buffer_copy_int(dcls->value, sendfile2_content_length);
|
||||
array_replace(con->response.headers, (data_unset *)dcls);
|
||||
|
||||
con->parsed_response |= HTTP_CONTENT_LENGTH;
|
||||
con->response.content_length = sendfile2_content_length;
|
||||
return 200;
|
||||
}
|
||||
|
||||
/* CGI/1.1 rev 03 - 7.2.1.2 */
|
||||
if ((con->parsed_response & HTTP_LOCATION) &&
|
||||
!(con->parsed_response & HTTP_STATUS)) {
|
||||
con->http_status = 302;
|
||||