Browse Source

Squash merge into master branch

master
Christopher Ramey 10 years ago
committed by cdramey
parent
commit
95e1a981ab
  1. 3
      .gitignore
  2. 24
      LICENCE
  3. 17
      Makefile
  4. 38
      README.md
  5. 17
      TODO
  6. 37
      lua-fastcgi.lua
  7. 126
      src/config.c
  8. 16
      src/config.h
  9. 137
      src/lfuncs.c
  10. 4
      src/lfuncs.h
  11. 171
      src/lua-fastcgi.c
  12. 5
      src/lua-fastcgi.h
  13. 366
      src/lua.c
  14. 20
      src/lua.h

3
.gitignore

@ -0,0 +1,3 @@
*.swp
*.o
lua-fastcgi

24
LICENCE

@ -0,0 +1,24 @@
Copyright (c) 2012, Christopher Ramey
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

17
Makefile

@ -0,0 +1,17 @@
CFLAGS=-c -std=gnu99 -Wall
LDFLAGS=-O2 -Wl,-Bstatic -lfcgi -llua5.1 -Wl,-Bdynamic -lm -lpthread
.c.o:
$(CC) $(CFLAGS) $< -o $@
all: lua-fastcgi
debug: CFLAGS+=-g -DDEBUG
debug: lua-fastcgi
lua-fastcgi: src/lua-fastcgi.o src/lfuncs.o src/lua.o src/config.o
$(CC) $^ $(LDFLAGS) -o $@
clean:
rm -f src/*.o lua-fastcgi

38
README.md

@ -0,0 +1,38 @@
lua-fastcgi
===========
lua-fastcgi is a sandboxed Lua backend for FastCGI. That is, you can write
Lua scripts that serve up web pages. Options exist in lua-fastcgi.lua
to configure a fixed amount of memory, cpu usage, and output bytes
for each request. While sandboxed, lua-fastcgi supports a limit set
of functions. If sandboxing is disabled, lua-fastcgi loads the standard
libraries and users may load modules as needed.
compiling
---------
lua-fastcgi requires libfcgi and liblua to successfully compile. lua-fastcgi
has been tested under Linux, specifically Ubuntu 10.10. Other versions of
Ubuntu will likely work without any changes. Other flavors of Linux should
work with little to no effort. Other Unix-like operating systems are untested
and unsupported, for now.
running
-------
lua-fastcgi reads lua-fastcgi.lua in the current working directory. If it
fails to read this file, it will assume certain defaults and continue anyway.
Configuration defaults are documented in the included lua-fastcgi.lua file.
lua-fastcgi has been tested with nginx, but will likely work with other
FastCGI compatible web servers with little effort. lua-fastcgi relies only
on SCRIPT_NAME and SCRIPT_FILENAME FastCGI variables passed to it. Lua scripts
can be configured inside of an nginx server directive as follows:
location ~* \.lua$ {
include /etc/nginx/fastcgi_params;
fastcgi_pass 127.0.0.1:9222;
}

17
TODO

@ -0,0 +1,17 @@
High Priority
-------------
POST variable parsing
Basic sandboxed database support (e.g. GET/PUT)
Sandboxed dofile() / loadfile() / loadstring()
Medium Priority
---------------
File Upload Support
Database error logging
Consider switching to mmap() for file reads
Low Priority
------------
Database file adapter

37
lua-fastcgi.lua

@ -0,0 +1,37 @@
return {
-- IP/Port to listen on
-- Default: "127.0.0.1:9222"
listen = "127.0.0.1:9222",
-- How many connections should be back-logged by each thread
-- Default: 100
backlog = 100,
-- Number of threads to spin off. Usually one per CPU
-- (plus hardware threads) is a good idea
-- Default: 4
threads = 1,
-- Indicates if states should be sandboxed (i.e. Denied access to
-- file system resources or system-level functions)
-- Default: true
sandbox = true,
-- Maximum amount of memory a state may consume or 0 for no limit
-- Default: 65536
mem_max = 65536,
-- Maximum amount of CPU time, in both seconds and picoseconds for each
-- execution. If cpu_sec and cpu_usec are 0, there is no limit
-- Default: 5000000
cpu_usec = 500000,
-- Default: 0
cpu_sec = 0,
-- Limit page output to x bytes or 0 for unlimited
-- Default: 65536
output_max = 0,
-- Default content type returned in header
content_type = "text/html; charset=iso-8859-1"
}

126
src/config.c

@ -0,0 +1,126 @@
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <lua5.1/lua.h>
#include <lua5.1/lauxlib.h>
#include <lua5.1/lualib.h>
#include "config.h"
// Checks if a file exists
static int LF_fileexists(char *path)
{
if(access(path, F_OK) == 0){ return 1; }
else { return 0; }
}
// Create configuration with default settings
LF_config *LF_createconfig()
{
LF_config *c = malloc(sizeof(LF_config));
if(c == NULL){ return NULL; }
// Default settings
c->listen = "127.0.0.1:9222";
c->backlog = 100;
c->threads = 1;
c->sandbox = 1;
c->mem_max = 65536;
c->output_max = 65536;
c->cpu_usec = 500000;
c->cpu_sec = 0;
return c;
}
// Load configuration
int LF_loadconfig(LF_config *cfg, char *path)
{
if(!LF_fileexists(path)){ return 1; }
lua_State *l = luaL_newstate();
if(luaL_loadfile(l, path) || lua_pcall(l,0,1,0)){
printf("%s\n", lua_tostring(l, -1));
lua_close(l);
return 1;
}
lua_settop(l, 1);
if(lua_istable(l, 1)){
lua_pushstring(l, "listen");
lua_rawget(l, 1);
if(lua_isstring(l, 2)){
size_t len = 0;
const char *str = lua_tolstring(l, 2, &len);
if(len > 0){
cfg->listen = malloc(len+1);
memcpy(cfg->listen, str, len+1);
}
}
lua_settop(l, 1);
lua_pushstring(l, "backlog");
lua_rawget(l, 1);
if(lua_isnumber(l, 2)){ cfg->backlog = lua_tonumber(l, 2); }
lua_settop(l, 1);
lua_pushstring(l, "threads");
lua_rawget(l, 1);
if(lua_isnumber(l, 2)){ cfg->threads = lua_tonumber(l, 2); }
lua_settop(l, 1);
lua_pushstring(l, "sandbox");
lua_rawget(l, 1);
if(lua_isboolean(l, 2)){ cfg->sandbox = lua_toboolean(l, 2); }
lua_settop(l, 1);
lua_pushstring(l, "mem_max");
lua_rawget(l, 1);
if(lua_isnumber(l, 2)){ cfg->mem_max = lua_tonumber(l, 2); }
lua_settop(l, 1);
lua_pushstring(l, "cpu_usec");
lua_rawget(l, 1);
if(lua_isnumber(l, 2)){ cfg->cpu_usec = lua_tonumber(l, 2); }
lua_settop(l, 1);
lua_pushstring(l, "cpu_sec");
lua_rawget(l, 1);
if(lua_isnumber(l, 2)){ cfg->cpu_sec = lua_tonumber(l, 2); }
lua_settop(l, 1);
lua_pushstring(l, "output_max");
lua_rawget(l, 1);
if(lua_isnumber(l, 2)){ cfg->output_max = lua_tonumber(l, 2); }
lua_settop(l, 1);
lua_pushstring(l, "content_type");
lua_rawget(l, 1);
if(lua_isstring(l, 2)){
size_t len = 0;
const char *str = lua_tolstring(l, 2, &len);
if(len > 0){
cfg->content_type = malloc(len+1);
memcpy(cfg->content_type, str, len+1);
}
}
}
lua_close(l);
return 0;
}

16
src/config.h

@ -0,0 +1,16 @@
typedef struct {
char *listen;
int backlog;
int threads;
int sandbox;
size_t mem_max;
size_t output_max;
unsigned long cpu_usec;
unsigned long cpu_sec;
char *content_type;
} LF_config;
LF_config *LF_createconfig();
int LF_loadconfig(LF_config *, char *);

137
src/lfuncs.c

@ -0,0 +1,137 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcgiapp.h>
#include <lua5.1/lua.h>
#include <lua5.1/lauxlib.h>
#include "lua.h"
#include "lfuncs.h"
// replacement print function, outputs to FCGI stream
static int LF_pprint(lua_State *l, int cr)
{
int args = lua_gettop(l);
// Fetch the response
lua_pushstring(l, "RESPONSE");
lua_rawget(l, LUA_REGISTRYINDEX);
LF_response *response = lua_touserdata(l, args+1);
lua_pop(l, 1);
// fetch limits
lua_pushstring(l, "RESPONSE_LIMIT");
lua_rawget(l, LUA_REGISTRYINDEX);
size_t *limit = lua_touserdata(l, args+1);
lua_pop(l, 1);
// If the response isn't committed, send the header
if(!response->committed){
lua_getglobal(l, "HEADER");
if(!lua_istable(l, args+1)){ luaL_error(l, "Invalid HEADER (Not table)."); }
lua_pushstring(l, "Status");
lua_rawget(l, args+1);
// If the status has been explicitly set, send that
if(!lua_isnil(l, args+2)){
if(!lua_isstring(l, args+2)){
luaL_error(l, "Invalid HEADER (Invalid Status).");
}
size_t len;
const char *str = lua_tolstring(l, args+2, &len);
if(limit){
if((len+10) > *limit){ luaL_error(l, "Output limit exceeded."); }
*limit -= (len+10);
}
FCGX_PutStr("Status: ", 8, response->out);
FCGX_PutStr(str, len, response->out);
FCGX_PutStr("\r\n", 2, response->out);
response->committed = 1;
}
lua_pop(l, 1); // Pop the status
// Loop over the header, ignoring status, but sending everything else
lua_pushnil(l);
while(lua_next(l, args+1)){
// If the key or the value isn't a string (or number) throw an error
if(!lua_isstring(l, args+2) || !lua_isstring(l, args+3)){
luaL_error(l, "Invalid HEADER (Invalid key and/or value).");
}
size_t keylen = 0;
const char *key = lua_tolstring(l, args+2, &keylen);
if(keylen == 6 && memcmp(key, "Status", 6) == 0){
// Clear the last value out
lua_pop(l, 1);
continue;
}
size_t vallen = 0;
const char *val = lua_tolstring(l, args+3, &vallen);
if(limit){
if((vallen+keylen+4) > *limit){ luaL_error(l, "Output limit exceeded."); }
*limit -= (vallen+keylen+4);
}
FCGX_PutStr(key, keylen, response->out);
FCGX_PutStr(": ", 2, response->out);
FCGX_PutStr(val, vallen, response->out);
FCGX_PutStr("\r\n", 2, response->out);
response->committed = 1;
lua_pop(l, 1); // Clear the last value out
}
lua_pop(l, 1); // Clear the table out
if(limit){
if(2 >= *limit){ luaL_error(l, "Output limit exceeded."); }
*limit -= 2;
}
FCGX_PutS("\r\n", response->out);
response->committed = 1;
}
size_t strlen;
const char *str;
for(int i=1; i <= args; i++){
switch(lua_type(l, i)){
case LUA_TSTRING:
case LUA_TNUMBER:
case LUA_TBOOLEAN:
str = lua_tolstring(l, i, &strlen);
if(limit){
if(strlen > *limit){ luaL_error(l, "Output limit exceeded."); }
*limit -= strlen;
}
FCGX_PutStr(str, strlen, response->out);
break;
default: /* Ignore other types */ break;
}
}
if(cr){
if(limit){
if(*limit == 0){ luaL_error(l, "Output limit exceeded."); }
(*limit)--;
}
FCGX_PutChar('\n', response->out);
}
return 0;
}
int LF_print(lua_State *l){ return LF_pprint(l, 1); }
int LF_write(lua_State *l){ return LF_pprint(l, 0); }

