1 #include "first.h" 2 3 #include "base.h" 4 #include "log.h" 5 #include "buffer.h" 6 #include "http_chunk.h" 7 #include "http_header.h" 8 9 #include "plugin.h" 10 11 #include "mod_magnet_cache.h" 12 #include "sock_addr.h" 13 #include "stat_cache.h" 14 #include "status_counter.h" 15 #include "etag.h" 16 17 #include <stdlib.h> 18 #include <string.h> 19 #include <setjmp.h> 20 21 #include <lua.h> 22 #include <lauxlib.h> 23 24 #define LUA_RIDX_LIGHTTPD_REQUEST "lighty.request" 25 26 #define MAGNET_RESTART_REQUEST 99 27 28 /* plugin config for all request/connections */ 29 30 static jmp_buf exceptionjmp; 31 32 typedef struct { 33 const array *url_raw; 34 const array *physical_path; 35 } plugin_config; 36 37 typedef struct { 38 PLUGIN_DATA; 39 plugin_config defaults; 40 plugin_config conf; 41 42 script_cache cache; 43 } plugin_data; 44 45 INIT_FUNC(mod_magnet_init) { 46 return calloc(1, sizeof(plugin_data)); 47 } 48 49 FREE_FUNC(mod_magnet_free) { 50 plugin_data *p = p_d; 51 script_cache_free_data(&p->cache); 52 } 53 54 static void mod_magnet_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 55 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 56 case 0: /* magnet.attract-raw-url-to */ 57 pconf->url_raw = cpv->v.a; 58 break; 59 case 1: /* magnet.attract-physical-path-to */ 60 pconf->physical_path = cpv->v.a; 61 break; 62 default:/* should not happen */ 63 return; 64 } 65 } 66 67 static void mod_magnet_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 68 do { 69 mod_magnet_merge_config_cpv(pconf, cpv); 70 } while ((++cpv)->k_id != -1); 71 } 72 73 static void mod_magnet_patch_config(request_st * const r, plugin_data * const p) { 74 p->conf = p->defaults; /* copy small struct instead of memcpy() */ 75 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/ 76 for (int i = 1, used = p->nconfig; i < used; ++i) { 77 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 78 mod_magnet_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]); 79 } 80 } 81 82 SETDEFAULTS_FUNC(mod_magnet_set_defaults) { 83 static const config_plugin_keys_t cpk[] = { 84 { CONST_STR_LEN("magnet.attract-raw-url-to"), 85 T_CONFIG_ARRAY_VLIST, 86 T_CONFIG_SCOPE_CONNECTION } 87 ,{ CONST_STR_LEN("magnet.attract-physical-path-to"), 88 T_CONFIG_ARRAY_VLIST, 89 T_CONFIG_SCOPE_CONNECTION } 90 ,{ NULL, 0, 91 T_CONFIG_UNSET, 92 T_CONFIG_SCOPE_UNSET } 93 }; 94 95 plugin_data * const p = p_d; 96 if (!config_plugin_values_init(srv, p, cpk, "mod_magnet")) 97 return HANDLER_ERROR; 98 99 /* process and validate config directives 100 * (init i to 0 if global context; to 1 to skip empty global context) */ 101 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 102 const config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 103 for (; -1 != cpv->k_id; ++cpv) { 104 switch (cpv->k_id) { 105 case 0: /* magnet.attract-raw-url-to */ 106 case 1: /* magnet.attract-physical-path-to */ 107 for (uint32_t j = 0; j < cpv->v.a->used; ++j) { 108 data_string *ds = (data_string *)cpv->v.a->data[j]; 109 if (buffer_string_is_empty(&ds->value)) { 110 log_error(srv->errh, __FILE__, __LINE__, 111 "unexpected (blank) value for %s; " 112 "expected list of \"scriptpath\"", cpk[cpv->k_id].k); 113 return HANDLER_ERROR; 114 } 115 } 116 break; 117 default:/* should not happen */ 118 break; 119 } 120 } 121 } 122 123 /* initialize p->defaults from global config context */ 124 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 125 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 126 if (-1 != cpv->k_id) 127 mod_magnet_merge_config(&p->defaults, cpv); 128 } 129 130 return HANDLER_GO_ON; 131 } 132 133 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502 134 /* lua5.1 backward compat definition */ 135 static void lua_pushglobaltable(lua_State *L) { /* (-0, +1, -) */ 136 lua_pushvalue(L, LUA_GLOBALSINDEX); 137 } 138 #endif 139 140 static void magnet_setfenv_mainfn(lua_State *L, int funcIndex) { /* (-1, 0, -) */ 141 #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 502 142 /* set "_ENV" upvalue, which should be the first upvalue of a "main" lua 143 * function if it uses any global names 144 */ 145 146 const char* first_upvalue_name = lua_getupvalue(L, funcIndex, 1); 147 if (NULL == first_upvalue_name) return; /* doesn't have any upvalues */ 148 lua_pop(L, 1); /* only need the name of the upvalue, not the value */ 149 150 if (0 != strcmp(first_upvalue_name, "_ENV")) return; 151 152 if (NULL == lua_setupvalue(L, funcIndex, 1)) { 153 /* pop value if lua_setupvalue didn't set the (not existing) upvalue */ 154 lua_pop(L, 1); 155 } 156 #else 157 lua_setfenv(L, funcIndex); 158 #endif 159 } 160 161 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502 162 /* lua 5.2 already supports __pairs */ 163 164 /* See http://lua-users.org/wiki/GeneralizedPairsAndIpairs for implementation details. 165 * Override the default pairs() function to allow us to use a __pairs metakey 166 */ 167 static int magnet_pairs(lua_State *L) { 168 luaL_checkany(L, 1); /* "self" */ 169 170 if (luaL_getmetafield(L, 1, "__pairs")) { 171 /* call __pairs(self) */ 172 lua_pushvalue(L, 1); 173 lua_call(L, 1, 3); 174 } else { 175 /* call <original-pairs-method>(self) */ 176 lua_pushvalue(L, lua_upvalueindex(1)); 177 lua_pushvalue(L, 1); 178 lua_call(L, 1, 3); 179 } 180 return 3; 181 } 182 #endif 183 184 static void magnet_push_buffer(lua_State *L, const buffer *b) { 185 if (!buffer_is_empty(b)) 186 lua_pushlstring(L, CONST_BUF_LEN(b)); 187 else 188 lua_pushnil(L); 189 } 190 191 #if 0 192 static int magnet_array_get_element(lua_State *L, const array *a) { 193 /* __index: param 1 is the (empty) table the value was not found in */ 194 size_t klen; 195 const char * const k = luaL_checklstring(L, 2, &klen); 196 const data_string * const ds = (const data_string *) 197 array_get_element_klen(a, k, klen); 198 magnet_push_buffer(L, NULL != ds ? &ds->value : NULL); 199 return 1; 200 } 201 #endif 202 203 /* Define a function that will iterate over an array* (in upval 1) using current position (upval 2) */ 204 static int magnet_array_next(lua_State *L) { 205 data_unset *du; 206 data_string *ds; 207 data_integer *di; 208 209 size_t pos = lua_tointeger(L, lua_upvalueindex(1)); 210 array *a = lua_touserdata(L, lua_upvalueindex(2)); 211 212 lua_settop(L, 0); 213 214 if (pos >= a->used) return 0; 215 if (NULL != (du = a->data[pos])) { 216 lua_pushlstring(L, CONST_BUF_LEN(&du->key)); 217 switch (du->type) { 218 case TYPE_STRING: 219 ds = (data_string *)du; 220 magnet_push_buffer(L, &ds->value); 221 break; 222 case TYPE_INTEGER: 223 di = (data_integer *)du; 224 lua_pushinteger(L, di->value); 225 break; 226 default: 227 lua_pushnil(L); 228 break; 229 } 230 231 /* Update our positional upval to reflect our new current position */ 232 pos++; 233 lua_pushinteger(L, pos); 234 lua_replace(L, lua_upvalueindex(1)); 235 236 /* Returning 2 items on the stack (key, value) */ 237 return 2; 238 } 239 return 0; 240 } 241 242 /* Create the closure necessary to iterate over the array *a with the above function */ 243 static int magnet_array_pairs(lua_State *L, array *a) { 244 lua_pushinteger(L, 0); /* Push our current pos (the start) into upval 1 */ 245 lua_pushlightuserdata(L, a); /* Push our array *a into upval 2 */ 246 lua_pushcclosure(L, magnet_array_next, 2); /* Push our new closure with 2 upvals */ 247 return 1; 248 } 249 250 static request_st * magnet_get_request(lua_State *L) { 251 lua_getfield(L, LUA_REGISTRYINDEX, LUA_RIDX_LIGHTTPD_REQUEST); 252 request_st * const r = lua_touserdata(L, -1); 253 lua_pop(L, 1); 254 return r; 255 } 256 257 typedef struct { 258 const char *ptr; 259 size_t len; 260 } const_buffer; 261 262 static const_buffer magnet_checkconstbuffer(lua_State *L, int index) { 263 const_buffer cb; 264 cb.ptr = luaL_checklstring(L, index, &cb.len); 265 return cb; 266 } 267 268 static buffer* magnet_checkbuffer(lua_State *L, int index) { 269 const_buffer cb = magnet_checkconstbuffer(L, index); 270 buffer *b = buffer_init(); 271 buffer_copy_string_len(b, cb.ptr, cb.len); 272 return b; 273 } 274 275 static int magnet_print(lua_State *L) { 276 const_buffer cb = magnet_checkconstbuffer(L, 1); 277 request_st * const r = magnet_get_request(L); 278 log_error(r->conf.errh, __FILE__, __LINE__, "(lua-print) %s", cb.ptr); 279 return 0; 280 } 281 282 static int magnet_stat(lua_State *L) { 283 buffer * const sb = magnet_checkbuffer(L, 1); 284 stat_cache_entry * const sce = stat_cache_get_entry(sb); 285 buffer_free(sb); 286 if (NULL == sce) { 287 lua_pushnil(L); 288 return 1; 289 } 290 291 lua_newtable(L); // return value 292 293 lua_pushboolean(L, S_ISREG(sce->st.st_mode)); 294 lua_setfield(L, -2, "is_file"); 295 296 lua_pushboolean(L, S_ISDIR(sce->st.st_mode)); 297 lua_setfield(L, -2, "is_dir"); 298 299 lua_pushboolean(L, S_ISCHR(sce->st.st_mode)); 300 lua_setfield(L, -2, "is_char"); 301 302 lua_pushboolean(L, S_ISBLK(sce->st.st_mode)); 303 lua_setfield(L, -2, "is_block"); 304 305 lua_pushboolean(L, S_ISSOCK(sce->st.st_mode)); 306 lua_setfield(L, -2, "is_socket"); 307 308 lua_pushboolean(L, S_ISLNK(sce->st.st_mode)); 309 lua_setfield(L, -2, "is_link"); 310 311 lua_pushboolean(L, S_ISFIFO(sce->st.st_mode)); 312 lua_setfield(L, -2, "is_fifo"); 313 314 lua_pushinteger(L, sce->st.st_mtime); 315 lua_setfield(L, -2, "st_mtime"); 316 317 lua_pushinteger(L, sce->st.st_ctime); 318 lua_setfield(L, -2, "st_ctime"); 319 320 lua_pushinteger(L, sce->st.st_atime); 321 lua_setfield(L, -2, "st_atime"); 322 323 lua_pushinteger(L, sce->st.st_uid); 324 lua_setfield(L, -2, "st_uid"); 325 326 lua_pushinteger(L, sce->st.st_gid); 327 lua_setfield(L, -2, "st_gid"); 328 329 lua_pushinteger(L, sce->st.st_size); 330 lua_setfield(L, -2, "st_size"); 331 332 lua_pushinteger(L, sce->st.st_ino); 333 lua_setfield(L, -2, "st_ino"); 334 335 request_st * const r = magnet_get_request(L); 336 const buffer *etag = stat_cache_etag_get(sce, r->conf.etag_flags); 337 if (!buffer_string_is_empty(etag)) { 338 /* we have to mutate the etag */ 339 buffer * const tb = r->tmp_buf; 340 etag_mutate(tb, etag); 341 lua_pushlstring(L, CONST_BUF_LEN(tb)); 342 } else { 343 lua_pushnil(L); 344 } 345 lua_setfield(L, -2, "etag"); 346 347 const buffer *content_type = stat_cache_content_type_get(sce, r); 348 if (!buffer_string_is_empty(content_type)) { 349 lua_pushlstring(L, CONST_BUF_LEN(content_type)); 350 } else { 351 lua_pushnil(L); 352 } 353 lua_setfield(L, -2, "content-type"); 354 355 return 1; 356 } 357 358 359 static int magnet_atpanic(lua_State *L) { 360 const_buffer cb = magnet_checkconstbuffer(L, 1); 361 request_st * const r = magnet_get_request(L); 362 log_error(r->conf.errh, __FILE__, __LINE__, "(lua-atpanic) %s", cb.ptr); 363 longjmp(exceptionjmp, 1); 364 } 365 366 static int magnet_reqhdr_get(lua_State *L) { 367 /* __index: param 1 is the (empty) table the value was not found in */ 368 size_t klen; 369 const char * const k = luaL_checklstring(L, 2, &klen); 370 request_st * const r = magnet_get_request(L); 371 const buffer * const vb = 372 http_header_request_get(r, HTTP_HEADER_UNSPECIFIED, k, klen); 373 magnet_push_buffer(L, NULL != vb ? vb : NULL); 374 return 1; 375 } 376 377 static int magnet_reqhdr_pairs(lua_State *L) { 378 request_st * const r = magnet_get_request(L); 379 return magnet_array_pairs(L, &r->rqst_headers); 380 } 381 382 static int magnet_status_get(lua_State *L) { 383 /* __index: param 1 is the (empty) table the value was not found in */ 384 const_buffer key = magnet_checkconstbuffer(L, 2); 385 int *i = status_counter_get_counter(key.ptr, key.len); 386 lua_pushinteger(L, (lua_Integer)*i); 387 388 return 1; 389 } 390 391 static int magnet_status_set(lua_State *L) { 392 /* __newindex: param 1 is the (empty) table the value is supposed to be set in */ 393 const_buffer key = magnet_checkconstbuffer(L, 2); 394 int counter = (int) luaL_checkinteger(L, 3); 395 396 status_counter_set(key.ptr, key.len, counter); 397 398 return 0; 399 } 400 401 static int magnet_status_pairs(lua_State *L) { 402 return magnet_array_pairs(L, &plugin_stats); 403 } 404 405 typedef struct { 406 const char *name; 407 enum { 408 MAGNET_ENV_UNSET, 409 410 MAGNET_ENV_PHYSICAL_PATH, 411 MAGNET_ENV_PHYSICAL_REL_PATH, 412 MAGNET_ENV_PHYSICAL_DOC_ROOT, 413 MAGNET_ENV_PHYSICAL_BASEDIR, 414 415 MAGNET_ENV_URI_PATH, 416 MAGNET_ENV_URI_PATH_RAW, 417 MAGNET_ENV_URI_SCHEME, 418 MAGNET_ENV_URI_AUTHORITY, 419 MAGNET_ENV_URI_QUERY, 420 421 MAGNET_ENV_REQUEST_METHOD, 422 MAGNET_ENV_REQUEST_URI, 423 MAGNET_ENV_REQUEST_ORIG_URI, 424 MAGNET_ENV_REQUEST_PATH_INFO, 425 MAGNET_ENV_REQUEST_REMOTE_IP, 426 MAGNET_ENV_REQUEST_SERVER_ADDR, 427 MAGNET_ENV_REQUEST_PROTOCOL 428 } type; 429 } magnet_env_t; 430 431 static const magnet_env_t magnet_env[] = { 432 { "physical.path", MAGNET_ENV_PHYSICAL_PATH }, 433 { "physical.rel-path", MAGNET_ENV_PHYSICAL_REL_PATH }, 434 { "physical.doc-root", MAGNET_ENV_PHYSICAL_DOC_ROOT }, 435 { "physical.basedir", MAGNET_ENV_PHYSICAL_BASEDIR }, 436 437 { "uri.path", MAGNET_ENV_URI_PATH }, 438 { "uri.path-raw", MAGNET_ENV_URI_PATH_RAW }, 439 { "uri.scheme", MAGNET_ENV_URI_SCHEME }, 440 { "uri.authority", MAGNET_ENV_URI_AUTHORITY }, 441 { "uri.query", MAGNET_ENV_URI_QUERY }, 442 443 { "request.method", MAGNET_ENV_REQUEST_METHOD }, 444 { "request.uri", MAGNET_ENV_REQUEST_URI }, 445 { "request.orig-uri", MAGNET_ENV_REQUEST_ORIG_URI }, 446 { "request.path-info", MAGNET_ENV_REQUEST_PATH_INFO }, 447 { "request.remote-ip", MAGNET_ENV_REQUEST_REMOTE_IP }, 448 { "request.remote-addr", MAGNET_ENV_REQUEST_REMOTE_IP }, 449 { "request.server-addr", MAGNET_ENV_REQUEST_SERVER_ADDR }, 450 { "request.protocol", MAGNET_ENV_REQUEST_PROTOCOL }, 451 452 { NULL, MAGNET_ENV_UNSET } 453 }; 454 455 static buffer *magnet_env_get_buffer_by_id(request_st * const r, int id) { 456 buffer *dest = NULL; 457 458 /** 459 * map all internal variables to lua 460 * 461 */ 462 463 switch (id) { 464 case MAGNET_ENV_PHYSICAL_PATH: dest = &r->physical.path; break; 465 case MAGNET_ENV_PHYSICAL_REL_PATH: dest = &r->physical.rel_path; break; 466 case MAGNET_ENV_PHYSICAL_DOC_ROOT: dest = &r->physical.doc_root; break; 467 case MAGNET_ENV_PHYSICAL_BASEDIR: dest = &r->physical.basedir; break; 468 469 case MAGNET_ENV_URI_PATH: dest = &r->uri.path; break; 470 case MAGNET_ENV_URI_PATH_RAW: 471 { 472 dest = r->tmp_buf; 473 buffer_clear(dest); 474 uint32_t len = buffer_string_length(&r->target); 475 char *qmark = memchr(r->target.ptr, '?', len); 476 buffer_copy_string_len(dest, r->target.ptr, qmark ? (uint32_t)(qmark - r->target.ptr) : len); 477 break; 478 } 479 case MAGNET_ENV_URI_SCHEME: dest = &r->uri.scheme; break; 480 case MAGNET_ENV_URI_AUTHORITY: dest = &r->uri.authority; break; 481 case MAGNET_ENV_URI_QUERY: dest = &r->uri.query; break; 482 483 case MAGNET_ENV_REQUEST_METHOD: 484 dest = r->tmp_buf; 485 buffer_clear(dest); 486 http_method_append(dest, r->http_method); 487 break; 488 case MAGNET_ENV_REQUEST_URI: dest = &r->target; break; 489 case MAGNET_ENV_REQUEST_ORIG_URI: dest = &r->target_orig; break; 490 case MAGNET_ENV_REQUEST_PATH_INFO: dest = &r->pathinfo; break; 491 case MAGNET_ENV_REQUEST_REMOTE_IP: dest = r->con->dst_addr_buf; break; 492 case MAGNET_ENV_REQUEST_SERVER_ADDR: 493 { 494 const server_socket * const srv_socket = r->con->srv_socket; 495 dest = r->tmp_buf; 496 buffer_clear(dest); 497 switch (srv_socket->addr.plain.sa_family) { 498 case AF_INET: 499 case AF_INET6: 500 if (sock_addr_is_addr_wildcard(&srv_socket->addr)) { 501 sock_addr addrbuf; 502 socklen_t addrlen = sizeof(addrbuf); 503 const int fd = r->con->fd; 504 if (0 == getsockname(fd,(struct sockaddr *)&addrbuf,&addrlen)) { 505 char buf[INET6_ADDRSTRLEN + 1]; 506 const char *s = sock_addr_inet_ntop(&addrbuf, buf, sizeof(buf)-1); 507 if (NULL != s) 508 buffer_copy_string_len(dest, s, strlen(s)); 509 } 510 } 511 else { 512 buffer_copy_buffer(dest, srv_socket->srv_token); 513 if (dest->ptr[0] != '[' || dest->ptr[buffer_string_length(dest)-1] != ']') { 514 char *s = strrchr(dest->ptr, ':'); 515 if (s != NULL) /* local IP without port */ 516 buffer_string_set_length(dest, s - dest->ptr); 517 } 518 } 519 break; 520 default: 521 break; 522 } 523 break; 524 } 525 case MAGNET_ENV_REQUEST_PROTOCOL: 526 dest = r->tmp_buf; 527 buffer_copy_string(dest, get_http_version_name(r->http_version)); 528 break; 529 530 case MAGNET_ENV_UNSET: break; 531 } 532 533 return dest; 534 } 535 536 static int magnet_env_get_id(const char * const key) { 537 for (int i = 0; magnet_env[i].name; ++i) { 538 if (0 == strcmp(key, magnet_env[i].name)) 539 return magnet_env[i].type; 540 } 541 return MAGNET_ENV_UNSET; 542 } 543 544 static buffer *magnet_env_get_buffer(request_st * const r, const char * const key) { 545 return magnet_env_get_buffer_by_id(r, magnet_env_get_id(key)); 546 } 547 548 static int magnet_env_get(lua_State *L) { 549 /* __index: param 1 is the (empty) table the value was not found in */ 550 const char *key = luaL_checkstring(L, 2); 551 request_st * const r = magnet_get_request(L); 552 magnet_push_buffer(L, magnet_env_get_buffer(r, key)); 553 return 1; 554 } 555 556 static int magnet_env_set(lua_State *L) { 557 /* __newindex: param 1 is the (empty) table the value is supposed to be set in */ 558 const char * const key = luaL_checkstring(L, 2); 559 luaL_checkany(L, 3); /* nil or a string */ 560 561 request_st * const r = magnet_get_request(L); 562 const int env_id = magnet_env_get_id(key); 563 564 if (env_id == MAGNET_ENV_URI_PATH_RAW) { 565 /* modify uri-path of r->target; preserve query-part, if present */ 566 /* XXX: should we require that resulting path begin with '/' or %2F ? */ 567 const uint32_t len = buffer_string_length(&r->target); 568 const char * const qmark = memchr(r->target.ptr, '?', len); 569 const_buffer val = { NULL, 0 }; 570 if (!lua_isnil(L, 3)) 571 val = magnet_checkconstbuffer(L, 3); 572 if (NULL != qmark) 573 buffer_copy_string_len(r->tmp_buf, qmark, 574 len - (uint32_t)(qmark - r->target.ptr)); 575 buffer_copy_string_len(&r->target, val.ptr, val.len); 576 if (NULL != qmark) 577 buffer_append_string_buffer(&r->target, r->tmp_buf); 578 return 0; 579 } 580 581 buffer * const dest = magnet_env_get_buffer_by_id(r, env_id); 582 if (NULL == dest) 583 return luaL_error(L, "couldn't store '%s' in lighty.env[]", key); 584 585 if (lua_isnil(L, 3)) { 586 if (env_id==MAGNET_ENV_URI_QUERY || env_id==MAGNET_ENV_PHYSICAL_PATH) 587 buffer_clear(dest); 588 else 589 buffer_string_set_length(dest, 0); 590 } 591 else { 592 const_buffer val = magnet_checkconstbuffer(L, 3); 593 buffer_copy_string_len(dest, val.ptr, val.len); 594 /* NB: setting r->uri.query does not modify query-part in r->target 595 * (r->uri.query is uri-decoded; r->target is not) */ 596 } 597 598 switch (env_id) { 599 case MAGNET_ENV_URI_SCHEME: 600 case MAGNET_ENV_URI_AUTHORITY: 601 buffer_to_lower(dest); 602 if (env_id == MAGNET_ENV_URI_AUTHORITY) 603 r->server_name = dest; 604 break; 605 default: 606 break; 607 } 608 609 return 0; 610 } 611 612 static int magnet_env_next(lua_State *L) { 613 const int pos = lua_tointeger(L, lua_upvalueindex(1)); 614 615 /* ignore previous key: use upvalue for current pos */ 616 lua_settop(L, 0); 617 618 if (NULL == magnet_env[pos].name) return 0; /* end of list */ 619 /* Update our positional upval to reflect our new current position */ 620 lua_pushinteger(L, pos + 1); 621 lua_replace(L, lua_upvalueindex(1)); 622 623 /* key to return */ 624 lua_pushstring(L, magnet_env[pos].name); 625 626 /* get value */ 627 request_st * const r = magnet_get_request(L); 628 magnet_push_buffer(L, magnet_env_get_buffer_by_id(r, magnet_env[pos].type)); 629 630 /* return 2 items on the stack (key, value) */ 631 return 2; 632 } 633 634 static int magnet_env_pairs(lua_State *L) { 635 lua_pushinteger(L, 0); /* Push our current pos (the start) into upval 1 */ 636 lua_pushcclosure(L, magnet_env_next, 1); /* Push our new closure with 1 upvals */ 637 return 1; 638 } 639 640 static int magnet_cgi_get(lua_State *L) { 641 /* __index: param 1 is the (empty) table the value was not found in */ 642 size_t klen; 643 const char * const k = luaL_checklstring(L, 2, &klen); 644 request_st * const r = magnet_get_request(L); 645 const buffer * const vb = http_header_env_get(r, k, klen); 646 magnet_push_buffer(L, NULL != vb ? vb : NULL); 647 return 1; 648 } 649 650 static int magnet_cgi_set(lua_State *L) { 651 /* __newindex: param 1 is the (empty) table the value is supposed to be set in */ 652 const_buffer key = magnet_checkconstbuffer(L, 2); 653 const_buffer val = magnet_checkconstbuffer(L, 3); 654 request_st * const r = magnet_get_request(L); 655 http_header_env_set(r, key.ptr, key.len, val.ptr, val.len); 656 return 0; 657 } 658 659 static int magnet_cgi_pairs(lua_State *L) { 660 request_st * const r = magnet_get_request(L); 661 return magnet_array_pairs(L, &r->env); 662 } 663 664 665 static int magnet_copy_response_header(request_st * const r, lua_State * const L, int lighty_table_ndx) { 666 force_assert(lua_istable(L, lighty_table_ndx)); 667 668 lua_getfield(L, lighty_table_ndx, "header"); /* lighty.header */ 669 if (lua_istable(L, -1)) { 670 /* header is found, and is a table */ 671 672 lua_pushnil(L); 673 while (lua_next(L, -2) != 0) { 674 if (lua_isstring(L, -1) && lua_isstring(L, -2)) { 675 const_buffer key = magnet_checkconstbuffer(L, -2); 676 const_buffer val = magnet_checkconstbuffer(L, -1); 677 enum http_header_e id = http_header_hkey_get(key.ptr, key.len); 678 679 val.len 680 ? http_header_response_set(r, id, key.ptr, key.len, val.ptr, val.len) 681 : http_header_response_unset(r, id, key.ptr, key.len); 682 } 683 684 lua_pop(L, 1); 685 } 686 } 687 lua_pop(L, 1); /* pop lighty.header */ 688 689 return 0; 690 } 691 692 /** 693 * walk through the content array 694 * 695 * content = { "<pre>", { file = "/content" } , "</pre>" } 696 * 697 * header["Content-Type"] = "text/html" 698 * 699 * return 200 700 */ 701 static int magnet_attach_content(request_st * const r, lua_State * const L, int lighty_table_ndx) { 702 force_assert(lua_istable(L, lighty_table_ndx)); 703 704 lua_getfield(L, lighty_table_ndx, "content"); /* lighty.content */ 705 if (lua_istable(L, -1)) { 706 int i; 707 /* content is found, and is a table */ 708 709 for (i = 1; ; i++) { 710 lua_rawgeti(L, -1, i); 711 712 /* -1 is the value and should be the value ... aka a table */ 713 if (lua_isstring(L, -1)) { 714 const_buffer data = magnet_checkconstbuffer(L, -1); 715 716 chunkqueue_append_mem(r->write_queue, data.ptr, data.len); 717 } else if (lua_istable(L, -1)) { 718 lua_getfield(L, -1, "filename"); 719 lua_getfield(L, -2, "length"); /* (0-based) end of range (not actually "length") */ 720 lua_getfield(L, -3, "offset"); /* (0-based) start of range */ 721 722 if (lua_isstring(L, -3)) { /* filename has to be a string */ 723 off_t off = (off_t) luaL_optinteger(L, -1, 0); 724 off_t len = (off_t) luaL_optinteger(L, -2, -1); /*(-1 to http_chunk_append_file_range() uses file size minus offset)*/ 725 if (off < 0) { 726 return luaL_error(L, "offset for '%s' is negative", lua_tostring(L, -3)); 727 } 728 729 if (len >= off) { 730 len -= off; 731 } else if (-1 != len) { 732 return luaL_error(L, "offset > length for '%s'", lua_tostring(L, -3)); 733 } 734 735 if (0 != len) { 736 buffer *fn = magnet_checkbuffer(L, -3); 737 int rc = http_chunk_append_file_range(r, fn, off, len); 738 buffer_free(fn); 739 if (0 != rc) { 740 return luaL_error(L, "error opening file content '%s' at offset %lld", lua_tostring(L, -3), (long long)off); 741 } 742 } 743 } else { 744 return luaL_error(L, "content[%d] is a table and requires the field \"filename\"", i); 745 } 746 747 lua_pop(L, 3); 748 } else if (lua_isnil(L, -1)) { 749 /* end of list */ 750 751 lua_pop(L, 1); 752 753 break; 754 } else { 755 return luaL_error(L, "content[%d] is neither a string nor a table: ", i); 756 } 757 758 lua_pop(L, 1); /* pop the content[...] entry value */ 759 } 760 } else { 761 return luaL_error(L, "lighty.content has to be a table"); 762 } 763 lua_pop(L, 1); /* pop lighty.content */ 764 765 return 0; 766 } 767 768 static int traceback(lua_State *L) { 769 if (!lua_isstring(L, 1)) /* 'message' not a string? */ 770 return 1; /* keep it intact */ 771 lua_getglobal(L, "debug"); 772 if (!lua_istable(L, -1)) { 773 lua_pop(L, 1); 774 return 1; 775 } 776 lua_getfield(L, -1, "traceback"); 777 if (!lua_isfunction(L, -1)) { 778 lua_pop(L, 2); 779 return 1; 780 } 781 lua_pushvalue(L, 1); /* pass error message */ 782 lua_pushinteger(L, 2); /* skip this function and traceback */ 783 lua_call(L, 2, 1); /* call debug.traceback */ 784 return 1; 785 } 786 787 /* push traceback function before calling lua_pcall after narg arguments 788 * have been pushed (inserts it before the arguments). returns index for 789 * traceback function ("msgh" in lua_pcall) 790 */ 791 static int push_traceback(lua_State *L, int narg) { 792 int base = lua_gettop(L) - narg; /* function index */ 793 lua_pushcfunction(L, traceback); 794 lua_insert(L, base); 795 return base; 796 } 797 798 static handler_t magnet_attract(request_st * const r, plugin_data * const p, buffer * const name) { 799 lua_State *L; 800 int lua_return_value; 801 const int func_ndx = 1; 802 const int lighty_table_ndx = 2; 803 804 /* get the script-context */ 805 L = script_cache_get_script(&p->cache, name, r->conf.etag_flags); 806 807 if (lua_isstring(L, -1)) { 808 log_error(r->conf.errh, __FILE__, __LINE__, 809 "loading script %s failed: %s", name->ptr, lua_tostring(L, -1)); 810 811 lua_pop(L, 1); 812 813 force_assert(lua_gettop(L) == 0); /* only the error should have been on the stack */ 814 815 r->http_status = 500; 816 r->handler_module = NULL; 817 818 return HANDLER_FINISHED; 819 } 820 821 force_assert(lua_gettop(L) == 1); 822 force_assert(lua_isfunction(L, func_ndx)); 823 824 lua_pushlightuserdata(L, r); 825 lua_setfield(L, LUA_REGISTRYINDEX, LUA_RIDX_LIGHTTPD_REQUEST); 826 827 lua_atpanic(L, magnet_atpanic); 828 829 /** 830 * we want to create empty environment for our script 831 * 832 * setmetatable({}, {__index = _G}) 833 * 834 * if a function symbol is not defined in our env, __index will lookup 835 * in the global env. 836 * 837 * all variables created in the script-env will be thrown 838 * away at the end of the script run. 839 */ 840 lua_newtable(L); /* my empty environment aka {} (sp += 1) */ 841 842 /* we have to overwrite the print function */ 843 lua_pushcfunction(L, magnet_print); /* (sp += 1) */ 844 lua_setfield(L, -2, "print"); /* -1 is the env we want to set(sp -= 1) */ 845 846 /** 847 * lighty.request[] (ro) has the HTTP-request headers 848 * lighty.env[] (rw) has various url/physical file paths and 849 * request meta data; might contain nil values 850 * lighty.req_env[] (ro) has the cgi environment 851 * lighty.status[] (ro) has the status counters 852 * lighty.content[] (rw) is a table of string/file 853 * lighty.header[] (rw) is a array to set response headers 854 */ 855 856 lua_newtable(L); /* lighty.* (sp += 1) */ 857 858 lua_newtable(L); /* {} (sp += 1) */ 859 lua_newtable(L); /* the meta-table for the request-table (sp += 1) */ 860 lua_pushcfunction(L, magnet_reqhdr_get); /* (sp += 1) */ 861 lua_setfield(L, -2, "__index"); /* (sp -= 1) */ 862 lua_pushcfunction(L, magnet_reqhdr_pairs); /* (sp += 1) */ 863 lua_setfield(L, -2, "__pairs"); /* (sp -= 1) */ 864 lua_setmetatable(L, -2); /* tie the metatable to request (sp -= 1) */ 865 lua_setfield(L, -2, "request"); /* content = {} (sp -= 1) */ 866 867 lua_newtable(L); /* {} (sp += 1) */ 868 lua_newtable(L); /* the meta-table for the env-table (sp += 1) */ 869 lua_pushcfunction(L, magnet_env_get); /* (sp += 1) */ 870 lua_setfield(L, -2, "__index"); /* (sp -= 1) */ 871 lua_pushcfunction(L, magnet_env_set); /* (sp += 1) */ 872 lua_setfield(L, -2, "__newindex"); /* (sp -= 1) */ 873 lua_pushcfunction(L, magnet_env_pairs); /* (sp += 1) */ 874 lua_setfield(L, -2, "__pairs"); /* (sp -= 1) */ 875 lua_setmetatable(L, -2); /* tie the metatable to env (sp -= 1) */ 876 lua_setfield(L, -2, "env"); /* content = {} (sp -= 1) */ 877 878 lua_newtable(L); /* {} (sp += 1) */ 879 lua_newtable(L); /* the meta-table for the req_env-table (sp += 1) */ 880 lua_pushcfunction(L, magnet_cgi_get); /* (sp += 1) */ 881 lua_setfield(L, -2, "__index"); /* (sp -= 1) */ 882 lua_pushcfunction(L, magnet_cgi_set); /* (sp += 1) */ 883 lua_setfield(L, -2, "__newindex"); /* (sp -= 1) */ 884 lua_pushcfunction(L, magnet_cgi_pairs); /* (sp += 1) */ 885 lua_setfield(L, -2, "__pairs"); /* (sp -= 1) */ 886 lua_setmetatable(L, -2); /* tie the metatable to req_env (sp -= 1) */ 887 lua_setfield(L, -2, "req_env"); /* content = {} (sp -= 1) */ 888 889 lua_newtable(L); /* {} (sp += 1) */ 890 lua_newtable(L); /* the meta-table for the status-table (sp += 1) */ 891 lua_pushcfunction(L, magnet_status_get); /* (sp += 1) */ 892 lua_setfield(L, -2, "__index"); /* (sp -= 1) */ 893 lua_pushcfunction(L, magnet_status_set); /* (sp += 1) */ 894 lua_setfield(L, -2, "__newindex"); /* (sp -= 1) */ 895 lua_pushcfunction(L, magnet_status_pairs); /* (sp += 1) */ 896 lua_setfield(L, -2, "__pairs"); /* (sp -= 1) */ 897 lua_setmetatable(L, -2); /* tie the metatable to statzs (sp -= 1) */ 898 lua_setfield(L, -2, "status"); /* content = {} (sp -= 1) */ 899 900 /* add empty 'content' and 'header' tables */ 901 lua_newtable(L); /* {} (sp += 1) */ 902 lua_setfield(L, -2, "content"); /* content = {} (sp -= 1) */ 903 904 lua_newtable(L); /* {} (sp += 1) */ 905 lua_setfield(L, -2, "header"); /* header = {} (sp -= 1) */ 906 907 lua_pushinteger(L, MAGNET_RESTART_REQUEST); 908 lua_setfield(L, -2, "RESTART_REQUEST"); 909 910 lua_pushcfunction(L, magnet_stat); /* (sp += 1) */ 911 lua_setfield(L, -2, "stat"); /* -1 is the env we want to set (sp -= 1) */ 912 913 /* insert lighty table at index 2 */ 914 lua_pushvalue(L, -1); 915 lua_insert(L, lighty_table_ndx); 916 917 lua_setfield(L, -2, "lighty"); /* lighty.* (sp -= 1) */ 918 919 #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502 920 /* override the default pairs() function to our __pairs capable version; 921 * not needed for lua 5.2+ 922 */ 923 lua_getglobal(L, "pairs"); /* push original pairs() (sp += 1) */ 924 lua_pushcclosure(L, magnet_pairs, 1); 925 lua_setfield(L, -2, "pairs"); /* (sp -= 1) */ 926 #endif 927 928 lua_newtable(L); /* the meta-table for the new env (sp += 1) */ 929 lua_pushglobaltable(L); /* (sp += 1) */ 930 lua_setfield(L, -2, "__index"); /* { __index = _G } (sp -= 1) */ 931 lua_setmetatable(L, -2); /* setmetatable({}, {__index = _G}) (sp -= 1) */ 932 933 magnet_setfenv_mainfn(L, 1); /* (sp -= 1) */ 934 935 /* pcall will destroy the func value, duplicate it */ /* (sp += 1) */ 936 lua_pushvalue(L, func_ndx); 937 { 938 int errfunc = push_traceback(L, 0); 939 int ret = lua_pcall(L, 0, 1, errfunc); 940 lua_remove(L, errfunc); 941 942 /* reset environment */ 943 lua_pushglobaltable(L); /* (sp += 1) */ 944 magnet_setfenv_mainfn(L, 1); /* (sp -= 1) */ 945 946 if (0 != ret) { 947 log_error(r->conf.errh, __FILE__, __LINE__, 948 "lua_pcall(): %s", lua_tostring(L, -1)); 949 lua_pop(L, 2); /* remove the error-msg and the lighty table at index 2 */ 950 951 force_assert(lua_gettop(L) == 1); /* only the function should be on the stack */ 952 953 r->http_status = 500; 954 r->handler_module = NULL; 955 956 return HANDLER_FINISHED; 957 } 958 } 959 960 /* we should have the function, the lighty table and the return value on the stack */ 961 force_assert(lua_gettop(L) == 3); 962 963 switch (lua_type(L, -1)) { 964 case LUA_TNUMBER: 965 case LUA_TNIL: 966 lua_return_value = (int) luaL_optinteger(L, -1, -1); 967 break; 968 default: 969 log_error(r->conf.errh, __FILE__, __LINE__, 970 "lua_pcall(): unexpected return type: %s", luaL_typename(L, -1)); 971 lua_return_value = -1; 972 break; 973 } 974 975 lua_pop(L, 1); /* pop return value */ 976 977 magnet_copy_response_header(r, L, lighty_table_ndx); 978 979 { 980 handler_t result = HANDLER_GO_ON; 981 982 if (lua_return_value > 99) { 983 r->http_status = lua_return_value; 984 r->resp_body_finished = 1; 985 986 /* try { ...*/ 987 if (0 == setjmp(exceptionjmp)) { 988 magnet_attach_content(r, L, lighty_table_ndx); 989 if (!chunkqueue_is_empty(r->write_queue)) { 990 r->handler_module = p->self; 991 } 992 } else { 993 lua_settop(L, 2); /* remove all but function and lighty table */ 994 /* } catch () { */ 995 r->http_status = 500; 996 r->handler_module = NULL; 997 } 998 999 result = HANDLER_FINISHED; 1000 } else if (MAGNET_RESTART_REQUEST == lua_return_value) { 1001 result = HANDLER_COMEBACK; 1002 } 1003 1004 lua_pop(L, 1); /* pop the lighty table */ 1005 force_assert(lua_gettop(L) == 1); /* only the function should remain on the stack */ 1006 1007 return result; 1008 } 1009 } 1010 1011 static handler_t magnet_attract_array(request_st * const r, plugin_data * const p, int is_uri) { 1012 mod_magnet_patch_config(r, p); 1013 1014 const array * const files = (is_uri) 1015 ? p->conf.url_raw 1016 : p->conf.physical_path; 1017 1018 /* no filename set */ 1019 if (NULL == files || files->used == 0) return HANDLER_GO_ON; 1020 1021 r->con->srv->request_env(r); 1022 1023 /** 1024 * execute all files and jump out on the first !HANDLER_GO_ON 1025 */ 1026 handler_t ret = HANDLER_GO_ON; 1027 for (uint32_t i = 0; i < files->used && ret == HANDLER_GO_ON; ++i) { 1028 data_string *ds = (data_string *)files->data[i]; 1029 ret = magnet_attract(r, p, &ds->value); 1030 } 1031 1032 if (r->error_handler_saved_status) { 1033 /* retrieve (possibly modified) REDIRECT_STATUS and store as number */ 1034 unsigned long x; 1035 const buffer * const vb = http_header_env_get(r, CONST_STR_LEN("REDIRECT_STATUS")); 1036 if (vb && (x = strtoul(vb->ptr, NULL, 10)) < 1000) 1037 /*(simplified validity check x < 1000)*/ 1038 r->error_handler_saved_status = 1039 r->error_handler_saved_status > 0 ? (int)x : -(int)x; 1040 } 1041 1042 return ret; 1043 } 1044 1045 URIHANDLER_FUNC(mod_magnet_uri_handler) { 1046 return magnet_attract_array(r, p_d, 1); 1047 } 1048 1049 URIHANDLER_FUNC(mod_magnet_physical) { 1050 return magnet_attract_array(r, p_d, 0); 1051 } 1052 1053 1054 int mod_magnet_plugin_init(plugin *p); 1055 int mod_magnet_plugin_init(plugin *p) { 1056 p->version = LIGHTTPD_VERSION_ID; 1057 p->name = "magnet"; 1058 1059 p->init = mod_magnet_init; 1060 p->handle_uri_clean = mod_magnet_uri_handler; 1061 p->handle_physical = mod_magnet_physical; 1062 p->set_defaults = mod_magnet_set_defaults; 1063 p->cleanup = mod_magnet_free; 1064 1065 return 0; 1066 } 1067