[mod_cgi,mod_fastcgi,mod_scgi] X-Sendfile features

[core] http_response_send_file() shared code (#2017)
[mod_fastcgi] use http_response_xsendfile()
  (fixes #799, fixes #851, fixes #2017, fixes #2076)
[mod_scgi] X-Sendfile feature (fixes #2253)
[mod_cgi] X-Sendfile feature (fixes #2313)

Merge branch 'feature-2017-http_response_send_file' into master

github: closes #59
This commit is contained in:
Glenn Strauss 2016-04-28 11:28:27 -04:00
commit c380d22729
7 changed files with 560 additions and 380 deletions

View File

@ -107,7 +107,8 @@ fastcgi.server
"max-procs" => <integer>, # OPTIONAL
"broken-scriptfilename" => <boolean>, # OPTIONAL
"disable-time" => <integer>, # optional
"allow-x-send-file" => <boolean>, # optional
"x-sendfile" => <boolean>, # optional (replaces "allow-x-send-file")
"x-sendfile-docroot" => <boolean>, # optional
"kill-signal" => <integer>, # OPTIONAL
"fix-root-scriptname" => <boolean>,
# OPTIONAL
@ -143,8 +144,10 @@ fastcgi.server
PHP can extract PATH_INFO from it (default: disabled)
:"disable-time": time to wait before a disabled backend is checked
again
:"allow-x-send-file": controls if X-LIGHTTPD-send-file headers
are allowed
:"x-sendfile": controls if X-Sendfile backend response header is allowed
(deprecated headers: X-Sendfile2 and X-LIGHTTPD-send-file)
("x-sendfile" replaces "allow-x-sendfile")
:"x-sendfile-docroot": list of directory trees permitted with X-Sendfile
:"fix-root-scriptname": fix broken path-info split for "/" extension ("prefix")
If bin-path is set:

View File

@ -5,7 +5,9 @@
#include "buffer.h"
#include "log.h"
#include "etag.h"
#include "http_chunk.h"
#include "response.h"
#include "stat_cache.h"
#include <string.h>
#include <errno.h>
@ -323,3 +325,400 @@ int http_response_handle_cachable(server *srv, connection *con, buffer *mtime) {
return HANDLER_GO_ON;
}
static int http_response_parse_range(server *srv, connection *con, buffer *path, stat_cache_entry *sce) {
int multipart = 0;
int error;
off_t start, end;
const char *s, *minus;
char *boundary = "fkj49sn38dcn3";
data_string *ds;
buffer *content_type = NULL;
start = 0;
end = sce->st.st_size - 1;
con->response.content_length = 0;
if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) {
content_type = ds->value;
}
for (s = con->request.http_range, error = 0;
!error && *s && NULL != (minus = strchr(s, '-')); ) {
char *err;
off_t la, le;
if (s == minus) {
/* -<stop> */
le = strtoll(s, &err, 10);
if (le == 0) {
/* RFC 2616 - 14.35.1 */
con->http_status = 416;
error = 1;
} else if (*err == '\0') {
/* end */
s = err;
end = sce->st.st_size - 1;
start = sce->st.st_size + le;
} else if (*err == ',') {
multipart = 1;
s = err + 1;
end = sce->st.st_size - 1;
start = sce->st.st_size + le;
} else {
error = 1;
}
} else if (*(minus+1) == '\0' || *(minus+1) == ',') {
/* <start>- */
la = strtoll(s, &err, 10);
if (err == minus) {
/* ok */
if (*(err + 1) == '\0') {
s = err + 1;
end = sce->st.st_size - 1;
start = la;
} else if (*(err + 1) == ',') {
multipart = 1;
s = err + 2;
end = sce->st.st_size - 1;
start = la;
} else {
error = 1;
}
} else {
/* error */
error = 1;
}
} else {
/* <start>-<stop> */
la = strtoll(s, &err, 10);
if (err == minus) {
le = strtoll(minus+1, &err, 10);
/* RFC 2616 - 14.35.1 */
if (la > le) {
error = 1;
}
if (*err == '\0') {
/* ok, end*/
s = err;
end = le;
start = la;
} else if (*err == ',') {
multipart = 1;
s = err + 1;
end = le;
start = la;
} else {
/* error */
error = 1;
}
} else {
/* error */
error = 1;
}
}
if (!error) {
if (start < 0) start = 0;
/* RFC 2616 - 14.35.1 */
if (end > sce->st.st_size - 1) end = sce->st.st_size - 1;
if (start > sce->st.st_size - 1) {
error = 1;
con->http_status = 416;
}
}
if (!error) {
if (multipart) {
/* write boundary-header */
buffer *b = buffer_init();
buffer_copy_string_len(b, CONST_STR_LEN("\r\n--"));
buffer_append_string(b, boundary);
/* write Content-Range */
buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Range: bytes "));
buffer_append_int(b, start);
buffer_append_string_len(b, CONST_STR_LEN("-"));
buffer_append_int(b, end);
buffer_append_string_len(b, CONST_STR_LEN("/"));
buffer_append_int(b, sce->st.st_size);
buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Type: "));
buffer_append_string_buffer(b, content_type);
/* write END-OF-HEADER */
buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n"));
con->response.content_length += buffer_string_length(b);
chunkqueue_append_buffer(con->write_queue, b);
buffer_free(b);
}
chunkqueue_append_file(con->write_queue, path, start, end - start + 1);
con->response.content_length += end - start + 1;
}
}
/* something went wrong */
if (error) return -1;
if (multipart) {
/* add boundary end */
buffer *b = buffer_init();
buffer_copy_string_len(b, "\r\n--", 4);
buffer_append_string(b, boundary);
buffer_append_string_len(b, "--\r\n", 4);
con->response.content_length += buffer_string_length(b);
chunkqueue_append_buffer(con->write_queue, b);
buffer_free(b);
/* set header-fields */
buffer_copy_string_len(srv->tmp_buf, CONST_STR_LEN("multipart/byteranges; boundary="));
buffer_append_string(srv->tmp_buf, boundary);
/* overwrite content-type */
response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(srv->tmp_buf));
} else {
/* add Content-Range-header */
buffer_copy_string_len(srv->tmp_buf, CONST_STR_LEN("bytes "));
buffer_append_int(srv->tmp_buf, start);
buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("-"));
buffer_append_int(srv->tmp_buf, end);
buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("/"));
buffer_append_int(srv->tmp_buf, sce->st.st_size);
response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(srv->tmp_buf));
}
/* ok, the file is set-up */
return 0;
}
void http_response_send_file (server *srv, connection *con, buffer *path) {
stat_cache_entry *sce = NULL;
buffer *mtime = NULL;
data_string *ds;
int allow_caching = (0 == con->http_status || 200 == con->http_status);
if (HANDLER_ERROR == stat_cache_get_entry(srv, con, path, &sce)) {
con->http_status = (errno == ENOENT) ? 404 : 403;
log_error_write(srv, __FILE__, __LINE__, "sbsb",
"not a regular file:", con->uri.path,
"->", path);
return;
}
/* we only handline regular files */
#ifdef HAVE_LSTAT
if ((sce->is_symlink == 1) && !con->conf.follow_symlink) {
con->http_status = 403;
if (con->conf.log_request_handling) {
log_error_write(srv, __FILE__, __LINE__, "s", "-- access denied due symlink restriction");
log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", path);
}
return;
}
#endif
if (!S_ISREG(sce->st.st_mode)) {
con->http_status = 403;
if (con->conf.log_file_not_found) {
log_error_write(srv, __FILE__, __LINE__, "sbsb",
"not a regular file:", con->uri.path,
"->", sce->name);
}
return;
}
/* mod_compress might set several data directly, don't overwrite them */
/* set response content-type, if not set already */
if (NULL == array_get_element(con->response.headers, "Content-Type")) {
if (buffer_string_is_empty(sce->content_type)) {
/* we are setting application/octet-stream, but also announce that
* this header field might change in the seconds few requests
*
* This should fix the aggressive caching of FF and the script download
* seen by the first installations
*/
response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
allow_caching = 0;
} else {
response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
}
}
if (con->conf.range_requests) {
response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes"));
}
if (allow_caching) {
if (con->etag_flags != 0 && !buffer_string_is_empty(sce->etag)) {
if (NULL == array_get_element(con->response.headers, "ETag")) {
/* generate e-tag */
etag_mutate(con->physical.etag, sce->etag);
response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
}
}
/* prepare header */
if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) {
mtime = strftime_cache_get(srv, sce->st.st_mtime);
response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
} else {
mtime = ds->value;
}
if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
return;
}
}
if (con->request.http_range && con->conf.range_requests
&& (200 == con->http_status || 0 == con->http_status)
&& NULL == array_get_element(con->response.headers, "Content-Encoding")) {
int do_range_request = 1;
/* check if we have a conditional GET */
if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If-Range"))) {
/* if the value is the same as our ETag, we do a Range-request,
* otherwise a full 200 */
if (ds->value->ptr[0] == '"') {
/**
* client wants a ETag
*/
if (!con->physical.etag) {
do_range_request = 0;
} else if (!buffer_is_equal(ds->value, con->physical.etag)) {
do_range_request = 0;
}
} else if (!mtime) {
/**
* we don't have a Last-Modified and can match the If-Range:
*
* sending all
*/
do_range_request = 0;
} else if (!buffer_is_equal(ds->value, mtime)) {
do_range_request = 0;
}
}
if (do_range_request) {
/* content prepared, I'm done */
con->file_finished = 1;
if (0 == http_response_parse_range(srv, con, path, sce)) {
con->http_status = 206;
}
return;
}
}
/* if we are still here, prepare body */
/* we add it here for all requests
* the HEAD request will drop it afterwards again
*/
if (0 == sce->st.st_size || 0 == http_chunk_append_file(srv, con, path)) {
con->http_status = 200;
con->file_finished = 1;
} else {
con->http_status = 403;
}
}
void http_response_xsendfile (server *srv, connection *con, buffer *path, const array *xdocroot) {
const int status = con->http_status;
int valid = 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
* is modified between stat() (stat_cache_get_entry()) and open(). */
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;
}
buffer_urldecode_path(path);
buffer_path_simplify(path, path);
if (con->conf.force_lowercase_filenames) {
buffer_to_lower(path);
}
/* check that path is under xdocroot(s)
* - xdocroot should have trailing slash appended at config time
* - con->conf.force_lowercase_filenames is not a server-wide setting,
* and so can not be definitively applied to xdocroot at config time*/
if (xdocroot->used) {
size_t i, xlen = buffer_string_length(path);
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(path->ptr, ds->value->ptr, dlen)
: 0 == strncasecmp(path->ptr, ds->value->ptr, dlen))) {
break;
}
}
if (i == xdocroot->used) {
log_error_write(srv, __FILE__, __LINE__, "SBs",
"X-Sendfile (", path,
") not under configured x-sendfile-docroot(s)");
con->http_status = 403;
valid = 0;
}
}
if (valid) http_response_send_file(srv, con, path);
if (con->http_status >= 400 && status < 300) {
con->mode = DIRECT;
} else if (0 != status && 200 != status) {
con->http_status = status;
}
}

