xref: /lighttpd1.4/src/mod_magnet.c (revision c44adc55)
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 "fdevent.h"
13 #include "h2.h"
14 #include "http_chunk.h"
15 #include "http_etag.h"
16 #include "http_header.h"
17 #include "rand.h"
18 #include "response.h"   /* http_response_send_1xx() */
19 
20 #include "plugin.h"
21 
22 #include "mod_magnet_cache.h"
23 #include "sock_addr.h"
24 #include "stat_cache.h"
25 
26 #include <dirent.h>
27 #include <stdlib.h>
28 #include <string.h>
29 /*#include <setjmp.h>*//*(not currently used)*/
30 
31 #include <lua.h>
32 #include <lauxlib.h>
33 
34 #define MAGNET_RESTART_REQUEST      99
35 
36 /* plugin config for all request/connections */
37 
38 /*static jmp_buf exceptionjmp;*//*(not currently used)*/
39 
40 typedef struct {
41     script * const *url_raw;
42     script * const *physical_path;
43     script * const *response_start;
44     int stage;
45 } plugin_config;
46 
47 typedef struct {
48     PLUGIN_DATA;
49     plugin_config defaults;
50     plugin_config conf;
51 
52     script_cache cache;
53 } plugin_data;
54 
55 static plugin_data *plugin_data_singleton;
56 
57 INIT_FUNC(mod_magnet_init) {
58     plugin_data_singleton = (plugin_data *)calloc(1, sizeof(plugin_data));
59     return plugin_data_singleton;
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 < 504
187 #define lua_newuserdata0(L, sz) lua_newuserdata((L),(sz))
188 #else
189 #define lua_newuserdata0(L, sz) lua_newuserdatauv((L),(sz),0)
190 #endif
191 
192 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503
193 #define lua_getfield_and_type(L,idx,k) \
194        (lua_getfield((L),(idx),(k)), lua_type((L),-1))
195 #define lua_getglobal_and_type(L,name) \
196        (lua_getglobal((L),(name)), lua_type((L),-1))
197 #else
198 #define lua_getfield_and_type(L,idx,k) \
199         lua_getfield((L),(idx),(k))
200 #define lua_getglobal_and_type(L,name) \
201         lua_getglobal((L),(name))
202 #endif
203 
204 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
205 static lua_Integer
206 lua_tointegerx (lua_State * const L, int idx, int *isnum)
207 {
208     /*(caller should check for LUA_TNIL if using a default value is desired)*/
209     /*(note: return 0 for floating point not convertible to integer)*/
210     *isnum = lua_isnumber(L, idx);
211     return *isnum ? lua_tointeger(L, idx) : 0;
212 }
213 #endif
214 
215 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
216 /* lua5.1 backward compat definition */
217 static void lua_pushglobaltable(lua_State *L) { /* (-0, +1, -) */
218 	lua_pushvalue(L, LUA_GLOBALSINDEX);
219 }
220 #endif
221 
222 static void magnet_setfenv_mainfn(lua_State *L, int funcIndex) { /* (-1, 0, -) */
223 #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 502
224 	/* set "_ENV" upvalue, which should be the first upvalue of a "main" lua
225 	 * function if it uses any global names
226 	 */
227 
228 	const char* first_upvalue_name = lua_getupvalue(L, funcIndex, 1);
229 	if (NULL == first_upvalue_name) return; /* doesn't have any upvalues */
230 	lua_pop(L, 1); /* only need the name of the upvalue, not the value */
231 
232 	if (0 != strcmp(first_upvalue_name, "_ENV")) return;
233 
234 	if (NULL == lua_setupvalue(L, funcIndex, 1)) {
235 		/* pop value if lua_setupvalue didn't set the (not existing) upvalue */
236 		lua_pop(L, 1);
237 	}
238 #else
239 	lua_setfenv(L, funcIndex);
240 #endif
241 }
242 
243 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
244 /* lua 5.1 deprecated luaL_getn() for lua_objlen() */
245 /* lua 5.2 renamed lua_objlen() to lua_rawlen() */
246 #define lua_rawlen lua_objlen
247 /* lua 5.2 deprecated luaL_register() for luaL_setfuncs()
248  * (this define is valid only when 0 == nup) */
249 #define luaL_setfuncs(L, l, nup) luaL_register((L), NULL, (l))
250 #endif
251 
252 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
253 /* lua 5.2 already supports __pairs */
254 
255 /* See http://lua-users.org/wiki/GeneralizedPairsAndIpairs for implementation details.
256  * Override the default pairs() function to allow us to use a __pairs metakey
257  */
258 static int magnet_pairs(lua_State *L) {
259 	luaL_checkany(L, 1); /* "self" */
260 
261 	if (luaL_getmetafield(L, 1, "__pairs")) {
262 		/* call __pairs(self) */
263 		lua_pushvalue(L, 1);
264 		lua_call(L, 1, 3);
265 	} else {
266 		/* call <original-pairs-method>(self) */
267 		lua_pushvalue(L, lua_upvalueindex(1));
268 		lua_pushvalue(L, 1);
269 		lua_call(L, 1, 3);
270 	}
271 	return 3;
272 }
273 #endif
274 
275 
276 /* XXX: mystery why dir walk (readdir) is not already part of lua io liolib.c */
277 
278 #ifndef _D_EXACT_NAMLEN
279 #ifdef _DIRENT_HAVE_D_NAMLEN
280 #define _D_EXACT_NAMLEN(d) ((d)->d_namlen)
281 #else
282 #define _D_EXACT_NAMLEN(d) (strlen ((d)->d_name))
283 #endif
284 #endif
285 
286 static int magnet_readdir_iter(lua_State *L) {
287     DIR ** const d = (DIR **)lua_touserdata(L, lua_upvalueindex(1));
288     if (NULL == *d) return 0;
289 
290     /* readdir() and skip over "." and ".." */
291     struct dirent *de;
292     const char *n;
293     do {
294         de = readdir(*d);
295     } while (de && (n = de->d_name)[0] == '.'
296              && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0')));
297 
298     if (de) {
299         lua_pushlstring(L, de->d_name, _D_EXACT_NAMLEN(de));
300         return 1;
301     }
302     else { /* EOF */
303         closedir(*d);
304         *d = NULL;
305         return 0;
306     }
307 }
308 
309 static int magnet_readdir_gc(lua_State *L) {
310     /*DIR ** const d = ((DIR **)luaL_checkudata(L, 1, "li.DIR"));*/
311     DIR ** const d = lua_touserdata(L, 1);
312     if (*d) closedir(*d);
313     return 0;
314 }
315 
316 static void magnet_readdir_metatable(lua_State * const L) {
317     if (luaL_newmetatable(L, "li.DIR")) {                     /* (sp += 1) */
318         lua_pushcclosure(L, magnet_readdir_gc, 0);            /* (sp += 1) */
319         lua_setfield(L, -2, "__gc");                          /* (sp -= 1) */
320         lua_pushboolean(L, 0);                                /* (sp += 1) */
321         lua_setfield(L, -2, "__metatable"); /* protect metatable (sp -= 1) */
322     }
323 }
324 
325 static int magnet_readdir(lua_State *L) {
326     const char * const s = luaL_checkstring(L, 1);
327     DIR * const d = opendir(s);
328     if (d) {
329         *(DIR **)lua_newuserdata0(L, sizeof(DIR *)) = d;
330         magnet_readdir_metatable(L);
331         lua_setmetatable(L, -2);
332         lua_pushcclosure(L, magnet_readdir_iter, 1);
333     }
334     else
335         lua_pushnil(L);
336     return 1;
337 }
338 
339 
340 __attribute_cold__
341 static int magnet_newindex_readonly(lua_State *L) {
342     lua_pushliteral(L, "lua table is read-only");
343     return lua_error(L);
344 }
345 
346 static void magnet_push_buffer(lua_State *L, const buffer *b) {
347     if (b && !buffer_is_unset(b))
348         lua_pushlstring(L, BUF_PTR_LEN(b));
349     else
350         lua_pushnil(L);
351 }
352 
353 #if 0
354 static int magnet_array_get_element(lua_State *L, const array *a) {
355     /* __index: param 1 is the (empty) table the value was not found in */
356     size_t klen;
357     const char * const k = luaL_checklstring(L, 2, &klen);
358     const data_string * const ds = (const data_string *)
359       array_get_element_klen(a, k, klen);
360     magnet_push_buffer(L, NULL != ds ? &ds->value : NULL);
361     return 1;
362 }
363 #endif
364 
365 /* Define a function that will iterate over an array* (in upval 2) using current position (upval 1) */
366 static int magnet_array_next(lua_State *L) {
367 	lua_settop(L, 0);
368 	const uint32_t pos = lua_tointeger(L, lua_upvalueindex(1));
369 	const array * const a = lua_touserdata(L, lua_upvalueindex(2));
370 	const data_unset * const du = pos < a->used ? a->data[pos] : NULL;
371 	if (NULL == du) return 0;
372 
373 		lua_pushlstring(L, BUF_PTR_LEN(&du->key));
374 		switch (du->type) {
375 			case TYPE_STRING:
376 				magnet_push_buffer(L, &((const data_string *)du)->value);
377 				break;
378 			case TYPE_INTEGER:
379 				lua_pushinteger(L, ((const data_integer *)du)->value);
380 				break;
381 			default:
382 				lua_pushnil(L);
383 				break;
384 		}
385 
386 		/* Update our positional upval to reflect our new current position */
387 		lua_pushinteger(L, pos+1);
388 		lua_replace(L, lua_upvalueindex(1));
389 
390 		/* Returning 2 items on the stack (key, value) */
391 		return 2;
392 }
393 
394 /* Create the closure necessary to iterate over the array *a with the above function */
395 __attribute_noinline__
396 static int magnet_array_pairs(lua_State *L, array *a) {
397 	lua_pushinteger(L, 0); /* Push our current pos (the start) into upval 1 */
398 	lua_pushlightuserdata(L, a); /* Push our array *a into upval 2 */
399 	lua_pushcclosure(L, magnet_array_next, 2); /* Push our new closure with 2 upvals */
400 	return 1;
401 }
402 
403 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503
404 #define LUA_RIDX_LIGHTTPD_REQUEST "li.request"
405 #endif
406 
407 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503
408 __attribute_noinline__
409 #endif
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 void magnet_set_request(lua_State *L, request_st * const r) {
422      #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503
423 	lua_pushlightuserdata(L, r);
424 	lua_setfield(L, LUA_REGISTRYINDEX, LUA_RIDX_LIGHTTPD_REQUEST);
425      #else
426 	*(request_st **)lua_getextraspace(L) = r;
427      #endif
428 }
429 
430 #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 504
431 __attribute_noinline__
432 #endif
433 static buffer * magnet_tmpbuf_acquire(lua_State *L)
434 {
435   #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503
436     UNUSED(L);
437     return chunk_buffer_acquire();
438   #else
439     request_st * const r = magnet_get_request(L);
440     buffer * const tb = r->tmp_buf;
441     buffer_clear(tb);
442     return tb;
443   #endif
444 }
445 
446 static void magnet_tmpbuf_release(buffer *b)
447 {
448   #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503
449     chunk_buffer_release(b);
450   #else
451     UNUSED(b);
452   #endif
453 }
454 
455 typedef struct {
456 	const char *ptr;
457 	size_t len;
458 } const_buffer;
459 
460 __attribute_noinline__
461 static const_buffer magnet_checkconstbuffer(lua_State *L, int idx) {
462 	const_buffer cb;
463 	if (!lua_isnoneornil(L, idx))
464 		cb.ptr = luaL_checklstring(L, idx, &cb.len);
465 	else {
466 		cb.ptr = NULL;
467 		cb.len = 0;
468 	}
469 	return cb;
470 }
471 
472 static const buffer* magnet_checkbuffer(lua_State *L, int idx, buffer *b) {
473 	const_buffer cb = magnet_checkconstbuffer(L, idx);
474 	/* assign result into (buffer *), and return (const buffer *)
475 	 * (note: caller must not free result) */
476 	*(const char **)&b->ptr = cb.ptr ? cb.ptr : "";
477 	b->used = cb.len+1;
478 	b->size = 0;
479 	return b;
480 }
481 
482 
483 static int magnet_return_upvalue2(lua_State *L) {
484     /*(XXX: is there a better way to do this?)*/
485     lua_pushvalue(L, lua_upvalueindex(1));
486     lua_pushvalue(L, lua_upvalueindex(2));
487     return 2;
488 }
489 
490 static int magnet_stat_field(lua_State *L) {
491     if (lua_gettop(L) != 2)
492         return 0; /*(should not happen; __index method in protected metatable)*/
493 
494     stat_cache_entry * const sce = *(stat_cache_entry **)lua_touserdata(L, -2);
495     const_buffer k = magnet_checkconstbuffer(L, -1);
496     switch (k.len ? k.ptr[0] : 0) {
497       case 'c': { /* content-type */
498         if (0 != strcmp(k.ptr, "content-type")) break;
499         request_st * const r = magnet_get_request(L);
500         const buffer *content_type = stat_cache_content_type_get(sce, r);
501         if (content_type && !buffer_is_blank(content_type))
502             lua_pushlstring(L, BUF_PTR_LEN(content_type));
503         else
504             lua_pushnil(L);
505         return 1;
506       }
507       case 'e': { /* etag */
508         if (0 != strcmp(k.ptr, "etag")) break;
509         request_st * const r = magnet_get_request(L);
510         const buffer *etag = stat_cache_etag_get(sce, r->conf.etag_flags);
511         if (etag && !buffer_is_blank(etag))
512             lua_pushlstring(L, BUF_PTR_LEN(etag));
513         else
514             lua_pushnil(L);
515         return 1;
516       }
517       case 'h': { /* http-response-send-file */
518         if (0 != strcmp(k.ptr, "http-response-send-file")) break;
519         request_st * const r = magnet_get_request(L);
520         r->http_status = 0;
521         http_response_body_clear(r, 0);
522         http_response_send_file(r, &sce->name, sce);
523         lua_pushinteger(L, r->http_status);
524         return 1;
525       }
526       case 'i': /* is_* */
527         if (k.len < 4) break;
528         switch (k.ptr[3]) {
529           case 'b': /* is_block */
530             if (0 == strcmp(k.ptr, "is_block")) {
531                 lua_pushboolean(L, S_ISBLK(sce->st.st_mode));
532                 return 1;
533             }
534             break;
535           case 'c': /* is_char */
536             if (0 == strcmp(k.ptr, "is_char")) {
537                 lua_pushboolean(L, S_ISCHR(sce->st.st_mode));
538                 return 1;
539             }
540             break;
541           case 'd': /* is_dir */
542             if (0 == strcmp(k.ptr, "is_dir")) {
543                 lua_pushboolean(L, S_ISDIR(sce->st.st_mode));
544                 return 1;
545             }
546             break;
547           case 'f': /* is_file is_fifo */
548             if (0 == strcmp(k.ptr, "is_file")) {
549                 lua_pushboolean(L, S_ISREG(sce->st.st_mode));
550                 return 1;
551             }
552             if (0 == strcmp(k.ptr, "is_fifo")) {
553                 lua_pushboolean(L, S_ISFIFO(sce->st.st_mode));
554                 return 1;
555             }
556             break;
557           case 'l': /* is_link */
558             if (0 == strcmp(k.ptr, "is_link")) {
559                 lua_pushboolean(L, S_ISLNK(sce->st.st_mode));
560                 return 1;
561             }
562             break;
563           case 's': /* is_socket */
564             if (0 == strcmp(k.ptr, "is_socket")) {
565                 lua_pushboolean(L, S_ISSOCK(sce->st.st_mode));
566                 return 1;
567             }
568             break;
569           default:
570             break;
571         }
572         break;
573       case 's': /* st_* */
574         if (k.len < 4) break;
575         switch (k.ptr[3]) {
576           case 'a': /* st_atime */
577             if (0 == strcmp(k.ptr, "st_atime")) {
578                 lua_pushinteger(L, TIME64_CAST(sce->st.st_atime));
579                 return 1;
580             }
581             if (0 == strcmp(k.ptr, "st_atim")) {
582                 lua_pushinteger(L, TIME64_CAST(sce->st.st_atime));
583               #ifdef st_atime /* high-precision timestamp if available */
584               #if defined(__APPLE__) && defined(__MACH__)
585                 lua_pushinteger(L, sce->st.st_atimespec.tv_nsec);
586               #else
587                 lua_pushinteger(L, sce->st.st_atim.tv_nsec);
588               #endif
589               #else
590                 lua_pushinteger(L, 0);
591               #endif
592                 lua_pushcclosure(L, magnet_return_upvalue2, 2);
593                 return 1;
594             }
595             break;
596           case 'c': /* st_ctime */
597             if (0 == strcmp(k.ptr, "st_ctime")) {
598                 lua_pushinteger(L, TIME64_CAST(sce->st.st_ctime));
599                 return 1;
600             }
601             if (0 == strcmp(k.ptr, "st_ctim")) {
602                 lua_pushinteger(L, TIME64_CAST(sce->st.st_ctime));
603               #ifdef st_ctime /* high-precision timestamp if available */
604               #if defined(__APPLE__) && defined(__MACH__)
605                 lua_pushinteger(L, sce->st.st_ctimespec.tv_nsec);
606               #else
607                 lua_pushinteger(L, sce->st.st_ctim.tv_nsec);
608               #endif
609               #else
610                 lua_pushinteger(L, 0);
611               #endif
612                 lua_pushcclosure(L, magnet_return_upvalue2, 2);
613                 return 1;
614             }
615             break;
616           case 'i': /* st_ino */
617             if (0 == strcmp(k.ptr, "st_ino")) {
618                 lua_pushinteger(L, sce->st.st_ino);
619                 return 1;
620             }
621             break;
622           case 'm': /* st_mtime st_mode */
623             if (0 == strcmp(k.ptr, "st_mtime")) {
624                 lua_pushinteger(L, TIME64_CAST(sce->st.st_mtime));
625                 return 1;
626             }
627             if (0 == strcmp(k.ptr, "st_mtim")) {
628                 lua_pushinteger(L, TIME64_CAST(sce->st.st_mtime));
629               #ifdef st_mtime /* high-precision timestamp if available */
630               #if defined(__APPLE__) && defined(__MACH__)
631                 lua_pushinteger(L, sce->st.st_mtimespec.tv_nsec);
632               #else
633                 lua_pushinteger(L, sce->st.st_mtim.tv_nsec);
634               #endif
635               #else
636                 lua_pushinteger(L, 0);
637               #endif
638                 lua_pushcclosure(L, magnet_return_upvalue2, 2);
639                 return 1;
640             }
641             if (0 == strcmp(k.ptr, "st_mode")) {
642                 lua_pushinteger(L, sce->st.st_mode);
643                 return 1;
644             }
645             break;
646           case 'g': /* st_gid */
647             if (0 == strcmp(k.ptr, "st_gid")) {
648                 lua_pushinteger(L, sce->st.st_gid);
649                 return 1;
650             }
651             break;
652           case 's': /* st_size */
653             if (0 == strcmp(k.ptr, "st_size")) {
654                 lua_pushinteger(L, sce->st.st_size);
655                 return 1;
656             }
657             break;
658           case 'u': /* st_uid */
659             if (0 == strcmp(k.ptr, "st_uid")) {
660                 lua_pushinteger(L, sce->st.st_uid);
661                 return 1;
662             }
663             break;
664           default:
665             break;
666         }
667         break;
668       default:
669         break;
670     }
671 
672     lua_pushliteral(L, "stat[\"field\"] invalid: ");
673     lua_pushvalue(L, -2); /* field */
674     lua_concat(L, 2);
675     lua_error(L);
676     return 0;
677 }
678 
679 
680 __attribute_cold__
681 static int magnet_stat_pairs_noimpl_iter(lua_State *L) {
682     request_st * const r = magnet_get_request(L);
683     log_error(r->conf.errh, __FILE__, __LINE__,
684       "(lua) pairs() not implemented on lighty.stat object; "
685       "returning empty iter");
686     return 0;
687 }
688 
689 
690 __attribute_cold__
691 static int magnet_stat_pairs_noimpl(lua_State *L) {
692     lua_pushcclosure(L, magnet_stat_pairs_noimpl_iter, 0);
693     return 1;
694 }
695 
696 
697 static void magnet_stat_metatable(lua_State *L) {
698     if (luaL_newmetatable(L, "li.stat")) {                      /* (sp += 1) */
699         lua_pushcfunction(L, magnet_stat_field);                /* (sp += 1) */
700         lua_setfield(L, -2, "__index");                         /* (sp -= 1) */
701         lua_pushcfunction(L, magnet_newindex_readonly);         /* (sp += 1) */
702         lua_setfield(L, -2, "__newindex");                      /* (sp -= 1) */
703         lua_pushcfunction(L, magnet_stat_pairs_noimpl);         /* (sp += 1) */
704         lua_setfield(L, -2, "__pairs");                         /* (sp -= 1) */
705         lua_pushboolean(L, 0);                                  /* (sp += 1) */
706         lua_setfield(L, -2, "__metatable"); /* protect metatable   (sp -= 1) */
707     }
708 }
709 
710 
711 static int magnet_stat(lua_State *L) {
712     buffer stor; /*(note: do not free magnet_checkbuffer() result)*/
713     const buffer * const sb = magnet_checkbuffer(L, 1, &stor);
714     stat_cache_entry * const sce = (!buffer_is_blank(sb))
715       ? stat_cache_get_entry(sb)
716       : NULL;
717     if (NULL == sce) {
718         lua_pushnil(L);
719         return 1;
720     }
721 
722     /* note: caching sce valid only for procedural script which does not yield;
723      * (sce might not be valid if script yields and is later resumed)
724      * (script must not cache sce in persistent global state for later use)
725      * (If we did want sce to be persistent, then could increment sce refcnt,
726      *  and set up __gc metatable method to decrement sce refcnt) */
727     stat_cache_entry ** const udata =(struct stat_cache_entry**)/* (sp += 1) */
728       lua_newuserdata0(L, sizeof(stat_cache_entry *));
729     *udata = sce;
730 
731     magnet_stat_metatable(L);                                   /* (sp += 1) */
732     lua_setmetatable(L, -2);                                    /* (sp -= 1) */
733     return 1;
734 }
735 
736 
737 static int magnet_time(lua_State *L) {
738     lua_pushinteger(L, (lua_Integer)log_epoch_secs);
739     return 1;
740 }
741 
742 
743 static int magnet_hrtime(lua_State *L) {
744     unix_timespec64_t ts;
745     if (0 != log_clock_gettime_realtime(&ts))
746         return 0;
747     lua_pushinteger(L, (lua_Integer)ts.tv_sec);
748     lua_pushinteger(L, (lua_Integer)ts.tv_nsec);
749     return 2;
750 }
751 
752 
753 static int magnet_rand(lua_State *L) {
754     lua_pushinteger(L, (lua_Integer)li_rand_pseudo());
755     return 1;
756 }
757 
758 
759 static int magnet_md_once(lua_State *L) {
760     if (lua_gettop(L) != 2) {
761         lua_pushliteral(L,
762           "lighty.c.md(algo, data): incorrect number of arguments");
763         return lua_error(L);
764     }
765     const_buffer algo = magnet_checkconstbuffer(L, -2);
766     const_buffer msg  = magnet_checkconstbuffer(L, -1);
767     uint8_t digest[MD_DIGEST_LENGTH_MAX];
768     uint32_t dlen = 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             SHA512_once(digest, msg.ptr, 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             SHA256_once(digest, msg.ptr, 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             SHA1_once(digest, msg.ptr, 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             MD5_once(digest, msg.ptr, msg.len);
800             dlen = MD5_DIGEST_LENGTH;
801             break;
802         }
803         break;
804       default:
805         break;
806     }
807 
808     if (dlen) {
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_hmac_once(lua_State *L) {
820     if (lua_gettop(L) != 3) {
821         lua_pushliteral(L,
822           "lighty.c.hmac(algo, secret, data): incorrect number of arguments");
823         return lua_error(L);
824     }
825     const_buffer algo   = magnet_checkconstbuffer(L, -3);
826     const_buffer secret = magnet_checkconstbuffer(L, -2);
827     const_buffer msg    = magnet_checkconstbuffer(L, -1);
828     const uint8_t * const msgptr = (uint8_t *)msg.ptr;
829     uint8_t digest[MD_DIGEST_LENGTH_MAX];
830     uint32_t dlen = 0;
831     int rc = 0;
832     switch (algo.len) {
833      #ifdef USE_LIB_CRYPTO
834       case 6:
835        #ifdef USE_LIB_CRYPTO_SHA512
836         if (0 == memcmp(algo.ptr, "sha512", 6)) {
837             rc = li_hmac_sha512(digest,secret.ptr,secret.len,msgptr,msg.len);
838             dlen = SHA512_DIGEST_LENGTH;
839             break;
840         }
841        #endif
842        #ifdef USE_LIB_CRYPTO_SHA256
843         if (0 == memcmp(algo.ptr, "sha256", 6)) {
844             rc = li_hmac_sha256(digest,secret.ptr,secret.len,msgptr,msg.len);
845             dlen = SHA256_DIGEST_LENGTH;
846             break;
847         }
848        #endif
849         break;
850       case 4:
851        #ifdef USE_LIB_CRYPTO_SHA1
852         if (0 == memcmp(algo.ptr, "sha1", 4)) {
853             rc = li_hmac_sha1(digest,secret.ptr,secret.len,msgptr,msg.len);
854             dlen = SHA1_DIGEST_LENGTH;
855             break;
856         }
857        #endif
858         break;
859      #endif
860       case 3:
861         if (0 == memcmp(algo.ptr, "md5", 3)) {
862             rc = li_hmac_md5(digest,secret.ptr,secret.len,msgptr,msg.len);
863             dlen = MD5_DIGEST_LENGTH;
864             break;
865         }
866         break;
867       default:
868         break;
869     }
870 
871     if (rc) {
872         char dighex[MD_DIGEST_LENGTH_MAX*2+1];
873         li_tohex_uc(dighex, sizeof(dighex), (char *)digest, dlen);
874         lua_pushlstring(L, dighex, dlen*2);
875     }
876     else
877         lua_pushnil(L);
878 
879     return 1;
880 }
881 
882 static int magnet_digest_eq(lua_State *L) {
883     if (lua_gettop(L) != 2) {
884         lua_pushliteral(L,
885           "lighty.c.digest_eq(d1, d2): incorrect number of arguments");
886         return lua_error(L);
887     }
888     const_buffer d1 = magnet_checkconstbuffer(L, -2);
889     const_buffer d2 = magnet_checkconstbuffer(L, -1);
890     /* convert hex to binary: validate hex and eliminate hex case comparison */
891     uint8_t b1[MD_DIGEST_LENGTH_MAX];
892     uint8_t b2[MD_DIGEST_LENGTH_MAX];
893     int rc = (d1.len == d2.len)
894           && 0 == li_hex2bin(b1, sizeof(b1), d1.ptr, d1.len)
895           && 0 == li_hex2bin(b2, sizeof(b2), d2.ptr, d2.len)
896           && ck_memeq_const_time_fixed_len(b1, b2, d2.len >> 1);
897     lua_pushboolean(L, rc);
898     return 1;
899 }
900 
901 static int magnet_secret_eq(lua_State *L) {
902     if (lua_gettop(L) != 2) {
903         lua_pushliteral(L,
904           "lighty.c.secret_eq(d1, d2): incorrect number of arguments");
905         return lua_error(L);
906     }
907     const_buffer d1 = magnet_checkconstbuffer(L, -2);
908     const_buffer d2 = magnet_checkconstbuffer(L, -1);
909     lua_pushboolean(L, ck_memeq_const_time(d1.ptr, d1.len, d2.ptr, d2.len));
910     return 1;
911 }
912 
913 static int magnet_b64dec(lua_State *L, base64_charset dict) {
914     if (lua_isnoneornil(L, -1)) {
915         lua_pushlstring(L, "", 0);
916         return 1;
917     }
918     const_buffer s = magnet_checkconstbuffer(L, -1);
919     if (0 == s.len) {
920         lua_pushvalue(L, -1);
921         return 1;
922     }
923     buffer * const b = magnet_tmpbuf_acquire(L);
924     if (buffer_append_base64_decode(b, s.ptr, s.len, dict))
925         lua_pushlstring(L, BUF_PTR_LEN(b));
926     else
927         lua_pushnil(L);
928     magnet_tmpbuf_release(b);
929     return 1;
930 }
931 
932 static int magnet_b64enc(lua_State *L, base64_charset dict) {
933     if (lua_isnoneornil(L, -1)) {
934         lua_pushlstring(L, "", 0);
935         return 1;
936     }
937     const_buffer s = magnet_checkconstbuffer(L, -1);
938     if (0 == s.len) {
939         lua_pushvalue(L, -1);
940         return 1;
941     }
942     buffer * const b = magnet_tmpbuf_acquire(L);
943     buffer_append_base64_encode_no_padding(b, (uint8_t *)s.ptr, s.len, dict);
944     lua_pushlstring(L, BUF_PTR_LEN(b));
945     magnet_tmpbuf_release(b);
946     return 1;
947 }
948 
949 static int magnet_b64urldec(lua_State *L) {
950     return magnet_b64dec(L, BASE64_URL);
951 }
952 
953 static int magnet_b64urlenc(lua_State *L) {
954     return magnet_b64enc(L, BASE64_URL);
955 }
956 
957 static int magnet_b64stddec(lua_State *L) {
958     return magnet_b64dec(L, BASE64_STANDARD);
959 }
960 
961 static int magnet_b64stdenc(lua_State *L) {
962     return magnet_b64enc(L, BASE64_STANDARD);
963 }
964 
965 static int magnet_hexdec(lua_State *L) {
966     if (lua_isnoneornil(L, -1)) {
967         lua_pushlstring(L, "", 0);
968         return 1;
969     }
970     const_buffer s = magnet_checkconstbuffer(L, -1);
971     if (0 == s.len) {
972         lua_pushvalue(L, -1);
973         return 1;
974     }
975     buffer * const b = magnet_tmpbuf_acquire(L);
976     uint8_t * const p = (uint8_t *)buffer_extend(b, s.len >> 1);
977     int rc = li_hex2bin(p, s.len >> 1, s.ptr, s.len);
978     if (0 == rc)
979         lua_pushlstring(L, BUF_PTR_LEN(b));
980     magnet_tmpbuf_release(b);
981     return rc+1; /* 1 on success (pushed string); 0 on failure (no value) */
982 }
983 
984 static int magnet_hexenc(lua_State *L) {
985     if (lua_isnoneornil(L, -1)) {
986         lua_pushlstring(L, "", 0);
987         return 1;
988     }
989     const_buffer s = magnet_checkconstbuffer(L, -1);
990     if (0 == s.len) {
991         lua_pushvalue(L, -1);
992         return 1;
993     }
994     buffer * const b = magnet_tmpbuf_acquire(L);
995     buffer_append_string_encoded_hex_uc(b, s.ptr, s.len);
996     lua_pushlstring(L, BUF_PTR_LEN(b));
997     magnet_tmpbuf_release(b);
998     return 1; /* uppercase hex string; use lua s = s:lower() to lowercase */
999 }
1000 
1001 static int magnet_quoteddec(lua_State *L) {
1002     if (lua_isnoneornil(L, -1)) {
1003         lua_pushlstring(L, "", 0);
1004         return 1;
1005     }
1006     const_buffer s = magnet_checkconstbuffer(L, -1);
1007     if (0 == s.len || s.ptr[0] != '"') {
1008         lua_pushvalue(L, -1);
1009         return 1;
1010     }
1011     buffer * const b = magnet_tmpbuf_acquire(L);
1012     char *p = buffer_string_prepare_append(b, s.len);/*(s.len-1 is sufficient)*/
1013     size_t i = 1;
1014     for (; i < s.len && s.ptr[i] != '"'; ++i) {
1015         if (s.ptr[i] == '\\') {
1016             if (i+2 < s.len)
1017                 ++i;
1018             else
1019                 break;
1020         }
1021         *p++ = s.ptr[i];
1022     }
1023     int rc = (i == s.len-1 && s.ptr[i] == '"');
1024     if (rc)
1025         lua_pushlstring(L, b->ptr, (size_t)(p - b->ptr));
1026     magnet_tmpbuf_release(b);
1027     return rc; /* 1 on success (pushed string); 0 on failure (no value) */
1028 }
1029 
1030 static int magnet_quotedenc(lua_State *L) {
1031     if (lua_isnoneornil(L, -1)) {
1032         lua_pushlstring(L, "", 0);
1033         return 1;
1034     }
1035     const_buffer s = magnet_checkconstbuffer(L, -1);
1036     if (0 == s.len) {
1037         lua_pushvalue(L, -1);
1038         return 1;
1039     }
1040     buffer * const b = magnet_tmpbuf_acquire(L);
1041     char *p = buffer_string_prepare_append(b, 2+(s.len << 1));
1042     *p++ = '"';
1043     for (size_t i = 0; i < s.len; ++i) {
1044         /*(note: not strictly checking for TEXT)*/
1045         /*(TEXT: any OCTET except CTLs but including LWS)*/
1046         if (s.ptr[i] == '"' || s.ptr[i] == '\\')
1047             *p++ = '\\';
1048         *p++ = s.ptr[i];
1049     }
1050     *p++ = '"';
1051     lua_pushlstring(L, b->ptr, (size_t)(p - b->ptr));
1052     magnet_tmpbuf_release(b);
1053     return 1;
1054 }
1055 
1056 /*(future: might move to buffer.c:buffer_append_bs_unescaped())*/
1057 static void
1058 magnet_buffer_append_bsdec (buffer * const restrict b,
1059                             const char * restrict s, const size_t len)
1060 {
1061     /* decode backslash escapes */
1062     /*(caller must check result for decoded '\0', if necessary)*/
1063     char *d = buffer_string_prepare_append(b, len); /*(upper-bound len)*/
1064     for (const char * const end = s+len; s < end; ++s) {
1065         const char * const ptr = s;
1066         while (__builtin_expect( (*s != '\\'), 1) && ++s < end) ;
1067         if (s - ptr) {
1068             memcpy(d, ptr, (size_t)(s - ptr));
1069             d += (size_t)(s - ptr);
1070         }
1071 
1072         if (s == end)
1073             break;
1074 
1075         int c;
1076         switch ((c = ++s != end ? *s : '\\')) { /*(preserve stray '\\' at end)*/
1077           case '"': case '\\':
1078           default:
1079             break;
1080           case 'x':
1081             if (s+3 <= end) {
1082                 unsigned char hi = hex2int(((unsigned char *)s)[1]);
1083                 unsigned char lo = hex2int(((unsigned char *)s)[2]);
1084                 if (0xFF != hi && 0xFF != lo) {
1085                     c = (hi << 4) | lo;
1086                     s += 2;
1087                 }
1088             }
1089             break;
1090           case 'a':case 'b':case 't':case 'n':case 'v':case 'f':case 'r':
1091             c = "\a\bcde\fghijklm\nopq\rstu\vwxyz"[c-'a'];
1092             break;
1093           case 'u':
1094             if (s+5 <= end) {
1095                 unsigned char hi = hex2int(((unsigned char *)s)[3]);
1096                 unsigned char lo = hex2int(((unsigned char *)s)[4]);
1097                 if (0xFF == hi || 0xFF == lo)
1098                     break;
1099                 c = (hi << 4) | lo;
1100                 if (__builtin_expect( (s[1] != '0'), 0)
1101                     || __builtin_expect( (s[2] != '0'), 0)) {
1102                     unsigned char hhi = hex2int(((unsigned char *)s)[1]);
1103                     unsigned char hlo = hex2int(((unsigned char *)s)[2]);
1104                     if (0xFF == hhi || 0xFF == hlo)
1105                         break;
1106                     c |= (int)((hhi << 12) | (hlo << 8));
1107                     if ((unsigned int)c - 0xd800u < 0x800)
1108                         break; /* 0xD800 - 0xDFFF ill-formed UTF-8 */
1109                 }
1110                 /* adapted from
1111                  * https://stackoverflow.com/questions/4607413/is-there-a-c-library-to-convert-unicode-code-points-to-utf-8 */
1112                 if (__builtin_expect( (c > 0x7F), 0)) {
1113                     if (c < 0x800)
1114                         *d++ = 0xC0 | (c >> 6);
1115                     else {
1116                         *d++ = 0xE0 | (c >> 12);
1117                         *d++ = 0x80 | ((c >> 6) & 0x3F);
1118                     }
1119                     c = 0x80 | (c & 0x3F);
1120                 }
1121                 s += 4;
1122             }
1123             break;
1124           case '0': case '1': case '2': case '3':
1125             if (s+3 <= end
1126                 /*&& ((unsigned char *)s)[0] - '0' < 4*//* 0-3 */
1127                 && ((unsigned char *)s)[1] - '0' < 8    /* 0-7 */
1128                 && ((unsigned char *)s)[2] - '0' < 8) { /* 0-7 */
1129                 c = ((s[0]-'0') << 6) | ((s[1]-'0') << 3) | (s[2]-'0');
1130                 s += 2;
1131             }
1132             else if (*s == '0')
1133                 c = '\0'; /*(special-case "\\0" not part of octal "\\ooo")*/
1134             break;
1135         }
1136         *d++ = c;
1137     }
1138     buffer_truncate(b, d - b->ptr);
1139 }
1140 
1141 static int magnet_bsdec(lua_State *L) {
1142     if (lua_isnoneornil(L, -1)) {
1143         lua_pushlstring(L, "", 0);
1144         return 1;
1145     }
1146     const_buffer s = magnet_checkconstbuffer(L, -1);
1147     if (0 == s.len) {
1148         lua_pushvalue(L, -1);
1149         return 1;
1150     }
1151     const char *ptr = s.ptr;
1152     size_t len = s.len;
1153     if (ptr[0] == '"' && ptr[len-1] == '"') {
1154         /*(ignore double-quotes ('"') surrounding string for convenience)*/
1155         ++ptr;
1156         len -= 2;
1157     }
1158     buffer * const b = magnet_tmpbuf_acquire(L);
1159     magnet_buffer_append_bsdec(b, ptr, len);
1160     lua_pushlstring(L, BUF_PTR_LEN(b));
1161     magnet_tmpbuf_release(b);
1162     return 1;
1163 }
1164 
1165 static int magnet_bsenc(lua_State *L, const int esc_json) {
1166     if (lua_isnoneornil(L, -1)) {
1167         lua_pushlstring(L, "", 0);
1168         return 1;
1169     }
1170     const_buffer s = magnet_checkconstbuffer(L, -1);
1171     if (0 == s.len) {
1172         lua_pushvalue(L, -1);
1173         return 1;
1174     }
1175     buffer * const b = magnet_tmpbuf_acquire(L);
1176     if (esc_json)
1177         buffer_append_bs_escaped(b, s.ptr, s.len);
1178     else
1179         buffer_append_bs_escaped_json(b, s.ptr, s.len);
1180     lua_pushlstring(L, BUF_PTR_LEN(b));
1181     magnet_tmpbuf_release(b);
1182     return 1;
1183 }
1184 
1185 static int magnet_bsenc_default(lua_State *L) {
1186     return magnet_bsenc(L, 0);
1187 }
1188 
1189 static int magnet_bsenc_json(lua_State *L) {
1190     return magnet_bsenc(L, 1);
1191 }
1192 
1193 static int magnet_xmlenc(lua_State *L) {
1194     if (lua_isnoneornil(L, -1)) {
1195         lua_pushlstring(L, "", 0);
1196         return 1;
1197     }
1198     const_buffer s = magnet_checkconstbuffer(L, -1);
1199     if (0 == s.len) {
1200         lua_pushvalue(L, -1);
1201         return 1;
1202     }
1203     buffer * const b = magnet_tmpbuf_acquire(L);
1204   #if 1
1205     buffer_append_string_encoded(b, s.ptr, s.len, ENCODING_MINIMAL_XML);
1206   #else
1207     const char *e;
1208     size_t i, n, elen;
1209     for (i = 0, n = 0; i < s.len; ++i) {
1210         switch (s.ptr[i]) {
1211           default: continue;
1212           case '<':  e = "&lt;";   elen = sizeof("&lt;")-1;   break;
1213           case '>':  e = "&gt;";   elen = sizeof("&gt;")-1;   break;
1214           case '&':  e = "&amp;";  elen = sizeof("&amp;")-1;  break;
1215           case '\'': e = "&apos;"; elen = sizeof("&apos;")-1; break;
1216           case '"':  e = "&quot;"; elen = sizeof("&quot;")-1; break;
1217           /*(XXX: would be good idea to add CTRLs, DEL, '`' */
1218         }
1219         buffer_append_str2(b, s.ptr+n, i-n, e, elen);
1220         n = i+1;
1221     }
1222     if (i-n)
1223         buffer_append_string_len(b, s.ptr+n, i-n);
1224   #endif
1225     lua_pushlstring(L, BUF_PTR_LEN(b));
1226     magnet_tmpbuf_release(b);
1227     return 1;
1228 }
1229 
1230 static int magnet_urldec(lua_State *L) {
1231     /* url-decode and replace non-printable chars with '_'
1232      * This function should not be used on query-string unless it is used on
1233      * portions of query-string after splitting on '&', replacing '+' w/ ' ' */
1234     if (lua_isnoneornil(L, -1)) {
1235         lua_pushlstring(L, "", 0);
1236         return 1;
1237     }
1238     const_buffer s = magnet_checkconstbuffer(L, -1);
1239     if (0 == s.len) {
1240         lua_pushvalue(L, -1);
1241         return 1;
1242     }
1243     buffer * const b = magnet_tmpbuf_acquire(L);
1244     buffer_copy_string_len(b, s.ptr, s.len);
1245     buffer_urldecode_path(b);
1246     lua_pushlstring(L, BUF_PTR_LEN(b));
1247     magnet_tmpbuf_release(b);
1248     return 1;
1249 }
1250 
1251 static int magnet_urlenc(lua_State *L) {
1252     /* url-encode path
1253      * ('?' is encoded, if present)
1254      *  caller must split string if '?' is part of query-string)
1255      * ('/' is not encoded; caller must encode if not path separator) */
1256     if (lua_isnoneornil(L, -1)) {
1257         lua_pushlstring(L, "", 0);
1258         return 1;
1259     }
1260     const_buffer s = magnet_checkconstbuffer(L, -1);
1261     if (0 == s.len) {
1262         lua_pushvalue(L, -1);
1263         return 1;
1264     }
1265     buffer * const b = magnet_tmpbuf_acquire(L);
1266     buffer_append_string_encoded(b, s.ptr, s.len, ENCODING_REL_URI);
1267     lua_pushlstring(L, BUF_PTR_LEN(b));
1268     magnet_tmpbuf_release(b);
1269     return 1;
1270 }
1271 
1272 static void magnet_urldec_query_part(buffer * const b, const char *s, const size_t slen) {
1273     buffer_clear(b);
1274     char *p = buffer_extend(b, slen);
1275     for (size_t i = 0; i < slen; ++i)
1276         p[i] = (s[i] != '+') ? s[i] : ' ';
1277     buffer_urldecode_path(b);
1278 }
1279 
1280 static int magnet_urldec_query(lua_State *L) {
1281     /* split on '&' and '=', url-decode and replace non-printable chars w/ '_',
1282      * and store components in table
1283      * (string input should be query-string without leading '?')
1284      * (note: duplicated keys replace earlier values, but this interface returns
1285      *  a table useful for lookups, so this limitation is often acceptable) */
1286     lua_createtable(L, 0, 0);
1287     if (lua_isnoneornil(L, 1)) {
1288         return 1;
1289     }
1290     const_buffer s = magnet_checkconstbuffer(L, 1);
1291     if (0 == s.len) {
1292         return 1;
1293     }
1294     buffer * const b = magnet_tmpbuf_acquire(L);
1295     for (const char *qs = s.ptr, *eq, *amp; *qs; qs = amp+1) {
1296         for (amp = qs, eq = NULL; *amp && *amp != '&'; ++amp) {
1297             if (*amp == '=' && !eq) eq = amp;
1298         }
1299         if (amp != qs) {
1300             if (eq) {
1301                 magnet_urldec_query_part(b, qs, (size_t)(eq - qs));
1302                 lua_pushlstring(L, BUF_PTR_LEN(b));
1303                 magnet_urldec_query_part(b, eq+1, (size_t)(amp - (eq+1)));
1304                 lua_pushlstring(L, BUF_PTR_LEN(b));
1305             }
1306             else {
1307                 magnet_urldec_query_part(b, qs, (size_t)(amp - qs));
1308                 lua_pushlstring(L, BUF_PTR_LEN(b));
1309                 lua_pushlstring(L, "", 0); /*(lua_pushnil() would delete key)*/
1310             }
1311             lua_rawset(L, -3);
1312         }
1313         if (*amp == '\0') break;
1314     }
1315     magnet_tmpbuf_release(b);
1316     return 1;
1317 }
1318 
1319 static void magnet_urlenc_query_part(buffer * const b, const char * const s, const size_t slen, const int iskey) {
1320     /* encode query part (each part is typically much smaller than chunk buffer)
1321      * all required chars plus '&' ';' '+' '\'' (and encode '=' if part of key)
1322      * (burl_append(b,str,len,BURL_ENCODE_ALL) works, but over-encodes) */
1323   #if 0
1324     /* alternative: (over-encodes some, but less than burl_append()) */
1325     UNUSED(iskey);
1326     buffer_append_string_encoded(b, s, slen, ENCODING_REL_URI);
1327   #else
1328     static const char hex_chars_uc[] = "0123456789ABCDEF";
1329     char * const p = buffer_string_prepare_append(b, slen*3);
1330     int j = 0;
1331     for (size_t i = 0; i < slen; ++i, ++j) {
1332         int c = s[i];
1333         if (!light_isalnum(c)) switch (c) {
1334           case ' ':
1335             c = '+';
1336             break;
1337           /*case '\'':*//*(ok in url query-part, but might be misused in HTML)*/
1338           case '!': case '$': case '(': case ')': case '*': case ',': case '-':
1339           case '.': case '/': case ':': case '?': case '@': case '_': case '~':
1340             break;
1341           case '=':
1342             if (!iskey) break;
1343             __attribute_fallthrough__
1344           default:
1345             p[j]   = '%';
1346             p[++j] = hex_chars_uc[(s[i] >> 4) & 0xF];
1347             p[++j] = hex_chars_uc[s[i] & 0xF];
1348             continue;
1349         }
1350         p[j] = c;
1351     }
1352     buffer_commit(b, j);
1353   #endif
1354 }
1355 
1356 static int magnet_urlenc_query(lua_State *L) {
1357     /* encode pairs in lua table into query string
1358      * (caller should add leading '?' or '&' when appending to full URL)
1359      * (caller should skip empty table if appending to existing query-string) */
1360     if (!lua_istable(L, 1)) {
1361         lua_pushlstring(L, "", 0);
1362         return 1;
1363     }
1364     buffer * const b = magnet_tmpbuf_acquire(L);
1365     const_buffer s;
1366     for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
1367         if (lua_isstring(L, -2)) {
1368             if (!buffer_is_blank(b))
1369                 buffer_append_char(b, '&');
1370             s = magnet_checkconstbuffer(L, -2);
1371             magnet_urlenc_query_part(b, s.ptr, s.len, 1);
1372             if (!lua_isnil(L, -1)) {
1373                 s = magnet_checkconstbuffer(L, -1);
1374                 buffer_append_char(b, '=');
1375                 magnet_urlenc_query_part(b, s.ptr, s.len, 0);
1376             }
1377         }
1378     }
1379     lua_pushlstring(L, BUF_PTR_LEN(b));
1380     magnet_tmpbuf_release(b);
1381     return 1;
1382 }
1383 
1384 static int magnet_urlenc_normalize(lua_State *L) {
1385     /* normalize url-encoding
1386      * url-encode (and url-decode safe chars) to normalize url-path
1387      * ('?' is treated as start of query-string and is not encoded;
1388      *  caller must encode '?' if intended to be part of url-path)
1389      * ('/' is not encoded; caller must encode if not path separator)
1390      * (burl_append() is not exposed here; caller might want to build
1391      *  url with lighty.c.urlenc() and lighty.c.urlenc_query(),
1392      *  then call lighty.c.urlenc_normalize()) */
1393     if (lua_isnoneornil(L, -1)) {
1394         lua_pushlstring(L, "", 0);
1395         return 1;
1396     }
1397     const_buffer s = magnet_checkconstbuffer(L, -1);
1398     if (0 == s.len) {
1399         lua_pushvalue(L, -1);
1400         return 1;
1401     }
1402     buffer * const b = magnet_tmpbuf_acquire(L);
1403     buffer * const t = chunk_buffer_acquire();
1404   #if 0 /*(?maybe have different interface to use config policy?)*/
1405     request_st * const r = magnet_get_request(L);
1406     const int flags = r->conf.http_parseopts;
1407   #else
1408     const int flags = HTTP_PARSEOPT_URL_NORMALIZE
1409                     | HTTP_PARSEOPT_URL_NORMALIZE_UNRESERVED
1410                     | HTTP_PARSEOPT_URL_NORMALIZE_REQUIRED
1411                     | HTTP_PARSEOPT_URL_NORMALIZE_PATH_2F_DECODE
1412                     | HTTP_PARSEOPT_URL_NORMALIZE_PATH_DOTSEG_REMOVE
1413                     | HTTP_PARSEOPT_URL_NORMALIZE_QUERY_20_PLUS;
1414   #endif
1415     buffer_copy_string_len(b, s.ptr, s.len);
1416     burl_normalize(b, t, flags);
1417     lua_pushlstring(L, BUF_PTR_LEN(b));
1418     chunk_buffer_release(t);
1419     magnet_tmpbuf_release(b);
1420     return 1;
1421 }
1422 
1423 static int magnet_fspath_simplify(lua_State *L) {
1424     /* simplify filesystem path */
1425     if (lua_isnoneornil(L, -1)) {
1426         lua_pushlstring(L, "", 0);
1427         return 1;
1428     }
1429     const_buffer s = magnet_checkconstbuffer(L, -1);
1430     if (0 == s.len) {
1431         lua_pushvalue(L, -1);
1432         return 1;
1433     }
1434     buffer * const b = magnet_tmpbuf_acquire(L);
1435     buffer_copy_string_len(b, s.ptr, s.len);
1436     buffer_path_simplify(b);
1437     lua_pushlstring(L, BUF_PTR_LEN(b));
1438     magnet_tmpbuf_release(b);
1439     return 1;
1440 }
1441 
1442 __attribute_pure__
1443 static const char * magnet_scan_quoted_string (const char *s) {
1444     /* scan to end of of quoted-string (with s starting at '"')
1445      * loose parse; non-validating
1446      * - permit missing '"' at end of string (caller should check)
1447      * - stop at stray '\\' at end of string missing closing '"'
1448      * - not rejecting non-WS CTL chars
1449      */
1450     /*force_assert(*s == '"');*/
1451     do { ++s; } while (*s && *s != '"' && (*s != '\\' || (s[1] ? ++s : 0)));
1452     /*do { ++s; } while (*s && *s != '"' && (*s != '\\' || (*++s || (--s, 0))));*/
1453     return s;
1454 }
1455 
1456 static const char * magnet_push_quoted_string_range (lua_State *L, const char *b, const char *s) {
1457     /* quoted-string is unmodified (except for quoted-string end consistency)
1458      * including surrounding double-quotes and with quoted-pair unmodified */
1459     if (__builtin_expect( (*s == '"'), 1))
1460         lua_pushlstring(L, b, (size_t)(++s-b));
1461     else { /*(else invalid quoted-string, but handle anyway)*/
1462         /* append closing '"' for consistency */
1463         lua_pushlstring(L, b, (size_t)(s-b));
1464         if (*s != '\\')
1465             lua_pushlstring(L, "\"", 1);
1466         else { /* unescaped backslash at end of string; escape and close '"' */
1467             lua_pushlstring(L, "\\\\\"", 3);
1468             ++s; /*(now *s == '\0')*/
1469         }
1470         lua_concat(L, 2);
1471     }
1472     return s;
1473 }
1474 
1475 static const char * magnet_push_quoted_string(lua_State *L, const char *s) {
1476     return magnet_push_quoted_string_range(L, s, magnet_scan_quoted_string(s));
1477 }
1478 
1479 static const char * magnet_cookie_param_push_token(lua_State *L, const char *s) {
1480     const char *b = s;
1481     while (*s!='=' /*(note: not strictly rejecting all 'separators')*/
1482             && *s!=';' && *s!=' ' && *s!='\t' && *s!='\r' && *s!='\n' && *s)
1483         ++s;
1484     lua_pushlstring(L, b, (size_t)(s-b));
1485     return s;
1486 }
1487 
1488 static int magnet_cookie_tokens(lua_State *L) {
1489     /*(special-case cookies (';' separator); dup cookie-names overwritten)*/
1490     lua_createtable(L, 0, 0);
1491     if (lua_isnoneornil(L, 1))
1492         return 1;
1493     const char *s = luaL_checkstring(L, 1);
1494     do {
1495         while (*s==';' || *s==' ' || *s=='\t' || *s=='\r' || *s=='\n')
1496             ++s;
1497         if (*s == '\0') break;
1498         s = magnet_cookie_param_push_token(L, s);
1499         while (           *s==' ' || *s=='\t' || *s=='\r' || *s=='\n')
1500             ++s;
1501         if (*s == '=') {
1502             do {
1503                 ++s;
1504             } while (     *s==' ' || *s=='\t' || *s=='\r' || *s=='\n');
1505             if (*s==';' || *s=='\0')
1506                 lua_pushlstring(L, "", 0); /*(lua_pushnil() would delete key)*/
1507             else if (*s != '"')
1508                 s = magnet_cookie_param_push_token(L, s);
1509             else
1510                 s = magnet_push_quoted_string(L, s);
1511         }
1512         else {
1513             lua_pushlstring(L, "", 0); /*(lua_pushnil() would delete key)*/
1514         }
1515         lua_settable(L, -3);
1516         while (*s!=';' && *s!='\0') ++s; /* ignore/skip stray tokens */
1517     } while (*s++);
1518     return 1;
1519 }
1520 
1521 static const char * magnet_push_token(lua_State *L, const char *s) {
1522     const char *b = s;
1523     while (               *s!=' ' && *s!='\t' && *s!='\r' && *s!='\n'
1524            && *s!=',' && *s!=';' && *s!='=' && *s)
1525         ++s;
1526     lua_pushlstring(L, b, (size_t)(s-b));
1527     return s;
1528 }
1529 
1530 static int magnet_header_tokens(lua_State *L) {
1531     /* split into sequence of tokens/words
1532      *   Intermediate table is then more convenient to walk once quoted-string
1533      *   parsed into table entries since quoted-string may contain separators.
1534      *   Each token can be passed to lighty.c.quoteddec()
1535      *     (lighty.c.quoteddec() returns string as-is if not quoted-string)
1536      * (note: non-validating;
1537      *  e.g. existence of '=' token does not mean that next token is value,
1538      *       and '=' in value which is not part of quoted-string will be
1539      *       treated as separate token)
1540      * (note: case is preserved; non-quoted-string tokens are not lower-cased)
1541      * (words separated by whitespace are separate tokens unless quoted-string)
1542      *   (if that format not permitted in a specific header, caller must detect)
1543      * (optional whitespace (OWS) and bad whitespace (BWS) are removed)
1544      * (caller can create lookup table from sequence table, as appropriate) */
1545     lua_createtable(L, 0, 0);
1546     if (lua_isnoneornil(L, 1))
1547         return 1;
1548     const char *s = luaL_checkstring(L, 1);
1549     int i = 0;
1550     do {
1551         while (           *s==' ' || *s=='\t' || *s=='\r' || *s=='\n')
1552             ++s;
1553         if (*s=='\0') break;
1554         if (*s==',' || *s==';' || *s=='=')
1555             lua_pushlstring(L, s++, 1);
1556         else if (*s != '"')
1557             s = magnet_push_token(L, s);
1558         else
1559             s = magnet_push_quoted_string(L, s);
1560         lua_rawseti(L, -2, ++i);
1561     } while (*s);
1562     return 1;
1563 }
1564 
1565 static int magnet_reqhdr_get(lua_State *L) {
1566     size_t klen;
1567     const char * const k = luaL_checklstring(L, 2, &klen);
1568     const request_st * const r = **(request_st ***)lua_touserdata(L, 1);
1569     const int id = http_header_hkey_get(k, (uint32_t)klen);
1570     magnet_push_buffer(L, http_header_request_get(r, id, k, klen));
1571     return 1;
1572 }
1573 
1574 static int magnet_reqhdr_set(lua_State *L) {
1575     const_buffer k = magnet_checkconstbuffer(L, 2);
1576     const_buffer v = magnet_checkconstbuffer(L, 3);
1577 
1578     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
1579     enum http_header_e id = http_header_hkey_get(k.ptr, (uint32_t)k.len);
1580 
1581     switch (id) {
1582       /*case HTTP_HEADER_OTHER:*/
1583       default:
1584         break;
1585 
1586       case HTTP_HEADER_HOST:
1587         /* do not allow Host to be unset, even if HTTP/1.0
1588          * (change Host to something else, if you must */
1589         if (0 == v.len) return 0;
1590 
1591         /*(must set r->http_host if r->http_host was not previously set)*/
1592         /* copied from request.c:http_request_header_set_Host() */
1593         r->http_host = http_header_request_set_ptr(r, HTTP_HEADER_HOST,
1594                                                    CONST_STR_LEN("Host"));
1595         buffer_copy_string_len_lc(r->http_host, v.ptr, v.len);
1596         return 0;
1597 
1598       case HTTP_HEADER_CONTENT_LENGTH:
1599         /* not attempting to handle Content-Length modification; may revisit */
1600         /* future: might permit setting to 0 to force discard of request body
1601          * but would have to check if request body present, and if
1602          * Content-Length was set, or if Transfer-Encoding: chunked,
1603          * and handle resetting internal chunked encoding state,
1604          * as well as making sure that this is handled properly for HTTP/2 */
1605         return 0; /* silently ignore; do not allow modification */
1606 
1607       /* do not permit modification of hop-by-hop (connection) headers */
1608 
1609       case HTTP_HEADER_CONNECTION:
1610         /* do not permit modification of Connection, incl add/remove tokens */
1611         /* future: might provide a different interface to set r->keep_alive = 0,
1612          *         and also handle in context if HTTP/2 */
1613       case HTTP_HEADER_TRANSFER_ENCODING:
1614       case HTTP_HEADER_SET_COOKIE:/*(response hdr;avoid accidental reflection)*/
1615         return 0; /* silently ignore; do not allow modification */
1616      #if 0 /*(eh, if script sets Upgrade, script probably intends this action)*/
1617       case HTTP_HEADER_UPGRADE:
1618         /* note: modifications here do not modify Connection header
1619          *       to add or remove "upgrade" token */
1620         /* future: might allow removal of existing tokens, but not addition */
1621         if (0 != v.len) return 0; /* do not allow Upgrade other than to unset */
1622         break;
1623      #endif
1624      #if 0 /*(eh, if script sets TE, script probably intends this action)*/
1625       case HTTP_HEADER_TE:
1626         if (0 != v.len) return 0; /* do not allow TE other than to unset */
1627         break;
1628      #endif
1629     }
1630 
1631     v.len
1632       ? http_header_request_set(r, id, k.ptr, k.len, v.ptr, v.len)
1633       : http_header_request_unset(r, id, k.ptr, k.len);
1634     return 0;
1635 }
1636 
1637 static int magnet_reqhdr_pairs(lua_State *L) {
1638     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
1639     return magnet_array_pairs(L, &r->rqst_headers);
1640 }
1641 
1642 static int magnet_resphdr_get(lua_State *L) {
1643     /* Note: access to lighttpd r->resp_headers here is *independent* from
1644      * the (pending) changes in the (deprecated) lua lighty.header[] table */
1645     size_t klen;
1646     const char * const k = luaL_checklstring(L, 2, &klen);
1647     const request_st * const r = **(request_st ***)lua_touserdata(L, 1);
1648     const int id = http_header_hkey_get(k, (uint32_t)klen);
1649     magnet_push_buffer(L, http_header_response_get(r, id, k, klen));
1650     return 1;
1651 }
1652 
1653 static int magnet_resphdr_set_kv(lua_State *L, request_st * const r) {
1654     const const_buffer k = magnet_checkconstbuffer(L, -2);
1655     const const_buffer v = magnet_checkconstbuffer(L, -1);
1656     const enum http_header_e id = http_header_hkey_get(k.ptr, (uint32_t)k.len);
1657 
1658     switch (id) {
1659       /*case HTTP_HEADER_OTHER:*/
1660       default:
1661         break;
1662 
1663       case HTTP_HEADER_CONTENT_LENGTH:
1664         /* lighttpd handles Content-Length or Transfer-Encoding for response */
1665         return 0; /* silently ignore; do not allow modification */
1666 
1667       /* do not permit modification of hop-by-hop (connection) headers */
1668 
1669       case HTTP_HEADER_CONNECTION:
1670         /* do not permit modification of Connection, incl add/remove tokens */
1671         /* future: might provide a different interface to set r->keep_alive = 0,
1672          *         and also handle in context if HTTP/2 */
1673       case HTTP_HEADER_TRANSFER_ENCODING:
1674         return 0; /* silently ignore; do not allow modification */
1675     }
1676 
1677     if (0 == v.len) {
1678         http_header_response_unset(r, id, k.ptr, k.len);
1679         return 0;
1680     }
1681 
1682     buffer * const vb = http_header_response_set_ptr(r, id, k.ptr, k.len);
1683     buffer_copy_string_len(vb, v.ptr, v.len);
1684 
1685     if (r->http_version >= HTTP_VERSION_2) {
1686         /* handle multi-line response headers with HTTP/2
1687          * (lowercase header name and mark r->resp_header_repeated)
1688          * (similar to http_header.c:http_header_response_insert_addtl()) */
1689         for (char *n = vb->ptr; (n = strchr(n, '\n')); ) {
1690             r->resp_header_repeated = 1;
1691             do {
1692                 ++n;
1693                 if (light_isupper(*n)) *n |= 0x20;
1694             } while (*n != ':' && *n != '\n' && *n != '\0');
1695         }
1696     }
1697 
1698     return 0;
1699 }
1700 
1701 static int magnet_resphdr_set(lua_State *L) {
1702     /*const_buffer k = magnet_checkconstbuffer(L, 2);*/
1703     /*const_buffer v = magnet_checkconstbuffer(L, 3);*/
1704     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
1705     return magnet_resphdr_set_kv(L, r);
1706 }
1707 
1708 static int magnet_resphdr_pairs(lua_State *L) {
1709     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
1710     return magnet_array_pairs(L, &r->resp_headers);
1711 }
1712 
1713 static int magnet_plugin_stats_get(lua_State *L) {
1714     const_buffer k = magnet_checkconstbuffer(L, 2);
1715     lua_pushinteger(L, (lua_Integer)*plugin_stats_get_ptr(k.ptr, k.len));
1716     return 1;
1717 }
1718 
1719 static int magnet_plugin_stats_set(lua_State *L) {
1720     const_buffer k = magnet_checkconstbuffer(L, 2);
1721     plugin_stats_set(k.ptr, k.len, luaL_checkinteger(L, 3));
1722     return 0;
1723 }
1724 
1725 static int magnet_plugin_stats_pairs(lua_State *L) {
1726     return magnet_array_pairs(L, &plugin_stats);
1727 }
1728 
1729 
1730 static int
1731 magnet_req_item_get (lua_State *L)
1732 {
1733     size_t klen;
1734     const char * const k = luaL_checklstring(L, 2, &klen);
1735     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
1736     switch (klen) {
1737       case 8:
1738         if (0 == memcmp(k, "bytes_in", 8)) {
1739             lua_pushinteger(L, (lua_Integer)http_request_stats_bytes_in(r));
1740             return 1;
1741         }
1742         break;
1743       case 9:
1744         if (0 == memcmp(k, "bytes_out", 9)) {
1745             lua_pushinteger(L, (lua_Integer)http_request_stats_bytes_out(r));
1746             return 1;
1747         }
1748         if (0 == memcmp(k, "stream_id", 9)) {
1749             lua_pushinteger(L, (lua_Integer)r->h2id);
1750             return 1;
1751         }
1752         if (0 == memcmp(k, "req_count", 9)) {
1753             lua_pushinteger(L, (lua_Integer)r->con->request_count);
1754             return 1;
1755         }
1756         break;
1757       case 10:
1758         if (0 == memcmp(k, "start_time", 10)) {
1759             lua_pushinteger(L, (lua_Integer)r->start_hp.tv_sec);
1760             lua_pushinteger(L, (lua_Integer)r->start_hp.tv_nsec);
1761             lua_pushcclosure(L, magnet_return_upvalue2, 2);
1762             return 1;
1763         }
1764         if (0 == memcmp(k, "keep_alive", 10)) {
1765             lua_pushinteger(L, (lua_Integer)r->keep_alive);
1766             return 1;
1767         }
1768         break;
1769       case 11:
1770         if (0 == memcmp(k, "http_status", 11)) {
1771             lua_pushinteger(L, (lua_Integer)r->http_status);
1772             return 1;
1773         }
1774         break;
1775       case 14:
1776         if (0 == memcmp(k, "req_header_len", 14)) {
1777             lua_pushinteger(L, (lua_Integer)r->rqst_header_len);
1778             return 1;
1779         }
1780         break;
1781       case 15:
1782         if (0 == memcmp(k, "resp_header_len", 15)) {
1783             lua_pushinteger(L, (lua_Integer)r->resp_header_len);
1784             return 1;
1785         }
1786         break;
1787       default:
1788         break;
1789     }
1790     return luaL_error(L, "r.req_item['%s'] invalid", k);
1791 }
1792 
1793 static int
1794 magnet_req_item_set (lua_State *L)
1795 {
1796     size_t klen;
1797     const char * const k = luaL_checklstring(L, 2, &klen);
1798     int v = (int)luaL_checkinteger(L, 3);
1799 
1800     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
1801     switch (klen) {
1802       case 10:
1803         if (0 == memcmp(k, "keep_alive", 10)) {
1804             if (v == 0 || v == -1) r->keep_alive = v;
1805             return 0;
1806         }
1807         break;
1808       default:
1809         break;
1810     }
1811     return luaL_error(L, "r.req_item['%s'] invalid or read-only", k);
1812 }
1813 
1814 
1815 typedef struct {
1816 	const char *name;
1817 	uint32_t nlen;
1818 	enum {
1819 		MAGNET_ENV_UNSET,
1820 
1821 		MAGNET_ENV_PHYSICAL_PATH,
1822 		MAGNET_ENV_PHYSICAL_REL_PATH,
1823 		MAGNET_ENV_PHYSICAL_DOC_ROOT,
1824 		MAGNET_ENV_PHYSICAL_BASEDIR,
1825 
1826 		MAGNET_ENV_URI_PATH,
1827 		MAGNET_ENV_URI_PATH_RAW,
1828 		MAGNET_ENV_URI_SCHEME,
1829 		MAGNET_ENV_URI_AUTHORITY,
1830 		MAGNET_ENV_URI_QUERY,
1831 
1832 		MAGNET_ENV_REQUEST_METHOD,
1833 		MAGNET_ENV_REQUEST_URI,
1834 		MAGNET_ENV_REQUEST_ORIG_URI,
1835 		MAGNET_ENV_REQUEST_PATH_INFO,
1836 		MAGNET_ENV_REQUEST_REMOTE_ADDR,
1837 		MAGNET_ENV_REQUEST_REMOTE_PORT,
1838 		MAGNET_ENV_REQUEST_SERVER_ADDR,
1839 		MAGNET_ENV_REQUEST_SERVER_PORT,
1840 		MAGNET_ENV_REQUEST_PROTOCOL,
1841 		MAGNET_ENV_REQUEST_SERVER_NAME,
1842 		MAGNET_ENV_REQUEST_STAGE,
1843 
1844 		/* remove after lighttpd 1.4.65 */
1845 		MAGNET_ENV_RESPONSE_HTTP_STATUS,
1846 		MAGNET_ENV_RESPONSE_BODY_LENGTH,
1847 		MAGNET_ENV_RESPONSE_BODY
1848 	} type;
1849 } magnet_env_t;
1850 
1851 /*(NB: coordinate any changes with scan offsets in magnet_env_get_id())*/
1852 static const magnet_env_t magnet_env[] = {
1853     { CONST_STR_LEN("physical.path"),        MAGNET_ENV_PHYSICAL_PATH },
1854     { CONST_STR_LEN("physical.rel-path"),    MAGNET_ENV_PHYSICAL_REL_PATH },
1855     { CONST_STR_LEN("physical.doc-root"),    MAGNET_ENV_PHYSICAL_DOC_ROOT },
1856     { CONST_STR_LEN("physical.basedir"),     MAGNET_ENV_PHYSICAL_BASEDIR },
1857 
1858     { CONST_STR_LEN("uri.path"),             MAGNET_ENV_URI_PATH },
1859     { CONST_STR_LEN("uri.path-raw"),         MAGNET_ENV_URI_PATH_RAW },
1860     { CONST_STR_LEN("uri.scheme"),           MAGNET_ENV_URI_SCHEME },
1861     { CONST_STR_LEN("uri.authority"),        MAGNET_ENV_URI_AUTHORITY },
1862     { CONST_STR_LEN("uri.query"),            MAGNET_ENV_URI_QUERY },
1863 
1864     { CONST_STR_LEN("request.method"),       MAGNET_ENV_REQUEST_METHOD },
1865     { CONST_STR_LEN("request.uri"),          MAGNET_ENV_REQUEST_URI },
1866     { CONST_STR_LEN("request.orig-uri"),     MAGNET_ENV_REQUEST_ORIG_URI },
1867     { CONST_STR_LEN("request.path-info"),    MAGNET_ENV_REQUEST_PATH_INFO },
1868     { CONST_STR_LEN("request.remote-ip"),    MAGNET_ENV_REQUEST_REMOTE_ADDR },
1869     { CONST_STR_LEN("request.remote-addr"),  MAGNET_ENV_REQUEST_REMOTE_ADDR },
1870     { CONST_STR_LEN("request.remote-port"),  MAGNET_ENV_REQUEST_REMOTE_PORT },
1871     { CONST_STR_LEN("request.server-addr"),  MAGNET_ENV_REQUEST_SERVER_ADDR },
1872     { CONST_STR_LEN("request.server-port"),  MAGNET_ENV_REQUEST_SERVER_PORT },
1873     { CONST_STR_LEN("request.protocol"),     MAGNET_ENV_REQUEST_PROTOCOL },
1874     { CONST_STR_LEN("request.server-name"),  MAGNET_ENV_REQUEST_SERVER_NAME },
1875     { CONST_STR_LEN("request.stage"),        MAGNET_ENV_REQUEST_STAGE },
1876 
1877     { CONST_STR_LEN("response.http-status"), MAGNET_ENV_RESPONSE_HTTP_STATUS },
1878     { CONST_STR_LEN("response.body-length"), MAGNET_ENV_RESPONSE_BODY_LENGTH },
1879     { CONST_STR_LEN("response.body"),        MAGNET_ENV_RESPONSE_BODY },
1880 
1881     { NULL, 0, MAGNET_ENV_UNSET }
1882 };
1883 
1884 __attribute_cold__
1885 static void
1886 magnet_env_get_uri_path_raw (buffer * const dest, const buffer * const target)
1887 {
1888     const uint32_t len = buffer_clen(target);
1889     const char * const qmark = memchr(target->ptr, '?', len);
1890     buffer_copy_string_len(dest, target->ptr,
1891                            qmark ? (uint32_t)(qmark - target->ptr) : len);
1892 }
1893 
1894 __attribute_cold__
1895 static int
1896 magnet_env_set_uri_path_raw (request_st * const r,
1897                              const const_buffer * const val)
1898 {
1899     /* modify uri-path of r->target; preserve query-part, if present */
1900     /* XXX: should we require that resulting path begin with '/' or %2F ? */
1901     const uint32_t len = buffer_clen(&r->target);
1902     const char * const qmark = memchr(r->target.ptr, '?', len);
1903     if (NULL != qmark)
1904         buffer_copy_string_len(r->tmp_buf, qmark,
1905                                len - (uint32_t)(qmark - r->target.ptr));
1906     buffer_copy_string_len(&r->target, val->ptr, val->len);
1907     if (NULL != qmark)
1908         buffer_append_string_buffer(&r->target, r->tmp_buf);
1909     return 0;
1910 }
1911 
1912 __attribute_cold__
1913 __attribute_noinline__
1914 static buffer *
1915 magnet_env_get_laddr_by_id (request_st * const r, const int id)
1916 {
1917     buffer * const dest = r->tmp_buf;
1918     const server_socket * const srv_socket = r->con->srv_socket;
1919     switch (id) {
1920       case MAGNET_ENV_REQUEST_SERVER_ADDR: /* local IP without port */
1921         if (sock_addr_is_addr_wildcard(&srv_socket->addr)) {
1922             sock_addr addrbuf;
1923             socklen_t addrlen = sizeof(addrbuf);
1924             const int fd = r->con->fd;
1925             if (0 == getsockname(fd,(struct sockaddr *)&addrbuf,&addrlen)) {
1926                 char buf[INET6_ADDRSTRLEN + 1];
1927                 const char *s = sock_addr_inet_ntop(&addrbuf, buf, sizeof(buf));
1928                 if (NULL != s) {
1929                     buffer_copy_string_len(dest, s, strlen(s));
1930                     break;
1931                 }
1932             }
1933         }
1934         buffer_copy_string_len(dest, srv_socket->srv_token->ptr,
1935                                srv_socket->srv_token_colon);
1936         break;
1937       case MAGNET_ENV_REQUEST_SERVER_PORT:
1938       {
1939         const buffer * const srv_token = srv_socket->srv_token;
1940         const uint32_t tlen = buffer_clen(srv_token);
1941         uint32_t portoffset = srv_socket->srv_token_colon;
1942         portoffset = portoffset < tlen ? portoffset+1 : tlen;
1943         buffer_copy_string_len(dest, srv_token->ptr+portoffset,
1944                                tlen-portoffset);
1945         break;
1946       }
1947       default:
1948         break;
1949     }
1950     return dest;
1951 }
1952 
1953 static buffer *
1954 magnet_env_get_buffer_by_id (request_st * const r, const int id)
1955 {
1956 	buffer *dest = r->tmp_buf;
1957 	buffer_clear(dest);
1958 
1959 	switch (id) {
1960 	case MAGNET_ENV_PHYSICAL_PATH: dest = &r->physical.path; break;
1961 	case MAGNET_ENV_PHYSICAL_REL_PATH: dest = &r->physical.rel_path; break;
1962 	case MAGNET_ENV_PHYSICAL_DOC_ROOT: dest = &r->physical.doc_root; break;
1963 	case MAGNET_ENV_PHYSICAL_BASEDIR: dest = &r->physical.basedir; break;
1964 
1965 	case MAGNET_ENV_URI_PATH: dest = &r->uri.path; break;
1966 	case MAGNET_ENV_URI_PATH_RAW:
1967 		magnet_env_get_uri_path_raw(dest, &r->target);
1968 		break;
1969 	case MAGNET_ENV_URI_SCHEME: dest = &r->uri.scheme; break;
1970 	case MAGNET_ENV_URI_AUTHORITY: dest = &r->uri.authority; break;
1971 	case MAGNET_ENV_URI_QUERY: dest = &r->uri.query; break;
1972 
1973 	case MAGNET_ENV_REQUEST_METHOD:
1974 		http_method_append(dest, r->http_method);
1975 		break;
1976 	case MAGNET_ENV_REQUEST_URI:      dest = &r->target; break;
1977 	case MAGNET_ENV_REQUEST_ORIG_URI: dest = &r->target_orig; break;
1978 	case MAGNET_ENV_REQUEST_PATH_INFO: dest = &r->pathinfo; break;
1979 	case MAGNET_ENV_REQUEST_REMOTE_ADDR: dest = &r->con->dst_addr_buf; break;
1980 	case MAGNET_ENV_REQUEST_REMOTE_PORT:
1981 		buffer_append_int(dest, sock_addr_get_port(&r->con->dst_addr));
1982 		break;
1983 	case MAGNET_ENV_REQUEST_SERVER_ADDR: /* local IP without port */
1984 	case MAGNET_ENV_REQUEST_SERVER_PORT:
1985 		return magnet_env_get_laddr_by_id(r, id);
1986 	case MAGNET_ENV_REQUEST_PROTOCOL:
1987 		http_version_append(dest, r->http_version);
1988 		break;
1989 	case MAGNET_ENV_REQUEST_SERVER_NAME:
1990 		buffer_copy_buffer(dest, r->server_name);
1991 		break;
1992 	case MAGNET_ENV_REQUEST_STAGE:
1993 		if (http_request_state_is_keep_alive(r))
1994 			buffer_append_string_len(dest, CONST_STR_LEN("keep-alive"));
1995 		else
1996 			http_request_state_append(dest, r->state);
1997 		break;
1998 
1999 	/* remove after lighttpd 1.4.65 */
2000 	case MAGNET_ENV_RESPONSE_HTTP_STATUS:
2001 		buffer_append_int(dest, r->http_status);
2002 		break;
2003 	case MAGNET_ENV_RESPONSE_BODY_LENGTH:
2004 		if (!r->resp_body_finished)
2005 			return NULL;
2006 		log_error(r->conf.errh, __FILE__, __LINE__,
2007 		          "lighty.r.req_attr['response.body-length'] is deprecated "
2008 		          "and will be removed. Use lighty.r.resp_body.len instead.");
2009 		buffer_append_int(dest, chunkqueue_length(&r->write_queue));
2010 		break;
2011 	case MAGNET_ENV_RESPONSE_BODY:
2012 		if (!r->resp_body_finished)
2013 			return NULL;
2014 		log_error(r->conf.errh, __FILE__, __LINE__,
2015 		          "lighty.r.req_attr['response.body'] is deprecated "
2016 		          "and will be removed. Use lighty.r.resp_body.get instead.");
2017 		if (0 == chunkqueue_length(&r->write_queue)
2018 		    || !(dest = chunkqueue_read_squash(&r->write_queue,r->conf.errh)))
2019 			buffer_copy_string_len((dest = r->tmp_buf), CONST_STR_LEN(""));
2020 		break;
2021 
2022 	case MAGNET_ENV_UNSET:
2023 		return NULL;
2024 	}
2025 
2026 	return dest;
2027 }
2028 
2029 __attribute_pure__
2030 static int magnet_env_get_id(const char * const key, const size_t klen) {
2031     /*(NB: ensure offsets match position in magnet_env[])*/
2032     int i; /* magnet_env[] scan offset */
2033     switch (*key) {
2034       case 'r': /* request.* or response.* */
2035         i = klen > 7 && key[7] == '.' ? 9 : 21;
2036         break;
2037       case 'u': /* uri.* */
2038       default:
2039         i = 4;
2040         break;
2041       case 'p': /* physical.* */
2042         i = 0;
2043         break;
2044     }
2045     for (; i < (int)(sizeof(magnet_env)/sizeof(*magnet_env)); ++i) {
2046         if (klen == magnet_env[i].nlen
2047             && 0 == memcmp(key, magnet_env[i].name, klen))
2048             return magnet_env[i].type;
2049     }
2050     return MAGNET_ENV_UNSET;
2051 }
2052 
2053 static int magnet_env_get(lua_State *L) {
2054     size_t klen;
2055     const char * const k = luaL_checklstring(L, 2, &klen);
2056     const int env_id = magnet_env_get_id(k, klen);
2057     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
2058     magnet_push_buffer(L, magnet_env_get_buffer_by_id(r, env_id));
2059     return 1;
2060 }
2061 
2062 __attribute_cold__
2063 static int
2064 magnet_env_set_raddr_by_id (lua_State *L, request_st * const r, const int id,
2065                             const const_buffer * const val)
2066 {
2067     connection * const con = r->con;
2068     switch (id) {
2069       case MAGNET_ENV_REQUEST_REMOTE_ADDR:
2070        #ifdef HAVE_SYS_UN_H
2071         if (val->len && *val->ptr == '/'
2072             && 0 == sock_addr_assign(&con->dst_addr, AF_UNIX, 0, val->ptr)) {
2073         }
2074         else
2075        #endif
2076         {
2077             sock_addr saddr;
2078             saddr.plain.sa_family = AF_UNSPEC;
2079             if (1 == sock_addr_from_str_numeric(&saddr, val->ptr, r->conf.errh)
2080                 && saddr.plain.sa_family != AF_UNSPEC) {
2081                 sock_addr_set_port(&saddr, 0);
2082                 memcpy(&con->dst_addr, &saddr, sizeof(sock_addr));
2083             }
2084             else {
2085                 return luaL_error(L,
2086                                   "r.req_attr['remote-addr'] invalid addr: %s",
2087                                   val->ptr);
2088             }
2089         }
2090         buffer_copy_string_len(&con->dst_addr_buf, val->ptr, val->len);
2091         config_cond_cache_reset_item(r, COMP_HTTP_REMOTE_IP);
2092         break;
2093       case MAGNET_ENV_REQUEST_REMOTE_PORT:
2094         sock_addr_set_port(&con->dst_addr, (unsigned short)atoi(val->ptr));
2095         break;
2096       default:
2097         break;
2098     }
2099     return 0;
2100 }
2101 
2102 #if 0
2103 __attribute_cold__
2104 static int
2105 magnet_env_set_server_name (request_st * const r,
2106                             const const_buffer * const val)
2107 {
2108     r->server_name = &r->server_name_buf;
2109     buffer_copy_string_len(&r->server_name_buf, val->ptr, val->len);
2110     return 0;
2111 }
2112 #endif
2113 
2114 static int magnet_env_set(lua_State *L) {
2115     size_t klen;
2116     const char * const key = luaL_checklstring(L, 2, &klen);
2117     const_buffer val = magnet_checkconstbuffer(L, 3);
2118 
2119     const int env_id = magnet_env_get_id(key, klen);
2120     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
2121 
2122     switch (env_id) {
2123       default:
2124         break;
2125       case MAGNET_ENV_URI_PATH_RAW:
2126         return magnet_env_set_uri_path_raw(r, &val);
2127       case MAGNET_ENV_REQUEST_REMOTE_ADDR:
2128       case MAGNET_ENV_REQUEST_REMOTE_PORT:
2129         return magnet_env_set_raddr_by_id(L, r, env_id, &val);
2130      #if 0 /*(leave read-only for now; change attempts silently ignored)*/
2131       case MAGNET_ENV_REQUEST_SERVER_NAME:
2132         return magnet_env_set_server_name(r, &val);
2133      #endif
2134       /*case MAGNET_ENV_REQUEST_STAGE:*//*(change attempts silently ignored)*/
2135       /* remove after lighttpd 1.4.65 */
2136       /*case MAGNET_ENV_RESPONSE_HTTP_STATUS:*/
2137       /*case MAGNET_ENV_RESPONSE_BODY_LENGTH:*/
2138       case MAGNET_ENV_RESPONSE_BODY:
2139         return luaL_error(L, "r.req_attr['%s'] is read-only", key);
2140     }
2141 
2142     buffer * const dest = magnet_env_get_buffer_by_id(r, env_id);
2143     if (NULL == dest)
2144         return luaL_error(L, "couldn't store '%s' in r.req_attr[]", key);
2145 
2146     if (lua_isnoneornil(L, 3)) {
2147         if (env_id==MAGNET_ENV_URI_QUERY || env_id==MAGNET_ENV_PHYSICAL_PATH)
2148             buffer_clear(dest);
2149         else
2150             buffer_blank(dest);
2151     }
2152     else {
2153         buffer_copy_string_len(dest, val.ptr, val.len);
2154         /* NB: setting r->uri.query does not modify query-part in r->target */
2155     }
2156 
2157     switch (env_id) {
2158       case MAGNET_ENV_URI_SCHEME:
2159         buffer_to_lower(dest);
2160         config_cond_cache_reset_item(r, COMP_HTTP_SCHEME);
2161         break;
2162       case MAGNET_ENV_URI_AUTHORITY:
2163         r->server_name = dest;
2164         buffer_to_lower(dest);
2165         config_cond_cache_reset_item(r, COMP_HTTP_HOST);
2166         break;
2167       case MAGNET_ENV_URI_PATH:
2168         config_cond_cache_reset_item(r, COMP_HTTP_URL);
2169         break;
2170       case MAGNET_ENV_URI_QUERY:
2171         config_cond_cache_reset_item(r, COMP_HTTP_QUERY_STRING);
2172         break;
2173       default:
2174         break;
2175     }
2176 
2177     return 0;
2178 }
2179 
2180 static int magnet_env_next(lua_State *L) {
2181 	/* ignore previous key: use upvalue for current pos */
2182 	lua_settop(L, 0);
2183 	const int pos = lua_tointeger(L, lua_upvalueindex(1));
2184 
2185 	if (NULL == magnet_env[pos].name) return 0; /* end of list */
2186 	/* Update our positional upval to reflect our new current position */
2187 	lua_pushinteger(L, pos + 1);
2188 	lua_replace(L, lua_upvalueindex(1));
2189 
2190 	/* key to return */
2191 	lua_pushlstring(L, magnet_env[pos].name, magnet_env[pos].nlen);
2192 
2193 	/* get value */
2194 	request_st * const r = lua_touserdata(L, lua_upvalueindex(2));
2195 	magnet_push_buffer(L, magnet_env_get_buffer_by_id(r, magnet_env[pos].type));
2196 
2197 	/* return 2 items on the stack (key, value) */
2198 	return 2;
2199 }
2200 
2201 static int magnet_env_pairs(lua_State *L) {
2202     lua_pushinteger(L, 0); /* Push our current pos (the start) into upval 1 */
2203     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
2204     lua_pushlightuserdata(L, r); /* Push request_st *r into upval 2 */
2205     lua_pushcclosure(L, magnet_env_next, 2); /* Push new closure with 2 upvals*/
2206     return 1;
2207 }
2208 
2209 static int magnet_envvar_get(lua_State *L) {
2210     size_t klen;
2211     const char * const k = luaL_checklstring(L, 2, &klen);
2212     const request_st * const r = **(request_st ***)lua_touserdata(L, 1);
2213     magnet_push_buffer(L, http_header_env_get(r, k, klen));
2214     return 1;
2215 }
2216 
2217 static int magnet_envvar_set(lua_State *L) {
2218     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
2219     const_buffer key = magnet_checkconstbuffer(L, 2);
2220     if (__builtin_expect( (lua_isnil(L, 3)), 0)) {
2221         buffer * const v = http_header_env_get(r, key.ptr, key.len);
2222         if (v) buffer_clear(v); /*(unset)*/
2223         return 0;
2224     }
2225     const_buffer val = magnet_checkconstbuffer(L, 3);
2226     http_header_env_set(r, key.ptr, key.len, val.ptr, val.len);
2227     return 0;
2228 }
2229 
2230 static int magnet_envvar_pairs(lua_State *L) {
2231     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
2232     return magnet_array_pairs(L, &r->env);
2233 }
2234 
2235 
2236 static int magnet_respbody_add(lua_State *L) {
2237     request_st * const r = lua_touserdata(L, lua_upvalueindex(1));
2238     if (lua_isstring(L, -1)) {
2239         const_buffer data = magnet_checkconstbuffer(L, -1);
2240         http_chunk_append_mem(r, data.ptr, data.len);
2241         return 1; /* boolean true */
2242     }
2243     else if (!lua_istable(L, -1))
2244         return 0; /* boolean false */
2245 
2246     /* note: differs from magnet_attach_content();
2247      * magnet_attach_content() has misnamed 'length' param which
2248      * is treated as 0-offset pos one after end of range to send.
2249      * Here, 'length' means 'length', as one would expect */
2250     for (int i=1, end=0, n=(int)lua_rawlen(L,-1); !end && i <= n; ++i) {
2251         lua_rawgeti(L, -1, i);
2252 
2253         if (lua_isstring(L, -1)) {
2254             const_buffer data = magnet_checkconstbuffer(L, -1);
2255             http_chunk_append_mem(r, data.ptr, data.len);
2256         }
2257         else if (lua_istable(L, -1)) {
2258             lua_getfield(L, -1, "filename");
2259             lua_getfield(L, -2, "length");
2260             lua_getfield(L, -3, "offset");
2261 
2262             if (lua_isstring(L, -3)) { /* filename has to be a string */
2263                 off_t off = (off_t) luaL_optinteger(L, -1, 0);
2264                 off_t len = (off_t) luaL_optinteger(L, -2, -1);
2265                 /*(-1 len as flag to use file size minus offset (below))*/
2266                 buffer stor; /*(note: do not free magnet_checkbuffer() result)*/
2267                 const buffer * const fn = magnet_checkbuffer(L, -3, &stor);
2268                 stat_cache_entry * const sce = (!buffer_is_blank(fn))
2269                   ? stat_cache_get_entry_open(fn, r->conf.follow_symlink)
2270                   : NULL;
2271                 if (sce && (sce->fd >= 0 || sce->st.st_size == 0)) {
2272                     /* treat negative offset as bytes from end of file */
2273                     /* treat negative len as bytes from offset to end of file */
2274                     if (off > sce->st.st_size)
2275                         off = sce->st.st_size;
2276                     else if (off < 0) {
2277                         off = sce->st.st_size - off;
2278                         if (off < 0) off = 0;
2279                     }
2280                     if (len < 0 || sce->st.st_size - off < len)
2281                         len = sce->st.st_size - off;
2282                     if (len)
2283                         http_chunk_append_file_ref_range(r, sce, off, len);
2284                 }
2285                 else {
2286                     log_error(r->conf.errh, __FILE__, __LINE__,
2287                       "error opening file '%s'", fn->ptr);
2288                     end = 1;
2289                 }
2290             }
2291             else {
2292                 log_error(r->conf.errh, __FILE__, __LINE__,
2293                   "body[%d] table field \"filename\" must be a string", i);
2294                 end = 1;
2295             }
2296 
2297             lua_pop(L, 3);
2298         }
2299         else if (lua_isnil(L, -1)) { /* end of list */
2300             end = 1;
2301         }
2302         else {
2303             log_error(r->conf.errh, __FILE__, __LINE__,
2304               "body[%d] is neither a string nor a table", i);
2305             end = 1;
2306         }
2307 
2308         lua_pop(L, 1); /* pop the content[...] entry value */
2309     }
2310 
2311     return 1; /* boolean true */
2312 }
2313 
2314 
2315 static int magnet_respbody(lua_State *L) {
2316     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
2317     size_t klen;
2318     const char * const k = luaL_checklstring(L, 2, &klen);
2319     switch (k[0]) {
2320       case 'a': /* add; r.resp_body.add */
2321         if (k[1] == 'd' && k[2] == 'd' && k[3] == '\0') {
2322             lua_pushlightuserdata(L, r);
2323             lua_pushcclosure(L, magnet_respbody_add, 1);
2324             return 1;
2325         }
2326         break;
2327       case 'b':
2328         if (klen == 8 && 0 == memcmp(k, "bytes_in", 8)) {
2329             lua_pushinteger(L, r->write_queue.bytes_in);
2330             return 1;
2331         }
2332         if (klen == 9 && 0 == memcmp(k, "bytes_out", 9)) {
2333             lua_pushinteger(L, r->write_queue.bytes_out);
2334             return 1;
2335         }
2336         break;
2337      #if 0 /*(future: provide pairs() interface to iterate over chunkqueue)*/
2338            /*(might convert chunks into table of strings, {filename="..."})*/
2339            /*(what about c->offset into chunk?)*/
2340      #endif
2341       case 'g': /* get; r.resp_body.get */
2342         if (k[1] == 'e' && k[2] == 't' && k[3] == '\0') {
2343             if (r->resp_body_finished) {
2344                 chunkqueue * const cq = &r->write_queue;
2345                 chunkqueue_length(cq)
2346                   ? magnet_push_buffer(L,
2347                                        chunkqueue_read_squash(cq,r->conf.errh))
2348                   : (void)lua_pushlstring(L, "", 0);
2349             }
2350             else
2351                 lua_pushnil(L); /*(?maybe return -1 instead if len unknown?)*/
2352             return 1;
2353         }
2354         break;
2355       case 'l': /* len; r.resp_body.len */
2356         if (k[1] == 'e' && k[2] == 'n' && k[3] == '\0') {
2357             if (r->resp_body_finished)
2358                 lua_pushinteger(L, chunkqueue_length(&r->write_queue));
2359             else
2360                 lua_pushnil(L); /*(?maybe return -1 instead if len unknown?)*/
2361             return 1;
2362         }
2363         break;
2364       case 's': /* set; r.resp_body.set */
2365         if (k[1] == 'e' && k[2] == 't' && k[3] == '\0') {
2366             http_response_body_clear(r, 0); /* clear respbody, then add */
2367             lua_pushlightuserdata(L, r);
2368             lua_pushcclosure(L, magnet_respbody_add, 1);
2369             return 1;
2370         }
2371         break;
2372       default:
2373         break;
2374     }
2375     lua_pushliteral(L, "r.resp_body invalid method or param");
2376     lua_error(L);
2377     return 0;
2378 }
2379 
2380 
2381 static int magnet_reqbody_add(lua_State *L) {
2382     request_st * const r = lua_touserdata(L, lua_upvalueindex(1));
2383     chunkqueue * const cq = &r->reqbody_queue;
2384     const int tempfile = (cq->last && cq->last->file.is_temp);
2385     if (lua_isstring(L, -1)) {
2386         const_buffer data = magnet_checkconstbuffer(L, -1);
2387         r->reqbody_length += data.len;
2388         if (r->reqbody_length <= 65536 && !tempfile)
2389             chunkqueue_append_mem(cq, data.ptr, data.len);
2390         else if (chunkqueue_append_mem_to_tempfile(cq, data.ptr, data.len,
2391                                                    r->conf.errh))
2392             return 0; /* boolean false */
2393         return 1; /* boolean true */
2394     }
2395     else if (!lua_istable(L, -1))
2396         return 0; /* boolean false */
2397 
2398     for (int i=1, end=0, n=(int)lua_rawlen(L,-1); !end && i <= n; ++i) {
2399         lua_rawgeti(L, -1, i);
2400 
2401         if (lua_isstring(L, -1)) {
2402             const_buffer data = magnet_checkconstbuffer(L, -1);
2403             r->reqbody_length += data.len;
2404             if (r->reqbody_length <= 65536 && !tempfile)
2405                 chunkqueue_append_mem(cq, data.ptr, data.len);
2406             else if (chunkqueue_append_mem_to_tempfile(cq, data.ptr, data.len,
2407                                                        r->conf.errh))
2408                 return 0; /* boolean false */
2409         }
2410         else if (lua_isnil(L, -1)) { /* end of list */
2411             end = 1;
2412         }
2413         else {
2414             log_error(r->conf.errh, __FILE__, __LINE__,
2415               "body[%d] table must contain strings", i);
2416             end = 1;
2417         }
2418 
2419         lua_pop(L, 1); /* pop the content[...] entry value */
2420     }
2421 
2422     return 1; /* boolean true */
2423 }
2424 
2425 
2426 static int magnet_reqbody(lua_State *L) {
2427     request_st * const r = **(request_st ***)lua_touserdata(L, 1);
2428     size_t klen;
2429     const char * const k = luaL_checklstring(L, 2, &klen);
2430     switch (k[0]) {
2431       case 'a': /* add; r.req_body.add */
2432         if (k[1] == 'd' && k[2] == 'd' && k[3] == '\0') {
2433             chunkqueue * const cq = &r->reqbody_queue;
2434             if (cq->bytes_in == (off_t)r->reqbody_length) {
2435                 lua_pushlightuserdata(L, r);
2436                 lua_pushcclosure(L, magnet_reqbody_add, 1);
2437             }
2438             else /* reqbody not yet collected */
2439                 lua_pushnil(L);
2440             return 1;
2441         }
2442         break;
2443       case 'b':
2444         if (klen == 8 && 0 == memcmp(k, "bytes_in", 8)) {
2445             lua_pushinteger(L, r->reqbody_queue.bytes_in);
2446             return 1;
2447         }
2448         if (klen == 9 && 0 == memcmp(k, "bytes_out", 9)) {
2449             lua_pushinteger(L, r->reqbody_queue.bytes_out);
2450             return 1;
2451         }
2452         break;
2453       case 'c': /* collect; r.req_body.collect */
2454         if (klen == 7 && 0 == memcmp(k, "collect", 7)) {
2455             chunkqueue * const cq = &r->reqbody_queue;
2456             if (cq->bytes_in == (off_t)r->reqbody_length)
2457                 lua_pushboolean(L, 1);
2458             else if (NULL == r->handler_module) {
2459                 r->conf.stream_request_body &=
2460                   ~(FDEVENT_STREAM_REQUEST|FDEVENT_STREAM_REQUEST_BUFMIN);
2461                 r->handler_module = plugin_data_singleton->self;
2462                 lua_pushboolean(L, 0);
2463             }
2464             else if (0 == strcmp(r->handler_module->name, "security3")) {
2465                 /*(mod_security3 uses similar technique to collect req body)*/
2466                 lua_pushboolean(L, 0);
2467             }
2468             else {
2469                 log_error(r->conf.errh, __FILE__, __LINE__,
2470                   "unable to collect request body (handler already set); "
2471                   "(perhaps load mod_magnet earlier in server.modules, "
2472                   "before mod_%s; or require r.req_env['REMOTE_USER'] before "
2473                   "attempting r.req_body.collect?)", r->handler_module->name);
2474                 lua_pushnil(L);
2475             }
2476             return 1;
2477         }
2478         break;
2479       case 'g': /* get; r.req_body.get */
2480         if (k[1] == 'e' && k[2] == 't' && k[3] == '\0') {
2481             chunkqueue * const cq = &r->reqbody_queue;
2482             if (cq->bytes_in == (off_t)r->reqbody_length)
2483                 chunkqueue_length(cq)
2484                   ? magnet_push_buffer(L,
2485                                        chunkqueue_read_squash(cq, r->conf.errh))
2486                   : (void)lua_pushlstring(L, "", 0);
2487             else
2488                 lua_pushnil(L); /*(?maybe return -1 instead if len unknown?)*/
2489             return 1;
2490         }
2491         break;
2492       case 'l': /* len */
2493         if (k[1] == 'e' && k[2] == 'n' && k[3] == '\0') {
2494             lua_pushinteger(L, r->reqbody_length);
2495             return 1;
2496         }
2497         break;
2498       case 's': /* set; r.req_body.set */
2499         if (k[1] == 'e' && k[2] == 't' && k[3] == '\0') {
2500             chunkqueue * const cq = &r->reqbody_queue;
2501             if (cq->bytes_in == (off_t)r->reqbody_length) {
2502                 r->reqbody_length = 0;
2503                 chunkqueue_reset(&r->reqbody_queue);
2504                 lua_pushlightuserdata(L, r);
2505                 lua_pushcclosure(L, magnet_reqbody_add, 1);
2506             }
2507             else /* reqbody not yet collected */
2508                 lua_pushnil(L);
2509             return 1;
2510         }
2511         break;
2512       default:
2513         break;
2514     }
2515     lua_pushliteral(L, "r.req_body invalid method or param");
2516     lua_error(L);
2517     return 0;
2518 }
2519 
2520 
2521 __attribute_cold__
2522 static int magnet_lighty_result_get(lua_State *L) {
2523     /* __index: param 1 is the lighty table the value was not found in */
2524     lua_pushvalue(L, 2);
2525     lua_rawget(L, lua_upvalueindex(1));
2526     if (lua_isnil(L, -1)) {
2527         const_buffer k = magnet_checkconstbuffer(L, 2);
2528         if (   (k.len == 6 && 0 == memcmp(k.ptr, "header", 6))
2529             || (k.len == 7 && 0 == memcmp(k.ptr, "content", 7))) {
2530             lua_pop(L, 1);            /* pop nil */
2531             lua_createtable(L, 0, 0); /* create "header","content" on demand */
2532             lua_pushvalue(L, 2);      /* k: "header" or "content" */
2533             lua_pushvalue(L, -2);     /* v: table */
2534             lua_rawset(L, lua_upvalueindex(1)); /* set in result table */
2535         }
2536     }
2537     return 1;
2538 }
2539 
2540 __attribute_cold__
2541 static int magnet_lighty_result_set(lua_State *L) {
2542     /* __newindex: param 1 is lighty table the value is supposed to be set in */
2543     /* assign value to alternate table; replacing existing value, if any */
2544     lua_rawset(L, lua_upvalueindex(1)); /* set in result table */
2545     return 0;
2546 }
2547 
2548 
2549 __attribute_cold__
2550 __attribute_noinline__
2551 static void magnet_copy_response_header(lua_State * const L, request_st * const r) {
2552     for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
2553         if (lua_isstring(L, -1) && lua_isstring(L, -2))
2554             magnet_resphdr_set_kv(L, r);
2555     }
2556 }
2557 
2558 /**
2559  * (deprecated API)
2560  * walk through the content array set by lua script, e.g.
2561  *   lighty.header["Content-Type"] = "text/html"
2562  *   lighty.content =
2563  *     { "<html><body><pre>", { file = "/content" } , "</pre></body></html>" }
2564  *   return 200
2565  */
2566 __attribute_cold__
2567 __attribute_noinline__
2568 static void magnet_attach_content(lua_State * const L, request_st * const r) {
2569 		http_response_body_clear(r, 0);
2570 		for (int i=1, end=0, n=(int)lua_rawlen(L,-1); !end && i <= n; ++i) {
2571 			lua_rawgeti(L, -1, i);
2572 
2573 			/* -1 is the value and should be the value ... aka a table */
2574 			if (lua_isstring(L, -1)) {
2575 				const_buffer data = magnet_checkconstbuffer(L, -1);
2576 				http_chunk_append_mem(r, data.ptr, data.len);
2577 			} else if (lua_istable(L, -1)) {
2578 				lua_getfield(L, -1, "filename");
2579 				lua_getfield(L, -2, "length"); /* (0-based) end of range (not actually "length") */
2580 				lua_getfield(L, -3, "offset"); /* (0-based) start of range */
2581 
2582 				if (lua_isstring(L, -3)) { /* filename has to be a string */
2583 					/*(luaL_optinteger might raise error, which we want to avoid)*/
2584 					/*off_t off = (off_t) luaL_optinteger(L, -1, 0);*/
2585 					/*off_t len = (off_t) luaL_optinteger(L, -2, -1);*/ /*(-1 as flag to use file size minus offset (below))*/
2586 					int isnum = 1;
2587 					off_t off = lua_isnil(L, -1) ? 0 : (off_t) lua_tointegerx(L, -1, &isnum);
2588 					if (!isnum) {
2589 						off = 0;
2590 						log_error(r->conf.errh, __FILE__, __LINE__,
2591 						  "content[%d] is a table and field \"offset\" must be an integer", i);
2592 					}
2593 					isnum = 1;
2594 					off_t len = lua_isnil(L, -2) ? -1 : (off_t) lua_tointegerx(L, -2, &isnum);
2595 					/*(-1 len as flag to use file size minus offset (below))*/
2596 					if (!isnum) {
2597 						len = -1;
2598 						log_error(r->conf.errh, __FILE__, __LINE__,
2599 						  "content[%d] is a table and field \"length\" must be an integer", i);
2600 					}
2601 					if (off < 0) {
2602 						log_error(r->conf.errh, __FILE__, __LINE__,
2603 						  "offset for '%s' is negative", lua_tostring(L, -3));
2604 						end = 1;
2605 					} else if (len >= off) {
2606 						len -= off;
2607 					} else if (-1 != len) {
2608 						log_error(r->conf.errh, __FILE__, __LINE__,
2609 						  "offset > length for '%s'", lua_tostring(L, -3));
2610 						end = 1;
2611 					}
2612 
2613 					if (!end && 0 != len) {
2614 						buffer stor; /*(note: do not free magnet_checkbuffer() result)*/
2615 						const buffer * const fn = magnet_checkbuffer(L, -3, &stor);
2616 						stat_cache_entry * const sce = (!buffer_is_blank(fn))
2617 						  ? stat_cache_get_entry_open(fn, r->conf.follow_symlink)
2618 						  : NULL;
2619 						if (sce && (sce->fd >= 0 || sce->st.st_size == 0)) {
2620 							if (len == -1 || sce->st.st_size - off < len)
2621 								len = sce->st.st_size - off;
2622 							if (len > 0)
2623 								http_chunk_append_file_ref_range(r, sce, off, len);
2624 						} else {
2625 							log_error(r->conf.errh, __FILE__, __LINE__,
2626 							  "error opening file content '%s' at offset %lld",
2627 							          lua_tostring(L, -3), (long long)off);
2628 							end = 1;
2629 						}
2630 					}
2631 				} else {
2632 					log_error(r->conf.errh, __FILE__, __LINE__,
2633 					  "content[%d] is a table and field \"filename\" must be a string", i);
2634 					end = 1;
2635 				}
2636 
2637 				lua_pop(L, 3);
2638 			} else if (lua_isnil(L, -1)) {
2639 				/* end of list */
2640 				end = 1;
2641 			} else {
2642 				log_error(r->conf.errh, __FILE__, __LINE__,
2643 				  "content[%d] is neither a string nor a table", i);
2644 				end = 1;
2645 			}
2646 
2647 			lua_pop(L, 1); /* pop the content[...] entry value */
2648 		}
2649 }
2650 
2651 __attribute_cold__
2652 static void magnet_mainenv_metatable(lua_State * const L) {
2653     if (luaL_newmetatable(L, "li.mainenv")) {                 /* (sp += 1) */
2654         lua_pushglobaltable(L);                               /* (sp += 1) */
2655         lua_setfield(L, -2, "__index"); /* { __index = _G }      (sp -= 1) */
2656         lua_pushboolean(L, 0);                                /* (sp += 1) */
2657         lua_setfield(L, -2, "__metatable"); /* protect metatable (sp -= 1) */
2658     }
2659 }
2660 
2661 static void
2662 magnet_request_userdata_method (lua_State * const L, request_st ** const rr, const char *meta)
2663 {
2664     /*(meta is name of cached metatable; meta must start w/ "li." prefix)*/
2665     *(request_st ***)lua_newuserdata0(L, sizeof(request_st **)) = rr;
2666   #ifdef __COVERITY__ /* shut up coverity; read the comment below */
2667     if (luaL_newmetatable(L, meta)) { }
2668   #else
2669     luaL_newmetatable(L, meta); /*(should not fail; init'd in script setup)*/
2670   #endif
2671     lua_setmetatable(L, -2);
2672     lua_setfield(L, -2, meta+3); /*(meta+3 to skip over "li." prefix)*/
2673 }
2674 
2675 static void
2676 magnet_request_table (lua_State * const L, request_st ** const rr)
2677 {
2678     /* r table
2679      *
2680      * r.req_header[]         HTTP request headers
2681      * r.req_attr[]           HTTP request attributes / components (strings)
2682      * r.req_item[]           HTTP request items (struct members, statistics)
2683      * r.req_env[]            HTTP request environment variables
2684      * r.req_body.*           HTTP request body accessors
2685      * r.req_body.bytes_in    HTTP request body chunkqueue bytes_in
2686      * r.req_body.bytes_out   HTTP request body chunkqueue bytes_out
2687      * r.resp_header[]        HTTP response headers
2688      * r.resp_body.*          HTTP response body accessors
2689      * r.resp_body.len        HTTP response body length
2690      * r.resp_body.add()      HTTP response body add (string or table)
2691      * r.resp_body.set()      HTTP response body set (string or table)
2692      * r.resp_body.bytes_in   HTTP response body chunkqueue bytes_in
2693      * r.resp_body.bytes_out  HTTP response body chunkqueue bytes_out
2694      */
2695     lua_createtable(L, 0, 7);                                 /* (sp += 1) */
2696 
2697     /* userdata methods share ptr-ptr to external object userdata rr
2698      * (similar functionality to that provided w/ luaL_setfuncs() in lua 5.2+)*/
2699     magnet_request_userdata_method(L, rr, "li.req_header"); /* req_header */
2700     magnet_request_userdata_method(L, rr, "li.req_attr");   /* req_attr */
2701     magnet_request_userdata_method(L, rr, "li.req_item");   /* req_item */
2702     magnet_request_userdata_method(L, rr, "li.req_env");    /* req_env */
2703     magnet_request_userdata_method(L, rr, "li.resp_header");/* resp_header */
2704     magnet_request_userdata_method(L, rr, "li.resp_body");  /* resp_body */
2705     magnet_request_userdata_method(L, rr, "li.req_body");   /* req_body */
2706 
2707     lua_createtable(L, 0, 2); /* metatable for r table           (sp += 1) */
2708     lua_pushcfunction(L, magnet_newindex_readonly);           /* (sp += 1) */
2709     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2710     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2711     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2712     lua_setmetatable(L, -2); /* tie the metatable to r           (sp -= 1) */
2713 }
2714 
2715 static int
2716 magnet_request_iter (lua_State *L)
2717 {
2718     /* upvalue 1: (connection *) in linked list
2719      * upvalue 2: index into h2con->r[]
2720      * upvalue 3: request userdata
2721      * upvalue 4: request table (references r in userdata) */
2722     connection *con = lua_touserdata(L, lua_upvalueindex(1));
2723 
2724     /* skip over HTTP/2 connections with no active requests */
2725     while (con && con->h2 && 0 == con->h2->rused)
2726         con = con->next;
2727     if (NULL == con)
2728         return 0;
2729 
2730     /* set (request_st *)r */
2731     int32_t i = -1;
2732     if (con->h2) {
2733         /* get index into h2con->r[] */
2734         i = lua_tointeger(L, lua_upvalueindex(2));
2735         /* set (request_st *)r in userdata */
2736         if (-1 == i)
2737             *(request_st **)lua_touserdata(L,lua_upvalueindex(3))=&con->request;
2738         else
2739             *(request_st **)lua_touserdata(L,lua_upvalueindex(3))=con->h2->r[i];
2740         /* step to next index into h2con->r[] */
2741         if ((uint32_t)++i == con->h2->rused)
2742             i = -1;
2743         lua_pushinteger(L, i);
2744         lua_replace(L, lua_upvalueindex(2));
2745     }
2746     else {
2747         /* set (request_st *) in userdata */
2748         *(request_st **)lua_touserdata(L, lua_upvalueindex(3)) = &con->request;
2749     }
2750 
2751     if (-1 == i) {
2752         /* step to next connection */
2753         con = con->next;
2754         lua_pushlightuserdata(L, con);
2755         lua_replace(L, lua_upvalueindex(1));
2756     }
2757 
2758     /* return request object (which references (request_st *)r in userdata) */
2759     lua_pushvalue(L, lua_upvalueindex(4));
2760     return 1;
2761 }
2762 
2763 static int
2764 magnet_irequests (lua_State *L)
2765 {
2766     /* NB: iterator request object *is invalid* outside of iteration
2767      * For efficiency, r is stored in userdata as upvalue to iteration
2768      * and is invalid (and may be cleaned up) outside of iterator loop.
2769      * A C pointer into userdata is stored in iterator request object methods.
2770      * The iterator request object is *reused* for each iteration loop and the
2771      * upvalue to the userdata is changed each iteration to point to next r.
2772      * The iterator request object *must not* be saved for use outside
2773      * the iteration loop.  Extract data that must be saved and store data
2774      * in a persistent object if data is to be used outside iterator loop.
2775      * (Were it desirable to produce a persistent request object for use outside
2776      *  the iteration loop, a future enhancement would be to add a method such
2777      *  as lighty.server.irequests_clone(r) which creates a new request object
2778      *  pointing into a new userdata, and saving that new userdata in the new
2779      *  request object table as '_r_userdata')
2780      * NB: iterator request objects should generally be treated read-only.
2781      * Modifications may in some cases be unsafe and cause lighttpd to crash. */
2782     request_st *r = magnet_get_request(L);
2783     lua_pushlightuserdata(L, r->con->srv->conns);
2784     lua_pushinteger(L, -1);
2785     request_st ** const r_userdata =
2786       (request_st **)lua_newuserdata0(L, sizeof(request_st *));
2787     magnet_request_table(L, r_userdata);
2788     lua_pushcclosure(L, magnet_request_iter, 4);
2789     return 1;
2790 }
2791 
2792 static int
2793 magnet_server_stats_get (lua_State *L)
2794 {
2795     size_t klen;
2796     const char * const k = luaL_checklstring(L, 2, &klen);
2797     const request_st * const r = magnet_get_request(L);
2798     const server * const srv = r->con->srv;
2799     switch (klen) {
2800       case 6:
2801         if (0 == memcmp(k, "uptime", 6)) {
2802             lua_pushinteger(L, (lua_Integer)(log_epoch_secs - srv->startup_ts));
2803             return 1;
2804         }
2805         break;
2806       case 7:
2807         if (0 == memcmp(k, "version", 7)) {
2808             lua_pushlstring(L, BUF_PTR_LEN(srv->default_server_tag));
2809             return 1;
2810         }
2811         break;
2812       case 12:
2813         /*(could calculate from irequests: ++count on remote-addr/port change)*/
2814         if (0 == memcmp(k, "clients_open", 12)) {
2815             lua_pushinteger(L, (lua_Integer)
2816                             (srv->srvconf.max_conns - srv->lim_conns));
2817             return 1;
2818         }
2819         break;
2820       default:
2821         break;
2822     }
2823     return luaL_error(L, "server.stats['%s'] invalid", k);
2824 }
2825 
2826 
2827 __attribute_cold__
2828 static void
2829 magnet_req_header_metatable (lua_State * const L)
2830 {
2831     if (luaL_newmetatable(L, "li.req_header") == 0)           /* (sp += 1) */
2832         return;
2833 
2834     lua_pushcfunction(L, magnet_reqhdr_get);                  /* (sp += 1) */
2835     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2836     lua_pushcfunction(L, magnet_reqhdr_set);                  /* (sp += 1) */
2837     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2838     lua_pushcfunction(L, magnet_reqhdr_pairs);                /* (sp += 1) */
2839     lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
2840     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2841     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2842 }
2843 
2844 __attribute_cold__
2845 static void
2846 magnet_req_attr_metatable (lua_State * const L)
2847 {
2848     if (luaL_newmetatable(L, "li.req_attr") == 0)             /* (sp += 1) */
2849         return;
2850 
2851     lua_pushcfunction(L, magnet_env_get);                     /* (sp += 1) */
2852     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2853     lua_pushcfunction(L, magnet_env_set);                     /* (sp += 1) */
2854     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2855     lua_pushcfunction(L, magnet_env_pairs);                   /* (sp += 1) */
2856     lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
2857     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2858     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2859 }
2860 
2861 __attribute_cold__
2862 static void
2863 magnet_req_item_metatable (lua_State * const L)
2864 {
2865     if (luaL_newmetatable(L, "li.req_item") == 0)             /* (sp += 1) */
2866         return;
2867 
2868     lua_pushcfunction(L, magnet_req_item_get);                /* (sp += 1) */
2869     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2870     lua_pushcfunction(L, magnet_req_item_set);                /* (sp += 1) */
2871     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2872     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2873     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2874 }
2875 
2876 __attribute_cold__
2877 static void
2878 magnet_req_env_metatable (lua_State * const L)
2879 {
2880     if (luaL_newmetatable(L, "li.req_env") == 0)              /* (sp += 1) */
2881         return;
2882 
2883     lua_pushcfunction(L, magnet_envvar_get);                  /* (sp += 1) */
2884     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2885     lua_pushcfunction(L, magnet_envvar_set);                  /* (sp += 1) */
2886     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2887     lua_pushcfunction(L, magnet_envvar_pairs);                /* (sp += 1) */
2888     lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
2889     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2890     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2891 }
2892 
2893 __attribute_cold__
2894 static void
2895 magnet_resp_header_metatable (lua_State * const L)
2896 {
2897     if (luaL_newmetatable(L, "li.resp_header") == 0)          /* (sp += 1) */
2898         return;
2899 
2900     lua_pushcfunction(L, magnet_resphdr_get);                 /* (sp += 1) */
2901     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2902     lua_pushcfunction(L, magnet_resphdr_set);                 /* (sp += 1) */
2903     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2904     lua_pushcfunction(L, magnet_resphdr_pairs);               /* (sp += 1) */
2905     lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
2906     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2907     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2908 }
2909 
2910 __attribute_cold__
2911 static void
2912 magnet_resp_body_metatable (lua_State * const L)
2913 {
2914     if (luaL_newmetatable(L, "li.resp_body") == 0)            /* (sp += 1) */
2915         return;
2916 
2917     lua_pushcfunction(L, magnet_respbody);                    /* (sp += 1) */
2918     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2919     lua_pushcfunction(L, magnet_newindex_readonly);           /* (sp += 1) */
2920     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2921     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2922     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2923 }
2924 
2925 __attribute_cold__
2926 static void
2927 magnet_req_body_metatable (lua_State * const L)
2928 {
2929     if (luaL_newmetatable(L, "li.req_body") == 0)             /* (sp += 1) */
2930         return;
2931 
2932     lua_pushcfunction(L, magnet_reqbody);                     /* (sp += 1) */
2933     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2934     lua_pushcfunction(L, magnet_newindex_readonly);           /* (sp += 1) */
2935     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2936     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2937     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2938 }
2939 
2940 __attribute_cold__
2941 static int magnet_atpanic(lua_State *L) {
2942 	request_st * const r = magnet_get_request(L);
2943 	log_error(r->conf.errh, __FILE__, __LINE__, "(lua-atpanic) %s",
2944 	          lua_isstring(L, 1) ? lua_tostring(L, 1) : "");
2945 	/*longjmp(exceptionjmp, 1);*//*(must be init with setjmp() elsewhere)*/
2946 	return 0;
2947 }
2948 
2949 __attribute_cold__
2950 static int magnet_print(lua_State *L) {
2951 	const_buffer cb = magnet_checkconstbuffer(L, 1);
2952 	request_st * const r = magnet_get_request(L);
2953 	log_error(r->conf.errh, __FILE__, __LINE__, "(lua-print) %s", cb.ptr);
2954 	return 0;
2955 }
2956 
2957 __attribute_cold__
2958 static void
2959 magnet_plugin_stats_table (lua_State * const L)
2960 {
2961     lua_createtable(L, 0, 0); /* {}                              (sp += 1) */
2962     lua_createtable(L, 0, 4); /* metatable for plugin_stats      (sp += 1) */
2963     lua_pushcfunction(L, magnet_plugin_stats_get);            /* (sp += 1) */
2964     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2965     lua_pushcfunction(L, magnet_plugin_stats_set);            /* (sp += 1) */
2966     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2967     lua_pushcfunction(L, magnet_plugin_stats_pairs);          /* (sp += 1) */
2968     lua_setfield(L, -2, "__pairs");                           /* (sp -= 1) */
2969     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2970     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2971     lua_setmetatable(L, -2);                                  /* (sp -= 1) */
2972 }
2973 
2974 __attribute_cold__
2975 static void
2976 magnet_server_stats_table (lua_State * const L)
2977 {
2978     lua_createtable(L, 0, 0); /* {}                              (sp += 1) */
2979     lua_createtable(L, 0, 3); /* metatable for stats             (sp += 1) */
2980     lua_pushcfunction(L, magnet_server_stats_get);            /* (sp += 1) */
2981     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
2982     lua_pushcfunction(L, magnet_newindex_readonly);           /* (sp += 1) */
2983     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
2984     lua_pushboolean(L, 0);                                    /* (sp += 1) */
2985     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
2986     lua_setmetatable(L, -2);                                  /* (sp -= 1) */
2987 }
2988 
2989 __attribute_cold__
2990 static void
2991 magnet_server_table (lua_State * const L)
2992 {
2993     lua_createtable(L, 0, 3); /* {}                              (sp += 1) */
2994     lua_pushcfunction(L, magnet_irequests);                   /* (sp += 1) */
2995     lua_setfield(L, -2, "irequests"); /* iterate over requests   (sp -= 1) */
2996     magnet_plugin_stats_table(L);                             /* (sp += 1) */
2997     lua_setfield(L, -2, "plugin_stats");                      /* (sp -= 1) */
2998     magnet_server_stats_table(L);                             /* (sp += 1) */
2999     lua_setfield(L, -2, "stats");                             /* (sp -= 1) */
3000     lua_createtable(L, 0, 2); /* metatable for server table      (sp += 1) */
3001     lua_pushcfunction(L, magnet_newindex_readonly);           /* (sp += 1) */
3002     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
3003     lua_pushboolean(L, 0);                                    /* (sp += 1) */
3004     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
3005     lua_setmetatable(L, -2); /* tie the metatable to server      (sp -= 1) */
3006 }
3007 
3008 __attribute_cold__
3009 static void
3010 magnet_script_setup_global_state (lua_State * const L)
3011 {
3012     lua_atpanic(L, magnet_atpanic);
3013 
3014     lua_pushglobaltable(L);                                   /* (sp += 1) */
3015     /* override default print() function */
3016     lua_pushcfunction(L, magnet_print);                       /* (sp += 1) */
3017     lua_setfield(L, -2, "print");                             /* (sp -= 1) */
3018   #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
3019     /* override default pairs() function to our __pairs capable version */
3020     lua_getglobal(L, "pairs"); /* push original pairs()          (sp += 1) */
3021     lua_pushcclosure(L, magnet_pairs, 1);            /* (sp -= 1; sp += 1) */
3022     lua_setfield(L, -2, "pairs");                             /* (sp -= 1) */
3023   #endif
3024     lua_pop(L, 1); /* pop global table */                     /* (sp -= 1) */
3025 
3026     magnet_req_header_metatable(L);    /* init for mem locality  (sp += 1) */
3027     magnet_req_attr_metatable(L);      /* init for mem locality  (sp += 1) */
3028     magnet_req_item_metatable(L);      /* init for mem locality  (sp += 1) */
3029     magnet_req_env_metatable(L);       /* init for mem locality  (sp += 1) */
3030     magnet_resp_header_metatable(L);   /* init for mem locality  (sp += 1) */
3031     magnet_resp_body_metatable(L);     /* init for mem locality  (sp += 1) */
3032     magnet_req_body_metatable(L);      /* init for mem locality  (sp += 1) */
3033     magnet_stat_metatable(L);    /* init table for mem locality  (sp += 1) */
3034     magnet_readdir_metatable(L); /* init table for mem locality  (sp += 1) */
3035     lua_pop(L, 9);               /* pop init'd metatables        (sp -= 9) */
3036 }
3037 
3038 __attribute_cold__
3039 static void
3040 magnet_init_lighty_table (lua_State * const L, request_st **rr,
3041                           const int result_ndx)
3042 {
3043     /* lighty table
3044      *
3045      * lighty.r.*                HTTP request object methods
3046      * lighty.c.*                lighttpd C methods callable from lua
3047      * lighty.server.*           lighttpd server object methods
3048      *
3049      * (older interface)
3050      *
3051      * lighty.request[]      HTTP request headers
3052      * lighty.req_env[]      environment variables
3053      * lighty.env[]          lighttpd request metadata,
3054      *                       various url components,
3055      *                       physical file paths;
3056      *                       might contain nil values
3057      *
3058      * lighty.header[]       (script) HTTP response headers
3059      * lighty.content[]      (script) HTTP response body (table of string/file)
3060      *
3061      * lighty.status[]       lighttpd status counters
3062      */
3063 
3064     /*(adjust the preallocation if more entries are added)*/
3065     lua_createtable(L, 0, 9); /* lighty.* (returned on stack)    (sp += 1) */
3066 
3067     magnet_request_table(L, rr); /* lighty.r                     (sp += 1) */
3068     lua_setfield(L, -2, "r"); /* lighty.r = {}                   (sp -= 1) */
3069 
3070     magnet_server_table(L); /* lighty.server                     (sp += 1) */
3071     lua_setfield(L, -2, "server"); /* server = {}                (sp -= 1) */
3072 
3073     /* compatibility with previous mod_magnet interfaces in top of lighty.* */
3074     lua_getfield(L, -1, "r");                                 /* (sp += 1) */
3075     /* alias lighty.request -> lighty.r.req_header */
3076     lua_getfield(L, -1, "req_header");                        /* (sp += 1) */
3077     lua_setfield(L, -3, "request"); /* request = {}              (sp -= 1) */
3078     /* alias lighty.env     -> lighty.r.req_attr */
3079     lua_getfield(L, -1, "req_attr");                          /* (sp += 1) */
3080     lua_setfield(L, -3, "env"); /* env = {}                      (sp -= 1) */
3081     /* alias lighty.req_env -> lighty.r.req_env */
3082     lua_getfield(L, -1, "req_env");                           /* (sp += 1) */
3083     lua_setfield(L, -3, "req_env"); /* req_env = {}              (sp -= 1) */
3084     lua_pop(L, 1);                                            /* (sp -= 1) */
3085 
3086     /* alias lighty.server.stats -> lighty.status */
3087     lua_getfield(L, -1, "server");                            /* (sp += 1) */
3088     lua_getfield(L, -1, "plugin_stats");                      /* (sp += 1) */
3089     lua_setfield(L, -3, "status"); /* status = {}                (sp -= 1) */
3090     lua_pop(L, 1);                                            /* (sp -= 1) */
3091 
3092     lua_pushinteger(L, MAGNET_RESTART_REQUEST);
3093     lua_setfield(L, -2, "RESTART_REQUEST");
3094 
3095     /* alias lighty.c.stat -> lighty.stat */
3096     lua_pushcfunction(L, magnet_stat);                        /* (sp += 1) */
3097     lua_setfield(L, -2, "stat"); /* -1 is the env we want to set (sp -= 1) */
3098 
3099     static const luaL_Reg cmethods[] = {
3100       { "stat",             magnet_stat }
3101      ,{ "time",             magnet_time }
3102      ,{ "hrtime",           magnet_hrtime }
3103      ,{ "rand",             magnet_rand }
3104      ,{ "md",               magnet_md_once   } /* message digest */
3105      ,{ "hmac",             magnet_hmac_once } /* HMAC */
3106      ,{ "digest_eq",        magnet_digest_eq } /* timing-safe eq fixed len */
3107      ,{ "secret_eq",        magnet_secret_eq } /* timing-safe eq variable len */
3108      ,{ "b64urldec",        magnet_b64urldec } /* validate and decode base64url */
3109      ,{ "b64urlenc",        magnet_b64urlenc } /* base64url encode, no padding */
3110      ,{ "b64dec",           magnet_b64stddec } /* validate and decode base64 */
3111      ,{ "b64enc",           magnet_b64stdenc } /* base64 encode, no padding */
3112      ,{ "hexdec",           magnet_hexdec } /* validate and decode hex str */
3113      ,{ "hexenc",           magnet_hexenc } /* uc; lc w/ lua s = s:lower() */
3114      ,{ "xmlenc",           magnet_xmlenc } /* xml-encode/html-encode: <>&'\" */
3115      ,{ "urldec",           magnet_urldec } /* url-decode (path) */
3116      ,{ "urlenc",           magnet_urlenc } /* url-encode (path) */
3117      ,{ "urldec_query",     magnet_urldec_query } /* url-decode query-string */
3118      ,{ "urlenc_query",     magnet_urlenc_query } /* url-encode query-string */
3119      ,{ "urlenc_normalize", magnet_urlenc_normalize }/* url-enc normalization */
3120      ,{ "fspath_simplify",  magnet_fspath_simplify } /* simplify fspath */
3121      ,{ "cookie_tokens",    magnet_cookie_tokens } /* parse cookie tokens */
3122      ,{ "header_tokens",    magnet_header_tokens } /* parse header tokens seq */
3123      ,{ "readdir",          magnet_readdir } /* dir walk */
3124      ,{ "quoteddec",        magnet_quoteddec } /* quoted-string decode */
3125      ,{ "quotedenc",        magnet_quotedenc } /* quoted-string encode */
3126      ,{ "bsdec",            magnet_bsdec } /* backspace-escape decode */
3127      ,{ "bsenc",            magnet_bsenc_default } /* backspace-escape encode */
3128      ,{ "bsenc_json",       magnet_bsenc_json } /* backspace-escape encode json */
3129      ,{ NULL, NULL }
3130     };
3131 
3132     lua_createtable(L, 0, sizeof(cmethods)/sizeof(luaL_Reg)-1);/*(sp += 1) */
3133     luaL_setfuncs(L, cmethods, 0);
3134     lua_createtable(L, 0, 2); /* metatable for c table           (sp += 1) */
3135     lua_pushcfunction(L, magnet_newindex_readonly);           /* (sp += 1) */
3136     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
3137     lua_pushboolean(L, 0);                                    /* (sp += 1) */
3138     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
3139     lua_setmetatable(L, -2); /* tie the metatable to c           (sp -= 1) */
3140     lua_setfield(L, -2, "c"); /* c = {}                          (sp -= 1) */
3141 
3142     /* lighty.* table is read-only;
3143      * provide alternative scratch table for legacy API, historical (mis)use */
3144     lua_createtable(L, 0, 3); /* metatable for lighty table      (sp += 1) */
3145     lua_pushvalue(L, result_ndx);                             /* (sp += 1) */
3146     lua_pushcclosure(L, magnet_lighty_result_get, 1);/* (sp -= 1; sp += 1) */
3147     lua_setfield(L, -2, "__index");                           /* (sp -= 1) */
3148     lua_pushvalue(L, result_ndx);                             /* (sp += 1) */
3149     lua_pushcclosure(L, magnet_lighty_result_set, 1);/* (sp -= 1; sp += 1) */
3150     lua_setfield(L, -2, "__newindex");                        /* (sp -= 1) */
3151     lua_pushboolean(L, 0);                                    /* (sp += 1) */
3152     lua_setfield(L, -2, "__metatable"); /* protect metatable     (sp -= 1) */
3153     lua_setmetatable(L, -2); /* tie the metatable to lighty      (sp -= 1) */
3154 
3155     /* lighty table (returned on stack) */
3156 }
3157 
3158 static void magnet_clear_table(lua_State * const L, int ndx) {
3159     /*(avoid lua_absindex() func call since expecting empty result tables for
3160      * legacy interfaces to lighty.header and .content, though not script-env)*/
3161     /*ndx = lua_absindex(ndx);*//*(lua 5.2+)*/
3162     if (ndx < 0) --ndx;
3163     for (lua_pushnil(L); lua_next(L, ndx); ) {
3164         lua_pop(L, 1);
3165         lua_pushvalue(L, -1);
3166         lua_pushnil(L);
3167         lua_rawset(L, ndx < 0 ? ndx - 2 : ndx);
3168     }
3169 }
3170 
3171 __attribute_cold__
3172 static int magnet_traceback(lua_State *L) {
3173 	if (!lua_isstring(L, 1))  /* 'message' not a string? */
3174 		return 1;  /* keep it intact */
3175 	if (lua_getglobal_and_type(L, "debug") != LUA_TTABLE) {
3176 		lua_pop(L, 1);
3177 		return 1;
3178 	}
3179 	if (lua_getfield_and_type(L, -1, "traceback") != LUA_TFUNCTION) {
3180 		lua_pop(L, 2);
3181 		return 1;
3182 	}
3183 	lua_pushvalue(L, 1);  /* pass error message */
3184 	lua_pushinteger(L, 2);  /* skip this function and traceback */
3185 	lua_call(L, 2, 1);  /* call debug.traceback */
3186 	return 1;
3187 }
3188 
3189 __attribute_cold__
3190 __attribute_noinline__
3191 static int
3192 magnet_script_setup (request_st * const r, plugin_data * const p, script * const sc)
3193 {
3194 	lua_State * const L = sc->L;
3195 	const int func_ndx = 1;
3196 	if (lua_isfunction(L, func_ndx)) {
3197 		/* initial setup for global lua_State */
3198 		magnet_script_setup_global_state(L);
3199 		/*force_assert(lua_gettop(L) == 1);*/ /* errfunc_ndx = 2 */
3200 		lua_pushcfunction(L, magnet_traceback);/*errfunc*//* (sp += 1) */
3201 		/* create empty table for script environment (reused)
3202 		 *   setmetatable({}, {__index = _G})
3203 		 *     if a function symbol is not defined in our env,
3204 		 *     __index will lookup in the global env. */
3205 		lua_createtable(L, 0, 1); /* env_ndx = 3 */       /* (sp += 1) */
3206 		magnet_mainenv_metatable(L);                      /* (sp += 1) */
3207 		lua_setmetatable(L, -2);                          /* (sp -= 1) */
3208 		/* set script env in first upvalue (_ENV upvalue) for func */
3209 		lua_pushvalue(L, -1);                             /* (sp += 1) */
3210 		magnet_setfenv_mainfn(L, func_ndx);               /* (sp -= 1) */
3211 		/* result table (modifiable) (for mod_magnet legacy API) */
3212 		/* (prefer lighty.r.resp_header(), lighty.r.resp_body()) */
3213 		lua_createtable(L, 0, 2); /* (result_ndx = 4) */  /* (sp += 1) */
3214 		const int result_ndx = 4;
3215 		/* shared userdata (ud_ndx = 5) */
3216 		request_st ** const r_userdata =                  /* (sp += 1) */
3217 		  (request_st **)lua_newuserdata0(L, sizeof(request_st *));
3218 		/* insert lighty table (lighty_table_ndx = 6) */
3219 		magnet_init_lighty_table(L,r_userdata,result_ndx);/* (sp += 1) */
3220 		return 1;
3221 	}
3222 	else {
3223 		if (lua_isstring(L, func_ndx))
3224 			log_error(r->conf.errh, __FILE__, __LINE__,
3225 			  "loading script %s failed: %s", sc->name.ptr,
3226 			  lua_tostring(L, func_ndx));
3227 		else /*(lua_gettop(L) == 0)*/
3228 			log_perror(r->conf.errh, __FILE__, __LINE__,
3229 			  "loading script %s failed", sc->name.ptr);
3230 		lua_settop(L, 0);
3231 
3232 		if (p->conf.stage >= 0) { /*(before response_start)*/
3233 			r->http_status = 500;
3234 			r->handler_module = NULL;
3235 		}
3236 
3237 		return 0;
3238 	}
3239 }
3240 
3241 static handler_t
3242 magnet_attract (request_st * const r, plugin_data * const p, script * const sc)
3243 {
3244 	lua_State * const L = sc->L;
3245 	const int func_ndx = 1;
3246 	const int errfunc_ndx = 2;
3247 	const int env_ndx = 3;
3248 	const int result_ndx = 4;
3249 	const int ud_ndx = 5;
3250 	const int lighty_table_ndx = 6;
3251 
3252 	if (__builtin_expect( (lua_gettop(L) != lighty_table_ndx), 0)) {
3253 		if (!magnet_script_setup(r, p, sc))
3254 			return HANDLER_FINISHED;
3255 	}
3256 
3257 	/* set r in global state for L for out-of-band access to r
3258 	 * (r-conf.errh and others) */
3259         magnet_set_request(L, r);
3260 	/* set r in userdata shared by lighty.r object methods */
3261 	*(request_st **)lua_touserdata(L, ud_ndx) = r;
3262 
3263 	/* add lighty table to script-env
3264 	 * script-env is cleared at the end of each script run */
3265 	lua_pushvalue(L, lighty_table_ndx);                       /* (sp += 1) */
3266 	lua_setfield(L, env_ndx, "lighty"); /* lighty.*              (sp -= 1) */
3267 
3268 	/* push script func; pcall will consume the func value */
3269 	lua_pushvalue(L, func_ndx);                               /* (sp += 1) */
3270 	int ret = lua_pcall(L, 0, 1, errfunc_ndx);       /* (sp -= 1; sp += 1) */
3271 
3272 	handler_t result = HANDLER_GO_ON;
3273 	if (0 != ret) {
3274 			size_t errlen;
3275 			const char * const err = lua_tolstring(L, -1, &errlen);
3276 			log_error_multiline(r->conf.errh, __FILE__, __LINE__,
3277 			                    err, errlen, "lua: ");
3278 			/*lua_pop(L, 1);*/ /* pop error msg */ /* defer to later */
3279 			if (p->conf.stage >= 0) { /*(before response_start)*/
3280 				r->http_status = 500;
3281 				r->handler_module = NULL;
3282 				result = HANDLER_FINISHED;
3283 			}
3284 	}
3285 	else do {
3286 		/*(luaL_optinteger might raise error, which we want to avoid)*/
3287 		/*lua_return_value = (int) luaL_optinteger(L, -1, -1);*/
3288 		int isnum = 1;
3289 		int lua_return_value = lua_isnil(L, -1)
3290 		  ? 0
3291 		  : (int) lua_tointegerx(L, -1, &isnum);
3292 		if (!isnum) {
3293 			log_error(r->conf.errh, __FILE__, __LINE__,
3294 			  "lua_pcall(): unexpected non-integer return type: %s",
3295 			  luaL_typename(L, -1));
3296 			break;
3297 		}
3298 		/*lua_pop(L, 1);*/ /* pop return value */ /* defer to later */
3299 		/*force_assert(lua_istable(sc->L, -1));*/
3300 
3301 		if (lua_getfield_and_type(L, result_ndx, "header") == LUA_TTABLE) {
3302 			magnet_copy_response_header(L, r); /* deprecated legacy API */
3303 		}
3304 		/*lua_pop(L, 1);*//* defer to later */
3305 
3306 		if (lua_return_value >= 200) {
3307 			r->http_status = lua_return_value;
3308 			/*(note: body may already have been set via lighty.r.resp_body.*)*/
3309 			if (lua_getfield_and_type(L, result_ndx, "content") == LUA_TTABLE) {
3310 				magnet_attach_content(L, r); /* deprecated legacy API */
3311 			}
3312 			/*lua_pop(L, 1);*//* defer to later */
3313 			if (!chunkqueue_is_empty(&r->write_queue)) {
3314 				r->handler_module = p->self;
3315 			}
3316 			r->resp_body_finished = 1;
3317 			result = HANDLER_FINISHED;
3318 		} else if (lua_return_value >= 100) {
3319 			/*(skip for response-start; send response as-is w/ added headers)*/
3320 			if (p->conf.stage < 0) break;
3321 			/*(custom lua code should not return 101 Switching Protocols)*/
3322 			r->http_status = lua_return_value;
3323 			result = http_response_send_1xx(r)
3324 			  ? HANDLER_GO_ON
3325 			  : HANDLER_ERROR;
3326 		} else if (MAGNET_RESTART_REQUEST == lua_return_value) {
3327 			/*(could detect restart loops in same way as is done in mod_rewrite,
3328 			 * but using r->env means that we do not need to reset plugin state
3329 			 * at end of every request, as is done in mod_rewrite.  mod_rewrite
3330 			 * always restarts the request processing (if request is rewritten),
3331 			 * whereas mod_magnet can be used in many other ways)*/
3332 			buffer *vb =
3333 			  http_header_env_get(r, CONST_STR_LEN("_L_MAGNET_RESTART"));
3334 			if (NULL == vb) {
3335 				vb =
3336 				  http_header_env_set_ptr(r,CONST_STR_LEN("_L_MAGNET_RESTART"));
3337 				buffer_append_char(vb, '0');
3338 			}
3339 			result = HANDLER_COMEBACK;
3340 			if (++*vb->ptr-'0' >= 10) {
3341 				log_error(r->conf.errh, __FILE__, __LINE__,
3342 				  "too many request restarts (infinite loop?) for %s",
3343 				  sc->name.ptr);
3344 				result = HANDLER_ERROR;
3345 			}
3346 		}
3347 	} while (0);
3348 	magnet_clear_table(L, env_ndx);
3349 	magnet_clear_table(L, result_ndx);
3350 	/* reset stack to reuse stack up to lighty table; pop the excess */
3351 	lua_settop(L, lighty_table_ndx); /*(handle deferred lua_pop()s)*/
3352 	return result;
3353 }
3354 
3355 static handler_t magnet_attract_array(request_st * const r, plugin_data * const p, int stage) {
3356 	mod_magnet_patch_config(r, p);
3357 	p->conf.stage = stage;
3358 
3359 	script * const *scripts;
3360 	switch (stage) {
3361 	  case  1: scripts = p->conf.url_raw; break;
3362 	  case  0: scripts = p->conf.physical_path; break;
3363 	  case -1: scripts = p->conf.response_start; break;
3364 	  default: scripts = NULL; break;
3365 	}
3366 	if (NULL == scripts) return HANDLER_GO_ON; /* no scripts set */
3367 
3368 	/*(always check at least mtime and size to trigger script reload)*/
3369 	const int etag_flags = r->conf.etag_flags | ETAG_USE_MTIME | ETAG_USE_SIZE;
3370 	int req_env_inited = 0;
3371 
3372 	/* execute scripts sequentially while HANDLER_GO_ON */
3373 	handler_t rc = HANDLER_GO_ON;
3374 	do {
3375 		script_cache_check_script(*scripts, etag_flags);
3376 		if ((*scripts)->req_env_init && !req_env_inited) {
3377 			/*(request env init is deferred until needed)*/
3378 			req_env_inited = 1;
3379 			r->con->srv->request_env(r);
3380 		}
3381 		rc = magnet_attract(r, p, *scripts);
3382 	} while (rc == HANDLER_GO_ON && *++scripts);
3383 
3384 	if (r->error_handler_saved_status) {
3385 		/* retrieve (possibly modified) REDIRECT_STATUS and store as number */
3386 		int x;
3387 		const buffer * const vb = http_header_env_get(r, CONST_STR_LEN("REDIRECT_STATUS"));
3388 		if (vb && (x = http_header_str_to_code(vb->ptr)) != -1)
3389 			r->error_handler_saved_status =
3390 			  r->error_handler_saved_status > 0 ? (int)x : -(int)x;
3391 	}
3392 
3393 	return rc;
3394 }
3395 
3396 URIHANDLER_FUNC(mod_magnet_uri_handler) {
3397 	return magnet_attract_array(r, p_d, 1);
3398 }
3399 
3400 URIHANDLER_FUNC(mod_magnet_physical) {
3401 	return magnet_attract_array(r, p_d, 0);
3402 }
3403 
3404 URIHANDLER_FUNC(mod_magnet_response_start) {
3405 	return magnet_attract_array(r, p_d, -1);
3406 }
3407 
3408 SUBREQUEST_FUNC(mod_magnet_handle_subrequest) {
3409     /* read entire request body from network and then restart request */
3410     UNUSED(p_d);
3411 
3412     if (r->state == CON_STATE_READ_POST) {
3413         /*(streaming flags were removed when magnet installed this handler)*/
3414         handler_t rc = r->con->reqbody_read(r);
3415         if (rc != HANDLER_GO_ON) return rc;
3416         if (r->state == CON_STATE_READ_POST)
3417             return HANDLER_WAIT_FOR_EVENT;
3418     }
3419 
3420     buffer_clear(&r->physical.path);
3421     r->handler_module = NULL;
3422     return HANDLER_COMEBACK;
3423 }
3424 
3425 
3426 int mod_magnet_plugin_init(plugin *p);
3427 int mod_magnet_plugin_init(plugin *p) {
3428 	p->version     = LIGHTTPD_VERSION_ID;
3429 	p->name        = "magnet";
3430 
3431 	p->init        = mod_magnet_init;
3432 	p->handle_uri_clean  = mod_magnet_uri_handler;
3433 	p->handle_physical   = mod_magnet_physical;
3434 	p->handle_response_start = mod_magnet_response_start;
3435 	p->handle_subrequest = mod_magnet_handle_subrequest;
3436 	p->set_defaults  = mod_magnet_set_defaults;
3437 	p->cleanup     = mod_magnet_free;
3438 
3439 	return 0;
3440 }
3441