4
src/lfuncs.h

@ -0,0 +1,4 @@
// Writes FCGI output followed by a carriage return
int LF_print(lua_State *);
// Writes FCGI output without a carriage return
int LF_write(lua_State *);

171
src/lua-fastcgi.c

@ -0,0 +1,171 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <fcgi_config.h>
#include <fcgiapp.h>
#include <lua5.1/lua.h>
#include <pthread.h>
#include "lua.h"
#include "config.h"
#include "lua-fastcgi.h"
static char *http_status_strings[] = {
[200] = "OK",
[403] = "Forbidden",
[404] = "Not Found",
[500] = "Internal Server Error"
};
#define senderror(status_code,error_string) \
if(!response.committed){ \
FCGX_FPrintF(request.out, "Status: %d %s\r\n", status_code, http_status_strings[status_code]); \
FCGX_FPrintF(request.out, "Content-Type: %s\r\n\r\n", config->content_type); \
response.committed = 1; \
} \
FCGX_PutS(error_string, response.out);
#ifdef DEBUG
static void printcfg(LF_config *cfg)
{
printf("Listen: %s\n", cfg->listen);
printf("Backlog: %d\n", cfg->backlog);
printf("Threads: %d\n", cfg->threads);
printf("Sandbox: %d\n", cfg->sandbox);
printf("Max Memory: %zu\n", cfg->mem_max);
printf("Max Output: %zu\n", cfg->output_max);
printf("CPU usec: %lu\n", cfg->cpu_usec);
printf("CPU sec: %lu\n", cfg->cpu_sec);
printf("Default Content Type: %s\n", cfg->content_type);
printf("\n");
}
static void printvars(FCGX_Request *request)
{
for(int i=0; request->envp[i] != NULL; i++){
printf("%s\n", request->envp[i]);
}
printf("\n");
}
#endif
void *thread_run(void *arg)
{
LF_params *params = arg;
LF_config *config = params->config;
LF_limits *limits = LF_newlimits();
LF_response response;
lua_State *l;
FCGX_Request request;
FCGX_InitRequest(&request, params->socket, 0);
for(;;){
LF_setlimits(
limits, config->mem_max, config->output_max,
config->cpu_sec, config->cpu_usec
);
l = LF_newstate(config->sandbox, config->content_type);
if(FCGX_Accept_r(&request)){
printf("FCGX_Accept_r() failure\n");
LF_closestate(l);
continue;
}
#ifdef DEBUG
printvars(&request);
#endif
LF_parserequest(l, &request, &response);
LF_enablelimits(l, limits);
switch(LF_loadfile(l)){
case 0:
if(lua_pcall(l, 0, 0, 0)){
senderror(500, lua_tostring(l, -1));
} else if(!response.committed){
senderror(200, "");
}
break;
case EACCES:
senderror(403, lua_tostring(l, -1));
break;
case ENOENT:
senderror(404, lua_tostring(l, -1));
break;
default:
senderror(500, lua_tostring(l, -1));
break;
}
FCGX_Finish_r(&request);
LF_closestate(l);
}
}
int main()
{
if(FCGX_Init() != 0){
printf("FCGX_Init() failure\n");
exit(EXIT_FAILURE);
}
LF_config *config = LF_createconfig();
if(config == NULL){
printf("LF_createconfig(): memory allocation error\n");
exit(EXIT_FAILURE);
}
if(LF_loadconfig(config, "./lua-fastcgi.lua")){
printf("Error loading lua-fastcgi.lua\n");
}
#ifdef DEBUG
printcfg(config);
#endif
int socket = FCGX_OpenSocket(config->listen, config->backlog);
if(socket < 0){
printf("FCGX_OpenSocket() failure: could not open %s\n", config->listen);
exit(EXIT_FAILURE);
}
LF_params *params = malloc(sizeof(LF_params));
params->socket = socket;
params->config = config;
if(config->threads == 1){
thread_run(params);
} else {
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_t threads[config->threads];
for(int i=0; i < config->threads; i++){
int r = pthread_create(&threads[i], &attr, &thread_run, params);
if(r){
printf("Thread creation error: %d\n", r);
exit(EXIT_FAILURE);
}
}
pthread_join(threads[0], NULL);
}
return 0;
}

