From b949545598eaa75b38b4d57c9aea6216bd82256c Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 4 Jan 2013 23:14:07 +0100 Subject: [PATCH] add lua plugin support --- CMakeLists.txt | 44 +++++++- cgi.c | 4 +- lua.c | 280 +++++++++++++++++++++++++++++++++++++++++++++++++ main.c | 20 ++++ plugin.c | 55 ++++++++++ plugin.h | 39 +++++++ proc.c | 6 +- uhttpd.h | 8 +- 8 files changed, 448 insertions(+), 8 deletions(-) create mode 100644 lua.c create mode 100644 plugin.c create mode 100644 plugin.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d0c1b0..889c32e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") ADD_DEFINITIONS(-Os -Wall -Werror -Wmissing-declarations --std=gnu99 -g3) OPTION(TLS_SUPPORT "TLS support" ON) +OPTION(LUA_SUPPORT "Lua support" ON) IF(APPLE) INCLUDE_DIRECTORIES(/opt/local/include) @@ -16,7 +17,7 @@ IF(LIBS STREQUAL "LIBS-NOTFOUND") SET(LIBS "") ENDIF() -SET(SOURCES main.c listen.c client.c utils.c file.c auth.c cgi.c relay.c proc.c) +SET(SOURCES main.c listen.c client.c utils.c file.c auth.c cgi.c relay.c proc.c plugin.c) IF(TLS_SUPPORT) SET(SOURCES ${SOURCES} tls.c) ADD_DEFINITIONS(-DHAVE_TLS) @@ -24,3 +25,44 @@ ENDIF() ADD_EXECUTABLE(uhttpd ${SOURCES}) TARGET_LINK_LIBRARIES(uhttpd ubox dl ${LIBS}) + +SET(PLUGINS "") +IF(LUA_SUPPORT) + FIND_PROGRAM(PKG_CONFIG pkg-config) + + IF(NOT LUA_CFLAGS AND PKG_CONFIG) + EXECUTE_PROCESS( + COMMAND pkg-config --silence-errors --cflags lua5.1 + OUTPUT_VARIABLE LUA_CFLAGS + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + ENDIF() + + IF(NOT LUA_LIBS AND PKG_CONFIG) + EXECUTE_PROCESS( + COMMAND pkg-config --silence-errors --libs lua5.1 + OUTPUT_VARIABLE LUA_LIBS + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + ENDIF() + + IF(NOT LUA_LIBS) + SET(LUA_LIBS "lua") + ENDIF() + + SET(PLUGINS ${PLUGINS} uhttpd_lua) + ADD_DEFINITIONS(-DHAVE_LUA ${LUA_CFLAGS}) + ADD_LIBRARY(uhttpd_lua MODULE lua.c) + TARGET_LINK_LIBRARIES(uhttpd_lua ${LUA_LIBS} m dl) +ENDIF() + +IF(PLUGINS) + SET_TARGET_PROPERTIES(${PLUGINS} PROPERTIES + PREFIX "" + ) +ENDIF() + +INSTALL(TARGETS uhttpd ${PLUGINS} + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib +) diff --git a/cgi.c b/cgi.c index ad280cd..2b7cffb 100644 --- a/cgi.c +++ b/cgi.c @@ -36,7 +36,7 @@ void uh_interpreter_add(const char *ext, const char *path) list_add_tail(&in->list, &interpreters); } -static void cgi_main(struct client *cl, struct path_info *pi) +static void cgi_main(struct client *cl, struct path_info *pi, const char *url) { const struct interpreter *ip = pi->ip; struct env_var *var; @@ -74,7 +74,7 @@ static void cgi_handle_request(struct client *cl, const char *url, struct path_i return; } - if (!uh_create_process(cl, pi, cgi_main)) { + if (!uh_create_process(cl, pi, url, cgi_main)) { uh_client_error(cl, 500, "Internal Server Error", "Failed to create CGI process: %s", strerror(errno)); return; diff --git a/lua.c b/lua.c new file mode 100644 index 0000000..e404add --- /dev/null +++ b/lua.c @@ -0,0 +1,280 @@ +/* + * uhttpd - Tiny single-threaded httpd + * + * Copyright (C) 2010-2012 Jo-Philipp Wich + * Copyright (C) 2012 Felix Fietkau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include +#include +#include +#include +#include +#include + +#include "uhttpd.h" +#include "plugin.h" + +#define UH_LUA_CB "handle_request" + +static const struct uhttpd_ops *ops; +static struct config *_conf; +#define conf (*_conf) + +static lua_State *_L; + +static int uh_lua_recv(lua_State *L) +{ + static struct pollfd pfd = { + .fd = STDIN_FILENO, + .events = POLLIN, + }; + luaL_Buffer B; + int data_len = 0; + int len; + int r; + + len = luaL_checknumber(L, 1); + luaL_buffinit(L, &B); + while(len > 0) { + char *buf; + + buf = luaL_prepbuffer(&B); + r = read(STDIN_FILENO, buf, LUAL_BUFFERSIZE); + if (r < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + pfd.revents = 0; + poll(&pfd, 1, 1000); + if (pfd.revents & POLLIN) + continue; + } + if (errno == EINTR) + continue; + + if (!data_len) + data_len = -1; + break; + } + if (!r) + break; + + luaL_addsize(&B, r); + data_len += r; + if (r != LUAL_BUFFERSIZE) + break; + } + + luaL_pushresult(&B); + lua_pushnumber(L, data_len); + if (data_len > 0) { + lua_pushvalue(L, -2); + lua_remove(L, -3); + return 2; + } else { + lua_remove(L, -2); + return 1; + } +} + +static int +uh_lua_strconvert(lua_State *L, int (*convert)(char *, int, const char *, int)) +{ + const char *in_buf; + static char out_buf[4096]; + size_t in_len; + int out_len; + + in_buf = luaL_checklstring(L, 1, &in_len); + out_len = convert(out_buf, sizeof(out_buf), in_buf, in_len); + + if (out_len < 0) { + const char *error; + + if (out_len == -1) + error = "buffer overflow"; + else + error = "malformed string"; + + luaL_error(L, "%s on URL conversion\n", error); + } + + lua_pushlstring(L, out_buf, out_len); + return 1; +} + +static int uh_lua_urldecode(lua_State *L) +{ + return uh_lua_strconvert(L, ops->urldecode); +} + +static int uh_lua_urlencode(lua_State *L) +{ + return uh_lua_strconvert(L, ops->urlencode); +} + +static lua_State *uh_lua_state_init(void) +{ + const char *msg = "(unknown error)"; + const char *status; + lua_State *L; + int ret; + + L = lua_open(); + luaL_openlibs(L); + + /* build uhttpd api table */ + lua_newtable(L); + + /* + * use print as send and sendc implementation, + * chunked transfer is handled in the main server + */ + lua_getglobal(L, "print"); + lua_pushvalue(L, -1); + lua_setfield(L, -3, "send"); + lua_setfield(L, -2, "sendc"); + + lua_pushcfunction(L, uh_lua_recv); + lua_setfield(L, -2, "recv"); + + lua_pushcfunction(L, uh_lua_urldecode); + lua_setfield(L, -2, "urldecode"); + + lua_pushcfunction(L, uh_lua_urlencode); + lua_setfield(L, -2, "urlencode"); + + lua_pushstring(L, conf.docroot); + lua_setfield(L, -2, "docroot"); + + lua_setglobal(L, "uhttpd"); + + ret = luaL_loadfile(L, conf.lua_handler); + if (ret) { + status = "loading"; + goto error; + } + + ret = lua_pcall(L, 0, 0, 0); + if (ret) { + status = "initializing"; + goto error; + } + + lua_getglobal(L, UH_LUA_CB); + if (!lua_isfunction(L, -1)) { + fprintf(stderr, "Error: Lua handler provides no " UH_LUA_CB "() callback.\n"); + exit(1); + } + + return L; + +error: + if (!lua_isnil(L, -1)) + msg = lua_tostring(L, -1); + + fprintf(stderr, "Error %s Lua handler: %s\n", status, msg); + exit(1); + return NULL; +} + +static void lua_main(struct client *cl, struct path_info *pi, const char *url) +{ + const char *error; + struct env_var *var; + lua_State *L = _L; + int path_len, prefix_len; + char *str; + + lua_getglobal(L, UH_LUA_CB); + + /* new env table for this request */ + lua_newtable(L); + + prefix_len = strlen(conf.lua_prefix); + path_len = strlen(url); + str = strchr(url, '?'); + if (str) { + pi->query = str; + path_len = str - url; + } + if (path_len > prefix_len) { + lua_pushlstring(L, url + prefix_len, + path_len - prefix_len); + lua_setfield(L, -2, "PATH_INFO"); + } + + for (var = ops->get_process_vars(cl, pi); var->name; var++) { + if (!var->value) + continue; + + lua_pushstring(L, var->value); + lua_setfield(L, -2, var->name); + } + + lua_pushnumber(L, 0.9 + (cl->request.version / 10.0)); + lua_setfield(L, -2, "HTTP_VERSION"); + + switch(lua_pcall(L, 1, 0, 0)) { + case LUA_ERRMEM: + case LUA_ERRRUN: + error = luaL_checkstring(L, -1); + if (!error) + error = "(unknown error)"; + + printf("Status: 500 Internal Server Error\r\n\r\n" + "Unable to launch the requested Lua program:\n" + " %s: %s\n", pi->phys, strerror(errno)); + } + + exit(0); +} + +static void lua_handle_request(struct client *cl, const char *url, struct path_info *pi) +{ + static struct path_info _pi; + + pi = &_pi; + pi->name = conf.lua_prefix; + pi->phys = conf.lua_handler; + + if (!ops->create_process(cl, pi, url, lua_main)) { + ops->client_error(cl, 500, "Internal Server Error", + "Failed to create CGI process: %s", strerror(errno)); + } +} + +static bool check_lua_url(const char *url) +{ + return ops->path_match(conf.lua_prefix, url); +} + +static struct dispatch_handler lua_dispatch = { + .check_url = check_lua_url, + .handle_request = lua_handle_request, +}; + +static int lua_plugin_init(const struct uhttpd_ops *o, struct config *c) +{ + ops = o; + _conf = c; + _L = uh_lua_state_init(); + ops->dispatch_add(&lua_dispatch); + return 0; +} + +const struct uhttpd_plugin uhttpd_plugin = { + .init = lua_plugin_init, +}; diff --git a/main.c b/main.c index 160f932..cefd4ab 100644 --- a/main.c +++ b/main.c @@ -329,6 +329,15 @@ int main(int argc, char **argv) case 'K': tls_key = optarg; break; +#ifdef HAVE_LUA + case 'l': + conf.lua_prefix = optarg; + break; + + case 'L': + conf.lua_handler = optarg; + break; +#endif default: return usage(argv[0]); } @@ -357,6 +366,17 @@ int main(int argc, char **argv) #endif } +#ifdef HAVE_LUA + if (conf.lua_handler || conf.lua_prefix) { + if (!conf.lua_handler || !conf.lua_prefix) { + fprintf(stderr, "Need handler and prefix to enable Lua support\n"); + return 1; + } + if (uh_plugin_init("uhttpd_lua.so")) + return 1; + } +#endif + /* fork (if not disabled) */ if (!nofork) { switch (fork()) { diff --git a/plugin.c b/plugin.c new file mode 100644 index 0000000..25a8464 --- /dev/null +++ b/plugin.c @@ -0,0 +1,55 @@ +/* + * uhttpd - Tiny single-threaded httpd - Main header + * + * Copyright (C) 2010-2012 Jo-Philipp Wich + * Copyright (C) 2012 Felix Fietkau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "uhttpd.h" +#include "plugin.h" + +static const struct uhttpd_ops ops = { + .dispatch_add = uh_dispatch_add, + .path_match = uh_path_match, + .create_process = uh_create_process, + .get_process_vars = uh_get_process_vars, + .client_error = uh_client_error, + .chunk_write = uh_chunk_write, + .urlencode = uh_urlencode, + .urldecode = uh_urldecode, +}; + +int uh_plugin_init(const char *name) +{ + struct uhttpd_plugin *p; + const char *sym; + void *dlh; + + dlh = dlopen(name, RTLD_LAZY | RTLD_LOCAL); + if (!dlh) { + fprintf(stderr, "Could not open plugin %s: %s\n", name, dlerror()); + return -ENOENT; + } + + sym = "uhttpd_plugin"; + p = dlsym(dlh, sym); + if (!p) { + fprintf(stderr, "Could not find symbol '%s' in plugin '%s'\n", sym, name); + return -ENOENT; + } + + return p->init(&ops, &conf); +} diff --git a/plugin.h b/plugin.h new file mode 100644 index 0000000..dd73c57 --- /dev/null +++ b/plugin.h @@ -0,0 +1,39 @@ +/* + * uhttpd - Tiny single-threaded httpd - Main header + * + * Copyright (C) 2010-2012 Jo-Philipp Wich + * Copyright (C) 2012 Felix Fietkau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uhttpd.h" + +struct uhttpd_ops { + void (*dispatch_add)(struct dispatch_handler *d); + bool (*path_match)(const char *prefix, const char *url); + + bool (*create_process)(struct client *cl, struct path_info *pi, const char *url, + void (*cb)(struct client *cl, struct path_info *pi, const char *url)); + struct env_var *(*get_process_vars)(struct client *cl, struct path_info *pi); + + void (*client_error)(struct client *cl, int code, const char *summary, const char *fmt, ...); + void (*chunk_write)(struct client *cl, const void *data, int len); + + int (*urlencode)(char *buf, int blen, const char *src, int slen); + int (*urldecode)(char *buf, int blen, const char *src, int slen); +}; + +struct uhttpd_plugin { + int (*init)(const struct uhttpd_ops *ops, struct config *conf); +}; diff --git a/proc.c b/proc.c index ad15abb..8680d01 100644 --- a/proc.c +++ b/proc.c @@ -296,8 +296,8 @@ static int proc_data_send(struct client *cl, const char *data, int len) return retlen; } -bool uh_create_process(struct client *cl, struct path_info *pi, - void (*cb)(struct client *cl, struct path_info *pi)) +bool uh_create_process(struct client *cl, struct path_info *pi, const char *url, + void (*cb)(struct client *cl, struct path_info *pi, const char *url)) { struct dispatch *d = &cl->dispatch; struct dispatch_proc *proc = &d->proc; @@ -331,7 +331,7 @@ bool uh_create_process(struct client *cl, struct path_info *pi, close(wfd[1]); uh_close_fds(); - cb(cl, pi); + cb(cl, pi, url); exit(0); } diff --git a/uhttpd.h b/uhttpd.h index 9ed6439..b8dfece 100644 --- a/uhttpd.h +++ b/uhttpd.h @@ -49,6 +49,8 @@ struct config { const char *error_handler; const char *cgi_prefix; const char *cgi_path; + const char *lua_handler; + const char *lua_prefix; int no_symlinks; int no_dirlists; int network_timeout; @@ -235,7 +237,9 @@ void uh_relay_close(struct relay *r, int ret); void uh_relay_free(struct relay *r); struct env_var *uh_get_process_vars(struct client *cl, struct path_info *pi); -bool uh_create_process(struct client *cl, struct path_info *pi, - void (*cb)(struct client *cl, struct path_info *pi)); +bool uh_create_process(struct client *cl, struct path_info *pi, const char *url, + void (*cb)(struct client *cl, struct path_info *pi, const char *url)); + +int uh_plugin_init(const char *name); #endif -- 2.30.2