Home | History | Annotate | Line # | Download | only in httpd
lua-bozo.c revision 1.11
      1 /*	$NetBSD: lua-bozo.c,v 1.10 2014/07/19 18:38:34 lneto Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2013 Marc Balmer <marc (at) msys.ch>
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer and
     14  *    dedication in the documentation and/or other materials provided
     15  *    with the distribution.
     16  *
     17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
     22  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
     24  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     25  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     27  * SUCH DAMAGE.
     28  *
     29  */
     30 
     31 /* this code implements dynamic content generation using Lua for bozohttpd */
     32 
     33 #ifndef NO_LUA_SUPPORT
     34 
     35 #include <sys/param.h>
     36 
     37 #include <lua.h>
     38 #include <lauxlib.h>
     39 #include <lualib.h>
     40 #include <stdlib.h>
     41 #include <string.h>
     42 #include <unistd.h>
     43 
     44 #include "bozohttpd.h"
     45 
     46 /* Lua binding for bozohttp */
     47 
     48 #if LUA_VERSION_NUM < 502
     49 #define LUA_HTTPDLIBNAME "httpd"
     50 #endif
     51 
     52 #define FORM	"application/x-www-form-urlencoded"
     53 
     54 static int
     55 lua_flush(lua_State *L)
     56 {
     57 	bozohttpd_t *httpd;
     58 
     59 	lua_pushstring(L, "bozohttpd");
     60 	lua_gettable(L, LUA_REGISTRYINDEX);
     61 	httpd = lua_touserdata(L, -1);
     62 	lua_pop(L, 1);
     63 
     64 	bozo_flush(httpd, stdout);
     65 	return 0;
     66 }
     67 
     68 static int
     69 lua_print(lua_State *L)
     70 {
     71 	bozohttpd_t *httpd;
     72 
     73 	lua_pushstring(L, "bozohttpd");
     74 	lua_gettable(L, LUA_REGISTRYINDEX);
     75 	httpd = lua_touserdata(L, -1);
     76 	lua_pop(L, 1);
     77 
     78 	bozo_printf(httpd, "%s\r\n", lua_tostring(L, -1));
     79 	return 0;
     80 }
     81 
     82 static int
     83 lua_read(lua_State *L)
     84 {
     85 	bozohttpd_t *httpd;
     86 	int n, len;
     87 	char *data;
     88 
     89 	lua_pushstring(L, "bozohttpd");
     90 	lua_gettable(L, LUA_REGISTRYINDEX);
     91 	httpd = lua_touserdata(L, -1);
     92 	lua_pop(L, 1);
     93 
     94 	len = luaL_checkinteger(L, -1);
     95 	data = bozomalloc(httpd, len + 1);
     96 	n = bozo_read(httpd, STDIN_FILENO, data, len);
     97 	if (n >= 0) {
     98 		data[n] = '\0';
     99 		lua_pushstring(L, data);
    100 	} else
    101 		lua_pushnil(L);
    102 	free(data);
    103 	return 1;
    104 }
    105 
    106 static int
    107 lua_register_handler(lua_State *L)
    108 {
    109 	lua_state_map_t *map;
    110 	lua_handler_t *handler;
    111 	bozohttpd_t *httpd;
    112 
    113 	lua_pushstring(L, "lua_state_map");
    114 	lua_gettable(L, LUA_REGISTRYINDEX);
    115 	map = lua_touserdata(L, -1);
    116 	lua_pushstring(L, "bozohttpd");
    117 	lua_gettable(L, LUA_REGISTRYINDEX);
    118 	httpd = lua_touserdata(L, -1);
    119 	lua_pop(L, 2);
    120 
    121 	luaL_checkstring(L, 1);
    122 	luaL_checktype(L, 2, LUA_TFUNCTION);
    123 
    124 	handler = bozomalloc(httpd, sizeof(lua_handler_t));
    125 
    126 	handler->name = bozostrdup(httpd, lua_tostring(L, 1));
    127 	handler->ref = luaL_ref(L, LUA_REGISTRYINDEX);
    128 	SIMPLEQ_INSERT_TAIL(&map->handlers, handler, h_next);
    129 	httpd->process_lua = 1;
    130 	return 0;
    131 }
    132 
    133 static int
    134 lua_write(lua_State *L)
    135 {
    136 	bozohttpd_t *httpd;
    137 	const char *data;
    138 
    139 	lua_pushstring(L, "bozohttpd");
    140 	lua_gettable(L, LUA_REGISTRYINDEX);
    141 	httpd = lua_touserdata(L, -1);
    142 	lua_pop(L, 1);
    143 
    144 	data = luaL_checkstring(L, -1);
    145 	lua_pushinteger(L, bozo_write(httpd, STDIN_FILENO, data, strlen(data)));
    146 	return 1;
    147 }
    148 
    149 static int
    150 luaopen_httpd(lua_State *L)
    151 {
    152 	struct luaL_Reg functions[] = {
    153 		{ "flush",		lua_flush },
    154 		{ "print",		lua_print },
    155 		{ "read",		lua_read },
    156 		{ "register_handler",	lua_register_handler },
    157 		{ "write",		lua_write },
    158 		{ NULL, NULL }
    159 	};
    160 #if LUA_VERSION_NUM >= 502
    161 	luaL_newlib(L, functions);
    162 #else
    163 	luaL_register(L, LUA_HTTPDLIBNAME, functions);
    164 #endif
    165 	lua_pushstring(L, "httpd 1.0.0");
    166 	lua_setfield(L, -2, "_VERSION");
    167 	return 1;
    168 }
    169 
    170 #if LUA_VERSION_NUM < 502
    171 static void
    172 lua_openlib(lua_State *L, const char *name, lua_CFunction fn)
    173 {
    174 	lua_pushcfunction(L, fn);
    175 	lua_pushstring(L, name);
    176 	lua_call(L, 1, 0);
    177 }
    178 #endif
    179 
    180 /* bozohttpd integration */
    181 void
    182 bozo_add_lua_map(bozohttpd_t *httpd, const char *prefix, const char *script)
    183 {
    184 	lua_state_map_t *map;
    185 
    186 	map = bozomalloc(httpd, sizeof(lua_state_map_t));
    187 	map->prefix = bozostrdup(httpd, prefix);
    188 	if (*script == '/')
    189 		map->script = bozostrdup(httpd, script);
    190 	else {
    191 		char cwd[MAXPATHLEN], *path;
    192 
    193 		getcwd(cwd, sizeof(cwd) - 1);
    194 		asprintf(&path, "%s/%s", cwd, script);
    195 		map->script = path;
    196 	}
    197 	map->L = luaL_newstate();
    198 	if (map->L == NULL)
    199 		bozo_err(httpd, 1, "can't create Lua state");
    200 	SIMPLEQ_INIT(&map->handlers);
    201 
    202 #if LUA_VERSION_NUM >= 502
    203 	luaL_openlibs(map->L);
    204 	lua_getglobal(map->L, "package");
    205 	lua_getfield(map->L, -1, "preload");
    206 	lua_pushcfunction(map->L, luaopen_httpd);
    207 	lua_setfield(map->L, -2, "httpd");
    208 	lua_pop(map->L, 2);
    209 #else
    210 	lua_openlib(map->L, "", luaopen_base);
    211 	lua_openlib(map->L, LUA_LOADLIBNAME, luaopen_package);
    212 	lua_openlib(map->L, LUA_TABLIBNAME, luaopen_table);
    213 	lua_openlib(map->L, LUA_STRLIBNAME, luaopen_string);
    214 	lua_openlib(map->L, LUA_MATHLIBNAME, luaopen_math);
    215 	lua_openlib(map->L, LUA_OSLIBNAME, luaopen_os);
    216 	lua_openlib(map->L, LUA_IOLIBNAME, luaopen_io);
    217 	lua_openlib(map->L, LUA_HTTPDLIBNAME, luaopen_httpd);
    218 #endif
    219 	lua_pushstring(map->L, "lua_state_map");
    220 	lua_pushlightuserdata(map->L, map);
    221 	lua_settable(map->L, LUA_REGISTRYINDEX);
    222 
    223 	lua_pushstring(map->L, "bozohttpd");
    224 	lua_pushlightuserdata(map->L, httpd);
    225 	lua_settable(map->L, LUA_REGISTRYINDEX);
    226 
    227 	if (luaL_loadfile(map->L, script))
    228 		bozo_err(httpd, 1, "failed to load script %s: %s", script,
    229 		    lua_tostring(map->L, -1));
    230 	if (lua_pcall(map->L, 0, 0, 0))
    231 		bozo_err(httpd, 1, "failed to execute script %s: %s", script,
    232 		    lua_tostring(map->L, -1));
    233 	SIMPLEQ_INSERT_TAIL(&httpd->lua_states, map, s_next);
    234 }
    235 
    236 static void
    237 lua_env(lua_State *L, const char *name, const char *value)
    238 {
    239 	lua_pushstring(L, value);
    240 	lua_setfield(L, -2, name);
    241 }
    242 
    243 /* decode query string */
    244 static void
    245 lua_url_decode(lua_State *L, char *s)
    246 {
    247 	char *v, *p, *val, *q;
    248 	char buf[3];
    249 	int c;
    250 
    251 	v = strchr(s, '=');
    252 	if (v == NULL)
    253 		return;
    254 	*v++ = '\0';
    255 	val = malloc(strlen(v) + 1);
    256 	if (val == NULL)
    257 		return;
    258 
    259 	for (p = v, q = val; *p; p++) {
    260 		switch (*p) {
    261 		case '%':
    262 			if (*(p + 1) == '\0' || *(p + 2) == '\0') {
    263 				free(val);
    264 				return;
    265 			}
    266 			buf[0] = *++p;
    267 			buf[1] = *++p;
    268 			buf[2] = '\0';
    269 			sscanf(buf, "%2x", &c);
    270 			*q++ = (char)c;
    271 			break;
    272 		case '+':
    273 			*q++ = ' ';
    274 			break;
    275 		default:
    276 			*q++ = *p;
    277 		}
    278 	}
    279 	*q = '\0';
    280 	lua_pushstring(L, val);
    281 	lua_setfield(L, -2, s);
    282 	free(val);
    283 }
    284 
    285 static void
    286 lua_decode_query(lua_State *L, char *query)
    287 {
    288 	char *s;
    289 
    290 	s = strtok(query, "&");
    291 	while (s) {
    292 		lua_url_decode(L, s);
    293 		s = strtok(NULL, "&");
    294 	}
    295 }
    296 
    297 int
    298 bozo_process_lua(bozo_httpreq_t *request)
    299 {
    300 	bozohttpd_t *httpd = request->hr_httpd;
    301 	lua_state_map_t *map;
    302 	lua_handler_t *hndlr;
    303 	int n, ret, length;
    304 	char date[40];
    305 	bozoheaders_t *headp;
    306 	char *s, *query, *uri, *file, *command, *info, *content;
    307 	const char *type, *clen;
    308 	char *prefix, *handler, *p;
    309 	int rv = 0;
    310 
    311 	if (!httpd->process_lua)
    312 		return 0;
    313 
    314 	uri = request->hr_oldfile ? request->hr_oldfile : request->hr_file;
    315 
    316 	if (*uri == '/') {
    317 		file = bozostrdup(httpd, uri);
    318 		prefix = bozostrdup(httpd, &uri[1]);
    319 	} else {
    320 		prefix = bozostrdup(httpd, uri);
    321 		asprintf(&file, "/%s", uri);
    322 	}
    323 	if (file == NULL) {
    324 		free(prefix);
    325 		return 0;
    326 	}
    327 
    328 	if (request->hr_query && strlen(request->hr_query))
    329 		query = bozostrdup(httpd, request->hr_query);
    330 	else
    331 		query = NULL;
    332 
    333 	p = strchr(prefix, '/');
    334 	if (p == NULL){
    335 		free(prefix);
    336 		return 0;
    337 	}
    338 	*p++ = '\0';
    339 	handler = p;
    340 	if (!*handler) {
    341 		free(prefix);
    342 		return 0;
    343 	}
    344 	p = strchr(handler, '/');
    345 	if (p != NULL)
    346 		*p++ = '\0';
    347 
    348 	info = NULL;
    349 	command = file + 1;
    350 	if ((s = strchr(command, '/')) != NULL) {
    351 		info = bozostrdup(httpd, s);
    352 		*s = '\0';
    353 	}
    354 
    355 	type = request->hr_content_type;
    356 	clen = request->hr_content_length;
    357 
    358 	SIMPLEQ_FOREACH(map, &httpd->lua_states, s_next) {
    359 		if (strcmp(map->prefix, prefix))
    360 			continue;
    361 
    362 		SIMPLEQ_FOREACH(hndlr, &map->handlers, h_next) {
    363 			if (strcmp(hndlr->name, handler))
    364 				continue;
    365 
    366 			lua_rawgeti(map->L, LUA_REGISTRYINDEX, hndlr->ref);
    367 
    368 			/* Create the "environment" */
    369 			lua_newtable(map->L);
    370 			lua_env(map->L, "SERVER_NAME",
    371 			    BOZOHOST(httpd, request));
    372 			lua_env(map->L, "GATEWAY_INTERFACE", "Luigi/1.0");
    373 			lua_env(map->L, "SERVER_PROTOCOL", request->hr_proto);
    374 			lua_env(map->L, "REQUEST_METHOD",
    375 			    request->hr_methodstr);
    376 			lua_env(map->L, "SCRIPT_PREFIX", map->prefix);
    377 			lua_env(map->L, "SCRIPT_NAME", file);
    378 			lua_env(map->L, "HANDLER_NAME", hndlr->name);
    379 			lua_env(map->L, "SCRIPT_FILENAME", map->script);
    380 			lua_env(map->L, "SERVER_SOFTWARE",
    381 			    httpd->server_software);
    382 			lua_env(map->L, "REQUEST_URI", uri);
    383 			lua_env(map->L, "DATE_GMT",
    384 			    bozo_http_date(date, sizeof(date)));
    385 			if (query && *query)
    386 				lua_env(map->L, "QUERY_STRING", query);
    387 			if (info && *info)
    388 				lua_env(map->L, "PATH_INFO", info);
    389 			if (type && *type)
    390 				lua_env(map->L, "CONTENT_TYPE", type);
    391 			if (clen && *clen)
    392 				lua_env(map->L, "CONTENT_LENGTH", clen);
    393 			if (request->hr_serverport && *request->hr_serverport)
    394 				lua_env(map->L, "SERVER_PORT",
    395 				    request->hr_serverport);
    396 			if (request->hr_remotehost && *request->hr_remotehost)
    397 				lua_env(map->L, "REMOTE_HOST",
    398 				    request->hr_remotehost);
    399 			if (request->hr_remoteaddr && *request->hr_remoteaddr)
    400 				lua_env(map->L, "REMOTE_ADDR",
    401 				    request->hr_remoteaddr);
    402 
    403 			/* Pass the headers in a separate table */
    404 			lua_newtable(map->L);
    405 			SIMPLEQ_FOREACH(headp, &request->hr_headers, h_next)
    406 				lua_env(map->L, headp->h_header,
    407 				    headp->h_value);
    408 
    409 			/* Pass the query variables */
    410 			if ((query && *query) ||
    411 			    (type && *type && !strcmp(type, FORM))) {
    412 				lua_newtable(map->L);
    413 				if (query && *query)
    414 					lua_decode_query(map->L, query);
    415 				if (type && *type && !strcmp(type, FORM)) {
    416 					if (clen && *clen && atol(clen) > 0) {
    417 						length = atol(clen);
    418 						content = bozomalloc(httpd,
    419 						    length + 1);
    420 						n = bozo_read(httpd,
    421 						    STDIN_FILENO, content,
    422 						    length);
    423 						if (n >= 0) {
    424 							content[n] = '\0';
    425 							lua_decode_query(map->L,
    426 							    content);
    427 						} else {
    428 							lua_pop(map->L, 1);
    429 							lua_pushnil(map->L);
    430 						}
    431 						free(content);
    432 					}
    433 				}
    434 			} else
    435 				lua_pushnil(map->L);
    436 
    437 			ret = lua_pcall(map->L, 3, 0, 0);
    438 			if (ret)
    439 				printf("<br>Lua error: %s\n",
    440 				    lua_tostring(map->L, -1));
    441 			bozo_flush(httpd, stdout);
    442 			rv = 1;
    443 			goto out;
    444 		}
    445 	}
    446 out:
    447 	free(prefix);
    448 	free(uri);
    449 	free(info);
    450 	free(query);
    451 	free(file);
    452 	return rv;
    453 }
    454 
    455 #endif /* NO_LUA_SUPPORT */
    456