1 #include "first.h" 2 3 #include "fdevent.h" 4 #include "fdlog.h" 5 #include "log.h" 6 #include "array.h" 7 #include "buffer.h" 8 #include "chunk.h" 9 #include "http_cgi.h" 10 #include "http_chunk.h" 11 #include "http_etag.h" 12 #include "http_header.h" 13 #include "request.h" 14 #include "stat_cache.h" 15 16 #include "plugin.h" 17 18 #include "response.h" 19 20 #include "sys-socket.h" 21 #include "sys-time.h" 22 23 #include <sys/types.h> 24 #include <sys/stat.h> 25 #ifdef HAVE_SYS_WAIT_H 26 #include <sys/wait.h> 27 #endif 28 29 #include <ctype.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <errno.h> 33 #include <fcntl.h> 34 #include <unistd.h> 35 36 #ifdef HAVE_PWD_H 37 # include <pwd.h> 38 #endif 39 40 typedef struct { 41 const array *ssi_extension; 42 const buffer *content_type; 43 unsigned short conditional_requests; 44 unsigned short ssi_exec; 45 unsigned short ssi_recursion_max; 46 } plugin_config; 47 48 typedef struct { 49 PLUGIN_DATA; 50 plugin_config defaults; 51 plugin_config conf; 52 array *ssi_vars; 53 array *ssi_cgi_env; 54 buffer stat_fn; 55 buffer timefmt; 56 } plugin_data; 57 58 typedef struct { 59 array *ssi_vars; 60 array *ssi_cgi_env; 61 buffer *stat_fn; 62 buffer *timefmt; 63 int sizefmt; 64 65 int if_level, if_is_false_level, if_is_false, if_is_false_endif; 66 unsigned short ssi_recursion_depth; 67 68 chunkqueue wq; 69 log_error_st *errh; 70 plugin_config conf; 71 } handler_ctx; 72 73 static handler_ctx * handler_ctx_init(plugin_data *p, log_error_st *errh) { 74 handler_ctx *hctx = ck_calloc(1, sizeof(*hctx)); 75 hctx->errh = errh; 76 hctx->timefmt = &p->timefmt; 77 hctx->stat_fn = &p->stat_fn; 78 hctx->ssi_vars = p->ssi_vars; 79 hctx->ssi_cgi_env = p->ssi_cgi_env; 80 memcpy(&hctx->conf, &p->conf, sizeof(plugin_config)); 81 return hctx; 82 } 83 84 static void handler_ctx_free(handler_ctx *hctx) { 85 chunkqueue_reset(&hctx->wq); 86 free(hctx); 87 } 88 89 /* The newest modified time of included files for include statement */ 90 static volatile unix_time64_t include_file_last_mtime = 0; 91 92 INIT_FUNC(mod_ssi_init) { 93 plugin_data * const p = ck_calloc(1, sizeof(*p)); 94 p->ssi_vars = array_init(8); 95 p->ssi_cgi_env = array_init(32); 96 return p; 97 } 98 99 FREE_FUNC(mod_ssi_free) { 100 plugin_data *p = p_d; 101 array_free(p->ssi_vars); 102 array_free(p->ssi_cgi_env); 103 free(p->timefmt.ptr); 104 free(p->stat_fn.ptr); 105 } 106 107 static void mod_ssi_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 108 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 109 case 0: /* ssi.extension */ 110 pconf->ssi_extension = cpv->v.a; 111 break; 112 case 1: /* ssi.content-type */ 113 pconf->content_type = cpv->v.b; 114 break; 115 case 2: /* ssi.conditional-requests */ 116 pconf->conditional_requests = cpv->v.u; 117 break; 118 case 3: /* ssi.exec */ 119 pconf->ssi_exec = cpv->v.u; 120 break; 121 case 4: /* ssi.recursion-max */ 122 pconf->ssi_recursion_max = cpv->v.shrt; 123 break; 124 default:/* should not happen */ 125 return; 126 } 127 } 128 129 static void mod_ssi_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 130 do { 131 mod_ssi_merge_config_cpv(pconf, cpv); 132 } while ((++cpv)->k_id != -1); 133 } 134 135 static void mod_ssi_patch_config(request_st * const r, plugin_data * const p) { 136 memcpy(&p->conf, &p->defaults, sizeof(plugin_config)); 137 for (int i = 1, used = p->nconfig; i < used; ++i) { 138 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 139 mod_ssi_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]); 140 } 141 } 142 143 SETDEFAULTS_FUNC(mod_ssi_set_defaults) { 144 static const config_plugin_keys_t cpk[] = { 145 { CONST_STR_LEN("ssi.extension"), 146 T_CONFIG_ARRAY_VLIST, 147 T_CONFIG_SCOPE_CONNECTION } 148 ,{ CONST_STR_LEN("ssi.content-type"), 149 T_CONFIG_STRING, 150 T_CONFIG_SCOPE_CONNECTION } 151 ,{ CONST_STR_LEN("ssi.conditional-requests"), 152 T_CONFIG_BOOL, 153 T_CONFIG_SCOPE_CONNECTION } 154 ,{ CONST_STR_LEN("ssi.exec"), 155 T_CONFIG_BOOL, 156 T_CONFIG_SCOPE_CONNECTION } 157 ,{ CONST_STR_LEN("ssi.recursion-max"), 158 T_CONFIG_SHORT, 159 T_CONFIG_SCOPE_CONNECTION } 160 ,{ NULL, 0, 161 T_CONFIG_UNSET, 162 T_CONFIG_SCOPE_UNSET } 163 }; 164 165 plugin_data * const p = p_d; 166 if (!config_plugin_values_init(srv, p, cpk, "mod_ssi")) 167 return HANDLER_ERROR; 168 169 /* process and validate config directives 170 * (init i to 0 if global context; to 1 to skip empty global context) */ 171 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 172 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 173 for (; -1 != cpv->k_id; ++cpv) { 174 switch (cpv->k_id) { 175 case 0: /* ssi.extension */ 176 break; 177 case 1: /* ssi.content-type */ 178 if (buffer_is_blank(cpv->v.b)) 179 cpv->v.b = NULL; 180 break; 181 case 2: /* ssi.conditional-requests */ 182 case 3: /* ssi.exec */ 183 case 4: /* ssi.recursion-max */ 184 break; 185 default:/* should not happen */ 186 break; 187 } 188 } 189 } 190 191 p->defaults.ssi_exec = 1; 192 193 /* initialize p->defaults from global config context */ 194 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 195 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 196 if (-1 != cpv->k_id) 197 mod_ssi_merge_config(&p->defaults, cpv); 198 } 199 200 return HANDLER_GO_ON; 201 } 202 203 204 205 206 #define TK_AND 1 207 #define TK_OR 2 208 #define TK_EQ 3 209 #define TK_NE 4 210 #define TK_GT 5 211 #define TK_GE 6 212 #define TK_LT 7 213 #define TK_LE 8 214 #define TK_NOT 9 215 #define TK_LPARAN 10 216 #define TK_RPARAN 11 217 #define TK_VALUE 12 218 219 typedef struct { 220 const char *input; 221 size_t offset; 222 size_t size; 223 int in_brace; 224 int depth; 225 handler_ctx *p; 226 } ssi_tokenizer_t; 227 228 typedef struct { 229 buffer str; 230 enum { SSI_TYPE_UNSET, SSI_TYPE_BOOL, SSI_TYPE_STRING } type; 231 int bo; 232 } ssi_val_t; 233 234 __attribute_pure__ 235 static int ssi_val_tobool(const ssi_val_t *B) { 236 return B->type == SSI_TYPE_BOOL ? B->bo : !buffer_is_blank(&B->str); 237 } 238 239 __attribute_pure__ 240 static int ssi_eval_expr_cmp(const ssi_val_t * const v1, const ssi_val_t * const v2, const int cond) { 241 int cmp = (v1->type != SSI_TYPE_BOOL && v2->type != SSI_TYPE_BOOL) 242 ? strcmp(v1->str.ptr ? v1->str.ptr : "", 243 v2->str.ptr ? v2->str.ptr : "") 244 : ssi_val_tobool(v1) - ssi_val_tobool(v2); 245 switch (cond) { 246 case TK_EQ: return (cmp == 0); 247 case TK_NE: return (cmp != 0); 248 case TK_GE: return (cmp >= 0); 249 case TK_GT: return (cmp > 0); 250 case TK_LE: return (cmp <= 0); 251 case TK_LT: return (cmp < 0); 252 default: return 0;/*(should not happen)*/ 253 } 254 } 255 256 __attribute_pure__ 257 static int ssi_eval_expr_cmp_bool(const ssi_val_t * const v1, const ssi_val_t * const v2, const int cond) { 258 return (cond == TK_OR) 259 ? ssi_val_tobool(v1) || ssi_val_tobool(v2) /* TK_OR */ 260 : ssi_val_tobool(v1) && ssi_val_tobool(v2); /* TK_AND */ 261 } 262 263 static void ssi_eval_expr_append_val(buffer * const b, const char *s, const size_t slen) { 264 if (buffer_is_blank(b)) 265 buffer_append_string_len(b, s, slen); 266 else if (slen) 267 buffer_append_str2(b, CONST_STR_LEN(" "), s, slen); 268 } 269 270 static int ssi_expr_tokenizer(ssi_tokenizer_t * const t, buffer * const token) { 271 size_t i; 272 273 while (t->offset < t->size 274 && (t->input[t->offset] == ' ' || t->input[t->offset] == '\t')) { 275 ++t->offset; 276 } 277 if (t->offset >= t->size) 278 return 0; 279 if (t->input[t->offset] == '\0') { 280 log_error(t->p->errh, __FILE__, __LINE__, 281 "pos: %zu foobar", t->offset+1); 282 return -1; 283 } 284 285 switch (t->input[t->offset]) { 286 case '=': 287 #if 0 /*(maybe accept "==", too)*/ 288 if (t->input[t->offset + 1] == '=') 289 ++t->offset; 290 #endif 291 t->offset++; 292 return TK_EQ; 293 case '>': 294 if (t->input[t->offset + 1] == '=') { 295 t->offset += 2; 296 return TK_GE; 297 } 298 else { 299 t->offset += 1; 300 return TK_GT; 301 } 302 case '<': 303 if (t->input[t->offset + 1] == '=') { 304 t->offset += 2; 305 return TK_LE; 306 } 307 else { 308 t->offset += 1; 309 return TK_LT; 310 } 311 case '!': 312 if (t->input[t->offset + 1] == '=') { 313 t->offset += 2; 314 return TK_NE; 315 } 316 else { 317 t->offset += 1; 318 return TK_NOT; 319 } 320 case '&': 321 if (t->input[t->offset + 1] == '&') { 322 t->offset += 2; 323 return TK_AND; 324 } 325 else { 326 log_error(t->p->errh, __FILE__, __LINE__, 327 "pos: %zu missing second &", t->offset+1); 328 return -1; 329 } 330 case '|': 331 if (t->input[t->offset + 1] == '|') { 332 t->offset += 2; 333 return TK_OR; 334 } 335 else { 336 log_error(t->p->errh, __FILE__, __LINE__, 337 "pos: %zu missing second |", t->offset+1); 338 return -1; 339 } 340 case '(': 341 t->offset++; 342 t->in_brace++; 343 return TK_LPARAN; 344 case ')': 345 t->offset++; 346 t->in_brace--; 347 return TK_RPARAN; 348 case '\'': 349 /* search for the terminating "'" */ 350 i = 1; 351 while (t->input[t->offset + i] && t->input[t->offset + i] != '\'') 352 ++i; 353 if (t->input[t->offset + i]) { 354 ssi_eval_expr_append_val(token, t->input + t->offset + 1, i-1); 355 t->offset += i + 1; 356 return TK_VALUE; 357 } 358 else { 359 log_error(t->p->errh, __FILE__, __LINE__, 360 "pos: %zu missing closing quote", t->offset+1); 361 return -1; 362 } 363 case '$': { 364 const char *var; 365 size_t varlen; 366 if (t->input[t->offset + 1] == '{') { 367 i = 2; 368 while (t->input[t->offset + i] && t->input[t->offset + i] != '}') 369 ++i; 370 if (t->input[t->offset + i] != '}') { 371 log_error(t->p->errh, __FILE__, __LINE__, 372 "pos: %zu missing closing curly-brace", t->offset+1); 373 return -1; 374 } 375 ++i; /* step past '}' */ 376 var = t->input + t->offset + 2; 377 varlen = i-3; 378 } 379 else { 380 for (i = 1; light_isalpha(t->input[t->offset + i]) || 381 t->input[t->offset + i] == '_' || 382 ((i > 1) && light_isdigit(t->input[t->offset + i])); ++i) ; 383 var = t->input + t->offset + 1; 384 varlen = i-1; 385 } 386 387 const data_string *ds; 388 if ((ds = (const data_string *) 389 array_get_element_klen(t->p->ssi_cgi_env, var, varlen)) 390 || (ds = (const data_string *) 391 array_get_element_klen(t->p->ssi_vars, var, varlen))) 392 ssi_eval_expr_append_val(token, BUF_PTR_LEN(&ds->value)); 393 t->offset += i; 394 return TK_VALUE; 395 } 396 default: 397 for (i = 0; isgraph(((unsigned char *)t->input)[t->offset + i]); ++i) { 398 char d = t->input[t->offset + i]; 399 switch(d) { 400 default: continue; 401 case ' ': 402 case '\t': 403 case ')': 404 case '(': 405 case '\'': 406 case '=': 407 case '!': 408 case '<': 409 case '>': 410 case '&': 411 case '|': 412 break; 413 } 414 break; 415 } 416 ssi_eval_expr_append_val(token, t->input + t->offset, i); 417 t->offset += i; 418 return TK_VALUE; 419 } 420 } 421 422 static int ssi_eval_expr_loop(ssi_tokenizer_t * const t, ssi_val_t * const v); 423 424 static int ssi_eval_expr_step(ssi_tokenizer_t * const t, ssi_val_t * const v) { 425 buffer_clear(&v->str); 426 v->type = SSI_TYPE_UNSET; /*(not SSI_TYPE_BOOL)*/ 427 int next; 428 const int level = t->in_brace; 429 switch ((next = ssi_expr_tokenizer(t, &v->str))) { 430 case TK_VALUE: 431 do { next = ssi_expr_tokenizer(t, &v->str); } while (next == TK_VALUE); 432 return next; 433 case TK_LPARAN: 434 if (t->in_brace > 16) return -1; /*(arbitrary limit)*/ 435 next = ssi_eval_expr_loop(t, v); 436 if (next == TK_RPARAN && level == t->in_brace) { 437 int result = ssi_val_tobool(v); 438 next = ssi_eval_expr_step(t, v); /*(resets v)*/ 439 v->bo = result; 440 v->type = SSI_TYPE_BOOL; 441 return (next==TK_AND || next==TK_OR || next==TK_RPARAN || 0==next) 442 ? next 443 : -1; 444 } 445 else 446 return -1; 447 case TK_RPARAN: 448 return t->in_brace >= 0 ? TK_RPARAN : -1; 449 case TK_NOT: 450 if (++t->depth > 16) return -1; /*(arbitrary limit)*/ 451 next = ssi_eval_expr_step(t, v); 452 --t->depth; 453 if (-1 == next) return next; 454 v->bo = !ssi_val_tobool(v); 455 v->type = SSI_TYPE_BOOL; 456 return next; 457 default: 458 return next; 459 } 460 } 461 462 static int ssi_eval_expr_loop_cmp(ssi_tokenizer_t * const t, ssi_val_t * const v1, int cond) { 463 ssi_val_t v2 = { { NULL, 0, 0 }, SSI_TYPE_UNSET, 0 }; 464 int next = ssi_eval_expr_step(t, &v2); 465 if (-1 != next) { 466 v1->bo = ssi_eval_expr_cmp(v1, &v2, cond); 467 v1->type = SSI_TYPE_BOOL; 468 } 469 buffer_free_ptr(&v2.str); 470 return next; 471 } 472 473 static int ssi_eval_expr_loop(ssi_tokenizer_t * const t, ssi_val_t * const v1) { 474 int next = ssi_eval_expr_step(t, v1); 475 switch (next) { 476 case TK_AND: case TK_OR: 477 break; 478 case TK_EQ: case TK_NE: 479 case TK_GT: case TK_GE: 480 case TK_LT: case TK_LE: 481 next = ssi_eval_expr_loop_cmp(t, v1, next); 482 if (next == TK_RPARAN || 0 == next) return next; 483 if (next != TK_AND && next != TK_OR) { 484 log_error(t->p->errh, __FILE__, __LINE__, 485 "pos: %zu parser failed somehow near here", t->offset+1); 486 return -1; 487 } 488 break; 489 default: 490 return next; 491 } 492 493 /*(Note: '&&' and '||' evaluations are not short-circuited)*/ 494 ssi_val_t v2 = { { NULL, 0, 0 }, SSI_TYPE_UNSET, 0 }; 495 do { 496 int cond = next; 497 next = ssi_eval_expr_step(t, &v2); 498 switch (next) { 499 case TK_AND: case TK_OR: case 0: 500 break; 501 case TK_EQ: case TK_NE: 502 case TK_GT: case TK_GE: 503 case TK_LT: case TK_LE: 504 next = ssi_eval_expr_loop_cmp(t, &v2, next); 505 if (-1 == next) continue; 506 break; 507 case TK_RPARAN: 508 break; 509 default: 510 continue; 511 } 512 v1->bo = ssi_eval_expr_cmp_bool(v1, &v2, cond); 513 v1->type = SSI_TYPE_BOOL; 514 } while (next == TK_AND || next == TK_OR); 515 buffer_free_ptr(&v2.str); 516 return next; 517 } 518 519 static int ssi_eval_expr(handler_ctx *p, const char *expr) { 520 ssi_tokenizer_t t; 521 t.input = expr; 522 t.offset = 0; 523 t.size = strlen(expr); 524 t.in_brace = 0; 525 t.depth = 0; 526 t.p = p; 527 528 ssi_val_t v = { { NULL, 0, 0 }, SSI_TYPE_UNSET, 0 }; 529 int rc = ssi_eval_expr_loop(&t, &v); 530 rc = (0 == rc && 0 == t.in_brace && 0 == t.depth) 531 ? ssi_val_tobool(&v) 532 : -1; 533 buffer_free_ptr(&v.str); 534 535 return rc; 536 } 537 538 539 540 541 static int ssi_env_add(void *venv, const char *key, size_t klen, const char *val, size_t vlen) { 542 array_set_key_value((array *)venv, key, klen, val, vlen); 543 return 0; 544 } 545 546 static int build_ssi_cgi_vars(request_st * const r, handler_ctx * const p) { 547 http_cgi_opts opts = { 0, 0, NULL, NULL }; 548 /* temporarily remove Authorization from request headers 549 * so that Authorization does not end up in SSI environment */ 550 buffer *vb_auth = http_header_request_get(r, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization")); 551 buffer b_auth; 552 if (vb_auth) { 553 memcpy(&b_auth, vb_auth, sizeof(buffer)); 554 memset(vb_auth, 0, sizeof(buffer)); 555 } 556 557 array_reset_data_strings(p->ssi_cgi_env); 558 559 if (0 != http_cgi_headers(r, &opts, ssi_env_add, p->ssi_cgi_env)) { 560 r->http_status = 400; 561 return -1; 562 } 563 564 if (vb_auth) { 565 memcpy(vb_auth, &b_auth, sizeof(buffer)); 566 } 567 568 return 0; 569 } 570 571 static void mod_ssi_timefmt (buffer * const b, buffer *timefmtb, unix_time64_t t, int localtm) { 572 struct tm tm; 573 const char * const timefmt = buffer_is_blank(timefmtb) 574 ? "%a, %d %b %Y %T %Z" 575 : timefmtb->ptr; 576 buffer_append_strftime(b, timefmt, localtm 577 ? localtime64_r(&t, &tm) 578 : gmtime64_r(&t, &tm)); 579 if (buffer_is_blank(b)) 580 buffer_copy_string_len(b, CONST_STR_LEN("(none)")); 581 } 582 583 static int mod_ssi_process_file(request_st *r, handler_ctx *p, struct stat *st); 584 585 static int process_ssi_stmt(request_st * const r, handler_ctx * const p, const char ** const l, size_t n, struct stat * const st) { 586 587 /** 588 * <!--#element attribute=value attribute=value ... --> 589 * 590 * config DONE 591 * errmsg -- missing 592 * sizefmt DONE 593 * timefmt DONE 594 * echo DONE 595 * var DONE 596 * encoding -- missing 597 * exec DONE 598 * cgi -- never 599 * cmd DONE 600 * fsize DONE 601 * file DONE 602 * virtual DONE 603 * flastmod DONE 604 * file DONE 605 * virtual DONE 606 * include DONE 607 * file DONE 608 * virtual DONE 609 * printenv DONE 610 * set DONE 611 * var DONE 612 * value DONE 613 * 614 * if DONE 615 * elif DONE 616 * else DONE 617 * endif DONE 618 * 619 * 620 * expressions 621 * AND, OR DONE 622 * comp DONE 623 * ${...} -- missing 624 * $... DONE 625 * '...' DONE 626 * ( ... ) DONE 627 * 628 * 629 * 630 * ** all DONE ** 631 * DATE_GMT 632 * The current date in Greenwich Mean Time. 633 * DATE_LOCAL 634 * The current date in the local time zone. 635 * DOCUMENT_NAME 636 * The filename (excluding directories) of the document requested by the user. 637 * DOCUMENT_URI 638 * The (%-decoded) URL path of the document requested by the user. Note that in the case of nested include files, this is not then URL for the current document. 639 * LAST_MODIFIED 640 * The last modification date of the document requested by the user. 641 * USER_NAME 642 * Contains the owner of the file which included it. 643 * 644 */ 645 646 size_t i, ssicmd = 0; 647 buffer *tb = NULL; 648 649 static const struct { 650 const char *var; 651 enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD, 652 SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF, 653 SSI_ELSE, SSI_ENDIF, SSI_EXEC, SSI_COMMENT } type; 654 } ssicmds[] = { 655 { "echo", SSI_ECHO }, 656 { "include", SSI_INCLUDE }, 657 { "flastmod", SSI_FLASTMOD }, 658 { "fsize", SSI_FSIZE }, 659 { "config", SSI_CONFIG }, 660 { "printenv", SSI_PRINTENV }, 661 { "set", SSI_SET }, 662 { "if", SSI_IF }, 663 { "elif", SSI_ELIF }, 664 { "endif", SSI_ENDIF }, 665 { "else", SSI_ELSE }, 666 { "exec", SSI_EXEC }, 667 { "comment", SSI_COMMENT }, 668 669 { NULL, SSI_UNSET } 670 }; 671 672 for (i = 0; ssicmds[i].var; i++) { 673 if (0 == strcmp(l[1], ssicmds[i].var)) { 674 ssicmd = ssicmds[i].type; 675 break; 676 } 677 } 678 679 chunkqueue * const cq = &p->wq; 680 681 switch(ssicmd) { 682 case SSI_ECHO: { 683 /* echo */ 684 int var = 0; 685 /* int enc = 0; */ 686 const char *var_val = NULL; 687 688 static const struct { 689 const char *var; 690 enum { 691 SSI_ECHO_UNSET, 692 SSI_ECHO_DATE_GMT, 693 SSI_ECHO_DATE_LOCAL, 694 SSI_ECHO_DOCUMENT_NAME, 695 SSI_ECHO_DOCUMENT_URI, 696 SSI_ECHO_LAST_MODIFIED, 697 SSI_ECHO_USER_NAME, 698 SSI_ECHO_SCRIPT_URI, 699 SSI_ECHO_SCRIPT_URL, 700 } type; 701 } echovars[] = { 702 { "DATE_GMT", SSI_ECHO_DATE_GMT }, 703 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL }, 704 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME }, 705 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI }, 706 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED }, 707 { "USER_NAME", SSI_ECHO_USER_NAME }, 708 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI }, 709 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL }, 710 711 { NULL, SSI_ECHO_UNSET } 712 }; 713 714 /* 715 static const struct { 716 const char *var; 717 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type; 718 } encvars[] = { 719 { "url", SSI_ENC_URL }, 720 { "none", SSI_ENC_NONE }, 721 { "entity", SSI_ENC_ENTITY }, 722 723 { NULL, SSI_ENC_UNSET } 724 }; 725 */ 726 727 for (i = 2; i < n; i += 2) { 728 if (0 == strcmp(l[i], "var")) { 729 int j; 730 731 var_val = l[i+1]; 732 733 for (j = 0; echovars[j].var; j++) { 734 if (0 == strcmp(l[i+1], echovars[j].var)) { 735 var = echovars[j].type; 736 break; 737 } 738 } 739 } else if (0 == strcmp(l[i], "encoding")) { 740 /* 741 int j; 742 743 for (j = 0; encvars[j].var; j++) { 744 if (0 == strcmp(l[i+1], encvars[j].var)) { 745 enc = encvars[j].type; 746 break; 747 } 748 } 749 */ 750 } else { 751 log_error(r->conf.errh, __FILE__, __LINE__, 752 "ssi: unknown attribute for %s %s", l[1], l[i]); 753 } 754 } 755 756 if (p->if_is_false) break; 757 758 if (!var_val) { 759 log_error(r->conf.errh, __FILE__, __LINE__, 760 "ssi: %s var is missing", l[1]); 761 break; 762 } 763 764 switch(var) { 765 case SSI_ECHO_USER_NAME: { 766 tb = r->tmp_buf; 767 buffer_clear(tb); 768 #ifdef HAVE_PWD_H 769 struct passwd *pw; 770 if (NULL == (pw = getpwuid(st->st_uid))) { 771 buffer_append_int(tb, st->st_uid); 772 } else { 773 buffer_copy_string(tb, pw->pw_name); 774 } 775 #else 776 buffer_append_int(tb, st->st_uid); 777 #endif 778 chunkqueue_append_mem(cq, BUF_PTR_LEN(tb)); 779 break; 780 } 781 case SSI_ECHO_LAST_MODIFIED: 782 case SSI_ECHO_DATE_LOCAL: 783 case SSI_ECHO_DATE_GMT: 784 tb = r->tmp_buf; 785 buffer_clear(tb); 786 mod_ssi_timefmt(tb, p->timefmt, 787 (var == SSI_ECHO_LAST_MODIFIED) 788 ? st->st_mtime 789 : log_epoch_secs, 790 (var != SSI_ECHO_DATE_GMT)); 791 chunkqueue_append_mem(cq, BUF_PTR_LEN(tb)); 792 break; 793 case SSI_ECHO_DOCUMENT_NAME: { 794 char *sl; 795 796 if (NULL == (sl = strrchr(r->physical.path.ptr, '/'))) { 797 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->physical.path)); 798 } else { 799 chunkqueue_append_mem(cq, sl + 1, strlen(sl + 1)); 800 } 801 break; 802 } 803 case SSI_ECHO_DOCUMENT_URI: { 804 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->uri.path)); 805 break; 806 } 807 case SSI_ECHO_SCRIPT_URI: { 808 if (!buffer_is_blank(&r->uri.scheme) && !buffer_is_blank(&r->uri.authority)) { 809 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->uri.scheme)); 810 chunkqueue_append_mem(cq, CONST_STR_LEN("://")); 811 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->uri.authority)); 812 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->target)); 813 if (!buffer_is_blank(&r->uri.query)) { 814 chunkqueue_append_mem(cq, CONST_STR_LEN("?")); 815 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->uri.query)); 816 } 817 } 818 break; 819 } 820 case SSI_ECHO_SCRIPT_URL: { 821 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->target)); 822 if (!buffer_is_blank(&r->uri.query)) { 823 chunkqueue_append_mem(cq, CONST_STR_LEN("?")); 824 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->uri.query)); 825 } 826 break; 827 } 828 default: { 829 const data_string *ds; 830 /* check if it is a cgi-var or a ssi-var */ 831 832 if (NULL != (ds = (const data_string *)array_get_element_klen(p->ssi_cgi_env, var_val, strlen(var_val))) || 833 NULL != (ds = (const data_string *)array_get_element_klen(p->ssi_vars, var_val, strlen(var_val)))) { 834 chunkqueue_append_mem(cq, BUF_PTR_LEN(&ds->value)); 835 } else { 836 chunkqueue_append_mem(cq, CONST_STR_LEN("(none)")); 837 } 838 839 break; 840 } 841 } 842 break; 843 } 844 case SSI_INCLUDE: 845 case SSI_FLASTMOD: 846 case SSI_FSIZE: { 847 const char * file_path = NULL, *virt_path = NULL; 848 struct stat stb; 849 850 for (i = 2; i < n; i += 2) { 851 if (0 == strcmp(l[i], "file")) { 852 file_path = l[i+1]; 853 } else if (0 == strcmp(l[i], "virtual")) { 854 virt_path = l[i+1]; 855 } else { 856 log_error(r->conf.errh, __FILE__, __LINE__, 857 "ssi: unknown attribute for %s %s", l[1], l[i]); 858 } 859 } 860 861 if (!file_path && !virt_path) { 862 log_error(r->conf.errh, __FILE__, __LINE__, 863 "ssi: %s file or virtual is missing", l[1]); 864 break; 865 } 866 867 if (file_path && virt_path) { 868 log_error(r->conf.errh, __FILE__, __LINE__, 869 "ssi: %s only one of file and virtual is allowed here", l[1]); 870 break; 871 } 872 873 874 if (p->if_is_false) break; 875 876 tb = r->tmp_buf; 877 878 if (file_path) { 879 /* current doc-root */ 880 buffer_copy_string(tb, file_path); 881 buffer_urldecode_path(tb); 882 if (!buffer_is_valid_UTF8(tb)) { 883 log_error(r->conf.errh, __FILE__, __LINE__, 884 "SSI invalid UTF-8 after url-decode: %s", tb->ptr); 885 break; 886 } 887 buffer_path_simplify(tb); 888 char *sl = strrchr(r->physical.path.ptr, '/'); 889 if (NULL == sl) break; /*(not expected)*/ 890 buffer_copy_path_len2(p->stat_fn, 891 r->physical.path.ptr, 892 sl - r->physical.path.ptr + 1, 893 BUF_PTR_LEN(tb)); 894 } else { 895 /* virtual */ 896 897 buffer_clear(tb); 898 if (virt_path[0] != '/') { 899 /* there is always a / */ 900 const char * const sl = strrchr(r->uri.path.ptr, '/'); 901 buffer_copy_string_len(tb, r->uri.path.ptr, sl - r->uri.path.ptr + 1); 902 } 903 buffer_append_string(tb, virt_path); 904 905 buffer_urldecode_path(tb); 906 if (!buffer_is_valid_UTF8(tb)) { 907 log_error(r->conf.errh, __FILE__, __LINE__, 908 "SSI invalid UTF-8 after url-decode: %s", tb->ptr); 909 break; 910 } 911 buffer_path_simplify(tb); 912 913 /* we have an uri */ 914 915 /* Destination physical path (similar to code in mod_webdav.c) 916 * src r->physical.path might have been remapped with mod_alias, mod_userdir. 917 * (but neither modifies r->physical.rel_path) 918 * Find matching prefix to support relative paths to current physical path. 919 * Aliasing of paths underneath current r->physical.basedir might not work. 920 * Likewise, mod_rewrite URL rewriting might thwart this comparison. 921 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir. 922 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir. 923 * (Related, use mod_auth to protect this basedir, but avoid attempting to 924 * use mod_auth on paths underneath this basedir, as target path is not 925 * validated with mod_auth) 926 */ 927 928 /* find matching URI prefix 929 * check if remaining r->physical.rel_path matches suffix 930 * of r->physical.basedir so that we can use it to 931 * remap Destination physical path */ 932 { 933 const char *sep, *sep2; 934 sep = r->uri.path.ptr; 935 sep2 = tb->ptr; 936 for (i = 0; sep[i] && sep[i] == sep2[i]; ++i) ; 937 while (i != 0 && sep[--i] != '/') ; /* find matching directory path */ 938 } 939 if (r->conf.force_lowercase_filenames) { 940 buffer_to_lower(tb); 941 } 942 uint32_t remain = buffer_clen(&r->uri.path) - i; 943 uint32_t plen = buffer_clen(&r->physical.path); 944 if (plen >= remain 945 && (!r->conf.force_lowercase_filenames 946 ? 0 == memcmp(r->physical.path.ptr+plen-remain, r->physical.rel_path.ptr+i, remain) 947 : buffer_eq_icase_ssn(r->physical.path.ptr+plen-remain, r->physical.rel_path.ptr+i, remain))) { 948 buffer_copy_path_len2(p->stat_fn, 949 r->physical.path.ptr, 950 plen-remain, 951 tb->ptr+i, 952 buffer_clen(tb)-i); 953 } else { 954 /* unable to perform physical path remap here; 955 * assume doc_root/rel_path and no remapping */ 956 buffer_copy_path_len2(p->stat_fn, 957 BUF_PTR_LEN(&r->physical.doc_root), 958 BUF_PTR_LEN(tb)); 959 } 960 } 961 962 if (!r->conf.follow_symlink 963 && 0 != stat_cache_path_contains_symlink(p->stat_fn, r->conf.errh)) { 964 break; 965 } 966 967 int fd = stat_cache_open_rdonly_fstat(p->stat_fn, &stb, r->conf.follow_symlink); 968 if (fd >= 0) { 969 switch (ssicmd) { 970 case SSI_FSIZE: 971 buffer_clear(tb); 972 if (p->sizefmt) { 973 int j = 0; 974 const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL }; 975 976 off_t s = stb.st_size; 977 978 for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++); 979 980 buffer_append_int(tb, s); 981 buffer_append_string_len(tb, abr[j], j ? 3 : 2); 982 } else { 983 buffer_append_int(tb, stb.st_size); 984 } 985 chunkqueue_append_mem(cq, BUF_PTR_LEN(tb)); 986 break; 987 case SSI_FLASTMOD: 988 buffer_clear(tb); 989 mod_ssi_timefmt(tb, p->timefmt, stb.st_mtime, 1); 990 chunkqueue_append_mem(cq, BUF_PTR_LEN(tb)); 991 break; 992 case SSI_INCLUDE: 993 /* Keep the newest mtime of included files */ 994 if (include_file_last_mtime < TIME64_CAST(stb.st_mtime)) 995 include_file_last_mtime = TIME64_CAST(stb.st_mtime); 996 997 if (file_path || 0 == p->conf.ssi_recursion_max) { 998 /* don't process if #include file="..." is used */ 999 chunkqueue_append_file_fd(cq, p->stat_fn, fd, 0, stb.st_size); 1000 fd = -1; 1001 } else { 1002 buffer upsave, ppsave, prpsave; 1003 1004 /* only allow predefined recursion depth */ 1005 if (p->ssi_recursion_depth >= p->conf.ssi_recursion_max) { 1006 chunkqueue_append_mem(cq, CONST_STR_LEN("(error: include directives recurse deeper than pre-defined ssi.recursion-max)")); 1007 break; 1008 } 1009 1010 /* prevents simple infinite loop */ 1011 if (buffer_is_equal(&r->physical.path, p->stat_fn)) { 1012 chunkqueue_append_mem(cq, CONST_STR_LEN("(error: include directives create an infinite loop)")); 1013 break; 1014 } 1015 1016 /* save and restore r->physical.path, r->physical.rel_path, and r->uri.path around include 1017 * 1018 * tb contains url-decoded, path-simplified, and lowercased (if r->conf.force_lowercase) uri path of target. 1019 * r->uri.path and r->physical.rel_path are set to the same since we only operate on filenames here, 1020 * not full re-run of all modules for subrequest */ 1021 upsave = r->uri.path; 1022 ppsave = r->physical.path; 1023 prpsave = r->physical.rel_path; 1024 1025 r->physical.path = *p->stat_fn; 1026 memset(p->stat_fn, 0, sizeof(buffer)); 1027 1028 memset(&r->uri.path, 0, sizeof(buffer)); 1029 buffer_copy_buffer(&r->uri.path, tb); 1030 r->physical.rel_path = r->uri.path; 1031 1032 close(fd); 1033 fd = -1; 1034 1035 /*(ignore return value; muddle along as best we can if error occurs)*/ 1036 ++p->ssi_recursion_depth; 1037 mod_ssi_process_file(r, p, &stb); 1038 --p->ssi_recursion_depth; 1039 1040 free(r->uri.path.ptr); 1041 r->uri.path = upsave; 1042 r->physical.rel_path = prpsave; 1043 1044 free(p->stat_fn->ptr); 1045 *p->stat_fn = r->physical.path; 1046 r->physical.path = ppsave; 1047 } 1048 1049 break; 1050 } 1051 1052 if (fd >= 0) close(fd); 1053 } else { 1054 log_perror(r->conf.errh, __FILE__, __LINE__, 1055 "ssi: stating %s failed", p->stat_fn->ptr); 1056 } 1057 break; 1058 } 1059 case SSI_SET: { 1060 const char *key = NULL, *val = NULL; 1061 for (i = 2; i < n; i += 2) { 1062 if (0 == strcmp(l[i], "var")) { 1063 key = l[i+1]; 1064 } else if (0 == strcmp(l[i], "value")) { 1065 val = l[i+1]; 1066 } else { 1067 log_error(r->conf.errh, __FILE__, __LINE__, 1068 "ssi: unknown attribute for %s %s", l[1], l[i]); 1069 } 1070 } 1071 1072 if (p->if_is_false) break; 1073 1074 if (key && val) { 1075 array_set_key_value(p->ssi_vars, key, strlen(key), val, strlen(val)); 1076 } else if (key || val) { 1077 log_error(r->conf.errh, __FILE__, __LINE__, 1078 "ssi: var and value have to be set in <!--#set %s=%s -->", l[1], l[2]); 1079 } else { 1080 log_error(r->conf.errh, __FILE__, __LINE__, 1081 "ssi: var and value have to be set in <!--#set var=... value=... -->"); 1082 } 1083 break; 1084 } 1085 case SSI_CONFIG: 1086 if (p->if_is_false) break; 1087 1088 for (i = 2; i < n; i += 2) { 1089 if (0 == strcmp(l[i], "timefmt")) { 1090 buffer_copy_string(p->timefmt, l[i+1]); 1091 } else if (0 == strcmp(l[i], "sizefmt")) { 1092 if (0 == strcmp(l[i+1], "abbrev")) { 1093 p->sizefmt = 1; 1094 } else if (0 == strcmp(l[i+1], "bytes")) { 1095 p->sizefmt = 0; 1096 } else { 1097 log_error(r->conf.errh, __FILE__, __LINE__, 1098 "ssi: unknown value for attribute '%s' for %s %s", 1099 l[i], l[1], l[i+1]); 1100 } 1101 } else { 1102 log_error(r->conf.errh, __FILE__, __LINE__, 1103 "ssi: unknown attribute for %s %s", l[1], l[i]); 1104 } 1105 } 1106 break; 1107 case SSI_PRINTENV: 1108 if (p->if_is_false) break; 1109 1110 tb = r->tmp_buf; 1111 buffer_clear(tb); 1112 for (i = 0; i < p->ssi_vars->used; i++) { 1113 data_string *ds = (data_string *)p->ssi_vars->sorted[i]; 1114 1115 buffer_append_str2(tb, BUF_PTR_LEN(&ds->key), CONST_STR_LEN("=")); 1116 buffer_append_string_encoded(tb, BUF_PTR_LEN(&ds->value), ENCODING_MINIMAL_XML); 1117 buffer_append_char(tb, '\n'); 1118 } 1119 for (i = 0; i < p->ssi_cgi_env->used; i++) { 1120 data_string *ds = (data_string *)p->ssi_cgi_env->sorted[i]; 1121 1122 buffer_append_str2(tb, BUF_PTR_LEN(&ds->key), CONST_STR_LEN("=")); 1123 buffer_append_string_encoded(tb, BUF_PTR_LEN(&ds->value), ENCODING_MINIMAL_XML); 1124 buffer_append_char(tb, '\n'); 1125 } 1126 chunkqueue_append_mem(cq, BUF_PTR_LEN(tb)); 1127 break; 1128 case SSI_EXEC: { 1129 const char *cmd = NULL; 1130 pid_t pid; 1131 chunk *c; 1132 char *args[4]; 1133 log_error_st *errh = p->errh; 1134 1135 if (!p->conf.ssi_exec) { /* <!--#exec ... --> disabled by config */ 1136 break; 1137 } 1138 1139 for (i = 2; i < n; i += 2) { 1140 if (0 == strcmp(l[i], "cmd")) { 1141 cmd = l[i+1]; 1142 } else { 1143 log_error(errh, __FILE__, __LINE__, 1144 "ssi: unknown attribute for %s %s", l[1], l[i]); 1145 } 1146 } 1147 1148 if (p->if_is_false) break; 1149 1150 /* 1151 * as exec is assumed evil it is implemented synchronously 1152 */ 1153 1154 if (!cmd) break; 1155 1156 /* send cmd output to a temporary file */ 1157 if (0 != chunkqueue_append_mem_to_tempfile(cq, "", 0, errh)) break; 1158 c = cq->last; 1159 1160 *(const char **)&args[0] = "/bin/sh"; 1161 *(const char **)&args[1] = "-c"; 1162 *(const char **)&args[2] = cmd; 1163 args[3] = NULL; 1164 1165 int status = 0; 1166 struct stat stb; 1167 stb.st_size = 0; 1168 /*(expects STDIN_FILENO open to /dev/null)*/ 1169 int serrh_fd = r->conf.serrh ? r->conf.serrh->fd : -1; 1170 pid = fdevent_fork_execve(args[0], args, NULL, -1, c->file.fd, serrh_fd, -1); 1171 if (-1 == pid) { 1172 log_perror(errh, __FILE__, __LINE__, "spawning exec failed: %s", cmd); 1173 } else if (fdevent_waitpid(pid, &status, 0) < 0) { 1174 log_perror(errh, __FILE__, __LINE__, "waitpid failed"); 1175 } else { 1176 /* wait for the client to end */ 1177 /* NOTE: synchronous; blocks entire lighttpd server */ 1178 1179 /* 1180 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it 1181 */ 1182 if (!WIFEXITED(status)) { 1183 log_error(errh, __FILE__, __LINE__, "process exited abnormally: %s", cmd); 1184 } 1185 if (0 == fstat(c->file.fd, &stb)) { 1186 } 1187 } 1188 chunkqueue_update_file(cq, c, stb.st_size); 1189 break; 1190 } 1191 case SSI_IF: { 1192 const char *expr = NULL; 1193 1194 for (i = 2; i < n; i += 2) { 1195 if (0 == strcmp(l[i], "expr")) { 1196 expr = l[i+1]; 1197 } else { 1198 log_error(r->conf.errh, __FILE__, __LINE__, 1199 "ssi: unknown attribute for %s %s", l[1], l[i]); 1200 } 1201 } 1202 1203 if (!expr) { 1204 log_error(r->conf.errh, __FILE__, __LINE__, 1205 "ssi: %s expr missing", l[1]); 1206 break; 1207 } 1208 1209 if ((!p->if_is_false) && 1210 ((p->if_is_false_level == 0) || 1211 (p->if_level < p->if_is_false_level))) { 1212 switch (ssi_eval_expr(p, expr)) { 1213 case -1: 1214 case 0: 1215 p->if_is_false = 1; 1216 p->if_is_false_level = p->if_level; 1217 break; 1218 case 1: 1219 p->if_is_false = 0; 1220 break; 1221 } 1222 } 1223 1224 p->if_level++; 1225 1226 break; 1227 } 1228 case SSI_ELSE: 1229 p->if_level--; 1230 1231 if (p->if_is_false) { 1232 if ((p->if_level == p->if_is_false_level) && 1233 (p->if_is_false_endif == 0)) { 1234 p->if_is_false = 0; 1235 } 1236 } else { 1237 p->if_is_false = 1; 1238 1239 p->if_is_false_level = p->if_level; 1240 } 1241 p->if_level++; 1242 1243 break; 1244 case SSI_ELIF: { 1245 const char *expr = NULL; 1246 for (i = 2; i < n; i += 2) { 1247 if (0 == strcmp(l[i], "expr")) { 1248 expr = l[i+1]; 1249 } else { 1250 log_error(r->conf.errh, __FILE__, __LINE__, 1251 "ssi: unknown attribute for %s %s", l[1], l[i]); 1252 } 1253 } 1254 1255 if (!expr) { 1256 log_error(r->conf.errh, __FILE__, __LINE__, 1257 "ssi: %s expr missing", l[1]); 1258 break; 1259 } 1260 1261 p->if_level--; 1262 1263 if (p->if_level == p->if_is_false_level) { 1264 if ((p->if_is_false) && 1265 (p->if_is_false_endif == 0)) { 1266 switch (ssi_eval_expr(p, expr)) { 1267 case -1: 1268 case 0: 1269 p->if_is_false = 1; 1270 p->if_is_false_level = p->if_level; 1271 break; 1272 case 1: 1273 p->if_is_false = 0; 1274 break; 1275 } 1276 } else { 1277 p->if_is_false = 1; 1278 p->if_is_false_level = p->if_level; 1279 p->if_is_false_endif = 1; 1280 } 1281 } 1282 1283 p->if_level++; 1284 1285 break; 1286 } 1287 case SSI_ENDIF: 1288 p->if_level--; 1289 1290 if (p->if_level == p->if_is_false_level) { 1291 p->if_is_false = 0; 1292 p->if_is_false_endif = 0; 1293 } 1294 1295 break; 1296 case SSI_COMMENT: 1297 break; 1298 default: 1299 log_error(r->conf.errh, __FILE__, __LINE__, 1300 "ssi: unknown ssi-command: %s", l[1]); 1301 break; 1302 } 1303 1304 return 0; 1305 1306 } 1307 1308 __attribute_pure__ 1309 static int mod_ssi_parse_ssi_stmt_value(const unsigned char * const s, const int len) { 1310 int n; 1311 const int c = (s[0] == '"' ? '"' : s[0] == '\'' ? '\'' : 0); 1312 if (0 != c) { 1313 for (n = 1; n < len; ++n) { 1314 if (s[n] == c) return n+1; 1315 if (s[n] == '\\') { 1316 if (n+1 == len) return 0; /* invalid */ 1317 ++n; 1318 } 1319 } 1320 return 0; /* invalid */ 1321 } else { 1322 for (n = 0; n < len; ++n) { 1323 if (isspace(s[n])) return n; 1324 if (s[n] == '\\') { 1325 if (n+1 == len) return 0; /* invalid */ 1326 ++n; 1327 } 1328 } 1329 return n; 1330 } 1331 } 1332 1333 static int mod_ssi_parse_ssi_stmt_offlen(int o[10], const unsigned char * const s, const int len) { 1334 1335 /** 1336 * <!--#element attribute=value attribute=value ... --> 1337 */ 1338 1339 /* s must begin "<!--#" and must end with "-->" */ 1340 int n = 5; 1341 o[0] = n; 1342 for (; light_isalpha(s[n]); ++n) ; /*(n = 5 to begin after "<!--#")*/ 1343 o[1] = n - o[0]; 1344 if (0 == o[1]) return -1; /* empty token */ 1345 1346 if (n+3 == len) return 2; /* token only; no params */ 1347 if (!isspace(s[n])) return -1; 1348 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */ 1349 if (n+3 == len) return 2; /* token only; no params */ 1350 1351 o[2] = n; 1352 for (; light_isalpha(s[n]); ++n) ; 1353 o[3] = n - o[2]; 1354 if (0 == o[3] || s[n++] != '=') return -1; 1355 1356 o[4] = n; 1357 o[5] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3); 1358 if (0 == o[5]) return -1; /* empty or invalid token */ 1359 n += o[5]; 1360 1361 if (n+3 == len) return 6; /* token and one param */ 1362 if (!isspace(s[n])) return -1; 1363 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */ 1364 if (n+3 == len) return 6; /* token and one param */ 1365 1366 o[6] = n; 1367 for (; light_isalpha(s[n]); ++n) ; 1368 o[7] = n - o[6]; 1369 if (0 == o[7] || s[n++] != '=') return -1; 1370 1371 o[8] = n; 1372 o[9] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3); 1373 if (0 == o[9]) return -1; /* empty or invalid token */ 1374 n += o[9]; 1375 1376 if (n+3 == len) return 10; /* token and two params */ 1377 if (!isspace(s[n])) return -1; 1378 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */ 1379 if (n+3 == len) return 10; /* token and two params */ 1380 return -1; 1381 } 1382 1383 static void mod_ssi_parse_ssi_stmt(request_st * const r, handler_ctx * const p, char * const s, int len, struct stat * const st) { 1384 1385 /** 1386 * <!--#element attribute=value attribute=value ... --> 1387 */ 1388 1389 int o[10]; 1390 int m; 1391 const int n = mod_ssi_parse_ssi_stmt_offlen(o, (unsigned char *)s, len); 1392 char *l[6] = { s, NULL, NULL, NULL, NULL, NULL }; 1393 if (-1 == n) { 1394 /* ignore <!--#comment ... --> */ 1395 if (len >= 16 1396 && 0 == memcmp(s+5, "comment", sizeof("comment")-1) 1397 && (s[12] == ' ' || s[12] == '\t')) 1398 return; 1399 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */ 1400 chunkqueue_append_mem(&p->wq, s, len); /* append stmt as-is */ 1401 return; 1402 } 1403 1404 #if 0 1405 /* dup s and then modify s */ 1406 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/ 1407 l[0] = ck_malloc((size_t)(len+1)); 1408 memcpy(l[0], s, (size_t)len); 1409 (l[0])[len] = '\0'; 1410 #endif 1411 1412 /* modify s in-place to split string into arg tokens */ 1413 for (m = 0; m < n; m += 2) { 1414 char *ptr = s+o[m]; 1415 switch (*ptr) { 1416 case '"': 1417 case '\'': (++ptr)[o[m+1]-2] = '\0'; break; 1418 default: ptr[o[m+1]] = '\0'; break; 1419 } 1420 l[1+(m>>1)] = ptr; 1421 if (m == 4 || m == 8) { 1422 /* XXX: removing '\\' escapes from param value would be 1423 * the right thing to do, but would potentially change 1424 * current behavior, e.g. <!--#exec cmd=... --> */ 1425 } 1426 } 1427 1428 process_ssi_stmt(r, p, (const char **)l, 1+(n>>1), st); 1429 1430 #if 0 1431 free(l[0]); 1432 #endif 1433 } 1434 1435 static int mod_ssi_stmt_len(const char *s, const int len) { 1436 /* s must begin "<!--#" */ 1437 int n, sq = 0, dq = 0, bs = 0; 1438 for (n = 5; n < len; ++n) { /*(n = 5 to begin after "<!--#")*/ 1439 switch (s[n]) { 1440 default: 1441 break; 1442 case '-': 1443 if (!sq && !dq && n+2 < len && s[n+1] == '-' && s[n+2] == '>') return n+3; /* found end of stmt */ 1444 break; 1445 case '"': 1446 if (!sq && (!dq || !bs)) dq = !dq; 1447 break; 1448 case '\'': 1449 if (!dq && (!sq || !bs)) sq = !sq; 1450 break; 1451 case '\\': 1452 if (sq || dq) bs = !bs; 1453 break; 1454 } 1455 } 1456 return 0; /* incomplete directive "<!--#...-->" */ 1457 } 1458 1459 static void mod_ssi_read_fd(request_st * const r, handler_ctx * const p, struct stat * const st, int fd) { 1460 ssize_t rd; 1461 size_t offset, pretag; 1462 /* allocate to reduce chance of stack exhaustion upon deep recursion */ 1463 buffer * const b = chunk_buffer_acquire(); 1464 chunkqueue * const cq = &p->wq; 1465 const size_t bufsz = 8192; 1466 chunk_buffer_prepare_append(b, bufsz-1); 1467 char * const buf = b->ptr; 1468 1469 offset = 0; 1470 pretag = 0; 1471 while (0 < (rd = read(fd, buf+offset, bufsz-offset))) { 1472 char *s; 1473 size_t prelen = 0, len; 1474 offset += (size_t)rd; 1475 for (; (s = memchr(buf+prelen, '<', offset-prelen)); ++prelen) { 1476 prelen = s - buf; 1477 if (prelen + 5 <= offset) { /*("<!--#" is 5 chars)*/ 1478 if (0 != memcmp(s+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */ 1479 1480 if (prelen - pretag && !p->if_is_false) { 1481 chunkqueue_append_mem(cq, buf+pretag, prelen-pretag); 1482 } 1483 1484 len = mod_ssi_stmt_len(buf+prelen, offset-prelen); 1485 if (len) { /* num of chars to be consumed */ 1486 mod_ssi_parse_ssi_stmt(r, p, buf+prelen, len, st); 1487 prelen += (len - 1); /* offset to '>' at end of SSI directive; incremented at top of loop */ 1488 pretag = prelen + 1; 1489 if (pretag == offset) { 1490 offset = pretag = 0; 1491 break; 1492 } 1493 } else if (0 == prelen && offset == bufsz) { /*(full buf)*/ 1494 /* SSI statement is way too long 1495 * NOTE: skipping this buf will expose *the rest* of this SSI statement */ 1496 chunkqueue_append_mem(cq, CONST_STR_LEN("<!-- [an error occurred: directive too long] ")); 1497 /* check if buf ends with "-" or "--" which might be part of "-->" 1498 * (buf contains at least 5 chars for "<!--#") */ 1499 if (buf[offset-2] == '-' && buf[offset-1] == '-') { 1500 chunkqueue_append_mem(cq, CONST_STR_LEN("--")); 1501 } else if (buf[offset-1] == '-') { 1502 chunkqueue_append_mem(cq, CONST_STR_LEN("-")); 1503 } 1504 offset = pretag = 0; 1505 break; 1506 } else { /* incomplete directive "<!--#...-->" */ 1507 memmove(buf, buf+prelen, (offset -= prelen)); 1508 pretag = 0; 1509 break; 1510 } 1511 } else if (prelen + 1 == offset || 0 == memcmp(s+1, "!--", offset - prelen - 1)) { 1512 if (prelen - pretag && !p->if_is_false) { 1513 chunkqueue_append_mem(cq, buf+pretag, prelen-pretag); 1514 } 1515 memcpy(buf, buf+prelen, (offset -= prelen)); 1516 pretag = 0; 1517 break; 1518 } 1519 /* loop to look for next '<' */ 1520 } 1521 if (offset == bufsz) { 1522 if (!p->if_is_false) { 1523 chunkqueue_append_mem(cq, buf+pretag, offset-pretag); 1524 } 1525 offset = pretag = 0; 1526 } 1527 /* flush intermediate cq to r->write_queue (and possibly to 1528 * temporary file) if last MEM_CHUNK has less than 1k-1 avail 1529 * (reduce occurrence of copying to reallocate larger chunk) */ 1530 if (cq->last && cq->last->type == MEM_CHUNK 1531 && buffer_string_space(cq->last->mem) < 1023) 1532 if (0 != http_chunk_transfer_cqlen(r, cq, chunkqueue_length(cq))) 1533 chunkqueue_remove_empty_chunks(&r->write_queue); 1534 /*(likely unrecoverable error if r->resp_send_chunked)*/ 1535 } 1536 1537 if (0 != rd) { 1538 log_perror(r->conf.errh, __FILE__, __LINE__, 1539 "read(): %s", r->physical.path.ptr); 1540 } 1541 1542 if (offset - pretag) { 1543 /* copy remaining data in buf */ 1544 if (!p->if_is_false) { 1545 chunkqueue_append_mem(cq, buf+pretag, offset-pretag); 1546 } 1547 } 1548 1549 chunk_buffer_release(b); 1550 if (0 != http_chunk_transfer_cqlen(r, cq, chunkqueue_length(cq))) 1551 chunkqueue_remove_empty_chunks(&r->write_queue); 1552 /*(likely error unrecoverable if r->resp_send_chunked)*/ 1553 } 1554 1555 1556 static int mod_ssi_process_file(request_st * const r, handler_ctx * const p, struct stat * const st) { 1557 int fd = stat_cache_open_rdonly_fstat(&r->physical.path, st, r->conf.follow_symlink); 1558 if (-1 == fd) { 1559 log_perror(r->conf.errh, __FILE__, __LINE__, 1560 "open(): %s", r->physical.path.ptr); 1561 return -1; 1562 } 1563 1564 mod_ssi_read_fd(r, p, st, fd); 1565 1566 close(fd); 1567 return 0; 1568 } 1569 1570 1571 static int mod_ssi_handle_request(request_st * const r, handler_ctx * const p) { 1572 struct stat st; 1573 1574 /* get a stream to the file */ 1575 1576 buffer_clear(p->timefmt); 1577 array_reset_data_strings(p->ssi_vars); 1578 array_reset_data_strings(p->ssi_cgi_env); 1579 build_ssi_cgi_vars(r, p); 1580 1581 /* Reset the modified time of included files */ 1582 include_file_last_mtime = 0; 1583 1584 if (mod_ssi_process_file(r, p, &st)) return -1; 1585 1586 r->resp_body_started = 1; 1587 r->resp_body_finished = 1; 1588 1589 if (!p->conf.content_type) { 1590 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); 1591 } else { 1592 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), BUF_PTR_LEN(p->conf.content_type)); 1593 } 1594 1595 if (p->conf.conditional_requests) { 1596 /* Generate "ETag" & "Last-Modified" headers */ 1597 1598 /* use most recently modified include file for ETag and Last-Modified */ 1599 if (TIME64_CAST(st.st_mtime) < include_file_last_mtime) 1600 st.st_mtime = include_file_last_mtime; 1601 1602 http_etag_create(r->tmp_buf, &st, r->conf.etag_flags); 1603 http_header_response_set(r, HTTP_HEADER_ETAG, CONST_STR_LEN("ETag"), BUF_PTR_LEN(r->tmp_buf)); 1604 1605 const buffer * const mtime = http_response_set_last_modified(r, st.st_mtime); 1606 if (HANDLER_FINISHED == http_response_handle_cachable(r, mtime, st.st_mtime)) { 1607 /* ok, the client already has our content, 1608 * no need to send it again */ 1609 1610 chunkqueue_reset(&r->write_queue); 1611 } 1612 } 1613 1614 /* Reset the modified time of included files */ 1615 include_file_last_mtime = 0; 1616 1617 return 0; 1618 } 1619 1620 URIHANDLER_FUNC(mod_ssi_physical_path) { 1621 plugin_data *p = p_d; 1622 1623 if (NULL != r->handler_module) return HANDLER_GO_ON; 1624 /* r->physical.path is non-empty for handle_subrequest_start */ 1625 /*if (buffer_is_blank(&r->physical.path)) return HANDLER_GO_ON;*/ 1626 1627 mod_ssi_patch_config(r, p); 1628 if (NULL == p->conf.ssi_extension) return HANDLER_GO_ON; 1629 1630 if (array_match_value_suffix(p->conf.ssi_extension, &r->physical.path)) { 1631 r->plugin_ctx[p->id] = handler_ctx_init(p, r->conf.errh); 1632 r->handler_module = p->self; 1633 } 1634 1635 return HANDLER_GO_ON; 1636 } 1637 1638 SUBREQUEST_FUNC(mod_ssi_handle_subrequest) { 1639 plugin_data *p = p_d; 1640 handler_ctx *hctx = r->plugin_ctx[p->id]; 1641 if (NULL == hctx) return HANDLER_GO_ON; 1642 /* 1643 * NOTE: if mod_ssi modified to use fdevents, HANDLER_WAIT_FOR_EVENT, 1644 * instead of blocking to completion, then hctx->timefmt, hctx->ssi_vars, 1645 * and hctx->ssi_cgi_env should be allocated and cleaned up per request. 1646 */ 1647 1648 /* handle ssi-request */ 1649 1650 if (mod_ssi_handle_request(r, hctx)) { 1651 /* on error */ 1652 r->http_status = 500; 1653 r->handler_module = NULL; 1654 } 1655 1656 return HANDLER_FINISHED; 1657 } 1658 1659 static handler_t mod_ssi_handle_request_reset(request_st * const r, void *p_d) { 1660 plugin_data *p = p_d; 1661 handler_ctx *hctx = r->plugin_ctx[p->id]; 1662 if (hctx) { 1663 handler_ctx_free(hctx); 1664 r->plugin_ctx[p->id] = NULL; 1665 } 1666 1667 return HANDLER_GO_ON; 1668 } 1669 1670 1671 __attribute_cold__ 1672 int mod_ssi_plugin_init(plugin *p); 1673 int mod_ssi_plugin_init(plugin *p) { 1674 p->version = LIGHTTPD_VERSION_ID; 1675 p->name = "ssi"; 1676 1677 p->init = mod_ssi_init; 1678 p->handle_subrequest_start = mod_ssi_physical_path; 1679 p->handle_subrequest = mod_ssi_handle_subrequest; 1680 p->handle_request_reset = mod_ssi_handle_request_reset; 1681 p->set_defaults = mod_ssi_set_defaults; 1682 p->cleanup = mod_ssi_free; 1683 1684 return 0; 1685 } 1686