xref: /netbsd-src/libexec/httpd/lua-bozo.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
1 /*	$NetBSD: lua-bozo.c,v 1.8 2013/11/19 07:51:56 mbalmer 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 <lua.h>
36 #include <lauxlib.h>
37 #include <lualib.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 
42 #include "bozohttpd.h"
43 
44 /* Lua binding for bozohttp */
45 
46 #if LUA_VERSION_NUM < 502
47 #define LUA_HTTPDLIBNAME "httpd"
48 #endif
49 
50 #define FORM	"application/x-www-form-urlencoded"
51 
52 static int
53 lua_flush(lua_State *L)
54 {
55 	bozohttpd_t *httpd;
56 
57 	lua_pushstring(L, "bozohttpd");
58 	lua_gettable(L, LUA_REGISTRYINDEX);
59 	httpd = lua_touserdata(L, -1);
60 	lua_pop(L, 1);
61 
62 	bozo_flush(httpd, stdout);
63 	return 0;
64 }
65 
66 static int
67 lua_print(lua_State *L)
68 {
69 	bozohttpd_t *httpd;
70 
71 	lua_pushstring(L, "bozohttpd");
72 	lua_gettable(L, LUA_REGISTRYINDEX);
73 	httpd = lua_touserdata(L, -1);
74 	lua_pop(L, 1);
75 
76 	bozo_printf(httpd, "%s\r\n", lua_tostring(L, -1));
77 	return 0;
78 }
79 
80 static int
81 lua_read(lua_State *L)
82 {
83 	bozohttpd_t *httpd;
84 	int n, len;
85 	char *data;
86 
87 	lua_pushstring(L, "bozohttpd");
88 	lua_gettable(L, LUA_REGISTRYINDEX);
89 	httpd = lua_touserdata(L, -1);
90 	lua_pop(L, 1);
91 
92 	len = luaL_checkinteger(L, -1);
93 	data = bozomalloc(httpd, len + 1);
94 	n = bozo_read(httpd, STDIN_FILENO, data, len);
95 	if (n >= 0) {
96 		data[n] = '\0';
97 		lua_pushstring(L, data);
98 	} else
99 		lua_pushnil(L);
100 	free(data);
101 	return 1;
102 }
103 
104 static int
105 lua_register_handler(lua_State *L)
106 {
107 	lua_state_map_t *map;
108 	lua_handler_t *handler;
109 	bozohttpd_t *httpd;
110 
111 	lua_pushstring(L, "lua_state_map");
112 	lua_gettable(L, LUA_REGISTRYINDEX);
113 	map = lua_touserdata(L, -1);
114 	lua_pushstring(L, "bozohttpd");
115 	lua_gettable(L, LUA_REGISTRYINDEX);
116 	httpd = lua_touserdata(L, -1);
117 	lua_pop(L, 2);
118 
119 	luaL_checkstring(L, 1);
120 	luaL_checktype(L, 2, LUA_TFUNCTION);
121 
122 	handler = bozomalloc(httpd, sizeof(lua_handler_t));
123 
124 	handler->name = bozostrdup(httpd, lua_tostring(L, 1));
125 	handler->ref = luaL_ref(L, LUA_REGISTRYINDEX);
126 	SIMPLEQ_INSERT_TAIL(&map->handlers, handler, h_next);
127 	httpd->process_lua = 1;
128 	return 0;
129 }
130 
131 static int
132 lua_write(lua_State *L)
133 {
134 	bozohttpd_t *httpd;
135 	const char *data;
136 
137 	lua_pushstring(L, "bozohttpd");
138 	lua_gettable(L, LUA_REGISTRYINDEX);
139 	httpd = lua_touserdata(L, -1);
140 	lua_pop(L, 1);
141 
142 	data = luaL_checkstring(L, -1);
143 	lua_pushinteger(L, bozo_write(httpd, STDIN_FILENO, data, strlen(data)));
144 	return 1;
145 }
146 
147 static int
148 luaopen_httpd(lua_State *L)
149 {
150 	struct luaL_Reg functions[] = {
151 		{ "flush",		lua_flush },
152 		{ "print",		lua_print },
153 		{ "read",		lua_read },
154 		{ "register_handler",	lua_register_handler },
155 		{ "write",		lua_write },
156 		{ NULL, NULL }
157 	};
158 #if LUA_VERSION_NUM >= 502
159 	luaL_newlib(L, functions);
160 #else
161 	luaL_register(L, LUA_HTTPDLIBNAME, functions);
162 #endif
163 	lua_pushstring(L, "httpd 1.0.0");
164 	lua_setfield(L, -2, "_VERSION");
165 	return 1;
166 }
167 
168 #if LUA_VERSION_NUM < 502
169 static void
170 lua_openlib(lua_State *L, const char *name, lua_CFunction fn)
171 {
172 	lua_pushcfunction(L, fn);
173 	lua_pushstring(L, name);
174 	lua_call(L, 1, 0);
175 }
176 #endif
177 
178 /* bozohttpd integration */
179 void
180 bozo_add_lua_map(bozohttpd_t *httpd, const char *prefix, const char *script)
181 {
182 	lua_state_map_t *map;
183 	char *cwd, *path;
184 
185 	map = bozomalloc(httpd, sizeof(lua_state_map_t));
186 	map->prefix = bozostrdup(httpd, prefix);
187 	if (*script == '/')
188 		map->script = bozostrdup(httpd, script);
189 	else {
190 		cwd = getwd(NULL);
191 		asprintf(&path, "%s/%s", cwd, script);
192 		map->script = path;
193 		free(cwd);
194 	}
195 	map->L = luaL_newstate();
196 	if (map->L == NULL)
197 		bozo_err(httpd, 1, "can't create Lua state");
198 	SIMPLEQ_INIT(&map->handlers);
199 
200 #if LUA_VERSION_NUM >= 502
201 	luaL_openlibs(map->L);
202 	lua_getglobal(L, "package");
203 	lua_getfield(L, -1, "preload");
204 	lua_pushcfunction(L, luaopen_httpd);
205 	lua_setfield(L, -2, "httpd");
206 	lua_pop(L, 2);
207 #else
208 	lua_openlib(map->L, "", luaopen_base);
209 	lua_openlib(map->L, LUA_LOADLIBNAME, luaopen_package);
210 	lua_openlib(map->L, LUA_TABLIBNAME, luaopen_table);
211 	lua_openlib(map->L, LUA_STRLIBNAME, luaopen_string);
212 	lua_openlib(map->L, LUA_MATHLIBNAME, luaopen_math);
213 	lua_openlib(map->L, LUA_OSLIBNAME, luaopen_os);
214 	lua_openlib(map->L, LUA_IOLIBNAME, luaopen_io);
215 	lua_openlib(map->L, LUA_HTTPDLIBNAME, luaopen_httpd);
216 #endif
217 	lua_pushstring(map->L, "lua_state_map");
218 	lua_pushlightuserdata(map->L, map);
219 	lua_settable(map->L, LUA_REGISTRYINDEX);
220 
221 	lua_pushstring(map->L, "bozohttpd");
222 	lua_pushlightuserdata(map->L, httpd);
223 	lua_settable(map->L, LUA_REGISTRYINDEX);
224 
225 	if (luaL_loadfile(map->L, script))
226 		bozo_err(httpd, 1, "failed to load script %s: %s", script,
227 		    lua_tostring(map->L, -1));
228 	if (lua_pcall(map->L, 0, 0, 0))
229 		bozo_err(httpd, 1, "failed to execute script %s: %s", script,
230 		    lua_tostring(map->L, -1));
231 	SIMPLEQ_INSERT_TAIL(&httpd->lua_states, map, s_next);
232 }
233 
234 static void
235 lua_env(lua_State *L, const char *name, const char *value)
236 {
237 	lua_pushstring(L, value);
238 	lua_setfield(L, -2, name);
239 }
240 
241 /* decode query string */
242 static void
243 lua_url_decode(lua_State *L, char *s)
244 {
245 	char *v, *p, *val, *q;
246 	char buf[3];
247 	int c;
248 
249 	v = strchr(s, '=');
250 	if (v == NULL)
251 		return;
252 	*v++ = '\0';
253 	val = malloc(strlen(v) + 1);
254 	if (val == NULL)
255 		return;
256 
257 	for (p = v, q = val; *p; p++) {
258 		switch (*p) {
259 		case '%':
260 			if (*(p + 1) == '\0' || *(p + 2) == '\0') {
261 				free(val);
262 				return;
263 			}
264 			buf[0] = *++p;
265 			buf[1] = *++p;
266 			buf[2] = '\0';
267 			sscanf(buf, "%2x", &c);
268 			*q++ = (char)c;
269 			break;
270 		case '+':
271 			*q++ = ' ';
272 			break;
273 		default:
274 			*q++ = *p;
275 		}
276 	}
277 	lua_pushstring(L, val);
278 	lua_setfield(L, -2, s);
279 	free(val);
280 }
281 
282 static void
283 lua_decode_query(lua_State *L, char *query)
284 {
285 	char *s;
286 
287 	s = strtok(query, "&");
288 	while (s) {
289 		lua_url_decode(L, s);
290 		s = strtok(NULL, "&");
291 	}
292 }
293 
294 int
295 bozo_process_lua(bozo_httpreq_t *request)
296 {
297 	bozohttpd_t *httpd = request->hr_httpd;
298 	lua_state_map_t *map;
299 	lua_handler_t *hndlr;
300 	int n, ret, length;
301 	char date[40];
302 	bozoheaders_t *headp;
303 	char *s, *query, *uri, *file, *command, *info, *content;
304 	const char *type, *clen;
305 	char *prefix, *handler, *p;
306 	int rv = 0;
307 
308 	if (!httpd->process_lua)
309 		return 0;
310 
311 	uri = request->hr_oldfile ? request->hr_oldfile : request->hr_file;
312 
313 	if (*uri == '/') {
314 		file = bozostrdup(httpd, uri);
315 		prefix = bozostrdup(httpd, &uri[1]);
316 	} else {
317 		prefix = bozostrdup(httpd, uri);
318 		asprintf(&file, "/%s", uri);
319 	}
320 	if (file == NULL) {
321 		free(prefix);
322 		return 0;
323 	}
324 
325 	if (request->hr_query && strlen(request->hr_query))
326 		query = bozostrdup(httpd, request->hr_query);
327 	else
328 		query = NULL;
329 
330 	p = strchr(prefix, '/');
331 	if (p == NULL){
332 		free(prefix);
333 		return 0;
334 	}
335 	*p++ = '\0';
336 	handler = p;
337 	if (!*handler) {
338 		free(prefix);
339 		return 0;
340 	}
341 	p = strchr(handler, '/');
342 	if (p != NULL)
343 		*p++ = '\0';
344 
345 	info = NULL;
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