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