├── shlib_version ├── GNUmakefile ├── Makefile ├── test.lua └── luafcgi.c /shlib_version: -------------------------------------------------------------------------------- 1 | major=1 2 | minor=0 3 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | SRCS= luafcgi.c 2 | LIB= fcgi 3 | 4 | LUAVER= `lua -v 2>&1 | cut -c 5-7` 5 | 6 | CFLAGS+= -O3 -Wall -fPIC -I/usr/include -I/usr/include/lua${LUAVER} \ 7 | -D_GNU_SOURCE 8 | LDADD+= -L/usr/lib -lfcgi 9 | 10 | LIBDIR= /usr/lib/lua/${LUAVER} 11 | 12 | ${LIB}.so: ${SRCS:.c=.o} 13 | cc -shared -o ${LIB}.so ${CFLAGS} ${SRCS:.c=.o} ${LDADD} 14 | 15 | clean: 16 | rm -f *.o *.so 17 | install: 18 | install -d ${DESTDIR}${LIBDIR} 19 | install ${LIB}.so ${DESTDIR}${LIBDIR} 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRCS= luafcgi.c 2 | LIB= fcgi 3 | 4 | OS!= uname 5 | 6 | .if ${OS} == "NetBSD" 7 | LOCALBASE= /usr/pkg 8 | LDADD+= -R/usr/lib -R${LOCALBASE}/lib -lfcgi 9 | .else 10 | LOCALBASE= /usr/local 11 | .endif 12 | 13 | NOLINT= 1 14 | CFLAGS+= -I${LOCALBASE}/include 15 | LDADD+= -L${XDIR}/lib -L${LOCALBASE}/lib 16 | 17 | LIBDIR= ${LOCALBASE}/lib/lua/5.2 18 | 19 | libinstall: 20 | 21 | install: 22 | ${INSTALL} -d ${DESTDIR}${LIBDIR} 23 | ${INSTALL} lib${LIB}.so ${DESTDIR}${LIBDIR}/${LIB}.so 24 | 25 | .include 26 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | -- FastCGI Test Program 2 | 3 | local fcgi = require 'fcgi' 4 | 5 | -- Create a local socket, make sure the FastCGI enabled webserver 6 | -- has r/w access to it, might need a chmod call here 7 | 8 | local socket = fcgi.openSocket('/var/run/.s.luafcgi', 4) 9 | 10 | local request = fcgi.initRequest(socket) 11 | 12 | -- Accept requests in a loop 13 | while request:accept() == 0 do 14 | print('Incoming request') 15 | 16 | local data = request:parse() 17 | 18 | print('request parsed', type(data)) 19 | 20 | for k, v in pairs(data) do 21 | print('decoded', k .. ':') 22 | if type(v) == 'table' then 23 | -- uploaded files have a __isFile boolean set to true 24 | if v.__isFile == true then 25 | for a, b in pairs(v) do 26 | print(a) 27 | if a ~= 'filedata' then 28 | print(b) 29 | end 30 | end 31 | local f = io.open(v.filename, 'w') 32 | f:write(v.filedata) 33 | f:close() 34 | else 35 | -- not a file, must be a selection multiple 36 | for a, b in pairs(v) do 37 | print(a, b) 38 | end 39 | end 40 | else 41 | print(string.format('%s', v)) 42 | end 43 | end 44 | 45 | -- Send some HTML to the client 46 | request:putStr('Content-Type: text/html\n') 47 | 48 | request:putStr('\n') 49 | request:putStr('

Hello from Lua

\n') 50 | request:putStr([[ 51 |
52 | 53 | 54 | 55 | 60 | 61 |
62 | ]]) 63 | 64 | -- finish this request 65 | request:finish() 66 | end 67 | print('ending') 68 | -------------------------------------------------------------------------------- /luafcgi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 - 2025 Micro Systems Marc Balmer, CH-5073 Gipf-Oberfrick 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of Micro Systems Marc Balmer nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL MICRO SYSTEMS MARC BALMER BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /* FastCGI interface for Lua */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #define FCGX_REQUEST_METATABLE "FastCGI request" 38 | 39 | static int 40 | fcgi_open_socket(lua_State *L) 41 | { 42 | int sock; 43 | 44 | sock = FCGX_OpenSocket(luaL_checkstring(L, 1), luaL_checkinteger(L, 2)); 45 | lua_pushinteger(L, sock); 46 | return 1; 47 | } 48 | 49 | static int 50 | fcgi_init_request(lua_State *L) 51 | { 52 | FCGX_Request *req; 53 | int socket; 54 | 55 | socket = luaL_checkinteger(L, 1); 56 | 57 | req = lua_newuserdata(L, sizeof(FCGX_Request)); 58 | luaL_setmetatable(L, FCGX_REQUEST_METATABLE); 59 | if (FCGX_InitRequest(req, socket, 0)) 60 | lua_pushnil(L); 61 | return 1; 62 | } 63 | 64 | /* Request methods */ 65 | static int 66 | fcgi_accept(lua_State *L) 67 | { 68 | FCGX_Request *req = luaL_checkudata(L, 1, FCGX_REQUEST_METATABLE); 69 | 70 | lua_pushinteger(L, FCGX_Accept_r(req)); 71 | return 1; 72 | } 73 | 74 | static int 75 | fcgi_finish(lua_State *L) 76 | { 77 | FCGX_Request *req = luaL_checkudata(L, 1, FCGX_REQUEST_METATABLE); 78 | 79 | FCGX_Finish_r(req); 80 | return 0; 81 | } 82 | 83 | static int 84 | fcgi_fflush(lua_State *L) 85 | { 86 | FCGX_Request *req = luaL_checkudata(L, 1, FCGX_REQUEST_METATABLE); 87 | 88 | lua_pushinteger(L, FCGX_FFlush(req->out)); 89 | return 1; 90 | } 91 | 92 | static int 93 | fcgi_get_line(lua_State *L) 94 | { 95 | FCGX_Request *req = luaL_checkudata(L, 1, FCGX_REQUEST_METATABLE); 96 | int len; 97 | char *str, *p; 98 | 99 | len = luaL_checkinteger(L, 2); 100 | str = malloc(len + 1); 101 | if (str == NULL) 102 | return luaL_error(L, "memory error"); 103 | p = FCGX_GetLine(str, len, req->in); 104 | if (p == NULL) 105 | lua_pushnil(L); 106 | else 107 | lua_pushstring(L, str); 108 | free(str); 109 | return 1; 110 | } 111 | 112 | static int 113 | fcgi_get_param(lua_State *L) 114 | { 115 | FCGX_Request *req = luaL_checkudata(L, 1, FCGX_REQUEST_METATABLE); 116 | char *p; 117 | 118 | p = FCGX_GetParam(luaL_checkstring(L, 2), req->envp); 119 | if (p == NULL) 120 | lua_pushnil(L); 121 | else 122 | lua_pushstring(L, p); 123 | return 1; 124 | } 125 | 126 | static int 127 | fcgi_get_env(lua_State *L) 128 | { 129 | FCGX_Request *req = luaL_checkudata(L, 1, FCGX_REQUEST_METATABLE); 130 | 131 | char **p = req->envp; 132 | char *v; 133 | 134 | if (p == NULL) 135 | lua_pushnil(L); 136 | else { 137 | lua_newtable(L); 138 | for (; *p; p++) { 139 | v = strchr(*p, '='); 140 | if (v != NULL) { 141 | lua_pushlstring(L, *p, v - *p); 142 | lua_pushstring(L, ++v); 143 | lua_settable(L, -3); 144 | } 145 | } 146 | } 147 | return 1; 148 | } 149 | 150 | static int 151 | fcgi_get_str(lua_State *L) 152 | { 153 | FCGX_Request *req = luaL_checkudata(L, 1, FCGX_REQUEST_METATABLE); 154 | int len, rc; 155 | char *str; 156 | 157 | len = luaL_checkinteger(L, 2); 158 | str = malloc(len + 1); 159 | if (str == NULL) 160 | return luaL_error(L, "memory error"); 161 | rc = FCGX_GetStr(str, len, req->in); 162 | str[len] = '\0'; 163 | if (rc == 0) 164 | lua_pushnil(L); 165 | else 166 | lua_pushstring(L, str); 167 | lua_pushinteger(L, rc); 168 | free(str); 169 | return 2; 170 | } 171 | 172 | static int 173 | fcgi_put_str(lua_State *L) 174 | { 175 | FCGX_Request *req = luaL_checkudata(L, 1, FCGX_REQUEST_METATABLE); 176 | const char *str; 177 | size_t len; 178 | 179 | str = luaL_checklstring(L, 2, &len); 180 | lua_pushinteger(L, FCGX_PutStr(str, len, req->out)); 181 | return 1; 182 | } 183 | 184 | /* Convenience functions */ 185 | /* decode query string */ 186 | static void 187 | lua_url_decode(lua_State *L, char *s) 188 | { 189 | char *v, *p, *val, *q; 190 | char buf[3]; 191 | int c; 192 | 193 | v = strchr(s, '='); 194 | if (v == NULL) 195 | return; 196 | *v++ = '\0'; 197 | val = malloc(strlen(v) + 1); 198 | if (val == NULL) 199 | return; 200 | 201 | for (p = v, q = val; *p; p++) { 202 | switch (*p) { 203 | case '%': 204 | if (*(p + 1) == '\0' || *(p + 2) == '\0') { 205 | free(val); 206 | return; 207 | } 208 | buf[0] = *++p; 209 | buf[1] = *++p; 210 | buf[2] = '\0'; 211 | sscanf(buf, "%2x", &c); 212 | *q++ = (char)c; 213 | break; 214 | case '+': 215 | *q++ = ' '; 216 | break; 217 | default: 218 | *q++ = *p; 219 | } 220 | } 221 | *q = '\0'; 222 | 223 | /* 224 | * Check there already is a field with this name, in the table, if so, 225 | * convert it to a table and store the existing and new value in that 226 | * table. 227 | */ 228 | lua_getfield(L, -1, s); 229 | if (!lua_isnil(L, -1)) { 230 | if (lua_istable(L, -1)) { 231 | int len; 232 | /* append value to existing table */ 233 | 234 | lua_len(L, -1); 235 | len = lua_tointeger(L, -1); 236 | lua_pop(L, 1); 237 | lua_pushinteger(L, len + 1); 238 | lua_pushstring(L, val); 239 | lua_settable(L, -3); 240 | 241 | } else { 242 | /* replace the current value with a table */ 243 | lua_newtable(L); 244 | 245 | /* push the old value */ 246 | lua_pushinteger(L, 1); 247 | lua_pushvalue(L, -3); 248 | lua_settable(L, -3); 249 | 250 | /* push the new value */ 251 | lua_pushinteger(L, 2); 252 | lua_pushstring(L, val); 253 | lua_settable(L, -3); 254 | lua_setfield(L, -3, s); 255 | } 256 | lua_pop(L, 1); 257 | } else { 258 | lua_pop(L, 1); 259 | lua_pushstring(L, val); 260 | lua_setfield(L, -2, s); 261 | } 262 | free(val); 263 | } 264 | 265 | static void 266 | lua_decode_query(lua_State *L, char *query) 267 | { 268 | char *s; 269 | 270 | s = strtok(query, "&"); 271 | while (s) { 272 | lua_url_decode(L, s); 273 | s = strtok(NULL, "&"); 274 | } 275 | } 276 | 277 | static char * 278 | sgets(char **buf, char *to) 279 | { 280 | char *s, *p; 281 | 282 | p = s = *buf; 283 | if (*s == '\0') 284 | return NULL; 285 | 286 | while (*s && s != to && *s != '\r' && *s != '\n') 287 | s++; 288 | 289 | if (*s && *s == '\r') 290 | *s++ = '\0'; 291 | if (*s && *s == '\n') 292 | *s++ = '\0'; 293 | *buf = s; 294 | return p; 295 | } 296 | 297 | static void 298 | lua_decode_part(lua_State *L, char *from, char *to) 299 | { 300 | char *p, *header, *name, *filename, *type, *n; 301 | 302 | name = filename = type = NULL; 303 | p = from; 304 | 305 | do { 306 | header = sgets(&p, to); 307 | if (!strncmp(header, "Content-Disposition:", 308 | strlen("Content-Disposition:"))) { 309 | name = strstr(header, "name=\""); 310 | if (name == NULL) 311 | return; 312 | name += strlen("name=\""); 313 | for (n = name + 1; *n && *n != '"'; n++) 314 | ; 315 | if (*n == '"') 316 | *n++ = '\0'; 317 | 318 | filename = strstr(n, "filename=\""); 319 | if (filename != NULL) { 320 | filename += strlen("filename=\""); 321 | for (n = filename + 1; *n && *n != '"'; n++) 322 | ; 323 | if (*n == '"') 324 | *n = '\0'; 325 | } 326 | } else if (!strncmp(header, "Content-Type: ", 327 | strlen("Content-Type: "))) { 328 | type = header + strlen("Content-Type: "); 329 | for (n = type; *n; n++) 330 | if (*n == '\r' || *n == '\n') 331 | *n = '\0'; 332 | } 333 | } while (strlen(header) && p < to); 334 | 335 | if (name) { 336 | if (filename) { 337 | lua_newtable(L); 338 | lua_pushboolean(L, 1); 339 | lua_setfield(L, -2, "__isFile"); 340 | lua_pushstring(L, filename); 341 | lua_setfield(L, -2, "filename"); 342 | if (type) { 343 | lua_pushstring(L, type); 344 | lua_setfield(L, -2, "filetype"); 345 | } 346 | lua_pushlstring(L, p, to - p); 347 | lua_setfield(L, -2, "filedata"); 348 | lua_setfield(L, -2, name); 349 | } else { 350 | /* 351 | * Check there already is a field with this name, in 352 | * the table, if so, convert it to a table and store 353 | * the existing and new value 354 | * in that table. 355 | */ 356 | lua_getfield(L, -1, name); 357 | if (!lua_isnil(L, -1)) { 358 | if (lua_istable(L, -1)) { 359 | int len; 360 | /* append value to existing table */ 361 | 362 | lua_len(L, -1); 363 | len = lua_tointeger(L, -1); 364 | lua_pop(L, 1); 365 | lua_pushinteger(L, len + 1); 366 | lua_pushlstring(L, p, to -p); 367 | lua_settable(L, -3); 368 | 369 | } else { 370 | lua_newtable(L); 371 | 372 | /* push the old value */ 373 | lua_pushinteger(L, 1); 374 | lua_pushvalue(L, -3); 375 | lua_settable(L, -3); 376 | 377 | /* push the new value */ 378 | lua_pushinteger(L, 2); 379 | lua_pushlstring(L, p, to -p); 380 | lua_settable(L, -3); 381 | lua_setfield(L, -3, name); 382 | } 383 | lua_pop(L, 1); 384 | } else { 385 | lua_pop(L, 1); 386 | lua_pushlstring(L, p, to - p); 387 | lua_setfield(L, -2, name); 388 | } 389 | } 390 | } 391 | } 392 | 393 | static void 394 | lua_decode_multipart(lua_State *L, char *ctype, char *content, int len) 395 | { 396 | char *boundary, *p, *np; 397 | 398 | /* 399 | * We can use strstr here instead of memmem, since the start of 400 | * of the content is actually text. 401 | */ 402 | boundary = strstr(ctype, "boundary="); 403 | if (boundary == NULL) 404 | return; 405 | else 406 | boundary += strlen("boundary="); 407 | 408 | for (p = boundary; *p && *p != '\n' && *p != '\r'; p++) 409 | ; 410 | 411 | /* Find first boundary */ 412 | p = strstr(content, boundary); 413 | if (p == NULL) 414 | return; 415 | 416 | /* Skip past CR/LF */ 417 | p += strlen(boundary); 418 | while (*p == '\n' || *p == '\r') 419 | p++; 420 | 421 | while (p) { 422 | /* 423 | * We have to use memmem here, since the data part may actually 424 | * be binary data, strstr does not handle correctly. 425 | */ 426 | np = memmem(p, len - (p - content), boundary, strlen(boundary)); 427 | if (np) { 428 | /* 429 | * boundary is prefixed by --, up to np - 2, also 430 | * take into account the CR/LF 431 | */ 432 | lua_decode_part(L, p, np - 4); 433 | 434 | /* Skip past CR/LF */ 435 | np += strlen(boundary); 436 | while (*np == '\n' || *np == '\r') 437 | np++; 438 | } 439 | p = np; 440 | } 441 | } 442 | 443 | static int 444 | fcgi_parse(lua_State *L) 445 | { 446 | FCGX_Request *req = luaL_checkudata(L, 1, FCGX_REQUEST_METATABLE); 447 | char *query, *ctype, *content; 448 | int clen, rc; 449 | 450 | query = FCGX_GetParam("QUERY_STRING", req->envp); 451 | clen = atoi(FCGX_GetParam("CONTENT_LENGTH", req->envp)); 452 | 453 | lua_newtable(L); 454 | if ((query && *query) || clen > 0) { 455 | if (query && *query) 456 | lua_decode_query(L, query); 457 | 458 | if (clen > 0) { 459 | content = malloc(clen + 1); 460 | if (content == NULL) 461 | return 1; 462 | 463 | rc = FCGX_GetStr(content, clen, req->in); 464 | content[rc] = '\0'; 465 | 466 | ctype = FCGX_GetParam("CONTENT_TYPE", req->envp); 467 | 468 | if (!strcmp(ctype, "application/x-www-form-urlencoded")) 469 | lua_decode_query(L, content); 470 | else { 471 | if (!strncmp(ctype, "multipart/form-data;", 472 | strlen("multipart/form-data;"))) 473 | lua_decode_multipart(L, ctype, content, 474 | clen); 475 | } 476 | free(content); 477 | } 478 | } 479 | return 1; 480 | } 481 | 482 | int 483 | luaopen_fcgi(lua_State* L) 484 | { 485 | static const struct luaL_Reg methods[] = { 486 | { "openSocket", fcgi_open_socket }, 487 | { "initRequest", fcgi_init_request }, 488 | { NULL, NULL } 489 | }; 490 | static const struct luaL_Reg request_methods[] = { 491 | { "accept", fcgi_accept }, 492 | { "finish", fcgi_finish }, 493 | { "fflush", fcgi_fflush }, 494 | { "getLine", fcgi_get_line }, 495 | { "getParam", fcgi_get_param }, 496 | { "getEnv", fcgi_get_env }, 497 | { "getStr", fcgi_get_str }, 498 | { "putStr", fcgi_put_str }, 499 | 500 | /* Convenience functions */ 501 | { "parse", fcgi_parse }, 502 | { NULL, NULL } 503 | }; 504 | 505 | if (luaL_newmetatable(L, FCGX_REQUEST_METATABLE)) { 506 | luaL_setfuncs(L, request_methods, 0); 507 | 508 | lua_pushliteral(L, "__index"); 509 | lua_pushvalue(L, -2); 510 | lua_settable(L, -3); 511 | 512 | lua_pushliteral(L, "__metatable"); 513 | lua_pushliteral(L, "must not access this metatable"); 514 | lua_settable(L, -3); 515 | } 516 | lua_pop(L, 1); 517 | 518 | luaL_newlib(L, methods); 519 | 520 | lua_pushliteral(L, "_COPYRIGHT"); 521 | lua_pushliteral(L, "Copyright (C) 2013 - 2025 " 522 | "micro systems marc balmer"); 523 | lua_settable(L, -3); 524 | lua_pushliteral(L, "_DESCRIPTION"); 525 | lua_pushliteral(L, "FastCGI for Lua"); 526 | lua_settable(L, -3); 527 | lua_pushliteral(L, "_VERSION"); 528 | lua_pushliteral(L, "fcgi 1.3.1"); 529 | lua_settable(L, -3); 530 | 531 | if (FCGX_Init()) 532 | lua_pushnil(L); 533 | 534 | return 1; 535 | } 536 | --------------------------------------------------------------------------------