handlers can read response before sending req body (fixes #131, #2566)
[lighttpd.git] / src / mod_cml_lua.c
blob519f9c89e349ec82ffa87f84fd942967484144e5
1 #include "first.h"
3 #include "mod_cml.h"
4 #include "mod_cml_funcs.h"
5 #include "log.h"
6 #include "stream.h"
8 #include "stat_cache.h"
10 #include <assert.h>
11 #include <stdio.h>
12 #include <errno.h>
13 #include <time.h>
14 #include <string.h>
16 #define HASHLEN 16
17 typedef unsigned char HASH[HASHLEN];
18 #define HASHHEXLEN 32
19 typedef char HASHHEX[HASHHEXLEN+1];
20 #ifdef USE_OPENSSL
21 #define IN const
22 #else
23 #define IN
24 #endif
25 #define OUT
27 #ifdef HAVE_LUA_H
29 #include <lua.h>
30 #include <lualib.h>
31 #include <lauxlib.h>
33 static int lua_to_c_get_string(lua_State *L, const char *varname, buffer *b) {
34 int curelem = lua_gettop(L);
35 int result;
37 lua_getglobal(L, varname);
39 if (lua_isstring(L, curelem)) {
40 buffer_copy_string(b, lua_tostring(L, curelem));
41 result = 0;
42 } else {
43 result = -1;
46 lua_pop(L, 1);
47 force_assert(curelem == lua_gettop(L));
48 return result;
51 static int lua_to_c_is_table(lua_State *L, const char *varname) {
52 int curelem = lua_gettop(L);
53 int result;
55 lua_getglobal(L, varname);
57 result = lua_istable(L, curelem) ? 1 : 0;
59 lua_pop(L, 1);
60 force_assert(curelem == lua_gettop(L));
61 return result;
64 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) {
65 lua_pushlstring(L, key, key_len);
66 lua_pushlstring(L, val, val_len);
67 lua_settable(L, tbl);
69 return 0;
72 static int cache_export_get_params(lua_State *L, int tbl, buffer *qrystr) {
73 size_t is_key = 1;
74 size_t i, len;
75 char *key = NULL, *val = NULL;
77 key = qrystr->ptr;
79 /* we need the \0 */
80 len = buffer_string_length(qrystr);
81 for (i = 0; i <= len; i++) {
82 switch(qrystr->ptr[i]) {
83 case '=':
84 if (is_key) {
85 val = qrystr->ptr + i + 1;
87 qrystr->ptr[i] = '\0';
89 is_key = 0;
92 break;
93 case '&':
94 case '\0': /* fin symbol */
95 if (!is_key) {
96 /* we need at least a = since the last & */
98 /* terminate the value */
99 qrystr->ptr[i] = '\0';
101 c_to_lua_push(L, tbl,
102 key, strlen(key),
103 val, strlen(val));
106 key = qrystr->ptr + i + 1;
107 val = NULL;
108 is_key = 1;
109 break;
113 return 0;
116 int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn) {
117 lua_State *L;
118 int ret = -1;
119 buffer *b;
121 b = buffer_init();
122 /* push the lua file to the interpreter and see what happends */
123 L = luaL_newstate();
124 luaL_openlibs(L);
126 /* register functions */
127 lua_register(L, "md5", f_crypto_md5);
128 lua_register(L, "file_mtime", f_file_mtime);
129 lua_register(L, "file_isreg", f_file_isreg);
130 lua_register(L, "file_isdir", f_file_isreg);
131 lua_register(L, "dir_files", f_dir_files);
133 #ifdef USE_MEMCACHED
134 lua_pushlightuserdata(L, p->conf.memc);
135 lua_pushcclosure(L, f_memcache_get_long, 1);
136 lua_setglobal(L, "memcache_get_long");
138 lua_pushlightuserdata(L, p->conf.memc);
139 lua_pushcclosure(L, f_memcache_get_string, 1);
140 lua_setglobal(L, "memcache_get_string");
142 lua_pushlightuserdata(L, p->conf.memc);
143 lua_pushcclosure(L, f_memcache_exists, 1);
144 lua_setglobal(L, "memcache_exists");
145 #endif
147 /* register CGI environment */
148 lua_newtable(L);
150 int header_tbl = lua_gettop(L);
152 c_to_lua_push(L, header_tbl, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri));
153 c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path));
154 c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path));
155 c_to_lua_push(L, header_tbl, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root));
156 if (!buffer_string_is_empty(con->request.pathinfo)) {
157 c_to_lua_push(L, header_tbl, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo));
160 c_to_lua_push(L, header_tbl, CONST_STR_LEN("CWD"), CONST_BUF_LEN(p->basedir));
161 c_to_lua_push(L, header_tbl, CONST_STR_LEN("BASEURL"), CONST_BUF_LEN(p->baseurl));
163 lua_setglobal(L, "request");
165 /* register GET parameter */
166 lua_newtable(L);
168 int get_tbl = lua_gettop(L);
170 buffer_copy_buffer(b, con->uri.query);
171 cache_export_get_params(L, get_tbl, b);
172 buffer_reset(b);
174 lua_setglobal(L, "get");
176 /* 2 default constants */
177 lua_pushinteger(L, 0);
178 lua_setglobal(L, "CACHE_HIT");
180 lua_pushinteger(L, 1);
181 lua_setglobal(L, "CACHE_MISS");
183 /* load lua program */
184 ret = luaL_loadfile(L, fn->ptr);
185 if (0 != ret) {
186 log_error_write(srv, __FILE__, __LINE__, "sbsS",
187 "failed loading cml_lua script",
189 ":",
190 lua_tostring(L, -1));
191 goto error;
194 if (lua_pcall(L, 0, 1, 0)) {
195 log_error_write(srv, __FILE__, __LINE__, "sbsS",
196 "failed running cml_lua script",
198 ":",
199 lua_tostring(L, -1));
200 goto error;
203 /* get return value */
204 ret = (int)lua_tointeger(L, -1);
205 lua_pop(L, 1);
207 /* fetch the data from lua */
208 lua_to_c_get_string(L, "trigger_handler", p->trigger_handler);
210 if (0 == lua_to_c_get_string(L, "output_contenttype", b)) {
211 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(b));
214 if (ret == 0) {
215 /* up to now it is a cache-hit, check if all files exist */
217 int curelem;
218 time_t mtime = 0;
220 if (!lua_to_c_is_table(L, "output_include")) {
221 log_error_write(srv, __FILE__, __LINE__, "s",
222 "output_include is missing or not a table");
223 ret = -1;
225 goto error;
228 lua_getglobal(L, "output_include");
229 curelem = lua_gettop(L);
231 /* HOW-TO build a etag ?
232 * as we don't just have one file we have to take the stat()
233 * from all base files, merge them and build the etag from
234 * it later.
236 * The mtime of the content is the mtime of the freshest base file
238 * */
240 lua_pushnil(L); /* first key */
241 while (lua_next(L, curelem) != 0) {
242 /* key' is at index -2 and value' at index -1 */
244 if (lua_isstring(L, -1)) {
245 const char *s = lua_tostring(L, -1);
246 struct stat st;
247 int fd;
249 /* the file is relative, make it absolute */
250 if (s[0] != '/') {
251 buffer_copy_buffer(b, p->basedir);
252 buffer_append_string(b, lua_tostring(L, -1));
253 } else {
254 buffer_copy_string(b, lua_tostring(L, -1));
257 fd = stat_cache_open_rdonly_fstat(srv, con, b, &st);
258 if (fd < 0) {
259 /* stat failed */
261 switch(errno) {
262 case ENOENT:
263 /* a file is missing, call the handler to generate it */
264 if (!buffer_string_is_empty(p->trigger_handler)) {
265 ret = 1; /* cache-miss */
267 log_error_write(srv, __FILE__, __LINE__, "s",
268 "a file is missing, calling handler");
270 break;
271 } else {
272 /* handler not set -> 500 */
273 ret = -1;
275 log_error_write(srv, __FILE__, __LINE__, "s",
276 "a file missing and no handler set");
278 break;
280 break;
281 default:
282 break;
284 } else {
285 chunkqueue_append_file_fd(con->write_queue, b, fd, 0, st.st_size);
286 if (st.st_mtime > mtime) mtime = st.st_mtime;
288 } else {
289 /* not a string */
290 ret = -1;
291 log_error_write(srv, __FILE__, __LINE__, "s",
292 "not a string");
293 break;
296 lua_pop(L, 1); /* removes value'; keeps key' for next iteration */
299 lua_settop(L, curelem - 1);
301 if (ret == 0) {
302 data_string *ds;
303 char timebuf[sizeof("Sat, 23 Jul 2005 21:20:01 GMT")];
305 con->file_finished = 1;
307 ds = (data_string *)array_get_element(con->response.headers, "Last-Modified");
308 if (0 == mtime) mtime = time(NULL); /* default last-modified to now */
310 /* no Last-Modified specified */
311 if (NULL == ds) {
313 strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&mtime));
315 response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), timebuf, sizeof(timebuf) - 1);
316 ds = (data_string *)array_get_element(con->response.headers, "Last-Modified");
317 force_assert(NULL != ds);
320 if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, ds->value)) {
321 /* ok, the client already has our content,
322 * no need to send it again */
324 chunkqueue_reset(con->write_queue);
325 ret = 0; /* cache-hit */
327 } else {
328 chunkqueue_reset(con->write_queue);
332 if (ret == 1 && !buffer_string_is_empty(p->trigger_handler)) {
333 /* cache-miss */
334 buffer_copy_buffer(con->uri.path, p->baseurl);
335 buffer_append_string_buffer(con->uri.path, p->trigger_handler);
337 buffer_copy_buffer(con->physical.path, p->basedir);
338 buffer_append_string_buffer(con->physical.path, p->trigger_handler);
340 chunkqueue_reset(con->write_queue);
343 error:
344 lua_close(L);
346 buffer_free(b);
348 return ret /* cache-error */;
350 #else
351 int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn) {
352 UNUSED(srv);
353 UNUSED(con);
354 UNUSED(p);
355 UNUSED(fn);
356 /* error */
357 return -1;
359 #endif