5
src/lua-fastcgi.h

@ -0,0 +1,5 @@
typedef struct {
LF_config *config;
int socket;
} LF_params;

366
src/lua.c

@ -0,0 +1,366 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <fcgiapp.h>
#include <lua5.1/lua.h>
#include <lua5.1/lauxlib.h>
#include <lua5.1/lualib.h>
#include "lua.h"
#include "lfuncs.h"
#define LF_BUFFERSIZE 4096
typedef struct {
int fd;
char buffer[LF_BUFFERSIZE];
size_t total;
} LF_loaderdata;
#ifdef DEBUG
void LF_printstack(lua_State *l)
{
int ss = lua_gettop(l);
printf("Lua Stack:\n");
for(int i=1; i <= ss; i++){
printf("\t%d) %s\n", i, lua_typename(l, lua_type(l, i)));
}
}
#endif
// Gets current thread usage
static int LF_threadusage(struct timeval *tv)
{
struct rusage usage;
if(getrusage(RUSAGE_SELF, &usage) == -1){
return 1;
}
// Add user to sys to get actual usage
tv->tv_usec = usage.ru_utime.tv_usec + usage.ru_stime.tv_usec;
tv->tv_sec = usage.ru_utime.tv_sec + usage.ru_stime.tv_sec;
if(tv->tv_usec > 1000000){
tv->tv_usec -= 1000000;
tv->tv_sec++;
}
return 0;
}
// limits cpu usage
static void LF_limit_hook(lua_State *l, lua_Debug *d)
{
lua_pushstring(l, "CPU_LIMIT");
lua_rawget(l, LUA_REGISTRYINDEX);
struct timeval *limit = lua_touserdata(l, -1);
lua_pop(l, 1);
struct timeval tv;
if(LF_threadusage(&tv)){ luaL_error(l, "CPU usage sample error"); }
if(timercmp(&tv, limit, >)){ luaL_error(l, "CPU limit exceeded"); }
}
// removes global variables
static void LF_nilglobal(lua_State *l, char *var)
{
lua_pushnil(l);
lua_setglobal(l, var);
}
// Limited memory allocator
static void *LF_limit_alloc(void *ud, void *ptr, size_t osize, size_t nsize)
{
size_t *limit = ud;
*limit += osize;
if(nsize == 0){
free(ptr);
return NULL;
} else {
if(*limit < nsize){ return NULL; }
*limit -= nsize;
return realloc(ptr, nsize);
}
}
// Allocates and initializes a new set of limits for a lua state
LF_limits *LF_newlimits()
{
LF_limits *limits = malloc(sizeof(LF_limits));
if(limits == NULL){ return NULL; }
memset(limits, 0, sizeof(LF_limits));
return limits;
}
void LF_setlimits(LF_limits *limits, size_t memory, size_t output, uint32_t cpu_sec, uint32_t cpu_usec)
{
limits->memory = memory;
limits->output = output;
limits->cpu.tv_usec = cpu_usec;
limits->cpu.tv_sec = cpu_sec;
}
void LF_enablelimits(lua_State *l, LF_limits *limits)
{
if(limits->cpu.tv_usec > 0 || limits->cpu.tv_sec > 0){
struct timeval curr;
if(LF_threadusage(&curr)){
printf("CPU usage sample error\n"); // FIX ME
} else {
timeradd(&limits->cpu, &curr, &limits->cpu);
lua_sethook(l, &LF_limit_hook, LUA_MASKCOUNT, 1000);
}
lua_pushstring(l, "CPU_LIMIT");
lua_pushlightuserdata(l, &limits->cpu);
lua_rawset(l, LUA_REGISTRYINDEX);
}
if(limits->output){
lua_pushstring(l, "RESPONSE_LIMIT");
lua_pushlightuserdata(l, &limits->output);
lua_rawset(l, LUA_REGISTRYINDEX);
}
if(limits->memory){ lua_setallocf(l, &LF_limit_alloc, &limits->memory); }
}
// Initialize a new lua state using specific parameters
lua_State *LF_newstate(int sandbox, char *content_type)
{
lua_State *l = luaL_newstate();
if(l == NULL){ return NULL; }
// Load base
lua_pushcfunction(l, luaopen_base);
lua_pushliteral(l, "");
lua_call(l, 1, 0);
if(sandbox){
// Load table
lua_pushcfunction(l, luaopen_table);
lua_pushliteral(l, LUA_TABLIBNAME);
lua_call(l, 1, 0);
// Load string
lua_pushcfunction(l, luaopen_string);
lua_pushliteral(l, LUA_STRLIBNAME);
lua_call(l, 1, 0);
// Load math
lua_pushcfunction(l, luaopen_math);
lua_pushliteral(l, LUA_MATHLIBNAME);
lua_call(l, 1, 0);
// Nil out unsafe functions/objects
LF_nilglobal(l, "dofile");
LF_nilglobal(l, "load");
LF_nilglobal(l, "loadfile");
LF_nilglobal(l, "xpcall");
LF_nilglobal(l, "pcall");
LF_nilglobal(l, "module");
LF_nilglobal(l, "require");
}
// Register the print function
lua_register(l, "print", &LF_print);
// Register the write function
lua_register(l, "write", &LF_write);
// Setup the "HEADER" value
lua_newtable(l);
lua_pushstring(l, "Content-Type");
lua_pushstring(l, content_type);
lua_rawset(l, 1);
lua_setglobal(l, "HEADER");
return l;
}
// Set GET variables
static void LF_parsequerystring(lua_State *l, char *query_string)
{
lua_newtable(l);
int stack = lua_gettop(l);
char *sptr, *optr, *nptr;
for(nptr = optr = sptr = &query_string[0]; 1; optr++){
switch(*optr){
case '+':
*nptr++ = ' ';
break;
case '=':
// Push a key, if it's valid and there's not already one
if(lua_gettop(l) == stack){
if((nptr-sptr) > 0){ lua_pushlstring(l, sptr, (nptr - sptr)); }
sptr = nptr;
} else {
*nptr++ = '=';
}
break;
case '&':
// Push key or value if valid
if((nptr-sptr) > 0){ lua_pushlstring(l, sptr, (nptr - sptr)); }
// Push value, if there is already a key
if(lua_gettop(l) == (stack+1)){ lua_pushstring(l, ""); }
// Set key/value if they exist
if(lua_gettop(l) == (stack+2)){ lua_rawset(l, stack); }
sptr = nptr;
break;
case '%': {
// Decode hex percent encoded sets, if valid
char c1 = *(optr+1), c2 = *(optr+2);
if(isxdigit(c1) && isxdigit(c2)){
char digit = 16 * (c1 >= 'A' ? (c1 & 0xdf) - '7' : (c1 - '0'));
digit += (c2 >= 'A' ? (c2 & 0xdf) - '7' : (c2 - '0'));
*nptr++ = digit;
optr += 2;
} else {
*nptr++ = '%';
}
} break;
case '\0':
// Push key or value if valid
if((nptr-sptr) > 0){ lua_pushlstring(l, sptr, (nptr - sptr)); }
// Push value, if needed
if(lua_gettop(l) == (stack+1)){ lua_pushstring(l, ""); }
// Set key/value if valid
if(lua_gettop(l) == (stack+2)){ lua_rawset(l, stack); }
// Finally, set the table
lua_setglobal(l, "GET");
return;
break;
default:
*nptr++ = *optr;
break;
}
}
}
// Parses fastcgi request
void LF_parserequest(lua_State *l, FCGX_Request *request, LF_response *response)
{
lua_newtable(l);
for(char **p = request->envp; *p; ++p){
char *vptr = strchr(*p, '=');
int keylen = (vptr - *p);
lua_pushlstring(l, *p, keylen); // Push Key
lua_pushstring(l, (vptr+1)); // Push Value
lua_rawset(l, 1); // Set key/value into table
if(keylen == 12 && memcmp(*p, "QUERY_STRING", 12) == 0){
LF_parsequerystring(l, (vptr+1));
}
}
lua_setglobal(l, "REQUEST");
response->committed = 0;
response->out = request->out;
lua_pushstring(l, "RESPONSE");
lua_pushlightuserdata(l, response);
lua_rawset(l, LUA_REGISTRYINDEX);
}
static const char *LF_filereader(lua_State *l, void *data, size_t *size)
{
LF_loaderdata *ld = data;
*size = read(ld->fd, ld->buffer, LF_BUFFERSIZE);
if(ld->total == 0 && *size > 3){
if(memcmp(ld->buffer, LUA_SIGNATURE, 4) == 0){
luaL_error(l, "Compiled bytecode not supported.");
}
}
switch(*size){
case 0: return NULL;
case -1: luaL_error(l, strerror(errno));
default:
ld->total += *size;
return ld->buffer;
}
}
// Loads a lua file into a state
int LF_loadfile(lua_State *l)
{
lua_getglobal(l, "REQUEST");
int stack = lua_gettop(l);
lua_pushstring(l, "SCRIPT_FILENAME");
lua_rawget(l, stack);
const char *path = lua_tostring(l, stack+1);
lua_pushstring(l, "SCRIPT_NAME");
lua_rawget(l, stack);
const char *name = lua_tostring(l, stack+2);
LF_loaderdata ld;
ld.total = 0;
ld.fd = open(path, O_RDONLY);
if(ld.fd == -1){
lua_pushstring(l, strerror(errno));
return errno;
}
// Generate a string with an '=' followed by the script name
// this ensures lua will generation a reasonable error
size_t len = strlen(name) + 1;
char scriptname[len + 1];
scriptname[0] = '=';
memcpy(&scriptname[1], name, len);
int r = lua_load(l, &LF_filereader, &ld, scriptname);
close(ld.fd);
return (r == 0 ? 0 : ENOMSG);
}
// Closes a state
void LF_closestate(lua_State *l)
{
lua_close(l);
}

20
src/lua.h

@ -0,0 +1,20 @@
typedef struct {
int committed;
FCGX_Stream *out;
} LF_response;
typedef struct {
size_t memory;
struct timeval cpu;
size_t output;
} LF_limits;
lua_State *LF_newstate(int, char *);
LF_limits *LF_newlimits();
void LF_setlimits(LF_limits *, size_t, size_t, uint32_t, uint32_t);
void LF_enablelimits(lua_State *, LF_limits *);
void LF_parserequest(lua_State *l, FCGX_Request *, LF_response *);
void LF_emptystack(lua_State *);
int LF_loadfile(lua_State *);
void LF_closestate(lua_State *);