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