| |
| /* |
| * Copyright (C) Xiaozhe Wang (chaoslawful) |
| * Copyright (C) Yichun Zhang (agentzh) |
| */ |
| |
| |
| #ifndef DDEBUG |
| #define DDEBUG 0 |
| #endif |
| #include "ddebug.h" |
| |
| |
| #include "ngx_http_lua_variable.h" |
| #include "ngx_http_lua_util.h" |
| |
| |
| static int ngx_http_lua_var_get(lua_State *L); |
| static int ngx_http_lua_var_set(lua_State *L); |
| |
| |
| void |
| ngx_http_lua_inject_variable_api(lua_State *L) |
| { |
| /* {{{ register reference maps */ |
| lua_newtable(L); /* ngx.var */ |
| |
| lua_createtable(L, 0, 2 /* nrec */); /* metatable for .var */ |
| lua_pushcfunction(L, ngx_http_lua_var_get); |
| lua_setfield(L, -2, "__index"); |
| lua_pushcfunction(L, ngx_http_lua_var_set); |
| lua_setfield(L, -2, "__newindex"); |
| lua_setmetatable(L, -2); |
| |
| lua_setfield(L, -2, "var"); |
| } |
| |
| |
| /** |
| * Get nginx internal variables content |
| * |
| * @retval Always return a string or nil on Lua stack. Return nil when failed |
| * to get content, and actual content string when found the specified variable. |
| * @seealso ngx_http_lua_var_set |
| * */ |
| static int |
| ngx_http_lua_var_get(lua_State *L) |
| { |
| ngx_http_request_t *r; |
| u_char *p, *lowcase; |
| size_t len; |
| ngx_uint_t hash; |
| ngx_str_t name; |
| ngx_http_variable_value_t *vv; |
| |
| #if (NGX_PCRE) |
| u_char *val; |
| ngx_uint_t n; |
| LUA_NUMBER index; |
| int *cap; |
| #endif |
| |
| r = ngx_http_lua_get_req(L); |
| if (r == NULL) { |
| return luaL_error(L, "no request object found"); |
| } |
| |
| ngx_http_lua_check_fake_request(L, r); |
| |
| #if (NGX_PCRE) |
| if (lua_type(L, -1) == LUA_TNUMBER) { |
| /* it is a regex capturing variable */ |
| |
| index = lua_tonumber(L, -1); |
| |
| if (index <= 0) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| n = (ngx_uint_t) index * 2; |
| |
| dd("n = %d, ncaptures = %d", (int) n, (int) r->ncaptures); |
| |
| if (r->captures == NULL |
| || r->captures_data == NULL |
| || n >= r->ncaptures) |
| { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| /* n >= 0 && n < r->ncaptures */ |
| |
| cap = r->captures; |
| |
| p = r->captures_data; |
| |
| val = &p[cap[n]]; |
| |
| lua_pushlstring(L, (const char *) val, (size_t) (cap[n + 1] - cap[n])); |
| |
| return 1; |
| } |
| #endif |
| |
| if (lua_type(L, -1) != LUA_TSTRING) { |
| return luaL_error(L, "bad variable name"); |
| } |
| |
| p = (u_char *) lua_tolstring(L, -1, &len); |
| |
| lowcase = lua_newuserdata(L, len); |
| |
| hash = ngx_hash_strlow(lowcase, p, len); |
| |
| name.len = len; |
| name.data = lowcase; |
| |
| vv = ngx_http_get_variable(r, &name, hash); |
| |
| if (vv == NULL || vv->not_found) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| lua_pushlstring(L, (const char *) vv->data, (size_t) vv->len); |
| return 1; |
| } |
| |
| |
| /** |
| * Set nginx internal variable content |
| * |
| * @retval Always return a boolean on Lua stack. Return true when variable |
| * content was modified successfully, false otherwise. |
| * @seealso ngx_http_lua_var_get |
| * */ |
| static int |
| ngx_http_lua_var_set(lua_State *L) |
| { |
| ngx_http_variable_t *v; |
| ngx_http_variable_value_t *vv; |
| ngx_http_core_main_conf_t *cmcf; |
| u_char *p, *lowcase, *val; |
| size_t len; |
| ngx_str_t name; |
| ngx_uint_t hash; |
| ngx_http_request_t *r; |
| int value_type; |
| const char *msg; |
| |
| r = ngx_http_lua_get_req(L); |
| if (r == NULL) { |
| return luaL_error(L, "no request object found"); |
| } |
| |
| ngx_http_lua_check_fake_request(L, r); |
| |
| /* we skip the first argument that is the table */ |
| |
| /* we read the variable name */ |
| |
| if (lua_type(L, 2) != LUA_TSTRING) { |
| return luaL_error(L, "bad variable name"); |
| } |
| |
| p = (u_char *) lua_tolstring(L, 2, &len); |
| |
| lowcase = lua_newuserdata(L, len + 1); |
| |
| hash = ngx_hash_strlow(lowcase, p, len); |
| lowcase[len] = '\0'; |
| |
| name.len = len; |
| name.data = lowcase; |
| |
| /* we read the variable new value */ |
| |
| value_type = lua_type(L, 3); |
| switch (value_type) { |
| case LUA_TNUMBER: |
| case LUA_TSTRING: |
| p = (u_char *) luaL_checklstring(L, 3, &len); |
| |
| val = ngx_palloc(r->pool, len); |
| if (val == NULL) { |
| return luaL_error(L, "memory allocation erorr"); |
| } |
| |
| ngx_memcpy(val, p, len); |
| |
| break; |
| |
| case LUA_TNIL: |
| /* undef the variable */ |
| |
| val = NULL; |
| len = 0; |
| |
| break; |
| |
| default: |
| msg = lua_pushfstring(L, "string, number, or nil expected, " |
| "but got %s", lua_typename(L, value_type)); |
| return luaL_argerror(L, 1, msg); |
| } |
| |
| /* we fetch the variable itself */ |
| |
| cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); |
| |
| v = ngx_hash_find(&cmcf->variables_hash, hash, name.data, name.len); |
| |
| if (v) { |
| if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) { |
| return luaL_error(L, "variable \"%s\" not changeable", lowcase); |
| } |
| |
| if (v->set_handler) { |
| |
| dd("set variables with set_handler"); |
| |
| vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t)); |
| if (vv == NULL) { |
| return luaL_error(L, "out of memory"); |
| } |
| |
| if (value_type == LUA_TNIL) { |
| vv->valid = 0; |
| vv->not_found = 1; |
| vv->no_cacheable = 0; |
| vv->data = NULL; |
| vv->len = 0; |
| |
| } else { |
| vv->valid = 1; |
| vv->not_found = 0; |
| vv->no_cacheable = 0; |
| |
| vv->data = val; |
| vv->len = len; |
| } |
| |
| v->set_handler(r, vv, v->data); |
| |
| return 0; |
| } |
| |
| if (v->flags & NGX_HTTP_VAR_INDEXED) { |
| vv = &r->variables[v->index]; |
| |
| dd("set indexed variable"); |
| |
| if (value_type == LUA_TNIL) { |
| vv->valid = 0; |
| vv->not_found = 1; |
| vv->no_cacheable = 0; |
| |
| vv->data = NULL; |
| vv->len = 0; |
| |
| } else { |
| vv->valid = 1; |
| vv->not_found = 0; |
| vv->no_cacheable = 0; |
| |
| vv->data = val; |
| vv->len = len; |
| } |
| |
| return 0; |
| } |
| |
| return luaL_error(L, "variable \"%s\" cannot be assigned a value", |
| lowcase); |
| } |
| |
| /* variable not found */ |
| |
| return luaL_error(L, "variable \"%s\" not found for writing; " |
| "maybe it is a built-in variable that is not changeable " |
| "or you forgot to use \"set $%s '';\" " |
| "in the config file to define it first", |
| lowcase, lowcase); |
| } |
| |
| |
| #ifndef NGX_HTTP_LUA_NO_FFI_API |
| int |
| ngx_http_lua_ffi_var_get(ngx_http_request_t *r, u_char *name_data, |
| size_t name_len, u_char *lowcase_buf, int capture_id, u_char **value, |
| size_t *value_len, char **err) |
| { |
| ngx_uint_t hash; |
| ngx_str_t name; |
| ngx_http_variable_value_t *vv; |
| |
| #if (NGX_PCRE) |
| u_char *p; |
| ngx_uint_t n; |
| int *cap; |
| #endif |
| |
| if (r == NULL) { |
| *err = "no request object found"; |
| return NGX_ERROR; |
| } |
| |
| if ((r)->connection->fd == -1) { |
| *err = "API disabled in the current context"; |
| return NGX_ERROR; |
| } |
| |
| #if (NGX_PCRE) |
| if (name_data == 0) { |
| if (capture_id <= 0) { |
| return NGX_DECLINED; |
| } |
| |
| /* it is a regex capturing variable */ |
| |
| n = (ngx_uint_t) capture_id * 2; |
| |
| dd("n = %d, ncaptures = %d", (int) n, (int) r->ncaptures); |
| |
| if (r->captures == NULL |
| || r->captures_data == NULL |
| || n >= r->ncaptures) |
| { |
| return NGX_DECLINED; |
| } |
| |
| /* n >= 0 && n < r->ncaptures */ |
| |
| cap = r->captures; |
| p = r->captures_data; |
| |
| *value = &p[cap[n]]; |
| *value_len = (size_t) (cap[n + 1] - cap[n]); |
| |
| return NGX_OK; |
| } |
| #endif |
| |
| hash = ngx_hash_strlow(lowcase_buf, name_data, name_len); |
| |
| name.data = lowcase_buf; |
| name.len = name_len; |
| |
| dd("variable name: %.*s", (int) name_len, lowcase_buf); |
| |
| vv = ngx_http_get_variable(r, &name, hash); |
| if (vv == NULL || vv->not_found) { |
| return NGX_DECLINED; |
| } |
| |
| *value = vv->data; |
| *value_len = vv->len; |
| return NGX_OK; |
| } |
| |
| |
| int |
| ngx_http_lua_ffi_var_set(ngx_http_request_t *r, u_char *name_data, |
| size_t name_len, u_char *lowcase_buf, u_char *value, size_t value_len, |
| u_char *errbuf, size_t errlen) |
| { |
| u_char *p; |
| ngx_uint_t hash; |
| ngx_http_variable_t *v; |
| ngx_http_variable_value_t *vv; |
| ngx_http_core_main_conf_t *cmcf; |
| |
| if (r == NULL) { |
| ngx_snprintf(errbuf, errlen, "no request object found"); |
| return NGX_ERROR; |
| } |
| |
| if ((r)->connection->fd == -1) { |
| ngx_snprintf(errbuf, errlen, "API disabled in the current context"); |
| return NGX_ERROR; |
| } |
| |
| hash = ngx_hash_strlow(lowcase_buf, name_data, name_len); |
| |
| dd("variable name: %.*s", (int) name_len, lowcase_buf); |
| |
| /* we fetch the variable itself */ |
| |
| cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); |
| |
| v = ngx_hash_find(&cmcf->variables_hash, hash, lowcase_buf, name_len); |
| |
| if (v) { |
| if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) { |
| dd("variable not changeable"); |
| ngx_snprintf(errbuf, errlen, "variable \"%*s\" not changeable", |
| name_len, lowcase_buf); |
| return NGX_ERROR; |
| } |
| |
| if (v->set_handler) { |
| |
| dd("set variables with set_handler"); |
| |
| if (value != NULL && value_len) { |
| vv = ngx_pnalloc(r->pool, sizeof(ngx_http_variable_value_t) |
| + value_len); |
| if (vv == NULL) { |
| goto nomem; |
| } |
| |
| p = (u_char *) vv + sizeof(ngx_http_variable_value_t); |
| ngx_memcpy(p, value, value_len); |
| value = p; |
| |
| } else { |
| vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t)); |
| if (vv == NULL) { |
| goto nomem; |
| } |
| } |
| |
| if (value == NULL) { |
| vv->valid = 0; |
| vv->not_found = 1; |
| vv->no_cacheable = 0; |
| vv->data = NULL; |
| vv->len = 0; |
| |
| } else { |
| vv->valid = 1; |
| vv->not_found = 0; |
| vv->no_cacheable = 0; |
| |
| vv->data = value; |
| vv->len = value_len; |
| } |
| |
| v->set_handler(r, vv, v->data); |
| return NGX_OK; |
| } |
| |
| if (v->flags & NGX_HTTP_VAR_INDEXED) { |
| vv = &r->variables[v->index]; |
| |
| dd("set indexed variable"); |
| |
| if (value == NULL) { |
| vv->valid = 0; |
| vv->not_found = 1; |
| vv->no_cacheable = 0; |
| |
| vv->data = NULL; |
| vv->len = 0; |
| |
| } else { |
| p = ngx_palloc(r->pool, value_len); |
| if (p == NULL) { |
| goto nomem; |
| } |
| ngx_memcpy(p, value, value_len); |
| value = p; |
| |
| vv->valid = 1; |
| vv->not_found = 0; |
| vv->no_cacheable = 0; |
| |
| vv->data = value; |
| vv->len = value_len; |
| } |
| |
| return NGX_OK; |
| } |
| |
| ngx_snprintf(errbuf, errlen, "variable \"%*s\" cannot be assigned " |
| "a value", name_len, lowcase_buf); |
| return NGX_ERROR; |
| } |
| |
| /* variable not found */ |
| |
| ngx_snprintf(errbuf, errlen, "variable \"%*s\" not found for writing; " |
| "maybe it is a built-in variable that is not changeable " |
| "or you forgot to use \"set $%*s '';\" " |
| "in the config file to define it first", |
| name_len, lowcase_buf, name_len, lowcase_buf); |
| return NGX_ERROR; |
| |
| nomem: |
| |
| ngx_snprintf(errbuf, errlen, "no memory"); |
| return NGX_ERROR; |
| } |
| #endif /* NGX_HTTP_LUA_NO_FFI_API */ |
| |
| |
| /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ |