lighttpd 1.4.x
https://www.lighttpd.net/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
468 lines
10 KiB
468 lines
10 KiB
#include <assert.h> |
|
#include <stdio.h> |
|
#include <errno.h> |
|
#include <time.h> |
|
|
|
#include "mod_cml.h" |
|
#include "mod_cml_funcs.h" |
|
#include "log.h" |
|
#include "stream.h" |
|
|
|
#include "stat_cache.h" |
|
|
|
#ifdef USE_OPENSSL |
|
# include <openssl/md5.h> |
|
#else |
|
# include "md5.h" |
|
#endif |
|
|
|
#define HASHLEN 16 |
|
typedef unsigned char HASH[HASHLEN]; |
|
#define HASHHEXLEN 32 |
|
typedef char HASHHEX[HASHHEXLEN+1]; |
|
#ifdef USE_OPENSSL |
|
#define IN const |
|
#else |
|
#define IN |
|
#endif |
|
#define OUT |
|
|
|
#ifdef HAVE_LUA_H |
|
|
|
#include <lua.h> |
|
#include <lualib.h> |
|
#include <lauxlib.h> |
|
|
|
typedef struct { |
|
stream st; |
|
int done; |
|
} readme; |
|
|
|
static const char * load_file(lua_State *L, void *data, size_t *size) { |
|
readme *rm = data; |
|
|
|
UNUSED(L); |
|
|
|
if (rm->done) return 0; |
|
|
|
*size = rm->st.size; |
|
rm->done = 1; |
|
return rm->st.start; |
|
} |
|
|
|
static int lua_to_c_get_string(lua_State *L, const char *varname, buffer *b) { |
|
int curelem; |
|
|
|
lua_pushstring(L, varname); |
|
|
|
curelem = lua_gettop(L); |
|
lua_gettable(L, LUA_GLOBALSINDEX); |
|
|
|
/* it should be a table */ |
|
if (!lua_isstring(L, curelem)) { |
|
lua_settop(L, curelem - 1); |
|
|
|
return -1; |
|
} |
|
|
|
buffer_copy_string(b, lua_tostring(L, curelem)); |
|
|
|
lua_pop(L, 1); |
|
|
|
assert(curelem - 1 == lua_gettop(L)); |
|
|
|
return 0; |
|
} |
|
|
|
static int lua_to_c_is_table(lua_State *L, const char *varname) { |
|
int curelem; |
|
|
|
lua_pushstring(L, varname); |
|
|
|
curelem = lua_gettop(L); |
|
lua_gettable(L, LUA_GLOBALSINDEX); |
|
|
|
/* it should be a table */ |
|
if (!lua_istable(L, curelem)) { |
|
lua_settop(L, curelem - 1); |
|
|
|
return 0; |
|
} |
|
|
|
lua_settop(L, curelem - 1); |
|
|
|
assert(curelem - 1 == lua_gettop(L)); |
|
|
|
return 1; |
|
} |
|
|
|
static int c_to_lua_push(lua_State *L, int tbl, const char *key, size_t key_len, const char *val, size_t val_len) { |
|
lua_pushlstring(L, key, key_len); |
|
lua_pushlstring(L, val, val_len); |
|
lua_settable(L, tbl); |
|
|
|
return 0; |
|
} |
|
|
|
|
|
int cache_export_get_params(lua_State *L, int tbl, buffer *qrystr) { |
|
size_t is_key = 1; |
|
size_t i; |
|
char *key = NULL, *val = NULL; |
|
|
|
key = qrystr->ptr; |
|
|
|
/* we need the \0 */ |
|
for (i = 0; i < qrystr->used; i++) { |
|
switch(qrystr->ptr[i]) { |
|
case '=': |
|
if (is_key) { |
|
val = qrystr->ptr + i + 1; |
|
|
|
qrystr->ptr[i] = '\0'; |
|
|
|
is_key = 0; |
|
} |
|
|
|
break; |
|
case '&': |
|
case '\0': /* fin symbol */ |
|
if (!is_key) { |
|
/* we need at least a = since the last & */ |
|
|
|
/* terminate the value */ |
|
qrystr->ptr[i] = '\0'; |
|
|
|
c_to_lua_push(L, tbl, |
|
key, strlen(key), |
|
val, strlen(val)); |
|
} |
|
|
|
key = qrystr->ptr + i + 1; |
|
val = NULL; |
|
is_key = 1; |
|
break; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
#if 0 |
|
int cache_export_cookie_params(server *srv, connection *con, plugin_data *p) { |
|
data_unset *d; |
|
|
|
UNUSED(srv); |
|
|
|
if (NULL != (d = array_get_element(con->request.headers, "Cookie"))) { |
|
data_string *ds = (data_string *)d; |
|
size_t key = 0, value = 0; |
|
size_t is_key = 1, is_sid = 0; |
|
size_t i; |
|
|
|
/* found COOKIE */ |
|
if (!DATA_IS_STRING(d)) return -1; |
|
if (ds->value->used == 0) return -1; |
|
|
|
if (ds->value->ptr[0] == '\0' || |
|
ds->value->ptr[0] == '=' || |
|
ds->value->ptr[0] == ';') return -1; |
|
|
|
buffer_reset(p->session_id); |
|
for (i = 0; i < ds->value->used; i++) { |
|
switch(ds->value->ptr[i]) { |
|
case '=': |
|
if (is_key) { |
|
if (0 == strncmp(ds->value->ptr + key, "PHPSESSID", i - key)) { |
|
/* found PHP-session-id-key */ |
|
is_sid = 1; |
|
} |
|
value = i + 1; |
|
|
|
is_key = 0; |
|
} |
|
|
|
break; |
|
case ';': |
|
if (is_sid) { |
|
buffer_copy_string_len(p->session_id, ds->value->ptr + value, i - value); |
|
} |
|
|
|
is_sid = 0; |
|
key = i + 1; |
|
value = 0; |
|
is_key = 1; |
|
break; |
|
case ' ': |
|
if (is_key == 1 && key == i) key = i + 1; |
|
if (is_key == 0 && value == i) value = i + 1; |
|
break; |
|
case '\0': |
|
if (is_sid) { |
|
buffer_copy_string_len(p->session_id, ds->value->ptr + value, i - value); |
|
} |
|
/* fin */ |
|
break; |
|
} |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn) { |
|
lua_State *L; |
|
readme rm; |
|
int ret = -1; |
|
buffer *b = buffer_init(); |
|
int header_tbl = 0; |
|
|
|
rm.done = 0; |
|
stream_open(&rm.st, fn); |
|
|
|
/* push the lua file to the interpreter and see what happends */ |
|
L = luaL_newstate(); |
|
luaL_openlibs(L); |
|
|
|
/* register functions */ |
|
lua_register(L, "md5", f_crypto_md5); |
|
lua_register(L, "file_mtime", f_file_mtime); |
|
lua_register(L, "file_isreg", f_file_isreg); |
|
lua_register(L, "file_isdir", f_file_isreg); |
|
lua_register(L, "dir_files", f_dir_files); |
|
|
|
#ifdef HAVE_MEMCACHE_H |
|
lua_pushliteral(L, "memcache_get_long"); |
|
lua_pushlightuserdata(L, p->conf.mc); |
|
lua_pushcclosure(L, f_memcache_get_long, 1); |
|
lua_settable(L, LUA_GLOBALSINDEX); |
|
|
|
lua_pushliteral(L, "memcache_get_string"); |
|
lua_pushlightuserdata(L, p->conf.mc); |
|
lua_pushcclosure(L, f_memcache_get_string, 1); |
|
lua_settable(L, LUA_GLOBALSINDEX); |
|
|
|
lua_pushliteral(L, "memcache_exists"); |
|
lua_pushlightuserdata(L, p->conf.mc); |
|
lua_pushcclosure(L, f_memcache_exists, 1); |
|
lua_settable(L, LUA_GLOBALSINDEX); |
|
#endif |
|
/* register CGI environment */ |
|
lua_pushliteral(L, "request"); |
|
lua_newtable(L); |
|
lua_settable(L, LUA_GLOBALSINDEX); |
|
|
|
lua_pushliteral(L, "request"); |
|
header_tbl = lua_gettop(L); |
|
lua_gettable(L, LUA_GLOBALSINDEX); |
|
|
|
c_to_lua_push(L, header_tbl, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)); |
|
c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); |
|
c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path)); |
|
c_to_lua_push(L, header_tbl, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root)); |
|
if (!buffer_is_empty(con->request.pathinfo)) { |
|
c_to_lua_push(L, header_tbl, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)); |
|
} |
|
|
|
c_to_lua_push(L, header_tbl, CONST_STR_LEN("CWD"), CONST_BUF_LEN(p->basedir)); |
|
c_to_lua_push(L, header_tbl, CONST_STR_LEN("BASEURL"), CONST_BUF_LEN(p->baseurl)); |
|
|
|
/* register GET parameter */ |
|
lua_pushliteral(L, "get"); |
|
lua_newtable(L); |
|
lua_settable(L, LUA_GLOBALSINDEX); |
|
|
|
lua_pushliteral(L, "get"); |
|
header_tbl = lua_gettop(L); |
|
lua_gettable(L, LUA_GLOBALSINDEX); |
|
|
|
buffer_copy_string_buffer(b, con->uri.query); |
|
cache_export_get_params(L, header_tbl, b); |
|
buffer_reset(b); |
|
|
|
/* 2 default constants */ |
|
lua_pushliteral(L, "CACHE_HIT"); |
|
lua_pushboolean(L, 0); |
|
lua_settable(L, LUA_GLOBALSINDEX); |
|
|
|
lua_pushliteral(L, "CACHE_MISS"); |
|
lua_pushboolean(L, 1); |
|
lua_settable(L, LUA_GLOBALSINDEX); |
|
|
|
/* load lua program */ |
|
if (lua_load(L, load_file, &rm, fn->ptr) || lua_pcall(L,0,1,0)) { |
|
log_error_write(srv, __FILE__, __LINE__, "s", |
|
lua_tostring(L,-1)); |
|
|
|
goto error; |
|
} |
|
|
|
/* get return value */ |
|
ret = (int)lua_tonumber(L, -1); |
|
lua_pop(L, 1); |
|
|
|
/* fetch the data from lua */ |
|
lua_to_c_get_string(L, "trigger_handler", p->trigger_handler); |
|
|
|
if (0 == lua_to_c_get_string(L, "output_contenttype", b)) { |
|
response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(b)); |
|
} |
|
|
|
if (ret == 0) { |
|
/* up to now it is a cache-hit, check if all files exist */ |
|
|
|
int curelem; |
|
time_t mtime = 0; |
|
|
|
if (!lua_to_c_is_table(L, "output_include")) { |
|
log_error_write(srv, __FILE__, __LINE__, "s", |
|
"output_include is missing or not a table"); |
|
ret = -1; |
|
|
|
goto error; |
|
} |
|
|
|
lua_pushstring(L, "output_include"); |
|
|
|
curelem = lua_gettop(L); |
|
lua_gettable(L, LUA_GLOBALSINDEX); |
|
|
|
/* HOW-TO build a etag ? |
|
* as we don't just have one file we have to take the stat() |
|
* from all base files, merge them and build the etag from |
|
* it later. |
|
* |
|
* The mtime of the content is the mtime of the freshest base file |
|
* |
|
* */ |
|
|
|
lua_pushnil(L); /* first key */ |
|
while (lua_next(L, curelem) != 0) { |
|
stat_cache_entry *sce = NULL; |
|
/* key' is at index -2 and value' at index -1 */ |
|
|
|
if (lua_isstring(L, -1)) { |
|
const char *s = lua_tostring(L, -1); |
|
|
|
/* the file is relative, make it absolute */ |
|
if (s[0] != '/') { |
|
buffer_copy_string_buffer(b, p->basedir); |
|
buffer_append_string(b, lua_tostring(L, -1)); |
|
} else { |
|
buffer_copy_string(b, lua_tostring(L, -1)); |
|
} |
|
|
|
if (HANDLER_ERROR == stat_cache_get_entry(srv, con, b, &sce)) { |
|
/* stat failed */ |
|
|
|
switch(errno) { |
|
case ENOENT: |
|
/* a file is missing, call the handler to generate it */ |
|
if (!buffer_is_empty(p->trigger_handler)) { |
|
ret = 1; /* cache-miss */ |
|
|
|
log_error_write(srv, __FILE__, __LINE__, "s", |
|
"a file is missing, calling handler"); |
|
|
|
break; |
|
} else { |
|
/* handler not set -> 500 */ |
|
ret = -1; |
|
|
|
log_error_write(srv, __FILE__, __LINE__, "s", |
|
"a file missing and no handler set"); |
|
|
|
break; |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
} else { |
|
chunkqueue_append_file(con->write_queue, b, 0, sce->st.st_size); |
|
if (sce->st.st_mtime > mtime) mtime = sce->st.st_mtime; |
|
} |
|
} else { |
|
/* not a string */ |
|
ret = -1; |
|
log_error_write(srv, __FILE__, __LINE__, "s", |
|
"not a string"); |
|
break; |
|
} |
|
|
|
lua_pop(L, 1); /* removes value'; keeps key' for next iteration */ |
|
} |
|
|
|
lua_settop(L, curelem - 1); |
|
|
|
if (ret == 0) { |
|
data_string *ds; |
|
char timebuf[sizeof("Sat, 23 Jul 2005 21:20:01 GMT")]; |
|
buffer tbuf; |
|
|
|
con->file_finished = 1; |
|
|
|
ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"); |
|
|
|
/* no Last-Modified specified */ |
|
if ((mtime) && (NULL == ds)) { |
|
|
|
strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&mtime)); |
|
|
|
response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), timebuf, sizeof(timebuf) - 1); |
|
|
|
|
|
tbuf.ptr = timebuf; |
|
tbuf.used = sizeof(timebuf); |
|
tbuf.size = sizeof(timebuf); |
|
} else if (ds) { |
|
tbuf.ptr = ds->value->ptr; |
|
tbuf.used = ds->value->used; |
|
tbuf.size = ds->value->size; |
|
} else { |
|
tbuf.size = 0; |
|
tbuf.used = 0; |
|
tbuf.ptr = NULL; |
|
} |
|
|
|
if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, &tbuf)) { |
|
/* ok, the client already has our content, |
|
* no need to send it again */ |
|
|
|
chunkqueue_reset(con->write_queue); |
|
ret = 0; /* cache-hit */ |
|
} |
|
} else { |
|
chunkqueue_reset(con->write_queue); |
|
} |
|
} |
|
|
|
if (ret == 1 && !buffer_is_empty(p->trigger_handler)) { |
|
/* cache-miss */ |
|
buffer_copy_string_buffer(con->uri.path, p->baseurl); |
|
buffer_append_string_buffer(con->uri.path, p->trigger_handler); |
|
|
|
buffer_copy_string_buffer(con->physical.path, p->basedir); |
|
buffer_append_string_buffer(con->physical.path, p->trigger_handler); |
|
|
|
chunkqueue_reset(con->write_queue); |
|
} |
|
|
|
error: |
|
lua_close(L); |
|
|
|
stream_close(&rm.st); |
|
buffer_free(b); |
|
|
|
return ret /* cache-error */; |
|
} |
|
#else |
|
int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn) { |
|
UNUSED(srv); |
|
UNUSED(con); |
|
UNUSED(p); |
|
UNUSED(fn); |
|
/* error */ |
|
return -1; |
|
} |
|
#endif
|
|
|