xref: /lighttpd1.4/src/mod_magnet.c (revision 54c07b54)
1 #include "first.h"
2 
3 #include "sys-crypto-md.h"
4 #include "algo_hmac.h"
5 #include "base.h"
6 #include "base64.h"
7 #include "burl.h"
8 #include "log.h"
9 #include "buffer.h"
10 #include "chunk.h"
11 #include "ck.h"
12 #include "http_chunk.h"
13 #include "http_etag.h"
14 #include "http_header.h"
15 #include "rand.h"
16 #include "response.h"   /* http_response_send_1xx() */
17 
18 #include "plugin.h"
19 
20 #include "mod_magnet_cache.h"
21 #include "sock_addr.h"
22 #include "stat_cache.h"
23 #include "status_counter.h"
24 
25 #include <dirent.h>
26 #include <stdlib.h>
27 #include <string.h>
28 /*#include <setjmp.h>*//*(not currently used)*/
29 
30 #include <lua.h>
31 #include <lauxlib.h>
32 
33 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503
34 #define LUA_RIDX_LIGHTTPD_REQUEST "lighty.request"
35 #endif
36 
37 #define MAGNET_RESTART_REQUEST      99
38 
39 /* plugin config for all request/connections */
40 
41 /*static jmp_buf exceptionjmp;*//*(not currently used)*/
42 
43 typedef struct {
44     script * const *url_raw;
45     script * const *physical_path;
46     script * const *response_start;
47     int stage;
48 } plugin_config;
49 
50 typedef struct {
51     PLUGIN_DATA;
52     plugin_config defaults;
53     plugin_config conf;
54 
55     script_cache cache;
56 } plugin_data;
57 
58 INIT_FUNC(mod_magnet_init) {
59     return calloc(1, sizeof(plugin_data));
60 }
61 
62 FREE_FUNC(mod_magnet_free) {
63     plugin_data * const p = p_d;
64     script_cache_free_data(&p->cache);
65     if (NULL == p->cvlist) return;
66     /* (init i to 0 if global context; to 1 to skip empty global context) */
67     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
68         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
69         for (; -1 != cpv->k_id; ++cpv) {
70             if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
71             switch (cpv->k_id) {
72               case 0: /* magnet.attract-raw-url-to */
73               case 1: /* magnet.attract-physical-path-to */
74               case 2: /* magnet.attract-response-start-to */
75                 free(cpv->v.v);
76                 break;
77               default:
78                 break;
79             }
80         }
81     }
82 }
83 
84 static void mod_magnet_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
85     if (cpv->vtype != T_CONFIG_LOCAL)
86         return;
87     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
88       case 0: /* magnet.attract-raw-url-to */
89         pconf->url_raw = cpv->v.v;
90         break;
91       case 1: /* magnet.attract-physical-path-to */
92         pconf->physical_path = cpv->v.v;
93         break;
94       case 2: /* magnet.attract-response-start-to */
95         pconf->response_start = cpv->v.v;
96         break;
97       default:/* should not happen */
98         return;
99     }
100 }
101 
102 static void mod_magnet_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
103     do {
104         mod_magnet_merge_config_cpv(pconf, cpv);
105     } while ((++cpv)->k_id != -1);
106 }
107 
108 static void mod_magnet_patch_config(request_st * const r, plugin_data * const p) {
109     p->conf = p->defaults; /* copy small struct instead of memcpy() */
110     /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
111     for (int i = 1, used = p->nconfig; i < used; ++i) {
112         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
113             mod_magnet_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
114     }
115 }
116 
117 SETDEFAULTS_FUNC(mod_magnet_set_defaults) {
118     static const config_plugin_keys_t cpk[] = {
119       { CONST_STR_LEN("magnet.attract-raw-url-to"),
120         T_CONFIG_ARRAY_VLIST,
121         T_CONFIG_SCOPE_CONNECTION }
122      ,{ CONST_STR_LEN("magnet.attract-physical-path-to"),
123         T_CONFIG_ARRAY_VLIST,
124         T_CONFIG_SCOPE_CONNECTION }
125      ,{ CONST_STR_LEN("magnet.attract-response-start-to"),
126         T_CONFIG_ARRAY_VLIST,
127         T_CONFIG_SCOPE_CONNECTION }
128      ,{ NULL, 0,
129         T_CONFIG_UNSET,
130         T_CONFIG_SCOPE_UNSET }
131     };
132 
133     plugin_data * const p = p_d;
134     if (!config_plugin_values_init(srv, p, cpk, "mod_magnet"))
135         return HANDLER_ERROR;
136 
137     /* process and validate config directives
138      * (init i to 0 if global context; to 1 to skip empty global context) */
139     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
140         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
141         for (; -1 != cpv->k_id; ++cpv) {
142             switch (cpv->k_id) {
143               case 0: /* magnet.attract-raw-url-to */
144               case 1: /* magnet.attract-physical-path-to */
145               case 2: /* magnet.attract-response-start-to */
146                 if (0 == cpv->v.a->used) {
147                     cpv->v.v = NULL;
148                     cpv->vtype = T_CONFIG_LOCAL;
149                 }
150                 else {
151                     script ** const a =
152                       malloc(sizeof(script *)*(cpv->v.a->used+1));
153                     force_assert(a);
154                     for (uint32_t j = 0; j < cpv->v.a->used; ++j) {
155                         data_string *ds = (data_string *)cpv->v.a->data[j];
156                         if (buffer_is_blank(&ds->value)) {
157                             log_error(srv->errh, __FILE__, __LINE__,
158                               "unexpected (blank) value for %s; "
159                               "expected list of \"scriptpath\"", cpk[cpv->k_id].k);
160                             free(a);
161                             return HANDLER_ERROR;
162                         }
163                         a[j] = script_cache_get_script(&p->cache, &ds->value);
164                     }
165                     a[cpv->v.a->used] = NULL;
166                     cpv->v.v = a;
167                     cpv->vtype = T_CONFIG_LOCAL;
168                 }
169                 break;
170               default:/* should not happen */
171                 break;
172             }
173         }
174     }
175 
176     /* initialize p->defaults from global config context */
177     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
178         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
179         if (-1 != cpv->k_id)
180             mod_magnet_merge_config(&p->defaults, cpv);
181     }
182 
183     return HANDLER_GO_ON;
184 }
185 
186 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503
187 #define lua_getfield_and_type(L,idx,k) \
188        (lua_getfield((L),(idx),(k)), lua_type((L),-1))
189 #define lua_getglobal_and_type(L,name) \
190        (lua_getglobal((L),(name)), lua_type((L),-1))
191 #else
192 #define lua_getfield_and_type(L,idx,k) \
193         lua_getfield((L),(idx),(k))
194 #define lua_getglobal_and_type(L,name) \
195         lua_getglobal((L),(name))
196 #endif
197 
198 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
199 static lua_Integer
200 lua_tointegerx (lua_State * const L, int idx, int *isnum)
201 {
202     /*(caller should check for LUA_TNIL if using a default value is desired)*/
203     /*(note: return 0 for floating point not convertible to integer)*/
204     *isnum = lua_isnumber(L, idx);
205     return *isnum ? lua_tointeger(L, idx) : 0;
206 }
207 #endif
208 
209 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
210 /* lua5.1 backward compat definition */
211 static void lua_pushglobaltable(lua_State *L) { /* (-0, +1, -) */
212 	lua_pushvalue(L, LUA_GLOBALSINDEX);
213 }
214 #endif
215 
216 static void magnet_setfenv_mainfn(lua_State *L, int funcIndex) { /* (-1, 0, -) */
217 #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 502
218 	/* set "_ENV" upvalue, which should be the first upvalue of a "main" lua
219 	 * function if it uses any global names
220 	 */
221 
222 	const char* first_upvalue_name = lua_getupvalue(L, funcIndex, 1);
223 	if (NULL == first_upvalue_name) return; /* doesn't have any upvalues */
224 	lua_pop(L, 1); /* only need the name of the upvalue, not the value */
225 
226 	if (0 != strcmp(first_upvalue_name, "_ENV")) return;
227 
228 	if (NULL == lua_setupvalue(L, funcIndex, 1)) {
229 		/* pop value if lua_setupvalue didn't set the (not existing) upvalue */
230 		lua_pop(L, 1);
231 	}
232 #else
233 	lua_setfenv(L, funcIndex);
234 #endif
235 }
236 
237 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
238 /* lua 5.1 deprecated luaL_getn() for lua_objlen() */
239 /* lua 5.2 renamed lua_objlen() to lua_rawlen() */
240 #define lua_rawlen lua_objlen
241 /* lua 5.2 deprecated luaL_register() for luaL_setfuncs()
242  * (this define is valid only when 0 == nup) */
243 #define luaL_setfuncs(L, l, nup) luaL_register((L), NULL, (l))
244 #endif
245 
246 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
247 /* lua 5.2 already supports __pairs */
248 
249 /* See http://lua-users.org/wiki/GeneralizedPairsAndIpairs for implementation details.
250  * Override the default pairs() function to allow us to use a __pairs metakey
251  */
252 static int magnet_pairs(lua_State *L) {
253 	luaL_checkany(L, 1); /* "self" */
254 
255 	if (luaL_getmetafield(L, 1, "__pairs")) {
256 		/* call __pairs(self) */
257 		lua_pushvalue(L, 1);
258 		lua_call(L, 1, 3);
259 	} else {
260 		/* call <original-pairs-method>(self) */
261 		lua_pushvalue(L, lua_upvalueindex(1));
262 		lua_pushvalue(L, 1);
263 		lua_call(L, 1, 3);
264 	}
265 	return 3;
266 }
267 #endif
268 
269 
270 /* XXX: mystery why dir walk (readdir) is not already part of lua io liolib.c */
271 
272 #ifndef _D_EXACT_NAMLEN
273 #ifdef _DIRENT_HAVE_D_NAMLEN
274 #define _D_EXACT_NAMLEN(d) ((d)->d_namlen)
275 #else
276 #define _D_EXACT_NAMLEN(d) (strlen ((d)->d_name))
277 #endif
278 #endif
279 
280 static int magnet_readdir_iter(lua_State *L) {
281     DIR ** const d = (DIR **)lua_touserdata(L, lua_upvalueindex(1));
282     if (NULL == *d) return 0;
283 
284     /* readdir() and skip over "." and ".." */
285     struct dirent *de;
286     const char *n;
287     do {
288         de = readdir(*d);
289     } while (de && (n = de->d_name)[0] == '.'
290              && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0')));
291 
292     if (de) {
293         lua_pushlstring(L, de->d_name, _D_EXACT_NAMLEN(de));
294         return 1;
295     }
296     else { /* EOF */
297         closedir(*d);
298         *d = NULL;
299         return 0;
300     }
301 }
302 
303 static int magnet_readdir_gc(lua_State *L) {
304     /*DIR ** const d = ((DIR **)luaL_checkudata(L, 1, "lighty.DIR"));*/
305     DIR ** const d = lua_touserdata(L, 1);
306     if (*d) closedir(*d);
307     return 0;
308 }
309 
310 static void magnet_readdir_metatable(lua_State * const L) {
311     if (luaL_newmetatable(L, "lighty.DIR")) {                 /* (sp += 1) */
312         lua_pushcclosure(L, magnet_readdir_gc, 0);            /* (sp += 1) */
313         lua_setfield(L, -2, "__gc");                          /* (sp -= 1) */
314         lua_pushboolean(L, 0);                                /* (sp += 1) */
315         lua_setfield(L, -2, "__metatable"); /* protect metatable (sp -= 1) */
316     }
317 }
318 
319 static int magnet_readdir(lua_State *L) {
320     const char * const s = luaL_checkstring(L, 1);
321     DIR * const d = opendir(s);
322     if (d) {
323       #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 504
324         DIR ** const dp = (DIR **)lua_newuserdata(L, sizeof(DIR *));
325       #else
326         DIR ** const dp = (DIR **)lua_newuserdatauv(L, sizeof(DIR *), 0);
327       #endif
328         *dp = d;
329         magnet_readdir_metatable(L);
330         lua_setmetatable(L, -2);
331         lua_pushcclosure(L, magnet_readdir_iter, 1);
332     }
333     else
334         lua_pushnil(L);
335     return 1;
336 }
337 
338 
339 static int magnet_newindex_readonly(lua_State *L) {
340     lua_pushliteral(L, "lua table is read-only");
341     return lua_error(L);
342 }
343 
344 static void magnet_push_buffer(lua_State *L, const buffer *b) {
345     if (b && !buffer_is_unset(b))
346         lua_pushlstring(L, BUF_PTR_LEN(b));
347     else
348         lua_pushnil(L);
349 }
350 
351 #if 0
352 static int magnet_array_get_element(lua_State *L, const array *a) {
353     /* __index: param 1 is the (empty) table the value was not found in */
354     size_t klen;
355     const char * const k = luaL_checklstring(L, 2, &klen);
356     const data_string * const ds = (const data_string *)
357       array_get_element_klen(a, k, klen);
358     magnet_push_buffer(L, NULL != ds ? &ds->value : NULL);
359     return 1;
360 }
361 #endif
362 
363 /* Define a function that will iterate over an array* (in upval 1) using current position (upval 2) */
364 static int magnet_array_next(lua_State *L) {
365 	data_unset *du;
366 	data_string *ds;
367 	data_integer *di;
368 
369 	size_t pos = lua_tointeger(L, lua_upvalueindex(1));
370 	array *a = lua_touserdata(L, lua_upvalueindex(2));
371 
372 	lua_settop(L, 0);
373 
374 	if (pos >= a->used) return 0;
375 	if (NULL != (du = a->data[pos])) {
376 		lua_pushlstring(L, BUF_PTR_LEN(&du->key));
377 		switch (du->type) {
378 			case TYPE_STRING:
379 				ds = (data_string *)du;
380 				magnet_push_buffer(L, &ds->value);
381 				break;
382 			case TYPE_INTEGER:
383 				di = (data_integer *)du;
384 				lua_pushinteger(L, di->value);
385 				break;
386 			default:
387 				lua_pushnil(L);
388 				break;
389 		}
390 
391 		/* Update our positional upval to reflect our new current position */
392 		pos++;
393 		lua_pushinteger(L, pos);
394 		lua_replace(L, lua_upvalueindex(1));
395 
396 		/* Returning 2 items on the stack (key, value) */
397 		return 2;
398 	}
399 	return 0;
400 }
401 
402 /* Create the closure necessary to iterate over the array *a with the above function */
403 static int magnet_array_pairs(lua_State *L, array *a) {
404 	lua_pushinteger(L, 0); /* Push our current pos (the start) into upval 1 */
405 	lua_pushlightuserdata(L, a); /* Push our array *a into upval 2 */
406 	lua_pushcclosure(L, magnet_array_next, 2); /* Push our new closure with 2 upvals */
407 	return 1;
408 }
409 
410 static request_st * magnet_get_request(lua_State *L) {
411      #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503
412 	lua_getfield(L, LUA_REGISTRYINDEX, LUA_RIDX_LIGHTTPD_REQUEST);
413 	request_st * const r = lua_touserdata(L, -1);
414 	lua_pop(L, 1);
415 	return r;
416      #else
417 	return *(request_st **)lua_getextraspace(L);
418      #endif
419 }
420 
421 static buffer * magnet_tmpbuf_acquire(lua_State *L)
422 {
423   #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503
424     UNUSED(L);
425     return chunk_buffer_acquire();
426   #else
427     request_st * const r = magnet_get_request(L);
428     buffer * const tb = r->tmp_buf;
429     buffer_clear(tb);
430     return tb;
431   #endif
432 }
433 
434 static void magnet_tmpbuf_release(buffer *b)
435 {
436   #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503
437     chunk_buffer_release(b);
438   #else
439     UNUSED(b);
440   #endif
441 }
442 
443 typedef struct {
444 	const char *ptr;
445 	size_t len;
446 } const_buffer;
447 
448 static const_buffer magnet_checkconstbuffer(lua_State *L, int idx) {
449 	const_buffer cb;
450 	if (!lua_isnoneornil(L, idx))
451 		cb.ptr = luaL_checklstring(L, idx, &cb.len);
452 	else {
453 		cb.ptr = NULL;
454 		cb.len = 0;
455 	}
456 	return cb;
457 }
458 
459 static const buffer* magnet_checkbuffer(lua_State *L, int idx, buffer *b) {
460 	const_buffer cb = magnet_checkconstbuffer(L, idx);
461 	/* assign result into (buffer *), and return (const buffer *)
462 	 * (note: caller must not free result) */
463 	*(const char **)&b->ptr = cb.ptr ? cb.ptr : "";
464 	b->used = cb.len+1;
465 	b->size = 0;
466 	return b;
467 }
468 
469 static int magnet_print(lua_State *L) {
470 	const_buffer cb = magnet_checkconstbuffer(L, 1);
471 	request_st * const r = magnet_get_request(L);
472 	log_error(r->conf.errh, __FILE__, __LINE__, "(lua-print) %s", cb.ptr);
473 	return 0;
474 }
475 
476 
477 static int magnet_stat_field(lua_State *L) {
478     if (lua_gettop(L) != 2)
479         return 0; /*(should not happen; __index method in protected metatable)*/
480 
481     stat_cache_entry * const sce = *(stat_cache_entry **)lua_touserdata(L, -2);
482     const_buffer k = magnet_checkconstbuffer(L, -1);
483     switch (k.len ? k.ptr[0] : 0) {
484       case 'c': { /* content-type */
485         if (0 != strcmp(k.ptr, "content-type")) break;
486         request_st * const r = magnet_get_request(L);
487         const buffer *content_type = stat_cache_content_type_get(sce, r);
488         if (content_type && !buffer_is_blank(content_type))
489             lua_pushlstring(L, BUF_PTR_LEN(content_type));
490         else
491             lua_pushnil(L);
492         return 1;
493       }
494       case 'e': { /* etag */
495         if (0 != strcmp(k.ptr, "etag")) break;
496         request_st * const r = magnet_get_request(L);
497         const buffer *etag = stat_cache_etag_get(sce, r->conf.etag_flags);
498         if (etag && !buffer_is_blank(etag))
499             lua_pushlstring(L, BUF_PTR_LEN(etag));
500         else
501             lua_pushnil(L);
502         return 1;
503       }
504       case 'h': { /* http-response-send-file */
505         if (0 != strcmp(k.ptr, "http-response-send-file")) break;
506         request_st * const r = magnet_get_request(L);
507         http_response_send_file(r, &sce->name, sce);
508         lua_pushinteger(L, r->http_status);
509         return 1;
510       }
511       case 'i': /* is_* */
512         if (k.len < 4) break;
513         switch (k.ptr[3]) {
514           case 'b': /* is_block */
515             if (0 == strcmp(k.ptr, "is_block")) {
516                 lua_pushboolean(L, S_ISBLK(sce->st.st_mode));
517                 return 1;
518             }
519             break;
520           case 'c': /* is_char */
521             if (0 == strcmp(k.ptr, "is_char")) {
522                 lua_pushboolean(L, S_ISCHR(sce->st.st_mode));
523                 return 1;
524             }
525             break;
526           case 'd': /* is_dir */
527             if (0 == strcmp(k.ptr, "is_dir")) {
528                 lua_pushboolean(L, S_ISDIR(sce->st.st_mode));
529                 return 1;
530             }
531             break;
532           case 'f': /* is_file is_fifo */
533             if (0 == strcmp(k.ptr, "is_file")) {
534                 lua_pushboolean(L, S_ISREG(sce->st.st_mode));
535                 return 1;
536             }
537             if (0 == strcmp(k.ptr, "is_fifo")) {
538                 lua_pushboolean(L, S_ISFIFO(sce->st.st_mode));
539                 return 1;
540             }
541             break;
542           case 'l': /* is_link */
543             if (0 == strcmp(k.ptr, "is_link")) {
544                 lua_pushboolean(L, S_ISLNK(sce->st.st_mode));
545                 return 1;
546             }
547             break;
548           case 's': /* is_socket */
549             if (0 == strcmp(k.ptr, "is_socket")) {
550                 lua_pushboolean(L, S_ISSOCK(sce->st.st_mode));
551                 return 1;
552             }
553             break;
554           default:
555             break;
556         }
557         break;
558       case 's': /* st_* */
559         if (k.len < 4) break;
560         switch (k.ptr[3]) {
561           case 'a': /* st_atime */
562             if (0 == strcmp(k.ptr, "st_atime")) {
563                 lua_pushinteger(L, TIME64_CAST(sce->st.st_atime));
564                 return 1;
565             }
566             break;
567           case 'c': /* st_ctime */
568             if (0 == strcmp(k.ptr, "st_ctime")) {
569                 lua_pushinteger(L, TIME64_CAST(sce->st.st_ctime));
570                 return 1;
571             }
572             break;
573           case 'i': /* st_ino */
574             if (0 == strcmp(k.ptr, "st_ino")) {
575                 lua_pushinteger(L, sce->st.st_ino);
576                 return 1;
577             }
578             break;
579           case 'm': /* st_mtime st_mode */
580             if (0 == strcmp(k.ptr, "st_mtime")) {
581                 lua_pushinteger(L, TIME64_CAST(sce->st.st_mtime));
582                 return 1;
583             }
584             if (0 == strcmp(k.ptr, "st_mode")) {
585                 lua_pushinteger(L, sce->st.st_mode);
586                 return 1;
587             }
588             break;
589           case 'g': /* st_gid */
590             if (0 == strcmp(k.ptr, "st_gid")) {
591                 lua_pushinteger(L, sce->st.st_gid);
592                 return 1;
593             }
594             break;
595           case 's': /* st_size */
596             if (0 == strcmp(k.ptr, "st_size")) {
597                 lua_pushinteger(L, sce->st.st_size);
598                 return 1;
599             }
600             break;
601           case 'u': /* st_uid */
602             if (0 == strcmp(k.ptr, "st_uid")) {
603                 lua_pushinteger(L, sce->st.st_uid);
604                 return 1;
605             }
606             break;
607           default:
608             break;
609         }
610         break;
611       default:
612         break;
613     }
614 
615     lua_pushliteral(L, "stat[\"field\"] invalid: ");
616     lua_pushvalue(L, -2); /* field */
617     lua_concat(L, 2);
618     lua_error(L);
619     return 0;
620 }
621 
622 
623 __attribute_cold__
624 static int magnet_stat_pairs_noimpl_iter(lua_State *L) {
625     request_st * const r = magnet_get_request(L);
626     log_error(r->conf.errh, __FILE__, __LINE__,
627       "(lua) pairs() not implemented on lighty.stat object; "
628       "returning empty iter");
629     return 0;
630 }
631 
632 
633 __attribute_cold__
634 static int magnet_stat_pairs_noimpl(lua_State *L) {
635     lua_pushcclosure(L, magnet_stat_pairs_noimpl_iter, 0);
636     return 1;
637 }
638 
639 
640 static void magnet_stat_metatable(lua_State *L) {
641     if (luaL_newmetatable(L, "lighty.stat")) {                  /* (sp += 1) */
642         lua_pushcfunction(L, magnet_stat_field);                /* (sp += 1) */
643         lua_setfield(L, -2, "__index");                         /* (sp -= 1) */
644         lua_pushcfunction(L, magnet_newindex_readonly);         /* (sp += 1) */
645         lua_setfield(L, -2, "__newindex");                      /* (sp -= 1) */
646         lua_pushcfunction(L, magnet_stat_pairs_noimpl);         /* (sp += 1) */
647         lua_setfield(L, -2, "__pairs");                         /* (sp -= 1) */
648         lua_pushboolean(L, 0);                                  /* (sp += 1) */
649         lua_setfield(L, -2, "__metatable"); /* protect metatable   (sp -= 1) */
650     }
651 }
652 
653 
654 static int magnet_stat(lua_State *L) {
655     buffer stor; /*(note: do not free magnet_checkbuffer() result)*/
656     const buffer * const sb = magnet_checkbuffer(L, 1, &stor);
657     stat_cache_entry * const sce = (!buffer_is_blank(sb))
658       ? stat_cache_get_entry(sb)
659       : NULL;
660     if (NULL == sce) {
661         lua_pushnil(L);
662         return 1;
663     }
664 
665     /* note: caching sce valid only for procedural script which does not yield;
666      * (sce might not be valid if script yields and is later resumed)
667      * (script must not cache sce in persistent global state for later use)
668      * (If we did want sce to be persistent, then could increment sce refcnt,
669      *  and set up __gc metatable method to decrement sce refcnt) */
670     stat_cache_entry ** const udata =(struct stat_cache_entry**)/* (sp += 1) */
671      #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 504
672       lua_newuserdata(L, sizeof(stat_cache_entry *));
673      #else
674       lua_newuserdatauv(L, sizeof(stat_cache_entry *), 0);
675      #endif
676     *udata = sce;
677 
678     magnet_stat_metatable(L);                                   /* (sp += 1) */
679     lua_setmetatable(L, -2);                                    /* (sp -= 1) */
680     return 1;
681 }
682 
683 
684 static int magnet_time(lua_State *L) {
685     lua_pushinteger(L, (lua_Integer)log_epoch_secs);
686     return 1;
687 }
688 
689 
690 static int magnet_rand(lua_State *L) {
691     lua_pushinteger(L, (lua_Integer)li_rand_pseudo());
692     return 1;
693 }
694 
695 
696 static int magnet_md_once(lua_State *L) {
697     if (lua_gettop(L) != 2) {
698         lua_pushliteral(L,
699           "lighty.c.md(algo, data): incorrect number of arguments");
700         return lua_error(L);
701     }
702     const_buffer algo = magnet_checkconstbuffer(L, -2);
703     const_buffer msg  = magnet_checkconstbuffer(L, -1);
704     uint8_t digest[MD_DIGEST_LENGTH_MAX];
705     uint32_t dlen = 0;
706     switch (algo.len) {
707      #ifdef USE_LIB_CRYPTO
708       case 6:
709        #ifdef USE_LIB_CRYPTO_SHA512
710         if (0 == memcmp(algo.ptr, "sha512", 6)) {
711             SHA512_once(digest, msg.ptr, msg.len);
712             dlen = SHA512_DIGEST_LENGTH;
713             break;
714         }
715        #endif
716        #ifdef USE_LIB_CRYPTO_SHA256
717         if (0 == memcmp(algo.ptr, "sha256", 6)) {
718             SHA256_once(digest, msg.ptr, msg.len);
719             dlen = SHA256_DIGEST_LENGTH;
720             break;
721         }
722        #endif
723         break;
724       case 4:
725        #ifdef USE_LIB_CRYPTO_SHA1
726         if (0 == memcmp(algo.ptr, "sha1", 4)) {
727             SHA1_once(digest, msg.ptr, msg.len);
728             dlen = SHA1_DIGEST_LENGTH;
729             break;
730         }
731        #endif
732         break;
733      #endif
734       case 3:
735         if (0 == memcmp(algo.ptr, "md5", 3)) {
736             MD5_once(digest, msg.ptr, msg.len);
737             dlen = MD5_DIGEST_LENGTH;
738             break;
739         }
740         break;
741       default:
742         break;
743     }
744 
745     if (dlen) {
746         char dighex[MD_DIGEST_LENGTH_MAX*2+1];
747         li_tohex_uc(dighex, sizeof(dighex), (char *)digest, dlen);
748         lua_pushlstring(L, dighex, dlen*2);
749     }
750     else
751         lua_pushnil(L);
752 
753     return 1;
754 }
755 
756 static int magnet_hmac_once(lua_State *L) {
757     if (lua_gettop(L) != 3) {
758         lua_pushliteral(L,
759           "lighty.c.hmac(algo, secret, data): incorrect number of arguments");
760         return lua_error(L);
761     }
762     const_buffer algo   = magnet_checkconstbuffer(L, -3);
763     const_buffer secret = magnet_checkconstbuffer(L, -2);
764     const_buffer msg    = magnet_checkconstbuffer(L, -1);
765     const uint8_t * const msgptr = (uint8_t *)msg.ptr;
766     uint8_t digest[MD_DIGEST_LENGTH_MAX];
767     uint32_t dlen = 0;
768     int rc = 0;
769     switch (algo.len) {
770      #ifdef USE_LIB_CRYPTO
771       case 6:
772        #ifdef USE_LIB_CRYPTO_SHA512
773         if (0 == memcmp(algo.ptr, "sha512", 6)) {
774             rc = li_hmac_sha512(digest,secret.ptr,secret.len,msgptr,msg.len);
775             dlen = SHA512_DIGEST_LENGTH;
776             break;
777         }
778        #endif
779        #ifdef USE_LIB_CRYPTO_SHA256
780         if (0 == memcmp(algo.ptr, "sha256", 6)) {
781             rc = li_hmac_sha256(digest,secret.ptr,secret.len,msgptr,msg.len);
782             dlen = SHA256_DIGEST_LENGTH;
783             break;
784         }
785        #endif
786         break;
787       case 4:
788        #ifdef USE_LIB_CRYPTO_SHA1
789         if (0 == memcmp(algo.ptr, "sha1", 4)) {
790             rc = li_hmac_sha1(digest,secret.ptr,secret.len,msgptr,msg.len);
791             dlen = SHA1_DIGEST_LENGTH;
792             break;
793         }
794        #endif
795         break;
796      #endif
797       case 3:
798         if (0 == memcmp(algo.ptr, "md5", 3)) {
799             rc = li_hmac_md5(digest,secret.ptr,secret.len,msgptr,msg.len);
800             dlen = MD5_DIGEST_LENGTH;
801             break;
802         }
803         break;
804       default:
805         break;
806     }
807 
808     if (rc) {
809         char dighex[MD_DIGEST_LENGTH_MAX*2+1];
810         li_tohex_uc(dighex, sizeof(dighex), (char *)digest, dlen);
811         lua_pushlstring(L, dighex, dlen*2);
812     }
813     else
814         lua_pushnil(L);
815 
816     return 1;
817 }
818 
819 static int magnet_digest_eq(lua_State *L) {
820     if (lua_gettop(L) != 2) {
821         lua_pushliteral(L,
822           "lighty.c.digest_eq(d1, d2): incorrect number of arguments");
823         return lua_error(L);
824     }
825     const_buffer d1 = magnet_checkconstbuffer(L, -2);
826     const_buffer d2 = magnet_checkconstbuffer(L, -1);
827     /* convert hex to binary: validate hex and eliminate hex case comparison */
828     uint8_t b1[MD_DIGEST_LENGTH_MAX];
829     uint8_t b2[MD_DIGEST_LENGTH_MAX];
830     int rc = (d1.len == d2.len)
831           && 0 == li_hex2bin(b1, sizeof(b1), d1.ptr, d1.len)
832           && 0 == li_hex2bin(b2, sizeof(b2), d2.ptr, d2.len)
833           && ck_memeq_const_time_fixed_len(b1, b2, d2.len >> 1);
834     lua_pushboolean(L, rc);
835     return 1;
836 }
837 
838 static int magnet_secret_eq(lua_State *L) {
839     if (lua_gettop(L) != 2) {
840         lua_pushliteral(L,
841           "lighty.c.secret_eq(d1, d2): incorrect number of arguments");
842         return lua_error(L);
843     }
844     const_buffer d1 = magnet_checkconstbuffer(L, -2);
845     const_buffer d2 = magnet_checkconstbuffer(L, -1);
846     lua_pushboolean(L, ck_memeq_const_time(d1.ptr, d1.len, d2.ptr, d2.len));
847     return 1;
848 }
849 
850 static int magnet_b64dec(lua_State *L, base64_charset dict) {
851     if (lua_isnoneornil(L, -1)) {
852         lua_pushlstring(L, "", 0);
853         return 1;
854     }
855     const_buffer s = magnet_checkconstbuffer(L, -1);
856     if (0 == s.len) {
857         lua_pushvalue(L, -1);
858         return 1;
859     }
860     buffer * const b = magnet_tmpbuf_acquire(L);
861     if (buffer_append_base64_decode(b, s.ptr, s.len, dict))
862         lua_pushlstring(L, BUF_PTR_LEN(b));
863     else
864         lua_pushnil(L);
865     magnet_tmpbuf_release(b);
866     return 1;
867 }
868 
869 static int magnet_b64enc(lua_State *L, base64_charset dict) {
870     if (lua_isnoneornil(L, -1)) {
871         lua_pushlstring(L, "", 0);
872         return 1;
873     }
874     const_buffer s = magnet_checkconstbuffer(L, -1);
875     if (0 == s.len) {
876         lua_pushvalue(L, -1);
877         return 1;
878     }
879     buffer * const b = magnet_tmpbuf_acquire(L);
880     buffer_append_base64_encode_no_padding(b, (uint8_t *)s.ptr, s.len, dict);
881     lua_pushlstring(L, BUF_PTR_LEN(b));
882     magnet_tmpbuf_release(b);
883     return 1;
884 }
885 
886 static int magnet_b64urldec(lua_State *L) {
887     return magnet_b64dec(L, BASE64_URL);
888 }
889 
890 static int magnet_b64urlenc(lua_State *L) {
891     return magnet_b64enc(L, BASE64_URL);
892 }
893 
894 static int magnet_b64stddec(lua_State *L) {
895     return magnet_b64dec(L, BASE64_STANDARD);
896 }
897 
898 static int magnet_b64stdenc(lua_State *L) {
899     return magnet_b64enc(L, BASE64_STANDARD);
900 }
901 
902 static int magnet_hexdec(lua_State *L) {
903     if (lua_isnoneornil(L, -1)) {
904         lua_pushlstring(L, "", 0);
905         return 1;
906     }
907     const_buffer s = magnet_checkconstbuffer(L, -1);
908     if (0 == s.len) {
909         lua_pushvalue(L, -1);
910         return 1;
911     }
912     buffer * const b = magnet_tmpbuf_acquire(L);
913     uint8_t * const p = (uint8_t *)buffer_extend(b, s.len >> 1);
914     int rc = li_hex2bin(p, s.len >> 1, s.ptr, s.len);
915     if (0 == rc)
916         lua_pushlstring(L, BUF_PTR_LEN(b));
917     magnet_tmpbuf_release(b);
918     return rc+1; /* 1 on success (pushed string); 0 on failure (no value) */
919 }
920 
921 static int magnet_hexenc(lua_State *L) {
922     if (lua_isnoneornil(L, -1)) {
923         lua_pushlstring(L, "", 0);
924         return 1;
925     }
926     const_buffer s = magnet_checkconstbuffer(L, -1);
927     if (0 == s.len) {
928         lua_pushvalue(L, -1);
929         return 1;
930     }
931     buffer * const b = magnet_tmpbuf_acquire(L);
932     buffer_append_string_encoded_hex_uc(b, s.ptr, s.len);
933     lua_pushlstring(L, BUF_PTR_LEN(b));
934     magnet_tmpbuf_release(b);
935     return 1; /* uppercase hex string; use lua s = s:lower() to lowercase */
936 }
937 
938 static int magnet_quoteddec(lua_State *L) {
939     if (lua_isnoneornil(L, -1)) {
940         lua_pushlstring(L, "", 0);
941         return 1;
942     }
943     const_buffer s = magnet_checkconstbuffer(L, -1);
944     if (0 == s.len || s.ptr[0] != '"') {
945         lua_pushvalue(L, -1);
946         return 1;
947     }
948     buffer * const b = magnet_tmpbuf_acquire(L);
949     char *p = buffer_string_prepare_append(b, s.len);/*(s.len-1 is sufficient)*/
950     size_t i = 1;
951     for (; i < s.len && s.ptr[i] != '"'; ++i) {
952         if (s.ptr[i] == '\\') {
953             if (i+2 < s.len)
954                 ++i;
955             else
956                 break;
957         }
958         *p++ = s.ptr[i];
959     }
960     int rc = (i == s.len-1 && s.ptr[i] == '"');
961     if (rc)
962         lua_pushlstring(L, b->ptr, (size_t)(p - b->ptr));
963     magnet_tmpbuf_release(b);
964     return rc; /* 1 on success (pushed string); 0 on failure (no value) */
965 }
966 
967 static int magnet_quotedenc(lua_State *L) {
968     if (lua_isnoneornil(L, -1)) {
969         lua_pushlstring(L, "", 0);
970         return 1;
971     }
972     const_buffer s = magnet_checkconstbuffer(L, -1);
973     if (0 == s.len) {
974         lua_pushvalue(L, -1);
975         return 1;
976     }
977     buffer * const b = magnet_tmpbuf_acquire(L);
978     char *p = buffer_string_prepare_append(b, 2+(s.len << 1));
979     *p++ = '"';
980     for (size_t i = 0; i < s.len; ++i) {
981         /*(note: not strictly checking for TEXT)*/
982         /*(TEXT: any OCTET except CTLs but including LWS)*/
983         if (s.ptr[i] == '"' || s.ptr[i] == '\\')
984             *p++ = '\\';
985         *p++ = s.ptr[i];
986     }
987     *p++ = '"';
988     lua_pushlstring(L, b->ptr, (size_t)(p - b->ptr));
989     magnet_tmpbuf_release(b);
990     return 1;
991 }
992 
993 static int magnet_xmlenc(lua_State *L) {
994     if (lua_isnoneornil(L, -1)) {
995         lua_pushlstring(L, "", 0);
996         return 1;
997     }
998     const_buffer s = magnet_checkconstbuffer(L, -1);
999     if (0 == s.len) {
1000         lua_pushvalue(L, -1);
1001         return 1;
1002     }
1003     buffer * const b = magnet_tmpbuf_acquire(L);
1004   #if 1
1005     buffer_append_string_encoded(b, s.ptr, s.len, ENCODING_MINIMAL_XML);
1006   #else
1007     const char *e;
1008     size_t i, n, elen;
1009     for (i = 0, n = 0; i < s.len; ++i) {
1010         switch (s.ptr[i]) {
1011           default: continue;
1012           case '<':  e = "&lt;";   elen = sizeof("&lt;")-1;   break;
1013           case '>':  e = "&gt;";   elen = sizeof("&gt;")-1;   break;
1014           case '&':  e = "&amp;";  elen = sizeof("&amp;")-1;  break;
1015           case '\'': e = "&apos;"; elen = sizeof("&apos;")-1; break;
1016           case '"':  e = "&quot;"; elen = sizeof("&quot;")-1; break;
1017           /*(XXX: would be good idea to add CTRLs, DEL, '`' */
1018         }
1019         buffer_append_str2(b, s.ptr+n, i-n, e, elen);
1020         n = i+1;
1021     }
1022     if (i-n)
1023         buffer_append_string_len(b, s.ptr+n, i-n);
1024   #endif
1025     lua_pushlstring(L, BUF_PTR_LEN(b));
1026     magnet_tmpbuf_release(b);
1027     return 1;
1028 }
1029 
1030 static int magnet_urldec(lua_State *L) {
1031     /* url-decode and replace non-printable chars with '_'
1032      * This function should not be used on query-string unless it is used on
1033      * portions of query-string after splitting on '&', replacing '+' w/ ' ' */
1034     if (lua_isnoneornil(L, -1)) {
1035         lua_pushlstring(L, "", 0);
1036         return 1;
1037     }
1038     const_buffer s = magnet_checkconstbuffer(L, -1);
1039     if (0 == s.len) {
1040         lua_pushvalue(L, -1);
1041         return 1;
1042     }
1043     buffer * const b = magnet_tmpbuf_acquire(L);
1044     buffer_copy_string_len(b, s.ptr, s.len);
1045     buffer_urldecode_path(b);
1046     lua_pushlstring(L, BUF_PTR_LEN(b));
1047     magnet_tmpbuf_release(b);
1048     return 1;
1049 }
1050 
1051 static int magnet_urlenc(lua_State *L) {
1052     /* url-encode path
1053      * ('?' is encoded, if present)
1054      *  caller must split string if '?' is part of query-string)
1055      * ('/' is not encoded; caller must encode if not path separator) */
1056     if (lua_isnoneornil(L, -1)) {
1057         lua_pushlstring(L, "", 0);
1058         return 1;
1059     }
1060     const_buffer s = magnet_checkconstbuffer(L, -1);
1061     if (0 == s.len) {
1062         lua_pushvalue(L, -1);
1063         return 1;
1064     }
1065     buffer * const b = magnet_tmpbuf_acquire(L);
1066     buffer_append_string_encoded(b, s.ptr, s.len, ENCODING_REL_URI);
1067     lua_pushlstring(L, BUF_PTR_LEN(b));
1068     magnet_tmpbuf_release(b);
1069     return 1;
1070 }
1071 
1072 static void magnet_urldec_query_part(buffer * const b, const char *s, const size_t slen) {
1073     buffer_clear(b);
1074     char *p = buffer_extend(b, slen);
1075     for (size_t i = 0; i < slen; ++i)
1076         p[i] = (s[i] != '+') ? s[i] : ' ';
1077     buffer_urldecode_path(b);
1078 }
1079 
1080 static int magnet_urldec_query(lua_State *L) {
1081     /* split on '&' and '=', url-decode and replace non-printable chars w/ '_',
1082      * and store components in table
1083      * (string input should be query-string without leading '?')
1084      * (note: duplicated keys replace earlier values, but this interface returns
1085      *  a table useful for lookups, so this limitation is often acceptable) */
1086     lua_createtable(L, 0, 0);
1087     if (lua_isnoneornil(L, 1)) {
1088         return 1;
1089     }
1090     const_buffer s = magnet_checkconstbuffer(L, 1);
1091     if (0 == s.len) {
1092         return 1;
1093     }
1094     buffer * const b = magnet_tmpbuf_acquire(L);
1095     for (const char *qs = s.ptr, *eq, *amp; *qs; qs = amp+1) {
1096         for (amp = qs, eq = NULL; *amp && *amp != '&'; ++amp) {
1097             if (*amp == '=' && !eq) eq = amp;
1098         }
1099         if (amp != qs) {
1100             if (eq) {
1101                 magnet_urldec_query_part(b, qs, (size_t)(eq - qs));
1102                 lua_pushlstring(L, BUF_PTR_LEN(b));
1103                 magnet_urldec_query_part(b, eq+1, (size_t)(amp - (eq+1)));
1104                 lua_pushlstring(L, BUF_PTR_LEN(b));
1105             }
1106             else {
1107                 magnet_urldec_query_part(b, qs, (size_t)(amp - qs));
1108                 lua_pushlstring(L, BUF_PTR_LEN(b));
1109                 lua_pushlstring(L, "", 0); /*(lua_pushnil() would delete key)*/
1110             }
1111             lua_rawset(L, -3);
1112         }
1113         if (*amp == '\0') break;
1114     }
1115     magnet_tmpbuf_release(b);
1116     return 1;
1117 }
1118 
1119 static void magnet_urlenc_query_part(buffer * const b, const char * const s, const size_t slen, const int iskey) {
1120     /* encode query part (each part is typically much smaller than chunk buffer)
1121      * all required chars plus '&' ';' '+' '\'' (and encode '=' if part of key)
1122      * (burl_append(b,str,len,BURL_ENCODE_ALL) works, but over-encodes) */
1123   #if 0
1124     /* alternative: (over-encodes some, but less than burl_append()) */
1125     UNUSED(iskey);
1126     buffer_append_string_encoded(b, s, slen, ENCODING_REL_URI);
1127   #else
1128     static const char hex_chars_uc[] = "0123456789ABCDEF";
1129     char * const p = buffer_string_prepare_append(b, slen*3);
1130     int j = 0;
1131     for (size_t i = 0; i < slen; ++i, ++j) {
1132         int c = s[i];
1133         if (!light_isalnum(c)) switch (c) {
1134           case ' ':
1135             c = '+';
1136             break;
1137           /*case '\'':*//*(ok in url query-part, but might be misused in HTML)*/
1138           case '!': case '$': case '(': case ')': case '*': case ',': case '-':
1139           case '.': case '/': case ':': case '?': case '@': case '_': case '~':
1140             break;
1141           case '=':
1142             if (!iskey) break;
1143             __attribute_fallthrough__
1144           default:
1145             p[j]   = '%';
1146             p[++j] = hex_chars_uc[(s[i] >> 4) & 0xF];
1147             p[++j] = hex_chars_uc[s[i] & 0xF];
1148             continue;
1149         }
1150         p[j] = c;
1151     }
1152     buffer_commit(b, j);
1153   #endif
1154 }
1155 
1156 static int magnet_urlenc_query(lua_State *L) {
1157     /* encode pairs in lua table into query string
1158      * (caller should add leading '?' or '&' when appending to full URL)
1159      * (caller should skip empty table if appending to existing query-string) */
1160     if (!lua_istable(L, 1)) {
1161         lua_pushlstring(L, "", 0);
1162         return 1;
1163     }
1164     buffer * const b = magnet_tmpbuf_acquire(L);
1165     const_buffer s;
1166     for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
1167         if (lua_isstring(L, -2)) {
1168             if (!buffer_is_blank(b))
1169                 buffer_append_string_len(b, CONST_STR_LEN("&"));
1170             s = magnet_checkconstbuffer(L, -2);
1171             magnet_urlenc_query_part(b, s.ptr, s.len, 1);
1172             if (!lua_isnil(L, -1)) {
1173                 s = magnet_checkconstbuffer(L, -1);
1174                 buffer_append_string_len(b, CONST_STR_LEN("="));
1175                 magnet_urlenc_query_part(b, s.ptr, s.len, 0);
1176             }
1177         }
1178     }
1179     lua_pushlstring(L, BUF_PTR_LEN(b));
1180     magnet_tmpbuf_release(b);
1181     return 1;
1182 }
1183 
1184 static int magnet_urlenc_normalize(lua_State *L) {
1185     /* normalize url-encoding
1186      * url-encode (and url-decode safe chars) to normalize url-path
1187      * ('?' is treated as start of query-string and is not encoded;
1188      *  caller must encode '?' if intended to be part of url-path)
1189      * ('/' is not encoded; caller must encode if not path separator)
1190      * (burl_append() is not exposed here; caller might want to build
1191      *  url with lighty.c.urlenc() and lighty.c.urlenc_query(),
1192      *  then call lighty.c.urlenc_normalize()) */
1193     if (lua_isnoneornil(L, -1)) {
1194         lua_pushlstring(L, "", 0);
1195         return 1;
1196     }
1197     const_buffer s = magnet_checkconstbuffer(L, -1);
1198     if (0 == s.len) {
1199         lua_pushvalue(L, -1);
1200         return 1;
1201     }
1202     buffer * const b = magnet_tmpbuf_acquire(L);
1203     buffer * const t = chunk_buffer_acquire();
1204   #if 0 /*(?maybe have different interface to use config policy?)*/
1205     request_st * const r = magnet_get_request(L);
1206     const int flags = r->conf.http_parseopts;
1207   #else
1208     const int flags = HTTP_PARSEOPT_URL_NORMALIZE
1209                     | HTTP_PARSEOPT_URL_NORMALIZE_UNRESERVED
1210                     | HTTP_PARSEOPT_URL_NORMALIZE_REQUIRED
1211                     | HTTP_PARSEOPT_URL_NORMALIZE_PATH_2F_DECODE
1212                     | HTTP_PARSEOPT_URL_NORMALIZE_PATH_DOTSEG_REMOVE
1213                     | HTTP_PARSEOPT_URL_NORMALIZE_QUERY_20_PLUS;
1214   #endif
1215     buffer_copy_string_len(b, s.ptr, s.len);
1216     burl_normalize(b, t, flags);
1217     lua_pushlstring(L, BUF_PTR_LEN(b));
1218     chunk_buffer_release(t);
1219     magnet_tmpbuf_release(b);
1220     return 1;
1221 }
1222 
1223 static int magnet_fspath_simplify(lua_State *L) {
1224     /* simplify filesystem path */
1225     if (lua_isnoneornil(L, -1)) {
1226         lua_pushlstring(L, "", 0);
1227         return 1;
1228     }
1229     const_buffer s = magnet_checkconstbuffer(L, -1);
1230     if (0 == s.len) {
1231         lua_pushvalue(L, -1);
1232         return 1;
1233     }
1234     buffer * const b = magnet_tmpbuf_acquire(L);
1235     buffer_copy_string_len(b, s.ptr, s.len);
1236     buffer_path_simplify(b);
1237     lua_pushlstring(L, BUF_PTR_LEN(b));
1238     magnet_tmpbuf_release(b);
1239     return 1;
1240 }
1241 
1242 __attribute_pure__
1243 static const char * magnet_scan_quoted_string (const char *s) {
1244     /* scan to end of of quoted-string (with s starting at '"')
1245      * loose parse; non-validating
1246      * - permit missing '"' at end of string (caller should check)
1247      * - stop at stray '\\' at end of string missing closing '"'
1248      * - not rejecting non-WS CTL chars
1249      */
1250     /*force_assert(*s == '"');*/
1251     do { ++s; } while (*s && *s != '"' && (*s != '\\' || (s[1] ? ++s : 0)));
1252     /*do { ++s; } while (*s && *s != '"' && (*s != '\\' || (*++s || (--s, 0))));*/
1253     return s;
1254 }
1255 
1256 static const char * magnet_push_quoted_string_range (lua_State *L, const char *b, const char *s) {
1257     /* quoted-string is unmodified (except for quoted-string end consistency)
1258      * including surrounding double-quotes and with quoted-pair unmodified */
1259     if (__builtin_expect( (*s == '"'), 1))
1260         lua_pushlstring(L, b, (size_t)(++s-b));
1261     else { /*(else invalid quoted-string, but handle anyway)*/
1262         /* append closing '"' for consistency */
1263         lua_pushlstring(L, b, (size_t)(s-b));
1264         if (*s != '\\')
1265             lua_pushlstring(L, "\"", 1);
1266         else { /* unescaped backslash at end of string; escape and close '"' */
1267             lua_pushlstring(L, "\\\\\"", 3);
1268             ++s; /*(now *s == '\0')*/
1269         }
1270         lua_concat(L, 2);
1271     }
1272     return s;
1273 }
1274 
1275 static const char * magnet_push_quoted_string(lua_State *L, const char *s) {
1276     return magnet_push_quoted_string_range(L, s, magnet_scan_quoted_string(s));
1277 }
1278 
1279 static const char * magnet_cookie_param_push_token(lua_State *L, const char *s) {
1280     const char *b = s;
1281     while (*s!='=' /*(note: not strictly rejecting all 'separators')*/
1282             && *s!=';' && *s!=' ' && *s!='\t' && *s!='\r' && *s!='\n' && *s)
1283         ++s;
1284     lua_pushlstring(L, b, (size_t)(s-b));
1285     return s;
1286 }
1287 
1288 static int magnet_cookie_tokens(lua_State *L) {
1289     /*(special-case cookies (';' separator); dup cookie-names overwritten)*/
1290     lua_createtable(L, 0, 0);
1291     if (lua_isnoneornil(L, 1))
1292         return 1;
1293     const char *s = luaL_checkstring(L, 1);
1294     do {
1295         while (*s==';' || *s==' ' || *s=='\t' || *s=='\r' || *s=='\n')
1296             ++s;
1297         if (*s == '\0') break;
1298         s = magnet_cookie_param_push_token(L, s);
1299         while (           *s==' ' || *s=='\t' || *s=='\r' || *s=='\n')
1300             ++s;
1301         if (*s == '=') {
1302             do {
1303                 ++s;
1304             } while (     *s==' ' || *s=='\t' || *s=='\r' || *s=='\n');
1305             if (*s==';' || *s=='\0')
1306                 lua_pushlstring(L, "", 0); /*(lua_pushnil() would delete key)*/
1307             else if (*s != '"')
1308                 s = magnet_cookie_param_push_token(L, s);
1309             else
1310                 s = magnet_push_quoted_string(L, s);
1311         }
1312         else {
1313             lua_pushlstring(L, "", 0); /*(lua_pushnil() would delete key)*/
1314         }
1315         lua_settable(L, -3);
1316         while (*s!=';' && *s!='\0') ++s; /* ignore/skip stray tokens */
1317     } while (*s++);
1318     return 1;
1319 }
1320 
1321 static const char * magnet_push_token(lua_State *L, const char *s) {
1322     const char *b = s;
1323     while (               *s!=' ' && *s!='\t' && *s!='\r' && *s!='\n'
1324            && *s!=',' && *s!=';' && *s!='=' && *s)
1325         ++s;
1326     lua_pushlstring(L, b, (size_t)(s-b));
1327     return s;
1328 }
1329 
1330 static int magnet_header_tokens(lua_State *L) {
1331     /* split into sequence of tokens/words
1332      *   Intermediate table is then more convenient to walk once quoted-string
1333      *   parsed into table entries since quoted-string may contain separators.
1334      *   Each token can be passed to lighty.c.quoteddec()
1335      *     (lighty.c.quoteddec() returns string as-is if not quoted-string)
1336      * (note: non-validating;
1337      *  e.g. existence of '=' token does not mean that next token is value,
1338      *       and '=' in value which is not part of quoted-string will be
1339      *       treated as separate token)
1340      * (note: case is preserved; non-quoted-string tokens are not lower-cased)
1341      * (words separated by whitespace are separate tokens unless quoted-string)
1342      *   (if that format not permitted in a specific header, caller must detect)
1343      * (optional whitespace (OWS) and bad whitespace (BWS) are removed)
1344      * (caller can create lookup table from sequence table, as appropriate) */
1345     lua_createtable(L, 0, 0);
1346     if (lua_isnoneornil(L, 1))
1347         return 1;
1348     const char *s = luaL_checkstring(L, 1);
1349     int i = 0;
1350     do {
1351         while (           *s==' ' || *s=='\t' || *s=='\r' || *s=='\n')
1352             ++s;
1353         if (*s=='\0') break;
1354         if (*s==',' || *s==';' || *s=='=')
1355             lua_pushlstring(L, s++, 1);
1356         else if (*s != '"')
1357             s = magnet_push_token(L, s);
1358         else
1359             s = magnet_push_quoted_string(L, s);
1360         lua_rawseti(L, -2, ++i);
1361     } while (*s);
1362     return 1;
1363 }
1364 
1365 static int magnet_atpanic(lua_State *L) {
1366 	request_st * const r = magnet_get_request(L);
1367 	log_error(r->conf.errh, __FILE__, __LINE__, "(lua-atpanic) %s",
1368 	          lua_isstring(L, 1) ? lua_tostring(L, 1) : "");
1369 	/*longjmp(exceptionjmp, 1);*//*(must be init with setjmp() elsewhere)*/
1370 	return 0;
1371 }
1372 
1373 static int magnet_reqhdr_get(lua_State *L) {
1374     /* __index: param 1 is the (empty) table the value was not found in */
1375     size_t klen;
1376     const char * const k = luaL_checklstring(L, 2, &klen);
1377     request_st * const r = magnet_get_request(L);
1378     const int id = http_header_hkey_get(k, (uint32_t)klen);
1379     magnet_push_buffer(L, http_header_request_get(r, id, k, klen));
1380     return 1;
1381 }
1382 
1383 static int magnet_reqhdr_set(lua_State *L) {
1384     /* __newindex: param 1 is (empty) table in which value is to be set */
1385     const_buffer k = magnet_checkconstbuffer(L, 2);
1386     const_buffer v = magnet_checkconstbuffer(L, 3);
1387 
1388     request_st * const r = magnet_get_request(L);
1389     enum http_header_e id = http_header_hkey_get(k.ptr, (uint32_t)k.len);
1390 
1391     switch (id) {
1392       /*case HTTP_HEADER_OTHER:*/
1393       default:
1394         break;
1395 
1396       case HTTP_HEADER_HOST:
1397         /* do not allow Host to be unset, even if HTTP/1.0
1398          * (change Host to something else, if you must */
1399         if (0 == v.len) return 0;
1400 
1401         /*(must set r->http_host if r->http_host was not previously set)*/
1402         /* copied from request.c:http_request_header_set_Host() */
1403         r->http_host = http_header_request_set_ptr(r, HTTP_HEADER_HOST,
1404                                                    CONST_STR_LEN("Host"));
1405         buffer_copy_string_len_lc(r->http_host, v.ptr, v.len);
1406         return 0;
1407 
1408       case HTTP_HEADER_CONTENT_LENGTH:
1409         /* not attempting to handle Content-Length modification; may revisit */
1410         /* future: might permit setting to 0 to force discard of request body
1411          * but would have to check if request body present, and if
1412          * Content-Length was set, or if Transfer-Encoding: chunked,
1413          * and handle resetting internal chunked encoding state,
1414          * as well as making sure that this is handled properly for HTTP/2 */
1415         return 0; /* silently ignore; do not allow modification */
1416 
1417       /* do not permit modification of hop-by-hop (connection) headers */
1418 
1419       case HTTP_HEADER_CONNECTION:
1420         /* do not permit modification of Connection, incl add/remove tokens */
1421         /* future: might provide a different interface to set r->keep_alive = 0,
1422          *         and also handle in context if HTTP/2 */
1423       case HTTP_HEADER_TRANSFER_ENCODING:
1424       case HTTP_HEADER_SET_COOKIE:/*(response hdr;avoid accidental reflection)*/
1425         return 0; /* silently ignore; do not allow modification */
1426      #if 0 /*(eh, if script sets Upgrade, script probably intends this action)*/
1427       case HTTP_HEADER_UPGRADE:
1428         /* note: modifications here do not modify Connection header
1429          *       to add or remove "upgrade" token */
1430         /* future: might allow removal of existing tokens, but not addition */
1431         if (0 != v.len) return 0; /* do not allow Upgrade other than to unset */
1432         break;
1433      #endif
1434      #if 0 /*(eh, if script sets TE, script probably intends this action)*/
1435       case HTTP_HEADER_TE:
1436         if (0 != v.len) return 0; /* do not allow TE other than to unset */
1437         break;
1438      #endif
1439     }
1440 
1441     v.len
1442       ? http_header_request_set(r, id, k.ptr, k.len, v.ptr, v.len)
1443       : http_header_request_unset(r, id, k.ptr, k.len);
1444     return 0;
1445 }
1446 
1447 static int magnet_reqhdr_pairs(lua_State *L) {
1448 	request_st * const r = magnet_get_request(L);
1449 	return magnet_array_pairs(L, &r->rqst_headers);
1450 }
1451 
1452 static int magnet_resphdr_get(lua_State *L) {
1453     /* __index: param 1 is the (empty) table the value was not found in */
1454     /* Note: access to lighttpd r->resp_headers here is *independent* from
1455      * the (pending) changes in the (deprecated) lua lighty.headers[] table */
1456     size_t klen;
1457     const char * const k = luaL_checklstring(L, 2, &klen);
1458     request_st * const r = magnet_get_request(L);
1459     const int id = http_header_hkey_get(k, (uint32_t)klen);
1460     magnet_push_buffer(L, http_header_response_get(r, id, k, klen));
1461     return 1;
1462 }
1463 
1464 static int magnet_resphdr_set_kv(lua_State *L, request_st * const r) {
1465     const const_buffer k = magnet_checkconstbuffer(L, -2);
1466     const const_buffer v = magnet_checkconstbuffer(L, -1);
1467     const enum http_header_e id = http_header_hkey_get(k.ptr, (uint32_t)k.len);
1468 
1469     switch (id) {
1470       /*case HTTP_HEADER_OTHER:*/
1471       default:
1472         break;
1473 
1474       case HTTP_HEADER_CONTENT_LENGTH:
1475         /* lighttpd handles Content-Length or Transfer-Encoding for response */
1476         return 0; /* silently ignore; do not allow modification */
1477 
1478       /* do not permit modification of hop-by-hop (connection) headers */
1479 
1480       case HTTP_HEADER_CONNECTION:
1481         /* do not permit modification of Connection, incl add/remove tokens */
1482         /* future: might provide a different interface to set r->keep_alive = 0,
1483          *         and also handle in context if HTTP/2 */
1484       case HTTP_HEADER_TRANSFER_ENCODING:
1485         return 0; /* silently ignore; do not allow modification */
1486     }
1487 
1488     if (0 == v.len) {
1489         http_header_response_unset(r, id, k.ptr, k.len);
1490         return 0;
1491     }
1492 
1493     buffer * const vb = http_header_response_set_ptr(r, id, k.ptr, k.len);
1494     buffer_copy_string_len(vb, v.ptr, v.len);
1495 
1496     if (r->http_version >= HTTP_VERSION_2) {
1497         /* handle multi-line response headers with HTTP/2
1498          * (lowercase header name and mark r->resp_header_repeated)
1499          * (similar to http_header.c:http_header_response_insert_addtl()) */
1500         for (char *n = vb->ptr; (n = strchr(n, '\n')); ) {
1501             r->resp_header_repeated = 1;
1502             do {
1503                 ++n;
1504                 if (light_isupper(*n)) *n |= 0x20;
1505             } while (*n != ':' && *n != '\n' && *n != '\0');
1506         }
1507     }
1508 
1509     return 0;
1510 }
1511 
1512 static int magnet_resphdr_set(lua_State *L) {
1513     /* __newindex: param 1 is (empty) table in which value is to be set */
1514     /*const_buffer k = magnet_checkconstbuffer(L, 2);*/
1515     /*const_buffer v = magnet_checkconstbuffer(L, 3);*/
1516     request_st * const r = magnet_get_request(L);
1517     return magnet_resphdr_set_kv(L, r);
1518 }
1519 
1520 static int magnet_resphdr_pairs(lua_State *L) {
1521     request_st * const r = magnet_get_request(L);
1522     return magnet_array_pairs(L, &r->resp_headers);
1523 }
1524 
1525 static int magnet_status_get(lua_State *L) {
1526 	/* __index: param 1 is the (empty) table the value was not found in */
1527 	const_buffer key = magnet_checkconstbuffer(L, 2);
1528 	int *i = status_counter_get_counter(key.ptr, key.len);
1529 	lua_pushinteger(L, (lua_Integer)*i);
1530 
1531 	return 1;
1532 }
1533 
1534 static int magnet_status_set(lua_State *L) {
1535 	/* __newindex: param 1 is the (empty) table the value is supposed to be set in */
1536 	const_buffer key = magnet_checkconstbuffer(L, 2);
1537 	int counter = (int) luaL_checkinteger(L, 3);
1538 
1539 	status_counter_set(key.ptr, key.len, counter);
1540 
1541 	return 0;
1542 }
1543 
1544 static int magnet_status_pairs(lua_State *L) {
1545 	return magnet_array_pairs(L, &plugin_stats);
1546 }
1547 
1548 typedef struct {
1549 	const char *name;
1550 	uint32_t nlen;
1551 	enum {
1552 		MAGNET_ENV_UNSET,
1553 
1554 		MAGNET_ENV_PHYSICAL_PATH,
1555 		MAGNET_ENV_PHYSICAL_REL_PATH,
1556 		MAGNET_ENV_PHYSICAL_DOC_ROOT,
1557 		MAGNET_ENV_PHYSICAL_BASEDIR,
1558 
1559 		MAGNET_ENV_URI_PATH,
1560 		MAGNET_ENV_URI_PATH_RAW,
1561 		MAGNET_ENV_URI_SCHEME,
1562 		MAGNET_ENV_URI_AUTHORITY,
1563 		MAGNET_ENV_URI_QUERY,
1564 
1565 		MAGNET_ENV_REQUEST_METHOD,
1566 		MAGNET_ENV_REQUEST_URI,
1567 		MAGNET_ENV_REQUEST_ORIG_URI,
1568 		MAGNET_ENV_REQUEST_PATH_INFO,
1569 		MAGNET_ENV_REQUEST_REMOTE_ADDR,
1570 		MAGNET_ENV_REQUEST_REMOTE_PORT,
1571 		MAGNET_ENV_REQUEST_SERVER_ADDR,
1572 		MAGNET_ENV_REQUEST_SERVER_PORT,
1573 		MAGNET_ENV_REQUEST_PROTOCOL,
1574 
1575 		MAGNET_ENV_RESPONSE_HTTP_STATUS,
1576 		MAGNET_ENV_RESPONSE_BODY_LENGTH,
1577 		MAGNET_ENV_RESPONSE_BODY
1578 	} type;
1579 } magnet_env_t;
1580 
1581 static const magnet_env_t magnet_env[] = {
1582     { CONST_STR_LEN("physical.path"),        MAGNET_ENV_PHYSICAL_PATH },
1583     { CONST_STR_LEN("physical.rel-path"),    MAGNET_ENV_PHYSICAL_REL_PATH },
1584     { CONST_STR_LEN("physical.doc-root"),    MAGNET_ENV_PHYSICAL_DOC_ROOT },
1585     { CONST_STR_LEN("physical.basedir"),     MAGNET_ENV_PHYSICAL_BASEDIR },
1586 
1587     { CONST_STR_LEN("uri.path"),             MAGNET_ENV_URI_PATH },
1588     { CONST_STR_LEN("uri.path-raw"),         MAGNET_ENV_URI_PATH_RAW },
1589     { CONST_STR_LEN("uri.scheme"),           MAGNET_ENV_URI_SCHEME },
1590     { CONST_STR_LEN("uri.authority"),        MAGNET_ENV_URI_AUTHORITY },
1591     { CONST_STR_LEN("uri.query"),            MAGNET_ENV_URI_QUERY },
1592 
1593     { CONST_STR_LEN("request.method"),       MAGNET_ENV_REQUEST_METHOD },
1594     { CONST_STR_LEN("request.uri"),          MAGNET_ENV_REQUEST_URI },
1595     { CONST_STR_LEN("request.orig-uri"),     MAGNET_ENV_REQUEST_ORIG_URI },
1596     { CONST_STR_LEN("request.path-info"),    MAGNET_ENV_REQUEST_PATH_INFO },
1597     { CONST_STR_LEN("request.remote-ip"),    MAGNET_ENV_REQUEST_REMOTE_ADDR },
1598     { CONST_STR_LEN("request.remote-addr"),  MAGNET_ENV_REQUEST_REMOTE_ADDR },
1599     { CONST_STR_LEN("request.remote-port"),  MAGNET_ENV_REQUEST_REMOTE_PORT },
1600     { CONST_STR_LEN("request.server-addr"),  MAGNET_ENV_REQUEST_SERVER_ADDR },
1601     { CONST_STR_LEN("request.server-port"),  MAGNET_ENV_REQUEST_SERVER_PORT },
1602     { CONST_STR_LEN("request.protocol"),     MAGNET_ENV_REQUEST_PROTOCOL },
1603 
1604     { CONST_STR_LEN("response.http-status"), MAGNET_ENV_RESPONSE_HTTP_STATUS },
1605     { CONST_STR_LEN("response.body-length"), MAGNET_ENV_RESPONSE_BODY_LENGTH },
1606     { CONST_STR_LEN("response.body"),        MAGNET_ENV_RESPONSE_BODY },
1607 
1608     { NULL, 0, MAGNET_ENV_UNSET }
1609 };
1610 
1611 static buffer *magnet_env_get_buffer_by_id(request_st * const r, int id) {
1612 	buffer *dest = NULL;
1613 
1614 	/**
1615 	 * map all internal variables to lua
1616 	 *
1617 	 */
1618 
1619 	switch (id) {
1620 	case MAGNET_ENV_PHYSICAL_PATH: dest = &r->physical.path; break;
1621 	case MAGNET_ENV_PHYSICAL_REL_PATH: dest = &r->physical.rel_path; break;
1622 	case MAGNET_ENV_PHYSICAL_DOC_ROOT: dest = &r->physical.doc_root; break;
1623 	case MAGNET_ENV_PHYSICAL_BASEDIR: dest = &r->physical.basedir; break;
1624 
1625 	case MAGNET_ENV_URI_PATH: dest = &r->uri.path; break;
1626 	case MAGNET_ENV_URI_PATH_RAW:
1627 	    {
1628 		dest = r->tmp_buf;
1629 		buffer_clear(dest);
1630 		uint32_t len = buffer_clen(&r->target);
1631 		char *qmark = memchr(r->target.ptr, '?', len);
1632 		buffer_copy_string_len(dest, r->target.ptr, qmark ? (uint32_t)(qmark - r->target.ptr) : len);
1633 		break;
1634 	    }
1635 	case MAGNET_ENV_URI_SCHEME: dest = &r->uri.scheme; break;
1636 	case MAGNET_ENV_URI_AUTHORITY: dest = &r->uri.authority; break;
1637 	case MAGNET_ENV_URI_QUERY: dest = &r->uri.query; break;
1638 
1639 	case MAGNET_ENV_REQUEST_METHOD:
1640 		dest = r->tmp_buf;
1641 		buffer_clear(dest);
1642 		http_method_append(dest, r->http_method);
1643 		break;
1644 	case MAGNET_ENV_REQUEST_URI:      dest = &r->target; break;
1645 	case MAGNET_ENV_REQUEST_ORIG_URI: dest = &r->target_orig; break;
1646 	case MAGNET_ENV_REQUEST_PATH_INFO: dest = &r->pathinfo; break;
1647 	case MAGNET_ENV_REQUEST_REMOTE_ADDR: dest = &r->con->dst_addr_buf; break;
1648 	case MAGNET_ENV_REQUEST_REMOTE_PORT:
1649 		dest = r->tmp_buf;
1650 		buffer_clear(dest);
1651 		buffer_append_int(dest, sock_addr_get_port(&r->con->dst_addr));
1652 		break;
1653 	case MAGNET_ENV_REQUEST_SERVER_ADDR: /* local IP without port */
1654 	    {
1655 		const server_socket * const srv_socket = r->con->srv_socket;
1656 		dest = r->tmp_buf;
1657 		buffer_clear(dest);
1658 		switch (sock_addr_get_family(&srv_socket->addr)) {
1659 		case AF_INET:
1660 		case AF_INET6:
1661 			if (sock_addr_is_addr_wildcard(&srv_socket->addr)) {
1662 				sock_addr addrbuf;
1663 				socklen_t addrlen = sizeof(addrbuf);
1664 				const int fd = r->con->fd;
1665 				if (0 == getsockname(fd,(struct sockaddr *)&addrbuf,&addrlen)) {
1666 					char buf[INET6_ADDRSTRLEN + 1];
1667 					const char *s = sock_addr_inet_ntop(&addrbuf, buf, sizeof(buf));
1668 					if (NULL != s)
1669 						buffer_copy_string_len(dest, s, strlen(s));
1670 				}
1671 			}
1672 			else
1673 				buffer_copy_string_len(dest, srv_socket->srv_token->ptr,
1674 				                       srv_socket->srv_token_colon);
1675 			break;
1676 		default:
1677 			break;
1678 		}
1679 		break;
1680 	    }
1681 	case MAGNET_ENV_REQUEST_SERVER_PORT:
1682 	    {
1683 		const server_socket * const srv_socket = r->con->srv_socket;
1684 		const buffer * const srv_token = srv_socket->srv_token;
1685 		const uint32_t portoffset = srv_socket->srv_token_colon+1;
1686 		dest = r->tmp_buf;
1687 		buffer_copy_string_len(dest, srv_token->ptr+portoffset,
1688 		                       buffer_clen(srv_token)-portoffset);
1689 		break;
1690 	    }
1691 	case MAGNET_ENV_REQUEST_PROTOCOL:
1692 		dest = r->tmp_buf;
1693 		buffer_clear(dest);
1694 		http_version_append(dest, r->http_version);
1695 		break;
1696 	case MAGNET_ENV_RESPONSE_HTTP_STATUS:
1697 		dest = r->tmp_buf;
1698 		buffer_clear(dest);
1699 		buffer_append_int(dest, r->http_status);
1700 		break;
1701 	case MAGNET_ENV_RESPONSE_BODY_LENGTH:
1702 		dest = r->tmp_buf;
1703 		buffer_clear(dest);
1704 		if (!r->resp_body_finished)
1705 			break;
1706 		buffer_append_int(dest, chunkqueue_length(&r->write_queue));
1707 		break;
1708 	case MAGNET_ENV_RESPONSE_BODY:
1709 		if (!r->resp_body_finished)
1710 			break;
1711 		else {
1712 			chunkqueue * const cq = &r->write_queue;
1713 			off_t len = chunkqueue_length(cq);
1714 			if (0 == len) {
1715 				dest = r->tmp_buf;
1716 				buffer_copy_string_len(dest, CONST_STR_LEN(""));
1717 				break;
1718 			}
1719 			dest = chunkqueue_read_squash(cq, r->conf.errh);
1720 			if (NULL == dest) {
1721 				dest = r->tmp_buf;
1722 				buffer_clear(dest);
1723 			}
1724 		}
1725 		break;
1726 
1727 	case MAGNET_ENV_UNSET: break;
1728 	}
1729 
1730 	return dest;
1731 }
1732 
1733 static int magnet_env_get_id(const char * const key, const size_t klen) {
1734     for (int i = 0; magnet_env[i].name; ++i) {
1735         if (klen == magnet_env[i].nlen
1736             && 0 == memcmp(key, magnet_env[i].name, klen))
1737             return magnet_env[i].type;
1738     }
1739     return MAGNET_ENV_UNSET;
1740 }
1741 
1742 static buffer *magnet_env_get_buffer(request_st * const r, const char * const k, const size_t klen) {
1743     return magnet_env_get_buffer_by_id(r, magnet_env_get_id(k, klen));
1744 }
1745 
1746 static int magnet_env_get(lua_State *L) {
1747     /* __index: param 1 is the (empty) table the value was not found in */
1748     size_t klen;
1749     const char * const k = luaL_checklstring(L, 2, &klen);
1750     request_st * const r = magnet_get_request(L);
1751     magnet_push_buffer(L, magnet_env_get_buffer(r, k, klen));
1752     return 1;
1753 }
1754 
1755 static int magnet_env_set(lua_State *L) {
1756     /* __newindex: param 1 is the (empty) table the value is supposed to be set in */
1757     size_t klen;
1758     const char * const key = luaL_checklstring(L, 2, &klen);
1759     const_buffer val = magnet_checkconstbuffer(L, 3);
1760 
1761     request_st * const r = magnet_get_request(L);
1762     const int env_id = magnet_env_get_id(key, klen);
1763 
1764     switch (env_id) {
1765       default:
1766         break;
1767       case MAGNET_ENV_URI_PATH_RAW:
1768       {
1769         /* modify uri-path of r->target; preserve query-part, if present */
1770         /* XXX: should we require that resulting path begin with '/' or %2F ? */
1771         const uint32_t len = buffer_clen(&r->target);
1772         const char * const qmark = memchr(r->target.ptr, '?', len);
1773         if (NULL != qmark)
1774             buffer_copy_string_len(r->tmp_buf, qmark,
1775                                    len - (uint32_t)(qmark - r->target.ptr));
1776         buffer_copy_string_len(&r->target, val.ptr, val.len);
1777         if (NULL != qmark)
1778             buffer_append_string_buffer(&r->target, r->tmp_buf);
1779         return 0;
1780       }
1781       case MAGNET_ENV_REQUEST_REMOTE_ADDR:
1782        #ifdef HAVE_SYS_UN_H
1783         if (val.len && *val.ptr == '/'
1784             && 0 == sock_addr_assign(&r->con->dst_addr, AF_UNIX, 0, val.ptr)) {
1785         }
1786         else
1787        #endif
1788         {
1789             sock_addr saddr;
1790             saddr.plain.sa_family = AF_UNSPEC;
1791             if (1 == sock_addr_from_str_numeric(&saddr, val.ptr, r->conf.errh)
1792                 && saddr.plain.sa_family != AF_UNSPEC) {
1793                 sock_addr_set_port(&saddr, 0);
1794                 memcpy(&r->con->dst_addr, &saddr, sizeof(sock_addr));
1795             }
1796             else {
1797                 return luaL_error(L, "lighty.r.req_attr['%s'] invalid addr: %s",
1798                                   key, val.ptr);
1799             }
1800         }
1801         buffer_copy_string_len(&r->con->dst_addr_buf, val.ptr, val.len);
1802         config_cond_cache_reset_item(r, COMP_HTTP_REMOTE_IP);
1803         return 0;
1804       case MAGNET_ENV_REQUEST_REMOTE_PORT:
1805         sock_addr_set_port(&r->con->dst_addr, (unsigned short)atoi(val.ptr));
1806         return 0;
1807       case MAGNET_ENV_RESPONSE_HTTP_STATUS:
1808       case MAGNET_ENV_RESPONSE_BODY_LENGTH:
1809       case MAGNET_ENV_RESPONSE_BODY:
1810         return luaL_error(L, "lighty.r.req_attr['%s'] is read-only", key);
1811     }
1812 
1813     buffer * const dest = magnet_env_get_buffer_by_id(r, env_id);
1814     if (NULL == dest)
1815         return luaL_error(L, "couldn't store '%s' in lighty.r.req_attr[]", key);
1816 
1817     if (lua_isnoneornil(L, 3)) {
1818         if (env_id==MAGNET_ENV_URI_QUERY || env_id==MAGNET_ENV_PHYSICAL_PATH)
1819             buffer_clear(dest);
1820         else
1821             buffer_blank(dest);
1822     }
1823     else {
1824         buffer_copy_string_len(dest, val.ptr, val.len);
1825         /* NB: setting r->uri.query does not modify query-part in r->target */
1826     }
1827 
1828     switch (env_id) {
1829       case MAGNET_ENV_URI_SCHEME:
1830         buffer_to_lower(dest);
1831         config_cond_cache_reset_item(r, COMP_HTTP_SCHEME);
1832         break;
1833       case MAGNET_ENV_URI_AUTHORITY:
1834         r->server_name = dest;
1835         buffer_to_lower(dest);
1836         config_cond_cache_reset_item(r, COMP_HTTP_HOST);
1837         break;
1838       case MAGNET_ENV_URI_PATH:
1839         config_cond_cache_reset_item(r, COMP_HTTP_URL);
1840         break;
1841       case MAGNET_ENV_URI_QUERY:
1842         config_cond_cache_reset_item(r, COMP_HTTP_QUERY_STRING);
1843         break;
1844       default:
1845         break;
1846     }
1847 
1848     return 0;
1849 }
1850 
1851 static int magnet_env_next(lua_State *L) {
1852 	const int pos = lua_tointeger(L, lua_upvalueindex(1));
1853 
1854 	/* ignore previous key: use upvalue for current pos */
1855 	lua_settop(L, 0);
1856 
1857 	if (NULL == magnet_env[pos].name) return 0; /* end of list */
1858 	/* Update our positional upval to reflect our new current position */
1859 	lua_pushinteger(L, pos + 1);
1860 	lua_replace(L, lua_upvalueindex(1));
1861 
1862 	/* key to return */
1863 	lua_pushlstring(L, magnet_env[pos].name, magnet_env[pos].nlen);
1864 
1865 	/* get value */
1866 	request_st * const r = magnet_get_request(L);
1867 	magnet_push_buffer(L, magnet_env_get_buffer_by_id(r, magnet_env[pos].type));
1868 
1869 	/* return 2 items on the stack (key, value) */
1870 	return 2;
1871 }
1872 
1873 static int magnet_env_pairs(lua_State *L) {
1874 	lua_pushinteger(L, 0); /* Push our current pos (the start) into upval 1 */
1875 	lua_pushcclosure(L, magnet_env_next, 1); /* Push our new closure with 1 upvals */
1876 	return 1;
1877 }
1878 
1879 static int magnet_envvar_get(lua_State *L) {
1880     /* __index: param 1 is the (empty) table the value was not found in */
1881     size_t klen;
1882     const char * const k = luaL_checklstring(L, 2, &klen);
1883     request_st * const r = magnet_get_request(L);
1884     magnet_push_buffer(L, http_header_env_get(r, k, klen));
1885     return 1;
1886 }
1887 
1888 static int magnet_envvar_set(lua_State *L) {
1889     /* __newindex: param 1 is the (empty) table the value is supposed to be set in */
1890     const_buffer key = magnet_checkconstbuffer(L, 2);
1891     if (__builtin_expect( (lua_isnil(L, 3)), 0)) {
1892         request_st * const r = magnet_get_request(L);
1893         buffer * const v = http_header_env_get(r, key.ptr, key.len);
1894         if (v) buffer_clear(v); /*(unset)*/
1895         return 0;
1896     }
1897     const_buffer val = magnet_checkconstbuffer(L, 3);
1898     request_st * const r = magnet_get_request(L);
1899     http_header_env_set(r, key.ptr, key.len, val.ptr, val.len);
1900     return 0;
1901 }
1902 
1903 static int magnet_envvar_pairs(lua_State *L) {
1904 	request_st * const r = magnet_get_request(L);
1905 	return magnet_array_pairs(L, &r->env);
1906 }
1907 
1908 
1909 static int magnet_respbody_add(lua_State *L) {
1910     request_st * const r = magnet_get_request(L);
1911     if (lua_isstring(L, -1)) {
1912         const_buffer data = magnet_checkconstbuffer(L, -1);
1913         http_chunk_append_mem(r, data.ptr, data.len);
1914         return 1; /* boolean true */
1915     }
1916     else if (!lua_istable(L, -1))
1917         return 0; /* boolean false */
1918 
1919     /* note: differs from magnet_attach_content();
1920      * magnet_attach_content() has misnamed 'length' param which
1921      * is treated as 0-offset pos one after end of range to send.
1922      * Here, 'length' means 'length', as one would expect */
1923     for (int i=1, end=0, n=(int)lua_rawlen(L,-1); !end && i <= n; ++i) {
1924         lua_rawgeti(L, -1, i);
1925 
1926         if (lua_isstring(L, -1)) {
1927             const_buffer data = magnet_checkconstbuffer(L, -1);
1928             http_chunk_append_mem(r, data.ptr, data.len);
1929         }
1930         else if (lua_istable(L, -1)) {
1931             lua_getfield(L, -1, "filename");
1932             lua_getfield(L, -2, "length");
1933             lua_getfield(L, -3, "offset");
1934 
1935             if (lua_isstring(L, -3)) { /* filename has to be a string */
1936                 off_t off = (off_t) luaL_optinteger(L, -1, 0);
1937                 off_t len = (off_t) luaL_optinteger(L, -2, -1);
1938                 /*(-1 len as flag to use file size minus offset (below))*/
1939                 buffer stor; /*(note: do not free magnet_checkbuffer() result)*/
1940                 const buffer * const fn = magnet_checkbuffer(L, -3, &stor);
1941                 stat_cache_entry * const sce = (!buffer_is_blank(fn))
1942                   ? stat_cache_get_entry_open(fn, r->conf.follow_symlink)
1943                   : NULL;
1944                 if (sce && (sce->fd >= 0 || sce->st.st_size == 0)) {
1945                     /* treat negative offset as bytes from end of file */
1946                     /* treat negative len as bytes from offset to end of file */
1947                     if (off > sce->st.st_size)
1948                         off = sce->st.st_size;
1949                     else if (off < 0) {
1950                         off = sce->st.st_size - off;
1951                         if (off < 0) off = 0;
1952                     }
1953                     if (len < 0 || sce->st.st_size - off < len)
1954                         len = sce->st.st_size - off;
1955                     if (len)
1956                         http_chunk_append_file_ref_range(r, sce, off, len);
1957                 }
1958                 else {
1959                     log_error(r->conf.errh, __FILE__, __LINE__,
1960                       "error opening file '%s'", fn->ptr);
1961                     end = 1;
1962                 }
1963             }
1964             else {
1965                 log_error(r->conf.errh, __FILE__, __LINE__,
1966                   "body[%d] table field \"filename\" must be a string", i);
1967                 end = 1;
1968             }
1969 
1970             lua_pop(L, 3);
1971         }
1972         else if (lua_isnil(L, -1)) { /* end of list */
1973             end = 1;
1974         }
1975         else {
1976             log_error(r->conf.errh, __FILE__, __LINE__,
1977               "body[%d] is neither a string nor a table", i);
1978             end = 1;
1979         }
1980 
1981         lua_pop(L, 1); /* pop the content[...] entry value */
1982     }
1983 
1984     return 1; /* boolean true */
1985 }
1986 
1987 
1988 static int magnet_respbody(lua_State *L) {
1989     /* __index: param 1 is the (empty) table the value was not found in */
1990     size_t klen;
1991     const char * const k = luaL_checklstring(L, 2, &klen);
1992     switch (k[0]) {
1993       case 'a': /* add; lighty.r.resp_body.add */
1994         if (k[1] == 'd' && k[2] == 'd' && k[3] == '\0') {
1995             lua_pushcclosure(L, magnet_respbody_add, 0);
1996             return 1;
1997         }
1998         break;
1999      #if 0 /*(future: provide pairs() interface to iterate over chunkqueue)*/
2000            /*(might convert chunks into table of strings, {filename="..."})*/
2001            /*(what about c->offset into chunk?)*/
2002       case 'g': /* get; lighty.r.resp_body.get */
2003         if (k[1] == 'e' && k[2] == 't' && k[3] == '\0') {
2004             /* equivalent to lighty.r.attr["response.body"] */
2005             /* equivalent to lighty.env["response.body"] */
2006             if (r->resp_body_finished) {
2007                 chunkqueue * const cq = &r->write_queue;
2008                 chunkqueue_length(cq)
2009                   ? magnet_push_buffer(L,
2010                                        chunkqueue_read_squash(cq,r->conf.errh))
2011                   : lua_pushlstring(L, "", 0);
2012             }
2013             else
2014                 lua_pushnil(L); /*(?maybe return -1 instead if len unknown?)*/
2015             return 1;
2016         }
2017         break;
2018      #endif
2019       case 'l': /* len; lighty.r.resp_body.len */
2020         if (k[1] == 'e' && k[2] == 'n' && k[3] == '\0') {
2021             /* equivalent to lighty.r.req_attr["response.body-length"] */
2022             /* equivalent to lighty.env["response.body-length"] */
2023             request_st * const r = magnet_get_request(L);
2024             if (r->resp_body_finished)
2025                 lua_pushinteger(L, chunkqueue_length(&r->write_queue));
2026             else
2027                 lua_pushnil(L); /*(?maybe return -1 instead if len unknown?)*/
2028             return 1;
2029         }
2030         break;
2031       case 's': /* set; lighty.r.resp_body.set */
2032         if (k[1] == 'e' && k[2] == 't' && k[3] == '\0') {
2033             request_st * const r = magnet_get_request(L);
2034             http_response_body_clear(r, 0); /* clear respbody, then add */
2035             lua_pushcclosure(L, magnet_respbody_add, 0);
2036             return 1;
2037         }
2038         break;
2039       default:
2040         break;
2041     }
2042     lua_pushliteral(L, "lighty.r.resp_body invalid method or param");
2043     lua_error(L);
2044     return 0;
2045 }
2046 
2047 
2048 static int magnet_lighty_result_get(lua_State *L) {
2049     /* __index: param 1 is the lighty table the value was not found in */
2050     lua_getfield(L, 1, "result"); /* lighty.result */
2051     lua_pushvalue(L, 2);
2052     lua_rawget(L, -2);
2053     if (lua_isnil(L, -1)) {
2054         const_buffer k = magnet_checkconstbuffer(L, 2);
2055         if (k.len == 7 && 0 == memcmp(k.ptr, "content", 7)) {
2056             lua_createtable(L, 0, 0); /* create "content" table on demand */
2057             lua_pushvalue(L, 2);      /* k: "content" */
2058             lua_pushvalue(L, -2);     /* v: table */
2059             lua_rawset(L, 3); /* set in "lighty.result" */
2060         }
2061     }
2062     lua_replace(L, 3);
2063     return 1;
2064 }
2065 
2066 static int magnet_lighty_result_set(lua_State *L) {
2067     /* __newindex: param 1 is lighty table the value is supposed to be set in */
2068     /* assign value to table, replacing existing value, if any
2069      * (expecting "content" here, but compatible with prior misuse potential)
2070      * (special-case "header" back into lighty.header) */
2071     const_buffer k = magnet_checkconstbuffer(L, 2);
2072     if (k.len != 6 || 0 != memcmp(k.ptr, "header", 6)) {
2073         lua_getfield(L, 1, "result"); /* lighty.result */
2074         lua_replace(L, 1); /* replace param 1, original target table */
2075     }
2076     lua_rawset(L, -3);
2077     return 0;
2078 }
2079 
2080 
2081 static void magnet_copy_response_header(lua_State * const L, request_st * const r, const int lighty_table_ndx) {
2082     if (lua_getfield_and_type(L, lighty_table_ndx, "header") == LUA_TTABLE) {
2083         /* walk lighty.header */
2084         for (lua_pushnil(L); lua_next(L, -2); ) {
2085             if (lua_isstring(L, -1) && lua_isstring(L, -2))
2086                 magnet_resphdr_set_kv(L, r);
2087             /* unset value after use while walking table entries */
2088             lua_pop(L, 1);
2089             lua_pushvalue(L, -1);
2090             lua_pushnil(L);
2091             lua_rawset(L, -4);
2092         }
2093     }
2094     /*lua_pop(L, 1);*/ /* pop lighty.header */ /* defer to later */
2095 }
2096 
2097 /**
2098  * walk through the content array set by lua script, e.g.
2099  *   lighy.header["Content-Type"] = "text/html"
2100  *   lighty.content =
2101  *     { "<html><body><pre>", { file = "/content" } , "</pre></body></html>" }
2102  *   return 200
2103  */
2104 __attribute_noinline__
2105 static void magnet_attach_content_table(lua_State * const L, request_st * const r) {
2106 		for (int i=1, end=0, n=(int)lua_rawlen(L,-1); !end && i <= n; ++i) {
2107 			lua_rawgeti(L, -1, i);
2108 
2109 			/* -1 is the value and should be the value ... aka a table */
2110 			if (lua_isstring(L, -1)) {
2111 				const_buffer data = magnet_checkconstbuffer(L, -1);
2112 				http_chunk_append_mem(r, data.ptr, data.len);
2113 			} else if (lua_istable(L, -1)) {
2114 				lua_getfield(L, -1, "filename");
2115 				lua_getfield(L, -2, "length"); /* (0-based) end of range (not actually "length") */
2116 				lua_getfield(L, -3, "offset"); /* (0-based) start of range */
2117 
2118 				if (lua_isstring(L, -3)) { /* filename has to be a string */
2119 					/*(luaL_optinteger might raise error, which we want to avoid)*/
2120 					/*off_t off = (off_t) luaL_optinteger(L, -1, 0);*/
2121 					/*off_t len = (off_t) luaL_optinteger(L, -2, -1);*/ /*(-1 as flag to use file size minus offset (below))*/
2122 					int isnum = 1;
2123 					off_t off = lua_isnil(L, -1) ? 0 : (off_t) lua_tointegerx(L, -1, &isnum);
2124 					if (!isnum) {
2125 						off = 0;
2126 						log_error(r->conf.errh, __FILE__, __LINE__,
2127 						  "content[%d] is a table and field \"offset\" must be an integer", i);
2128 					}
2129 					isnum = 1;
2130 					off_t len = lua_isnil(L, -2) ? -1 : (off_t) lua_tointegerx(L, -2, &isnum);
2131 					/*(-1 len as flag to use file size minus offset (below))*/
2132 					if (!isnum) {
2133 						len = -1;
2134 						log_error(r->conf.errh, __FILE__, __LINE__,
2135 						  "content[%d] is a table and field \"length\" must be an integer", i);
2136 					}
2137 					if (off < 0) {
2138 						log_error(r->conf.errh, __FILE__, __LINE__,
2139 						  "offset for '%s' is negative", lua_tostring(L, -3));
2140 						end = 1;
2141 					} else if (len >= off) {
2142 						len -= off;
2143 					} else if (-1 != len) {
2144 						log_error(r->conf.errh, __FILE__, __LINE__,
2145 						  "offset > length for '%s'", lua_tostring(L, -3));
2146 						end = 1;
2147 					}
2148 
2149 					if (!end && 0 != len) {
2150 						buffer stor; /*(note: do not free magnet_checkbuffer() result)*/
2151 						const buffer * const fn = magnet_checkbuffer(L, -3, &stor);
2152 						stat_cache_entry * const sce = (!buffer_is_blank(fn))
2153 						  ? stat_cache_get_entry_open(fn, r->conf.follow_symlink)
2154 						  : NULL;
2155 						if (sce && (sce->fd >= 0 || sce->st.st_size == 0)) {
2156 							if (len == -1 || sce->st.st_size - off < len)
2157 								len = sce->st.st_size - off;
2158 							if (len > 0)
2159 								http_chunk_append_file_ref_range(r, sce, off, len);
2160 						} else {
2161 							log_error(r->conf.errh, __FILE__, __LINE__,
2162 							  "error opening file content '%s' at offset %lld",
2163 							          lua_tostring(L, -3), (long long)off);
2164 							end = 1;
2165 						}
2166 					}
2167 				} else {
2168 					log_error(r->conf.errh, __FILE__, __LINE__,
2169 					  "content[%d] is a table and field \"filename\" must be a string", i);
2170 					end = 1;
2171 				}
2172 
2173 				lua_pop(L, 3);
2174 			} else if (lua_isnil(L, -1)) {
2175 				/* end of list */
2176 				end = 1;
2177 			} else {
2178 				log_error(r->conf.errh, __FILE__, __LINE__,
2179 				  "content[%d] is neither a string nor a table", i);
2180 				end = 1;
2181 			}
2182 
2183 			lua_pop(L, 1); /* pop the content[...] entry value */
2184 		}
2185 }
2186 
2187 static void magnet_attach_content(lua_State * const L, request_st * const r, const int lighty_table_ndx) {
2188     if (lua_getfield_and_type(L, lighty_table_ndx, "result") != LUA_TTABLE) {
2189         /*lua_pop(L, 1);*/ /* pop nil lighty.result */ /* defer to later */
2190         return;
2191     }
2192     switch (lua_getfield_and_type(L, -1, "content")) {/*lighty.result.content*/
2193       case LUA_TNIL:
2194         break;
2195       case LUA_TTABLE:
2196         /* content found and is a table */
2197         http_response_body_clear(r, 0);
2198         magnet_attach_content_table(L, r);
2199         /* assign nil to content to remove from "result" table */
2200         lua_pushnil(L);
2201         lua_setfield(L, -3, "content");
2202         break;
2203       default:
2204         log_error(r->conf.errh, __FILE__, __LINE__,
2205           "lighty.content has to be a table");
2206         break;
2207     }
2208     /* defer lua_pop() to later */
2209     /*lua_pop(L, 2);*/ /* pop lighty.result.content and lighty.result */
2210 }
2211 
2212 static void magnet_mainenv_metatable(lua_State * const L) {
2213     if (luaL_newmetatable(L, "lighty.mainenv")) {             /* (sp += 1) */
2214         lua_pushglobaltable(L);                               /* (sp += 1) */
2215         lua_setfield(L, -2, "__index"); /* { __index = _G }      (sp -= 1) */
2216         lua_pushboolean(L, 0);                                /* (sp += 1) */
2217         lua_setfield(L, -2, "__metatable"); /* protect metatable (sp -= 1) */
2218     }
2219 }
2220 
2221 __attribute_cold__
2222 __attribute_noinline__
2223 static void magnet_init_lighty_table(lua_State * const L) {
2224     /* init lighty table and other initial setup for global lua_State */
2225 
2226     lua_atpanic(L, magnet_atpanic);
2227 
2228     lua_pushglobaltable(L);                                   /* (sp += 1) */
2229 
2230     /* we have to overwrite the print function */
2231     lua_pushcfunction(L, magnet_print);                       /* (sp += 1) */
2232     lua_setfield(L, -2, "print"); /* -1 is the env we want to set(sp -= 1) */
2233 
2234   #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
2235     /* override the default pairs() function to our __pairs capable version;
2236      * not needed for lua 5.2+
2237      */
2238     lua_getglobal(L, "pairs"); /* push original pairs()          (sp += 1) */
2239     lua_pushcclosure(L, magnet_pairs, 1);
2240     lua_setfield(L, -2, "pairs");                             /* (sp -= 1) */
2241   #endif
2242 
2243     lua_pop(L, 1); /* pop global table */                     /* (sp -= 1) */
2244 
2245     magnet_mainenv_metatable(L); /* init table for mem locality  (sp += 1) */
2246     magnet_stat_metatable(L);    /* init table for mem locality  (sp += 1) */
2247     magnet_readdir_metatable(L); /* init table for mem locality  (sp += 1) */
2248     lua_pop(L, 3);               /* pop init'd metatables        (sp -= 3) */
2249 
2250     /* lighty table
2251      *
2252      * lighty.r.req_header[]     HTTP request headers
2253      * lighty.r.req_attr[]       HTTP request attributes / components
2254      * lighty.r.req_env[]        HTTP request environment variables
2255      * lighty.r.resp_header[]    HTTP response headers
2256      * lighty.r.resp_body.*      HTTP response body accessors
2257      * lighty.r.resp_body.len    HTTP response body length
2258      * lighty.r.resp_body.add()  HTTP response body add (string or table)
2259      * lighty.r.resp_body.set()  HTTP response body set (string or table)
2260      *
2261      * (older interface)
2262      *
2263      * lighty.request[]      HTTP request headers
2264      * lighty.req_env[]      environment variables
2265      * lighty.env[]          lighttpd request metadata,
2266      *                       various url components,
2267      *                       physical file paths;
2268      *                       might contain nil values
2269      *
2270      * lighty.header[]       (script) HTTP response headers
2271      * lighty.content[]      (script) HTTP response body (table of string/file)
2272      *
2273      * lighty.status[]       lighttpd status counters
2274      */
2275 
2276     /*(adjust the preallocation if more entries are added)*/
2277     lua_createtable(L, 0, 9); /* lighty.* (returned on stack)    (sp += 1) */
2278 
2279     lua_createtable(L, 0, 5); /* lighty.r                        (sp += 1) */
2280 
2281     lua_createtable(L, 0, 0); /* {}                              (sp += 1) */
2282     lua_createtable(L, 0, 4); /* metatable for req_header table  (sp += 1) */
2283     lua_pushcfunction(L, magnet_reqhdr_get);                  /* (sp += 1) */
2284     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2285     lua_pushcfunction(L, magnet_reqhdr_set);                  /* (sp += 1) */
2286     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2287     lua_pushcfunction(L, magnet_reqhdr_pairs);                /* (sp += 1) */
2288     lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
2289     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2290     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2291     lua_setmetatable(L, -2); /* tie the metatable to req_header  (sp -= 1) */
2292     lua_setfield(L, -2, "req_header"); /* req_header = {}        (sp -= 1) */
2293 
2294     lua_createtable(L, 0, 0); /* {}                              (sp += 1) */
2295     lua_createtable(L, 0, 4); /* metatable for req_attr table    (sp += 1) */
2296     lua_pushcfunction(L, magnet_env_get);                     /* (sp += 1) */
2297     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2298     lua_pushcfunction(L, magnet_env_set);                     /* (sp += 1) */
2299     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2300     lua_pushcfunction(L, magnet_env_pairs);                   /* (sp += 1) */
2301     lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
2302     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2303     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2304     lua_setmetatable(L, -2); /* tie the metatable to req_attr    (sp -= 1) */
2305     lua_setfield(L, -2, "req_attr"); /* req_attr = {}            (sp -= 1) */
2306 
2307     lua_createtable(L, 0, 0); /* {}                              (sp += 1) */
2308     lua_createtable(L, 0, 4); /* metatable for req_env table     (sp += 1) */
2309     lua_pushcfunction(L, magnet_envvar_get);                  /* (sp += 1) */
2310     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2311     lua_pushcfunction(L, magnet_envvar_set);                  /* (sp += 1) */
2312     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2313     lua_pushcfunction(L, magnet_envvar_pairs);                /* (sp += 1) */
2314     lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
2315     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2316     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2317     lua_setmetatable(L, -2); /* tie the metatable to req_env     (sp -= 1) */
2318     lua_setfield(L, -2, "req_env"); /* req_env = {}              (sp -= 1) */
2319 
2320     lua_createtable(L, 0, 0); /* {}                              (sp += 1) */
2321     lua_createtable(L, 0, 4); /* metatable for resp_header table (sp += 1) */
2322     lua_pushcfunction(L, magnet_resphdr_get);                 /* (sp += 1) */
2323     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2324     lua_pushcfunction(L, magnet_resphdr_set);                 /* (sp += 1) */
2325     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2326     lua_pushcfunction(L, magnet_resphdr_pairs);               /* (sp += 1) */
2327     lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
2328     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2329     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2330     lua_setmetatable(L, -2); /* tie the metatable to resp_header (sp -= 1) */
2331     lua_setfield(L, -2, "resp_header"); /* resp_header = {}      (sp -= 1) */
2332 
2333     lua_createtable(L, 0, 0); /* {}                              (sp += 1) */
2334     lua_createtable(L, 0, 3); /* metatable for resp_body table   (sp += 1) */
2335     lua_pushcfunction(L, magnet_respbody);                    /* (sp += 1) */
2336     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2337     lua_pushcfunction(L, magnet_newindex_readonly);           /* (sp += 1) */
2338     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2339     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2340     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2341     lua_setmetatable(L, -2); /* tie the metatable to resp_body   (sp -= 1) */
2342     lua_setfield(L, -2, "resp_body"); /* resp_body = {}          (sp -= 1) */
2343 
2344     lua_createtable(L, 0, 2); /* metatable for r table           (sp += 1) */
2345     lua_pushcfunction(L, magnet_newindex_readonly);           /* (sp += 1) */
2346     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2347     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2348     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2349     lua_setmetatable(L, -2); /* tie the metatable to r           (sp -= 1) */
2350     lua_setfield(L, -2, "r"); /* lighty.r = {}                   (sp -= 1) */
2351 
2352     /* compatibility with previous mod_magnet interfaces in top of lighty.* */
2353     lua_getfield(L, -1, "r");                                 /* (sp += 1) */
2354     /* alias lighty.request -> lighty.r.req_header */
2355     lua_getfield(L, -1, "req_header");                        /* (sp += 1) */
2356     lua_setfield(L, -3, "request"); /* request = {}              (sp -= 1) */
2357     /* alias lighty.env     -> lighty.r.req_attr */
2358     lua_getfield(L, -1, "req_attr");                          /* (sp += 1) */
2359     lua_setfield(L, -3, "env"); /* env = {}                      (sp -= 1) */
2360     /* alias lighty.req_env -> lighty.r.req_env */
2361     lua_getfield(L, -1, "req_env");                           /* (sp += 1) */
2362     lua_setfield(L, -3, "req_env"); /* req_env = {}              (sp -= 1) */
2363     lua_pop(L, 1);                                            /* (sp -= 1) */
2364 
2365     lua_createtable(L, 0, 0); /* {}                              (sp += 1) */
2366     lua_createtable(L, 0, 4); /* metatable for status table      (sp += 1) */
2367     lua_pushcfunction(L, magnet_status_get);                  /* (sp += 1) */
2368     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2369     lua_pushcfunction(L, magnet_status_set);                  /* (sp += 1) */
2370     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2371     lua_pushcfunction(L, magnet_status_pairs);                /* (sp += 1) */
2372     lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
2373     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2374     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2375     lua_setmetatable(L, -2); /* tie the metatable to status      (sp -= 1) */
2376     lua_setfield(L, -2, "status"); /* status = {}                (sp -= 1) */
2377 
2378     lua_pushinteger(L, MAGNET_RESTART_REQUEST);
2379     lua_setfield(L, -2, "RESTART_REQUEST");
2380 
2381     lua_pushcfunction(L, magnet_stat);                        /* (sp += 1) */
2382     lua_setfield(L, -2, "stat"); /* -1 is the env we want to set (sp -= 1) */
2383 
2384     /* add empty 'header' and 'result' tables; ('content' is under 'result') */
2385     /* (prefer newer lighty.r.resp_header(), lighty.r.resp_body() interfaces) */
2386     lua_createtable(L, 0, 8); /* {}                              (sp += 1) */
2387     lua_setfield(L, -2, "header"); /* header = {}                (sp -= 1) */
2388 
2389     lua_createtable(L, 0, 1); /* {}                              (sp += 1) */
2390     lua_setfield(L, -2, "result"); /* result = {}                (sp -= 1) */
2391 
2392     static const luaL_Reg cmethods[] = {
2393       { "stat",             magnet_stat }
2394      ,{ "time",             magnet_time }
2395      ,{ "rand",             magnet_rand }
2396      ,{ "md",               magnet_md_once   } /* message digest */
2397      ,{ "hmac",             magnet_hmac_once } /* HMAC */
2398      ,{ "digest_eq",        magnet_digest_eq } /* timing-safe eq fixed len */
2399      ,{ "secret_eq",        magnet_secret_eq } /* timing-safe eq variable len */
2400      ,{ "b64urldec",        magnet_b64urldec } /* validate and decode base64url */
2401      ,{ "b64urlenc",        magnet_b64urlenc } /* base64url encode, no padding */
2402      ,{ "b64dec",           magnet_b64stddec } /* validate and decode base64 */
2403      ,{ "b64enc",           magnet_b64stdenc } /* base64 encode, no padding */
2404      ,{ "hexdec",           magnet_hexdec } /* validate and decode hex str */
2405      ,{ "hexenc",           magnet_hexenc } /* uc; lc w/ lua s = s:lower() */
2406      ,{ "xmlenc",           magnet_xmlenc } /* xml-encode/html-encode: <>&'\" */
2407      ,{ "urldec",           magnet_urldec } /* url-decode (path) */
2408      ,{ "urlenc",           magnet_urlenc } /* url-encode (path) */
2409      ,{ "urldec_query",     magnet_urldec_query } /* url-decode query-string */
2410      ,{ "urlenc_query",     magnet_urlenc_query } /* url-encode query-string */
2411      ,{ "urlenc_normalize", magnet_urlenc_normalize }/* url-enc normalization */
2412      ,{ "fspath_simplify",  magnet_fspath_simplify } /* simplify fspath */
2413      ,{ "cookie_tokens",    magnet_cookie_tokens } /* parse cookie tokens */
2414      ,{ "header_tokens",    magnet_header_tokens } /* parse header tokens seq */
2415      ,{ "readdir",          magnet_readdir } /* dir walk */
2416      ,{ "quoteddec",        magnet_quoteddec } /* quoted-string decode */
2417      ,{ "quotedenc",        magnet_quotedenc } /* quoted-string encode */
2418      ,{ NULL, NULL }
2419     };
2420 
2421     lua_createtable(L, 0, sizeof(cmethods)/sizeof(luaL_Reg)-1);/*(sp += 1) */
2422     luaL_setfuncs(L, cmethods, 0);
2423     lua_createtable(L, 0, 2); /* metatable for c table           (sp += 1) */
2424     lua_pushcfunction(L, magnet_newindex_readonly);           /* (sp += 1) */
2425     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2426     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2427     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2428     lua_setmetatable(L, -2); /* tie the metatable to c           (sp -= 1) */
2429     lua_setfield(L, -2, "c"); /* c = {}                          (sp -= 1) */
2430 
2431     lua_createtable(L, 0, 3); /* metatable for lighty table      (sp += 1) */
2432     lua_pushcfunction(L, magnet_lighty_result_get);           /* (sp += 1) */
2433     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2434     lua_pushcfunction(L, magnet_lighty_result_set);           /* (sp += 1) */
2435     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2436     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2437     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2438     lua_setmetatable(L, -2); /* tie the metatable to lighty      (sp -= 1) */
2439 
2440     /* lighty table (returned on stack) */
2441 }
2442 
2443 static void magnet_clear_table(lua_State * const L, int ndx) {
2444     /*(avoid lua_absindex() func call since expecting empty tables for legacy
2445      * interfaces to lighty.header and lighty.content, though not script-env)*/
2446     /*ndx = lua_absindex(ndx);*//*(lua 5.2+)*/
2447     if (ndx < 0) --ndx;
2448     for (lua_pushnil(L); lua_next(L, ndx); ) {
2449         lua_pop(L, 1);
2450         lua_pushvalue(L, -1);
2451         lua_pushnil(L);
2452         lua_rawset(L, ndx < 0 ? ndx - 2 : ndx);
2453     }
2454 }
2455 
2456 static void magnet_reset_lighty_table(lua_State * const L, const int lighty_table_ndx) {
2457     /* clear response tables (release mem if reusing lighty table) */
2458     if (lua_getfield_and_type(L, lighty_table_ndx, "result") == LUA_TTABLE)
2459         magnet_clear_table(L, -1); /*lighty.result*/
2460     else {
2461         lua_createtable(L, 0, 1);
2462         lua_setfield(L, lighty_table_ndx, "result");
2463     }
2464 
2465     if (lua_getfield_and_type(L, lighty_table_ndx, "header") == LUA_TTABLE)
2466         magnet_clear_table(L, -1); /*lighty.header*/
2467     else {
2468         lua_createtable(L, 0, 0);
2469         lua_setfield(L, lighty_table_ndx, "header");
2470     }
2471 
2472     /* pop two tables (or nils) added by two lua_getfield_and_type() calls */
2473     /*lua_pop(L, 2);*/ /* defer to later */
2474 }
2475 
2476 static int magnet_traceback(lua_State *L) {
2477 	if (!lua_isstring(L, 1))  /* 'message' not a string? */
2478 		return 1;  /* keep it intact */
2479 	if (lua_getglobal_and_type(L, "debug") != LUA_TTABLE) {
2480 		lua_pop(L, 1);
2481 		return 1;
2482 	}
2483 	if (lua_getfield_and_type(L, -1, "traceback") != LUA_TFUNCTION) {
2484 		lua_pop(L, 2);
2485 		return 1;
2486 	}
2487 	lua_pushvalue(L, 1);  /* pass error message */
2488 	lua_pushinteger(L, 2);  /* skip this function and traceback */
2489 	lua_call(L, 2, 1);  /* call debug.traceback */
2490 	return 1;
2491 }
2492 
2493 static handler_t magnet_attract(request_st * const r, plugin_data * const p, script * const sc) {
2494 	lua_State * const L = sc->L;
2495 	const int func_ndx = 1;
2496 	const int errfunc_ndx = 2;
2497 	const int env_ndx = 3;
2498 	const int lighty_table_ndx = 4;
2499 
2500 	if (__builtin_expect( (lua_gettop(L) == 4), 1)) {
2501 		/*force_assert(lua_istable(L, -1));*//* lighty.* table */
2502 	}
2503 	else if (lua_isfunction(L, func_ndx)) {
2504 	        /*force_assert(lua_gettop(L) == 1);*/
2505 		lua_pushcfunction(L, magnet_traceback);/*errfunc*//* (sp += 1) */
2506 		/* create empty table for script environment (reused)
2507 		 *   setmetatable({}, {__index = _G})
2508 		 *     if a function symbol is not defined in our env,
2509 		 *     __index will lookup in the global env. */
2510 		lua_createtable(L, 0, 1);                         /* (sp += 1) */
2511 		magnet_mainenv_metatable(L);                      /* (sp += 1) */
2512 		lua_setmetatable(L, -2);                          /* (sp -= 1) */
2513 		/* insert lighty table at index 4 (lighty_table_ndx = 4) */
2514 		magnet_init_lighty_table(L); /* lighty.*             (sp += 1) */
2515 	}
2516 	else {
2517 		if (lua_isstring(L, func_ndx))
2518 			log_error(r->conf.errh, __FILE__, __LINE__,
2519 			  "loading script %s failed: %s", sc->name.ptr,
2520 			  lua_tostring(L, func_ndx));
2521 		else /*(lua_gettop(L) == 0)*/
2522 			log_perror(r->conf.errh, __FILE__, __LINE__,
2523 			  "loading script %s failed", sc->name.ptr);
2524 		lua_settop(L, 0);
2525 
2526 		if (p->conf.stage != -1) { /* skip for response-start */
2527 			r->http_status = 500;
2528 			r->handler_module = NULL;
2529 		}
2530 
2531 		return HANDLER_FINISHED;
2532 	}
2533 
2534      #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503
2535 	lua_pushlightuserdata(L, r);
2536 	lua_setfield(L, LUA_REGISTRYINDEX, LUA_RIDX_LIGHTTPD_REQUEST);
2537      #else
2538 	*(request_st **)lua_getextraspace(L) = r;
2539      #endif
2540 
2541 	/**
2542 	 * all variables created in the script-env will be thrown
2543 	 * away at the end of the script run.
2544 	 */
2545 	lua_pushvalue(L, lighty_table_ndx);                       /* (sp += 1) */
2546 	lua_setfield(L, env_ndx, "lighty"); /* lighty.*              (sp -= 1) */
2547 	lua_pushvalue(L, env_ndx);                                /* (sp += 1) */
2548 	magnet_setfenv_mainfn(L, 1);                              /* (sp -= 1) */
2549 
2550 	/* pcall will destroy the func value, duplicate it */
2551 	lua_pushvalue(L, func_ndx);                               /* (sp += 1) */
2552 	int ret = lua_pcall(L, 0, 1, errfunc_ndx);       /* (sp -= 1; sp += 1) */
2553 
2554 		/* reset environment */
2555 		lua_pushglobaltable(L);                               /* (sp += 1) */
2556 		magnet_setfenv_mainfn(L, 1);                          /* (sp -= 1) */
2557 
2558 	handler_t result = HANDLER_GO_ON;
2559 	if (0 != ret) {
2560 			log_error(r->conf.errh, __FILE__, __LINE__,
2561 			  "lua_pcall(): %s", lua_tostring(L, -1));
2562 			/*lua_pop(L, 1);*/ /* pop error msg */ /* defer to later */
2563 			/* only func, errfunc, env, lighty table should remain on stack */
2564 			/*force_assert(lua_gettop(L) == 4);*//* lua_pop() deferred, so +1 */
2565 
2566 			if (p->conf.stage != -1) { /* skip for response-start */
2567 				r->http_status = 500;
2568 				r->handler_module = NULL;
2569 			}
2570 
2571 			result = HANDLER_FINISHED;
2572 	}
2573 	else {
2574 		/* should have func, errfunc, env, lighty table, return value on stack*/
2575 		/*force_assert(lua_gettop(L) == 5);*/
2576 
2577 		/*(luaL_optinteger might raise error, which we want to avoid)*/
2578 		/*lua_return_value = (int) luaL_optinteger(L, -1, -1);*/
2579 		int isnum = 1;
2580 		int lua_return_value = lua_isnil(L, -1)
2581 		  ? 0
2582 		  : (int) lua_tointegerx(L, -1, &isnum);
2583 		if (!isnum) {
2584 			lua_return_value = -1;
2585 			log_error(r->conf.errh, __FILE__, __LINE__,
2586 			  "lua_pcall(): unexpected non-integer return type: %s",
2587 			  luaL_typename(L, -1));
2588 		}
2589 		/*lua_pop(L, 1);*/ /* pop return value */ /* defer to later */
2590 		/*force_assert(lua_istable(sc->L, -1));*/
2591 
2592 		magnet_copy_response_header(L, r, lighty_table_ndx);
2593 
2594 		if (lua_return_value >= 200) {
2595 			r->http_status = lua_return_value;
2596 			r->resp_body_finished = 1;
2597 			/*(note: body may already have been set via lighty.r.resp_body.*)*/
2598 			magnet_attach_content(L, r, lighty_table_ndx);
2599 			if (!chunkqueue_is_empty(&r->write_queue)) {
2600 				r->handler_module = p->self;
2601 			}
2602 			result = HANDLER_FINISHED;
2603 		} else if (lua_return_value >= 100 && p->conf.stage != -1) {
2604 			/*(skip for response-start; send response as-is w/ added headers)*/
2605 			/*(custom lua code should not return 101 Switching Protocols)*/
2606 			r->http_status = lua_return_value;
2607 			result = http_response_send_1xx(r)
2608 			  ? HANDLER_GO_ON
2609 			  : HANDLER_ERROR;
2610 		} else if (MAGNET_RESTART_REQUEST == lua_return_value) {
2611 			/*(could detect restart loops in same way as is done in mod_rewrite,
2612 			 * but using r->env means that we do not need to reset plugin state
2613 			 * at end of every request, as is done in mod_rewrite.  mod_rewrite
2614 			 * always restarts the request processing (if request is rewritten),
2615 			 * whereas mod_magnet can be used in many other ways)*/
2616 			buffer *vb =
2617 			  http_header_env_get(r, CONST_STR_LEN("_L_MAGNET_RESTART"));
2618 			if (NULL == vb) {
2619 				vb =
2620 				  http_header_env_set_ptr(r,CONST_STR_LEN("_L_MAGNET_RESTART"));
2621 				buffer_append_string_len(vb, "0", 1);
2622 			}
2623 			result = HANDLER_COMEBACK;
2624 			if (++*vb->ptr-'0' >= 10) {
2625 				log_error(r->conf.errh, __FILE__, __LINE__,
2626 				  "too many request restarts (infinite loop?) for %s",
2627 				  sc->name.ptr);
2628 				result = HANDLER_ERROR;
2629 			}
2630 		}
2631 
2632 	}
2633 	magnet_clear_table(L, env_ndx);
2634 	magnet_reset_lighty_table(L, lighty_table_ndx);
2635 	/* reset stack to have only func, errfunc, env, lighty table */
2636 	lua_settop(L, lighty_table_ndx); /*(handle deferred lua_pop()s)*/
2637 	return result;
2638 }
2639 
2640 static handler_t magnet_attract_array(request_st * const r, plugin_data * const p, int stage) {
2641 	mod_magnet_patch_config(r, p);
2642 	p->conf.stage = stage;
2643 
2644 	script * const *scripts;
2645 	switch (stage) {
2646 	  case  1: scripts = p->conf.url_raw; break;
2647 	  case  0: scripts = p->conf.physical_path; break;
2648 	  case -1: scripts = p->conf.response_start; break;
2649 	  default: scripts = NULL; break;
2650 	}
2651 	if (NULL == scripts) return HANDLER_GO_ON; /* no scripts set */
2652 
2653 	/*(always check at least mtime and size to trigger script reload)*/
2654 	const int etag_flags = r->conf.etag_flags | ETAG_USE_MTIME | ETAG_USE_SIZE;
2655 	int req_env_inited = 0;
2656 
2657 	/* execute scripts sequentially while HANDLER_GO_ON */
2658 	handler_t rc = HANDLER_GO_ON;
2659 	do {
2660 		script_cache_check_script(*scripts, etag_flags);
2661 		if ((*scripts)->req_env_init && !req_env_inited) {
2662 			/*(request env init is deferred until needed)*/
2663 			req_env_inited = 1;
2664 			r->con->srv->request_env(r);
2665 		}
2666 		rc = magnet_attract(r, p, *scripts);
2667 	} while (rc == HANDLER_GO_ON && *++scripts);
2668 
2669 	if (r->error_handler_saved_status) {
2670 		/* retrieve (possibly modified) REDIRECT_STATUS and store as number */
2671 		int x;
2672 		const buffer * const vb = http_header_env_get(r, CONST_STR_LEN("REDIRECT_STATUS"));
2673 		if (vb && (x = http_header_str_to_code(vb->ptr)) != -1)
2674 			r->error_handler_saved_status =
2675 			  r->error_handler_saved_status > 0 ? (int)x : -(int)x;
2676 	}
2677 
2678 	return rc;
2679 }
2680 
2681 URIHANDLER_FUNC(mod_magnet_uri_handler) {
2682 	return magnet_attract_array(r, p_d, 1);
2683 }
2684 
2685 URIHANDLER_FUNC(mod_magnet_physical) {
2686 	return magnet_attract_array(r, p_d, 0);
2687 }
2688 
2689 URIHANDLER_FUNC(mod_magnet_response_start) {
2690 	return magnet_attract_array(r, p_d, -1);
2691 }
2692 
2693 
2694 int mod_magnet_plugin_init(plugin *p);
2695 int mod_magnet_plugin_init(plugin *p) {
2696 	p->version     = LIGHTTPD_VERSION_ID;
2697 	p->name        = "magnet";
2698 
2699 	p->init        = mod_magnet_init;
2700 	p->handle_uri_clean  = mod_magnet_uri_handler;
2701 	p->handle_physical   = mod_magnet_physical;
2702 	p->handle_response_start = mod_magnet_response_start;
2703 	p->set_defaults  = mod_magnet_set_defaults;
2704 	p->cleanup     = mod_magnet_free;
2705 
2706 	return 0;
2707 }
2708