View File

@ -6,6 +6,7 @@
#include "log.h"
#include "connections.h"
#include "joblist.h"
#include "response.h"
#include "http_chunk.h"
#include "network_backends.h"
@ -55,6 +56,8 @@ typedef struct {
typedef struct {
array *cgi;
unsigned short execute_x_only;
unsigned short xsendfile_allow;
array *xsendfile_docroot;
} plugin_config;
typedef struct {
@ -133,6 +136,7 @@ FREE_FUNC(mod_cgi_free) {
if (NULL == s) continue;
array_free(s->cgi);
array_free(s->xsendfile_docroot);
free(s);
}
@ -157,6 +161,8 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
config_values_t cv[] = {
{ "cgi.assign", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
{ "cgi.execute-x-only", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
{ "x-sendfile", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
{ "x-sendfile-docroot", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET}
};
@ -174,15 +180,38 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
s->cgi = array_init();
s->execute_x_only = 0;
s->xsendfile_allow= 0;
s->xsendfile_docroot = array_init();
cv[0].destination = s->cgi;
cv[1].destination = &(s->execute_x_only);
cv[2].destination = &(s->xsendfile_allow);
cv[3].destination = s->xsendfile_docroot;
p->config_storage[i] = s;
if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
return HANDLER_ERROR;
}
if (s->xsendfile_docroot->used) {
size_t j;
for (j = 0; j < s->xsendfile_docroot->used; ++j) {
data_string *ds = (data_string *)s->xsendfile_docroot->data[j];
if (ds->type != TYPE_STRING) {
log_error_write(srv, __FILE__, __LINE__, "s",
"unexpected type for key cgi.x-sendfile-docroot; expected: cgi.x-sendfile-docroot = ( \"/allowed/path\", ... )");
return HANDLER_ERROR;
}
if (ds->value->ptr[0] != '/') {
log_error_write(srv, __FILE__, __LINE__, "SBs",
"cgi.x-sendfile-docroot paths must begin with '/'; invalid: \"", ds->value, "\"");
return HANDLER_ERROR;
}
buffer_path_simplify(ds->value, ds->value);
buffer_append_slash(ds->value);
}
}
}
return HANDLER_GO_ON;
@ -497,6 +526,14 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
/* parse the response header */
cgi_response_parse(srv, con, p, hctx->response_header);
if (p->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, p->conf.xsendfile_docroot);
return FDEVENT_HANDLED_FINISHED;
}
}
/* enable chunked-transfer-encoding */
if (con->request.http_version == HTTP_VERSION_1_1 &&
!(con->parsed_response & HTTP_CONTENT_LENGTH)) {
@ -1307,6 +1344,8 @@ static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p
PATCH(cgi);
PATCH(execute_x_only);
PATCH(xsendfile_allow);
PATCH(xsendfile_docroot);
/* skip the first, the global context */
for (i = 1; i < srv->config_context->used; i++) {
@ -1324,6 +1363,10 @@ static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p
PATCH(cgi);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.execute-x-only"))) {
PATCH(execute_x_only);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.x-sendfile"))) {
PATCH(xsendfile_allow);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.x-sendfile-docroot"))) {
PATCH(xsendfile_docroot);
}
}
}

