lighttpd 1.4.x https://www.lighttpd.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2302 lines
80 KiB

#include "first.h"
#include "sys-crypto-md.h"
#include "algo_hmac.h"
#include "base.h"
#include "base64.h"
#include "burl.h"
#include "log.h"
#include "buffer.h"
#include "chunk.h"
#include "ck.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
6 years ago
#include "http_chunk.h"
#include "http_etag.h"
#include "http_header.h"
#include "rand.h"
#include "response.h" /* http_response_send_1xx() */
#include "plugin.h"
#include "mod_magnet_cache.h"
#include "sock_addr.h"
#include "stat_cache.h"
#include "status_counter.h"
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <lua.h>
#include <lauxlib.h>
#define LUA_RIDX_LIGHTTPD_REQUEST "lighty.request"
#define MAGNET_RESTART_REQUEST 99
/* plugin config for all request/connections */
static jmp_buf exceptionjmp;
typedef struct {
script * const *url_raw;
script * const *physical_path;
script * const *response_start;
[mod_magnet] magnet.attract-response-start-to (experimental) add option to run lua scripts in lighttpd response start hook allows for response header manipulation new params provide read-only access: lighty.env["response.http-status"] lighty.env["response.body-length"] lighty.env["response.body"] allows for content manipulation if the response body is complete The HTTP response status can be accessed in lua via lighty.env["response.http-status"] and should be checked, as appropriate, prior to body manipulation. The value is non-zero in response start hook (magnet.attract-response-start-to), but is likely to be 0 in scripts run from other lighttpd hooks earlier in request processing, e.g. magnet.attract-raw-url-to or magnet.attract-physical-path-to Caller should check lighty.env["response.body-length"] is a smaller and sane amount to read into memory and copy a second time into lua data structures. The value is lua nil if the response body is not yet complete (or if it is >= 2GB-1) Loading the response body (and all mod_magnet lua scripts) are executed serially (blocking) in lighttpd, so its use is highly discouraged on large files. The body can be accessed in lua via lighty.env["response.body"] if the response body is complete. (recommended config option: server.stream-response-body = 0 (default) if mod_magnet scripts must process the response body) Modifying HTTP response status and response body has not changed and is achieved by setting lua script return value and modifying the lighty.content lua table. (note: mod_magnet, mod_setenv, mod_deflate, mod_expire have their response start hooks run in the order listed in server.modules)
2 years ago
int stage;
} plugin_config;
typedef struct {
PLUGIN_DATA;
plugin_config defaults;
plugin_config conf;
script_cache cache;
} plugin_data;
INIT_FUNC(mod_magnet_init) {
return calloc(1, sizeof(plugin_data));
}
FREE_FUNC(mod_magnet_free) {
plugin_data * const p = p_d;
script_cache_free_data(&p->cache);
if (NULL == p->cvlist) return;
/* (init i to 0 if global context; to 1 to skip empty global context) */
for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
for (; -1 != cpv->k_id; ++cpv) {
if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
switch (cpv->k_id) {
case 0: /* magnet.attract-raw-url-to */
case 1: /* magnet.attract-physical-path-to */
case 2: /* magnet.attract-response-start-to */
free(cpv->v.v);
break;
default:
break;
}
}
}
}
static void mod_magnet_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
if (cpv->vtype != T_CONFIG_LOCAL)
return;
switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
case 0: /* magnet.attract-raw-url-to */
pconf->url_raw = cpv->v.v;
break;
case 1: /* magnet.attract-physical-path-to */
pconf->physical_path = cpv->v.v;
break;
[mod_magnet] magnet.attract-response-start-to (experimental) add option to run lua scripts in lighttpd response start hook allows for response header manipulation new params provide read-only access: lighty.env["response.http-status"] lighty.env["response.body-length"] lighty.env["response.body"] allows for content manipulation if the response body is complete The HTTP response status can be accessed in lua via lighty.env["response.http-status"] and should be checked, as appropriate, prior to body manipulation. The value is non-zero in response start hook (magnet.attract-response-start-to), but is likely to be 0 in scripts run from other lighttpd hooks earlier in request processing, e.g. magnet.attract-raw-url-to or magnet.attract-physical-path-to Caller should check lighty.env["response.body-length"] is a smaller and sane amount to read into memory and copy a second time into lua data structures. The value is lua nil if the response body is not yet complete (or if it is >= 2GB-1) Loading the response body (and all mod_magnet lua scripts) are executed serially (blocking) in lighttpd, so its use is highly discouraged on large files. The body can be accessed in lua via lighty.env["response.body"] if the response body is complete. (recommended config option: server.stream-response-body = 0 (default) if mod_magnet scripts must process the response body) Modifying HTTP response status and response body has not changed and is achieved by setting lua script return value and modifying the lighty.content lua table. (note: mod_magnet, mod_setenv, mod_deflate, mod_expire have their response start hooks run in the order listed in server.modules)
2 years ago
case 2: /* magnet.attract-response-start-to */
pconf->response_start = cpv->v.v;
[mod_magnet] magnet.attract-response-start-to (experimental) add option to run lua scripts in lighttpd response start hook allows for response header manipulation new params provide read-only access: lighty.env["response.http-status"] lighty.env["response.body-length"] lighty.env["response.body"] allows for content manipulation if the response body is complete The HTTP response status can be accessed in lua via lighty.env["response.http-status"] and should be checked, as appropriate, prior to body manipulation. The value is non-zero in response start hook (magnet.attract-response-start-to), but is likely to be 0 in scripts run from other lighttpd hooks earlier in request processing, e.g. magnet.attract-raw-url-to or magnet.attract-physical-path-to Caller should check lighty.env["response.body-length"] is a smaller and sane amount to read into memory and copy a second time into lua data structures. The value is lua nil if the response body is not yet complete (or if it is >= 2GB-1) Loading the response body (and all mod_magnet lua scripts) are executed serially (blocking) in lighttpd, so its use is highly discouraged on large files. The body can be accessed in lua via lighty.env["response.body"] if the response body is complete. (recommended config option: server.stream-response-body = 0 (default) if mod_magnet scripts must process the response body) Modifying HTTP response status and response body has not changed and is achieved by setting lua script return value and modifying the lighty.content lua table. (note: mod_magnet, mod_setenv, mod_deflate, mod_expire have their response start hooks run in the order listed in server.modules)
2 years ago
break;
default:/* should not happen */
return;
}
}
static void mod_magnet_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
do {
mod_magnet_merge_config_cpv(pconf, cpv);
} while ((++cpv)->k_id != -1);
}
static void mod_magnet_patch_config(request_st * const r, plugin_data * const p) {
p->conf = p->defaults; /* copy small struct instead of memcpy() */
/*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
for (int i = 1, used = p->nconfig; i < used; ++i) {
if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
mod_magnet_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
}
}
SETDEFAULTS_FUNC(mod_magnet_set_defaults) {
static const config_plugin_keys_t cpk[] = {
{ CONST_STR_LEN("magnet.attract-raw-url-to"),
T_CONFIG_ARRAY_VLIST,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("magnet.attract-physical-path-to"),
T_CONFIG_ARRAY_VLIST,
T_CONFIG_SCOPE_CONNECTION }
[mod_magnet] magnet.attract-response-start-to (experimental) add option to run lua scripts in lighttpd response start hook allows for response header manipulation new params provide read-only access: lighty.env["response.http-status"] lighty.env["response.body-length"] lighty.env["response.body"] allows for content manipulation if the response body is complete The HTTP response status can be accessed in lua via lighty.env["response.http-status"] and should be checked, as appropriate, prior to body manipulation. The value is non-zero in response start hook (magnet.attract-response-start-to), but is likely to be 0 in scripts run from other lighttpd hooks earlier in request processing, e.g. magnet.attract-raw-url-to or magnet.attract-physical-path-to Caller should check lighty.env["response.body-length"] is a smaller and sane amount to read into memory and copy a second time into lua data structures. The value is lua nil if the response body is not yet complete (or if it is >= 2GB-1) Loading the response body (and all mod_magnet lua scripts) are executed serially (blocking) in lighttpd, so its use is highly discouraged on large files. The body can be accessed in lua via lighty.env["response.body"] if the response body is complete. (recommended config option: server.stream-response-body = 0 (default) if mod_magnet scripts must process the response body) Modifying HTTP response status and response body has not changed and is achieved by setting lua script return value and modifying the lighty.content lua table. (note: mod_magnet, mod_setenv, mod_deflate, mod_expire have their response start hooks run in the order listed in server.modules)
2 years ago
,{ CONST_STR_LEN("magnet.attract-response-start-to"),
T_CONFIG_ARRAY_VLIST,
T_CONFIG_SCOPE_CONNECTION }
,{ NULL, 0,
T_CONFIG_UNSET,
T_CONFIG_SCOPE_UNSET }
};
plugin_data * const p = p_d;
if (!config_plugin_values_init(srv, p, cpk, "mod_magnet"))
return HANDLER_ERROR;
/* process and validate config directives
* (init i to 0 if global context; to 1 to skip empty global context) */
for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
for (; -1 != cpv->k_id; ++cpv) {
switch (cpv->k_id) {
case 0: /* magnet.attract-raw-url-to */
case 1: /* magnet.attract-physical-path-to */
[mod_magnet] magnet.attract-response-start-to (experimental) add option to run lua scripts in lighttpd response start hook allows for response header manipulation new params provide read-only access: lighty.env["response.http-status"] lighty.env["response.body-length"] lighty.env["response.body"] allows for content manipulation if the response body is complete The HTTP response status can be accessed in lua via lighty.env["response.http-status"] and should be checked, as appropriate, prior to body manipulation. The value is non-zero in response start hook (magnet.attract-response-start-to), but is likely to be 0 in scripts run from other lighttpd hooks earlier in request processing, e.g. magnet.attract-raw-url-to or magnet.attract-physical-path-to Caller should check lighty.env["response.body-length"] is a smaller and sane amount to read into memory and copy a second time into lua data structures. The value is lua nil if the response body is not yet complete (or if it is >= 2GB-1) Loading the response body (and all mod_magnet lua scripts) are executed serially (blocking) in lighttpd, so its use is highly discouraged on large files. The body can be accessed in lua via lighty.env["response.body"] if the response body is complete. (recommended config option: server.stream-response-body = 0 (default) if mod_magnet scripts must process the response body) Modifying HTTP response status and response body has not changed and is achieved by setting lua script return value and modifying the lighty.content lua table. (note: mod_magnet, mod_setenv, mod_deflate, mod_expire have their response start hooks run in the order listed in server.modules)
2 years ago
case 2: /* magnet.attract-response-start-to */
if (0 == cpv->v.a->used) {
cpv->v.v = NULL;
cpv->vtype = T_CONFIG_LOCAL;
}
else {
script ** const a =
malloc(sizeof(script *)*(cpv->v.a->used+1));
force_assert(a);
for (uint32_t j = 0; j < cpv->v.a->used; ++j) {
data_string *ds = (data_string *)cpv->v.a->data[j];
if (buffer_is_blank(&ds->value)) {
log_error(srv->errh, __FILE__, __LINE__,
"unexpected (blank) value for %s; "
"expected list of \"scriptpath\"", cpk[cpv->k_id].k);
free(a);
return HANDLER_ERROR;
}
a[j] = script_cache_get_script(&p->cache, &ds->value);
}
a[cpv->v.a->used] = NULL;
cpv->v.v = a;
cpv->vtype = T_CONFIG_LOCAL;
}
break;
default:/* should not happen */
break;
}
}
}
/* initialize p->defaults from global config context */
if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
if (-1 != cpv->k_id)
mod_magnet_merge_config(&p->defaults, cpv);
}
return HANDLER_GO_ON;
}
#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.1 deprecated luaL_getn() for lua_objlen() */
/* lua 5.2 renamed lua_objlen() to lua_rawlen() */
#define lua_rawlen lua_objlen
/* lua 5.2 deprecated luaL_register() for luaL_setfuncs()
* (this define is valid only when 0 == nup) */
#define luaL_setfuncs(L, l, nup) luaL_register((L), NULL, (l))
#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
static int magnet_newindex_readonly(lua_State *L) {
lua_pushliteral(L, "lua table is read-only");
return lua_error(L);
}
static void magnet_push_buffer(lua_State *L, const buffer *b) {
[multiple] reduce redundant NULL buffer checks This commit is a large set of code changes and results in removal of hundreds, perhaps thousands, of CPU instructions, a portion of which are on hot code paths. Most (buffer *) used by lighttpd are not NULL, especially since buffers were inlined into numerous larger structs such as request_st and chunk. In the small number of instances where that is not the case, a NULL check is often performed earlier in a function where that buffer is later used with a buffer_* func. In the handful of cases that remained, a NULL check was added, e.g. with r->http_host and r->conf.server_tag. - check for empty strings at config time and set value to NULL if blank string will be ignored at runtime; at runtime, simple pointer check for NULL can be used to check for a value that has been set and is not blank ("") - use buffer_is_blank() instead of buffer_string_is_empty(), and use buffer_is_unset() instead of buffer_is_empty(), where buffer is known not to be NULL so that NULL check can be skipped - use buffer_clen() instead of buffer_string_length() when buffer is known not to be NULL (to avoid NULL check at runtime) - use buffer_truncate() instead of buffer_string_set_length() to truncate string, and use buffer_extend() to extend Examples where buffer known not to be NULL: - cpv->v.b from config_plugin_values_init is not NULL if T_CONFIG_BOOL (though we might set it to NULL if buffer_is_blank(cpv->v.b)) - address of buffer is arg (&foo) (compiler optimizer detects this in most, but not all, cases) - buffer is checked for NULL earlier in func - buffer is accessed in same scope without a NULL check (e.g. b->ptr) internal behavior change: callers must not pass a NULL buffer to some funcs. - buffer_init_buffer() requires non-null args - buffer_copy_buffer() requires non-null args - buffer_append_string_buffer() requires non-null args - buffer_string_space() requires non-null arg
1 year ago
if (b && !buffer_is_unset(b))
lua_pushlstring(L, BUF_PTR_LEN(b));
else
lua_pushnil(L);
}
#if 0
static int magnet_array_get_element(lua_State *L, const array *a) {
/* __index: param 1 is the (empty) table the value was not found in */
size_t klen;
const char * const k = luaL_checklstring(L, 2, &klen);
const data_string * const ds = (const data_string *)
array_get_element_klen(a, k, klen);
magnet_push_buffer(L, NULL != ds ? &ds->value : NULL);
return 1;
}
#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])) {
[multiple] reduce redundant NULL buffer checks This commit is a large set of code changes and results in removal of hundreds, perhaps thousands, of CPU instructions, a portion of which are on hot code paths. Most (buffer *) used by lighttpd are not NULL, especially since buffers were inlined into numerous larger structs such as request_st and chunk. In the small number of instances where that is not the case, a NULL check is often performed earlier in a function where that buffer is later used with a buffer_* func. In the handful of cases that remained, a NULL check was added, e.g. with r->http_host and r->conf.server_tag. - check for empty strings at config time and set value to NULL if blank string will be ignored at runtime; at runtime, simple pointer check for NULL can be used to check for a value that has been set and is not blank ("") - use buffer_is_blank() instead of buffer_string_is_empty(), and use buffer_is_unset() instead of buffer_is_empty(), where buffer is known not to be NULL so that NULL check can be skipped - use buffer_clen() instead of buffer_string_length() when buffer is known not to be NULL (to avoid NULL check at runtime) - use buffer_truncate() instead of buffer_string_set_length() to truncate string, and use buffer_extend() to extend Examples where buffer known not to be NULL: - cpv->v.b from config_plugin_values_init is not NULL if T_CONFIG_BOOL (though we might set it to NULL if buffer_is_blank(cpv->v.b)) - address of buffer is arg (&foo) (compiler optimizer detects this in most, but not all, cases) - buffer is checked for NULL earlier in func - buffer is accessed in same scope without a NULL check (e.g. b->ptr) internal behavior change: callers must not pass a NULL buffer to some funcs. - buffer_init_buffer() requires non-null args - buffer_copy_buffer() requires non-null args - buffer_append_string_buffer() requires non-null args - buffer_string_space() requires non-null arg
1 year ago
lua_pushlstring(L, BUF_PTR_LEN(&du->key));
switch (du->type) {
case TYPE_STRING:
ds = (data_string *)du;
magnet_push_buffer(L, &ds->value);
break;
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 request_st * magnet_get_request(lua_State *L) {
lua_getfield(L, LUA_REGISTRYINDEX, LUA_RIDX_LIGHTTPD_REQUEST);
request_st * const r = lua_touserdata(L, -1);
lua_pop(L, 1);
return r;
}
typedef struct {
const char *ptr;
size_t len;
} const_buffer;
static const_buffer magnet_checkconstbuffer(lua_State *L, int idx) {
const_buffer cb;
cb.ptr = luaL_checklstring(L, idx, &cb.len);
return cb;
}
[multiple] reduce redundant NULL buffer checks This commit is a large set of code changes and results in removal of hundreds, perhaps thousands, of CPU instructions, a portion of which are on hot code paths. Most (buffer *) used by lighttpd are not NULL, especially since buffers were inlined into numerous larger structs such as request_st and chunk. In the small number of instances where that is not the case, a NULL check is often performed earlier in a function where that buffer is later used with a buffer_* func. In the handful of cases that remained, a NULL check was added, e.g. with r->http_host and r->conf.server_tag. - check for empty strings at config time and set value to NULL if blank string will be ignored at runtime; at runtime, simple pointer check for NULL can be used to check for a value that has been set and is not blank ("") - use buffer_is_blank() instead of buffer_string_is_empty(), and use buffer_is_unset() instead of buffer_is_empty(), where buffer is known not to be NULL so that NULL check can be skipped - use buffer_clen() instead of buffer_string_length() when buffer is known not to be NULL (to avoid NULL check at runtime) - use buffer_truncate() instead of buffer_string_set_length() to truncate string, and use buffer_extend() to extend Examples where buffer known not to be NULL: - cpv->v.b from config_plugin_values_init is not NULL if T_CONFIG_BOOL (though we might set it to NULL if buffer_is_blank(cpv->v.b)) - address of buffer is arg (&foo) (compiler optimizer detects this in most, but not all, cases) - buffer is checked for NULL earlier in func - buffer is accessed in same scope without a NULL check (e.g. b->ptr) internal behavior change: callers must not pass a NULL buffer to some funcs. - buffer_init_buffer() requires non-null args - buffer_copy_buffer() requires non-null args - buffer_append_string_buffer() requires non-null args - buffer_string_space() requires non-null arg
1 year ago
static const buffer* magnet_checkbuffer(lua_State *L, int idx, buffer *b) {
const_buffer cb = magnet_checkconstbuffer(L, idx);
[multiple] reduce redundant NULL buffer checks This commit is a large set of code changes and results in removal of hundreds, perhaps thousands, of CPU instructions, a portion of which are on hot code paths. Most (buffer *) used by lighttpd are not NULL, especially since buffers were inlined into numerous larger structs such as request_st and chunk. In the small number of instances where that is not the case, a NULL check is often performed earlier in a function where that buffer is later used with a buffer_* func. In the handful of cases that remained, a NULL check was added, e.g. with r->http_host and r->conf.server_tag. - check for empty strings at config time and set value to NULL if blank string will be ignored at runtime; at runtime, simple pointer check for NULL can be used to check for a value that has been set and is not blank ("") - use buffer_is_blank() instead of buffer_string_is_empty(), and use buffer_is_unset() instead of buffer_is_empty(), where buffer is known not to be NULL so that NULL check can be skipped - use buffer_clen() instead of buffer_string_length() when buffer is known not to be NULL (to avoid NULL check at runtime) - use buffer_truncate() instead of buffer_string_set_length() to truncate string, and use buffer_extend() to extend Examples where buffer known not to be NULL: - cpv->v.b from config_plugin_values_init is not NULL if T_CONFIG_BOOL (though we might set it to NULL if buffer_is_blank(cpv->v.b)) - address of buffer is arg (&foo) (compiler optimizer detects this in most, but not all, cases) - buffer is checked for NULL earlier in func - buffer is accessed in same scope without a NULL check (e.g. b->ptr) internal behavior change: callers must not pass a NULL buffer to some funcs. - buffer_init_buffer() requires non-null args - buffer_copy_buffer() requires non-null args - buffer_append_string_buffer() requires non-null args - buffer_string_space() requires non-null arg
1 year ago
/* assign result into (buffer *), and return (const buffer *)
* (note: caller must not free result) */
*(const char **)&b->ptr = cb.ptr;
b->used = cb.len+1;
b->size = 0;
return b;
}
static int magnet_print(lua_State *L) {
const_buffer cb = magnet_checkconstbuffer(L, 1);
request_st * const r = magnet_get_request(L);
log_error(r->conf.errh, __FILE__, __LINE__, "(lua-print) %s", cb.ptr);
return 0;
}
static int magnet_stat_field(lua_State *L) {
if (lua_gettop(L) != 2)
return 0; /*(should not happen; __index method in protected metatable)*/
stat_cache_entry * const sce = *(stat_cache_entry **)lua_touserdata(L, -2);
const_buffer k = magnet_checkconstbuffer(L, -1);
switch (k.ptr[0]) {
case 'c': { /* content-type */
if (0 != strcmp(k.ptr, "content-type")) break;
request_st * const r = magnet_get_request(L);
const buffer *content_type = stat_cache_content_type_get(sce, r);
if (content_type && !buffer_is_blank(content_type))
lua_pushlstring(L, BUF_PTR_LEN(content_type));
else
lua_pushnil(L);
return 1;
}
case 'e': { /* etag */
if (0 != strcmp(k.ptr, "etag")) break;
request_st * const r = magnet_get_request(L);
const buffer *etag = stat_cache_etag_get(sce, r->conf.etag_flags);
if (etag && !buffer_is_blank(etag))
lua_pushlstring(L, BUF_PTR_LEN(etag));
else
lua_pushnil(L);
return 1;
}
case 'i': /* is_* */
if (k.len < 4) break;
switch (k.ptr[3]) {
case 'b': /* is_block */
if (0 == strcmp(k.ptr, "is_block")) {
lua_pushboolean(L, S_ISBLK(sce->st.st_mode));
return 1;
}
break;
case 'c': /* is_char */
if (0 == strcmp(k.ptr, "is_char")) {
lua_pushboolean(L, S_ISCHR(sce->st.st_mode));
return 1;
}
break;
case 'd': /* is_dir */
if (0 == strcmp(k.ptr, "is_dir")) {
lua_pushboolean(L, S_ISDIR(sce->st.st_mode));
return 1;
}
break;
case 'f': /* is_file is_fifo */
if (0 == strcmp(k.ptr, "is_file")) {
lua_pushboolean(L, S_ISREG(sce->st.st_mode));
return 1;
}
if (0 == strcmp(k.ptr, "is_fifo")) {
lua_pushboolean(L, S_ISFIFO(sce->st.st_mode));
return 1;
}
break;
case 'l': /* is_link */
if (0 == strcmp(k.ptr, "is_link")) {
lua_pushboolean(L, S_ISLNK(sce->st.st_mode));
return 1;
}
break;
case 's': /* is_socket */
if (0 == strcmp(k.ptr, "is_socket")) {
lua_pushboolean(L, S_ISSOCK(sce->st.st_mode));
return 1;
}
break;
default:
break;
}
break;
case 's': /* st_* */
if (k.len < 4) break;
switch (k.ptr[3]) {
case 'a': /* st_atime */
if (0 == strcmp(k.ptr, "st_atime")) {
lua_pushinteger(L, TIME64_CAST(sce->st.st_atime));
return 1;
}
break;
case 'c': /* st_ctime */
if (0 == strcmp(k.ptr, "st_ctime")) {
lua_pushinteger(L, TIME64_CAST(sce->st.st_ctime));
return 1;
}
break;
case 'i': /* st_ino */
if (0 == strcmp(k.ptr, "st_ino")) {
lua_pushinteger(L, sce->st.st_ino);
return 1;
}
break;
case 'm': /* st_mtime st_mode */
if (0 == strcmp(k.ptr, "st_mtime")) {
lua_pushinteger(L, TIME64_CAST(sce->st.st_mtime));
return 1;
}
if (0 == strcmp(k.ptr, "st_mode")) {
lua_pushinteger(L, sce->st.st_mode);
return 1;
}
break;
case 'g': /* st_gid */
if (0 == strcmp(k.ptr, "st_gid")) {
lua_pushinteger(L, sce->st.st_gid);
return 1;
}
break;
case 's': /* st_size */
if (0 == strcmp(k.ptr, "st_size")) {
lua_pushinteger(L, sce->st.st_size);
return 1;
}
break;
case 'u': /* st_uid */
if (0 == strcmp(k.ptr, "st_uid")) {
lua_pushinteger(L, sce->st.st_uid);
return 1;
}
break;
default:
break;
}
break;
default:
break;
}
lua_pushliteral(L, "stat[\"field\"] invalid: ");
lua_pushvalue(L, -2); /* field */
lua_concat(L, 2);
lua_error(L);
return 0;
}
__attribute_cold__
static int magnet_stat_pairs_noimpl_iter(lua_State *L) {
request_st * const r = magnet_get_request(L);
log_error(r->conf.errh, __FILE__, __LINE__,
"(lua) pairs() not implemented on lighty.stat object; "
"returning empty iter");
return 0;
}
__attribute_cold__
static int magnet_stat_pairs_noimpl(lua_State *L) {
lua_pushcclosure(L, magnet_stat_pairs_noimpl_iter, 0);
return 1;
}
static void magnet_stat_metatable(lua_State *L) {
if (luaL_newmetatable(L, "lighty.stat")) { /* (sp += 1) */
lua_pushcfunction(L, magnet_stat_field); /* (sp += 1) */
lua_setfield(L, -2, "__index"); /* (sp -= 1) */
lua_pushcfunction(L, magnet_newindex_readonly); /* (sp += 1) */
lua_setfield(L, -2, "__newindex"); /* (sp -= 1) */
lua_pushcfunction(L, magnet_stat_pairs_noimpl); /* (sp += 1) */
lua_setfield(L, -2, "__pairs"); /* (sp -= 1) */
lua_pushboolean(L, 0); /* (sp += 1) */
lua_setfield(L, -2, "__metatable"); /* protect metatable (sp -= 1) */
}
}
static int magnet_stat(lua_State *L) {
buffer stor; /*(note: do not free magnet_checkbuffer() result)*/
const buffer * const sb = magnet_checkbuffer(L, 1, &stor);
stat_cache_entry * const sce = (!buffer_is_blank(sb))
? stat_cache_get_entry(sb)
: NULL;
if (NULL == sce) {
lua_pushnil(L);
return 1;
}
/* note: caching sce valid only for procedural script which does not yield;
* (sce might not be valid if script yields and is later resumed)
* (script must not cache sce in persistent global state for later use)
* (If we did want sce to be persistent, then could increment sce refcnt,
* and set up __gc metatable method to decrement sce refcnt) */
stat_cache_entry ** const udata = /* (sp += 1) */
(struct stat_cache_entry **)lua_newuserdata(L, sizeof(stat_cache_entry *));
*udata = sce;
magnet_stat_metatable(L); /* (sp += 1) */
lua_setmetatable(L, -2); /* (sp -= 1) */
return 1;
}
static int magnet_time(lua_State *L) {
lua_pushinteger(L, (lua_Integer)log_epoch_secs);
return 1;
}
static int magnet_rand(lua_State *L) {
lua_pushinteger(L, (lua_Integer)li_rand_pseudo());
return 1;
}
static int magnet_md_once(lua_State *L) {
if (lua_gettop(L) != 2) {
lua_pushliteral(L,
"lighty.c.md(algo, data): incorrect number of arguments");
return lua_error(L);
}
const_buffer algo = magnet_checkconstbuffer(L, -2);
const_buffer msg = magnet_checkconstbuffer(L, -1);
uint8_t digest[MD_DIGEST_LENGTH_MAX];
uint32_t dlen = 0;
switch (algo.len) {
#ifdef USE_LIB_CRYPTO
case 6:
#ifdef USE_LIB_CRYPTO_SHA512
if (0 == memcmp(algo.ptr, "sha512", 6)) {
SHA512_once(digest, msg.ptr, msg.len);
dlen = SHA512_DIGEST_LENGTH;
break;
}
#endif
#ifdef USE_LIB_CRYPTO_SHA256
if (0 == memcmp(algo.ptr, "sha256", 6)) {
SHA256_once(digest, msg.ptr, msg.len);
dlen = SHA256_DIGEST_LENGTH;
break;
}
#endif
break;
case 4:
#ifdef USE_LIB_CRYPTO_SHA1
if (0 == memcmp(algo.ptr, "sha1", 4)) {
SHA1_once(digest, msg.ptr, msg.len);
dlen = SHA1_DIGEST_LENGTH;
break;
}
#endif
break;
#endif
case 3:
if (0 == memcmp(algo.ptr, "md5", 3)) {
MD5_once(digest, msg.ptr, msg.len);
dlen = MD5_DIGEST_LENGTH;
break;
}
break;
default:
break;
}
if (dlen) {
char dighex[MD_DIGEST_LENGTH_MAX*2+1];
li_tohex_uc(dighex, sizeof(dighex), (char *)digest, dlen);
lua_pushlstring(L, dighex, dlen*2);
}
else
lua_pushnil(L);
return 1;
}
static int magnet_hmac_once(lua_State *L) {
if (lua_gettop(L) != 3) {
lua_pushliteral(L,
"lighty.c.hmac(algo, secret, data): incorrect number of arguments");
return lua_error(L);
}
const_buffer algo = magnet_checkconstbuffer(L, -3);
const_buffer secret = magnet_checkconstbuffer(L, -2);
const_buffer msg = magnet_checkconstbuffer(L, -1);
const uint8_t * const msgptr = (uint8_t *)msg.ptr;
uint8_t digest[MD_DIGEST_LENGTH_MAX];
uint32_t dlen = 0;
int rc = 0;
switch (algo.len) {
#ifdef USE_LIB_CRYPTO
case 6:
#ifdef USE_LIB_CRYPTO_SHA512
if (0 == memcmp(algo.ptr, "sha512", 6)) {
rc = li_hmac_sha512(digest,secret.ptr,secret.len,msgptr,msg.len);
dlen = SHA512_DIGEST_LENGTH;
break;
}
#endif
#ifdef USE_LIB_CRYPTO_SHA256
if (0 == memcmp(algo.ptr, "sha256", 6)) {
rc = li_hmac_sha256(digest,secret.ptr,secret.len,msgptr,msg.len);
dlen = SHA256_DIGEST_LENGTH;
break;
}
#endif
break;
case 4:
#ifdef USE_LIB_CRYPTO_SHA1
if (0 == memcmp(algo.ptr, "sha1", 4)) {
rc = li_hmac_sha1(digest,secret.ptr,secret.len,msgptr,msg.len);
dlen = SHA1_DIGEST_LENGTH;
break;
}
#endif
break;
#endif
case 3:
if (0 == memcmp(algo.ptr, "md5", 3)) {
rc = li_hmac_md5(digest,secret.ptr,secret.len,msgptr,msg.len);
dlen = MD5_DIGEST_LENGTH;
break;
}
break;
default:
break;
}
if (rc) {
char dighex[MD_DIGEST_LENGTH_MAX*2+1];
li_tohex_uc(dighex, sizeof(dighex), (char *)digest, dlen);
lua_pushlstring(L, dighex, dlen*2);
}
else
lua_pushnil(L);
return 1;
}
static int magnet_digest_eq(lua_State *L) {
if (lua_gettop(L) != 2) {
lua_pushliteral(L,
"lighty.c.digest_eq(d1, d2): incorrect number of arguments");
return lua_error(L);
}
const_buffer d1 = magnet_checkconstbuffer(L, -2);
const_buffer d2 = magnet_checkconstbuffer(L, -1);
/* convert hex to binary: validate hex and eliminate hex case comparison */
uint8_t b1[MD_DIGEST_LENGTH_MAX];
uint8_t b2[MD_DIGEST_LENGTH_MAX];
int rc = (d1.len == d2.len)
&& 0 == li_hex2bin(b1, sizeof(b1), d1.ptr, d1.len)
&& 0 == li_hex2bin(b2, sizeof(b2), d2.ptr, d2.len)
&& ck_memeq_const_time_fixed_len(b1, b2, d2.len >> 1);
lua_pushboolean(L, rc);
return 1;
}
static int magnet_secret_eq(lua_State *L) {
if (lua_gettop(L) != 2) {
lua_pushliteral(L,
"lighty.c.secret_eq(d1, d2): incorrect number of arguments");
return lua_error(L);
}
const_buffer d1 = magnet_checkconstbuffer(L, -2);
const_buffer d2 = magnet_checkconstbuffer(L, -1);
lua_pushboolean(L, ck_memeq_const_time(d1.ptr, d1.len, d2.ptr, d2.len));
return 1;
}
static int magnet_b64dec(lua_State *L, base64_charset dict) {
if (lua_isnil(L, -1)) {
lua_pushlstring(L, "", 0);
return 1;
}
const_buffer s = magnet_checkconstbuffer(L, -1);
if (0 == s.len) {
lua_pushvalue(L, -1);
return 1;
}
buffer * const b = chunk_buffer_acquire();
if (buffer_append_base64_decode(b, s.ptr, s.len, dict))
lua_pushlstring(L, BUF_PTR_LEN(b));
else
lua_pushnil(L);
chunk_buffer_release(b);
return 1;
}
static int magnet_b64enc(lua_State *L, base64_charset dict) {
if (lua_isnil(L, -1)) {
lua_pushlstring(L, "", 0);
return 1;
}
const_buffer s = magnet_checkconstbuffer(L, -1);
if (0 == s.len) {
lua_pushvalue(L, -1);
return 1;
}
buffer * const b = chunk_buffer_acquire();
buffer_append_base64_encode_no_padding(b, (uint8_t *)s.ptr, s.len, dict);
lua_pushlstring(L, BUF_PTR_LEN(b));
chunk_buffer_release(b);
return 1;
}
static int magnet_b64urldec(lua_State *L) {
return magnet_b64dec(L, BASE64_URL);
}
static int magnet_b64urlenc(lua_State *L) {
return magnet_b64enc(L, BASE64_URL);
}
static int magnet_b64stddec(lua_State *L) {
return magnet_b64dec(L, BASE64_STANDARD);
}
static int magnet_b64stdenc(lua_State *L) {
return magnet_b64enc(L, BASE64_STANDARD);
}
static int magnet_hexdec(lua_State *L) {
if (lua_isnil(L, -1)) {
lua_pushlstring(L, "", 0);
return 1;
}
const_buffer s = magnet_checkconstbuffer(L, -1);
if (0 == s.len) {
lua_pushvalue(L, -1);
return 1;
}
buffer * const b = chunk_buffer_acquire();
uint8_t * const p = (uint8_t *)buffer_extend(b, s.len >> 1);
int rc = li_hex2bin(p, s.len >> 1, s.ptr, s.len);
if (0 == rc)
lua_pushlstring(L, BUF_PTR_LEN(b));
chunk_buffer_release(b);
return rc+1; /* 1 on success (pushed string); 0 on failure (no value) */
}