Fix static file/dirlist handling and add etag support

personal/stbuehler/wip
Stefan Bühler 14 years ago
parent 325ec6a5b6
commit a62cdebb91

@ -52,6 +52,7 @@
#include <lighttpd/collect.h>
#include <lighttpd/network.h>
#include <lighttpd/encoding.h>
#include <lighttpd/etag.h>
#include <lighttpd/utils.h>
#define SERVER_VERSION ((guint) 0x01FF0000)

@ -3,6 +3,8 @@
#include <lighttpd/base.h>
typedef enum { ETAG_USE_INODE = 1, ETAG_USE_MTIME = 2, ETAG_USE_SIZE = 4 } etag_flags_t;
enum core_options_t {
CORE_OPTION_DEBUG_REQUEST_HANDLING = 0,
@ -18,7 +20,9 @@ enum core_options_t {
CORE_OPTION_MIME_TYPES,
CORE_OPTION_THROTTLE
CORE_OPTION_THROTTLE,
CORE_OPTION_ETAG_FLAGS
};
/* the core plugin always has base index 0, as it is the first plugin loaded */

@ -286,6 +286,7 @@ SET(COMMON_SRC
connection.c
encoding.c
environment.c
etag.c
filter_chunked.c
http_headers.c
http_request_parser.c

@ -163,24 +163,45 @@ static handler_t dirlist(vrequest *vr, gpointer param, gpointer *context) {
stat_cache_entry *sce;
dirlist_data *dd;
dirlist_plugin_data *pd;
UNUSED(context);
if (vrequest_is_handled(vr)) return HANDLER_GO_ON;
if (!vr->stat_cache_entry) {
if (vr->physical.path->len == 0) return HANDLER_GO_ON;
if (!vrequest_handle_direct(vr)) return HANDLER_GO_ON;
}
dd = param;
pd = dd->plugin->data;
/* redirect to scheme + host + path + / + querystring if directory without trailing slash */
/* TODO: local addr if HTTP 1.0 without host header, url encoding */
if (vr->request.uri.path->str[vr->request.uri.path->len-1] != G_DIR_SEPARATOR) {
GString *host = vr->request.uri.authority->len ? vr->request.uri.authority : vr->con->local_addr_str;
GString *uri = g_string_sized_new(
8 /* https:// */ + host->len +
sce = stat_cache_get_dir(vr, vr->physical.path);
if (!sce)
return HANDLER_WAIT_FOR_EVENT;
if (sce->data.failed) {
/* stat failed */
switch (sce->data.err) {
case ENOENT:
case ENOTDIR:
return HANDLER_GO_ON;
case EACCES:
if (!vrequest_handle_direct(vr)) return HANDLER_ERROR;
vr->response.http_status = 403;
return HANDLER_GO_ON;
default:
VR_ERROR(vr, "stat('%s') failed: %s", sce->data.path->str, g_strerror(sce->data.err));
return HANDLER_ERROR;
}
} else if (!S_ISDIR(sce->data.st.st_mode)) {
return HANDLER_GO_ON;
} else if (vr->request.uri.path->str[vr->request.uri.path->len-1] != G_DIR_SEPARATOR) {
GString *host, *uri;
if (!vrequest_handle_direct(vr)) return HANDLER_ERROR;
/* redirect to scheme + host + path + / + querystring if directory without trailing slash */
/* TODO: local addr if HTTP 1.0 without host header, url encoding */
host = vr->request.uri.authority->len ? vr->request.uri.authority : vr->con->local_addr_str;
uri = g_string_sized_new(
8 /* https:// */ + host->len +
vr->request.uri.orig_path->len + 2 /* /? */ + vr->request.uri.query->len
);
@ -200,39 +221,20 @@ static handler_t dirlist(vrequest *vr, gpointer param, gpointer *context) {
http_header_overwrite(vr->response.headers, CONST_STR_LEN("Location"), GSTR_LEN(uri));
g_string_free(uri, TRUE);
return HANDLER_GO_ON;
}
sce = stat_cache_get_dir(vr, vr->physical.path);
if (!sce)
return HANDLER_WAIT_FOR_EVENT;
if (sce->data.failed) {
/* stat failed */
VR_DEBUG(vr, "stat(\"%s\") failed: %s (%d)", sce->data.path->str, g_strerror(sce->data.err), sce->data.err);
switch (errno) {
case ENOENT:
vr->response.http_status = 404; break;
case EACCES:
case EFAULT:
vr->response.http_status = 403; break;
default:
vr->response.http_status = 500;
}
} else {
/* everything ok, we have the directory listing */
guint i;
guint j;
gboolean cachable;
guint i, j;
stat_cache_entry_data *sced;
GString *mime_str, *tmp_str = vr->con->wrk->tmp_str;
GArray *directories;
GArray *files;
GString *encoded;
GString *mime_str, *encoded;
GArray *directories, *files;
gchar sizebuf[sizeof("999.9K")+1];
gchar datebuf[sizeof("2005-Jan-01 22:23:24")+1];
guint datebuflen;
struct tm tm;
gboolean hide;
if (!vrequest_handle_direct(vr)) return HANDLER_ERROR;
vr->response.http_status = 200;
if (dd->debug)
@ -242,6 +244,11 @@ static handler_t dirlist(vrequest *vr, gpointer param, gpointer *context) {
encoded = g_string_sized_new(64);
http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
etag_set_header(vr, &sce->data.st, &cachable);
if (cachable) {
vr->response.http_status = 304;
return HANDLER_GO_ON;
}
/* seperate directories from other files */
directories = g_array_sized_new(FALSE, FALSE, sizeof(guint), 16);
@ -321,8 +328,7 @@ static handler_t dirlist(vrequest *vr, gpointer param, gpointer *context) {
string_encode(sced->path->str, encoded, ENCODING_HTML);
g_string_append_len(listing, GSTR_LEN(encoded));
g_string_append_len(listing, CONST_STR_LEN("</a></td><td class=\"modified\" val=\""));
l_g_string_from_int(tmp_str, sced->st.st_mtime);
g_string_append_len(listing, GSTR_LEN(tmp_str));
l_g_string_append_int(listing, sced->st.st_mtime);
g_string_append_len(listing, CONST_STR_LEN("\">"));
g_string_append_len(listing, datebuf, datebuflen);
g_string_append_len(listing, CONST_STR_LEN("</td>"
@ -353,13 +359,11 @@ static handler_t dirlist(vrequest *vr, gpointer param, gpointer *context) {
g_string_append_len(listing, CONST_STR_LEN(
"</a></td>"
"<td class=\"modified\" val=\""));
l_g_string_from_int(tmp_str, sced->st.st_mtime);
g_string_append_len(listing, GSTR_LEN(tmp_str));
l_g_string_append_int(listing, sced->st.st_mtime);
g_string_append_len(listing, CONST_STR_LEN("\">"));
g_string_append_len(listing, datebuf, datebuflen);
g_string_append_len(listing, CONST_STR_LEN("</td><td class=\"size\" val=\""));
l_g_string_from_int(tmp_str, sced->st.st_size);
g_string_append_len(listing, GSTR_LEN(tmp_str));
l_g_string_append_int(listing, sced->st.st_size);
g_string_append_len(listing, CONST_STR_LEN("\">"));
g_string_append(listing, sizebuf);
g_string_append_len(listing, CONST_STR_LEN("</td><td class=\"type\">"));

@ -191,85 +191,93 @@ static handler_t core_handle_static(vrequest *vr, gpointer param, gpointer *cont
UNUSED(param);
UNUSED(context);
switch (vr->request.http_method) {
case HTTP_METHOD_GET:
case HTTP_METHOD_HEAD:
break;
default:
return HANDLER_GO_ON;
}
if (vrequest_is_handled(vr)) return HANDLER_GO_ON;
if (!vr->stat_cache_entry) {
if (vr->physical.path->len == 0) return HANDLER_GO_ON;
if (!vrequest_handle_direct(vr)) return HANDLER_GO_ON;
}
sce = stat_cache_get(vr, vr->physical.path);
if (!sce)
return HANDLER_WAIT_FOR_EVENT;
VR_DEBUG(vr, "serving static file: %s", vr->physical.path->str);
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
VR_DEBUG(vr, "try serving static file: '%s'", vr->physical.path->str);
}
if (sce->data.failed) {
/* stat failed */
VR_DEBUG(vr, "stat(\"%s\") failed: %s (%d)", sce->data.path->str, g_strerror(sce->data.err), sce->data.err);
switch (sce->data.err) {
case ENOENT:
vr->response.http_status = 404; break;
case ENOTDIR:
VR_ERROR(vr, "stat('%s') failed: %s", sce->data.path->str, g_strerror(sce->data.err));
return HANDLER_GO_ON;
case EACCES:
case EFAULT:
vr->response.http_status = 403; break;
if (!vrequest_handle_direct(vr)) return HANDLER_ERROR;
vr->response.http_status = 403;
VR_ERROR(vr, "stat('%s') failed: %s", sce->data.path->str, g_strerror(sce->data.err));
return HANDLER_GO_ON;
default:
vr->response.http_status = 500;
VR_ERROR(vr, "stat('%s') failed: %s", sce->data.path->str, g_strerror(sce->data.err));
return HANDLER_ERROR;
}
} else if (S_ISDIR(sce->data.st.st_mode)) {
VR_ERROR(vr, "%s", "trace");
return HANDLER_GO_ON;
} else if (!S_ISREG(sce->data.st.st_mode)) {
VR_ERROR(vr, "%s", "trace");
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
VR_DEBUG(vr, "not a regular file: '%s'", vr->physical.path->str);
}
vr->response.http_status = 403;
} else if ((fd = open(vr->physical.path->str, O_RDONLY)) == -1) {
VR_DEBUG(vr, "open(\"%s\") failed: %s (%d)", vr->physical.path->str, g_strerror(errno), errno);
VR_ERROR(vr, "%s", "trace");
switch (errno) {
case ENOENT:
vr->response.http_status = 404; break;
case ENOTDIR:
return HANDLER_GO_ON;
case EACCES:
case EFAULT:
vr->response.http_status = 403; break;
if (!vrequest_handle_direct(vr)) return HANDLER_ERROR;
vr->response.http_status = 403;
return HANDLER_GO_ON;
default:
vr->response.http_status = 500;
VR_ERROR(vr, "open('%s') failed: %s", vr->physical.path->str, g_strerror(errno));
return HANDLER_ERROR;
}
} else {
GString *mime_str;
gboolean cachable;
#ifdef FD_CLOEXEC
fcntl(fd, F_SETFD, FD_CLOEXEC);
#endif
/* redirect to scheme + host + path + / + querystring if directory without trailing slash */
/* TODO: local addr if HTTP 1.0 without host header */
if (S_ISDIR(sce->data.st.st_mode) && vr->request.uri.orig_path->str[vr->request.uri.orig_path->len-1] != '/') {
GString *host = vr->request.uri.authority->len ? vr->request.uri.authority : vr->con->local_addr_str;
GString *uri = g_string_sized_new(
8 /* https:// */ + host->len +
vr->request.uri.orig_path->len + 2 /* /? */ + vr->request.uri.query->len
);
if (vr->con->is_ssl)
g_string_append_len(uri, CONST_STR_LEN("https://"));
else
g_string_append_len(uri, CONST_STR_LEN("http://"));
g_string_append_len(uri, GSTR_LEN(host));
g_string_append_len(uri, GSTR_LEN(vr->request.uri.orig_path));
g_string_append_c(uri, '/');
if (vr->request.uri.query->len) {
g_string_append_c(uri, '?');
g_string_append_len(uri, GSTR_LEN(vr->request.uri.query));
}
vr->response.http_status = 301;
http_header_overwrite(vr->response.headers, CONST_STR_LEN("Location"), GSTR_LEN(uri));
g_string_free(uri, TRUE);
close(fd);
} else if (!S_ISREG(sce->data.st.st_mode)) {
vr->response.http_status = 404;
if (!vrequest_handle_direct(vr)) {
close(fd);
} else {
GString *mime_str = mimetype_get(vr, vr->request.uri.path);
vr->response.http_status = 200;
if (mime_str)
http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), GSTR_LEN(mime_str));
else
http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
chunkqueue_append_file_fd(vr->out, NULL, 0, sce->data.st.st_size, fd);
return HANDLER_ERROR;
}
etag_set_header(vr, &sce->data.st, &cachable);
if (cachable) {
vr->response.http_status = 304;
return HANDLER_GO_ON;
}
mime_str = mimetype_get(vr, vr->request.uri.path);
vr->response.http_status = 200;
if (mime_str)
http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), GSTR_LEN(mime_str));
else
http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
chunkqueue_append_file_fd(vr->out, NULL, 0, sce->data.st.st_size, fd);
}
stat_cache_entry_release(vr);
@ -662,6 +670,46 @@ static void core_option_mime_types_free(server *srv, plugin *p, size_t ndx, opti
g_array_free(oval.list, TRUE);
}
static gboolean core_option_etag_use_parse(server *srv, plugin *p, size_t ndx, value *val, option_value *oval) {
GArray *arr;
guint flags = 0;
UNUSED(p);
UNUSED(ndx);
/* default value */
if (!val) {
oval->number = ETAG_USE_INODE | ETAG_USE_MTIME | ETAG_USE_SIZE;
return TRUE;
}
if (val->type != VALUE_LIST) {
ERROR(srv, "etag.use option expects a list of strings, parameter is of type %s", value_type_string(val->type));
}
arr = val->data.list;
for (guint i = 0; i < arr->len; i++) {
value *v = g_array_index(arr, value*, i);
if (v->type != VALUE_STRING) {
ERROR(srv, "etag.use option expects a list of strings, entry #%u is of type %s", i, value_type_string(v->type));
return FALSE;
}
if (0 == strcmp(v->data.string->str, "inode")) {
flags |= ETAG_USE_INODE;
} else if (0 == strcmp(v->data.string->str, "mtime")) {
flags |= ETAG_USE_MTIME;
} else if (0 == strcmp(v->data.string->str, "size")) {
flags |= ETAG_USE_SIZE;
} else {
ERROR(srv, "unknown etag.use flag: %s", v->data.string->str);
return FALSE;
}
}
oval->number = (guint64) flags;
return TRUE;
}
static handler_t core_handle_header_add(vrequest *vr, gpointer param, gpointer *context) {
GArray *l = (GArray*)param;
GString *k = g_array_index(l, value*, 0)->data.string;
@ -980,6 +1028,8 @@ static const plugin_option options[] = {
{ "throttle", VALUE_NUMBER, GINT_TO_POINTER(0), NULL, NULL },
{ "etag.use", VALUE_NONE, NULL, core_option_etag_use_parse, NULL },
{ NULL, 0, NULL, NULL, NULL }
};

Loading…
Cancel
Save