diff --git a/src/Makefile.am b/src/Makefile.am index ffec47e9..bf32b514 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -86,6 +86,12 @@ mod_webdav_la_CFLAGS = $(AM_CFLAGS) $(XML_CFLAGS) $(SQLITE_CFLAGS) mod_webdav_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined mod_webdav_la_LIBADD = $(common_libadd) $(XML_LIBS) $(SQLITE_LIBS) +lib_LTLIBRARIES += mod_magnet.la +mod_magnet_la_SOURCES = mod_magnet.c mod_magnet_cache.c +mod_magnet_la_CFLAGS = $(AM_CFLAGS) $(LUA_CFLAGS) +mod_magnet_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_magnet_la_LIBADD = $(common_libadd) $(LUA_LIBS) -lm + lib_LTLIBRARIES += mod_cml.la mod_cml_la_SOURCES = mod_cml.c mod_cml_lua.c mod_cml_funcs.c mod_cml_la_CFLAGS = $(AM_CFLAGS) $(LUA_CFLAGS) diff --git a/src/SConscript b/src/SConscript index ef412830..c2ce916d 100644 --- a/src/SConscript +++ b/src/SConscript @@ -72,6 +72,7 @@ modules = { 'mod_evasive' : { 'src' : [ 'mod_evasive.c' ] }, 'mod_ssi' : { 'src' : [ 'mod_ssi_exprparser.c', 'mod_ssi_expr.c', 'mod_ssi.c' ], 'lib' : [ env['LIBPCRE'] ] }, 'mod_flv_streaming' : { 'src' : [ 'mod_flv_streaming.c' ] }, + 'mod_magnet' : { 'src' : [ 'mod_magnet.c', 'mod_magnet_cache.c' ], 'lib' : [ 'lua' ] }, } staticenv = env.Copy(CPPFLAGS=[ env['CPPFLAGS'], '-DLIGHTTPD_STATIC', '-DOPENSSL_NO_KRB5']) diff --git a/src/mod_magnet.c b/src/mod_magnet.c new file mode 100644 index 00000000..ed7d4f45 --- /dev/null +++ b/src/mod_magnet.c @@ -0,0 +1,492 @@ +#include +#include +#include +#include + +#include +#include + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "mod_magnet_cache.h" +#include "response.h" +#include "stat_cache.h" + + +#define MAGNET_CONFIG_RAW_URL "magnet.attract-raw-url-to" +#define MAGNET_CONFIG_PHYSICAL_PATH "magnet.attract-physical-path-to" + +/* plugin config for all request/connections */ + +typedef struct { + buffer *url_raw; + buffer *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 (!s) continue; + + buffer_free(s->url_raw); + buffer_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_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { MAGNET_CONFIG_PHYSICAL_PATH, NULL, T_CONFIG_STRING, 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(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = calloc(1, sizeof(plugin_config)); + s->url_raw = buffer_init(); + s->physical_path = buffer_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, ((data_config *)srv->config_context->data[i])->value, cv)) { + 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 + +static int magnet_print(lua_State *L) { + const char *s = luaL_checkstring(L, 1); + server *srv; + + lua_pushstring(L, "lighty.srv"); + lua_gettable(L, LUA_REGISTRYINDEX); + srv = lua_touserdata(L, -1); + lua_pop(L, 1); + + log_error_write(srv, __FILE__, __LINE__, "ss", + "(lua-print)", s); + + return 0; +} + +/** + * copy all header-vars to the env + * + * + */ +static int magnet_add_request_header(server *srv, connection *con, plugin_data *p, lua_State *L) { + size_t i; + + for (i = 0; i < con->request.headers->used; i++) { + data_string *ds; + + ds = (data_string *)con->request.headers->data[i]; + + if (ds->value->used && ds->key->used) { + size_t j; + buffer_reset(p->encode_buf); + + if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) { + BUFFER_COPY_STRING_CONST(p->encode_buf, "HTTP_"); + p->encode_buf->used--; + } + + buffer_prepare_append(p->encode_buf, ds->key->used + 2); + for (j = 0; j < ds->key->used - 1; j++) { + char c = '_'; + if (light_isalpha(ds->key->ptr[j])) { + /* upper-case */ + c = ds->key->ptr[j] & ~32; + } else if (light_isdigit(ds->key->ptr[j])) { + /* copy */ + c = ds->key->ptr[j]; + } + p->encode_buf->ptr[p->encode_buf->used++] = c; + } + p->encode_buf->ptr[p->encode_buf->used++] = '\0'; + + lua_pushstring(L, ds->value->ptr); /* -1 */ + lua_setfield(L, -2, p->encode_buf->ptr); + } + } + + return 0; +} +static int magnet_copy_response_header(server *srv, connection *con, plugin_data *p, lua_State *L) { + /** + * get the environment of the function + */ + + lua_getfenv(L, -1); /* -1 is the function */ + + lua_getfield(L, -1, "header"); /* -1 is now the header table */ + if (lua_istable(L, -1)) { + /* header is found, and is a table */ + + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (lua_isstring(L, -1) && lua_isstring(L, -2)) { + const char *key, *val; + size_t key_len, val_len; + + key = lua_tolstring(L, -2, &key_len); + val = lua_tolstring(L, -1, &val_len); + + response_header_overwrite(srv, con, key, key_len, val, val_len); + } + + lua_pop(L, 1); + } + } + + lua_pop(L, 1); /* pop the header-table */ + lua_pop(L, 1); /* pop the function env */ + + return 0; +} + +/** + * walk through the content array + * + * content[0] = { type : "string", string : "foobar" } + * content[1] = { type : "file", filename : "...", [ offset : 0 [, length : ...] ] } + */ +static int magnet_attach_content(server *srv, connection *con, plugin_data *p, lua_State *L) { + /** + * get the environment of the function + */ + + assert(lua_isfunction(L, -1)); + lua_getfenv(L, -1); /* -1 is the function */ + + lua_getfield(L, -1, "content"); /* -1 is now the header table */ + if (lua_istable(L, -1)) { + int i; + /* header is found, and is a table */ + + for (i = 0; ; i++) { + lua_rawgeti(L, -1, i); + + /* we don't need the index */ + + /* -1 is the value and should be the value ... aka a table */ + if (lua_istable(L, -1)) { + int is_file = -1; + + lua_getfield(L, -1, "type"); + if (lua_isstring(L, -1)) { + if (0 == strcmp("file", lua_tostring(L, -1))) { + is_file = 1; + } else if (0 == strcmp("string", lua_tostring(L, -1))) { + is_file = 0; + } + } + lua_pop(L, 1); + + if (0 == is_file) { /* a string */ + lua_getfield(L, -1, "string"); + + if (lua_isstring(L, -1)) { + size_t s_len = 0; + const char *s = lua_tolstring(L, -1, &s_len); + + chunkqueue_append_mem(con->write_queue, s, s_len + 1); + } + + lua_pop(L, 1); + } else if (1 == is_file) { /* a file */ + lua_getfield(L, -1, "filename"); + lua_getfield(L, -2, "length"); + lua_getfield(L, -3, "offset"); + + if (lua_isstring(L, -3)) { /* filename has to be a string */ + buffer *fn = buffer_init(); + stat_cache_entry *sce; + off_t off = 0; + off_t len = 0; + + if (lua_isnumber(L, -1)) { + off = lua_tonumber(L, -1); + } + + if (lua_isnumber(L, -2)) { + len = lua_tonumber(L, -2); + } + + buffer_copy_string(fn, lua_tostring(L, -3)); + + if (HANDLER_GO_ON == stat_cache_get_entry(srv, con, fn, &sce)) { + chunkqueue_append_file(con->write_queue, fn, off, sce->st.st_size); + } + + buffer_free(fn); + } + + lua_pop(L, 3); + } /* ignore invalid types */ + } else if (lua_isnil(L, -1)) { + /* oops, end of list */ + + lua_pop(L, 1); + + break; + } + + lua_pop(L, 1); /* pop the content[...] table */ + } + } + lua_pop(L, 1); /* pop the header-table */ + lua_pop(L, 1); /* php the function env */ + + return 0; +} + +static handler_t magnet_attract(server *srv, connection *con, plugin_data *p, buffer *name) { + lua_State *L; + int lua_return_value = -1; + /* get the script-context */ + + + L = script_cache_get_script(srv, con, p->cache, name); + + if (lua_isstring(L, -1)) { + log_error_write(srv, __FILE__, __LINE__, + "sbss", + "loading script", + name, + "failed:", + lua_tostring(L, -1)); + + lua_pop(L, 1); + + assert(lua_gettop(L) == 0); /* only the function should be on the stack */ + + con->http_status = 500; + + return HANDLER_FINISHED; + } + + lua_pushstring(L, "lighty.srv"); + lua_pushlightuserdata(L, srv); + lua_settable(L, LUA_REGISTRYINDEX); /* registery[] = srv */ + + /** + * we want to create empty environment for our script + * + * setmetatable({}, {__index = _G}) + * + * if a function, symbol is not defined in our env, __index will lookup + * in the global env. + * + * all variables created in the script-env will be thrown + * away at the end of the script run. + */ + lua_newtable(L); /* my empty environment aka {} (sp += 1) */ + + /* we have to overwrite the print function */ + lua_pushcfunction(L, magnet_print); /* (sp += 1) */ + lua_setfield(L, -2, "print"); /* -1 is the env we want to set(sp -= 1) */ + + lua_newtable(L); /* {} (sp += 1) */ + magnet_add_request_header(srv, con, p, L); + lua_setfield(L, -2, "request"); /* content = {} (sp -= 1) */ + + /* add empty 'content' and 'header' tables */ + lua_newtable(L); /* {} (sp += 1) */ + lua_setfield(L, -2, "content"); /* content = {} (sp -= 1) */ + + lua_newtable(L); /* {} (sp += 1) */ + lua_setfield(L, -2, "header"); /* header = {} (sp -= 1) */ + + lua_newtable(L); /* the meta-table for the new env (sp += 1) */ + lua_pushvalue(L, LUA_GLOBALSINDEX); /* (sp += 1) */ + lua_setfield(L, -2, "__index"); /* { __index = _G } (sp += 1) */ + lua_setmetatable(L, -2); /* setmetatable({}, {__index = _G}) (sp -= 2) */ + + + lua_setfenv(L, -2); /* on the stack should be a modified env (sp -= 2) */ + + + if (lua_pcall(L, 0, 1, 0)) { + log_error_write(srv, __FILE__, __LINE__, + "sbss", + "lua_pcall() failed for", + name, + "with:", + lua_tostring(L, -1)); + lua_pop(L, 1); /* remove the error-msg and the function copy from the stack */ + + assert(lua_gettop(L) == 1); /* only the function should be on the stack */ + + con->http_status = 500; + + return HANDLER_FINISHED; + } + + /* we should have the function-copy and the return value on the stack */ + assert(lua_gettop(L) == 2); + + if (lua_isnumber(L, -1)) { + /* if the ret-value is a number, take it */ + lua_return_value = (int)lua_tonumber(L, -1); + } + lua_pop(L, 1); /* pop the ret-value */ + + magnet_copy_response_header(srv, con, p, L); + + if (lua_return_value > 99) { + con->http_status = lua_return_value; + con->file_finished = 1; + + magnet_attach_content(srv, con, p, L); + + assert(lua_gettop(L) == 1); /* only the function should be on the stack */ + + /* we are finished */ + return HANDLER_FINISHED; + } + + assert(lua_gettop(L) == 1); /* only the function should be on the stack */ + + return HANDLER_GO_ON; +} + +URIHANDLER_FUNC(mod_magnet_uri_handler) { + plugin_data *p = p_d; + + mod_magnet_patch_connection(srv, con, p); + + if (buffer_is_empty(p->conf.url_raw)) return HANDLER_GO_ON; + + /* looks like we have a handler for this request */ + + return magnet_attract(srv, con, p, p->conf.url_raw); +} + +URIHANDLER_FUNC(mod_magnet_physical) { + plugin_data *p = p_d; + + mod_magnet_patch_connection(srv, con, p); + + if (buffer_is_empty(p->conf.physical_path)) return HANDLER_GO_ON; + + /* looks like we have a handler for this request */ + + return magnet_attract(srv, con, p, p->conf.physical_path); +} + + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_magnet_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("magnet"); + + p->init = mod_magnet_init; + p->handle_uri_clean = mod_magnet_uri_handler; + p->handle_physical = mod_magnet_physical; + p->set_defaults = mod_magnet_set_defaults; + p->cleanup = mod_magnet_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_magnet_cache.c b/src/mod_magnet_cache.c new file mode 100644 index 00000000..8fe9a1b7 --- /dev/null +++ b/src/mod_magnet_cache.c @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include + +#include "mod_magnet_cache.h" +#include "stat_cache.h" + +script *script_init() { + script *sc; + + sc = calloc(1, sizeof(*sc)); + sc->name = buffer_init(); + sc->etag = buffer_init(); + + return sc; +} + +void script_free(script *sc) { + if (!sc) return; + + lua_pop(sc->L, 1); /* the function copy */ + + buffer_free(sc->name); + buffer_free(sc->etag); + + lua_close(sc->L); + + free(sc); +} + +script_cache *script_cache_init() { + script_cache *p; + + p = calloc(1, sizeof(*p)); + + return p; +} + +void script_cache_free(script_cache *p) { + size_t i; + + if (!p) return; + + for (i = 0; i < p->used; i++) { + script_free(p->ptr[i]); + } + + free(p->ptr); + + free(p); +} + +lua_State *script_cache_get_script(server *srv, connection *con, script_cache *cache, buffer *name) { + size_t i; + script *sc = NULL; + stat_cache_entry *sce; + + for (i = 0; i < cache->used; i++) { + sc = cache->ptr[i]; + + if (buffer_is_equal(name, sc->name)) { + sc->last_used = time(NULL); + + /* oops, the script failed last time */ + + if (lua_gettop(sc->L) == 0) break; + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, sc->name, &sce)) { + lua_pop(sc->L, 1); /* pop the old function */ + break; + } + + if (!buffer_is_equal(sce->etag, sc->etag)) { + /* the etag is outdated, reload the function */ + lua_pop(sc->L, 1); + break; + } + + assert(lua_isfunction(sc->L, -1)); + lua_pushvalue(sc->L, -1); /* copy the function-reference */ + + return sc->L; + } + + sc = NULL; + } + + /* if the script was script already loaded but either got changed or + * failed to load last time */ + if (sc == NULL) { + sc = script_init(); + + if (cache->size == 0) { + cache->size = 16; + cache->ptr = malloc(cache->size * sizeof(*(cache->ptr))); + } else if (cache->used == cache->size) { + cache->size += 16; + cache->ptr = realloc(cache->ptr, cache->size * sizeof(*(cache->ptr))); + } + + cache->ptr[cache->used++] = sc; + + buffer_copy_string_buffer(sc->name, name); + + sc->L = luaL_newstate(); + luaL_openlibs(sc->L); + } + + sc->last_used = time(NULL); + + if (0 != luaL_loadfile(sc->L, name->ptr)) { + /* oops, an error, return it */ + + return sc->L; + } + + if (HANDLER_GO_ON == stat_cache_get_entry(srv, con, sc->name, &sce)) { + buffer_copy_string_buffer(sc->etag, sce->etag); + } + + /** + * pcall() needs the function on the stack + * + * as pcall() will pop the script from the stack when done, we have to + * duplicate it here + */ + assert(lua_isfunction(sc->L, -1)); + lua_pushvalue(sc->L, -1); /* copy the function-reference */ + + return sc->L; +} diff --git a/src/mod_magnet_cache.h b/src/mod_magnet_cache.h new file mode 100644 index 00000000..20d22d40 --- /dev/null +++ b/src/mod_magnet_cache.h @@ -0,0 +1,31 @@ +#ifndef _MOD_MAGNET_CACHE_H_ +#define _MOD_MAGNET_CACHE_H_ + +#include + +#include "buffer.h" +#include "base.h" + +typedef struct { + buffer *name; + buffer *etag; + + lua_State *L; + + time_t last_used; /* LRU */ +} script; + +typedef struct { + script **ptr; + + size_t used; + size_t size; +} script_cache; + +script_cache *script_cache_init(void); +void script_cache_free(script_cache *cache); + +lua_State *script_cache_get_script(server *srv, connection *con, + script_cache *cache, buffer *name); + +#endif