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