View File

@ -226,11 +226,12 @@ typedef struct {
unsigned short fix_root_path_name;
/*
* If the backend includes X-LIGHTTPD-send-file in the response
* If the backend includes X-Sendfile in the response
* we use the value as filename and ignore the content.
*
*/
unsigned short allow_xsendfile;
unsigned short xsendfile_allow;
array *xsendfile_docroot;
ssize_t load; /* replace by host->load */
@ -549,6 +550,7 @@ static fcgi_extension_host *fastcgi_host_init(void) {
f->bin_env = array_init();
f->bin_env_copy = array_init();
f->strip_request_uri = buffer_init();
f->xsendfile_docroot = array_init();
return f;
}
@ -564,6 +566,7 @@ static void fastcgi_host_free(fcgi_extension_host *h) {
buffer_free(h->strip_request_uri);
array_free(h->bin_env);
array_free(h->bin_env_copy);
array_free(h->xsendfile_docroot);
fastcgi_process_free(h->first);
fastcgi_process_free(h->unused_procs);
@ -1287,6 +1290,8 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
{ "kill-signal", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 14 */
{ "fix-root-scriptname", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */
{ "listen-backlog", NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION }, /* 16 */
{ "x-sendfile", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 17 */
{ "x-sendfile-docroot",NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 18 */
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};
@ -1310,7 +1315,7 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
host->mode = FCGI_RESPONDER;
host->disable_time = 1;
host->break_scriptfilename_for_php = 0;
host->allow_xsendfile = 0; /* handle X-LIGHTTPD-send-file */
host->xsendfile_allow = 0;
host->kill_signal = SIGTERM;
host->fix_root_path_name = 0;
host->listen_backlog = 1024;
@ -1329,11 +1334,13 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
fcv[9].destination = host->bin_env;
fcv[10].destination = host->bin_env_copy;
fcv[11].destination = &(host->break_scriptfilename_for_php);
fcv[12].destination = &(host->allow_xsendfile);
fcv[12].destination = &(host->xsendfile_allow);
fcv[13].destination = host->strip_request_uri;
fcv[14].destination = &(host->kill_signal);
fcv[15].destination = &(host->fix_root_path_name);
fcv[16].destination = &(host->listen_backlog);
fcv[17].destination = &(host->xsendfile_allow);
fcv[18].destination = host->xsendfile_docroot;
if (0 != config_insert_values_internal(srv, da_host->value, fcv, T_CONFIG_SCOPE_CONNECTION)) {
goto error;
@ -1484,6 +1491,25 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
}
}
if (host->xsendfile_docroot->used) {
size_t k;
for (k = 0; k < host->xsendfile_docroot->used; ++k) {
data_string *ds = (data_string *)host->xsendfile_docroot->data[k];
if (ds->type != TYPE_STRING) {
log_error_write(srv, __FILE__, __LINE__, "s",
"unexpected type for x-sendfile-docroot; expected: \"x-sendfile-docroot\" => ( \"/allowed/path\", ... )");
goto error;
}
if (ds->value->ptr[0] != '/') {
log_error_write(srv, __FILE__, __LINE__, "SBs",
"x-sendfile-docroot paths must begin with '/'; invalid: \"", ds->value, "\"");
goto error;
}
buffer_path_simplify(ds->value, ds->value);
buffer_append_slash(ds->value);
}
}
/* if extension already exists, take it */
fastcgi_extension_insert(s->exts, da_ext->key, host);
host = NULL;
@ -2132,7 +2158,6 @@ static int fcgi_response_parse(server *srv, connection *con, plugin_data *p, buf
for (s = in->ptr; NULL != (ns = strchr(s, '\n')); s = ns + 1) {
char *key, *value;
int key_len;
data_string *ds = NULL;
/* a good day. Someone has read the specs and is sending a \r\n to us */
@ -2162,6 +2187,7 @@ static int fcgi_response_parse(server *srv, connection *con, plugin_data *p, buf
/* don't forward Status: */
if (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();
}
@ -2201,7 +2227,7 @@ static int fcgi_response_parse(server *srv, connection *con, plugin_data *p, buf
}
break;
case 11:
if (host->allow_xsendfile && 0 == strncasecmp(key, "X-Sendfile2", key_len)&& hctx->send_content_body) {
if (host->xsendfile_allow && 0 == strncasecmp(key, "X-Sendfile2", key_len) && hctx->send_content_body) {
char *pos = value;
have_sendfile2 = 1;
@ -2227,6 +2253,30 @@ static int fcgi_response_parse(server *srv, connection *con, plugin_data *p, buf
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 (p->conf.debug) {
log_error_write(srv, __FILE__, __LINE__, "sb",
@ -2526,44 +2576,26 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) {
hctx->send_content_body = 0;
}
if (host->allow_xsendfile && hctx->send_content_body &&
if (host->xsendfile_allow && hctx->send_content_body &&
(NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-LIGHTTPD-send-file"))
|| NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-Sendfile")))) {
if (0 == http_chunk_append_file(srv, con, ds->value)) {
/* found */
data_string *dcls = (data_string *) array_get_element(con->response.headers, "Content-Length");
if (dcls) buffer_reset(dcls->value);
con->parsed_response &= ~HTTP_CONTENT_LENGTH;
con->response.content_length = -1;
hctx->send_content_body = 0; /* ignore the content */
} else {
log_error_write(srv, __FILE__, __LINE__, "sb",
"send-file error: couldn't get stat_cache entry for:",
ds->value);
con->http_status = 404;
hctx->send_content_body = 0;
con->file_started = 1;
http_response_xsendfile(srv, con, ds->value, host->xsendfile_docroot);
if (con->mode == DIRECT) {
fin = 1;
break;
}
hctx->send_content_body = 0; /* ignore the content */
}
if (hctx->send_content_body && buffer_string_length(packet.b) > 0) {
/* enable chunked-transfer-encoding */
if (con->request.http_version == HTTP_VERSION_1_1 &&
!(con->parsed_response & HTTP_CONTENT_LENGTH)) {
con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
}
http_chunk_append_buffer(srv, con, packet.b);
}
} else if (hctx->send_content_body && !buffer_string_is_empty(packet.b)) {
/* enable chunked-transfer-encoding */
if (con->request.http_version == HTTP_VERSION_1_1 &&
!(con->parsed_response & HTTP_CONTENT_LENGTH)) {
/* enable chunked-transfer-encoding */
con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
}
}
if (hctx->send_content_body && !buffer_string_is_empty(packet.b)) {
http_chunk_append_buffer(srv, con, packet.b);
}
break;

View File

@ -212,6 +212,15 @@ typedef struct {
*/
unsigned short fix_root_path_name;
/*
* If the backend includes X-Sendfile in the response
* we use the value as filename and ignore the content.
*
*/
unsigned short xsendfile_allow;
array *xsendfile_docroot;
ssize_t load; /* replace by host->load */
size_t max_id; /* corresponds most of the time to
@ -416,6 +425,7 @@ static scgi_extension_host *scgi_host_init(void) {
f->bin_path = buffer_init();
f->bin_env = array_init();
f->bin_env_copy = array_init();
f->xsendfile_docroot = array_init();
return f;
}
@ -429,6 +439,7 @@ static void scgi_host_free(scgi_extension_host *h) {
buffer_free(h->bin_path);
array_free(h->bin_env);
array_free(h->bin_env_copy);
array_free(h->xsendfile_docroot);
scgi_process_free(h->first);
scgi_process_free(h->unused_procs);
@ -1053,6 +1064,8 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) {
{ "bin-copy-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
{ "fix-root-scriptname", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 13 */
{ "listen-backlog", NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION }, /* 14 */
{ "x-sendfile", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */
{ "x-sendfile-docroot",NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 16 */
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
@ -1077,6 +1090,7 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) {
df->disable_time = 60;
df->fix_root_path_name = 0;
df->listen_backlog = 1024;
df->xsendfile_allow = 0;
fcv[0].destination = df->host;
fcv[1].destination = df->docroot;
@ -1095,6 +1109,8 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) {
fcv[12].destination = df->bin_env_copy;
fcv[13].destination = &(df->fix_root_path_name);
fcv[14].destination = &(df->listen_backlog);
fcv[15].destination = &(df->xsendfile_allow);
fcv[16].destination = df->xsendfile_docroot;
if (0 != config_insert_values_internal(srv, da_host->value, fcv, T_CONFIG_SCOPE_CONNECTION)) {
@ -1227,6 +1243,25 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) {
df->max_procs = 1;
}
if (df->xsendfile_docroot->used) {
size_t k;
for (k = 0; k < df->xsendfile_docroot->used; ++k) {
data_string *ds = (data_string *)df->xsendfile_docroot->data[k];
if (ds->type != TYPE_STRING) {
log_error_write(srv, __FILE__, __LINE__, "s",
"unexpected type for x-sendfile-docroot; expected: \"x-sendfile-docroot\" => ( \"/allowed/path\", ... )");
goto error;
}
if (ds->value->ptr[0] != '/') {
log_error_write(srv, __FILE__, __LINE__, "SBs",
"x-sendfile-docroot paths must begin with '/'; invalid: \"", ds->value, "\"");
goto error;
}
buffer_path_simplify(ds->value, ds->value);
buffer_append_slash(ds->value);
}
}
/* if extension already exists, take it */
scgi_extension_insert(s->exts, da_ext->key, df);
df = NULL;
@ -1916,6 +1951,14 @@ static int scgi_demux_response(server *srv, handler_ctx *hctx) {
/* parse the response header */
scgi_response_parse(srv, con, p, hctx->response_header, eol);
if (hctx->host->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->host->xsendfile_docroot);
return 1;
}
}
/* enable chunked-transfer-encoding */
if (con->request.http_version == HTTP_VERSION_1_1 &&
!(con->parsed_response & HTTP_CONTENT_LENGTH)) {

View File

@ -34,8 +34,6 @@ typedef struct {
typedef struct {
PLUGIN_DATA;
buffer *range_buf;
plugin_config **config_storage;
plugin_config conf;
@ -47,8 +45,6 @@ INIT_FUNC(mod_staticfile_init) {
p = calloc(1, sizeof(*p));
p->range_buf = buffer_init();
return p;
}
@ -73,7 +69,6 @@ FREE_FUNC(mod_staticfile_free) {
}
free(p->config_storage);
}
buffer_free(p->range_buf);
free(p);
@ -156,215 +151,10 @@ static int mod_staticfile_patch_connection(server *srv, connection *con, plugin_
}
#undef PATCH
static int http_response_parse_range(server *srv, connection *con, plugin_data *p) {
int multipart = 0;
int error;
off_t start, end;
const char *s, *minus;
char *boundary = "fkj49sn38dcn3";
data_string *ds;
stat_cache_entry *sce = NULL;
buffer *content_type = NULL;
if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
SEGFAULT();
}
start = 0;
end = sce->st.st_size - 1;
con->response.content_length = 0;
if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) {
content_type = ds->value;
}
for (s = con->request.http_range, error = 0;
!error && *s && NULL != (minus = strchr(s, '-')); ) {
char *err;
off_t la, le;
if (s == minus) {
/* -<stop> */
le = strtoll(s, &err, 10);
if (le == 0) {
/* RFC 2616 - 14.35.1 */
con->http_status = 416;
error = 1;
} else if (*err == '\0') {
/* end */
s = err;
end = sce->st.st_size - 1;
start = sce->st.st_size + le;
} else if (*err == ',') {
multipart = 1;
s = err + 1;
end = sce->st.st_size - 1;
start = sce->st.st_size + le;
} else {
error = 1;
}
} else if (*(minus+1) == '\0' || *(minus+1) == ',') {
/* <start>- */
la = strtoll(s, &err, 10);
if (err == minus) {
/* ok */
if (*(err + 1) == '\0') {
s = err + 1;
end = sce->st.st_size - 1;
start = la;
} else if (*(err + 1) == ',') {
multipart = 1;
s = err + 2;
end = sce->st.st_size - 1;
start = la;
} else {
error = 1;
}
} else {
/* error */
error = 1;
}
} else {
/* <start>-<stop> */
la = strtoll(s, &err, 10);
if (err == minus) {
le = strtoll(minus+1, &err, 10);
/* RFC 2616 - 14.35.1 */
if (la > le) {
error = 1;
}
if (*err == '\0') {
/* ok, end*/
s = err;
end = le;
start = la;
} else if (*err == ',') {
multipart = 1;
s = err + 1;
end = le;
start = la;
} else {
/* error */
error = 1;
}
} else {
/* error */
error = 1;
}
}
if (!error) {
if (start < 0) start = 0;
/* RFC 2616 - 14.35.1 */
if (end > sce->st.st_size - 1) end = sce->st.st_size - 1;
if (start > sce->st.st_size - 1) {
error = 1;
con->http_status = 416;
}
}
if (!error) {
if (multipart) {
/* write boundary-header */
buffer *b = buffer_init();
buffer_copy_string_len(b, CONST_STR_LEN("\r\n--"));
buffer_append_string(b, boundary);
/* write Content-Range */
buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Range: bytes "));
buffer_append_int(b, start);
buffer_append_string_len(b, CONST_STR_LEN("-"));
buffer_append_int(b, end);
buffer_append_string_len(b, CONST_STR_LEN("/"));
buffer_append_int(b, sce->st.st_size);
buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Type: "));
buffer_append_string_buffer(b, content_type);
/* write END-OF-HEADER */
buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n"));
con->response.content_length += buffer_string_length(b);
chunkqueue_append_buffer(con->write_queue, b);
buffer_free(b);
}
chunkqueue_append_file(con->write_queue, con->physical.path, start, end - start + 1);
con->response.content_length += end - start + 1;
}
}
/* something went wrong */
if (error) return -1;
if (multipart) {
/* add boundary end */
buffer *b = buffer_init();
buffer_copy_string_len(b, "\r\n--", 4);
buffer_append_string(b, boundary);
buffer_append_string_len(b, "--\r\n", 4);
con->response.content_length += buffer_string_length(b);
chunkqueue_append_buffer(con->write_queue, b);
buffer_free(b);
/* set header-fields */
buffer_copy_string_len(p->range_buf, CONST_STR_LEN("multipart/byteranges; boundary="));
buffer_append_string(p->range_buf, boundary);
/* overwrite content-type */
response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf));
} else {
/* add Content-Range-header */
buffer_copy_string_len(p->range_buf, CONST_STR_LEN("bytes "));
buffer_append_int(p->range_buf, start);
buffer_append_string_len(p->range_buf, CONST_STR_LEN("-"));
buffer_append_int(p->range_buf, end);
buffer_append_string_len(p->range_buf, CONST_STR_LEN("/"));
buffer_append_int(p->range_buf, sce->st.st_size);
response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf));
}
/* ok, the file is set-up */
return 0;
}
URIHANDLER_FUNC(mod_staticfile_subrequest) {
plugin_data *p = p_d;
size_t k;
stat_cache_entry *sce = NULL;
buffer *mtime = NULL;
data_string *ds;
int allow_caching = 1;
/* someone else has done a decision for us */
if (con->http_status != 0) return HANDLER_GO_ON;
@ -412,140 +202,8 @@ URIHANDLER_FUNC(mod_staticfile_subrequest) {
log_error_write(srv, __FILE__, __LINE__, "s", "-- handling file as static file");
}
if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
con->http_status = 403;
log_error_write(srv, __FILE__, __LINE__, "sbsb",
"not a regular file:", con->uri.path,
"->", con->physical.path);
return HANDLER_FINISHED;
}
/* we only handline regular files */
#ifdef HAVE_LSTAT
if ((sce->is_symlink == 1) && !con->conf.follow_symlink) {
con->http_status = 403;
if (con->conf.log_request_handling) {
log_error_write(srv, __FILE__, __LINE__, "s", "-- access denied due symlink restriction");
log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
}
buffer_reset(con->physical.path);
return HANDLER_FINISHED;
}
#endif
if (!S_ISREG(sce->st.st_mode)) {
con->http_status = 404;
if (con->conf.log_file_not_found) {
log_error_write(srv, __FILE__, __LINE__, "sbsb",
"not a regular file:", con->uri.path,
"->", sce->name);
}
return HANDLER_FINISHED;
}
/* mod_compress might set several data directly, don't overwrite them */
/* set response content-type, if not set already */
if (NULL == array_get_element(con->response.headers, "Content-Type")) {
if (buffer_string_is_empty(sce->content_type)) {
/* we are setting application/octet-stream, but also announce that
* this header field might change in the seconds few requests
*
* This should fix the aggressive caching of FF and the script download
* seen by the first installations
*/
response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
allow_caching = 0;
} else {
response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
}
}
if (con->conf.range_requests) {
response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes"));
}
if (allow_caching) {
if (p->conf.etags_used && con->etag_flags != 0 && !buffer_string_is_empty(sce->etag)) {
if (NULL == array_get_element(con->response.headers, "ETag")) {
/* generate e-tag */
etag_mutate(con->physical.etag, sce->etag);
response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
}
}
/* prepare header */
if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) {
mtime = strftime_cache_get(srv, sce->st.st_mtime);
response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
} else {
mtime = ds->value;
}
if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
return HANDLER_FINISHED;
}
}
if (con->request.http_range && con->conf.range_requests) {
int do_range_request = 1;
/* check if we have a conditional GET */
if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If-Range"))) {
/* if the value is the same as our ETag, we do a Range-request,
* otherwise a full 200 */
if (ds->value->ptr[0] == '"') {
/**
* client wants a ETag
*/
if (!con->physical.etag) {
do_range_request = 0;
} else if (!buffer_is_equal(ds->value, con->physical.etag)) {
do_range_request = 0;
}
} else if (!mtime) {
/**
* we don't have a Last-Modified and can match the If-Range:
*
* sending all
*/
do_range_request = 0;
} else if (!buffer_is_equal(ds->value, mtime)) {
do_range_request = 0;
}
}
if (do_range_request) {
/* content prepared, I'm done */
con->file_finished = 1;
if (0 == http_response_parse_range(srv, con, p)) {
con->http_status = 206;
}
return HANDLER_FINISHED;
}
}
/* if we are still here, prepare body */
/* we add it here for all requests
* the HEAD request will drop it afterwards again
*/
if (0 == sce->st.st_size || 0 == http_chunk_append_file(srv, con, con->physical.path)) {
con->http_status = 200;
con->file_finished = 1;
} else {
con->http_status = 403;
}
if (!p->conf.etags_used) con->etag_flags = 0;
http_response_send_file(srv, con, con->physical.path);
return HANDLER_FINISHED;
}

View File

@ -16,6 +16,8 @@ int response_header_append(server *srv, connection *con, const char *key, size_t
handler_t http_response_prepare(server *srv, connection *con);
int http_response_redirect_to_directory(server *srv, connection *con);
int http_response_handle_cachable(server *srv, connection *con, buffer * mtime);
void http_response_send_file (server *srv, connection *con, buffer *path);
void http_response_xsendfile (server *srv, connection *con, buffer *path, const array *xdocroot);
buffer * strftime_cache_get(server *srv, time_t last_mod);
#endif