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