/* $NetBSD: lua-bozo.c,v 1.15 2017/05/28 22:37:36 alnsn Exp $ */ /* * Copyright (c) 2013 Marc Balmer * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer and * dedication in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* this code implements dynamic content generation using Lua for bozohttpd */ #ifndef NO_LUA_SUPPORT #include #include #include #include #include #include #include #include "bozohttpd.h" /* Lua binding for bozohttp */ #if LUA_VERSION_NUM < 502 #define LUA_HTTPDLIBNAME "httpd" #endif #define FORM "application/x-www-form-urlencoded" static bozohttpd_t * httpd_instance(lua_State *L) { bozohttpd_t *httpd; lua_pushstring(L, "bozohttpd"); lua_gettable(L, LUA_REGISTRYINDEX); httpd = lua_touserdata(L, -1); lua_pop(L, 1); return httpd; } static int lua_flush(lua_State *L) { bozohttpd_t *httpd = httpd_instance(L); bozo_flush(httpd, stdout); return 0; } static int lua_print(lua_State *L) { bozohttpd_t *httpd = httpd_instance(L); bozo_printf(httpd, "%s\r\n", lua_tostring(L, 1)); return 0; } static int lua_read(lua_State *L) { bozohttpd_t *httpd = httpd_instance(L); luaL_Buffer lbuf; char *data; lua_Integer len; ssize_t n; len = luaL_checkinteger(L, 1); data = luaL_buffinitsize(L, &lbuf, (size_t)len); if ((n = bozo_read(httpd, STDIN_FILENO, data, len)) >= 0) { luaL_pushresultsize(&lbuf, n); return 1; } else { lua_pushnil(L); lua_pushstring(L, "bozo_read() call failed"); return 2; } } static int lua_register_handler(lua_State *L) { bozohttpd_t *httpd = httpd_instance(L); lua_state_map_t *map; lua_handler_t *handler; const char *name; int ref; lua_pushstring(L, "lua_state_map"); lua_gettable(L, LUA_REGISTRYINDEX); map = lua_touserdata(L, -1); lua_pop(L, 1); name = luaL_checkstring(L, 1); luaL_checktype(L, 2, LUA_TFUNCTION); lua_pushvalue(L, 2); ref = luaL_ref(L, LUA_REGISTRYINDEX); handler = bozomalloc(httpd, sizeof(lua_handler_t)); handler->name = bozostrdup(httpd, NULL, name); handler->ref = ref; SIMPLEQ_INSERT_TAIL(&map->handlers, handler, h_next); httpd->process_lua = 1; return 0; } static int lua_write(lua_State *L) { bozohttpd_t *httpd = httpd_instance(L); const char *data; size_t len; ssize_t n; data = luaL_checklstring(L, 1, &len); if ((n = bozo_write(httpd, STDIN_FILENO, data, len)) >= 0) { lua_pushinteger(L, n); return 1; } else { lua_pushnil(L); lua_pushstring(L, "bozo_write() call failed"); return 2; } } static int luaopen_httpd(lua_State *L) { static struct luaL_Reg functions[] = { { "flush", lua_flush }, { "print", lua_print }, { "read", lua_read }, { "register_handler", lua_register_handler }, { "write", lua_write }, { NULL, NULL } }; #if LUA_VERSION_NUM >= 502 luaL_newlib(L, functions); #else luaL_register(L, LUA_HTTPDLIBNAME, functions); #endif lua_pushstring(L, "httpd 1.0.0"); lua_setfield(L, -2, "_VERSION"); return 1; } #if LUA_VERSION_NUM < 502 static void lua_openlib(lua_State *L, const char *name, lua_CFunction fn) { lua_pushcfunction(L, fn); lua_pushstring(L, name); lua_call(L, 1, 0); } #endif /* bozohttpd integration */ void bozo_add_lua_map(bozohttpd_t *httpd, const char *prefix, const char *script) { lua_state_map_t *map; map = bozomalloc(httpd, sizeof(lua_state_map_t)); map->prefix = bozostrdup(httpd, NULL, prefix); if (*script == '/') map->script = bozostrdup(httpd, NULL, script); else { char cwd[MAXPATHLEN], *path; getcwd(cwd, sizeof(cwd) - 1); bozoasprintf(httpd, &path, "%s/%s", cwd, script); map->script = path; } map->L = luaL_newstate(); if (map->L == NULL) bozoerr(httpd, 1, "can't create Lua state"); SIMPLEQ_INIT(&map->handlers); #if LUA_VERSION_NUM >= 502 luaL_openlibs(map->L); lua_getglobal(map->L, "package"); lua_getfield(map->L, -1, "preload"); lua_pushcfunction(map->L, luaopen_httpd); lua_setfield(map->L, -2, "httpd"); lua_pop(map->L, 2); #else lua_openlib(map->L, "", luaopen_base); lua_openlib(map->L, LUA_LOADLIBNAME, luaopen_package); lua_openlib(map->L, LUA_TABLIBNAME, luaopen_table); lua_openlib(map->L, LUA_STRLIBNAME, luaopen_string); lua_openlib(map->L, LUA_MATHLIBNAME, luaopen_math); lua_openlib(map->L, LUA_OSLIBNAME, luaopen_os); lua_openlib(map->L, LUA_IOLIBNAME, luaopen_io); lua_openlib(map->L, LUA_HTTPDLIBNAME, luaopen_httpd); #endif lua_pushstring(map->L, "lua_state_map"); lua_pushlightuserdata(map->L, map); lua_settable(map->L, LUA_REGISTRYINDEX); lua_pushstring(map->L, "bozohttpd"); lua_pushlightuserdata(map->L, httpd); lua_settable(map->L, LUA_REGISTRYINDEX); if (luaL_loadfile(map->L, script)) bozoerr(httpd, 1, "failed to load script %s: %s", script, lua_tostring(map->L, -1)); if (lua_pcall(map->L, 0, 0, 0)) bozoerr(httpd, 1, "failed to execute script %s: %s", script, lua_tostring(map->L, -1)); SIMPLEQ_INSERT_TAIL(&httpd->lua_states, map, s_next); } static void lua_env(lua_State *L, const char *name, const char *value) { lua_pushstring(L, value); lua_setfield(L, -2, name); } /* decode query string */ static void lua_url_decode(lua_State *L, char *s) { char *v, *p, *val, *q; char buf[3]; int c; v = strchr(s, '='); if (v == NULL) return; *v++ = '\0'; val = malloc(strlen(v) + 1); if (val == NULL) return; for (p = v, q = val; *p; p++) { switch (*p) { case '%': if (*(p + 1) == '\0' || *(p + 2) == '\0') { free(val); return; } buf[0] = *++p; buf[1] = *++p; buf[2] = '\0'; sscanf(buf, "%2x", &c); *q++ = (char)c; break; case '+': *q++ = ' '; break; default: *q++ = *p; } } *q = '\0'; lua_pushstring(L, val); lua_setfield(L, -2, s); free(val); } static void lua_decode_query(lua_State *L, char *query) { char *s; s = strtok(query, "&"); while (s) { lua_url_decode(L, s); s = strtok(NULL, "&"); } } int bozo_process_lua(bozo_httpreq_t *request) { bozohttpd_t *httpd = request->hr_httpd; lua_state_map_t *map; lua_handler_t *hndlr; int n, ret, length; char date[40]; bozoheaders_t *headp; char *s, *query, *uri, *file, *command, *info, *content; const char *type, *clen; char *prefix, *handler, *p; int rv = 0; if (!httpd->process_lua) return 0; info = NULL; query = NULL; prefix = NULL; uri = request->hr_oldfile ? request->hr_oldfile : request->hr_file; if (*uri == '/') { file = bozostrdup(httpd, request, uri); if (file == NULL) goto out; prefix = bozostrdup(httpd, request, &uri[1]); } else { if (asprintf(&file, "/%s", uri) < 0) goto out; prefix = bozostrdup(httpd, request, uri); } if (prefix == NULL) goto out; if (request->hr_query && request->hr_query[0]) query = bozostrdup(httpd, request, request->hr_query); p = strchr(prefix, '/'); if (p == NULL) goto out; *p++ = '\0'; handler = p; if (!*handler) goto out; p = strchr(handler, '/'); if (p != NULL) *p++ = '\0'; command = file + 1; if ((s = strchr(command, '/')) != NULL) { info = bozostrdup(httpd, request, s); *s = '\0'; } type = request->hr_content_type; clen = request->hr_content_length; SIMPLEQ_FOREACH(map, &httpd->lua_states, s_next) { if (strcmp(map->prefix, prefix)) continue; SIMPLEQ_FOREACH(hndlr, &map->handlers, h_next) { if (strcmp(hndlr->name, handler)) continue; lua_rawgeti(map->L, LUA_REGISTRYINDEX, hndlr->ref); /* Create the "environment" */ lua_newtable(map->L); lua_env(map->L, "SERVER_NAME", BOZOHOST(httpd, request)); lua_env(map->L, "GATEWAY_INTERFACE", "Luigi/1.0"); lua_env(map->L, "SERVER_PROTOCOL", request->hr_proto); lua_env(map->L, "REQUEST_METHOD", request->hr_methodstr); lua_env(map->L, "SCRIPT_PREFIX", map->prefix); lua_env(map->L, "SCRIPT_NAME", file); lua_env(map->L, "HANDLER_NAME", hndlr->name); lua_env(map->L, "SCRIPT_FILENAME", map->script); lua_env(map->L, "SERVER_SOFTWARE", httpd->server_software); lua_env(map->L, "REQUEST_URI", uri); lua_env(map->L, "DATE_GMT", bozo_http_date(date, sizeof(date))); if (query && *query) lua_env(map->L, "QUERY_STRING", query); if (info && *info) lua_env(map->L, "PATH_INFO", info); if (type && *type) lua_env(map->L, "CONTENT_TYPE", type); if (clen && *clen) lua_env(map->L, "CONTENT_LENGTH", clen); if (request->hr_serverport && *request->hr_serverport) lua_env(map->L, "SERVER_PORT", request->hr_serverport); if (request->hr_remotehost && *request->hr_remotehost) lua_env(map->L, "REMOTE_HOST", request->hr_remotehost); if (request->hr_remoteaddr && *request->hr_remoteaddr) lua_env(map->L, "REMOTE_ADDR", request->hr_remoteaddr); /* Pass the headers in a separate table */ lua_newtable(map->L); SIMPLEQ_FOREACH(headp, &request->hr_headers, h_next) lua_env(map->L, headp->h_header, headp->h_value); /* Pass the query variables */ if ((query && *query) || (type && *type && !strcmp(type, FORM))) { lua_newtable(map->L); if (query && *query) lua_decode_query(map->L, query); if (type && *type && !strcmp(type, FORM)) { if (clen && *clen && atol(clen) > 0) { length = atol(clen); content = bozomalloc(httpd, length + 1); n = bozo_read(httpd, STDIN_FILENO, content, length); if (n >= 0) { content[n] = '\0'; lua_decode_query(map->L, content); } else { lua_pop(map->L, 1); lua_pushnil(map->L); } free(content); } } } else lua_pushnil(map->L); ret = lua_pcall(map->L, 3, 0, 0); if (ret) printf("
Lua error: %s\n", lua_tostring(map->L, -1)); bozo_flush(httpd, stdout); rv = 1; goto out; } } out: free(prefix); free(uri); free(info); free(query); free(file); return rv; } #endif /* NO_LUA_SUPPORT */