When a variable is accessed in the Unit configuration, the value is cached.
This was useful prior to the URI rewrite feature, but now that the URI (more
precisely, the request target) can be rewritten, the contents of the variable
$uri (which contains the path part of the request target, and is decoded)
should not be cached anymore, or at least the cached value should be invalidated
after a URI rewrite.
Example:
{
"rewrite": "/prefix$uri",
"share": "$uri"
}
For a request line like GET /foo?bar=baz HTTP/1.1\r\n, the expected file
served in the response would be /prefix/foo, but due to the caching issue,
Unit currently serves /foo.
589 lines
12 KiB
C
589 lines
12 KiB
C
|
|
/*
|
|
* Copyright (C) NGINX, Inc.
|
|
*/
|
|
|
|
#include <nxt_main.h>
|
|
|
|
|
|
struct nxt_var_s {
|
|
size_t length;
|
|
nxt_uint_t vars;
|
|
u_char data[];
|
|
|
|
/*
|
|
nxt_var_sub_t subs[vars];
|
|
u_char raw[length];
|
|
*/
|
|
};
|
|
|
|
|
|
typedef struct {
|
|
uint32_t index;
|
|
uint32_t length;
|
|
uint32_t position;
|
|
} nxt_var_sub_t;
|
|
|
|
|
|
struct nxt_var_query_s {
|
|
nxt_mp_t *pool;
|
|
|
|
nxt_var_cache_t cache;
|
|
|
|
nxt_uint_t waiting;
|
|
nxt_uint_t failed; /* 1 bit */
|
|
|
|
void *ctx;
|
|
void *data;
|
|
|
|
nxt_work_handler_t ready;
|
|
nxt_work_handler_t error;
|
|
};
|
|
|
|
|
|
#define nxt_var_subs(var) ((nxt_var_sub_t *) (var)->data)
|
|
|
|
#define nxt_var_raw_start(var) \
|
|
((var)->data + (var)->vars * sizeof(nxt_var_sub_t))
|
|
|
|
|
|
static nxt_int_t nxt_var_hash_test(nxt_lvlhsh_query_t *lhq, void *data);
|
|
static nxt_var_decl_t *nxt_var_hash_find(nxt_str_t *name);
|
|
|
|
static nxt_var_decl_t *nxt_var_decl_get(nxt_str_t *name, nxt_array_t *fields,
|
|
uint32_t *index);
|
|
static nxt_var_field_t *nxt_var_field_add(nxt_array_t *fields, nxt_str_t *name,
|
|
uint32_t hash);
|
|
|
|
static nxt_int_t nxt_var_cache_test(nxt_lvlhsh_query_t *lhq, void *data);
|
|
static nxt_str_t *nxt_var_cache_value(nxt_task_t *task, nxt_var_cache_t *cache,
|
|
uint32_t index, void *ctx);
|
|
|
|
static u_char *nxt_var_next_part(u_char *start, u_char *end, nxt_str_t *part);
|
|
|
|
|
|
static const nxt_lvlhsh_proto_t nxt_var_hash_proto nxt_aligned(64) = {
|
|
NXT_LVLHSH_DEFAULT,
|
|
nxt_var_hash_test,
|
|
nxt_lvlhsh_alloc,
|
|
nxt_lvlhsh_free,
|
|
};
|
|
|
|
static const nxt_lvlhsh_proto_t nxt_var_cache_proto nxt_aligned(64) = {
|
|
NXT_LVLHSH_DEFAULT,
|
|
nxt_var_cache_test,
|
|
nxt_mp_lvlhsh_alloc,
|
|
nxt_mp_lvlhsh_free,
|
|
};
|
|
|
|
|
|
static nxt_lvlhsh_t nxt_var_hash;
|
|
static uint32_t nxt_var_count;
|
|
|
|
static nxt_var_decl_t **nxt_vars;
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_var_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
|
|
{
|
|
nxt_var_decl_t *decl;
|
|
|
|
decl = data;
|
|
|
|
return nxt_strstr_eq(&lhq->key, &decl->name) ? NXT_OK : NXT_DECLINED;
|
|
}
|
|
|
|
|
|
static nxt_var_decl_t *
|
|
nxt_var_hash_find(nxt_str_t *name)
|
|
{
|
|
nxt_lvlhsh_query_t lhq;
|
|
|
|
lhq.key_hash = nxt_djb_hash(name->start, name->length);
|
|
lhq.key = *name;
|
|
lhq.proto = &nxt_var_hash_proto;
|
|
|
|
if (nxt_lvlhsh_find(&nxt_var_hash, &lhq) != NXT_OK) {
|
|
return NULL;
|
|
}
|
|
|
|
return lhq.value;
|
|
}
|
|
|
|
|
|
static nxt_var_decl_t *
|
|
nxt_var_decl_get(nxt_str_t *name, nxt_array_t *fields, uint32_t *index)
|
|
{
|
|
u_char *p, *end;
|
|
int64_t hash;
|
|
uint16_t field;
|
|
nxt_str_t str;
|
|
nxt_var_decl_t *decl;
|
|
nxt_var_field_t *f;
|
|
|
|
f = NULL;
|
|
field = 0;
|
|
decl = nxt_var_hash_find(name);
|
|
|
|
if (decl == NULL) {
|
|
p = name->start;
|
|
end = p + name->length;
|
|
|
|
while (p < end) {
|
|
if (*p++ == '_') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (p == end) {
|
|
return NULL;
|
|
}
|
|
|
|
str.start = name->start;
|
|
str.length = p - 1 - name->start;
|
|
|
|
decl = nxt_var_hash_find(&str);
|
|
|
|
if (decl != NULL) {
|
|
str.start = p;
|
|
str.length = end - p;
|
|
|
|
hash = decl->field_hash(fields->mem_pool, &str);
|
|
if (nxt_slow_path(hash == -1)) {
|
|
return NULL;
|
|
}
|
|
|
|
f = nxt_var_field_add(fields, &str, (uint32_t) hash);
|
|
if (nxt_slow_path(f == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
field = f->index;
|
|
}
|
|
}
|
|
|
|
if (decl != NULL) {
|
|
if (decl->field_hash != NULL && f == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (index != NULL) {
|
|
*index = (decl->index << 16) | field;
|
|
}
|
|
}
|
|
|
|
return decl;
|
|
}
|
|
|
|
|
|
static nxt_var_field_t *
|
|
nxt_var_field_add(nxt_array_t *fields, nxt_str_t *name, uint32_t hash)
|
|
{
|
|
nxt_uint_t i;
|
|
nxt_var_field_t *field;
|
|
|
|
field = fields->elts;
|
|
|
|
for (i = 0; i < fields->nelts; i++) {
|
|
if (field[i].hash == hash
|
|
&& nxt_strstr_eq(&field[i].name, name))
|
|
{
|
|
return field;
|
|
}
|
|
}
|
|
|
|
field = nxt_array_add(fields);
|
|
if (nxt_slow_path(field == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
field->name = *name;
|
|
field->hash = hash;
|
|
field->index = fields->nelts - 1;
|
|
|
|
return field;
|
|
}
|
|
|
|
|
|
nxt_var_field_t *
|
|
nxt_var_field_get(nxt_array_t *fields, uint16_t index)
|
|
{
|
|
nxt_uint_t nfields;
|
|
nxt_var_field_t *field;
|
|
|
|
field = fields->elts;
|
|
nfields = fields->nelts;
|
|
|
|
if (nfields > 0 && index <= nfields) {
|
|
return &field[index];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_var_cache_test(nxt_lvlhsh_query_t *lhq, void *data)
|
|
{
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
static nxt_str_t *
|
|
nxt_var_cache_value(nxt_task_t *task, nxt_var_cache_t *cache, uint32_t index,
|
|
void *ctx)
|
|
{
|
|
nxt_int_t ret;
|
|
nxt_str_t *value;
|
|
nxt_var_decl_t *var;
|
|
nxt_lvlhsh_query_t lhq;
|
|
|
|
var = nxt_vars[index >> 16];
|
|
|
|
value = cache->spare;
|
|
|
|
if (value == NULL) {
|
|
value = nxt_mp_zget(cache->pool, sizeof(nxt_str_t));
|
|
if (nxt_slow_path(value == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
cache->spare = value;
|
|
}
|
|
|
|
if (!var->cacheable) {
|
|
goto not_cached;
|
|
}
|
|
|
|
lhq.key_hash = nxt_murmur_hash2_uint32(&index);
|
|
lhq.replace = 0;
|
|
lhq.key.length = sizeof(uint32_t);
|
|
lhq.key.start = (u_char *) &index;
|
|
lhq.value = value;
|
|
lhq.proto = &nxt_var_cache_proto;
|
|
lhq.pool = cache->pool;
|
|
|
|
ret = nxt_lvlhsh_insert(&cache->hash, &lhq);
|
|
if (nxt_slow_path(ret == NXT_ERROR)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (ret == NXT_DECLINED) {
|
|
return lhq.value;
|
|
}
|
|
|
|
not_cached:
|
|
|
|
ret = var->handler(task, value, ctx, index & 0xffff);
|
|
if (nxt_slow_path(ret != NXT_OK)) {
|
|
return NULL;
|
|
}
|
|
|
|
cache->spare = NULL;
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_var_register(nxt_var_decl_t *decl, size_t n)
|
|
{
|
|
nxt_uint_t i;
|
|
nxt_lvlhsh_query_t lhq;
|
|
|
|
lhq.replace = 0;
|
|
lhq.proto = &nxt_var_hash_proto;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
lhq.key = decl[i].name;
|
|
lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
|
|
lhq.value = &decl[i];
|
|
|
|
if (nxt_slow_path(nxt_lvlhsh_insert(&nxt_var_hash, &lhq) != NXT_OK)) {
|
|
return NXT_ERROR;
|
|
}
|
|
}
|
|
|
|
nxt_var_count += n;
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_var_index_init(void)
|
|
{
|
|
nxt_uint_t i;
|
|
nxt_var_decl_t *decl, **vars;
|
|
nxt_lvlhsh_each_t lhe;
|
|
|
|
vars = nxt_memalign(64, nxt_var_count * sizeof(nxt_var_decl_t *));
|
|
if (vars == NULL) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
nxt_lvlhsh_each_init(&lhe, &nxt_var_hash_proto);
|
|
|
|
for (i = 0; i < nxt_var_count; i++) {
|
|
decl = nxt_lvlhsh_each(&nxt_var_hash, &lhe);
|
|
decl->index = i;
|
|
vars[i] = decl;
|
|
}
|
|
|
|
nxt_vars = vars;
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
nxt_var_t *
|
|
nxt_var_compile(nxt_str_t *str, nxt_mp_t *mp, nxt_array_t *fields)
|
|
{
|
|
u_char *p, *end, *next, *src;
|
|
size_t size;
|
|
uint32_t index;
|
|
nxt_var_t *var;
|
|
nxt_str_t part;
|
|
nxt_uint_t n;
|
|
nxt_var_sub_t *subs;
|
|
nxt_var_decl_t *decl;
|
|
|
|
n = 0;
|
|
|
|
p = str->start;
|
|
end = p + str->length;
|
|
|
|
while (p < end) {
|
|
p = nxt_var_next_part(p, end, &part);
|
|
if (nxt_slow_path(p == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (part.start != NULL) {
|
|
n++;
|
|
}
|
|
}
|
|
|
|
size = sizeof(nxt_var_t) + n * sizeof(nxt_var_sub_t) + str->length;
|
|
|
|
var = nxt_mp_get(mp, size);
|
|
if (nxt_slow_path(var == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
var->length = str->length;
|
|
var->vars = n;
|
|
|
|
subs = nxt_var_subs(var);
|
|
src = nxt_var_raw_start(var);
|
|
|
|
nxt_memcpy(src, str->start, str->length);
|
|
|
|
n = 0;
|
|
p = str->start;
|
|
|
|
while (p < end) {
|
|
next = nxt_var_next_part(p, end, &part);
|
|
|
|
if (part.start != NULL) {
|
|
decl = nxt_var_decl_get(&part, fields, &index);
|
|
if (nxt_slow_path(decl == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
subs[n].index = index;
|
|
subs[n].length = next - p;
|
|
subs[n].position = p - str->start;
|
|
|
|
n++;
|
|
}
|
|
|
|
p = next;
|
|
}
|
|
|
|
return var;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_var_test(nxt_str_t *str, nxt_array_t *fields, u_char *error)
|
|
{
|
|
u_char *p, *end, *next;
|
|
nxt_str_t part;
|
|
nxt_var_decl_t *decl;
|
|
|
|
p = str->start;
|
|
end = p + str->length;
|
|
|
|
while (p < end) {
|
|
next = nxt_var_next_part(p, end, &part);
|
|
|
|
if (next == NULL) {
|
|
nxt_sprintf(error, error + NXT_MAX_ERROR_STR,
|
|
"Invalid variable at position %uz%Z", p - str->start);
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
if (part.start != NULL) {
|
|
decl = nxt_var_decl_get(&part, fields, NULL);
|
|
|
|
if (decl == NULL) {
|
|
nxt_sprintf(error, error + NXT_MAX_ERROR_STR,
|
|
"Unknown variable \"%V\"%Z", &part);
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
}
|
|
|
|
p = next;
|
|
}
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
static u_char *
|
|
nxt_var_next_part(u_char *start, u_char *end, nxt_str_t *part)
|
|
{
|
|
size_t length;
|
|
u_char *p, ch, c;
|
|
nxt_bool_t bracket;
|
|
|
|
p = memchr(start, '$', end - start);
|
|
|
|
if (p == start) {
|
|
p++;
|
|
|
|
if (p == end) {
|
|
return NULL;
|
|
}
|
|
|
|
if (*p == '{') {
|
|
bracket = 1;
|
|
|
|
if (end - p < 2) {
|
|
return NULL;
|
|
}
|
|
|
|
p++;
|
|
|
|
} else {
|
|
bracket = 0;
|
|
}
|
|
|
|
length = 0;
|
|
start = p;
|
|
|
|
while (p < end) {
|
|
ch = *p;
|
|
|
|
c = (u_char) (ch | 0x20);
|
|
|
|
if ((c >= 'a' && c <= 'z') || ch == '_') {
|
|
p++;
|
|
length++;
|
|
continue;
|
|
}
|
|
|
|
if (bracket && ch == '}') {
|
|
p++;
|
|
bracket = 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (bracket || length == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
part->length = length;
|
|
part->start = start;
|
|
|
|
} else {
|
|
if (p == NULL) {
|
|
p = end;
|
|
}
|
|
|
|
nxt_str_null(part);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_var_interpreter(nxt_task_t *task, nxt_var_cache_t *cache, nxt_var_t *var,
|
|
nxt_str_t *str, void *ctx, nxt_bool_t logging)
|
|
{
|
|
u_char *p, *src;
|
|
size_t length, last, next;
|
|
nxt_str_t *value, **part;
|
|
nxt_uint_t i;
|
|
nxt_array_t parts;
|
|
nxt_var_sub_t *subs;
|
|
|
|
nxt_memzero(&parts, sizeof(nxt_array_t));
|
|
nxt_array_init(&parts, cache->pool, sizeof(nxt_str_t *));
|
|
|
|
subs = nxt_var_subs(var);
|
|
|
|
length = var->length;
|
|
|
|
for (i = 0; i < var->vars; i++) {
|
|
value = nxt_var_cache_value(task, cache, subs[i].index, ctx);
|
|
if (nxt_slow_path(value == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
part = nxt_array_add(&parts);
|
|
if (nxt_slow_path(part == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
*part = value;
|
|
|
|
length += value->length - subs[i].length;
|
|
|
|
if (logging && value->start == NULL) {
|
|
length += 1;
|
|
}
|
|
}
|
|
|
|
p = nxt_mp_nget(cache->pool, length);
|
|
if (nxt_slow_path(p == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
str->length = length;
|
|
str->start = p;
|
|
|
|
part = parts.elts;
|
|
src = nxt_var_raw_start(var);
|
|
|
|
last = 0;
|
|
|
|
for (i = 0; i < var->vars; i++) {
|
|
next = subs[i].position;
|
|
|
|
if (next != last) {
|
|
p = nxt_cpymem(p, &src[last], next - last);
|
|
}
|
|
|
|
p = nxt_cpymem(p, part[i]->start, part[i]->length);
|
|
|
|
if (logging && part[i]->start == NULL) {
|
|
*p++ = '-';
|
|
}
|
|
|
|
last = next + subs[i].length;
|
|
}
|
|
|
|
if (last != var->length) {
|
|
p = nxt_cpymem(p, &src[last], var->length - last);
|
|
}
|
|
|
|
return NXT_OK;
|
|
}
|