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