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