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