1/*	$NetBSD: lua-bozo.c,v 1.9 2014/01/02 08:21:38 mrg Exp $	*/
2
3/*
4 * Copyright (c) 2013 Marc Balmer <marc@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
54static int
55lua_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
68static int
69lua_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
82static int
83lua_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
106static int
107lua_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
133static int
134lua_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
149static int
150luaopen_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
171static void
172lua_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 */
181void
182bozo_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(L, "package");
205	lua_getfield(L, -1, "preload");
206	lua_pushcfunction(L, luaopen_httpd);
207	lua_setfield(L, -2, "httpd");
208	lua_pop(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
236static void
237lua_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 */
244static void
245lua_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	lua_pushstring(L, val);
280	lua_setfield(L, -2, s);
281	free(val);
282}
283
284static void
285lua_decode_query(lua_State *L, char *query)
286{
287	char *s;
288
289	s = strtok(query, "&");
290	while (s) {
291		lua_url_decode(L, s);
292		s = strtok(NULL, "&");
293	}
294}
295
296int
297bozo_process_lua(bozo_httpreq_t *request)
298{
299	bozohttpd_t *httpd = request->hr_httpd;
300	lua_state_map_t *map;
301	lua_handler_t *hndlr;
302	int n, ret, length;
303	char date[40];
304	bozoheaders_t *headp;
305	char *s, *query, *uri, *file, *command, *info, *content;
306	const char *type, *clen;
307	char *prefix, *handler, *p;
308	int rv = 0;
309
310	if (!httpd->process_lua)
311		return 0;
312
313	uri = request->hr_oldfile ? request->hr_oldfile : request->hr_file;
314
315	if (*uri == '/') {
316		file = bozostrdup(httpd, uri);
317		prefix = bozostrdup(httpd, &uri[1]);
318	} else {
319		prefix = bozostrdup(httpd, uri);
320		asprintf(&file, "/%s", uri);
321	}
322	if (file == NULL) {
323		free(prefix);
324		return 0;
325	}
326
327	if (request->hr_query && strlen(request->hr_query))
328		query = bozostrdup(httpd, request->hr_query);
329	else
330		query = NULL;
331
332	p = strchr(prefix, '/');
333	if (p == NULL){
334		free(prefix);
335		return 0;
336	}
337	*p++ = '\0';
338	handler = p;
339	if (!*handler) {
340		free(prefix);
341		return 0;
342	}
343	p = strchr(handler, '/');
344	if (p != NULL)
345		*p++ = '\0';
346
347	info = NULL;
348	command = file + 1;
349	if ((s = strchr(command, '/')) != NULL) {
350		info = bozostrdup(httpd, s);
351		*s = '\0';
352	}
353
354	type = request->hr_content_type;
355	clen = request->hr_content_length;
356
357	SIMPLEQ_FOREACH(map, &httpd->lua_states, s_next) {
358		if (strcmp(map->prefix, prefix))
359			continue;
360
361		SIMPLEQ_FOREACH(hndlr, &map->handlers, h_next) {
362			if (strcmp(hndlr->name, handler))
363				continue;
364
365			lua_rawgeti(map->L, LUA_REGISTRYINDEX, hndlr->ref);
366
367			/* Create the "environment" */
368			lua_newtable(map->L);
369			lua_env(map->L, "SERVER_NAME",
370			    BOZOHOST(httpd, request));
371			lua_env(map->L, "GATEWAY_INTERFACE", "Luigi/1.0");
372			lua_env(map->L, "SERVER_PROTOCOL", request->hr_proto);
373			lua_env(map->L, "REQUEST_METHOD",
374			    request->hr_methodstr);
375			lua_env(map->L, "SCRIPT_PREFIX", map->prefix);
376			lua_env(map->L, "SCRIPT_NAME", file);
377			lua_env(map->L, "HANDLER_NAME", hndlr->name);
378			lua_env(map->L, "SCRIPT_FILENAME", map->script);
379			lua_env(map->L, "SERVER_SOFTWARE",
380			    httpd->server_software);
381			lua_env(map->L, "REQUEST_URI", uri);
382			lua_env(map->L, "DATE_GMT",
383			    bozo_http_date(date, sizeof(date)));
384			if (query && *query)
385				lua_env(map->L, "QUERY_STRING", query);
386			if (info && *info)
387				lua_env(map->L, "PATH_INFO", info);
388			if (type && *type)
389				lua_env(map->L, "CONTENT_TYPE", type);
390			if (clen && *clen)
391				lua_env(map->L, "CONTENT_LENGTH", clen);
392			if (request->hr_serverport && *request->hr_serverport)
393				lua_env(map->L, "SERVER_PORT",
394				    request->hr_serverport);
395			if (request->hr_remotehost && *request->hr_remotehost)
396				lua_env(map->L, "REMOTE_HOST",
397				    request->hr_remotehost);
398			if (request->hr_remoteaddr && *request->hr_remoteaddr)
399				lua_env(map->L, "REMOTE_ADDR",
400				    request->hr_remoteaddr);
401
402			/* Pass the headers in a separate table */
403			lua_newtable(map->L);
404			SIMPLEQ_FOREACH(headp, &request->hr_headers, h_next)
405				lua_env(map->L, headp->h_header,
406				    headp->h_value);
407
408			/* Pass the query variables */
409			if ((query && *query) ||
410			    (type && *type && !strcmp(type, FORM))) {
411				lua_newtable(map->L);
412				if (query && *query)
413					lua_decode_query(map->L, query);
414				if (type && *type && !strcmp(type, FORM)) {
415					if (clen && *clen && atol(clen) > 0) {
416						length = atol(clen);
417						content = bozomalloc(httpd,
418						    length + 1);
419						n = bozo_read(httpd,
420						    STDIN_FILENO, content,
421						    length);
422						if (n >= 0) {
423							content[n] = '\0';
424							lua_decode_query(map->L,
425							    content);
426						} else {
427							lua_pop(map->L, 1);
428							lua_pushnil(map->L);
429						}
430						free(content);
431					}
432				}
433			} else
434				lua_pushnil(map->L);
435
436			ret = lua_pcall(map->L, 3, 0, 0);
437			if (ret)
438				printf("<br>Lua error: %s\n",
439				    lua_tostring(map->L, -1));
440			bozo_flush(httpd, stdout);
441			rv = 1;
442			goto out;
443		}
444	}
445out:
446	free(prefix);
447	free(uri);
448	free(info);
449	free(query);
450	free(file);
451	return rv;
452}
453
454#endif /* NO_LUA_SUPPORT */
455