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