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