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