| #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 |