xref: /minix/libexec/httpd/lua-bozo.c (revision 9f81acbc)
1 /*	$NetBSD: lua-bozo.c,v 1.12 2015/07/04 22:39:23 christos 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 
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 	info = NULL;
315 	query = NULL;
316 	prefix = NULL;
317 	uri = request->hr_oldfile ? request->hr_oldfile : request->hr_file;
318 
319 	if (*uri == '/') {
320 		file = bozostrdup(httpd, uri);
321 		if (file == NULL)
322 			goto out;
323 		prefix = bozostrdup(httpd, &uri[1]);
324 	} else {
325 		if (asprintf(&file, "/%s", uri) < 0)
326 			goto out;
327 		prefix = bozostrdup(httpd, uri);
328 	}
329 	if (prefix == NULL)
330 		goto out;
331 
332 	if (request->hr_query && request->hr_query[0])
333 		query = bozostrdup(httpd, request->hr_query);
334 
335 	p = strchr(prefix, '/');
336 	if (p == NULL)
337 		goto out;
338 	*p++ = '\0';
339 	handler = p;
340 	if (!*handler)
341 		goto out;
342 	p = strchr(handler, '/');
343 	if (p != NULL)
344 		*p++ = '\0';
345 
346 	command = file + 1;
347 	if ((s = strchr(command, '/')) != NULL) {
348 		info = bozostrdup(httpd, s);
349 		*s = '\0';
350 	}
351 
352 	type = request->hr_content_type;
353 	clen = request->hr_content_length;
354 
355 	SIMPLEQ_FOREACH(map, &httpd->lua_states, s_next) {
356 		if (strcmp(map->prefix, prefix))
357 			continue;
358 
359 		SIMPLEQ_FOREACH(hndlr, &map->handlers, h_next) {
360 			if (strcmp(hndlr->name, handler))
361 				continue;
362 
363 			lua_rawgeti(map->L, LUA_REGISTRYINDEX, hndlr->ref);
364 
365 			/* Create the "environment" */
366 			lua_newtable(map->L);
367 			lua_env(map->L, "SERVER_NAME",
368 			    BOZOHOST(httpd, request));
369 			lua_env(map->L, "GATEWAY_INTERFACE", "Luigi/1.0");
370 			lua_env(map->L, "SERVER_PROTOCOL", request->hr_proto);
371 			lua_env(map->L, "REQUEST_METHOD",
372 			    request->hr_methodstr);
373 			lua_env(map->L, "SCRIPT_PREFIX", map->prefix);
374 			lua_env(map->L, "SCRIPT_NAME", file);
375 			lua_env(map->L, "HANDLER_NAME", hndlr->name);
376 			lua_env(map->L, "SCRIPT_FILENAME", map->script);
377 			lua_env(map->L, "SERVER_SOFTWARE",
378 			    httpd->server_software);
379 			lua_env(map->L, "REQUEST_URI", uri);
380 			lua_env(map->L, "DATE_GMT",
381 			    bozo_http_date(date, sizeof(date)));
382 			if (query && *query)
383 				lua_env(map->L, "QUERY_STRING", query);
384 			if (info && *info)
385 				lua_env(map->L, "PATH_INFO", info);
386 			if (type && *type)
387 				lua_env(map->L, "CONTENT_TYPE", type);
388 			if (clen && *clen)
389 				lua_env(map->L, "CONTENT_LENGTH", clen);
390 			if (request->hr_serverport && *request->hr_serverport)
391 				lua_env(map->L, "SERVER_PORT",
392 				    request->hr_serverport);
393 			if (request->hr_remotehost && *request->hr_remotehost)
394 				lua_env(map->L, "REMOTE_HOST",
395 				    request->hr_remotehost);
396 			if (request->hr_remoteaddr && *request->hr_remoteaddr)
397 				lua_env(map->L, "REMOTE_ADDR",
398 				    request->hr_remoteaddr);
399 
400 			/* Pass the headers in a separate table */
401 			lua_newtable(map->L);
402 			SIMPLEQ_FOREACH(headp, &request->hr_headers, h_next)
403 				lua_env(map->L, headp->h_header,
404 				    headp->h_value);
405 
406 			/* Pass the query variables */
407 			if ((query && *query) ||
408 			    (type && *type && !strcmp(type, FORM))) {
409 				lua_newtable(map->L);
410 				if (query && *query)
411 					lua_decode_query(map->L, query);
412 				if (type && *type && !strcmp(type, FORM)) {
413 					if (clen && *clen && atol(clen) > 0) {
414 						length = atol(clen);
415 						content = bozomalloc(httpd,
416 						    length + 1);
417 						n = bozo_read(httpd,
418 						    STDIN_FILENO, content,
419 						    length);
420 						if (n >= 0) {
421 							content[n] = '\0';
422 							lua_decode_query(map->L,
423 							    content);
424 						} else {
425 							lua_pop(map->L, 1);
426 							lua_pushnil(map->L);
427 						}
428 						free(content);
429 					}
430 				}
431 			} else
432 				lua_pushnil(map->L);
433 
434 			ret = lua_pcall(map->L, 3, 0, 0);
435 			if (ret)
436 				printf("<br>Lua error: %s\n",
437 				    lua_tostring(map->L, -1));
438 			bozo_flush(httpd, stdout);
439 			rv = 1;
440 			goto out;
441 		}
442 	}
443 out:
444 	free(prefix);
445 	free(uri);
446 	free(info);
447 	free(query);
448 	free(file);
449 	return rv;
450 }
451 
452 #endif /* NO_LUA_SUPPORT */
453