xref: /lighttpd1.4/src/mod_magnet.c (revision 3392e8fb)
1 #include "first.h"
2 
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
6 #include "http_chunk.h"
7 #include "http_header.h"
8 #include "response.h"   /* http_response_send_1xx() */
9 
10 #include "plugin.h"
11 
12 #include "mod_magnet_cache.h"
13 #include "sock_addr.h"
14 #include "stat_cache.h"
15 #include "status_counter.h"
16 
17 #include <stdlib.h>
18 #include <string.h>
19 #include <setjmp.h>
20 
21 #include <lua.h>
22 #include <lauxlib.h>
23 
24 #define LUA_RIDX_LIGHTTPD_REQUEST "lighty.request"
25 
26 #define MAGNET_RESTART_REQUEST      99
27 
28 /* plugin config for all request/connections */
29 
30 static jmp_buf exceptionjmp;
31 
32 typedef struct {
33     const array *url_raw;
34     const array *physical_path;
35     const array *response_start;
36     int stage;
37 } plugin_config;
38 
39 typedef struct {
40     PLUGIN_DATA;
41     plugin_config defaults;
42     plugin_config conf;
43 
44     script_cache cache;
45 } plugin_data;
46 
47 INIT_FUNC(mod_magnet_init) {
48     return calloc(1, sizeof(plugin_data));
49 }
50 
51 FREE_FUNC(mod_magnet_free) {
52     plugin_data *p = p_d;
53     script_cache_free_data(&p->cache);
54 }
55 
56 static void mod_magnet_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
57     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
58       case 0: /* magnet.attract-raw-url-to */
59         pconf->url_raw = cpv->v.a;
60         break;
61       case 1: /* magnet.attract-physical-path-to */
62         pconf->physical_path = cpv->v.a;
63         break;
64       case 2: /* magnet.attract-response-start-to */
65         pconf->response_start = cpv->v.a;
66         break;
67       default:/* should not happen */
68         return;
69     }
70 }
71 
72 static void mod_magnet_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
73     do {
74         mod_magnet_merge_config_cpv(pconf, cpv);
75     } while ((++cpv)->k_id != -1);
76 }
77 
78 static void mod_magnet_patch_config(request_st * const r, plugin_data * const p) {
79     p->conf = p->defaults; /* copy small struct instead of memcpy() */
80     /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
81     for (int i = 1, used = p->nconfig; i < used; ++i) {
82         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
83             mod_magnet_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
84     }
85 }
86 
87 SETDEFAULTS_FUNC(mod_magnet_set_defaults) {
88     static const config_plugin_keys_t cpk[] = {
89       { CONST_STR_LEN("magnet.attract-raw-url-to"),
90         T_CONFIG_ARRAY_VLIST,
91         T_CONFIG_SCOPE_CONNECTION }
92      ,{ CONST_STR_LEN("magnet.attract-physical-path-to"),
93         T_CONFIG_ARRAY_VLIST,
94         T_CONFIG_SCOPE_CONNECTION }
95      ,{ CONST_STR_LEN("magnet.attract-response-start-to"),
96         T_CONFIG_ARRAY_VLIST,
97         T_CONFIG_SCOPE_CONNECTION }
98      ,{ NULL, 0,
99         T_CONFIG_UNSET,
100         T_CONFIG_SCOPE_UNSET }
101     };
102 
103     plugin_data * const p = p_d;
104     if (!config_plugin_values_init(srv, p, cpk, "mod_magnet"))
105         return HANDLER_ERROR;
106 
107     /* process and validate config directives
108      * (init i to 0 if global context; to 1 to skip empty global context) */
109     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
110         const config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
111         for (; -1 != cpv->k_id; ++cpv) {
112             switch (cpv->k_id) {
113               case 0: /* magnet.attract-raw-url-to */
114               case 1: /* magnet.attract-physical-path-to */
115               case 2: /* magnet.attract-response-start-to */
116                 for (uint32_t j = 0; j < cpv->v.a->used; ++j) {
117                     data_string *ds = (data_string *)cpv->v.a->data[j];
118                     if (buffer_string_is_empty(&ds->value)) {
119                         log_error(srv->errh, __FILE__, __LINE__,
120                           "unexpected (blank) value for %s; "
121                           "expected list of \"scriptpath\"", cpk[cpv->k_id].k);
122                         return HANDLER_ERROR;
123                     }
124                 }
125                 break;
126               default:/* should not happen */
127                 break;
128             }
129         }
130     }
131 
132     /* initialize p->defaults from global config context */
133     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
134         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
135         if (-1 != cpv->k_id)
136             mod_magnet_merge_config(&p->defaults, cpv);
137     }
138 
139     return HANDLER_GO_ON;
140 }
141 
142 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
143 /* lua5.1 backward compat definition */
144 static void lua_pushglobaltable(lua_State *L) { /* (-0, +1, -) */
145 	lua_pushvalue(L, LUA_GLOBALSINDEX);
146 }
147 #endif
148 
149 static void magnet_setfenv_mainfn(lua_State *L, int funcIndex) { /* (-1, 0, -) */
150 #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 502
151 	/* set "_ENV" upvalue, which should be the first upvalue of a "main" lua
152 	 * function if it uses any global names
153 	 */
154 
155 	const char* first_upvalue_name = lua_getupvalue(L, funcIndex, 1);
156 	if (NULL == first_upvalue_name) return; /* doesn't have any upvalues */
157 	lua_pop(L, 1); /* only need the name of the upvalue, not the value */
158 
159 	if (0 != strcmp(first_upvalue_name, "_ENV")) return;
160 
161 	if (NULL == lua_setupvalue(L, funcIndex, 1)) {
162 		/* pop value if lua_setupvalue didn't set the (not existing) upvalue */
163 		lua_pop(L, 1);
164 	}
165 #else
166 	lua_setfenv(L, funcIndex);
167 #endif
168 }
169 
170 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
171 /* lua 5.2 already supports __pairs */
172 
173 /* See http://lua-users.org/wiki/GeneralizedPairsAndIpairs for implementation details.
174  * Override the default pairs() function to allow us to use a __pairs metakey
175  */
176 static int magnet_pairs(lua_State *L) {
177 	luaL_checkany(L, 1); /* "self" */
178 
179 	if (luaL_getmetafield(L, 1, "__pairs")) {
180 		/* call __pairs(self) */
181 		lua_pushvalue(L, 1);
182 		lua_call(L, 1, 3);
183 	} else {
184 		/* call <original-pairs-method>(self) */
185 		lua_pushvalue(L, lua_upvalueindex(1));
186 		lua_pushvalue(L, 1);
187 		lua_call(L, 1, 3);
188 	}
189 	return 3;
190 }
191 #endif
192 
193 static void magnet_push_buffer(lua_State *L, const buffer *b) {
194     if (!buffer_is_empty(b))
195         lua_pushlstring(L, CONST_BUF_LEN(b));
196     else
197         lua_pushnil(L);
198 }
199 
200 #if 0
201 static int magnet_array_get_element(lua_State *L, const array *a) {
202     /* __index: param 1 is the (empty) table the value was not found in */
203     size_t klen;
204     const char * const k = luaL_checklstring(L, 2, &klen);
205     const data_string * const ds = (const data_string *)
206       array_get_element_klen(a, k, klen);
207     magnet_push_buffer(L, NULL != ds ? &ds->value : NULL);
208     return 1;
209 }
210 #endif
211 
212 /* Define a function that will iterate over an array* (in upval 1) using current position (upval 2) */
213 static int magnet_array_next(lua_State *L) {
214 	data_unset *du;
215 	data_string *ds;
216 	data_integer *di;
217 
218 	size_t pos = lua_tointeger(L, lua_upvalueindex(1));
219 	array *a = lua_touserdata(L, lua_upvalueindex(2));
220 
221 	lua_settop(L, 0);
222 
223 	if (pos >= a->used) return 0;
224 	if (NULL != (du = a->data[pos])) {
225 		lua_pushlstring(L, CONST_BUF_LEN(&du->key));
226 		switch (du->type) {
227 			case TYPE_STRING:
228 				ds = (data_string *)du;
229 				magnet_push_buffer(L, &ds->value);
230 				break;
231 			case TYPE_INTEGER:
232 				di = (data_integer *)du;
233 				lua_pushinteger(L, di->value);
234 				break;
235 			default:
236 				lua_pushnil(L);
237 				break;
238 		}
239 
240 		/* Update our positional upval to reflect our new current position */
241 		pos++;
242 		lua_pushinteger(L, pos);
243 		lua_replace(L, lua_upvalueindex(1));
244 
245 		/* Returning 2 items on the stack (key, value) */
246 		return 2;
247 	}
248 	return 0;
249 }
250 
251 /* Create the closure necessary to iterate over the array *a with the above function */
252 static int magnet_array_pairs(lua_State *L, array *a) {
253 	lua_pushinteger(L, 0); /* Push our current pos (the start) into upval 1 */
254 	lua_pushlightuserdata(L, a); /* Push our array *a into upval 2 */
255 	lua_pushcclosure(L, magnet_array_next, 2); /* Push our new closure with 2 upvals */
256 	return 1;
257 }
258 
259 static request_st * magnet_get_request(lua_State *L) {
260 	lua_getfield(L, LUA_REGISTRYINDEX, LUA_RIDX_LIGHTTPD_REQUEST);
261 	request_st * const r = lua_touserdata(L, -1);
262 	lua_pop(L, 1);
263 	return r;
264 }
265 
266 typedef struct {
267 	const char *ptr;
268 	size_t len;
269 } const_buffer;
270 
271 static const_buffer magnet_checkconstbuffer(lua_State *L, int idx) {
272 	const_buffer cb;
273 	cb.ptr = luaL_checklstring(L, idx, &cb.len);
274 	return cb;
275 }
276 
277 static buffer* magnet_checkbuffer(lua_State *L, int idx) {
278 	const_buffer cb = magnet_checkconstbuffer(L, idx);
279 	buffer *b = buffer_init();
280 	buffer_copy_string_len(b, cb.ptr, cb.len);
281 	return b;
282 }
283 
284 static int magnet_print(lua_State *L) {
285 	const_buffer cb = magnet_checkconstbuffer(L, 1);
286 	request_st * const r = magnet_get_request(L);
287 	log_error(r->conf.errh, __FILE__, __LINE__, "(lua-print) %s", cb.ptr);
288 	return 0;
289 }
290 
291 static int magnet_stat(lua_State *L) {
292 	buffer * const sb = magnet_checkbuffer(L, 1);
293 	stat_cache_entry * const sce = (!buffer_string_is_empty(sb))
294 	  ? stat_cache_get_entry(sb)
295 	  : NULL;
296 	buffer_free(sb);
297 	if (NULL == sce) {
298 		lua_pushnil(L);
299 		return 1;
300 	}
301 
302 	lua_newtable(L); // return value
303 
304 	lua_pushboolean(L, S_ISREG(sce->st.st_mode));
305 	lua_setfield(L, -2, "is_file");
306 
307 	lua_pushboolean(L, S_ISDIR(sce->st.st_mode));
308 	lua_setfield(L, -2, "is_dir");
309 
310 	lua_pushboolean(L, S_ISCHR(sce->st.st_mode));
311 	lua_setfield(L, -2, "is_char");
312 
313 	lua_pushboolean(L, S_ISBLK(sce->st.st_mode));
314 	lua_setfield(L, -2, "is_block");
315 
316 	lua_pushboolean(L, S_ISSOCK(sce->st.st_mode));
317 	lua_setfield(L, -2, "is_socket");
318 
319 	lua_pushboolean(L, S_ISLNK(sce->st.st_mode));
320 	lua_setfield(L, -2, "is_link");
321 
322 	lua_pushboolean(L, S_ISFIFO(sce->st.st_mode));
323 	lua_setfield(L, -2, "is_fifo");
324 
325 	lua_pushinteger(L, sce->st.st_mtime);
326 	lua_setfield(L, -2, "st_mtime");
327 
328 	lua_pushinteger(L, sce->st.st_ctime);
329 	lua_setfield(L, -2, "st_ctime");
330 
331 	lua_pushinteger(L, sce->st.st_atime);
332 	lua_setfield(L, -2, "st_atime");
333 
334 	lua_pushinteger(L, sce->st.st_uid);
335 	lua_setfield(L, -2, "st_uid");
336 
337 	lua_pushinteger(L, sce->st.st_gid);
338 	lua_setfield(L, -2, "st_gid");
339 
340 	lua_pushinteger(L, sce->st.st_size);
341 	lua_setfield(L, -2, "st_size");
342 
343 	lua_pushinteger(L, sce->st.st_ino);
344 	lua_setfield(L, -2, "st_ino");
345 
346 	request_st * const r = magnet_get_request(L);
347 	const buffer *etag = stat_cache_etag_get(sce, r->conf.etag_flags);
348 	if (!buffer_string_is_empty(etag)) {
349 		lua_pushlstring(L, CONST_BUF_LEN(etag));
350 	} else {
351 		lua_pushnil(L);
352 	}
353 	lua_setfield(L, -2, "etag");
354 
355 	const buffer *content_type = stat_cache_content_type_get(sce, r);
356 	if (!buffer_string_is_empty(content_type)) {
357 		lua_pushlstring(L, CONST_BUF_LEN(content_type));
358 	} else {
359 		lua_pushnil(L);
360 	}
361 	lua_setfield(L, -2, "content-type");
362 
363 	return 1;
364 }
365 
366 
367 static int magnet_atpanic(lua_State *L) {
368 	request_st * const r = magnet_get_request(L);
369 	log_error(r->conf.errh, __FILE__, __LINE__, "(lua-atpanic) %s",
370 	          lua_isstring(L, 1) ? lua_tostring(L, 1) : "");
371 	longjmp(exceptionjmp, 1);
372 }
373 
374 static int magnet_reqhdr_get(lua_State *L) {
375     /* __index: param 1 is the (empty) table the value was not found in */
376     size_t klen;
377     const char * const k = luaL_checklstring(L, 2, &klen);
378     request_st * const r = magnet_get_request(L);
379     const int id = http_header_hkey_get(k, (uint32_t)klen);
380     const buffer * const vb = http_header_request_get(r, id, k, klen);
381     magnet_push_buffer(L, NULL != vb ? vb : NULL);
382     return 1;
383 }
384 
385 static int magnet_reqhdr_pairs(lua_State *L) {
386 	request_st * const r = magnet_get_request(L);
387 	return magnet_array_pairs(L, &r->rqst_headers);
388 }
389 
390 static int magnet_status_get(lua_State *L) {
391 	/* __index: param 1 is the (empty) table the value was not found in */
392 	const_buffer key = magnet_checkconstbuffer(L, 2);
393 	int *i = status_counter_get_counter(key.ptr, key.len);
394 	lua_pushinteger(L, (lua_Integer)*i);
395 
396 	return 1;
397 }
398 
399 static int magnet_status_set(lua_State *L) {
400 	/* __newindex: param 1 is the (empty) table the value is supposed to be set in */
401 	const_buffer key = magnet_checkconstbuffer(L, 2);
402 	int counter = (int) luaL_checkinteger(L, 3);
403 
404 	status_counter_set(key.ptr, key.len, counter);
405 
406 	return 0;
407 }
408 
409 static int magnet_status_pairs(lua_State *L) {
410 	return magnet_array_pairs(L, &plugin_stats);
411 }
412 
413 typedef struct {
414 	const char *name;
415 	enum {
416 		MAGNET_ENV_UNSET,
417 
418 		MAGNET_ENV_PHYSICAL_PATH,
419 		MAGNET_ENV_PHYSICAL_REL_PATH,
420 		MAGNET_ENV_PHYSICAL_DOC_ROOT,
421 		MAGNET_ENV_PHYSICAL_BASEDIR,
422 
423 		MAGNET_ENV_URI_PATH,
424 		MAGNET_ENV_URI_PATH_RAW,
425 		MAGNET_ENV_URI_SCHEME,
426 		MAGNET_ENV_URI_AUTHORITY,
427 		MAGNET_ENV_URI_QUERY,
428 
429 		MAGNET_ENV_REQUEST_METHOD,
430 		MAGNET_ENV_REQUEST_URI,
431 		MAGNET_ENV_REQUEST_ORIG_URI,
432 		MAGNET_ENV_REQUEST_PATH_INFO,
433 		MAGNET_ENV_REQUEST_REMOTE_IP,
434 		MAGNET_ENV_REQUEST_SERVER_ADDR,
435 		MAGNET_ENV_REQUEST_PROTOCOL,
436 
437 		MAGNET_ENV_RESPONSE_HTTP_STATUS,
438 		MAGNET_ENV_RESPONSE_BODY_LENGTH,
439 		MAGNET_ENV_RESPONSE_BODY
440 	} type;
441 } magnet_env_t;
442 
443 static const magnet_env_t magnet_env[] = {
444 	{ "physical.path", MAGNET_ENV_PHYSICAL_PATH },
445 	{ "physical.rel-path", MAGNET_ENV_PHYSICAL_REL_PATH },
446 	{ "physical.doc-root", MAGNET_ENV_PHYSICAL_DOC_ROOT },
447 	{ "physical.basedir", MAGNET_ENV_PHYSICAL_BASEDIR },
448 
449 	{ "uri.path", MAGNET_ENV_URI_PATH },
450 	{ "uri.path-raw", MAGNET_ENV_URI_PATH_RAW },
451 	{ "uri.scheme", MAGNET_ENV_URI_SCHEME },
452 	{ "uri.authority", MAGNET_ENV_URI_AUTHORITY },
453 	{ "uri.query", MAGNET_ENV_URI_QUERY },
454 
455 	{ "request.method", MAGNET_ENV_REQUEST_METHOD },
456 	{ "request.uri", MAGNET_ENV_REQUEST_URI },
457 	{ "request.orig-uri", MAGNET_ENV_REQUEST_ORIG_URI },
458 	{ "request.path-info", MAGNET_ENV_REQUEST_PATH_INFO },
459 	{ "request.remote-ip", MAGNET_ENV_REQUEST_REMOTE_IP },
460 	{ "request.remote-addr", MAGNET_ENV_REQUEST_REMOTE_IP },
461 	{ "request.server-addr", MAGNET_ENV_REQUEST_SERVER_ADDR },
462 	{ "request.protocol", MAGNET_ENV_REQUEST_PROTOCOL },
463 
464 	{ "response.http-status", MAGNET_ENV_RESPONSE_HTTP_STATUS },
465 	{ "response.body-length", MAGNET_ENV_RESPONSE_BODY_LENGTH },
466 	{ "response.body", MAGNET_ENV_RESPONSE_BODY },
467 
468 	{ NULL, MAGNET_ENV_UNSET }
469 };
470 
471 static buffer *magnet_env_get_buffer_by_id(request_st * const r, int id) {
472 	buffer *dest = NULL;
473 
474 	/**
475 	 * map all internal variables to lua
476 	 *
477 	 */
478 
479 	switch (id) {
480 	case MAGNET_ENV_PHYSICAL_PATH: dest = &r->physical.path; break;
481 	case MAGNET_ENV_PHYSICAL_REL_PATH: dest = &r->physical.rel_path; break;
482 	case MAGNET_ENV_PHYSICAL_DOC_ROOT: dest = &r->physical.doc_root; break;
483 	case MAGNET_ENV_PHYSICAL_BASEDIR: dest = &r->physical.basedir; break;
484 
485 	case MAGNET_ENV_URI_PATH: dest = &r->uri.path; break;
486 	case MAGNET_ENV_URI_PATH_RAW:
487 	    {
488 		dest = r->tmp_buf;
489 		buffer_clear(dest);
490 		uint32_t len = buffer_string_length(&r->target);
491 		char *qmark = memchr(r->target.ptr, '?', len);
492 		buffer_copy_string_len(dest, r->target.ptr, qmark ? (uint32_t)(qmark - r->target.ptr) : len);
493 		break;
494 	    }
495 	case MAGNET_ENV_URI_SCHEME: dest = &r->uri.scheme; break;
496 	case MAGNET_ENV_URI_AUTHORITY: dest = &r->uri.authority; break;
497 	case MAGNET_ENV_URI_QUERY: dest = &r->uri.query; break;
498 
499 	case MAGNET_ENV_REQUEST_METHOD:
500 		dest = r->tmp_buf;
501 		buffer_clear(dest);
502 		http_method_append(dest, r->http_method);
503 		break;
504 	case MAGNET_ENV_REQUEST_URI:      dest = &r->target; break;
505 	case MAGNET_ENV_REQUEST_ORIG_URI: dest = &r->target_orig; break;
506 	case MAGNET_ENV_REQUEST_PATH_INFO: dest = &r->pathinfo; break;
507 	case MAGNET_ENV_REQUEST_REMOTE_IP: dest = r->con->dst_addr_buf; break;
508 	case MAGNET_ENV_REQUEST_SERVER_ADDR: /* local IP without port */
509 	    {
510 		const server_socket * const srv_socket = r->con->srv_socket;
511 		dest = r->tmp_buf;
512 		buffer_clear(dest);
513 		switch (sock_addr_get_family(&srv_socket->addr)) {
514 		case AF_INET:
515 		case AF_INET6:
516 			if (sock_addr_is_addr_wildcard(&srv_socket->addr)) {
517 				sock_addr addrbuf;
518 				socklen_t addrlen = sizeof(addrbuf);
519 				const int fd = r->con->fd;
520 				if (0 == getsockname(fd,(struct sockaddr *)&addrbuf,&addrlen)) {
521 					char buf[INET6_ADDRSTRLEN + 1];
522 					const char *s = sock_addr_inet_ntop(&addrbuf, buf, sizeof(buf));
523 					if (NULL != s)
524 						buffer_copy_string_len(dest, s, strlen(s));
525 				}
526 			}
527 			else
528 				buffer_copy_string_len(dest, srv_socket->srv_token->ptr,
529 				                       srv_socket->srv_token_colon);
530 			break;
531 		default:
532 			break;
533 		}
534 		break;
535 	    }
536 	case MAGNET_ENV_REQUEST_PROTOCOL:
537 		dest = r->tmp_buf;
538 		buffer_clear(dest);
539 		http_version_append(dest, r->http_version);
540 		break;
541 	case MAGNET_ENV_RESPONSE_HTTP_STATUS:
542 		dest = r->tmp_buf;
543 		buffer_clear(dest);
544 		buffer_append_int(dest, r->http_status);
545 		break;
546 	case MAGNET_ENV_RESPONSE_BODY_LENGTH:
547 		dest = r->tmp_buf;
548 		buffer_clear(dest);
549 		if (!r->resp_body_finished)
550 			break;
551 		buffer_append_int(dest, chunkqueue_length(&r->write_queue));
552 		break;
553 	case MAGNET_ENV_RESPONSE_BODY:
554 		if (!r->resp_body_finished)
555 			break;
556 		else {
557 			chunkqueue * const cq = &r->write_queue;
558 			off_t len = chunkqueue_length(cq);
559 			if (0 == len) {
560 				dest = r->tmp_buf;
561 				buffer_copy_string_len(dest, CONST_STR_LEN(""));
562 				break;
563 			}
564 			dest = chunkqueue_read_squash(cq, r->conf.errh);
565 			if (NULL == dest) {
566 				dest = r->tmp_buf;
567 				buffer_clear(dest);
568 			}
569 		}
570 		break;
571 
572 	case MAGNET_ENV_UNSET: break;
573 	}
574 
575 	return dest;
576 }
577 
578 static int magnet_env_get_id(const char * const key) {
579 	for (int i = 0; magnet_env[i].name; ++i) {
580 		if (0 == strcmp(key, magnet_env[i].name))
581 			return magnet_env[i].type;
582 	}
583 	return MAGNET_ENV_UNSET;
584 }
585 
586 static buffer *magnet_env_get_buffer(request_st * const r, const char * const key) {
587 	return magnet_env_get_buffer_by_id(r, magnet_env_get_id(key));
588 }
589 
590 static int magnet_env_get(lua_State *L) {
591 	/* __index: param 1 is the (empty) table the value was not found in */
592 	const char *key = luaL_checkstring(L, 2);
593 	request_st * const r = magnet_get_request(L);
594 	magnet_push_buffer(L, magnet_env_get_buffer(r, key));
595 	return 1;
596 }
597 
598 static int magnet_env_set(lua_State *L) {
599     /* __newindex: param 1 is the (empty) table the value is supposed to be set in */
600     const char * const key = luaL_checkstring(L, 2);
601     luaL_checkany(L, 3); /* nil or a string */
602 
603     request_st * const r = magnet_get_request(L);
604     const int env_id = magnet_env_get_id(key);
605 
606     switch (env_id) {
607       default:
608         break;
609       case MAGNET_ENV_URI_PATH_RAW:
610       {
611         /* modify uri-path of r->target; preserve query-part, if present */
612         /* XXX: should we require that resulting path begin with '/' or %2F ? */
613         const uint32_t len = buffer_string_length(&r->target);
614         const char * const qmark = memchr(r->target.ptr, '?', len);
615         const_buffer val = { NULL, 0 };
616         if (!lua_isnil(L, 3))
617             val = magnet_checkconstbuffer(L, 3);
618         if (NULL != qmark)
619             buffer_copy_string_len(r->tmp_buf, qmark,
620                                    len - (uint32_t)(qmark - r->target.ptr));
621         buffer_copy_string_len(&r->target, val.ptr, val.len);
622         if (NULL != qmark)
623             buffer_append_string_buffer(&r->target, r->tmp_buf);
624         return 0;
625       }
626       case MAGNET_ENV_RESPONSE_HTTP_STATUS:
627       case MAGNET_ENV_RESPONSE_BODY_LENGTH:
628       case MAGNET_ENV_RESPONSE_BODY:
629         return luaL_error(L, "lighty.env['%s'] is read-only", key);
630     }
631 
632     buffer * const dest = magnet_env_get_buffer_by_id(r, env_id);
633     if (NULL == dest)
634         return luaL_error(L, "couldn't store '%s' in lighty.env[]", key);
635 
636     if (lua_isnil(L, 3)) {
637         if (env_id==MAGNET_ENV_URI_QUERY || env_id==MAGNET_ENV_PHYSICAL_PATH)
638             buffer_clear(dest);
639         else
640             buffer_string_set_length(dest, 0);
641     }
642     else {
643         const_buffer val = magnet_checkconstbuffer(L, 3);
644         buffer_copy_string_len(dest, val.ptr, val.len);
645         /* NB: setting r->uri.query does not modify query-part in r->target
646          * (r->uri.query is uri-decoded; r->target is not) */
647     }
648 
649     switch (env_id) {
650       case MAGNET_ENV_URI_SCHEME:
651       case MAGNET_ENV_URI_AUTHORITY:
652         buffer_to_lower(dest);
653         if (env_id == MAGNET_ENV_URI_AUTHORITY)
654             r->server_name = dest;
655         break;
656       default:
657         break;
658     }
659 
660     return 0;
661 }
662 
663 static int magnet_env_next(lua_State *L) {
664 	const int pos = lua_tointeger(L, lua_upvalueindex(1));
665 
666 	/* ignore previous key: use upvalue for current pos */
667 	lua_settop(L, 0);
668 
669 	if (NULL == magnet_env[pos].name) return 0; /* end of list */
670 	/* Update our positional upval to reflect our new current position */
671 	lua_pushinteger(L, pos + 1);
672 	lua_replace(L, lua_upvalueindex(1));
673 
674 	/* key to return */
675 	lua_pushstring(L, magnet_env[pos].name);
676 
677 	/* get value */
678 	request_st * const r = magnet_get_request(L);
679 	magnet_push_buffer(L, magnet_env_get_buffer_by_id(r, magnet_env[pos].type));
680 
681 	/* return 2 items on the stack (key, value) */
682 	return 2;
683 }
684 
685 static int magnet_env_pairs(lua_State *L) {
686 	lua_pushinteger(L, 0); /* Push our current pos (the start) into upval 1 */
687 	lua_pushcclosure(L, magnet_env_next, 1); /* Push our new closure with 1 upvals */
688 	return 1;
689 }
690 
691 static int magnet_cgi_get(lua_State *L) {
692     /* __index: param 1 is the (empty) table the value was not found in */
693     size_t klen;
694     const char * const k = luaL_checklstring(L, 2, &klen);
695     request_st * const r = magnet_get_request(L);
696     const buffer * const vb = http_header_env_get(r, k, klen);
697     magnet_push_buffer(L, NULL != vb ? vb : NULL);
698     return 1;
699 }
700 
701 static int magnet_cgi_set(lua_State *L) {
702     /* __newindex: param 1 is the (empty) table the value is supposed to be set in */
703     const_buffer key = magnet_checkconstbuffer(L, 2);
704     const_buffer val = magnet_checkconstbuffer(L, 3);
705     request_st * const r = magnet_get_request(L);
706     http_header_env_set(r, key.ptr, key.len, val.ptr, val.len);
707     return 0;
708 }
709 
710 static int magnet_cgi_pairs(lua_State *L) {
711 	request_st * const r = magnet_get_request(L);
712 	return magnet_array_pairs(L, &r->env);
713 }
714 
715 
716 static int magnet_copy_response_header(request_st * const r, lua_State * const L, int lighty_table_ndx) {
717 	force_assert(lua_istable(L, lighty_table_ndx));
718 
719 	lua_getfield(L, lighty_table_ndx, "header"); /* lighty.header */
720 	if (lua_istable(L, -1)) {
721 		/* header is found, and is a table */
722 
723 		lua_pushnil(L);
724 		while (lua_next(L, -2) != 0) {
725 			if (lua_isstring(L, -1) && lua_isstring(L, -2)) {
726 				const_buffer key = magnet_checkconstbuffer(L, -2);
727 				const_buffer val = magnet_checkconstbuffer(L, -1);
728 				enum http_header_e id = http_header_hkey_get(key.ptr, key.len);
729 
730 				val.len
731 				  ? http_header_response_set(r, id, key.ptr, key.len, val.ptr, val.len)
732 				  : http_header_response_unset(r, id, key.ptr, key.len);
733 			}
734 
735 			lua_pop(L, 1);
736 		}
737 	}
738 	lua_pop(L, 1); /* pop lighty.header */
739 
740 	return 0;
741 }
742 
743 /**
744  * walk through the content array
745  *
746  * content = { "<pre>", { file = "/content" } , "</pre>" }
747  *
748  * header["Content-Type"] = "text/html"
749  *
750  * return 200
751  */
752 static int magnet_attach_content(request_st * const r, lua_State * const L, int lighty_table_ndx) {
753 	force_assert(lua_istable(L, lighty_table_ndx));
754 
755 	lua_getfield(L, lighty_table_ndx, "content"); /* lighty.content */
756 	if (lua_istable(L, -1)) {
757 		/* content is found, and is a table */
758 		http_response_body_clear(r, 0);
759 		for (int i = 1, end = 0; !end; i++) {
760 			lua_rawgeti(L, -1, i);
761 
762 			/* -1 is the value and should be the value ... aka a table */
763 			if (lua_isstring(L, -1)) {
764 				const_buffer data = magnet_checkconstbuffer(L, -1);
765 				http_chunk_append_mem(r, data.ptr, data.len);
766 			} else if (lua_istable(L, -1)) {
767 				lua_getfield(L, -1, "filename");
768 				lua_getfield(L, -2, "length"); /* (0-based) end of range (not actually "length") */
769 				lua_getfield(L, -3, "offset"); /* (0-based) start of range */
770 
771 				if (lua_isstring(L, -3)) { /* filename has to be a string */
772 					off_t off = (off_t) luaL_optinteger(L, -1, 0);
773 					off_t len = (off_t) luaL_optinteger(L, -2, -1); /*(-1 to http_chunk_append_file_range() uses file size minus offset)*/
774 					if (off < 0) {
775 						log_error(r->conf.errh, __FILE__, __LINE__,
776 						  "offset for '%s' is negative", lua_tostring(L, -3));
777 						end = 1;
778 					} else if (len >= off) {
779 						len -= off;
780 					} else if (-1 != len) {
781 						log_error(r->conf.errh, __FILE__, __LINE__,
782 						  "offset > length for '%s'", lua_tostring(L, -3));
783 						end = 1;
784 					}
785 
786 					if (!end && 0 != len) {
787 						buffer *fn = magnet_checkbuffer(L, -3);
788 						int rc = http_chunk_append_file_range(r, fn, off, len);
789 						buffer_free(fn);
790 						if (0 != rc) {
791 							log_error(r->conf.errh, __FILE__, __LINE__,
792 							  "error opening file content '%s' at offset %lld",
793 							          lua_tostring(L, -3), (long long)off);
794 							end = 1;
795 						}
796 					}
797 				} else {
798 					log_error(r->conf.errh, __FILE__, __LINE__,
799 					  "content[%d] is a table and field \"filename\" must be a string", i);
800 					end = 1;
801 				}
802 
803 				lua_pop(L, 3);
804 			} else if (lua_isnil(L, -1)) {
805 				/* end of list */
806 				end = 1;
807 			} else {
808 				log_error(r->conf.errh, __FILE__, __LINE__,
809 				  "content[%d] is neither a string nor a table", i);
810 				end = 1;
811 			}
812 
813 			lua_pop(L, 1); /* pop the content[...] entry value */
814 		}
815 	} else {
816 		log_error(r->conf.errh, __FILE__, __LINE__,
817 		  "lighty.content has to be a table");
818 	}
819 	lua_pop(L, 1); /* pop lighty.content */
820 
821 	return 0;
822 }
823 
824 static int traceback(lua_State *L) {
825 	if (!lua_isstring(L, 1))  /* 'message' not a string? */
826 		return 1;  /* keep it intact */
827 	lua_getglobal(L, "debug");
828 	if (!lua_istable(L, -1)) {
829 		lua_pop(L, 1);
830 		return 1;
831 	}
832 	lua_getfield(L, -1, "traceback");
833 	if (!lua_isfunction(L, -1)) {
834 		lua_pop(L, 2);
835 		return 1;
836 	}
837 	lua_pushvalue(L, 1);  /* pass error message */
838 	lua_pushinteger(L, 2);  /* skip this function and traceback */
839 	lua_call(L, 2, 1);  /* call debug.traceback */
840 	return 1;
841 }
842 
843 /* push traceback function before calling lua_pcall after narg arguments
844  * have been pushed (inserts it before the arguments). returns index for
845  * traceback function ("msgh" in lua_pcall)
846  */
847 static int push_traceback(lua_State *L, int narg) {
848 	int base = lua_gettop(L) - narg;  /* function index */
849 	lua_pushcfunction(L, traceback);
850 	lua_insert(L, base);
851 	return base;
852 }
853 
854 static handler_t magnet_attract(request_st * const r, plugin_data * const p, buffer * const name) {
855 	lua_State *L;
856 	int lua_return_value;
857 	const int func_ndx = 1;
858 	const int lighty_table_ndx = 2;
859 
860 	/* get the script-context */
861 	L = script_cache_get_script(&p->cache, name, r->conf.etag_flags);
862 
863 	if (lua_isstring(L, -1)) {
864 		log_error(r->conf.errh, __FILE__, __LINE__,
865 		  "loading script %s failed: %s", name->ptr, lua_tostring(L, -1));
866 
867 		lua_pop(L, 1);
868 
869 		force_assert(lua_gettop(L) == 0); /* only the error should have been on the stack */
870 
871 		if (p->conf.stage != -1) { /* skip for response-start */
872 			r->http_status = 500;
873 			r->handler_module = NULL;
874 		}
875 
876 		return HANDLER_FINISHED;
877 	}
878 
879 	force_assert(lua_gettop(L) == 1);
880 	force_assert(lua_isfunction(L, func_ndx));
881 
882 	lua_pushlightuserdata(L, r);
883 	lua_setfield(L, LUA_REGISTRYINDEX, LUA_RIDX_LIGHTTPD_REQUEST);
884 
885 	lua_atpanic(L, magnet_atpanic);
886 
887 	/**
888 	 * we want to create empty environment for our script
889 	 *
890 	 * setmetatable({}, {__index = _G})
891 	 *
892 	 * if a function symbol is not defined in our env, __index will lookup
893 	 * in the global env.
894 	 *
895 	 * all variables created in the script-env will be thrown
896 	 * away at the end of the script run.
897 	 */
898 	lua_newtable(L); /* my empty environment aka {}              (sp += 1) */
899 
900 	/* we have to overwrite the print function */
901 	lua_pushcfunction(L, magnet_print);                       /* (sp += 1) */
902 	lua_setfield(L, -2, "print"); /* -1 is the env we want to set(sp -= 1) */
903 
904 	/**
905 	 * lighty.request[] (ro) has the HTTP-request headers
906 	 * lighty.env[] (rw) has various url/physical file paths and
907 	 *                   request meta data; might contain nil values
908 	 * lighty.req_env[] (ro) has the cgi environment
909 	 * lighty.status[] (ro) has the status counters
910 	 * lighty.content[] (rw) is a table of string/file
911 	 * lighty.header[] (rw) is a array to set response headers
912 	 */
913 
914 	lua_newtable(L); /* lighty.*                                 (sp += 1) */
915 
916 	lua_newtable(L); /*  {}                                      (sp += 1) */
917 	lua_newtable(L); /* the meta-table for the request-table     (sp += 1) */
918 	lua_pushcfunction(L, magnet_reqhdr_get);                  /* (sp += 1) */
919 	lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
920 	lua_pushcfunction(L, magnet_reqhdr_pairs);                /* (sp += 1) */
921 	lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
922 	lua_setmetatable(L, -2); /* tie the metatable to request     (sp -= 1) */
923 	lua_setfield(L, -2, "request"); /* content = {}              (sp -= 1) */
924 
925 	lua_newtable(L); /*  {}                                      (sp += 1) */
926 	lua_newtable(L); /* the meta-table for the env-table         (sp += 1) */
927 	lua_pushcfunction(L, magnet_env_get);                     /* (sp += 1) */
928 	lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
929 	lua_pushcfunction(L, magnet_env_set);                     /* (sp += 1) */
930 	lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
931 	lua_pushcfunction(L, magnet_env_pairs);                   /* (sp += 1) */
932 	lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
933 	lua_setmetatable(L, -2); /* tie the metatable to env         (sp -= 1) */
934 	lua_setfield(L, -2, "env"); /* content = {}                  (sp -= 1) */
935 
936 	lua_newtable(L); /*  {}                                      (sp += 1) */
937 	lua_newtable(L); /* the meta-table for the req_env-table     (sp += 1) */
938 	lua_pushcfunction(L, magnet_cgi_get);                     /* (sp += 1) */
939 	lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
940 	lua_pushcfunction(L, magnet_cgi_set);                     /* (sp += 1) */
941 	lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
942 	lua_pushcfunction(L, magnet_cgi_pairs);                   /* (sp += 1) */
943 	lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
944 	lua_setmetatable(L, -2); /* tie the metatable to req_env     (sp -= 1) */
945 	lua_setfield(L, -2, "req_env"); /* content = {}              (sp -= 1) */
946 
947 	lua_newtable(L); /*  {}                                      (sp += 1) */
948 	lua_newtable(L); /* the meta-table for the status-table      (sp += 1) */
949 	lua_pushcfunction(L, magnet_status_get);                  /* (sp += 1) */
950 	lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
951 	lua_pushcfunction(L, magnet_status_set);                  /* (sp += 1) */
952 	lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
953 	lua_pushcfunction(L, magnet_status_pairs);                /* (sp += 1) */
954 	lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
955 	lua_setmetatable(L, -2); /* tie the metatable to statzs      (sp -= 1) */
956 	lua_setfield(L, -2, "status"); /* content = {}               (sp -= 1) */
957 
958 	/* add empty 'content' and 'header' tables */
959 	lua_newtable(L); /*  {}                                      (sp += 1) */
960 	lua_setfield(L, -2, "content"); /* content = {}              (sp -= 1) */
961 
962 	lua_newtable(L); /*  {}                                      (sp += 1) */
963 	lua_setfield(L, -2, "header"); /* header = {}                (sp -= 1) */
964 
965 	lua_pushinteger(L, MAGNET_RESTART_REQUEST);
966 	lua_setfield(L, -2, "RESTART_REQUEST");
967 
968 	lua_pushcfunction(L, magnet_stat);                        /* (sp += 1) */
969 	lua_setfield(L, -2, "stat"); /* -1 is the env we want to set (sp -= 1) */
970 
971 	/* insert lighty table at index 2 */
972 	lua_pushvalue(L, -1);
973 	lua_insert(L, lighty_table_ndx);
974 
975 	lua_setfield(L, -2, "lighty"); /* lighty.*                   (sp -= 1) */
976 
977 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
978 	/* override the default pairs() function to our __pairs capable version;
979 	 * not needed for lua 5.2+
980 	 */
981 	lua_getglobal(L, "pairs"); /* push original pairs()          (sp += 1) */
982 	lua_pushcclosure(L, magnet_pairs, 1);
983 	lua_setfield(L, -2, "pairs");                             /* (sp -= 1) */
984 #endif
985 
986 	lua_newtable(L); /* the meta-table for the new env           (sp += 1) */
987 	lua_pushglobaltable(L);                                   /* (sp += 1) */
988 	lua_setfield(L, -2, "__index"); /* { __index = _G }          (sp -= 1) */
989 	lua_setmetatable(L, -2); /* setmetatable({}, {__index = _G}) (sp -= 1) */
990 
991 	magnet_setfenv_mainfn(L, 1);                              /* (sp -= 1) */
992 
993 	/* pcall will destroy the func value, duplicate it */     /* (sp += 1) */
994 	lua_pushvalue(L, func_ndx);
995 	{
996 		int errfunc = push_traceback(L, 0);
997 		int ret = lua_pcall(L, 0, 1, errfunc);
998 		lua_remove(L, errfunc);
999 
1000 		/* reset environment */
1001 		lua_pushglobaltable(L);                               /* (sp += 1) */
1002 		magnet_setfenv_mainfn(L, 1);                          /* (sp -= 1) */
1003 
1004 		if (0 != ret) {
1005 			log_error(r->conf.errh, __FILE__, __LINE__,
1006 			  "lua_pcall(): %s", lua_tostring(L, -1));
1007 			lua_pop(L, 2); /* remove the error-msg and the lighty table at index 2 */
1008 
1009 			force_assert(lua_gettop(L) == 1); /* only the function should be on the stack */
1010 
1011 			if (p->conf.stage != -1) { /* skip for response-start */
1012 				r->http_status = 500;
1013 				r->handler_module = NULL;
1014 			}
1015 
1016 			return HANDLER_FINISHED;
1017 		}
1018 	}
1019 
1020 	/* we should have the function, the lighty table and the return value on the stack */
1021 	force_assert(lua_gettop(L) == 3);
1022 
1023 	switch (lua_type(L, -1)) {
1024 	case LUA_TNUMBER:
1025 	case LUA_TNIL:
1026 		lua_return_value = (int) luaL_optinteger(L, -1, -1);
1027 		break;
1028 	default:
1029 		log_error(r->conf.errh, __FILE__, __LINE__,
1030 		  "lua_pcall(): unexpected return type: %s", luaL_typename(L, -1));
1031 		lua_return_value = -1;
1032 		break;
1033 	}
1034 
1035 	lua_pop(L, 1); /* pop return value */
1036 
1037 	magnet_copy_response_header(r, L, lighty_table_ndx);
1038 
1039 	{
1040 		handler_t result = HANDLER_GO_ON;
1041 
1042 		if (lua_return_value >= 200) {
1043 			r->http_status = lua_return_value;
1044 			r->resp_body_finished = 1;
1045 
1046 			if (0 == setjmp(exceptionjmp)) {
1047 				magnet_attach_content(r, L, lighty_table_ndx);
1048 				if (!chunkqueue_is_empty(&r->write_queue)) {
1049 					r->handler_module = p->self;
1050 				}
1051 			} else {
1052 				lua_settop(L, 2); /* remove all but function and lighty table */
1053 				r->http_status = 500;
1054 				r->handler_module = NULL;
1055 				http_response_body_clear(r, 0);
1056 			}
1057 
1058 			result = HANDLER_FINISHED;
1059 		} else if (lua_return_value >= 100) {
1060 			/*(custom lua code should not return 101 Switching Protocols)*/
1061 			r->http_status = lua_return_value;
1062 			result = http_response_send_1xx(r)
1063 			  ? HANDLER_GO_ON
1064 			  : HANDLER_ERROR;
1065 		} else if (MAGNET_RESTART_REQUEST == lua_return_value) {
1066 			result = HANDLER_COMEBACK;
1067 		}
1068 
1069 		lua_pop(L, 1); /* pop the lighty table */
1070 		force_assert(lua_gettop(L) == 1); /* only the function should remain on the stack */
1071 
1072 		return result;
1073 	}
1074 }
1075 
1076 static handler_t magnet_attract_array(request_st * const r, plugin_data * const p, int stage) {
1077 	mod_magnet_patch_config(r, p);
1078 	p->conf.stage = stage;
1079 
1080 	const array *files;
1081 	switch (stage) {
1082 	  case  1: files = p->conf.url_raw; break;
1083 	  case  0: files = p->conf.physical_path; break;
1084 	  case -1: files = p->conf.response_start; break;
1085 	  default: files = NULL; break;
1086 	}
1087 
1088 	/* no filename set */
1089 	if (NULL == files || files->used == 0) return HANDLER_GO_ON;
1090 
1091 	r->con->srv->request_env(r);
1092 
1093 	/**
1094 	 * execute all files and jump out on the first !HANDLER_GO_ON
1095 	 */
1096 	handler_t ret = HANDLER_GO_ON;
1097 	for (uint32_t i = 0; i < files->used && ret == HANDLER_GO_ON; ++i) {
1098 		data_string *ds = (data_string *)files->data[i];
1099 		ret = magnet_attract(r, p, &ds->value);
1100 	}
1101 
1102 	if (r->error_handler_saved_status) {
1103 		/* retrieve (possibly modified) REDIRECT_STATUS and store as number */
1104 		int x;
1105 		const buffer * const vb = http_header_env_get(r, CONST_STR_LEN("REDIRECT_STATUS"));
1106 		if (vb && (x = http_header_str_to_code(vb->ptr)) != -1)
1107 			r->error_handler_saved_status =
1108 			  r->error_handler_saved_status > 0 ? (int)x : -(int)x;
1109 	}
1110 
1111 	return ret;
1112 }
1113 
1114 URIHANDLER_FUNC(mod_magnet_uri_handler) {
1115 	return magnet_attract_array(r, p_d, 1);
1116 }
1117 
1118 URIHANDLER_FUNC(mod_magnet_physical) {
1119 	return magnet_attract_array(r, p_d, 0);
1120 }
1121 
1122 URIHANDLER_FUNC(mod_magnet_response_start) {
1123 	return magnet_attract_array(r, p_d, -1);
1124 }
1125 
1126 
1127 int mod_magnet_plugin_init(plugin *p);
1128 int mod_magnet_plugin_init(plugin *p) {
1129 	p->version     = LIGHTTPD_VERSION_ID;
1130 	p->name        = "magnet";
1131 
1132 	p->init        = mod_magnet_init;
1133 	p->handle_uri_clean  = mod_magnet_uri_handler;
1134 	p->handle_physical   = mod_magnet_physical;
1135 	p->handle_response_start = mod_magnet_response_start;
1136 	p->set_defaults  = mod_magnet_set_defaults;
1137 	p->cleanup     = mod_magnet_free;
1138 
1139 	return 0;
1140 }
1141