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