/* * Copyright (C) NGINX, Inc. */ #include struct nxt_var_s { size_t length; nxt_uint_t vars; uint8_t strz; /* 1 bit */ 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; typedef struct { nxt_var_t *var; nxt_str_t *value; } nxt_var_value_t; struct nxt_var_query_s { nxt_array_t values; /* of nxt_var_value_t */ nxt_array_t parts; /* of nxt_str_t * */ nxt_lvlhsh_t cache; nxt_str_t *spare; 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_int_t nxt_var_cache_test(nxt_lvlhsh_query_t *lhq, void *data); static nxt_str_t *nxt_var_cache_find(nxt_lvlhsh_t *lh, uint32_t index); static nxt_int_t nxt_var_cache_add(nxt_lvlhsh_t *lh, uint32_t index, nxt_str_t *value, nxt_mp_t *mp); static u_char *nxt_var_next_part(u_char *start, size_t length, nxt_str_t *part, nxt_bool_t *is_var); static void nxt_var_query_finish(nxt_task_t *task, nxt_var_query_t *query); 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_handler_t *nxt_var_index; void nxt_var_raw(nxt_var_t *var, nxt_str_t *str) { str->length = var->length; str->start = nxt_var_raw_start(var); } nxt_bool_t nxt_var_is_const(nxt_var_t *var) { return (var->vars == 0); } 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_int_t nxt_var_cache_test(nxt_lvlhsh_query_t *lhq, void *data) { return NXT_OK; } static nxt_str_t * nxt_var_cache_find(nxt_lvlhsh_t *lh, uint32_t index) { nxt_lvlhsh_query_t lhq; lhq.key_hash = nxt_murmur_hash2_uint32(&index); lhq.key.length = sizeof(uint32_t); lhq.key.start = (u_char *) &index; lhq.proto = &nxt_var_cache_proto; if (nxt_lvlhsh_find(lh, &lhq) != NXT_OK) { return NULL; } return lhq.value; } static nxt_int_t nxt_var_cache_add(nxt_lvlhsh_t *lh, uint32_t index, nxt_str_t *value, nxt_mp_t *mp) { nxt_lvlhsh_query_t lhq; 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 = mp; return nxt_lvlhsh_insert(lh, &lhq); } 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; nxt_var_handler_t *index; nxt_lvlhsh_each_t lhe; index = nxt_memalign(64, nxt_var_count * sizeof(nxt_var_handler_t)); if (index == 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; index[i] = decl->handler; } nxt_var_index = index; return NXT_OK; } nxt_var_t * nxt_var_compile(nxt_str_t *str, nxt_mp_t *mp, nxt_bool_t strz) { u_char *p, *end, *next, *src; size_t size; nxt_var_t *var; nxt_str_t part; nxt_uint_t n; nxt_bool_t is_var; 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 - p, &part, &is_var); if (nxt_slow_path(p == NULL)) { return NULL; } if (is_var) { n++; } } size = sizeof(nxt_var_t) + n * sizeof(nxt_var_sub_t) + str->length; var = nxt_mp_get(mp, size + strz); if (nxt_slow_path(var == NULL)) { return NULL; } var->length = str->length; var->vars = n; var->strz = strz; subs = nxt_var_subs(var); src = nxt_var_raw_start(var); nxt_memcpy(src, str->start, str->length); if (strz) { src[str->length] = '\0'; } n = 0; p = str->start; while (p < end) { next = nxt_var_next_part(p, end - p, &part, &is_var); if (is_var) { decl = nxt_var_hash_find(&part); if (nxt_slow_path(decl == NULL)) { return NULL; } subs[n].index = decl->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, u_char *error) { u_char *p, *end, *next; nxt_str_t part; nxt_bool_t is_var; nxt_var_decl_t *decl; p = str->start; end = p + str->length; while (p < end) { next = nxt_var_next_part(p, end - p, &part, &is_var); if (next == NULL) { nxt_sprintf(error, error + NXT_MAX_ERROR_STR, "Invalid variable at position %uz%Z", p - str->start); return NXT_ERROR; } if (is_var) { decl = nxt_var_hash_find(&part); 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, size_t length, nxt_str_t *part, nxt_bool_t *is_var) { u_char *p, *end, ch, c; nxt_bool_t bracket; end = start + length; p = nxt_memchr(start, '$', length); if (p == start) { *is_var = 1; p++; if (p == end) { return NULL; } if (*p == '{') { bracket = 1; if (end - p < 2) { return NULL; } p++; } else { bracket = 0; } start = p; for ( ;; ) { ch = *p; c = (u_char) (ch | 0x20); if ((c < 'a' || c > 'z') && ch != '_') { if (bracket && ch != '}') { return NULL; } break; } p++; if (p == end) { if (bracket) { return NULL; } break; } } length = p - start; end = p + bracket; } else { *is_var = 0; if (p != NULL) { length = p - start; end = p; } } part->length = length; part->start = start; return end; } nxt_int_t nxt_var_query_init(nxt_var_query_t **query_p, void *ctx, nxt_mp_t *mp) { nxt_var_query_t *query; query = *query_p; if (*query_p == NULL) { query = nxt_mp_zget(mp, sizeof(nxt_var_query_t)); if (nxt_slow_path(query == NULL)) { return NXT_ERROR; } nxt_array_init(&query->values, mp, sizeof(nxt_var_value_t)); nxt_array_init(&query->parts, mp, sizeof(nxt_str_t *)); } else { nxt_array_reset(&query->values); } query->ctx = ctx; *query_p = query; return NXT_OK; } void nxt_var_query(nxt_task_t *task, nxt_var_query_t *query, nxt_var_t *var, nxt_str_t *str) { uint32_t index; nxt_mp_t *mp; nxt_str_t *value; nxt_int_t ret; nxt_uint_t i; nxt_var_sub_t *subs; nxt_var_value_t *val; if (nxt_var_is_const(var)) { nxt_var_raw(var, str); return; } if (nxt_slow_path(query->failed)) { return; } mp = query->values.mem_pool; subs = nxt_var_subs(var); value = query->spare; for (i = 0; i < var->vars; i++) { if (value == NULL) { value = nxt_mp_zget(mp, sizeof(nxt_str_t)); if (nxt_slow_path(value == NULL)) { goto fail; } } index = subs[i].index; ret = nxt_var_cache_add(&query->cache, index, value, mp); if (ret != NXT_OK) { if (nxt_slow_path(ret == NXT_ERROR)) { goto fail; } continue; /* NXT_DECLINED */ } ret = nxt_var_index[index](task, query, value, query->ctx); value = NULL; if (ret != NXT_OK) { if (nxt_slow_path(ret != NXT_AGAIN)) { goto fail; } query->waiting++; } } query->spare = value; val = nxt_array_add(&query->values); if (nxt_slow_path(val == NULL)) { goto fail; } val->var = var; val->value = str; return; fail: query->failed = 1; } void nxt_var_query_resolve(nxt_task_t *task, nxt_var_query_t *query, void *data, nxt_work_handler_t ready, nxt_work_handler_t error) { query->data = data; query->ready = ready; query->error = error; if (query->waiting == 0) { nxt_var_query_finish(task, query); } } void nxt_var_query_handle(nxt_task_t *task, nxt_var_query_t *query, nxt_bool_t failed) { query->failed |= failed; if (--query->waiting == 0) { nxt_var_query_finish(task, query); } } static void nxt_var_query_finish(nxt_task_t *task, nxt_var_query_t *query) { u_char *p, *src; size_t length, last, next; nxt_str_t *str, **part; nxt_var_t *var; nxt_uint_t i, j; nxt_var_sub_t *subs; nxt_var_value_t *val; if (query->failed) { goto done; } val = query->values.elts; for (i = 0; i < query->values.nelts; i++) { var = val[i].var; subs = nxt_var_subs(var); length = var->length; for (j = 0; j < var->vars; j++) { str = nxt_var_cache_find(&query->cache, subs[j].index); nxt_assert(str != NULL); part = nxt_array_add(&query->parts); if (nxt_slow_path(part == NULL)) { query->failed = 1; goto done; } *part = str; length += str->length - subs[j].length; } p = nxt_mp_nget(query->values.mem_pool, length + var->strz); if (nxt_slow_path(p == NULL)) { query->failed = 1; goto done; } val[i].value->length = length; val[i].value->start = p; part = query->parts.elts; src = nxt_var_raw_start(var); last = 0; for (j = 0; j < var->vars; j++) { next = subs[j].position; if (next != last) { p = nxt_cpymem(p, &src[last], next - last); } p = nxt_cpymem(p, part[j]->start, part[j]->length); last = next + subs[j].length; } if (last != var->length) { p = nxt_cpymem(p, &src[last], var->length - last); } if (var->strz) { *p = '\0'; } nxt_array_reset(&query->parts); nxt_debug(task, "var: \"%*s\" -> \"%V\"", var->length, src, val[i].value); } done: nxt_work_queue_add(&task->thread->engine->fast_work_queue, query->failed ? query->error : query->ready, task, query->ctx, query->data); }