1/* $NetBSD: lua-bozo.c,v 1.9 2014/01/02 08:21:38 mrg 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 54static int 55lua_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 68static int 69lua_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 82static int 83lua_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 106static int 107lua_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 133static int 134lua_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 149static int 150luaopen_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 171static void 172lua_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 */ 181void 182bozo_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(L, "package"); 205 lua_getfield(L, -1, "preload"); 206 lua_pushcfunction(L, luaopen_httpd); 207 lua_setfield(L, -2, "httpd"); 208 lua_pop(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 236static void 237lua_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 */ 244static void 245lua_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 lua_pushstring(L, val); 280 lua_setfield(L, -2, s); 281 free(val); 282} 283 284static void 285lua_decode_query(lua_State *L, char *query) 286{ 287 char *s; 288 289 s = strtok(query, "&"); 290 while (s) { 291 lua_url_decode(L, s); 292 s = strtok(NULL, "&"); 293 } 294} 295 296int 297bozo_process_lua(bozo_httpreq_t *request) 298{ 299 bozohttpd_t *httpd = request->hr_httpd; 300 lua_state_map_t *map; 301 lua_handler_t *hndlr; 302 int n, ret, length; 303 char date[40]; 304 bozoheaders_t *headp; 305 char *s, *query, *uri, *file, *command, *info, *content; 306 const char *type, *clen; 307 char *prefix, *handler, *p; 308 int rv = 0; 309 310 if (!httpd->process_lua) 311 return 0; 312 313 uri = request->hr_oldfile ? request->hr_oldfile : request->hr_file; 314 315 if (*uri == '/') { 316 file = bozostrdup(httpd, uri); 317 prefix = bozostrdup(httpd, &uri[1]); 318 } else { 319 prefix = bozostrdup(httpd, uri); 320 asprintf(&file, "/%s", uri); 321 } 322 if (file == NULL) { 323 free(prefix); 324 return 0; 325 } 326 327 if (request->hr_query && strlen(request->hr_query)) 328 query = bozostrdup(httpd, request->hr_query); 329 else 330 query = NULL; 331 332 p = strchr(prefix, '/'); 333 if (p == NULL){ 334 free(prefix); 335 return 0; 336 } 337 *p++ = '\0'; 338 handler = p; 339 if (!*handler) { 340 free(prefix); 341 return 0; 342 } 343 p = strchr(handler, '/'); 344 if (p != NULL) 345 *p++ = '\0'; 346 347 info = NULL; 348 command = file + 1; 349 if ((s = strchr(command, '/')) != NULL) { 350 info = bozostrdup(httpd, s); 351 *s = '\0'; 352 } 353 354 type = request->hr_content_type; 355 clen = request->hr_content_length; 356 357 SIMPLEQ_FOREACH(map, &httpd->lua_states, s_next) { 358 if (strcmp(map->prefix, prefix)) 359 continue; 360 361 SIMPLEQ_FOREACH(hndlr, &map->handlers, h_next) { 362 if (strcmp(hndlr->name, handler)) 363 continue; 364 365 lua_rawgeti(map->L, LUA_REGISTRYINDEX, hndlr->ref); 366 367 /* Create the "environment" */ 368 lua_newtable(map->L); 369 lua_env(map->L, "SERVER_NAME", 370 BOZOHOST(httpd, request)); 371 lua_env(map->L, "GATEWAY_INTERFACE", "Luigi/1.0"); 372 lua_env(map->L, "SERVER_PROTOCOL", request->hr_proto); 373 lua_env(map->L, "REQUEST_METHOD", 374 request->hr_methodstr); 375 lua_env(map->L, "SCRIPT_PREFIX", map->prefix); 376 lua_env(map->L, "SCRIPT_NAME", file); 377 lua_env(map->L, "HANDLER_NAME", hndlr->name); 378 lua_env(map->L, "SCRIPT_FILENAME", map->script); 379 lua_env(map->L, "SERVER_SOFTWARE", 380 httpd->server_software); 381 lua_env(map->L, "REQUEST_URI", uri); 382 lua_env(map->L, "DATE_GMT", 383 bozo_http_date(date, sizeof(date))); 384 if (query && *query) 385 lua_env(map->L, "QUERY_STRING", query); 386 if (info && *info) 387 lua_env(map->L, "PATH_INFO", info); 388 if (type && *type) 389 lua_env(map->L, "CONTENT_TYPE", type); 390 if (clen && *clen) 391 lua_env(map->L, "CONTENT_LENGTH", clen); 392 if (request->hr_serverport && *request->hr_serverport) 393 lua_env(map->L, "SERVER_PORT", 394 request->hr_serverport); 395 if (request->hr_remotehost && *request->hr_remotehost) 396 lua_env(map->L, "REMOTE_HOST", 397 request->hr_remotehost); 398 if (request->hr_remoteaddr && *request->hr_remoteaddr) 399 lua_env(map->L, "REMOTE_ADDR", 400 request->hr_remoteaddr); 401 402 /* Pass the headers in a separate table */ 403 lua_newtable(map->L); 404 SIMPLEQ_FOREACH(headp, &request->hr_headers, h_next) 405 lua_env(map->L, headp->h_header, 406 headp->h_value); 407 408 /* Pass the query variables */ 409 if ((query && *query) || 410 (type && *type && !strcmp(type, FORM))) { 411 lua_newtable(map->L); 412 if (query && *query) 413 lua_decode_query(map->L, query); 414 if (type && *type && !strcmp(type, FORM)) { 415 if (clen && *clen && atol(clen) > 0) { 416 length = atol(clen); 417 content = bozomalloc(httpd, 418 length + 1); 419 n = bozo_read(httpd, 420 STDIN_FILENO, content, 421 length); 422 if (n >= 0) { 423 content[n] = '\0'; 424 lua_decode_query(map->L, 425 content); 426 } else { 427 lua_pop(map->L, 1); 428 lua_pushnil(map->L); 429 } 430 free(content); 431 } 432 } 433 } else 434 lua_pushnil(map->L); 435 436 ret = lua_pcall(map->L, 3, 0, 0); 437 if (ret) 438 printf("<br>Lua error: %s\n", 439 lua_tostring(map->L, -1)); 440 bozo_flush(httpd, stdout); 441 rv = 1; 442 goto out; 443 } 444 } 445out: 446 free(prefix); 447 free(uri); 448 free(info); 449 free(query); 450 free(file); 451 return rv; 452} 453 454#endif /* NO_LUA_SUPPORT */ 455