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 * 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 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 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 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 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 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 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 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 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 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 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 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 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