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