/* $NetBSD: lua-bozo.c,v 1.9 2014/01/02 08:21:38 mrg 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 int lua_flush(lua_State *L) { bozohttpd_t *httpd; lua_pushstring(L, "bozohttpd"); lua_gettable(L, LUA_REGISTRYINDEX); httpd = lua_touserdata(L, -1); lua_pop(L, 1); bozo_flush(httpd, stdout); return 0; } static int lua_print(lua_State *L) { bozohttpd_t *httpd; lua_pushstring(L, "bozohttpd"); lua_gettable(L, LUA_REGISTRYINDEX); httpd = lua_touserdata(L, -1); lua_pop(L, 1); bozo_printf(httpd, "%s\r\n", lua_tostring(L, -1)); return 0; } static int lua_read(lua_State *L) { bozohttpd_t *httpd; int n, len; char *data; lua_pushstring(L, "bozohttpd"); lua_gettable(L, LUA_REGISTRYINDEX); httpd = lua_touserdata(L, -1); lua_pop(L, 1); len = luaL_checkinteger(L, -1); data = bozomalloc(httpd, len + 1); n = bozo_read(httpd, STDIN_FILENO, data, len); if (n >= 0) { data[n] = '\0'; lua_pushstring(L, data); } else lua_pushnil(L); free(data); return 1; } static int lua_register_handler(lua_State *L) { lua_state_map_t *map; lua_handler_t *handler; bozohttpd_t *httpd; lua_pushstring(L, "lua_state_map"); lua_gettable(L, LUA_REGISTRYINDEX); map = lua_touserdata(L, -1); lua_pushstring(L, "bozohttpd"); lua_gettable(L, LUA_REGISTRYINDEX); httpd = lua_touserdata(L, -1); lua_pop(L, 2); luaL_checkstring(L, 1); luaL_checktype(L, 2, LUA_TFUNCTION); handler = bozomalloc(httpd, sizeof(lua_handler_t)); handler->name = bozostrdup(httpd, lua_tostring(L, 1)); handler->ref = luaL_ref(L, LUA_REGISTRYINDEX); SIMPLEQ_INSERT_TAIL(&map->handlers, handler, h_next); httpd->process_lua = 1; return 0; } static int lua_write(lua_State *L) { bozohttpd_t *httpd; const char *data; lua_pushstring(L, "bozohttpd"); lua_gettable(L, LUA_REGISTRYINDEX); httpd = lua_touserdata(L, -1); lua_pop(L, 1); data = luaL_checkstring(L, -1); lua_pushinteger(L, bozo_write(httpd, STDIN_FILENO, data, strlen(data))); return 1; } static int luaopen_httpd(lua_State *L) { 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, prefix); if (*script == '/') map->script = bozostrdup(httpd, script); else { char cwd[MAXPATHLEN], *path; getcwd(cwd, sizeof(cwd) - 1); asprintf(&path, "%s/%s", cwd, script); map->script = path; } map->L = luaL_newstate(); if (map->L == NULL) bozo_err(httpd, 1, "can't create Lua state"); SIMPLEQ_INIT(&map->handlers); #if LUA_VERSION_NUM >= 502 luaL_openlibs(map->L); lua_getglobal(L, "package"); lua_getfield(L, -1, "preload"); lua_pushcfunction(L, luaopen_httpd); lua_setfield(L, -2, "httpd"); lua_pop(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)) bozo_err(httpd, 1, "failed to load script %s: %s", script, lua_tostring(map->L, -1)); if (lua_pcall(map->L, 0, 0, 0)) bozo_err(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; } } 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; uri = request->hr_oldfile ? request->hr_oldfile : request->hr_file; if (*uri == '/') { file = bozostrdup(httpd, uri); prefix = bozostrdup(httpd, &uri[1]); } else { prefix = bozostrdup(httpd, uri); asprintf(&file, "/%s", uri); } if (file == NULL) { free(prefix); return 0; } if (request->hr_query && strlen(request->hr_query)) query = bozostrdup(httpd, request->hr_query); else query = NULL; p = strchr(prefix, '/'); if (p == NULL){ free(prefix); return 0; } *p++ = '\0'; handler = p; if (!*handler) { free(prefix); return 0; } p = strchr(handler, '/'); if (p != NULL) *p++ = '\0'; info = NULL; command = file + 1; if ((s = strchr(command, '/')) != NULL) { info = bozostrdup(httpd, 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 */