1 /* $NetBSD: lua-bozo.c,v 1.15 2017/05/28 22:37:36 alnsn 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 bozohttpd_t *
httpd_instance(lua_State * L)55 httpd_instance(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 return httpd;
65 }
66
67 static int
lua_flush(lua_State * L)68 lua_flush(lua_State *L)
69 {
70 bozohttpd_t *httpd = httpd_instance(L);
71
72 bozo_flush(httpd, stdout);
73 return 0;
74 }
75
76 static int
lua_print(lua_State * L)77 lua_print(lua_State *L)
78 {
79 bozohttpd_t *httpd = httpd_instance(L);
80
81 bozo_printf(httpd, "%s\r\n", lua_tostring(L, 1));
82 return 0;
83 }
84
85 static int
lua_read(lua_State * L)86 lua_read(lua_State *L)
87 {
88 bozohttpd_t *httpd = httpd_instance(L);
89 luaL_Buffer lbuf;
90 char *data;
91 lua_Integer len;
92 ssize_t n;
93
94 len = luaL_checkinteger(L, 1);
95 data = luaL_buffinitsize(L, &lbuf, (size_t)len);
96
97 if ((n = bozo_read(httpd, STDIN_FILENO, data, len)) >= 0) {
98 luaL_pushresultsize(&lbuf, n);
99 return 1;
100 } else {
101 lua_pushnil(L);
102 lua_pushstring(L, "bozo_read() call failed");
103 return 2;
104 }
105 }
106
107 static int
lua_register_handler(lua_State * L)108 lua_register_handler(lua_State *L)
109 {
110 bozohttpd_t *httpd = httpd_instance(L);
111 lua_state_map_t *map;
112 lua_handler_t *handler;
113 const char *name;
114 int ref;
115
116 lua_pushstring(L, "lua_state_map");
117 lua_gettable(L, LUA_REGISTRYINDEX);
118 map = lua_touserdata(L, -1);
119 lua_pop(L, 1);
120
121 name = luaL_checkstring(L, 1);
122
123 luaL_checktype(L, 2, LUA_TFUNCTION);
124 lua_pushvalue(L, 2);
125 ref = luaL_ref(L, LUA_REGISTRYINDEX);
126
127 handler = bozomalloc(httpd, sizeof(lua_handler_t));
128 handler->name = bozostrdup(httpd, NULL, name);
129 handler->ref = ref;
130 SIMPLEQ_INSERT_TAIL(&map->handlers, handler, h_next);
131 httpd->process_lua = 1;
132 return 0;
133 }
134
135 static int
lua_write(lua_State * L)136 lua_write(lua_State *L)
137 {
138 bozohttpd_t *httpd = httpd_instance(L);
139 const char *data;
140 size_t len;
141 ssize_t n;
142
143 data = luaL_checklstring(L, 1, &len);
144 if ((n = bozo_write(httpd, STDIN_FILENO, data, len)) >= 0) {
145 lua_pushinteger(L, n);
146 return 1;
147 } else {
148 lua_pushnil(L);
149 lua_pushstring(L, "bozo_write() call failed");
150 return 2;
151 }
152 }
153
154 static int
luaopen_httpd(lua_State * L)155 luaopen_httpd(lua_State *L)
156 {
157 static struct luaL_Reg functions[] = {
158 { "flush", lua_flush },
159 { "print", lua_print },
160 { "read", lua_read },
161 { "register_handler", lua_register_handler },
162 { "write", lua_write },
163 { NULL, NULL }
164 };
165 #if LUA_VERSION_NUM >= 502
166 luaL_newlib(L, functions);
167 #else
168 luaL_register(L, LUA_HTTPDLIBNAME, functions);
169 #endif
170 lua_pushstring(L, "httpd 1.0.0");
171 lua_setfield(L, -2, "_VERSION");
172 return 1;
173 }
174
175 #if LUA_VERSION_NUM < 502
176 static void
lua_openlib(lua_State * L,const char * name,lua_CFunction fn)177 lua_openlib(lua_State *L, const char *name, lua_CFunction fn)
178 {
179 lua_pushcfunction(L, fn);
180 lua_pushstring(L, name);
181 lua_call(L, 1, 0);
182 }
183 #endif
184
185 /* bozohttpd integration */
186 void
bozo_add_lua_map(bozohttpd_t * httpd,const char * prefix,const char * script)187 bozo_add_lua_map(bozohttpd_t *httpd, const char *prefix, const char *script)
188 {
189 lua_state_map_t *map;
190
191 map = bozomalloc(httpd, sizeof(lua_state_map_t));
192 map->prefix = bozostrdup(httpd, NULL, prefix);
193 if (*script == '/')
194 map->script = bozostrdup(httpd, NULL, script);
195 else {
196 char cwd[MAXPATHLEN], *path;
197
198 getcwd(cwd, sizeof(cwd) - 1);
199 bozoasprintf(httpd, &path, "%s/%s", cwd, script);
200 map->script = path;
201 }
202 map->L = luaL_newstate();
203 if (map->L == NULL)
204 bozoerr(httpd, 1, "can't create Lua state");
205 SIMPLEQ_INIT(&map->handlers);
206
207 #if LUA_VERSION_NUM >= 502
208 luaL_openlibs(map->L);
209 lua_getglobal(map->L, "package");
210 lua_getfield(map->L, -1, "preload");
211 lua_pushcfunction(map->L, luaopen_httpd);
212 lua_setfield(map->L, -2, "httpd");
213 lua_pop(map->L, 2);
214 #else
215 lua_openlib(map->L, "", luaopen_base);
216 lua_openlib(map->L, LUA_LOADLIBNAME, luaopen_package);
217 lua_openlib(map->L, LUA_TABLIBNAME, luaopen_table);
218 lua_openlib(map->L, LUA_STRLIBNAME, luaopen_string);
219 lua_openlib(map->L, LUA_MATHLIBNAME, luaopen_math);
220 lua_openlib(map->L, LUA_OSLIBNAME, luaopen_os);
221 lua_openlib(map->L, LUA_IOLIBNAME, luaopen_io);
222 lua_openlib(map->L, LUA_HTTPDLIBNAME, luaopen_httpd);
223 #endif
224 lua_pushstring(map->L, "lua_state_map");
225 lua_pushlightuserdata(map->L, map);
226 lua_settable(map->L, LUA_REGISTRYINDEX);
227
228 lua_pushstring(map->L, "bozohttpd");
229 lua_pushlightuserdata(map->L, httpd);
230 lua_settable(map->L, LUA_REGISTRYINDEX);
231
232 if (luaL_loadfile(map->L, script))
233 bozoerr(httpd, 1, "failed to load script %s: %s", script,
234 lua_tostring(map->L, -1));
235 if (lua_pcall(map->L, 0, 0, 0))
236 bozoerr(httpd, 1, "failed to execute script %s: %s", script,
237 lua_tostring(map->L, -1));
238 SIMPLEQ_INSERT_TAIL(&httpd->lua_states, map, s_next);
239 }
240
241 static void
lua_env(lua_State * L,const char * name,const char * value)242 lua_env(lua_State *L, const char *name, const char *value)
243 {
244 lua_pushstring(L, value);
245 lua_setfield(L, -2, name);
246 }
247
248 /* decode query string */
249 static void
lua_url_decode(lua_State * L,char * s)250 lua_url_decode(lua_State *L, char *s)
251 {
252 char *v, *p, *val, *q;
253 char buf[3];
254 int c;
255
256 v = strchr(s, '=');
257 if (v == NULL)
258 return;
259 *v++ = '\0';
260 val = malloc(strlen(v) + 1);
261 if (val == NULL)
262 return;
263
264 for (p = v, q = val; *p; p++) {
265 switch (*p) {
266 case '%':
267 if (*(p + 1) == '\0' || *(p + 2) == '\0') {
268 free(val);
269 return;
270 }
271 buf[0] = *++p;
272 buf[1] = *++p;
273 buf[2] = '\0';
274 sscanf(buf, "%2x", &c);
275 *q++ = (char)c;
276 break;
277 case '+':
278 *q++ = ' ';
279 break;
280 default:
281 *q++ = *p;
282 }
283 }
284 *q = '\0';
285 lua_pushstring(L, val);
286 lua_setfield(L, -2, s);
287 free(val);
288 }
289
290 static void
lua_decode_query(lua_State * L,char * query)291 lua_decode_query(lua_State *L, char *query)
292 {
293 char *s;
294
295 s = strtok(query, "&");
296 while (s) {
297 lua_url_decode(L, s);
298 s = strtok(NULL, "&");
299 }
300 }
301
302 int
bozo_process_lua(bozo_httpreq_t * request)303 bozo_process_lua(bozo_httpreq_t *request)
304 {
305 bozohttpd_t *httpd = request->hr_httpd;
306 lua_state_map_t *map;
307 lua_handler_t *hndlr;
308 int n, ret, length;
309 char date[40];
310 bozoheaders_t *headp;
311 char *s, *query, *uri, *file, *command, *info, *content;
312 const char *type, *clen;
313 char *prefix, *handler, *p;
314 int rv = 0;
315
316 if (!httpd->process_lua)
317 return 0;
318
319 info = NULL;
320 query = NULL;
321 prefix = NULL;
322 uri = request->hr_oldfile ? request->hr_oldfile : request->hr_file;
323
324 if (*uri == '/') {
325 file = bozostrdup(httpd, request, uri);
326 if (file == NULL)
327 goto out;
328 prefix = bozostrdup(httpd, request, &uri[1]);
329 } else {
330 if (asprintf(&file, "/%s", uri) < 0)
331 goto out;
332 prefix = bozostrdup(httpd, request, uri);
333 }
334 if (prefix == NULL)
335 goto out;
336
337 if (request->hr_query && request->hr_query[0])
338 query = bozostrdup(httpd, request, request->hr_query);
339
340 p = strchr(prefix, '/');
341 if (p == NULL)
342 goto out;
343 *p++ = '\0';
344 handler = p;
345 if (!*handler)
346 goto out;
347 p = strchr(handler, '/');
348 if (p != NULL)
349 *p++ = '\0';
350
351 command = file + 1;
352 if ((s = strchr(command, '/')) != NULL) {
353 info = bozostrdup(httpd, request, s);
354 *s = '\0';
355 }
356
357 type = request->hr_content_type;
358 clen = request->hr_content_length;
359
360 SIMPLEQ_FOREACH(map, &httpd->lua_states, s_next) {
361 if (strcmp(map->prefix, prefix))
362 continue;
363
364 SIMPLEQ_FOREACH(hndlr, &map->handlers, h_next) {
365 if (strcmp(hndlr->name, handler))
366 continue;
367
368 lua_rawgeti(map->L, LUA_REGISTRYINDEX, hndlr->ref);
369
370 /* Create the "environment" */
371 lua_newtable(map->L);
372 lua_env(map->L, "SERVER_NAME",
373 BOZOHOST(httpd, request));
374 lua_env(map->L, "GATEWAY_INTERFACE", "Luigi/1.0");
375 lua_env(map->L, "SERVER_PROTOCOL", request->hr_proto);
376 lua_env(map->L, "REQUEST_METHOD",
377 request->hr_methodstr);
378 lua_env(map->L, "SCRIPT_PREFIX", map->prefix);
379 lua_env(map->L, "SCRIPT_NAME", file);
380 lua_env(map->L, "HANDLER_NAME", hndlr->name);
381 lua_env(map->L, "SCRIPT_FILENAME", map->script);
382 lua_env(map->L, "SERVER_SOFTWARE",
383 httpd->server_software);
384 lua_env(map->L, "REQUEST_URI", uri);
385 lua_env(map->L, "DATE_GMT",
386 bozo_http_date(date, sizeof(date)));
387 if (query && *query)
388 lua_env(map->L, "QUERY_STRING", query);
389 if (info && *info)
390 lua_env(map->L, "PATH_INFO", info);
391 if (type && *type)
392 lua_env(map->L, "CONTENT_TYPE", type);
393 if (clen && *clen)
394 lua_env(map->L, "CONTENT_LENGTH", clen);
395 if (request->hr_serverport && *request->hr_serverport)
396 lua_env(map->L, "SERVER_PORT",
397 request->hr_serverport);
398 if (request->hr_remotehost && *request->hr_remotehost)
399 lua_env(map->L, "REMOTE_HOST",
400 request->hr_remotehost);
401 if (request->hr_remoteaddr && *request->hr_remoteaddr)
402 lua_env(map->L, "REMOTE_ADDR",
403 request->hr_remoteaddr);
404
405 /* Pass the headers in a separate table */
406 lua_newtable(map->L);
407 SIMPLEQ_FOREACH(headp, &request->hr_headers, h_next)
408 lua_env(map->L, headp->h_header,
409 headp->h_value);
410
411 /* Pass the query variables */
412 if ((query && *query) ||
413 (type && *type && !strcmp(type, FORM))) {
414 lua_newtable(map->L);
415 if (query && *query)
416 lua_decode_query(map->L, query);
417 if (type && *type && !strcmp(type, FORM)) {
418 if (clen && *clen && atol(clen) > 0) {
419 length = atol(clen);
420 content = bozomalloc(httpd,
421 length + 1);
422 n = bozo_read(httpd,
423 STDIN_FILENO, content,
424 length);
425 if (n >= 0) {
426 content[n] = '\0';
427 lua_decode_query(map->L,
428 content);
429 } else {
430 lua_pop(map->L, 1);
431 lua_pushnil(map->L);
432 }
433 free(content);
434 }
435 }
436 } else
437 lua_pushnil(map->L);
438
439 ret = lua_pcall(map->L, 3, 0, 0);
440 if (ret)
441 printf("<br>Lua error: %s\n",
442 lua_tostring(map->L, -1));
443 bozo_flush(httpd, stdout);
444 rv = 1;
445 goto out;
446 }
447 }
448 out:
449 free(prefix);
450 free(uri);
451 free(info);
452 free(query);
453 free(file);
454 return rv;
455 }
456
457 #endif /* NO_LUA_SUPPORT */
458