Squash merge into master branch
This commit is contained in:
parent
9857fcdf3f
commit
95e1a981ab
|
@ -0,0 +1,3 @@
|
|||
*.swp
|
||||
*.o
|
||||
lua-fastcgi
|
|
@ -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.
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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"
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 *);
|
|
@ -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); }
|
|
@ -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 *);
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
typedef struct {
|
||||
LF_config *config;
|
||||
int socket;
|
||||
} LF_params;
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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 *);
|
Reference in New Issue