lighttpd1.4/src/mod_magnet.c

1110 lines
31 KiB
C
Raw Normal View History

#include "first.h"
#include "base.h"
#include "log.h"
#include "buffer.h"
[core] open fd when appending file to cq (fixes #2655) http_chunk_append_file() opens fd when appending file to chunkqueue. Defers calculation of content length until response is finished. This reduces race conditions pertaining to stat() and then (later) open(), when the result of the stat() was used for Content-Length or to generate chunked headers. Note: this does not change how lighttpd handles files that are modified in-place by another process after having been opened by lighttpd -- don't do that. This *does* improve handling of files that are frequently modified via a temporary file and then atomically renamed into place. mod_fastcgi has been modified to use http_chunk_append_file_range() with X-Sendfile2 and will open the target file multiple times if there are multiple ranges. Note: (future todo) not implemented for chunk.[ch] interfaces used by range requests in mod_staticfile or by mod_ssi. Those uses could lead to too many open fds. For mod_staticfile, limits should be put in place for max number of ranges accepted by mod_staticfile. For mod_ssi, limits would need to be placed on the maximum number of includes, and the primary SSI file split across lots of SSI directives should either copy the pieces or perhaps chunk.h could be extended to allow for an open fd to be shared across multiple chunks. Doing either of these would improve the performance of SSI since they would replace many file opens on the pieces of the SSI file around the SSI directives. x-ref: "Serving a file that is getting updated can cause an empty response or incorrect content-length error" https://redmine.lighttpd.net/issues/2655 github: Closes #49
2016-03-30 10:39:33 +00:00
#include "http_chunk.h"
#include "plugin.h"
#include "mod_magnet_cache.h"
#include "response.h"
#include "stat_cache.h"
#include "status_counter.h"
#include "etag.h"
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <setjmp.h>
#ifdef HAVE_LUA_H
#include <lua.h>
#include <lauxlib.h>
#define LUA_RIDX_LIGHTTPD_SERVER "lighty.srv"
#define LUA_RIDX_LIGHTTPD_CONNECTION "lighty.con"
#define MAGNET_CONFIG_RAW_URL "magnet.attract-raw-url-to"
#define MAGNET_CONFIG_PHYSICAL_PATH "magnet.attract-physical-path-to"
#define MAGNET_RESTART_REQUEST 99
/* plugin config for all request/connections */
static jmp_buf exceptionjmp;
typedef struct {
array *url_raw;
array *physical_path;
} plugin_config;
typedef struct {
PLUGIN_DATA;
script_cache *cache;
buffer *encode_buf;
plugin_config **config_storage;
plugin_config conf;
} plugin_data;
/* init the plugin data */
INIT_FUNC(mod_magnet_init) {
plugin_data *p;
p = calloc(1, sizeof(*p));
p->cache = script_cache_init();
p->encode_buf = buffer_init();
return p;
}
/* detroy the plugin data */
FREE_FUNC(mod_magnet_free) {
plugin_data *p = p_d;
UNUSED(srv);
if (!p) return HANDLER_GO_ON;
if (p->config_storage) {
size_t i;
for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s = p->config_storage[i];
if (NULL == s) continue;
array_free(s->url_raw);
array_free(s->physical_path);
free(s);
}
free(p->config_storage);
}
script_cache_free(p->cache);
buffer_free(p->encode_buf);
free(p);
return HANDLER_GO_ON;
}
/* handle plugin config and check values */
SETDEFAULTS_FUNC(mod_magnet_set_defaults) {
plugin_data *p = p_d;
size_t i = 0;
config_values_t cv[] = {
{ MAGNET_CONFIG_RAW_URL, NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
{ MAGNET_CONFIG_PHYSICAL_PATH, NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};
if (!p) return HANDLER_ERROR;
p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
for (i = 0; i < srv->config_context->used; i++) {
data_config const* config = (data_config const*)srv->config_context->data[i];
plugin_config *s;
s = calloc(1, sizeof(plugin_config));
s->url_raw = array_init();
s->physical_path = array_init();
cv[0].destination = s->url_raw;
cv[1].destination = s->physical_path;
p->config_storage[i] = s;
if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
return HANDLER_ERROR;
}
}
return HANDLER_GO_ON;
}
#define PATCH(x) \
p->conf.x = s->x;
static int mod_magnet_patch_connection(server *srv, connection *con, plugin_data *p) {
size_t i, j;
plugin_config *s = p->config_storage[0];
PATCH(url_raw);
PATCH(physical_path);
/* skip the first, the global context */
for (i = 1; i < srv->config_context->used; i++) {
data_config *dc = (data_config *)srv->config_context->data[i];
s = p->config_storage[i];
/* condition didn't match */
if (!config_check_cond(srv, con, dc)) continue;
/* merge config */
for (j = 0; j < dc->value->used; j++) {
data_unset *du = dc->value->data[j];
if (buffer_is_equal_string(du->key, CONST_STR_LEN(MAGNET_CONFIG_RAW_URL))) {
PATCH(url_raw);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(MAGNET_CONFIG_PHYSICAL_PATH))) {
PATCH(physical_path);
}
}
}
return 0;
}
#undef PATCH
#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
/* lua5.1 backward compat definition */
static void lua_pushglobaltable(lua_State *L) { /* (-0, +1, -) */
lua_pushvalue(L, LUA_GLOBALSINDEX);
}
#endif
static void magnet_setfenv_mainfn(lua_State *L, int funcIndex) { /* (-1, 0, -) */
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 502
/* set "_ENV" upvalue, which should be the first upvalue of a "main" lua
* function if it uses any global names
*/
const char* first_upvalue_name = lua_getupvalue(L, funcIndex, 1);
if (NULL == first_upvalue_name) return; /* doesn't have any upvalues */
lua_pop(L, 1); /* only need the name of the upvalue, not the value */
if (0 != strcmp(first_upvalue_name, "_ENV")) return;
if (NULL == lua_setupvalue(L, funcIndex, 1)) {
/* pop value if lua_setupvalue didn't set the (not existing) upvalue */
lua_pop(L, 1);
}
#else
lua_setfenv(L, funcIndex);
#endif
}
#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
/* lua 5.2 already supports __pairs */
/* See http://lua-users.org/wiki/GeneralizedPairsAndIpairs for implementation details.
* Override the default pairs() function to allow us to use a __pairs metakey
*/
static int magnet_pairs(lua_State *L) {
luaL_checkany(L, 1); /* "self" */
if (luaL_getmetafield(L, 1, "__pairs")) {
/* call __pairs(self) */
lua_pushvalue(L, 1);
lua_call(L, 1, 3);
} else {
/* call <original-pairs-method>(self) */
lua_pushvalue(L, lua_upvalueindex(1));
lua_pushvalue(L, 1);
lua_call(L, 1, 3);
}
return 3;
}
#endif
/* Define a function that will iterate over an array* (in upval 1) using current position (upval 2) */
static int magnet_array_next(lua_State *L) {
data_unset *du;
data_string *ds;
data_integer *di;
size_t pos = lua_tointeger(L, lua_upvalueindex(1));
array *a = lua_touserdata(L, lua_upvalueindex(2));
lua_settop(L, 0);
if (pos >= a->used) return 0;
if (NULL != (du = a->data[pos])) {
lua_pushlstring(L, CONST_BUF_LEN(du->key));
switch (du->type) {
case TYPE_STRING:
ds = (data_string *)du;
if (!buffer_is_empty(ds->value)) {
lua_pushlstring(L, CONST_BUF_LEN(ds->value));
} else {
lua_pushnil(L);
}
break;
case TYPE_COUNT:
case TYPE_INTEGER:
di = (data_integer *)du;
lua_pushinteger(L, di->value);
break;
default:
lua_pushnil(L);
break;
}
/* Update our positional upval to reflect our new current position */
pos++;
lua_pushinteger(L, pos);
lua_replace(L, lua_upvalueindex(1));
/* Returning 2 items on the stack (key, value) */
return 2;
}
return 0;
}
/* Create the closure necessary to iterate over the array *a with the above function */
static int magnet_array_pairs(lua_State *L, array *a) {
lua_pushinteger(L, 0); /* Push our current pos (the start) into upval 1 */
lua_pushlightuserdata(L, a); /* Push our array *a into upval 2 */
lua_pushcclosure(L, magnet_array_next, 2); /* Push our new closure with 2 upvals */
return 1;
}
static server* magnet_get_server(lua_State *L) {
server *srv;
lua_getfield(L, LUA_REGISTRYINDEX, LUA_RIDX_LIGHTTPD_SERVER);
srv = lua_touserdata(L, -1);
lua_pop(L, 1);
return srv;
}
static connection* magnet_get_connection(lua_State *L) {
connection *con;
lua_getfield(L, LUA_REGISTRYINDEX, LUA_RIDX_LIGHTTPD_CONNECTION);
con = lua_touserdata(L, -1);
lua_pop(L, 1);
return con;
}
typedef struct {
const char *ptr;
size_t len;
} const_buffer;
static const_buffer magnet_checkconstbuffer(lua_State *L, int index) {
const_buffer cb;
cb.ptr = luaL_checklstring(L, index, &cb.len);
return cb;
}
static buffer* magnet_checkbuffer(lua_State *L, int index) {
const_buffer cb = magnet_checkconstbuffer(L, index);
buffer *b = buffer_init();
buffer_copy_string_len(b, cb.ptr, cb.len);
return b;
}
static int magnet_print(lua_State *L) {
buffer *b = magnet_checkbuffer(L, 1);
log_error_write(magnet_get_server(L), __FILE__, __LINE__, "sB",
"(lua-print)",
b);
buffer_free(b);
return 0;
}
static int magnet_stat(lua_State *L) {
buffer *sb = magnet_checkbuffer(L, 1);
server *srv = magnet_get_server(L);
connection *con = magnet_get_connection(L);
stat_cache_entry *sce = NULL;
handler_t res;
res = stat_cache_get_entry(srv, con, sb, &sce);
buffer_free(sb);
if (HANDLER_GO_ON != res) {
lua_pushnil(L);
return 1;
}
lua_newtable(L); // return value
lua_pushboolean(L, S_ISREG(sce->st.st_mode));
lua_setfield(L, -2, "is_file");
lua_pushboolean(L, S_ISDIR(sce->st.st_mode));
lua_setfield(L, -2, "is_dir");
lua_pushboolean(L, S_ISCHR(sce->st.st_mode));
lua_setfield(L, -2, "is_char");
lua_pushboolean(L, S_ISBLK(sce->st.st_mode));
lua_setfield(L, -2, "is_block");
lua_pushboolean(L, S_ISSOCK(sce->st.st_mode));
lua_setfield(L, -2, "is_socket");
lua_pushboolean(L, S_ISLNK(sce->st.st_mode));
lua_setfield(L, -2, "is_link");
lua_pushboolean(L, S_ISFIFO(sce->st.st_mode));
lua_setfield(L, -2, "is_fifo");
lua_pushinteger(L, sce->st.st_mtime);
lua_setfield(L, -2, "st_mtime");
lua_pushinteger(L, sce->st.st_ctime);
lua_setfield(L, -2, "st_ctime");
lua_pushinteger(L, sce->st.st_atime);
lua_setfield(L, -2, "st_atime");
lua_pushinteger(L, sce->st.st_uid);
lua_setfield(L, -2, "st_uid");
lua_pushinteger(L, sce->st.st_gid);
lua_setfield(L, -2, "st_gid");
lua_pushinteger(L, sce->st.st_size);
lua_setfield(L, -2, "st_size");
lua_pushinteger(L, sce->st.st_ino);
lua_setfield(L, -2, "st_ino");
fix buffer, chunk and http_chunk API * remove unused structs and functions (buffer_array, read_buffer) * change return type from int to void for many functions, as the return value (indicating error/success) was never checked, and the function would only fail on programming errors and not on invalid input; changed functions to use force_assert instead of returning an error. * all "len" parameters now are the real size of the memory to be read. the length of strings is given always without the terminating 0. * the "buffer" struct still counts the terminating 0 in ->used, provide buffer_string_length() to get the length of a string in a buffer. unset config "strings" have used == 0, which is used in some places to distinguish unset values from "" (empty string) values. * most buffer usages should now use it as string container. * optimise some buffer copying by "moving" data to other buffers * use (u)intmax_t for generic int-to-string functions * remove unused enum values: UNUSED_CHUNK, ENCODING_UNSET * converted BUFFER_APPEND_SLASH to inline function (no macro feature needed) * refactor: create chunkqueue_steal: moving (partial) chunks into another queue * http_chunk: added separate function to terminate chunked body instead of magic handling in http_chunk_append_mem(). http_chunk_append_* now handle empty chunks, and never terminate the chunked body. From: Stefan Bühler <stbuehler@web.de> git-svn-id: svn://svn.lighttpd.net/lighttpd/branches/lighttpd-1.4.x@2975 152afb58-edef-0310-8abb-c4023f1b3aa9
2015-02-08 12:37:10 +00:00
if (!buffer_string_is_empty(sce->etag)) {
/* we have to mutate the etag */
buffer *b = buffer_init();
etag_mutate(b, sce->etag);
lua_pushlstring(L, CONST_BUF_LEN(b));
buffer_free(b);
} else {
lua_pushnil(L);
}
lua_setfield(L, -2, "etag");
fix buffer, chunk and http_chunk API * remove unused structs and functions (buffer_array, read_buffer) * change return type from int to void for many functions, as the return value (indicating error/success) was never checked, and the function would only fail on programming errors and not on invalid input; changed functions to use force_assert instead of returning an error. * all "len" parameters now are the real size of the memory to be read. the length of strings is given always without the terminating 0. * the "buffer" struct still counts the terminating 0 in ->used, provide buffer_string_length() to get the length of a string in a buffer. unset config "strings" have used == 0, which is used in some places to distinguish unset values from "" (empty string) values. * most buffer usages should now use it as string container. * optimise some buffer copying by "moving" data to other buffers * use (u)intmax_t for generic int-to-string functions * remove unused enum values: UNUSED_CHUNK, ENCODING_UNSET * converted BUFFER_APPEND_SLASH to inline function (no macro feature needed) * refactor: create chunkqueue_steal: moving (partial) chunks into another queue * http_chunk: added separate function to terminate chunked body instead of magic handling in http_chunk_append_mem(). http_chunk_append_* now handle empty chunks, and never terminate the chunked body. From: Stefan Bühler <stbuehler@web.de> git-svn-id: svn://svn.lighttpd.net/lighttpd/branches/lighttpd-1.4.x@2975 152afb58-edef-0310-8abb-c4023f1b3aa9
2015-02-08 12:37:10 +00:00
if (!buffer_string_is_empty(sce->content_type)) {
lua_pushlstring(L, CONST_BUF_LEN(sce->content_type));
} else {
lua_pushnil(L);
}
lua_setfield(L, -2, "content-type");
return 1;
}
static int magnet_atpanic(lua_State *L) {
buffer *b = magnet_checkbuffer(L, 1);
log_error_write(magnet_get_server(L), __FILE__, __LINE__, "sB",
"(lua-atpanic)",
b);
buffer_free(b);
longjmp(exceptionjmp, 1);
}
static int magnet_reqhdr_get(lua_State *L) {
connection *con = magnet_get_connection(L);
data_string *ds;
/* __index: param 1 is the (empty) table the value was not found in */
const char *key = luaL_checkstring(L, 2);
if (NULL != (ds = (data_string *)array_get_element(con->request.headers, key))) {
if (!buffer_is_empty(ds->value)) {
lua_pushlstring(L, CONST_BUF_LEN(ds->value));
} else {
lua_pushnil(L);
}
} else {
lua_pushnil(L);
}
return 1;
}
static int magnet_reqhdr_pairs(lua_State *L) {
connection *con = magnet_get_connection(L);
return magnet_array_pairs(L, con->request.headers);
}
static int magnet_status_get(lua_State *L) {
data_integer *di;
server *srv = magnet_get_server(L);
/* __index: param 1 is the (empty) table the value was not found in */
const_buffer key = magnet_checkconstbuffer(L, 2);
di = status_counter_get_counter(srv, key.ptr, key.len);
lua_pushinteger(L, (lua_Integer)di->value);
return 1;
}
static int magnet_status_set(lua_State *L) {
server *srv = magnet_get_server(L);
/* __newindex: param 1 is the (empty) table the value is supposed to be set in */
const_buffer key = magnet_checkconstbuffer(L, 2);
int counter = (int) luaL_checkinteger(L, 3);
status_counter_set(srv, key.ptr, key.len, counter);
return 0;
}
static int magnet_status_pairs(lua_State *L) {
server *srv = magnet_get_server(L);
return magnet_array_pairs(L, srv->status);
}
typedef struct {
const char *name;
enum {
MAGNET_ENV_UNSET,
MAGNET_ENV_PHYICAL_PATH,
MAGNET_ENV_PHYICAL_REL_PATH,
MAGNET_ENV_PHYICAL_DOC_ROOT,
MAGNET_ENV_PHYICAL_BASEDIR,
MAGNET_ENV_URI_PATH,
MAGNET_ENV_URI_PATH_RAW,
MAGNET_ENV_URI_SCHEME,
MAGNET_ENV_URI_AUTHORITY,
MAGNET_ENV_URI_QUERY,
MAGNET_ENV_REQUEST_METHOD,
MAGNET_ENV_REQUEST_URI,
MAGNET_ENV_REQUEST_ORIG_URI,
MAGNET_ENV_REQUEST_PATH_INFO,
MAGNET_ENV_REQUEST_REMOTE_IP,
MAGNET_ENV_REQUEST_PROTOCOL
} type;
} magnet_env_t;
static const magnet_env_t magnet_env[] = {
{ "physical.path", MAGNET_ENV_PHYICAL_PATH },
{ "physical.rel-path", MAGNET_ENV_PHYICAL_REL_PATH },
{ "physical.doc-root", MAGNET_ENV_PHYICAL_DOC_ROOT },
{ "physical.basedir", MAGNET_ENV_PHYICAL_BASEDIR },
{ "uri.path", MAGNET_ENV_URI_PATH },
{ "uri.path-raw", MAGNET_ENV_URI_PATH_RAW },
{ "uri.scheme", MAGNET_ENV_URI_SCHEME },
{ "uri.authority", MAGNET_ENV_URI_AUTHORITY },
{ "uri.query", MAGNET_ENV_URI_QUERY },
{ "request.method", MAGNET_ENV_REQUEST_METHOD },
{ "request.uri", MAGNET_ENV_REQUEST_URI },
{ "request.orig-uri", MAGNET_ENV_REQUEST_ORIG_URI },
{ "request.path-info", MAGNET_ENV_REQUEST_PATH_INFO },
{ "request.remote-ip", MAGNET_ENV_REQUEST_REMOTE_IP },
{ "request.protocol", MAGNET_ENV_REQUEST_PROTOCOL },
{ NULL, MAGNET_ENV_UNSET }
};
static buffer *magnet_env_get_buffer_by_id(server *srv, connection *con, int id) {
buffer *dest = NULL;
UNUSED(srv);
/**
* map all internal variables to lua
*
*/
switch (id) {
case MAGNET_ENV_PHYICAL_PATH: dest = con->physical.path; break;
case MAGNET_ENV_PHYICAL_REL_PATH: dest = con->physical.rel_path; break;
case MAGNET_ENV_PHYICAL_DOC_ROOT: dest = con->physical.doc_root; break;
case MAGNET_ENV_PHYICAL_BASEDIR: dest = con->physical.basedir; break;
case MAGNET_ENV_URI_PATH: dest = con->uri.path; break;
case MAGNET_ENV_URI_PATH_RAW: dest = con->uri.path_raw; break;
case MAGNET_ENV_URI_SCHEME: dest = con->uri.scheme; break;
case MAGNET_ENV_URI_AUTHORITY: dest = con->uri.authority; break;
case MAGNET_ENV_URI_QUERY: dest = con->uri.query; break;
case MAGNET_ENV_REQUEST_METHOD:
buffer_copy_string(srv->tmp_buf, get_http_method_name(con->request.http_method));
dest = srv->tmp_buf;
break;
case MAGNET_ENV_REQUEST_URI: dest = con->request.uri; break;
case MAGNET_ENV_REQUEST_ORIG_URI: dest = con->request.orig_uri; break;
case MAGNET_ENV_REQUEST_PATH_INFO: dest = con->request.pathinfo; break;
case MAGNET_ENV_REQUEST_REMOTE_IP: dest = con->dst_addr_buf; break;
case MAGNET_ENV_REQUEST_PROTOCOL:
buffer_copy_string(srv->tmp_buf, get_http_version_name(con->request.http_version));
dest = srv->tmp_buf;
break;
case MAGNET_ENV_UNSET: break;
}
return dest;
}
static buffer *magnet_env_get_buffer(server *srv, connection *con, const char *key) {
size_t i;
for (i = 0; magnet_env[i].name; i++) {
if (0 == strcmp(key, magnet_env[i].name)) break;
}
return magnet_env_get_buffer_by_id(srv, con, magnet_env[i].type);
}
static int magnet_env_get(lua_State *L) {
server *srv = magnet_get_server(L);
connection *con = magnet_get_connection(L);
/* __index: param 1 is the (empty) table the value was not found in */
const char *key = luaL_checkstring(L, 2);
buffer *dest = NULL;
dest = magnet_env_get_buffer(srv, con, key);
if (!buffer_is_empty(dest)) {
lua_pushlstring(L, CONST_BUF_LEN(dest));
} else {
lua_pushnil(L);
}
return 1;
}
static int magnet_env_set(lua_State *L) {
server *srv = magnet_get_server(L);
connection *con = magnet_get_connection(L);
/* __newindex: param 1 is the (empty) table the value is supposed to be set in */
const char *key = luaL_checkstring(L, 2);
buffer *dest = NULL;
luaL_checkany(L, 3); /* nil or a string */
if (NULL != (dest = magnet_env_get_buffer(srv, con, key))) {
if (lua_isnil(L, 3)) {
buffer_reset(dest);
} else {
const_buffer val = magnet_checkconstbuffer(L, 3);
buffer_copy_string_len(dest, val.ptr, val.len);
}
} else {
/* couldn't save */
return luaL_error(L, "couldn't store '%s' in lighty.env[]", key);
}
return 0;
}
static int magnet_env_next(lua_State *L) {
server *srv = magnet_get_server(L);
connection *con = magnet_get_connection(L);
const int pos = lua_tointeger(L, lua_upvalueindex(1));
buffer *dest;
/* ignore previous key: use upvalue for current pos */
lua_settop(L, 0);
if (NULL == magnet_env[pos].name) return 0; /* end of list */
/* Update our positional upval to reflect our new current position */
lua_pushinteger(L, pos + 1);
lua_replace(L, lua_upvalueindex(1));
/* key to return */
lua_pushstring(L, magnet_env[pos].name);
/* get value */
dest = magnet_env_get_buffer_by_id(srv, con, magnet_env[pos].type);
if (!buffer_is_empty(dest)) {
lua_pushlstring(L, CONST_BUF_LEN(dest));
} else {
lua_pushnil(L);
}
/* return 2 items on the stack (key, value) */
return 2;
}
static int magnet_env_pairs(lua_State *L) {
lua_pushinteger(L, 0); /* Push our current pos (the start) into upval 1 */
lua_pushcclosure(L, magnet_env_next, 1); /* Push our new closure with 1 upvals */
return 1;
}
static int magnet_cgi_get(lua_State *L) {
connection *con = magnet_get_connection(L);
data_string *ds;
/* __index: param 1 is the (empty) table the value was not found in */
const char *key = luaL_checkstring(L, 2);
ds = (data_string *)array_get_element(con->environment, key);
if (NULL != ds && !buffer_is_empty(ds->value))
lua_pushlstring(L, CONST_BUF_LEN(ds->value));
else
lua_pushnil(L);
return 1;
}
static int magnet_cgi_set(lua_State *L) {
connection *con = magnet_get_connection(L);
/* __newindex: param 1 is the (empty) table the value is supposed to be set in */
const_buffer key = magnet_checkconstbuffer(L, 2);
const_buffer val = magnet_checkconstbuffer(L, 2);
array_set_key_value(con->environment, key.ptr, key.len, val.ptr, val.len);
return 0;
}
static int magnet_cgi_pairs(lua_State *L) {
connection *con = magnet_get_connection(L);
return magnet_array_pairs(L, con->environment);
}
static int magnet_copy_response_header(server *srv, connection *con, lua_State *L, int lighty_table_ndx) {
force_assert(lua_istable(L, lighty_table_ndx));