Having a configurable index filename will require adding an index field to this structure. The most natural name for that field is 'index', so the current index field should be renamed to allow for that. A sensible name is 'share_idx', since it's the index of the shares array in 'nxt_http_static_conf_t'. Instead of 'share_index' I opted for the shorter 'share_idx'. Also, when 'index' allows an array of filenames in a following commit, another similar variable 'index_idx' should be created, and having a different prefix and suffix seems more readable than for example 'index_index'.
1078 lines
26 KiB
C
1078 lines
26 KiB
C
|
|
/*
|
|
* Copyright (C) NGINX, Inc.
|
|
*/
|
|
|
|
#include <nxt_router.h>
|
|
#include <nxt_http.h>
|
|
|
|
|
|
typedef struct {
|
|
nxt_var_t *var;
|
|
#if (NXT_HAVE_OPENAT2)
|
|
u_char *fname;
|
|
#endif
|
|
uint8_t is_const; /* 1 bit */
|
|
} nxt_http_static_share_t;
|
|
|
|
|
|
typedef struct {
|
|
nxt_uint_t nshares;
|
|
nxt_http_static_share_t *shares;
|
|
#if (NXT_HAVE_OPENAT2)
|
|
nxt_var_t *chroot;
|
|
nxt_uint_t resolve;
|
|
#endif
|
|
nxt_http_route_rule_t *types;
|
|
} nxt_http_static_conf_t;
|
|
|
|
|
|
typedef struct {
|
|
nxt_http_action_t *action;
|
|
nxt_str_t share;
|
|
#if (NXT_HAVE_OPENAT2)
|
|
nxt_str_t chroot;
|
|
#endif
|
|
uint32_t share_idx;
|
|
uint8_t need_body; /* 1 bit */
|
|
} nxt_http_static_ctx_t;
|
|
|
|
|
|
#define NXT_HTTP_STATIC_BUF_COUNT 2
|
|
#define NXT_HTTP_STATIC_BUF_SIZE (128 * 1024)
|
|
|
|
|
|
static nxt_http_action_t *nxt_http_static(nxt_task_t *task,
|
|
nxt_http_request_t *r, nxt_http_action_t *action);
|
|
static void nxt_http_static_iterate(nxt_task_t *task, nxt_http_request_t *r,
|
|
nxt_http_static_ctx_t *ctx);
|
|
static void nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data);
|
|
static void nxt_http_static_var_error(nxt_task_t *task, void *obj, void *data);
|
|
static void nxt_http_static_next(nxt_task_t *task, nxt_http_request_t *r,
|
|
nxt_http_static_ctx_t *ctx, nxt_http_status_t status);
|
|
#if (NXT_HAVE_OPENAT2)
|
|
static u_char *nxt_http_static_chroot_match(u_char *chr, u_char *shr);
|
|
#endif
|
|
static void nxt_http_static_extract_extension(nxt_str_t *path,
|
|
nxt_str_t *exten);
|
|
static void nxt_http_static_body_handler(nxt_task_t *task, void *obj,
|
|
void *data);
|
|
static void nxt_http_static_buf_completion(nxt_task_t *task, void *obj,
|
|
void *data);
|
|
|
|
static nxt_int_t nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq,
|
|
void *data);
|
|
static void *nxt_http_static_mtypes_hash_alloc(void *data, size_t size);
|
|
static void nxt_http_static_mtypes_hash_free(void *data, void *p);
|
|
|
|
|
|
static const nxt_http_request_state_t nxt_http_static_send_state;
|
|
|
|
|
|
nxt_int_t
|
|
nxt_http_static_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
|
|
nxt_http_action_t *action, nxt_http_action_conf_t *acf)
|
|
{
|
|
uint32_t i;
|
|
nxt_mp_t *mp;
|
|
nxt_str_t str;
|
|
nxt_var_t *var;
|
|
nxt_conf_value_t *cv;
|
|
nxt_http_static_conf_t *conf;
|
|
|
|
mp = tmcf->router_conf->mem_pool;
|
|
|
|
conf = nxt_mp_zget(mp, sizeof(nxt_http_static_conf_t));
|
|
if (nxt_slow_path(conf == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
action->handler = nxt_http_static;
|
|
action->u.conf = conf;
|
|
|
|
conf->nshares = nxt_conf_array_elements_count_or_1(acf->share);
|
|
conf->shares = nxt_mp_zget(mp, sizeof(nxt_http_static_share_t)
|
|
* conf->nshares);
|
|
if (nxt_slow_path(conf->shares == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
for (i = 0; i < conf->nshares; i++) {
|
|
cv = nxt_conf_get_array_element_or_itself(acf->share, i);
|
|
nxt_conf_get_string(cv, &str);
|
|
|
|
var = nxt_var_compile(&str, mp, 1);
|
|
if (nxt_slow_path(var == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
conf->shares[i].var = var;
|
|
conf->shares[i].is_const = nxt_var_is_const(var);
|
|
}
|
|
|
|
#if (NXT_HAVE_OPENAT2)
|
|
if (acf->chroot.length > 0) {
|
|
nxt_str_t chr, shr;
|
|
nxt_bool_t is_const;
|
|
|
|
conf->chroot = nxt_var_compile(&acf->chroot, mp, 1);
|
|
if (nxt_slow_path(conf->chroot == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
is_const = nxt_var_is_const(conf->chroot);
|
|
|
|
for (i = 0; i < conf->nshares; i++) {
|
|
conf->shares[i].is_const &= is_const;
|
|
|
|
if (conf->shares[i].is_const) {
|
|
nxt_var_raw(conf->chroot, &chr);
|
|
nxt_var_raw(conf->shares[i].var, &shr);
|
|
|
|
conf->shares[i].fname = nxt_http_static_chroot_match(chr.start,
|
|
shr.start);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (acf->follow_symlinks != NULL
|
|
&& !nxt_conf_get_boolean(acf->follow_symlinks))
|
|
{
|
|
conf->resolve |= RESOLVE_NO_SYMLINKS;
|
|
}
|
|
|
|
if (acf->traverse_mounts != NULL
|
|
&& !nxt_conf_get_boolean(acf->traverse_mounts))
|
|
{
|
|
conf->resolve |= RESOLVE_NO_XDEV;
|
|
}
|
|
#endif
|
|
|
|
if (acf->types != NULL) {
|
|
conf->types = nxt_http_route_types_rule_create(task, mp, acf->types);
|
|
if (nxt_slow_path(conf->types == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
}
|
|
|
|
if (acf->fallback != NULL) {
|
|
action->fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t));
|
|
if (nxt_slow_path(action->fallback == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
return nxt_http_action_init(task, tmcf, acf->fallback,
|
|
action->fallback);
|
|
}
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
static nxt_http_action_t *
|
|
nxt_http_static(nxt_task_t *task, nxt_http_request_t *r,
|
|
nxt_http_action_t *action)
|
|
{
|
|
nxt_bool_t need_body;
|
|
nxt_http_static_ctx_t *ctx;
|
|
|
|
if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) {
|
|
|
|
if (!nxt_str_eq(r->method, "HEAD", 4)) {
|
|
if (action->fallback != NULL) {
|
|
return action->fallback;
|
|
}
|
|
|
|
nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED);
|
|
return NULL;
|
|
}
|
|
|
|
need_body = 0;
|
|
|
|
} else {
|
|
need_body = 1;
|
|
}
|
|
|
|
ctx = nxt_mp_zget(r->mem_pool, sizeof(nxt_http_static_ctx_t));
|
|
if (nxt_slow_path(ctx == NULL)) {
|
|
nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
|
|
return NULL;
|
|
}
|
|
|
|
ctx->action = action;
|
|
ctx->need_body = need_body;
|
|
|
|
nxt_http_static_iterate(task, r, ctx);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void
|
|
nxt_http_static_iterate(nxt_task_t *task, nxt_http_request_t *r,
|
|
nxt_http_static_ctx_t *ctx)
|
|
{
|
|
nxt_int_t ret;
|
|
nxt_http_static_conf_t *conf;
|
|
nxt_http_static_share_t *share;
|
|
|
|
conf = ctx->action->u.conf;
|
|
|
|
share = &conf->shares[ctx->share_idx];
|
|
|
|
#if (NXT_DEBUG)
|
|
nxt_str_t shr;
|
|
|
|
nxt_var_raw(share->var, &shr);
|
|
|
|
#if (NXT_HAVE_OPENAT2)
|
|
nxt_str_t chr;
|
|
|
|
if (conf->chroot != NULL) {
|
|
nxt_var_raw(conf->chroot, &chr);
|
|
|
|
} else {
|
|
nxt_str_set(&chr, "");
|
|
}
|
|
|
|
nxt_debug(task, "http static: \"%V\" (chroot: \"%V\")", &shr, &chr);
|
|
#else
|
|
nxt_debug(task, "http static: \"%V\"", &shr);
|
|
#endif
|
|
#endif /* NXT_DEBUG */
|
|
|
|
if (share->is_const) {
|
|
nxt_var_raw(share->var, &ctx->share);
|
|
|
|
#if (NXT_HAVE_OPENAT2)
|
|
if (conf->chroot != NULL && ctx->share_idx == 0) {
|
|
nxt_var_raw(conf->chroot, &ctx->chroot);
|
|
}
|
|
#endif
|
|
|
|
nxt_http_static_send_ready(task, r, ctx);
|
|
|
|
} else {
|
|
ret = nxt_var_query_init(&r->var_query, r, r->mem_pool);
|
|
if (nxt_slow_path(ret != NXT_OK)) {
|
|
nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
|
|
return;
|
|
}
|
|
|
|
nxt_var_query(task, r->var_query, share->var, &ctx->share);
|
|
|
|
#if (NXT_HAVE_OPENAT2)
|
|
if (conf->chroot != NULL && ctx->share_idx == 0) {
|
|
nxt_var_query(task, r->var_query, conf->chroot, &ctx->chroot);
|
|
}
|
|
#endif
|
|
|
|
nxt_var_query_resolve(task, r->var_query, ctx,
|
|
nxt_http_static_send_ready,
|
|
nxt_http_static_var_error);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data)
|
|
{
|
|
size_t length, encode;
|
|
u_char *p, *fname;
|
|
struct tm tm;
|
|
nxt_buf_t *fb;
|
|
nxt_int_t ret;
|
|
nxt_str_t *shr, exten, *mtype;
|
|
nxt_uint_t level;
|
|
nxt_file_t *f, file;
|
|
nxt_file_info_t fi;
|
|
nxt_http_field_t *field;
|
|
nxt_http_status_t status;
|
|
nxt_router_conf_t *rtcf;
|
|
nxt_http_action_t *action;
|
|
nxt_http_request_t *r;
|
|
nxt_work_handler_t body_handler;
|
|
nxt_http_static_ctx_t *ctx;
|
|
nxt_http_static_conf_t *conf;
|
|
|
|
static const nxt_str_t index = nxt_string("index.html");
|
|
|
|
r = obj;
|
|
ctx = data;
|
|
action = ctx->action;
|
|
conf = action->u.conf;
|
|
rtcf = r->conf->socket_conf->router_conf;
|
|
|
|
f = NULL;
|
|
mtype = NULL;
|
|
|
|
shr = &ctx->share;
|
|
|
|
if (shr->start[shr->length - 1] == '/') {
|
|
/* TODO: dynamic index setting. */
|
|
nxt_str_set(&exten, ".html");
|
|
|
|
length = shr->length + index.length;
|
|
|
|
fname = nxt_mp_nget(r->mem_pool, length + 1);
|
|
if (nxt_slow_path(fname == NULL)) {
|
|
goto fail;
|
|
}
|
|
|
|
p = fname;
|
|
p = nxt_cpymem(p, shr->start, shr->length);
|
|
p = nxt_cpymem(p, index.start, index.length);
|
|
*p = '\0';
|
|
|
|
} else {
|
|
if (conf->types == NULL) {
|
|
nxt_str_null(&exten);
|
|
|
|
} else {
|
|
nxt_http_static_extract_extension(shr, &exten);
|
|
mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
|
|
|
|
ret = nxt_http_route_test_rule(r, conf->types, mtype->start,
|
|
mtype->length);
|
|
if (nxt_slow_path(ret == NXT_ERROR)) {
|
|
goto fail;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
nxt_http_static_next(task, r, ctx, NXT_HTTP_FORBIDDEN);
|
|
return;
|
|
}
|
|
}
|
|
|
|
fname = ctx->share.start;
|
|
}
|
|
|
|
nxt_memzero(&file, sizeof(nxt_file_t));
|
|
|
|
file.name = fname;
|
|
|
|
#if (NXT_HAVE_OPENAT2)
|
|
if (conf->resolve != 0 || ctx->chroot.length > 0) {
|
|
nxt_str_t *chr;
|
|
nxt_uint_t resolve;
|
|
nxt_http_static_share_t *share;
|
|
|
|
share = &conf->shares[ctx->share_idx];
|
|
|
|
resolve = conf->resolve;
|
|
chr = &ctx->chroot;
|
|
|
|
if (chr->length > 0) {
|
|
resolve |= RESOLVE_IN_ROOT;
|
|
|
|
fname = share->is_const
|
|
? share->fname
|
|
: nxt_http_static_chroot_match(chr->start, file.name);
|
|
|
|
if (fname != NULL) {
|
|
file.name = chr->start;
|
|
ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN,
|
|
0);
|
|
|
|
} else {
|
|
file.error = NXT_EACCES;
|
|
ret = NXT_ERROR;
|
|
}
|
|
|
|
} else if (fname[0] == '/') {
|
|
file.name = (u_char *) "/";
|
|
ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 0);
|
|
|
|
} else {
|
|
file.name = (u_char *) ".";
|
|
file.fd = AT_FDCWD;
|
|
ret = NXT_OK;
|
|
}
|
|
|
|
if (nxt_fast_path(ret == NXT_OK)) {
|
|
nxt_file_t af;
|
|
|
|
af = file;
|
|
nxt_memzero(&file, sizeof(nxt_file_t));
|
|
file.name = fname;
|
|
|
|
ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY,
|
|
NXT_FILE_OPEN, 0, af.fd, resolve);
|
|
|
|
if (af.fd != AT_FDCWD) {
|
|
nxt_file_close(task, &af);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
|
|
}
|
|
|
|
#else
|
|
ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
|
|
#endif
|
|
|
|
if (nxt_slow_path(ret != NXT_OK)) {
|
|
|
|
switch (file.error) {
|
|
|
|
/*
|
|
* For Unix domain sockets "errno" is set to:
|
|
* - ENXIO on Linux;
|
|
* - EOPNOTSUPP on *BSD, MacOSX, and Solaris.
|
|
*/
|
|
|
|
case NXT_ENOENT:
|
|
case NXT_ENOTDIR:
|
|
case NXT_ENAMETOOLONG:
|
|
#if (NXT_LINUX)
|
|
case NXT_ENXIO:
|
|
#else
|
|
case NXT_EOPNOTSUPP:
|
|
#endif
|
|
level = NXT_LOG_ERR;
|
|
status = NXT_HTTP_NOT_FOUND;
|
|
break;
|
|
|
|
case NXT_EACCES:
|
|
#if (NXT_HAVE_OPENAT2)
|
|
case NXT_ELOOP:
|
|
case NXT_EXDEV:
|
|
#endif
|
|
level = NXT_LOG_ERR;
|
|
status = NXT_HTTP_FORBIDDEN;
|
|
break;
|
|
|
|
default:
|
|
level = NXT_LOG_ALERT;
|
|
status = NXT_HTTP_INTERNAL_SERVER_ERROR;
|
|
break;
|
|
}
|
|
|
|
if (status != NXT_HTTP_NOT_FOUND) {
|
|
#if (NXT_HAVE_OPENAT2)
|
|
nxt_str_t *chr = &ctx->chroot;
|
|
|
|
if (chr->length > 0) {
|
|
nxt_log(task, level, "opening \"%s\" at \"%V\" failed %E",
|
|
fname, chr, file.error);
|
|
|
|
} else {
|
|
nxt_log(task, level, "opening \"%s\" failed %E",
|
|
fname, file.error);
|
|
}
|
|
|
|
#else
|
|
nxt_log(task, level, "opening \"%s\" failed %E", fname, file.error);
|
|
#endif
|
|
}
|
|
|
|
if (level == NXT_LOG_ERR) {
|
|
nxt_http_static_next(task, r, ctx, status);
|
|
return;
|
|
}
|
|
|
|
goto fail;
|
|
}
|
|
|
|
f = nxt_mp_get(r->mem_pool, sizeof(nxt_file_t));
|
|
if (nxt_slow_path(f == NULL)) {
|
|
nxt_file_close(task, &file);
|
|
goto fail;
|
|
}
|
|
|
|
*f = file;
|
|
|
|
ret = nxt_file_info(f, &fi);
|
|
if (nxt_slow_path(ret != NXT_OK)) {
|
|
goto fail;
|
|
}
|
|
|
|
if (nxt_fast_path(nxt_is_file(&fi))) {
|
|
r->status = NXT_HTTP_OK;
|
|
r->resp.content_length_n = nxt_file_size(&fi);
|
|
|
|
field = nxt_list_zero_add(r->resp.fields);
|
|
if (nxt_slow_path(field == NULL)) {
|
|
goto fail;
|
|
}
|
|
|
|
nxt_http_field_name_set(field, "Last-Modified");
|
|
|
|
p = nxt_mp_nget(r->mem_pool, NXT_HTTP_DATE_LEN);
|
|
if (nxt_slow_path(p == NULL)) {
|
|
goto fail;
|
|
}
|
|
|
|
nxt_localtime(nxt_file_mtime(&fi), &tm);
|
|
|
|
field->value = p;
|
|
field->value_length = nxt_http_date(p, &tm) - p;
|
|
|
|
field = nxt_list_zero_add(r->resp.fields);
|
|
if (nxt_slow_path(field == NULL)) {
|
|
goto fail;
|
|
}
|
|
|
|
nxt_http_field_name_set(field, "ETag");
|
|
|
|
length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3;
|
|
|
|
p = nxt_mp_nget(r->mem_pool, length);
|
|
if (nxt_slow_path(p == NULL)) {
|
|
goto fail;
|
|
}
|
|
|
|
field->value = p;
|
|
field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"",
|
|
nxt_file_mtime(&fi),
|
|
nxt_file_size(&fi))
|
|
- p;
|
|
|
|
if (exten.start == NULL) {
|
|
nxt_http_static_extract_extension(shr, &exten);
|
|
}
|
|
|
|
if (mtype == NULL) {
|
|
mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
|
|
}
|
|
|
|
if (mtype->length != 0) {
|
|
field = nxt_list_zero_add(r->resp.fields);
|
|
if (nxt_slow_path(field == NULL)) {
|
|
goto fail;
|
|
}
|
|
|
|
nxt_http_field_name_set(field, "Content-Type");
|
|
|
|
field->value = mtype->start;
|
|
field->value_length = mtype->length;
|
|
}
|
|
|
|
if (ctx->need_body && nxt_file_size(&fi) > 0) {
|
|
fb = nxt_mp_zget(r->mem_pool, NXT_BUF_FILE_SIZE);
|
|
if (nxt_slow_path(fb == NULL)) {
|
|
goto fail;
|
|
}
|
|
|
|
fb->file = f;
|
|
fb->file_end = nxt_file_size(&fi);
|
|
|
|
r->out = fb;
|
|
|
|
body_handler = &nxt_http_static_body_handler;
|
|
|
|
} else {
|
|
nxt_file_close(task, f);
|
|
body_handler = NULL;
|
|
}
|
|
|
|
} else {
|
|
/* Not a file. */
|
|
nxt_file_close(task, f);
|
|
|
|
if (nxt_slow_path(!nxt_is_dir(&fi))) {
|
|
nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file",
|
|
f->name);
|
|
|
|
nxt_http_static_next(task, r, ctx, NXT_HTTP_NOT_FOUND);
|
|
return;
|
|
}
|
|
|
|
f = NULL;
|
|
|
|
r->status = NXT_HTTP_MOVED_PERMANENTLY;
|
|
r->resp.content_length_n = 0;
|
|
|
|
field = nxt_list_zero_add(r->resp.fields);
|
|
if (nxt_slow_path(field == NULL)) {
|
|
goto fail;
|
|
}
|
|
|
|
nxt_http_field_name_set(field, "Location");
|
|
|
|
encode = nxt_encode_uri(NULL, r->path->start, r->path->length);
|
|
length = r->path->length + encode * 2 + 1;
|
|
|
|
if (r->args->length > 0) {
|
|
length += 1 + r->args->length;
|
|
}
|
|
|
|
p = nxt_mp_nget(r->mem_pool, length);
|
|
if (nxt_slow_path(p == NULL)) {
|
|
goto fail;
|
|
}
|
|
|
|
field->value = p;
|
|
field->value_length = length;
|
|
|
|
if (encode > 0) {
|
|
p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length);
|
|
|
|
} else {
|
|
p = nxt_cpymem(p, r->path->start, r->path->length);
|
|
}
|
|
|
|
*p++ = '/';
|
|
|
|
if (r->args->length > 0) {
|
|
*p++ = '?';
|
|
nxt_memcpy(p, r->args->start, r->args->length);
|
|
}
|
|
|
|
body_handler = NULL;
|
|
}
|
|
|
|
nxt_http_request_header_send(task, r, body_handler, NULL);
|
|
|
|
r->state = &nxt_http_static_send_state;
|
|
return;
|
|
|
|
fail:
|
|
|
|
if (f != NULL) {
|
|
nxt_file_close(task, f);
|
|
}
|
|
|
|
nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
|
|
|
|
static void
|
|
nxt_http_static_var_error(nxt_task_t *task, void *obj, void *data)
|
|
{
|
|
nxt_http_request_t *r;
|
|
|
|
r = obj;
|
|
|
|
nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
|
|
|
|
static void
|
|
nxt_http_static_next(nxt_task_t *task, nxt_http_request_t *r,
|
|
nxt_http_static_ctx_t *ctx, nxt_http_status_t status)
|
|
{
|
|
nxt_http_action_t *action;
|
|
nxt_http_static_conf_t *conf;
|
|
|
|
action = ctx->action;
|
|
conf = action->u.conf;
|
|
|
|
ctx->share_idx++;
|
|
|
|
if (ctx->share_idx < conf->nshares) {
|
|
nxt_http_static_iterate(task, r, ctx);
|
|
return;
|
|
}
|
|
|
|
if (action->fallback != NULL) {
|
|
nxt_http_request_action(task, r, action->fallback);
|
|
return;
|
|
}
|
|
|
|
nxt_http_request_error(task, r, status);
|
|
}
|
|
|
|
|
|
#if (NXT_HAVE_OPENAT2)
|
|
|
|
static u_char *
|
|
nxt_http_static_chroot_match(u_char *chr, u_char *shr)
|
|
{
|
|
if (*chr != *shr) {
|
|
return NULL;
|
|
}
|
|
|
|
chr++;
|
|
shr++;
|
|
|
|
for ( ;; ) {
|
|
if (*shr == '\0') {
|
|
return NULL;
|
|
}
|
|
|
|
if (*chr == *shr) {
|
|
chr++;
|
|
shr++;
|
|
continue;
|
|
}
|
|
|
|
if (*chr == '\0') {
|
|
break;
|
|
}
|
|
|
|
if (*chr == '/') {
|
|
if (chr[-1] == '/') {
|
|
chr++;
|
|
continue;
|
|
}
|
|
|
|
} else if (*shr == '/') {
|
|
if (shr[-1] == '/') {
|
|
shr++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (shr[-1] != '/' && *shr != '/') {
|
|
return NULL;
|
|
}
|
|
|
|
while (*shr == '/') {
|
|
shr++;
|
|
}
|
|
|
|
return (*shr != '\0') ? shr : NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
static void
|
|
nxt_http_static_extract_extension(nxt_str_t *path, nxt_str_t *exten)
|
|
{
|
|
u_char ch, *p, *end;
|
|
|
|
end = path->start + path->length;
|
|
p = end;
|
|
|
|
for ( ;; ) {
|
|
/* There's always '/' in the beginning of the request path. */
|
|
|
|
p--;
|
|
ch = *p;
|
|
|
|
switch (ch) {
|
|
case '/':
|
|
p++;
|
|
/* Fall through. */
|
|
case '.':
|
|
exten->length = end - p;
|
|
exten->start = p;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
nxt_http_static_body_handler(nxt_task_t *task, void *obj, void *data)
|
|
{
|
|
size_t alloc;
|
|
nxt_buf_t *fb, *b, **next, *out;
|
|
nxt_off_t rest;
|
|
nxt_int_t n;
|
|
nxt_work_queue_t *wq;
|
|
nxt_http_request_t *r;
|
|
|
|
r = obj;
|
|
fb = r->out;
|
|
|
|
rest = fb->file_end - fb->file_pos;
|
|
out = NULL;
|
|
next = &out;
|
|
n = 0;
|
|
|
|
do {
|
|
alloc = nxt_min(rest, NXT_HTTP_STATIC_BUF_SIZE);
|
|
|
|
b = nxt_buf_mem_alloc(r->mem_pool, alloc, 0);
|
|
if (nxt_slow_path(b == NULL)) {
|
|
goto fail;
|
|
}
|
|
|
|
b->completion_handler = nxt_http_static_buf_completion;
|
|
b->parent = r;
|
|
|
|
nxt_mp_retain(r->mem_pool);
|
|
|
|
*next = b;
|
|
next = &b->next;
|
|
|
|
rest -= alloc;
|
|
|
|
} while (rest > 0 && ++n < NXT_HTTP_STATIC_BUF_COUNT);
|
|
|
|
wq = &task->thread->engine->fast_work_queue;
|
|
|
|
nxt_sendbuf_drain(task, wq, out);
|
|
return;
|
|
|
|
fail:
|
|
|
|
while (out != NULL) {
|
|
b = out;
|
|
out = b->next;
|
|
|
|
nxt_mp_free(r->mem_pool, b);
|
|
nxt_mp_release(r->mem_pool);
|
|
}
|
|
}
|
|
|
|
|
|
static const nxt_http_request_state_t nxt_http_static_send_state
|
|
nxt_aligned(64) =
|
|
{
|
|
.error_handler = nxt_http_request_error_handler,
|
|
};
|
|
|
|
|
|
static void
|
|
nxt_http_static_buf_completion(nxt_task_t *task, void *obj, void *data)
|
|
{
|
|
ssize_t n, size;
|
|
nxt_buf_t *b, *fb, *next;
|
|
nxt_off_t rest;
|
|
nxt_http_request_t *r;
|
|
|
|
b = obj;
|
|
r = data;
|
|
|
|
complete_buf:
|
|
|
|
fb = r->out;
|
|
|
|
if (nxt_slow_path(fb == NULL || r->error)) {
|
|
goto clean;
|
|
}
|
|
|
|
rest = fb->file_end - fb->file_pos;
|
|
size = nxt_buf_mem_size(&b->mem);
|
|
|
|
size = nxt_min(rest, (nxt_off_t) size);
|
|
|
|
n = nxt_file_read(fb->file, b->mem.start, size, fb->file_pos);
|
|
|
|
if (n != size) {
|
|
if (n >= 0) {
|
|
nxt_log(task, NXT_LOG_ERR, "file \"%FN\" has changed "
|
|
"while sending response to a client", fb->file->name);
|
|
}
|
|
|
|
nxt_http_request_error_handler(task, r, r->proto.any);
|
|
goto clean;
|
|
}
|
|
|
|
next = b->next;
|
|
|
|
if (n == rest) {
|
|
nxt_file_close(task, fb->file);
|
|
r->out = NULL;
|
|
|
|
b->next = nxt_http_buf_last(r);
|
|
|
|
} else {
|
|
fb->file_pos += n;
|
|
b->next = NULL;
|
|
}
|
|
|
|
b->mem.pos = b->mem.start;
|
|
b->mem.free = b->mem.pos + n;
|
|
|
|
nxt_http_request_send(task, r, b);
|
|
|
|
if (next != NULL) {
|
|
b = next;
|
|
goto complete_buf;
|
|
}
|
|
|
|
return;
|
|
|
|
clean:
|
|
|
|
do {
|
|
next = b->next;
|
|
|
|
nxt_mp_free(r->mem_pool, b);
|
|
nxt_mp_release(r->mem_pool);
|
|
|
|
b = next;
|
|
} while (b != NULL);
|
|
|
|
if (fb != NULL) {
|
|
nxt_file_close(task, fb->file);
|
|
r->out = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash)
|
|
{
|
|
nxt_str_t *type, exten;
|
|
nxt_int_t ret;
|
|
nxt_uint_t i;
|
|
|
|
static const struct {
|
|
nxt_str_t type;
|
|
const char *exten;
|
|
} default_types[] = {
|
|
|
|
{ nxt_string("text/html"), ".html" },
|
|
{ nxt_string("text/html"), ".htm" },
|
|
{ nxt_string("text/css"), ".css" },
|
|
|
|
{ nxt_string("image/svg+xml"), ".svg" },
|
|
{ nxt_string("image/webp"), ".webp" },
|
|
{ nxt_string("image/png"), ".png" },
|
|
{ nxt_string("image/apng"), ".apng" },
|
|
{ nxt_string("image/jpeg"), ".jpeg" },
|
|
{ nxt_string("image/jpeg"), ".jpg" },
|
|
{ nxt_string("image/gif"), ".gif" },
|
|
{ nxt_string("image/x-icon"), ".ico" },
|
|
|
|
{ nxt_string("image/avif"), ".avif" },
|
|
{ nxt_string("image/avif-sequence"), ".avifs" },
|
|
|
|
{ nxt_string("font/woff"), ".woff" },
|
|
{ nxt_string("font/woff2"), ".woff2" },
|
|
{ nxt_string("font/otf"), ".otf" },
|
|
{ nxt_string("font/ttf"), ".ttf" },
|
|
|
|
{ nxt_string("text/plain"), ".txt" },
|
|
{ nxt_string("text/markdown"), ".md" },
|
|
{ nxt_string("text/x-rst"), ".rst" },
|
|
|
|
{ nxt_string("application/javascript"), ".js" },
|
|
{ nxt_string("application/json"), ".json" },
|
|
{ nxt_string("application/xml"), ".xml" },
|
|
{ nxt_string("application/rss+xml"), ".rss" },
|
|
{ nxt_string("application/atom+xml"), ".atom" },
|
|
{ nxt_string("application/pdf"), ".pdf" },
|
|
|
|
{ nxt_string("application/zip"), ".zip" },
|
|
|
|
{ nxt_string("audio/mpeg"), ".mp3" },
|
|
{ nxt_string("audio/ogg"), ".ogg" },
|
|
{ nxt_string("audio/midi"), ".midi" },
|
|
{ nxt_string("audio/midi"), ".mid" },
|
|
{ nxt_string("audio/flac"), ".flac" },
|
|
{ nxt_string("audio/aac"), ".aac" },
|
|
{ nxt_string("audio/wav"), ".wav" },
|
|
|
|
{ nxt_string("video/mpeg"), ".mpeg" },
|
|
{ nxt_string("video/mpeg"), ".mpg" },
|
|
{ nxt_string("video/mp4"), ".mp4" },
|
|
{ nxt_string("video/webm"), ".webm" },
|
|
{ nxt_string("video/x-msvideo"), ".avi" },
|
|
|
|
{ nxt_string("application/octet-stream"), ".exe" },
|
|
{ nxt_string("application/octet-stream"), ".bin" },
|
|
{ nxt_string("application/octet-stream"), ".dll" },
|
|
{ nxt_string("application/octet-stream"), ".iso" },
|
|
{ nxt_string("application/octet-stream"), ".img" },
|
|
{ nxt_string("application/octet-stream"), ".msi" },
|
|
|
|
{ nxt_string("application/octet-stream"), ".deb" },
|
|
{ nxt_string("application/octet-stream"), ".rpm" },
|
|
|
|
{ nxt_string("application/x-httpd-php"), ".php" },
|
|
};
|
|
|
|
for (i = 0; i < nxt_nitems(default_types); i++) {
|
|
type = (nxt_str_t *) &default_types[i].type;
|
|
|
|
exten.start = (u_char *) default_types[i].exten;
|
|
exten.length = nxt_strlen(exten.start);
|
|
|
|
ret = nxt_http_static_mtypes_hash_add(mp, hash, &exten, type);
|
|
if (nxt_slow_path(ret != NXT_OK)) {
|
|
return NXT_ERROR;
|
|
}
|
|
}
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
static const nxt_lvlhsh_proto_t nxt_http_static_mtypes_hash_proto
|
|
nxt_aligned(64) =
|
|
{
|
|
NXT_LVLHSH_DEFAULT,
|
|
nxt_http_static_mtypes_hash_test,
|
|
nxt_http_static_mtypes_hash_alloc,
|
|
nxt_http_static_mtypes_hash_free,
|
|
};
|
|
|
|
|
|
typedef struct {
|
|
nxt_str_t exten;
|
|
nxt_str_t *type;
|
|
} nxt_http_static_mtype_t;
|
|
|
|
|
|
nxt_int_t
|
|
nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash,
|
|
nxt_str_t *exten, nxt_str_t *type)
|
|
{
|
|
nxt_lvlhsh_query_t lhq;
|
|
nxt_http_static_mtype_t *mtype;
|
|
|
|
mtype = nxt_mp_get(mp, sizeof(nxt_http_static_mtype_t));
|
|
if (nxt_slow_path(mtype == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
mtype->exten = *exten;
|
|
mtype->type = type;
|
|
|
|
lhq.key = *exten;
|
|
lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
|
|
lhq.replace = 1;
|
|
lhq.value = mtype;
|
|
lhq.proto = &nxt_http_static_mtypes_hash_proto;
|
|
lhq.pool = mp;
|
|
|
|
return nxt_lvlhsh_insert(hash, &lhq);
|
|
}
|
|
|
|
|
|
nxt_str_t *
|
|
nxt_http_static_mtype_get(nxt_lvlhsh_t *hash, nxt_str_t *exten)
|
|
{
|
|
nxt_lvlhsh_query_t lhq;
|
|
nxt_http_static_mtype_t *mtype;
|
|
|
|
static nxt_str_t empty = nxt_string("");
|
|
|
|
lhq.key = *exten;
|
|
lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
|
|
lhq.proto = &nxt_http_static_mtypes_hash_proto;
|
|
|
|
if (nxt_lvlhsh_find(hash, &lhq) == NXT_OK) {
|
|
mtype = lhq.value;
|
|
return mtype->type;
|
|
}
|
|
|
|
return ∅
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
|
|
{
|
|
nxt_http_static_mtype_t *mtype;
|
|
|
|
mtype = data;
|
|
|
|
return nxt_strcasestr_eq(&lhq->key, &mtype->exten) ? NXT_OK : NXT_DECLINED;
|
|
}
|
|
|
|
|
|
static void *
|
|
nxt_http_static_mtypes_hash_alloc(void *data, size_t size)
|
|
{
|
|
return nxt_mp_align(data, size, size);
|
|
}
|
|
|
|
|
|
static void
|
|
nxt_http_static_mtypes_hash_free(void *data, void *p)
|
|
{
|
|
nxt_mp_free(data, p);
|
|
}
|