diff --git a/include/lighttpd/core_lua.h b/include/lighttpd/core_lua.h index a583148..966b353 100644 --- a/include/lighttpd/core_lua.h +++ b/include/lighttpd/core_lua.h @@ -21,6 +21,13 @@ LI_API void li_lua_init_environment_mt(lua_State *L); LI_API liEnvironment* li_lua_get_environment(lua_State *L, int ndx); LI_API int li_lua_push_environment(lua_State *L, liEnvironment *env); +LI_API void li_lua_init_filter_mt(lua_State *L); +LI_API liFilter* li_lua_get_filter(lua_State *L, int ndx); +LI_API int li_lua_push_filter(lua_State *L, liFilter *f); +LI_API void li_lua_init_filters(lua_State *L, liServer* srv); +LI_API liFilter* li_lua_vrequest_add_filter_in(lua_State *L, liVRequest *vr, int state_ndx); +LI_API liFilter* li_lua_vrequest_add_filter_out(lua_State *L, liVRequest *vr, int state_ndx); + LI_API void li_lua_init_http_headers_mt(lua_State *L); LI_API liHttpHeaders* li_lua_get_http_headers(lua_State *L, int ndx); LI_API int li_lua_push_http_headers(lua_State *L, liHttpHeaders *headers); @@ -55,7 +62,15 @@ LI_API int li_lua_metatable_index(lua_State *L); LI_API void li_lua_init(lua_State* L, liServer* srv, liWorker* wrk); -LI_API int li_lua_push_traceback(lua_State *L, int narg); +LI_API int li_lua_push_traceback(lua_State *L, int nargs); + +/* nargs: number of arguments *with* object; object must be the first of the arguments + * returns: FALSE: an error occured. stack balance -nargs (i.e. popped args) + * TRUE: stack balance nresp-narg; popped args, pushed results + * srv/vr are only needed for error logging + * if optional is TRUE don't log an error if the method doesn't exist + */ +LI_API gboolean li_lua_call_object(liServer *srv, liVRequest *vr, lua_State *L, const char* method, int nargs, int nresults, gboolean optional); LI_API void li_lua_restore_globals(lua_State *L); LI_API void li_lua_new_globals(lua_State *L); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 31c8b7f..d608a0b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -225,6 +225,7 @@ SET(LIGHTTPD_SHARED_SRC ${LIGHTTPD_SHARED_SRC} core_lua.c connection_lua.c environment_lua.c + filters_lua.c http_headers_lua.c physical_lua.c request_lua.c diff --git a/src/main/Makefile.am b/src/main/Makefile.am index c4483f8..b0aba37 100644 --- a/src/main/Makefile.am +++ b/src/main/Makefile.am @@ -50,6 +50,7 @@ lua_src= \ connection_lua.c \ core_lua.c \ environment_lua.c \ + filters_lua.c \ http_headers_lua.c \ physical_lua.c \ request_lua.c \ diff --git a/src/main/chunk_lua.c b/src/main/chunk_lua.c index 15eb75a..e57ba2c 100644 --- a/src/main/chunk_lua.c +++ b/src/main/chunk_lua.c @@ -161,21 +161,45 @@ static int lua_chunkqueue_reset(lua_State *L) { return 0; } +static int lua_chunkqueue_steal_all(lua_State *L) { + liChunkQueue *cq, *cq_from; + + cq = li_lua_get_chunkqueue(L, 1); + cq_from = li_lua_get_chunkqueue(L, 2); + if (!cq_from) { + lua_pushliteral(L, "Expected source chunkqueue to steal from"); + return lua_error(L); + } + + li_chunkqueue_steal_all(cq, cq_from); + + return 0; +} + +static int lua_chunkqueue_skip_all(lua_State *L) { + liChunkQueue *cq; + + cq = li_lua_get_chunkqueue(L, 1); + li_chunkqueue_skip_all(cq); + + return 0; +} + + static const luaL_Reg chunkqueue_mt[] = { { "__index", lua_chunkqueue_index }, { "__newindex", lua_chunkqueue_newindex }, { "add", lua_chunkqueue_add }, { "reset", lua_chunkqueue_reset }, + { "steal_all", lua_chunkqueue_steal_all }, + { "skip_all", lua_chunkqueue_skip_all }, { NULL, NULL } }; static void init_chunkqueue_mt(lua_State *L) { luaL_register(L, NULL, chunkqueue_mt); - - lua_pushvalue(L, -1); - lua_setfield(L, -2, "__index"); } void li_lua_init_chunk_mt(lua_State *L) { diff --git a/src/main/core_lua.c b/src/main/core_lua.c index 9228ec7..c8181b5 100644 --- a/src/main/core_lua.c +++ b/src/main/core_lua.c @@ -23,13 +23,43 @@ static int traceback (lua_State *L) { return 1; } -int li_lua_push_traceback(lua_State *L, int narg) { - int base = lua_gettop(L) - narg; /* function index */ +int li_lua_push_traceback(lua_State *L, int nargs) { + int base = lua_gettop(L) - nargs; /* function index */ lua_pushcfunction(L, traceback); lua_insert(L, base); return base; } +gboolean li_lua_call_object(liServer *srv, liVRequest *vr, lua_State *L, const char* method, int nargs, int nresults, gboolean optional) { + int errfunc; + gboolean result; + + lua_getfield(L, -nargs, method); /* +1 "function" */ + + if (!lua_isfunction(L, -1)) { + if (!optional) { + _ERROR(srv, vr, "li_lua_call_object: method '%s' not found", method); + } + lua_pop(L, 1 + nargs); + return FALSE; + } + + lua_insert(L, lua_gettop(L) - nargs); /* move function before arguments */ + + errfunc = li_lua_push_traceback(L, nargs); /* +1 "errfunc" */ + if (lua_pcall(L, nargs, nresults, errfunc)) { /* pops func and arguments, push result */ + _ERROR(srv, vr, "lua_pcall(): %s", lua_tostring(L, -1)); + lua_pop(L, 1); /* -1 */ + result = FALSE; + } else { + result = TRUE; + } + lua_remove(L, errfunc); /* -1 "errfunc" */ + + /* "errfunc", "function" and args have been popped - only results remain (and nothing if lua_pcall failed) */ + return result; +} + int li_lua_metatable_index(lua_State *L) { /* search for entry in mt, i.e. functions */ if (!lua_getmetatable(L, 1)) return 0; /* +1 */ @@ -141,6 +171,7 @@ void li_lua_init(lua_State *L, liServer *srv, liWorker *wrk) { li_lua_init_chunk_mt(L); li_lua_init_connection_mt(L); li_lua_init_environment_mt(L); + li_lua_init_filter_mt(L); li_lua_init_physical_mt(L); li_lua_init_request_mt(L); li_lua_init_response_mt(L); @@ -157,6 +188,7 @@ void li_lua_init(lua_State *L, liServer *srv, liWorker *wrk) { } lua_newtable(L); /* lighty. */ + li_lua_init_filters(L, srv); lua_pushlightuserdata(L, srv); lua_pushcclosure(L, li_lua_error, 1); diff --git a/src/main/filters_lua.c b/src/main/filters_lua.c new file mode 100644 index 0000000..afd631d --- /dev/null +++ b/src/main/filters_lua.c @@ -0,0 +1,360 @@ + +#include +#include +#include + +#include +#include + +#define LUA_FILTER "liFilter*" + +typedef int (*lua_Filter_Attrib)(liFilter *f, lua_State *L); + +static int lua_filter_attr_read_in(liFilter *f, lua_State *L) { + li_lua_push_chunkqueue(L, f->in); + return 1; +} + +static int lua_filter_attr_read_out(liFilter *f, lua_State *L) { + li_lua_push_chunkqueue(L, f->out); + return 1; +} + +#define AR(m) { #m, lua_filter_attr_read_##m, NULL } +#define AW(m) { #m, NULL, lua_filter_attr_write_##m } +#define ARW(m) { #m, lua_filter_attr_read_##m, lua_filter_attr_write_##m } + +static const struct { + const char* key; + lua_Filter_Attrib read_attr, write_attr; +} filter_attribs[] = { + AR(in), + AR(out), + + { NULL, NULL, NULL } +}; + +static int lua_filter_index(lua_State *L) { + liFilter *f; + const char *key; + int i; + + if (lua_gettop(L) != 2) { + lua_pushstring(L, "incorrect number of arguments"); + lua_error(L); + } + + if (li_lua_metatable_index(L)) return 1; + + f = li_lua_get_filter(L, 1); + if (!f) return 0; + + if (lua_isnumber(L, 2)) return 0; + if (!lua_isstring(L, 2)) return 0; + + key = lua_tostring(L, 2); + for (i = 0; filter_attribs[i].key ; i++) { + if (0 == strcmp(key, filter_attribs[i].key)) { + if (filter_attribs[i].read_attr) + return filter_attribs[i].read_attr(f, L); + break; + } + } + + lua_pushstring(L, "cannot read attribute "); + lua_pushstring(L, key); + lua_pushstring(L, " in filter"); + lua_concat(L, 3); + lua_error(L); + + return 0; +} + +static int lua_filter_newindex(lua_State *L) { + liFilter *f; + const char *key; + int i; + + if (lua_gettop(L) != 3) { + lua_pushstring(L, "incorrect number of arguments"); + lua_error(L); + } + + f = li_lua_get_filter(L, 1); + if (!f) return 0; + + if (lua_isnumber(L, 2)) return 0; + if (!lua_isstring(L, 2)) return 0; + + key = lua_tostring(L, 2); + for (i = 0; filter_attribs[i].key ; i++) { + if (0 == strcmp(key, filter_attribs[i].key)) { + if (filter_attribs[i].write_attr) + return filter_attribs[i].write_attr(f, L); + break; + } + } + + lua_pushstring(L, "cannot write attribute "); + lua_pushstring(L, key); + lua_pushstring(L, "in filter"); + lua_concat(L, 3); + lua_error(L); + + return 0; +} + +static const luaL_Reg filter_mt[] = { + { "__index", lua_filter_index }, + { "__newindex", lua_filter_newindex }, + + { NULL, NULL } +}; + +static void init_filter_mt(lua_State *L) { + luaL_register(L, NULL, filter_mt); +} + +void li_lua_init_filter_mt(lua_State *L) { + if (luaL_newmetatable(L, LUA_FILTER)) { + init_filter_mt(L); + } + lua_pop(L, 1); +} + +liFilter* li_lua_get_filter(lua_State *L, int ndx) { + if (!lua_isuserdata(L, ndx)) return NULL; + if (!lua_getmetatable(L, ndx)) return NULL; + luaL_getmetatable(L, LUA_FILTER); + if (lua_isnil(L, -1) || lua_isnil(L, -2) || !lua_equal(L, -1, -2)) { + lua_pop(L, 2); + return NULL; + } + lua_pop(L, 2); + return *(liFilter**) lua_touserdata(L, ndx); +} + +int li_lua_push_filter(lua_State *L, liFilter *f) { + liFilter **pf; + + pf = (liFilter**) lua_newuserdata(L, sizeof(liFilter*)); + *pf = f; + + if (luaL_newmetatable(L, LUA_FILTER)) { + init_filter_mt(L); + } + + lua_setmetatable(L, -2); + return 1; +} + + + +typedef struct filter_lua_config filter_lua_config; +struct filter_lua_config { + lua_State *L; + int class_ref; +}; + +typedef struct filter_lua_state filter_lua_state; +struct filter_lua_state { + lua_State *L; + int object_ref; +}; + +static filter_lua_state* filter_lua_state_new(liVRequest *vr, filter_lua_config *config) { + int object_ref = LUA_NOREF; + liServer *srv = vr->wrk->srv; + lua_State *L = config->L; + gboolean dolock = (L == srv->L); + + if (dolock) li_lua_lock(srv); + + lua_rawgeti(L, LUA_REGISTRYINDEX, config->class_ref); /* +1 */ + li_lua_push_vrequest(L, vr); /* +1 */ + + if (li_lua_call_object(srv, vr, L, "new", 2, 1, FALSE)) { /* -2, +1 on success */ + if (!lua_isnil(L, -1)) { + object_ref = luaL_ref(L, LUA_REGISTRYINDEX); /* -1 */ + } else { /* no error; nil is interpreted as "don't need this filter for this request" */ + lua_pop(L, 1); /* -1 */ + } + } else { + VR_ERROR(vr, "%s", "li_lua_call_object failed"); + li_vrequest_error(vr); + } + + if (dolock) li_lua_unlock(srv); + + if (LUA_NOREF != object_ref) { + filter_lua_state *state = g_slice_new0(filter_lua_state); + state->L = L; + state->object_ref = object_ref; + + return state; + } else { + return NULL; + } +} + +static void filter_lua_state_free(liVRequest *vr, filter_lua_state *state) { + liServer *srv = vr->wrk->srv; + lua_State *L = state->L; + gboolean dolock = (L == srv->L); + + if (dolock) li_lua_lock(srv); + + lua_rawgeti(L, LUA_REGISTRYINDEX, state->object_ref); /* +1 */ + li_lua_push_vrequest(L, vr); /* +1 */ + li_lua_call_object(srv, vr, L, "finished", 2, 0, TRUE); /* -2 */ + + luaL_unref(L, LUA_REGISTRYINDEX, state->object_ref); + + if (dolock) li_lua_unlock(srv); + + g_slice_free(filter_lua_state, state); +} + +static void filter_lua_free(liVRequest *vr, liFilter *f) { + filter_lua_state *state = (filter_lua_state*) f->param; + + filter_lua_state_free(vr, state); +} + +static liHandlerResult filter_lua_handle(liVRequest *vr, liFilter *f) { + filter_lua_state *state = (filter_lua_state*) f->param; + liServer *srv = vr->wrk->srv; + lua_State *L = state->L; + gboolean dolock = (L == srv->L); + liHandlerResult res; + + if (dolock) li_lua_lock(srv); + + lua_rawgeti(L, LUA_REGISTRYINDEX, state->object_ref); /* +1 */ + li_lua_push_vrequest(L, vr); /* +1 */ + li_lua_push_chunkqueue(L, f->out); /* +1 */ + li_lua_push_chunkqueue(L, f->in); /* +1 */ + if (li_lua_call_object(NULL, vr, L, "handle", 4, 1, FALSE)) { /* -4, +1 on success */ + res = LI_HANDLER_GO_ON; + if (!lua_isnil(L, -1)) { + int rc = lua_tointeger(L, -1); + switch (rc) { + case LI_HANDLER_GO_ON: + case LI_HANDLER_COMEBACK: + case LI_HANDLER_WAIT_FOR_EVENT: + case LI_HANDLER_ERROR: + res = rc; + break; + default: + VR_ERROR(vr, "lua filter returned an error or an unknown value (%i)", rc); + res = LI_HANDLER_ERROR; + } + } + lua_pop(L, 1); + } else { + res = LI_HANDLER_ERROR; + } + + if (dolock) li_lua_unlock(srv); + + return res; +} + +static liHandlerResult filter_lua_in(liVRequest *vr, gpointer param, gpointer *context) { + filter_lua_config *config = param; + filter_lua_state *state = filter_lua_state_new(vr, config); + UNUSED(context); + + if (state) { + li_vrequest_add_filter_in(vr, filter_lua_handle, filter_lua_free, state); + } + + return LI_HANDLER_GO_ON; +} + +static liHandlerResult filter_lua_out(liVRequest *vr, gpointer param, gpointer *context) { + filter_lua_config *config = param; + filter_lua_state *state = filter_lua_state_new(vr, config); + UNUSED(context); + + if (state) { + li_vrequest_add_filter_out(vr, filter_lua_handle, filter_lua_free, state); + } + + return LI_HANDLER_GO_ON; +} + +static void filter_lua_action_free(liServer *srv, gpointer param) { + filter_lua_config *config = param; + lua_State *L = config->L; + gboolean dolock = (L == srv->L); + + if (dolock) li_lua_lock(srv); + luaL_unref(L, LUA_REGISTRYINDEX, config->class_ref); + if (dolock) li_lua_unlock(srv); + + g_slice_free(filter_lua_config, config); +} + +static int filter_lua_action_create(lua_State *L, liActionFuncCB act_cb) { + liServer *srv = lua_touserdata(L, lua_upvalueindex(1)); + liAction *act; + filter_lua_config *config; + + if (lua_gettop(L) != 1 || lua_isnil(L, 1)) { + int n = lua_gettop(L); + lua_pushstring(L, "expected exactly one parameter for lighty.filter_[in/out], got "); + lua_pushinteger(L, n); + lua_concat(L, 2); + return lua_error(L); + } + + config = g_slice_new0(filter_lua_config); + config->L = L; + config->class_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + act = li_action_new_function(act_cb, NULL, filter_lua_action_free, config); + return li_lua_push_action(srv, L, act); +} + +static int filter_lua_in_create(lua_State *L) { + return filter_lua_action_create(L, filter_lua_in); +} + +static int filter_lua_out_create(lua_State *L) { + return filter_lua_action_create(L, filter_lua_out); +} + +void li_lua_init_filters(lua_State *L, liServer* srv) { + lua_pushlightuserdata(L, srv); + lua_pushcclosure(L, filter_lua_in_create, 1); + lua_setfield(L, -2, "filter_in"); + + lua_pushlightuserdata(L, srv); + lua_pushcclosure(L, filter_lua_out_create, 1); + lua_setfield(L, -2, "filter_out"); +} + +liFilter* li_lua_vrequest_add_filter_in(lua_State *L, liVRequest *vr, int state_ndx) { + filter_lua_state *state; + + lua_pushvalue(L, state_ndx); + + state = g_slice_new0(filter_lua_state); + state->L = L; + state->object_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + return li_vrequest_add_filter_in(vr, filter_lua_handle, filter_lua_free, state); +} + +liFilter* li_lua_vrequest_add_filter_out(lua_State *L, liVRequest *vr, int state_ndx) { + filter_lua_state *state; + + lua_pushvalue(L, state_ndx); + + state = g_slice_new0(filter_lua_state); + state->L = L; + state->object_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + return li_vrequest_add_filter_out(vr, filter_lua_handle, filter_lua_free, state); +} diff --git a/src/main/virtualrequest.c b/src/main/virtualrequest.c index 9dd86e4..e03bd35 100644 --- a/src/main/virtualrequest.c +++ b/src/main/virtualrequest.c @@ -53,6 +53,9 @@ static gboolean filters_handle_out_close(liVRequest *vr, liFilters *fs) { case LI_HANDLER_WAIT_FOR_EVENT: break; /* ignore - filter has to call li_vrequest_joblist_append(vr); */ case LI_HANDLER_ERROR: + if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { + VR_DEBUG(vr, "filter %i return an error", i); + } return FALSE; } } @@ -78,6 +81,9 @@ static gboolean filters_run(liVRequest *vr, liFilters *fs) { case LI_HANDLER_WAIT_FOR_EVENT: break; /* ignore - filter has to call li_vrequest_joblist_append(vr); */ case LI_HANDLER_ERROR: + if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { + VR_DEBUG(vr, "filter %i return an error", i); + } return FALSE; } f->knows_out_is_closed = f->out->is_closed; @@ -460,12 +466,14 @@ static gboolean vrequest_do_handle_read(liVRequest *vr) { return TRUE; } -static gboolean vrequest_do_handle_write(liVRequest *vr) { +static void vrequest_do_handle_write(liVRequest *vr) { if (!filters_handle_out_close(vr, &vr->filters_out)) { li_vrequest_error(vr); + return; } if (!filters_run(vr, &vr->filters_out)) { li_vrequest_error(vr); + return; } switch (vr->handle_response_body(vr)) { @@ -473,14 +481,14 @@ static gboolean vrequest_do_handle_write(liVRequest *vr) { break; case LI_HANDLER_COMEBACK: li_vrequest_joblist_append(vr); /* come back later */ - return FALSE; + return; case LI_HANDLER_WAIT_FOR_EVENT: - return FALSE; + return; case LI_HANDLER_ERROR: li_vrequest_error(vr); break; } - return TRUE; + return; } void li_vrequest_state_machine(liVRequest *vr) { diff --git a/src/main/virtualrequest_lua.c b/src/main/virtualrequest_lua.c index 4bdfa49..274b63c 100644 --- a/src/main/virtualrequest_lua.c +++ b/src/main/virtualrequest_lua.c @@ -287,6 +287,32 @@ static int lua_vrequest_enter_action(lua_State *L) { return 0; } +static int lua_vrequest_add_filter_in(lua_State *L) { + liVRequest *vr; + + if (lua_gettop(L) != 2) { + lua_pushstring(L, "incorrect number of arguments"); + lua_error(L); + } + + vr = li_lua_get_vrequest(L, 1); + + return li_lua_push_filter(L, li_lua_vrequest_add_filter_in(L, vr, 2)); +} + +static int lua_vrequest_add_filter_out(lua_State *L) { + liVRequest *vr; + + if (lua_gettop(L) != 2) { + lua_pushstring(L, "incorrect number of arguments"); + lua_error(L); + } + + vr = li_lua_get_vrequest(L, 1); + + return li_lua_push_filter(L, li_lua_vrequest_add_filter_out(L, vr, 2)); +} + static const luaL_Reg vrequest_mt[] = { { "__index", lua_vrequest_index }, { "__newindex", lua_vrequest_newindex }, @@ -302,6 +328,9 @@ static const luaL_Reg vrequest_mt[] = { { "enter_action", lua_vrequest_enter_action }, + { "add_filter_in", lua_vrequest_add_filter_in }, + { "add_filter_out", lua_vrequest_add_filter_out }, + { NULL, NULL } }; diff --git a/src/main/wscript b/src/main/wscript index 570483f..ce8dcb2 100644 --- a/src/main/wscript +++ b/src/main/wscript @@ -69,6 +69,7 @@ def build(bld): connection_lua.c core_lua.c environment_lua.c + filters_lua.c http_headers_lua.c physical_lua.c request_lua.c