1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "apr.h" 18#include "apr_strings.h" 19#include "apr_thread_proc.h" 20#include "apr_hash.h" 21#include "apr_user.h" 22#include "apr_lib.h" 23#include "apr_optional.h" 24 25#define APR_WANT_STRFUNC 26#define APR_WANT_MEMFUNC 27#include "apr_want.h" 28 29#include "ap_config.h" 30#include "util_filter.h" 31#include "httpd.h" 32#include "http_config.h" 33#include "http_core.h" 34#include "http_request.h" 35#include "http_core.h" 36#include "http_protocol.h" 37#include "http_log.h" 38#include "http_main.h" 39#include "util_script.h" 40#include "http_core.h" 41#include "mod_include.h" 42#include "ap_expr.h" 43 44/* helper for Latin1 <-> entity encoding */ 45#if APR_CHARSET_EBCDIC 46#include "util_ebcdic.h" 47#define RAW_ASCII_CHAR(ch) apr_xlate_conv_byte(ap_hdrs_from_ascii, \ 48 (unsigned char)ch) 49#else /* APR_CHARSET_EBCDIC */ 50#define RAW_ASCII_CHAR(ch) (ch) 51#endif /* !APR_CHARSET_EBCDIC */ 52 53 54/* 55 * +-------------------------------------------------------+ 56 * | | 57 * | Types and Structures 58 * | | 59 * +-------------------------------------------------------+ 60 */ 61 62/* sll used for string expansion */ 63typedef struct result_item { 64 struct result_item *next; 65 apr_size_t len; 66 const char *string; 67} result_item_t; 68 69/* conditional expression parser stuff */ 70typedef enum { 71 TOKEN_STRING, 72 TOKEN_RE, 73 TOKEN_AND, 74 TOKEN_OR, 75 TOKEN_NOT, 76 TOKEN_EQ, 77 TOKEN_NE, 78 TOKEN_RBRACE, 79 TOKEN_LBRACE, 80 TOKEN_GROUP, 81 TOKEN_GE, 82 TOKEN_LE, 83 TOKEN_GT, 84 TOKEN_LT, 85 TOKEN_ACCESS 86} token_type_t; 87 88typedef struct { 89 token_type_t type; 90 const char *value; 91#ifdef DEBUG_INCLUDE 92 const char *s; 93#endif 94} token_t; 95 96typedef struct parse_node { 97 struct parse_node *parent; 98 struct parse_node *left; 99 struct parse_node *right; 100 token_t token; 101 int value; 102 int done; 103#ifdef DEBUG_INCLUDE 104 int dump_done; 105#endif 106} parse_node_t; 107 108typedef enum { 109 XBITHACK_OFF, 110 XBITHACK_ON, 111 XBITHACK_FULL, 112 XBITHACK_UNSET 113} xbithack_t; 114 115typedef struct { 116 const char *default_error_msg; 117 const char *default_time_fmt; 118 const char *undefined_echo; 119 xbithack_t xbithack; 120 signed char lastmodified; 121 signed char etag; 122 signed char legacy_expr; 123} include_dir_config; 124 125typedef struct { 126 const char *default_start_tag; 127 const char *default_end_tag; 128} include_server_config; 129 130/* main parser states */ 131typedef enum { 132 PARSE_PRE_HEAD, 133 PARSE_HEAD, 134 PARSE_DIRECTIVE, 135 PARSE_DIRECTIVE_POSTNAME, 136 PARSE_DIRECTIVE_TAIL, 137 PARSE_DIRECTIVE_POSTTAIL, 138 PARSE_PRE_ARG, 139 PARSE_ARG, 140 PARSE_ARG_NAME, 141 PARSE_ARG_POSTNAME, 142 PARSE_ARG_EQ, 143 PARSE_ARG_PREVAL, 144 PARSE_ARG_VAL, 145 PARSE_ARG_VAL_ESC, 146 PARSE_ARG_POSTVAL, 147 PARSE_TAIL, 148 PARSE_TAIL_SEQ, 149 PARSE_EXECUTE 150} parse_state_t; 151 152typedef struct arg_item { 153 struct arg_item *next; 154 char *name; 155 apr_size_t name_len; 156 char *value; 157 apr_size_t value_len; 158} arg_item_t; 159 160typedef struct { 161 const char *source; 162 const char *rexp; 163 apr_size_t nsub; 164 ap_regmatch_t match[AP_MAX_REG_MATCH]; 165 int have_match; 166} backref_t; 167 168typedef struct { 169 unsigned int T[256]; 170 unsigned int x; 171 apr_size_t pattern_len; 172} bndm_t; 173 174struct ssi_internal_ctx { 175 parse_state_t state; 176 int seen_eos; 177 int error; 178 char quote; /* quote character value (or \0) */ 179 apr_size_t parse_pos; /* parse position of partial matches */ 180 apr_size_t bytes_read; 181 182 apr_bucket_brigade *tmp_bb; 183 184 const char *start_seq; 185 bndm_t *start_seq_pat; 186 const char *end_seq; 187 apr_size_t end_seq_len; 188 char *directive; /* name of the current directive */ 189 apr_size_t directive_len; /* length of the current directive name */ 190 191 arg_item_t *current_arg; /* currently parsed argument */ 192 arg_item_t *argv; /* all arguments */ 193 194 backref_t *re; /* NULL if there wasn't a regex yet */ 195 196 const char *undefined_echo; 197 apr_size_t undefined_echo_len; 198 199 char legacy_expr; /* use ap_expr or legacy mod_include 200 expression parser? */ 201 202 ap_expr_eval_ctx_t *expr_eval_ctx; /* NULL if there wasn't an ap_expr yet */ 203 const char *expr_vary_this; /* for use by ap_expr_eval_ctx */ 204 const char *expr_err; /* for use by ap_expr_eval_ctx */ 205#ifdef DEBUG_INCLUDE 206 struct { 207 ap_filter_t *f; 208 apr_bucket_brigade *bb; 209 } debug; 210#endif 211}; 212 213 214/* 215 * +-------------------------------------------------------+ 216 * | | 217 * | Debugging Utilities 218 * | | 219 * +-------------------------------------------------------+ 220 */ 221 222#ifdef DEBUG_INCLUDE 223 224#define TYPE_TOKEN(token, ttype) do { \ 225 (token)->type = ttype; \ 226 (token)->s = #ttype; \ 227} while(0) 228 229#define CREATE_NODE(ctx, name) do { \ 230 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \ 231 (name)->parent = (name)->left = (name)->right = NULL; \ 232 (name)->done = 0; \ 233 (name)->dump_done = 0; \ 234} while(0) 235 236static void debug_printf(include_ctx_t *ctx, const char *fmt, ...) 237{ 238 va_list ap; 239 char *debug__str; 240 241 va_start(ap, fmt); 242 debug__str = apr_pvsprintf(ctx->pool, fmt, ap); 243 va_end(ap); 244 245 APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create( 246 debug__str, strlen(debug__str), ctx->pool, 247 ctx->intern->debug.f->c->bucket_alloc)); 248} 249 250#define DUMP__CHILD(ctx, is, node, child) if (1) { \ 251 parse_node_t *d__c = node->child; \ 252 if (d__c) { \ 253 if (!d__c->dump_done) { \ 254 if (d__c->parent != node) { \ 255 debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \ 256 if (!d__c->parent) { \ 257 debug_printf(ctx, "Parent of " #child " child node is " \ 258 "NULL.\n"); \ 259 } \ 260 else { \ 261 debug_printf(ctx, "Parent of " #child " child node " \ 262 "points to another node (of type %s)!\n", \ 263 d__c->parent->token.s); \ 264 } \ 265 return; \ 266 } \ 267 node = d__c; \ 268 continue; \ 269 } \ 270 } \ 271 else { \ 272 debug_printf(ctx, "%s(missing)\n", is); \ 273 } \ 274} 275 276static void debug_dump_tree(include_ctx_t *ctx, parse_node_t *root) 277{ 278 parse_node_t *current; 279 char *is; 280 281 if (!root) { 282 debug_printf(ctx, " -- Parse Tree empty --\n\n"); 283 return; 284 } 285 286 debug_printf(ctx, " ----- Parse Tree -----\n"); 287 current = root; 288 is = " "; 289 290 while (current) { 291 switch (current->token.type) { 292 case TOKEN_STRING: 293 case TOKEN_RE: 294 debug_printf(ctx, "%s%s (%s)\n", is, current->token.s, 295 current->token.value); 296 current->dump_done = 1; 297 current = current->parent; 298 continue; 299 300 case TOKEN_NOT: 301 case TOKEN_GROUP: 302 case TOKEN_RBRACE: 303 case TOKEN_LBRACE: 304 if (!current->dump_done) { 305 debug_printf(ctx, "%s%s\n", is, current->token.s); 306 is = apr_pstrcat(ctx->dpool, is, " ", NULL); 307 current->dump_done = 1; 308 } 309 310 DUMP__CHILD(ctx, is, current, right) 311 312 if (!current->right || current->right->dump_done) { 313 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4); 314 if (current->right) current->right->dump_done = 0; 315 current = current->parent; 316 } 317 continue; 318 319 default: 320 if (!current->dump_done) { 321 debug_printf(ctx, "%s%s\n", is, current->token.s); 322 is = apr_pstrcat(ctx->dpool, is, " ", NULL); 323 current->dump_done = 1; 324 } 325 326 DUMP__CHILD(ctx, is, current, left) 327 DUMP__CHILD(ctx, is, current, right) 328 329 if ((!current->left || current->left->dump_done) && 330 (!current->right || current->right->dump_done)) { 331 332 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4); 333 if (current->left) current->left->dump_done = 0; 334 if (current->right) current->right->dump_done = 0; 335 current = current->parent; 336 } 337 continue; 338 } 339 } 340 341 /* it is possible to call this function within the parser loop, to see 342 * how the tree is built. That way, we must cleanup after us to dump 343 * always the whole tree 344 */ 345 root->dump_done = 0; 346 if (root->left) root->left->dump_done = 0; 347 if (root->right) root->right->dump_done = 0; 348 349 debug_printf(ctx, " --- End Parse Tree ---\n\n"); 350 351 return; 352} 353 354#define DEBUG_INIT(ctx, filter, brigade) do { \ 355 (ctx)->intern->debug.f = filter; \ 356 (ctx)->intern->debug.bb = brigade; \ 357} while(0) 358 359#define DEBUG_PRINTF(arg) debug_printf arg 360 361#define DEBUG_DUMP_TOKEN(ctx, token) do { \ 362 token_t *d__t = (token); \ 363 \ 364 if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \ 365 DEBUG_PRINTF(((ctx), " Found: %s (%s)\n", d__t->s, d__t->value)); \ 366 } \ 367 else { \ 368 DEBUG_PRINTF((ctx, " Found: %s\n", d__t->s)); \ 369 } \ 370} while(0) 371 372#define DEBUG_DUMP_EVAL(ctx, node) do { \ 373 char c = '"'; \ 374 switch ((node)->token.type) { \ 375 case TOKEN_STRING: \ 376 debug_printf((ctx), " Evaluate: %s (%s) -> %c\n", (node)->token.s,\ 377 (node)->token.value, ((node)->value) ? '1':'0'); \ 378 break; \ 379 case TOKEN_AND: \ 380 case TOKEN_OR: \ 381 debug_printf((ctx), " Evaluate: %s (Left: %s; Right: %s) -> %c\n",\ 382 (node)->token.s, \ 383 (((node)->left->done) ? ((node)->left->value ?"1":"0") \ 384 : "short circuited"), \ 385 (((node)->right->done) ? ((node)->right->value?"1":"0") \ 386 : "short circuited"), \ 387 (node)->value ? '1' : '0'); \ 388 break; \ 389 case TOKEN_EQ: \ 390 case TOKEN_NE: \ 391 case TOKEN_GT: \ 392 case TOKEN_GE: \ 393 case TOKEN_LT: \ 394 case TOKEN_LE: \ 395 if ((node)->right->token.type == TOKEN_RE) c = '/'; \ 396 debug_printf((ctx), " Compare: %s (\"%s\" with %c%s%c) -> %c\n", \ 397 (node)->token.s, \ 398 (node)->left->token.value, \ 399 c, (node)->right->token.value, c, \ 400 (node)->value ? '1' : '0'); \ 401 break; \ 402 default: \ 403 debug_printf((ctx), " Evaluate: %s -> %c\n", (node)->token.s, \ 404 (node)->value ? '1' : '0'); \ 405 break; \ 406 } \ 407} while(0) 408 409#define DEBUG_DUMP_UNMATCHED(ctx, unmatched) do { \ 410 if (unmatched) { \ 411 DEBUG_PRINTF(((ctx), " Unmatched %c\n", (char)(unmatched))); \ 412 } \ 413} while(0) 414 415#define DEBUG_DUMP_COND(ctx, text) \ 416 DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \ 417 ((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0')) 418 419#define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root) 420 421#else /* DEBUG_INCLUDE */ 422 423#define TYPE_TOKEN(token, ttype) (token)->type = ttype 424 425#define CREATE_NODE(ctx, name) do { \ 426 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \ 427 (name)->parent = (name)->left = (name)->right = NULL; \ 428 (name)->done = 0; \ 429} while(0) 430 431#define DEBUG_INIT(ctx, f, bb) 432#define DEBUG_PRINTF(arg) 433#define DEBUG_DUMP_TOKEN(ctx, token) 434#define DEBUG_DUMP_EVAL(ctx, node) 435#define DEBUG_DUMP_UNMATCHED(ctx, unmatched) 436#define DEBUG_DUMP_COND(ctx, text) 437#define DEBUG_DUMP_TREE(ctx, root) 438 439#endif /* !DEBUG_INCLUDE */ 440 441 442/* 443 * +-------------------------------------------------------+ 444 * | | 445 * | Static Module Data 446 * | | 447 * +-------------------------------------------------------+ 448 */ 449 450/* global module structure */ 451module AP_MODULE_DECLARE_DATA include_module; 452 453/* function handlers for include directives */ 454static apr_hash_t *include_handlers; 455 456/* forward declaration of handler registry */ 457static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register; 458 459/* Sentinel value to store in subprocess_env for items that 460 * shouldn't be evaluated until/unless they're actually used 461 */ 462static const char lazy_eval_sentinel = '\0'; 463#define LAZY_VALUE (&lazy_eval_sentinel) 464 465/* default values */ 466#define DEFAULT_START_SEQUENCE "<!--#" 467#define DEFAULT_END_SEQUENCE "-->" 468#define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]" 469#define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z" 470#define DEFAULT_UNDEFINED_ECHO "(none)" 471 472#define UNSET -1 473 474#ifdef XBITHACK 475#define DEFAULT_XBITHACK XBITHACK_FULL 476#else 477#define DEFAULT_XBITHACK XBITHACK_OFF 478#endif 479 480 481/* 482 * +-------------------------------------------------------+ 483 * | | 484 * | Environment/Expansion Functions 485 * | | 486 * +-------------------------------------------------------+ 487 */ 488 489/* 490 * decodes a string containing html entities or numeric character references. 491 * 's' is overwritten with the decoded string. 492 * If 's' is syntatically incorrect, then the followed fixups will be made: 493 * unknown entities will be left undecoded; 494 * references to unused numeric characters will be deleted. 495 * In particular, � will not be decoded, but will be deleted. 496 */ 497 498/* maximum length of any ISO-LATIN-1 HTML entity name. */ 499#define MAXENTLEN (6) 500 501/* The following is a shrinking transformation, therefore safe. */ 502 503static void decodehtml(char *s) 504{ 505 int val, i, j; 506 char *p; 507 const char *ents; 508 static const char * const entlist[MAXENTLEN + 1] = 509 { 510 NULL, /* 0 */ 511 NULL, /* 1 */ 512 "lt\074gt\076", /* 2 */ 513 "amp\046ETH\320eth\360", /* 3 */ 514 "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml" 515 "\353iuml\357ouml\366uuml\374yuml\377", /* 4 */ 516 517 "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc" 518 "\333THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352" 519 "icirc\356ocirc\364ucirc\373thorn\376", /* 5 */ 520 521 "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311" 522 "Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde" 523 "\325Oslash\330Ugrave\331Uacute\332Yacute\335agrave\340" 524 "aacute\341atilde\343ccedil\347egrave\350eacute\351igrave" 525 "\354iacute\355ntilde\361ograve\362oacute\363otilde\365" 526 "oslash\370ugrave\371uacute\372yacute\375" /* 6 */ 527 }; 528 529 /* Do a fast scan through the string until we find anything 530 * that needs more complicated handling 531 */ 532 for (; *s != '&'; s++) { 533 if (*s == '\0') { 534 return; 535 } 536 } 537 538 for (p = s; *s != '\0'; s++, p++) { 539 if (*s != '&') { 540 *p = *s; 541 continue; 542 } 543 /* find end of entity */ 544 for (i = 1; s[i] != ';' && s[i] != '\0'; i++) { 545 continue; 546 } 547 548 if (s[i] == '\0') { /* treat as normal data */ 549 *p = *s; 550 continue; 551 } 552 553 /* is it numeric ? */ 554 if (s[1] == '#') { 555 for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) { 556 val = val * 10 + s[j] - '0'; 557 } 558 s += i; 559 if (j < i || val <= 8 || (val >= 11 && val <= 31) || 560 (val >= 127 && val <= 160) || val >= 256) { 561 p--; /* no data to output */ 562 } 563 else { 564 *p = RAW_ASCII_CHAR(val); 565 } 566 } 567 else { 568 j = i - 1; 569 if (j > MAXENTLEN || entlist[j] == NULL) { 570 /* wrong length */ 571 *p = '&'; 572 continue; /* skip it */ 573 } 574 for (ents = entlist[j]; *ents != '\0'; ents += i) { 575 if (strncmp(s + 1, ents, j) == 0) { 576 break; 577 } 578 } 579 580 if (*ents == '\0') { 581 *p = '&'; /* unknown */ 582 } 583 else { 584 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]); 585 s += i; 586 } 587 } 588 } 589 590 *p = '\0'; 591} 592 593static void add_include_vars(request_rec *r) 594{ 595 apr_table_t *e = r->subprocess_env; 596 char *t; 597 598 apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE); 599 apr_table_setn(e, "DATE_GMT", LAZY_VALUE); 600 apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE); 601 apr_table_setn(e, "DOCUMENT_URI", r->uri); 602 if (r->path_info && *r->path_info) { 603 apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info); 604 } 605 apr_table_setn(e, "USER_NAME", LAZY_VALUE); 606 if (r->filename && (t = strrchr(r->filename, '/'))) { 607 apr_table_setn(e, "DOCUMENT_NAME", ++t); 608 } 609 else { 610 apr_table_setn(e, "DOCUMENT_NAME", r->uri); 611 } 612 if (r->args) { 613 char *arg_copy = apr_pstrdup(r->pool, r->args); 614 615 ap_unescape_url(arg_copy); 616 apr_table_setn(e, "QUERY_STRING_UNESCAPED", 617 ap_escape_shell_cmd(r->pool, arg_copy)); 618 } 619} 620 621static const char *add_include_vars_lazy(request_rec *r, const char *var, const char *timefmt) 622{ 623 char *val; 624 if (!strcasecmp(var, "DATE_LOCAL")) { 625 val = ap_ht_time(r->pool, r->request_time, timefmt, 0); 626 } 627 else if (!strcasecmp(var, "DATE_GMT")) { 628 val = ap_ht_time(r->pool, r->request_time, timefmt, 1); 629 } 630 else if (!strcasecmp(var, "LAST_MODIFIED")) { 631 val = ap_ht_time(r->pool, r->finfo.mtime, timefmt, 0); 632 } 633 else if (!strcasecmp(var, "USER_NAME")) { 634 if (apr_uid_name_get(&val, r->finfo.user, r->pool) != APR_SUCCESS) { 635 val = "<unknown>"; 636 } 637 } 638 else { 639 val = NULL; 640 } 641 642 if (val) { 643 apr_table_setn(r->subprocess_env, var, val); 644 } 645 return val; 646} 647 648static const char *get_include_var(const char *var, include_ctx_t *ctx) 649{ 650 const char *val; 651 request_rec *r = ctx->r; 652 653 if (apr_isdigit(*var) && !var[1]) { 654 apr_size_t idx = *var - '0'; 655 backref_t *re = ctx->intern->re; 656 657 /* Handle $0 .. $9 from the last regex evaluated. 658 * The choice of returning NULL strings on not-found, 659 * v.s. empty strings on an empty match is deliberate. 660 */ 661 if (!re || !re->have_match) { 662 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01329) 663 "regex capture $%" APR_SIZE_T_FMT " refers to no regex in %s", 664 idx, r->filename); 665 return NULL; 666 } 667 else if (re->nsub < idx || idx >= AP_MAX_REG_MATCH) { 668 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01330) 669 "regex capture $%" APR_SIZE_T_FMT 670 " is out of range (last regex was: '%s') in %s", 671 idx, re->rexp, r->filename); 672 return NULL; 673 } 674 else if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) { 675 /* This particular subpattern was not used by the regex */ 676 return NULL; 677 } 678 else { 679 val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so, 680 re->match[idx].rm_eo - re->match[idx].rm_so); 681 } 682 } 683 else { 684 val = apr_table_get(r->subprocess_env, var); 685 686 if (val == LAZY_VALUE) { 687 val = add_include_vars_lazy(r, var, ctx->time_str); 688 } 689 } 690 691 return val; 692} 693 694static const char *include_expr_var_fn(ap_expr_eval_ctx_t *eval_ctx, 695 const void *data, 696 const char *arg) 697{ 698 const char *res, *name = data; 699 include_ctx_t *ctx = eval_ctx->data; 700 if (name[0] == 'e') { 701 /* keep legacy "env" semantics */ 702 if ((res = apr_table_get(ctx->r->notes, arg)) != NULL) 703 return res; 704 else if ((res = get_include_var(arg, ctx)) != NULL) 705 return res; 706 else 707 return getenv(arg); 708 } 709 else { 710 return get_include_var(arg, ctx); 711 } 712} 713 714static int include_expr_lookup(ap_expr_lookup_parms *parms) 715{ 716 switch (parms->type) { 717 case AP_EXPR_FUNC_STRING: 718 if (strcasecmp(parms->name, "v") == 0 || 719 strcasecmp(parms->name, "reqenv") == 0 || 720 strcasecmp(parms->name, "env") == 0) { 721 *parms->func = include_expr_var_fn; 722 *parms->data = parms->name; 723 return OK; 724 } 725 break; 726 /* 727 * We could also make the SSI vars available as %{...} style variables 728 * (AP_EXPR_FUNC_VAR), but this would create problems if we ever want 729 * to cache parsed expressions for performance reasons. 730 */ 731 } 732 return ap_run_expr_lookup(parms); 733} 734 735 736/* 737 * Do variable substitution on strings 738 * 739 * (Note: If out==NULL, this function allocs a buffer for the resulting 740 * string from ctx->pool. The return value is always the parsed string) 741 */ 742static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out, 743 apr_size_t length, int leave_name) 744{ 745 request_rec *r = ctx->r; 746 result_item_t *result = NULL, *current = NULL; 747 apr_size_t outlen = 0, inlen, span; 748 char *ret = NULL, *eout = NULL; 749 const char *p; 750 751 if (out) { 752 /* sanity check, out && !length is not supported */ 753 ap_assert(out && length); 754 755 ret = out; 756 eout = out + length - 1; 757 } 758 759 span = strcspn(in, "\\$"); 760 inlen = strlen(in); 761 762 /* fast exit */ 763 if (inlen == span) { 764 if (out) { 765 apr_cpystrn(out, in, length); 766 } 767 else { 768 ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen) 769 ? length - 1 : inlen); 770 } 771 772 return ret; 773 } 774 775 /* well, actually something to do */ 776 p = in + span; 777 778 if (out) { 779 if (span) { 780 memcpy(out, in, (out+span <= eout) ? span : (eout-out)); 781 out += span; 782 } 783 } 784 else { 785 current = result = apr_palloc(ctx->dpool, sizeof(*result)); 786 current->next = NULL; 787 current->string = in; 788 current->len = span; 789 outlen = span; 790 } 791 792 /* loop for specials */ 793 do { 794 if ((out && out >= eout) || (length && outlen >= length)) { 795 break; 796 } 797 798 /* prepare next entry */ 799 if (!out && current->len) { 800 current->next = apr_palloc(ctx->dpool, sizeof(*current->next)); 801 current = current->next; 802 current->next = NULL; 803 current->len = 0; 804 } 805 806 /* 807 * escaped character 808 */ 809 if (*p == '\\') { 810 if (out) { 811 *out++ = (p[1] == '$') ? *++p : *p; 812 ++p; 813 } 814 else { 815 current->len = 1; 816 current->string = (p[1] == '$') ? ++p : p; 817 ++p; 818 ++outlen; 819 } 820 } 821 822 /* 823 * variable expansion 824 */ 825 else { /* *p == '$' */ 826 const char *newp = NULL, *ep, *key = NULL; 827 828 if (*++p == '{') { 829 ep = ap_strchr_c(++p, '}'); 830 if (!ep) { 831 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01331) "Missing '}' on " 832 "variable \"%s\" in %s", p, r->filename); 833 break; 834 } 835 836 if (p < ep) { 837 key = apr_pstrmemdup(ctx->dpool, p, ep - p); 838 newp = ep + 1; 839 } 840 p -= 2; 841 } 842 else { 843 ep = p; 844 while (*ep == '_' || apr_isalnum(*ep)) { 845 ++ep; 846 } 847 848 if (p < ep) { 849 key = apr_pstrmemdup(ctx->dpool, p, ep - p); 850 newp = ep; 851 } 852 --p; 853 } 854 855 /* empty name results in a copy of '$' in the output string */ 856 if (!key) { 857 if (out) { 858 *out++ = *p++; 859 } 860 else { 861 current->len = 1; 862 current->string = p++; 863 ++outlen; 864 } 865 } 866 else { 867 const char *val = get_include_var(key, ctx); 868 apr_size_t len = 0; 869 870 if (val) { 871 len = strlen(val); 872 } 873 else if (leave_name) { 874 val = p; 875 len = ep - p; 876 } 877 878 if (val && len) { 879 if (out) { 880 memcpy(out, val, (out+len <= eout) ? len : (eout-out)); 881 out += len; 882 } 883 else { 884 current->len = len; 885 current->string = val; 886 outlen += len; 887 } 888 } 889 890 p = newp; 891 } 892 } 893 894 if ((out && out >= eout) || (length && outlen >= length)) { 895 break; 896 } 897 898 /* check the remainder */ 899 if (*p && (span = strcspn(p, "\\$")) > 0) { 900 if (!out && current->len) { 901 current->next = apr_palloc(ctx->dpool, sizeof(*current->next)); 902 current = current->next; 903 current->next = NULL; 904 } 905 906 if (out) { 907 memcpy(out, p, (out+span <= eout) ? span : (eout-out)); 908 out += span; 909 } 910 else { 911 current->len = span; 912 current->string = p; 913 outlen += span; 914 } 915 916 p += span; 917 } 918 } while (p < in+inlen); 919 920 /* assemble result */ 921 if (out) { 922 if (out > eout) { 923 *eout = '\0'; 924 } 925 else { 926 *out = '\0'; 927 } 928 } 929 else { 930 const char *ep; 931 932 if (length && outlen > length) { 933 outlen = length - 1; 934 } 935 936 ret = out = apr_palloc(ctx->pool, outlen + 1); 937 ep = ret + outlen; 938 939 do { 940 if (result->len) { 941 memcpy(out, result->string, (out+result->len <= ep) 942 ? result->len : (ep-out)); 943 out += result->len; 944 } 945 result = result->next; 946 } while (result && out < ep); 947 948 ret[outlen] = '\0'; 949 } 950 951 return ret; 952} 953 954 955/* 956 * +-------------------------------------------------------+ 957 * | | 958 * | Conditional Expression Parser 959 * | | 960 * +-------------------------------------------------------+ 961 */ 962 963static APR_INLINE int re_check(include_ctx_t *ctx, const char *string, 964 const char *rexp) 965{ 966 ap_regex_t *compiled; 967 backref_t *re = ctx->intern->re; 968 969 compiled = ap_pregcomp(ctx->dpool, rexp, AP_REG_EXTENDED); 970 if (!compiled) { 971 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "unable to " 972 "compile pattern \"%s\"", rexp); 973 return -1; 974 } 975 976 if (!re) { 977 re = ctx->intern->re = apr_palloc(ctx->pool, sizeof(*re)); 978 } 979 980 re->source = apr_pstrdup(ctx->pool, string); 981 re->rexp = apr_pstrdup(ctx->pool, rexp); 982 re->nsub = compiled->re_nsub; 983 re->have_match = !ap_regexec(compiled, string, AP_MAX_REG_MATCH, 984 re->match, 0); 985 986 ap_pregfree(ctx->dpool, compiled); 987 return re->have_match; 988} 989 990static int get_ptoken(include_ctx_t *ctx, const char **parse, token_t *token, token_t *previous) 991{ 992 const char *p; 993 apr_size_t shift; 994 int unmatched; 995 996 token->value = NULL; 997 998 if (!*parse) { 999 return 0; 1000 } 1001 1002 /* Skip leading white space */ 1003 while (apr_isspace(**parse)) { 1004 ++*parse; 1005 } 1006 1007 if (!**parse) { 1008 *parse = NULL; 1009 return 0; 1010 } 1011 1012 TYPE_TOKEN(token, TOKEN_STRING); /* the default type */ 1013 p = *parse; 1014 unmatched = 0; 1015 1016 switch (*(*parse)++) { 1017 case '(': 1018 TYPE_TOKEN(token, TOKEN_LBRACE); 1019 return 0; 1020 case ')': 1021 TYPE_TOKEN(token, TOKEN_RBRACE); 1022 return 0; 1023 case '=': 1024 if (**parse == '=') ++*parse; 1025 TYPE_TOKEN(token, TOKEN_EQ); 1026 return 0; 1027 case '!': 1028 if (**parse == '=') { 1029 TYPE_TOKEN(token, TOKEN_NE); 1030 ++*parse; 1031 return 0; 1032 } 1033 TYPE_TOKEN(token, TOKEN_NOT); 1034 return 0; 1035 case '\'': 1036 unmatched = '\''; 1037 break; 1038 case '/': 1039 /* if last token was ACCESS, this token is STRING */ 1040 if (previous != NULL && TOKEN_ACCESS == previous->type) { 1041 break; 1042 } 1043 TYPE_TOKEN(token, TOKEN_RE); 1044 unmatched = '/'; 1045 break; 1046 case '|': 1047 if (**parse == '|') { 1048 TYPE_TOKEN(token, TOKEN_OR); 1049 ++*parse; 1050 return 0; 1051 } 1052 break; 1053 case '&': 1054 if (**parse == '&') { 1055 TYPE_TOKEN(token, TOKEN_AND); 1056 ++*parse; 1057 return 0; 1058 } 1059 break; 1060 case '>': 1061 if (**parse == '=') { 1062 TYPE_TOKEN(token, TOKEN_GE); 1063 ++*parse; 1064 return 0; 1065 } 1066 TYPE_TOKEN(token, TOKEN_GT); 1067 return 0; 1068 case '<': 1069 if (**parse == '=') { 1070 TYPE_TOKEN(token, TOKEN_LE); 1071 ++*parse; 1072 return 0; 1073 } 1074 TYPE_TOKEN(token, TOKEN_LT); 1075 return 0; 1076 case '-': 1077 if (**parse == 'A') { 1078 TYPE_TOKEN(token, TOKEN_ACCESS); 1079 ++*parse; 1080 return 0; 1081 } 1082 break; 1083 } 1084 1085 /* It's a string or regex token 1086 * Now search for the next token, which finishes this string 1087 */ 1088 shift = 0; 1089 p = *parse = token->value = unmatched ? *parse : p; 1090 1091 for (; **parse; p = ++*parse) { 1092 if (**parse == '\\') { 1093 if (!*(++*parse)) { 1094 p = *parse; 1095 break; 1096 } 1097 1098 ++shift; 1099 } 1100 else { 1101 if (unmatched) { 1102 if (**parse == unmatched) { 1103 unmatched = 0; 1104 ++*parse; 1105 break; 1106 } 1107 } else if (apr_isspace(**parse)) { 1108 break; 1109 } 1110 else { 1111 int found = 0; 1112 1113 switch (**parse) { 1114 case '(': 1115 case ')': 1116 case '=': 1117 case '!': 1118 case '<': 1119 case '>': 1120 ++found; 1121 break; 1122 1123 case '|': 1124 case '&': 1125 if ((*parse)[1] == **parse) { 1126 ++found; 1127 } 1128 break; 1129 } 1130 1131 if (found) { 1132 break; 1133 } 1134 } 1135 } 1136 } 1137 1138 if (unmatched) { 1139 token->value = apr_pstrdup(ctx->dpool, ""); 1140 } 1141 else { 1142 apr_size_t len = p - token->value - shift; 1143 char *c = apr_palloc(ctx->dpool, len + 1); 1144 1145 p = token->value; 1146 token->value = c; 1147 1148 while (shift--) { 1149 const char *e = ap_strchr_c(p, '\\'); 1150 1151 memcpy(c, p, e-p); 1152 c += e-p; 1153 *c++ = *++e; 1154 len -= e-p; 1155 p = e+1; 1156 } 1157 1158 if (len) { 1159 memcpy(c, p, len); 1160 } 1161 c[len] = '\0'; 1162 } 1163 1164 return unmatched; 1165} 1166 1167static int parse_expr(include_ctx_t *ctx, const char *expr, int *was_error) 1168{ 1169 parse_node_t *new, *root = NULL, *current = NULL; 1170 request_rec *r = ctx->r; 1171 request_rec *rr = NULL; 1172 const char *error = "Invalid expression \"%s\" in file %s"; 1173 const char *parse = expr; 1174 unsigned regex = 0; 1175 1176 *was_error = 0; 1177 1178 if (!parse) { 1179 return 0; 1180 } 1181 1182 /* Create Parse Tree */ 1183 while (1) { 1184 /* uncomment this to see how the tree a built: 1185 * 1186 * DEBUG_DUMP_TREE(ctx, root); 1187 */ 1188 CREATE_NODE(ctx, new); 1189 1190 { 1191#ifdef DEBUG_INCLUDE 1192 int was_unmatched = 1193#endif 1194 get_ptoken(ctx, &parse, &new->token, 1195 (current != NULL ? ¤t->token : NULL)); 1196 if (!parse) 1197 break; 1198 1199 DEBUG_DUMP_UNMATCHED(ctx, was_unmatched); 1200 DEBUG_DUMP_TOKEN(ctx, &new->token); 1201 } 1202 1203 if (!current) { 1204 switch (new->token.type) { 1205 case TOKEN_STRING: 1206 case TOKEN_NOT: 1207 case TOKEN_ACCESS: 1208 case TOKEN_LBRACE: 1209 root = current = new; 1210 continue; 1211 1212 default: 1213 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, 1214 r->filename); 1215 *was_error = 1; 1216 return 0; 1217 } 1218 } 1219 1220 switch (new->token.type) { 1221 case TOKEN_STRING: 1222 switch (current->token.type) { 1223 case TOKEN_STRING: 1224 current->token.value = 1225 apr_pstrcat(ctx->dpool, current->token.value, 1226 *current->token.value ? " " : "", 1227 new->token.value, NULL); 1228 continue; 1229 1230 case TOKEN_RE: 1231 case TOKEN_RBRACE: 1232 case TOKEN_GROUP: 1233 break; 1234 1235 default: 1236 new->parent = current; 1237 current = current->right = new; 1238 continue; 1239 } 1240 break; 1241 1242 case TOKEN_RE: 1243 switch (current->token.type) { 1244 case TOKEN_EQ: 1245 case TOKEN_NE: 1246 new->parent = current; 1247 current = current->right = new; 1248 ++regex; 1249 continue; 1250 1251 default: 1252 break; 1253 } 1254 break; 1255 1256 case TOKEN_AND: 1257 case TOKEN_OR: 1258 switch (current->token.type) { 1259 case TOKEN_STRING: 1260 case TOKEN_RE: 1261 case TOKEN_GROUP: 1262 current = current->parent; 1263 1264 while (current) { 1265 switch (current->token.type) { 1266 case TOKEN_AND: 1267 case TOKEN_OR: 1268 case TOKEN_LBRACE: 1269 break; 1270 1271 default: 1272 current = current->parent; 1273 continue; 1274 } 1275 break; 1276 } 1277 1278 if (!current) { 1279 new->left = root; 1280 root->parent = new; 1281 current = root = new; 1282 continue; 1283 } 1284 1285 new->left = current->right; 1286 new->left->parent = new; 1287 new->parent = current; 1288 current = current->right = new; 1289 continue; 1290 1291 default: 1292 break; 1293 } 1294 break; 1295 1296 case TOKEN_EQ: 1297 case TOKEN_NE: 1298 case TOKEN_GE: 1299 case TOKEN_GT: 1300 case TOKEN_LE: 1301 case TOKEN_LT: 1302 if (current->token.type == TOKEN_STRING) { 1303 current = current->parent; 1304 1305 if (!current) { 1306 new->left = root; 1307 root->parent = new; 1308 current = root = new; 1309 continue; 1310 } 1311 1312 switch (current->token.type) { 1313 case TOKEN_LBRACE: 1314 case TOKEN_AND: 1315 case TOKEN_OR: 1316 new->left = current->right; 1317 new->left->parent = new; 1318 new->parent = current; 1319 current = current->right = new; 1320 continue; 1321 1322 default: 1323 break; 1324 } 1325 } 1326 break; 1327 1328 case TOKEN_RBRACE: 1329 while (current && current->token.type != TOKEN_LBRACE) { 1330 current = current->parent; 1331 } 1332 1333 if (current) { 1334 TYPE_TOKEN(¤t->token, TOKEN_GROUP); 1335 continue; 1336 } 1337 1338 error = "Unmatched ')' in \"%s\" in file %s"; 1339 break; 1340 1341 case TOKEN_NOT: 1342 case TOKEN_ACCESS: 1343 case TOKEN_LBRACE: 1344 switch (current->token.type) { 1345 case TOKEN_STRING: 1346 case TOKEN_RE: 1347 case TOKEN_RBRACE: 1348 case TOKEN_GROUP: 1349 break; 1350 1351 default: 1352 current->right = new; 1353 new->parent = current; 1354 current = new; 1355 continue; 1356 } 1357 break; 1358 1359 default: 1360 break; 1361 } 1362 1363 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename); 1364 *was_error = 1; 1365 return 0; 1366 } 1367 1368 DEBUG_DUMP_TREE(ctx, root); 1369 1370 /* Evaluate Parse Tree */ 1371 current = root; 1372 error = NULL; 1373 while (current) { 1374 switch (current->token.type) { 1375 case TOKEN_STRING: 1376 current->token.value = 1377 ap_ssi_parse_string(ctx, current->token.value, NULL, 0, 1378 SSI_EXPAND_DROP_NAME); 1379 current->value = !!*current->token.value; 1380 break; 1381 1382 case TOKEN_AND: 1383 case TOKEN_OR: 1384 if (!current->left || !current->right) { 1385 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01332) 1386 "Invalid expression \"%s\" in file %s", 1387 expr, r->filename); 1388 *was_error = 1; 1389 return 0; 1390 } 1391 1392 if (!current->left->done) { 1393 switch (current->left->token.type) { 1394 case TOKEN_STRING: 1395 current->left->token.value = 1396 ap_ssi_parse_string(ctx, current->left->token.value, 1397 NULL, 0, SSI_EXPAND_DROP_NAME); 1398 current->left->value = !!*current->left->token.value; 1399 DEBUG_DUMP_EVAL(ctx, current->left); 1400 current->left->done = 1; 1401 break; 1402 1403 default: 1404 current = current->left; 1405 continue; 1406 } 1407 } 1408 1409 /* short circuit evaluation */ 1410 if (!current->right->done && !regex && 1411 ((current->token.type == TOKEN_AND && !current->left->value) || 1412 (current->token.type == TOKEN_OR && current->left->value))) { 1413 current->value = current->left->value; 1414 } 1415 else { 1416 if (!current->right->done) { 1417 switch (current->right->token.type) { 1418 case TOKEN_STRING: 1419 current->right->token.value = 1420 ap_ssi_parse_string(ctx,current->right->token.value, 1421 NULL, 0, SSI_EXPAND_DROP_NAME); 1422 current->right->value = !!*current->right->token.value; 1423 DEBUG_DUMP_EVAL(ctx, current->right); 1424 current->right->done = 1; 1425 break; 1426 1427 default: 1428 current = current->right; 1429 continue; 1430 } 1431 } 1432 1433 if (current->token.type == TOKEN_AND) { 1434 current->value = current->left->value && 1435 current->right->value; 1436 } 1437 else { 1438 current->value = current->left->value || 1439 current->right->value; 1440 } 1441 } 1442 break; 1443 1444 case TOKEN_EQ: 1445 case TOKEN_NE: 1446 if (!current->left || !current->right || 1447 current->left->token.type != TOKEN_STRING || 1448 (current->right->token.type != TOKEN_STRING && 1449 current->right->token.type != TOKEN_RE)) { 1450 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01333) 1451 "Invalid expression \"%s\" in file %s", 1452 expr, r->filename); 1453 *was_error = 1; 1454 return 0; 1455 } 1456 current->left->token.value = 1457 ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0, 1458 SSI_EXPAND_DROP_NAME); 1459 current->right->token.value = 1460 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0, 1461 SSI_EXPAND_DROP_NAME); 1462 1463 if (current->right->token.type == TOKEN_RE) { 1464 current->value = re_check(ctx, current->left->token.value, 1465 current->right->token.value); 1466 --regex; 1467 } 1468 else { 1469 current->value = !strcmp(current->left->token.value, 1470 current->right->token.value); 1471 } 1472 1473 if (current->token.type == TOKEN_NE) { 1474 current->value = !current->value; 1475 } 1476 break; 1477 1478 case TOKEN_GE: 1479 case TOKEN_GT: 1480 case TOKEN_LE: 1481 case TOKEN_LT: 1482 if (!current->left || !current->right || 1483 current->left->token.type != TOKEN_STRING || 1484 current->right->token.type != TOKEN_STRING) { 1485 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01334) 1486 "Invalid expression \"%s\" in file %s", 1487 expr, r->filename); 1488 *was_error = 1; 1489 return 0; 1490 } 1491 1492 current->left->token.value = 1493 ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0, 1494 SSI_EXPAND_DROP_NAME); 1495 current->right->token.value = 1496 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0, 1497 SSI_EXPAND_DROP_NAME); 1498 1499 current->value = strcmp(current->left->token.value, 1500 current->right->token.value); 1501 1502 switch (current->token.type) { 1503 case TOKEN_GE: current->value = current->value >= 0; break; 1504 case TOKEN_GT: current->value = current->value > 0; break; 1505 case TOKEN_LE: current->value = current->value <= 0; break; 1506 case TOKEN_LT: current->value = current->value < 0; break; 1507 default: current->value = 0; break; /* should not happen */ 1508 } 1509 break; 1510 1511 case TOKEN_NOT: 1512 case TOKEN_GROUP: 1513 if (current->right) { 1514 if (!current->right->done) { 1515 current = current->right; 1516 continue; 1517 } 1518 current->value = current->right->value; 1519 } 1520 else { 1521 current->value = 1; 1522 } 1523 1524 if (current->token.type == TOKEN_NOT) { 1525 current->value = !current->value; 1526 } 1527 break; 1528 1529 case TOKEN_ACCESS: 1530 if (current->left || !current->right || 1531 (current->right->token.type != TOKEN_STRING && 1532 current->right->token.type != TOKEN_RE)) { 1533 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01335) 1534 "Invalid expression \"%s\" in file %s: Token '-A' must be followed by a URI string.", 1535 expr, r->filename); 1536 *was_error = 1; 1537 return 0; 1538 } 1539 current->right->token.value = 1540 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0, 1541 SSI_EXPAND_DROP_NAME); 1542 rr = ap_sub_req_lookup_uri(current->right->token.value, r, NULL); 1543 /* 400 and higher are considered access denied */ 1544 if (rr->status < HTTP_BAD_REQUEST) { 1545 current->value = 1; 1546 } 1547 else { 1548 current->value = 0; 1549 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rr->status, r, APLOGNO(01336) 1550 "mod_include: The tested " 1551 "subrequest -A \"%s\" returned an error code.", 1552 current->right->token.value); 1553 } 1554 ap_destroy_sub_req(rr); 1555 break; 1556 1557 case TOKEN_RE: 1558 if (!error) { 1559 error = "No operator before regex in expr \"%s\" in file %s"; 1560 } 1561 case TOKEN_LBRACE: 1562 if (!error) { 1563 error = "Unmatched '(' in \"%s\" in file %s"; 1564 } 1565 default: 1566 if (!error) { 1567 error = "internal parser error in \"%s\" in file %s"; 1568 } 1569 1570 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr,r->filename); 1571 *was_error = 1; 1572 return 0; 1573 } 1574 1575 DEBUG_DUMP_EVAL(ctx, current); 1576 current->done = 1; 1577 current = current->parent; 1578 } 1579 1580 return (root ? root->value : 0); 1581} 1582 1583/* same as above, but use common ap_expr syntax / API */ 1584static int parse_ap_expr(include_ctx_t *ctx, const char *expr, int *was_error) 1585{ 1586 ap_expr_info_t expr_info; 1587 const char *err; 1588 int ret; 1589 backref_t *re = ctx->intern->re; 1590 ap_expr_eval_ctx_t *eval_ctx = ctx->intern->expr_eval_ctx; 1591 1592 expr_info.filename = ctx->r->filename; 1593 expr_info.line_number = 0; 1594 expr_info.module_index = APLOG_MODULE_INDEX; 1595 expr_info.flags = AP_EXPR_FLAG_RESTRICTED; 1596 err = ap_expr_parse(ctx->r->pool, ctx->r->pool, &expr_info, expr, 1597 include_expr_lookup); 1598 if (err) { 1599 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01337) 1600 "Could not parse expr \"%s\" in %s: %s", expr, 1601 ctx->r->filename, err); 1602 *was_error = 1; 1603 return 0; 1604 } 1605 1606 if (!re) { 1607 ctx->intern->re = re = apr_pcalloc(ctx->pool, sizeof(*re)); 1608 } 1609 else { 1610 /* ap_expr_exec_ctx() does not care about re->have_match but only about 1611 * re->source 1612 */ 1613 if (!re->have_match) 1614 re->source = NULL; 1615 } 1616 1617 if (!eval_ctx) { 1618 eval_ctx = apr_pcalloc(ctx->pool, sizeof(*eval_ctx)); 1619 ctx->intern->expr_eval_ctx = eval_ctx; 1620 eval_ctx->r = ctx->r; 1621 eval_ctx->c = ctx->r->connection; 1622 eval_ctx->s = ctx->r->server; 1623 eval_ctx->p = ctx->r->pool; 1624 eval_ctx->data = ctx; 1625 eval_ctx->err = &ctx->intern->expr_err; 1626 eval_ctx->vary_this = &ctx->intern->expr_vary_this; 1627 eval_ctx->re_nmatch = AP_MAX_REG_MATCH; 1628 eval_ctx->re_pmatch = re->match; 1629 eval_ctx->re_source = &re->source; 1630 } 1631 1632 eval_ctx->info = &expr_info; 1633 ret = ap_expr_exec_ctx(eval_ctx); 1634 if (ret < 0) { 1635 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01338) 1636 "Could not evaluate expr \"%s\" in %s: %s", expr, 1637 ctx->r->filename, ctx->intern->expr_err); 1638 *was_error = 1; 1639 return 0; 1640 } 1641 *was_error = 0; 1642 if (re->source) 1643 re->have_match = 1; 1644 return ret; 1645} 1646 1647/* 1648 * +-------------------------------------------------------+ 1649 * | | 1650 * | Action Handlers 1651 * | | 1652 * +-------------------------------------------------------+ 1653 */ 1654 1655/* 1656 * Extract the next tag name and value. 1657 * If there are no more tags, set the tag name to NULL. 1658 * The tag value is html decoded if dodecode is non-zero. 1659 * The tag value may be NULL if there is no tag value.. 1660 */ 1661static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag, 1662 char **tag_val, int dodecode) 1663{ 1664 if (!ctx->intern->argv) { 1665 *tag = NULL; 1666 *tag_val = NULL; 1667 1668 return; 1669 } 1670 1671 *tag_val = ctx->intern->argv->value; 1672 *tag = ctx->intern->argv->name; 1673 1674 ctx->intern->argv = ctx->intern->argv->next; 1675 1676 if (dodecode && *tag_val) { 1677 decodehtml(*tag_val); 1678 } 1679 1680 return; 1681} 1682 1683static int find_file(request_rec *r, const char *directive, const char *tag, 1684 char *tag_val, apr_finfo_t *finfo) 1685{ 1686 char *to_send = tag_val; 1687 request_rec *rr = NULL; 1688 int ret=0; 1689 char *error_fmt = NULL; 1690 apr_status_t rv = APR_SUCCESS; 1691 1692 if (!strcmp(tag, "file")) { 1693 char *newpath; 1694 1695 /* be safe; only files in this directory or below allowed */ 1696 rv = apr_filepath_merge(&newpath, NULL, tag_val, 1697 APR_FILEPATH_SECUREROOTTEST | 1698 APR_FILEPATH_NOTABSOLUTE, r->pool); 1699 1700 if (rv != APR_SUCCESS) { 1701 error_fmt = "unable to access file \"%s\" " 1702 "in parsed file %s"; 1703 } 1704 else { 1705 /* note: it is okay to pass NULL for the "next filter" since 1706 we never attempt to "run" this sub request. */ 1707 rr = ap_sub_req_lookup_file(newpath, r, NULL); 1708 1709 if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) { 1710 to_send = rr->filename; 1711 if ((rv = apr_stat(finfo, to_send, 1712 APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS 1713 && rv != APR_INCOMPLETE) { 1714 error_fmt = "unable to get information about \"%s\" " 1715 "in parsed file %s"; 1716 } 1717 } 1718 else { 1719 error_fmt = "unable to lookup information about \"%s\" " 1720 "in parsed file %s"; 1721 } 1722 } 1723 1724 if (error_fmt) { 1725 ret = -1; 1726 ap_log_rerror(APLOG_MARK, APLOG_ERR, 1727 rv, r, error_fmt, to_send, r->filename); 1728 } 1729 1730 if (rr) ap_destroy_sub_req(rr); 1731 1732 return ret; 1733 } 1734 else if (!strcmp(tag, "virtual")) { 1735 /* note: it is okay to pass NULL for the "next filter" since 1736 we never attempt to "run" this sub request. */ 1737 rr = ap_sub_req_lookup_uri(tag_val, r, NULL); 1738 1739 if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) { 1740 memcpy((char *) finfo, (const char *) &rr->finfo, 1741 sizeof(rr->finfo)); 1742 ap_destroy_sub_req(rr); 1743 return 0; 1744 } 1745 else { 1746 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01339) "unable to get " 1747 "information about \"%s\" in parsed file %s", 1748 tag_val, r->filename); 1749 ap_destroy_sub_req(rr); 1750 return -1; 1751 } 1752 } 1753 else { 1754 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01340) "unknown parameter \"%s\" " 1755 "to tag %s in %s", tag, directive, r->filename); 1756 return -1; 1757 } 1758} 1759 1760/* 1761 * <!--#include virtual|file="..." [onerror|virtual|file="..."] ... --> 1762 * 1763 * Output each file/virtual in turn until one of them returns an error. 1764 * On error, ignore all further file/virtual attributes until we reach 1765 * an onerror attribute, where we make an attempt to serve the onerror 1766 * virtual url. If onerror fails, or no onerror is present, the default 1767 * error string is inserted into the stream. 1768 */ 1769static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f, 1770 apr_bucket_brigade *bb) 1771{ 1772 request_rec *r = f->r; 1773 char *last_error; 1774 1775 if (!ctx->argc) { 1776 ap_log_rerror(APLOG_MARK, 1777 (ctx->flags & SSI_FLAG_PRINTING) 1778 ? APLOG_ERR : APLOG_WARNING, 1779 0, r, APLOGNO(01341) 1780 "missing argument for include element in %s", 1781 r->filename); 1782 } 1783 1784 if (!(ctx->flags & SSI_FLAG_PRINTING)) { 1785 return APR_SUCCESS; 1786 } 1787 1788 if (!ctx->argc) { 1789 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 1790 return APR_SUCCESS; 1791 } 1792 1793 last_error = NULL; 1794 while (1) { 1795 char *tag = NULL; 1796 char *tag_val = NULL; 1797 request_rec *rr = NULL; 1798 char *error_fmt = NULL; 1799 char *parsed_string; 1800 apr_status_t rv = APR_SUCCESS; 1801 int status = 0; 1802 1803 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED); 1804 if (!tag || !tag_val) { 1805 break; 1806 } 1807 1808 if (strcmp(tag, "virtual") && strcmp(tag, "file") && strcmp(tag, 1809 "onerror")) { 1810 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01342) "unknown parameter " 1811 "\"%s\" to tag include in %s", tag, r->filename); 1812 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 1813 break; 1814 } 1815 1816 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0, 1817 SSI_EXPAND_DROP_NAME); 1818 if (tag[0] == 'f') { 1819 char *newpath; 1820 1821 /* be safe; only files in this directory or below allowed */ 1822 rv = apr_filepath_merge(&newpath, NULL, parsed_string, 1823 APR_FILEPATH_SECUREROOTTEST | 1824 APR_FILEPATH_NOTABSOLUTE, ctx->dpool); 1825 1826 if (rv != APR_SUCCESS) { 1827 error_fmt = "unable to include file \"%s\" in parsed file %s"; 1828 } 1829 else { 1830 rr = ap_sub_req_lookup_file(newpath, r, f->next); 1831 } 1832 } 1833 else if ((tag[0] == 'v' && !last_error) 1834 || (tag[0] == 'o' && last_error)) { 1835 if (r->kept_body) { 1836 rr = ap_sub_req_method_uri(r->method, parsed_string, r, f->next); 1837 } 1838 else { 1839 rr = ap_sub_req_lookup_uri(parsed_string, r, f->next); 1840 } 1841 } 1842 else { 1843 continue; 1844 } 1845 1846 if (!error_fmt && rr->status != HTTP_OK) { 1847 error_fmt = "unable to include \"%s\" in parsed file %s, subrequest setup returned %d"; 1848 } 1849 1850 if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) && 1851 rr->content_type && strncmp(rr->content_type, "text/", 5)) { 1852 1853 error_fmt = "unable to include potential exec \"%s\" in parsed " 1854 "file %s, content type not text/*"; 1855 } 1856 1857 /* See the Kludge in includes_filter for why. 1858 * Basically, it puts a bread crumb in here, then looks 1859 * for the crumb later to see if its been here. 1860 */ 1861 if (rr) { 1862 ap_set_module_config(rr->request_config, &include_module, r); 1863 } 1864 1865 if (!error_fmt && ((status = ap_run_sub_req(rr)))) { 1866 error_fmt = "unable to include \"%s\" in parsed file %s, subrequest returned %d"; 1867 } 1868 1869 if (error_fmt) { 1870 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, error_fmt, tag_val, 1871 r->filename, status ? status : rr ? rr->status : 0); 1872 if (last_error) { 1873 /* onerror threw an error, give up completely */ 1874 break; 1875 } 1876 last_error = error_fmt; 1877 } 1878 else { 1879 last_error = NULL; 1880 } 1881 1882 /* Do *not* destroy the subrequest here; it may have allocated 1883 * variables in this r->subprocess_env in the subrequest's 1884 * r->pool, so that pool must survive as long as this request. 1885 * Yes, this is a memory leak. */ 1886 1887 } 1888 1889 if (last_error) { 1890 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 1891 } 1892 1893 return APR_SUCCESS; 1894} 1895 1896/* 1897 * <!--#echo [decoding="..."] [encoding="..."] var="..." [decoding="..."] 1898 * [encoding="..."] var="..." ... --> 1899 */ 1900static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f, 1901 apr_bucket_brigade *bb) 1902{ 1903 const char *encoding = "entity", *decoding = "none"; 1904 request_rec *r = f->r; 1905 int error = 0; 1906 1907 if (!ctx->argc) { 1908 ap_log_rerror(APLOG_MARK, 1909 (ctx->flags & SSI_FLAG_PRINTING) 1910 ? APLOG_ERR : APLOG_WARNING, 1911 0, r, APLOGNO(01343) 1912 "missing argument for echo element in %s", 1913 r->filename); 1914 } 1915 1916 if (!(ctx->flags & SSI_FLAG_PRINTING)) { 1917 return APR_SUCCESS; 1918 } 1919 1920 if (!ctx->argc) { 1921 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 1922 return APR_SUCCESS; 1923 } 1924 1925 while (1) { 1926 char *tag = NULL; 1927 char *tag_val = NULL; 1928 1929 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED); 1930 if (!tag || !tag_val) { 1931 break; 1932 } 1933 1934 if (!strcmp(tag, "var")) { 1935 const char *val; 1936 const char *echo_text = NULL; 1937 apr_size_t e_len; 1938 1939 val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL, 1940 0, SSI_EXPAND_DROP_NAME), 1941 ctx); 1942 1943 if (val) { 1944 char *last = NULL; 1945 char *e, *d, *token; 1946 1947 echo_text = val; 1948 1949 d = apr_pstrdup(ctx->pool, decoding); 1950 token = apr_strtok(d, ", \t", &last); 1951 1952 while(token) { 1953 if (!strcasecmp(token, "none")) { 1954 /* do nothing */ 1955 } 1956 else if (!strcasecmp(token, "url")) { 1957 char *buf = apr_pstrdup(ctx->pool, echo_text); 1958 ap_unescape_url(buf); 1959 echo_text = buf; 1960 } 1961 else if (!strcasecmp(token, "urlencoded")) { 1962 char *buf = apr_pstrdup(ctx->pool, echo_text); 1963 ap_unescape_urlencoded(buf); 1964 echo_text = buf; 1965 } 1966 else if (!strcasecmp(token, "entity")) { 1967 char *buf = apr_pstrdup(ctx->pool, echo_text); 1968 decodehtml(buf); 1969 echo_text = buf; 1970 } 1971 else if (!strcasecmp(token, "base64")) { 1972 echo_text = ap_pbase64decode(ctx->dpool, echo_text); 1973 } 1974 else { 1975 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01344) "unknown value " 1976 "\"%s\" to parameter \"decoding\" of tag echo in " 1977 "%s", token, r->filename); 1978 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 1979 error = 1; 1980 break; 1981 } 1982 token = apr_strtok(NULL, ", \t", &last); 1983 } 1984 1985 e = apr_pstrdup(ctx->pool, encoding); 1986 token = apr_strtok(e, ", \t", &last); 1987 1988 while(token) { 1989 if (!strcasecmp(token, "none")) { 1990 /* do nothing */ 1991 } 1992 else if (!strcasecmp(token, "url")) { 1993 echo_text = ap_escape_uri(ctx->dpool, echo_text); 1994 } 1995 else if (!strcasecmp(token, "urlencoded")) { 1996 echo_text = ap_escape_urlencoded(ctx->dpool, echo_text); 1997 } 1998 else if (!strcasecmp(token, "entity")) { 1999 echo_text = ap_escape_html2(ctx->dpool, echo_text, 0); 2000 } 2001 else if (!strcasecmp(token, "base64")) { 2002 char *buf; 2003 buf = ap_pbase64encode(ctx->dpool, (char *)echo_text); 2004 echo_text = buf; 2005 } 2006 else { 2007 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01345) "unknown value " 2008 "\"%s\" to parameter \"encoding\" of tag echo in " 2009 "%s", token, r->filename); 2010 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2011 error = 1; 2012 break; 2013 } 2014 token = apr_strtok(NULL, ", \t", &last); 2015 } 2016 2017 e_len = strlen(echo_text); 2018 } 2019 else { 2020 echo_text = ctx->intern->undefined_echo; 2021 e_len = ctx->intern->undefined_echo_len; 2022 } 2023 2024 if (error) { 2025 break; 2026 } 2027 2028 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create( 2029 apr_pmemdup(ctx->pool, echo_text, e_len), 2030 e_len, ctx->pool, f->c->bucket_alloc)); 2031 } 2032 else if (!strcmp(tag, "decoding")) { 2033 decoding = tag_val; 2034 } 2035 else if (!strcmp(tag, "encoding")) { 2036 encoding = tag_val; 2037 } 2038 else { 2039 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01346) "unknown parameter " 2040 "\"%s\" in tag echo of %s", tag, r->filename); 2041 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2042 break; 2043 } 2044 } 2045 2046 return APR_SUCCESS; 2047} 2048 2049/* 2050 * <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."] 2051 * [echomsg="..."] --> 2052 */ 2053static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f, 2054 apr_bucket_brigade *bb) 2055{ 2056 request_rec *r = f->r; 2057 apr_table_t *env = r->subprocess_env; 2058 2059 if (!ctx->argc) { 2060 ap_log_rerror(APLOG_MARK, 2061 (ctx->flags & SSI_FLAG_PRINTING) 2062 ? APLOG_ERR : APLOG_WARNING, 2063 0, r, APLOGNO(01347) 2064 "missing argument for config element in %s", 2065 r->filename); 2066 } 2067 2068 if (!(ctx->flags & SSI_FLAG_PRINTING)) { 2069 return APR_SUCCESS; 2070 } 2071 2072 if (!ctx->argc) { 2073 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2074 return APR_SUCCESS; 2075 } 2076 2077 while (1) { 2078 char *tag = NULL; 2079 char *tag_val = NULL; 2080 2081 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW); 2082 if (!tag || !tag_val) { 2083 break; 2084 } 2085 2086 if (!strcmp(tag, "errmsg")) { 2087 ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0, 2088 SSI_EXPAND_DROP_NAME); 2089 } 2090 else if (!strcmp(tag, "echomsg")) { 2091 ctx->intern->undefined_echo = 2092 ap_ssi_parse_string(ctx, tag_val, NULL, 0,SSI_EXPAND_DROP_NAME); 2093 ctx->intern->undefined_echo_len=strlen(ctx->intern->undefined_echo); 2094 } 2095 else if (!strcmp(tag, "timefmt")) { 2096 apr_time_t date = r->request_time; 2097 2098 ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0, 2099 SSI_EXPAND_DROP_NAME); 2100 2101 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, 2102 ctx->time_str, 0)); 2103 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, 2104 ctx->time_str, 1)); 2105 apr_table_setn(env, "LAST_MODIFIED", 2106 ap_ht_time(r->pool, r->finfo.mtime, 2107 ctx->time_str, 0)); 2108 } 2109 else if (!strcmp(tag, "sizefmt")) { 2110 char *parsed_string; 2111 2112 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0, 2113 SSI_EXPAND_DROP_NAME); 2114 if (!strcmp(parsed_string, "bytes")) { 2115 ctx->flags |= SSI_FLAG_SIZE_IN_BYTES; 2116 } 2117 else if (!strcmp(parsed_string, "abbrev")) { 2118 ctx->flags &= SSI_FLAG_SIZE_ABBREV; 2119 } 2120 else { 2121 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01348) "unknown value " 2122 "\"%s\" to parameter \"sizefmt\" of tag config " 2123 "in %s", parsed_string, r->filename); 2124 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2125 break; 2126 } 2127 } 2128 else { 2129 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01349) "unknown parameter " 2130 "\"%s\" to tag config in %s", tag, r->filename); 2131 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2132 break; 2133 } 2134 } 2135 2136 return APR_SUCCESS; 2137} 2138 2139/* 2140 * <!--#fsize virtual|file="..." [virtual|file="..."] ... --> 2141 */ 2142static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f, 2143 apr_bucket_brigade *bb) 2144{ 2145 request_rec *r = f->r; 2146 2147 if (!ctx->argc) { 2148 ap_log_rerror(APLOG_MARK, 2149 (ctx->flags & SSI_FLAG_PRINTING) 2150 ? APLOG_ERR : APLOG_WARNING, 2151 0, r, APLOGNO(01350) 2152 "missing argument for fsize element in %s", 2153 r->filename); 2154 } 2155 2156 if (!(ctx->flags & SSI_FLAG_PRINTING)) { 2157 return APR_SUCCESS; 2158 } 2159 2160 if (!ctx->argc) { 2161 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2162 return APR_SUCCESS; 2163 } 2164 2165 while (1) { 2166 char *tag = NULL; 2167 char *tag_val = NULL; 2168 apr_finfo_t finfo; 2169 char *parsed_string; 2170 2171 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED); 2172 if (!tag || !tag_val) { 2173 break; 2174 } 2175 2176 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0, 2177 SSI_EXPAND_DROP_NAME); 2178 2179 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) { 2180 char *buf; 2181 apr_size_t len; 2182 2183 if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) { 2184 buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5)); 2185 len = 4; /* omit the \0 terminator */ 2186 } 2187 else { 2188 apr_size_t l, x, pos; 2189 char *tmp; 2190 2191 tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size); 2192 len = l = strlen(tmp); 2193 2194 for (x = 0; x < l; ++x) { 2195 if (x && !((l - x) % 3)) { 2196 ++len; 2197 } 2198 } 2199 2200 if (len == l) { 2201 buf = apr_pstrmemdup(ctx->pool, tmp, len); 2202 } 2203 else { 2204 buf = apr_palloc(ctx->pool, len); 2205 2206 for (pos = x = 0; x < l; ++x) { 2207 if (x && !((l - x) % 3)) { 2208 buf[pos++] = ','; 2209 } 2210 buf[pos++] = tmp[x]; 2211 } 2212 } 2213 } 2214 2215 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len, 2216 ctx->pool, f->c->bucket_alloc)); 2217 } 2218 else { 2219 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2220 break; 2221 } 2222 } 2223 2224 return APR_SUCCESS; 2225} 2226 2227/* 2228 * <!--#flastmod virtual|file="..." [virtual|file="..."] ... --> 2229 */ 2230static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f, 2231 apr_bucket_brigade *bb) 2232{ 2233 request_rec *r = f->r; 2234 2235 if (!ctx->argc) { 2236 ap_log_rerror(APLOG_MARK, 2237 (ctx->flags & SSI_FLAG_PRINTING) 2238 ? APLOG_ERR : APLOG_WARNING, 2239 0, r, APLOGNO(01351) 2240 "missing argument for flastmod element in %s", 2241 r->filename); 2242 } 2243 2244 if (!(ctx->flags & SSI_FLAG_PRINTING)) { 2245 return APR_SUCCESS; 2246 } 2247 2248 if (!ctx->argc) { 2249 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2250 return APR_SUCCESS; 2251 } 2252 2253 while (1) { 2254 char *tag = NULL; 2255 char *tag_val = NULL; 2256 apr_finfo_t finfo; 2257 char *parsed_string; 2258 2259 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED); 2260 if (!tag || !tag_val) { 2261 break; 2262 } 2263 2264 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0, 2265 SSI_EXPAND_DROP_NAME); 2266 2267 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) { 2268 char *t_val; 2269 apr_size_t t_len; 2270 2271 t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0); 2272 t_len = strlen(t_val); 2273 2274 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len, 2275 ctx->pool, f->c->bucket_alloc)); 2276 } 2277 else { 2278 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2279 break; 2280 } 2281 } 2282 2283 return APR_SUCCESS; 2284} 2285 2286/* 2287 * <!--#if expr="..." --> 2288 */ 2289static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f, 2290 apr_bucket_brigade *bb) 2291{ 2292 char *tag = NULL; 2293 char *expr = NULL; 2294 request_rec *r = f->r; 2295 int expr_ret, was_error; 2296 2297 if (ctx->argc != 1) { 2298 ap_log_rerror(APLOG_MARK, 2299 (ctx->flags & SSI_FLAG_PRINTING) 2300 ? APLOG_ERR : APLOG_WARNING, 2301 0, r, 2302 (ctx->argc) 2303 ? APLOGNO(01352) "too many arguments for if element in %s" 2304 : APLOGNO(01353) "missing expr argument for if element in %s", 2305 r->filename); 2306 } 2307 2308 if (!(ctx->flags & SSI_FLAG_PRINTING)) { 2309 ++(ctx->if_nesting_level); 2310 return APR_SUCCESS; 2311 } 2312 2313 if (ctx->argc != 1) { 2314 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2315 return APR_SUCCESS; 2316 } 2317 2318 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW); 2319 2320 if (strcmp(tag, "expr")) { 2321 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01354) "unknown parameter \"%s\" " 2322 "to tag if in %s", tag, r->filename); 2323 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2324 return APR_SUCCESS; 2325 } 2326 2327 if (!expr) { 2328 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01355) "missing expr value for if " 2329 "element in %s", r->filename); 2330 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2331 return APR_SUCCESS; 2332 } 2333 2334 DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr)); 2335 2336 if (ctx->intern->legacy_expr) 2337 expr_ret = parse_expr(ctx, expr, &was_error); 2338 else 2339 expr_ret = parse_ap_expr(ctx, expr, &was_error); 2340 2341 if (was_error) { 2342 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2343 return APR_SUCCESS; 2344 } 2345 2346 if (expr_ret) { 2347 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE); 2348 } 2349 else { 2350 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND; 2351 } 2352 2353 DEBUG_DUMP_COND(ctx, " if"); 2354 2355 ctx->if_nesting_level = 0; 2356 2357 return APR_SUCCESS; 2358} 2359 2360/* 2361 * <!--#elif expr="..." --> 2362 */ 2363static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f, 2364 apr_bucket_brigade *bb) 2365{ 2366 char *tag = NULL; 2367 char *expr = NULL; 2368 request_rec *r = f->r; 2369 int expr_ret, was_error; 2370 2371 if (ctx->argc != 1) { 2372 ap_log_rerror(APLOG_MARK, 2373 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING, 2374 0, r, 2375 (ctx->argc) 2376 ? APLOGNO(01356) "too many arguments for if element in %s" 2377 : APLOGNO(01357) "missing expr argument for if element in %s", 2378 r->filename); 2379 } 2380 2381 if (ctx->if_nesting_level) { 2382 return APR_SUCCESS; 2383 } 2384 2385 if (ctx->argc != 1) { 2386 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2387 return APR_SUCCESS; 2388 } 2389 2390 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW); 2391 2392 if (strcmp(tag, "expr")) { 2393 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01358) "unknown parameter \"%s\" " 2394 "to tag if in %s", tag, r->filename); 2395 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2396 return APR_SUCCESS; 2397 } 2398 2399 if (!expr) { 2400 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01359) "missing expr in elif " 2401 "statement: %s", r->filename); 2402 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2403 return APR_SUCCESS; 2404 } 2405 2406 DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr)); 2407 DEBUG_DUMP_COND(ctx, " elif"); 2408 2409 if (ctx->flags & SSI_FLAG_COND_TRUE) { 2410 ctx->flags &= SSI_FLAG_CLEAR_PRINTING; 2411 return APR_SUCCESS; 2412 } 2413 2414 if (ctx->intern->legacy_expr) 2415 expr_ret = parse_expr(ctx, expr, &was_error); 2416 else 2417 expr_ret = parse_ap_expr(ctx, expr, &was_error); 2418 2419 if (was_error) { 2420 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2421 return APR_SUCCESS; 2422 } 2423 2424 if (expr_ret) { 2425 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE); 2426 } 2427 else { 2428 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND; 2429 } 2430 2431 DEBUG_DUMP_COND(ctx, " elif"); 2432 2433 return APR_SUCCESS; 2434} 2435 2436/* 2437 * <!--#else --> 2438 */ 2439static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f, 2440 apr_bucket_brigade *bb) 2441{ 2442 request_rec *r = f->r; 2443 2444 if (ctx->argc) { 2445 ap_log_rerror(APLOG_MARK, 2446 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING, 2447 0, r, APLOGNO(01360) 2448 "else directive does not take tags in %s", 2449 r->filename); 2450 } 2451 2452 if (ctx->if_nesting_level) { 2453 return APR_SUCCESS; 2454 } 2455 2456 if (ctx->argc) { 2457 if (ctx->flags & SSI_FLAG_PRINTING) { 2458 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2459 } 2460 2461 return APR_SUCCESS; 2462 } 2463 2464 DEBUG_DUMP_COND(ctx, " else"); 2465 2466 if (ctx->flags & SSI_FLAG_COND_TRUE) { 2467 ctx->flags &= SSI_FLAG_CLEAR_PRINTING; 2468 } 2469 else { 2470 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE); 2471 } 2472 2473 return APR_SUCCESS; 2474} 2475 2476/* 2477 * <!--#endif --> 2478 */ 2479static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f, 2480 apr_bucket_brigade *bb) 2481{ 2482 request_rec *r = f->r; 2483 2484 if (ctx->argc) { 2485 ap_log_rerror(APLOG_MARK, 2486 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING, 2487 0, r, APLOGNO(01361) 2488 "endif directive does not take tags in %s", 2489 r->filename); 2490 } 2491 2492 if (ctx->if_nesting_level) { 2493 --(ctx->if_nesting_level); 2494 return APR_SUCCESS; 2495 } 2496 2497 if (ctx->argc) { 2498 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2499 return APR_SUCCESS; 2500 } 2501 2502 DEBUG_DUMP_COND(ctx, "endif"); 2503 2504 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE); 2505 2506 return APR_SUCCESS; 2507} 2508 2509/* 2510 * <!--#set var="..." value="..." ... --> 2511 */ 2512static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f, 2513 apr_bucket_brigade *bb) 2514{ 2515 const char *encoding = "none", *decoding = "none"; 2516 char *var = NULL; 2517 request_rec *r = f->r; 2518 request_rec *sub = r->main; 2519 apr_pool_t *p = r->pool; 2520 int error = 0; 2521 2522 if (ctx->argc < 2) { 2523 ap_log_rerror(APLOG_MARK, 2524 (ctx->flags & SSI_FLAG_PRINTING) 2525 ? APLOG_ERR : APLOG_WARNING, 2526 0, r, 2527 APLOGNO(01362) "missing argument for set element in %s", 2528 r->filename); 2529 } 2530 2531 if (!(ctx->flags & SSI_FLAG_PRINTING)) { 2532 return APR_SUCCESS; 2533 } 2534 2535 if (ctx->argc < 2) { 2536 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2537 return APR_SUCCESS; 2538 } 2539 2540 /* we need to use the 'main' request pool to set notes as that is 2541 * a notes lifetime 2542 */ 2543 while (sub) { 2544 p = sub->pool; 2545 sub = sub->main; 2546 } 2547 2548 while (1) { 2549 char *tag = NULL; 2550 char *tag_val = NULL; 2551 2552 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW); 2553 2554 if (!tag || !tag_val) { 2555 break; 2556 } 2557 2558 if (!strcmp(tag, "var")) { 2559 decodehtml(tag_val); 2560 var = ap_ssi_parse_string(ctx, tag_val, NULL, 0, 2561 SSI_EXPAND_DROP_NAME); 2562 } 2563 else if (!strcmp(tag, "decoding")) { 2564 decoding = tag_val; 2565 } 2566 else if (!strcmp(tag, "encoding")) { 2567 encoding = tag_val; 2568 } 2569 else if (!strcmp(tag, "value")) { 2570 char *parsed_string; 2571 2572 if (!var) { 2573 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01363) "variable must " 2574 "precede value in set directive in %s", 2575 r->filename); 2576 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2577 break; 2578 } 2579 2580 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0, 2581 SSI_EXPAND_DROP_NAME); 2582 2583 if (parsed_string) { 2584 char *last = NULL; 2585 char *e, *d, *token; 2586 2587 d = apr_pstrdup(ctx->pool, decoding); 2588 token = apr_strtok(d, ", \t", &last); 2589 2590 while(token) { 2591 if (!strcasecmp(token, "none")) { 2592 /* do nothing */ 2593 } 2594 else if (!strcasecmp(token, "url")) { 2595 char *buf = apr_pstrdup(ctx->pool, parsed_string); 2596 ap_unescape_url(buf); 2597 parsed_string = buf; 2598 } 2599 else if (!strcasecmp(token, "urlencoded")) { 2600 char *buf = apr_pstrdup(ctx->pool, parsed_string); 2601 ap_unescape_urlencoded(buf); 2602 parsed_string = buf; 2603 } 2604 else if (!strcasecmp(token, "entity")) { 2605 char *buf = apr_pstrdup(ctx->pool, parsed_string); 2606 decodehtml(buf); 2607 parsed_string = buf; 2608 } 2609 else if (!strcasecmp(token, "base64")) { 2610 parsed_string = ap_pbase64decode(ctx->dpool, parsed_string); 2611 } 2612 else { 2613 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01364) "unknown value " 2614 "\"%s\" to parameter \"decoding\" of tag set in " 2615 "%s", token, r->filename); 2616 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2617 error = 1; 2618 break; 2619 } 2620 token = apr_strtok(NULL, ", \t", &last); 2621 } 2622 2623 e = apr_pstrdup(ctx->pool, encoding); 2624 token = apr_strtok(e, ", \t", &last); 2625 2626 while(token) { 2627 if (!strcasecmp(token, "none")) { 2628 /* do nothing */ 2629 } 2630 else if (!strcasecmp(token, "url")) { 2631 parsed_string = ap_escape_uri(ctx->dpool, parsed_string); 2632 } 2633 else if (!strcasecmp(token, "urlencoded")) { 2634 parsed_string = ap_escape_urlencoded(ctx->dpool, parsed_string); 2635 } 2636 else if (!strcasecmp(token, "entity")) { 2637 parsed_string = ap_escape_html2(ctx->dpool, parsed_string, 0); 2638 } 2639 else if (!strcasecmp(token, "base64")) { 2640 char *buf; 2641 buf = ap_pbase64encode(ctx->dpool, (char *)parsed_string); 2642 parsed_string = buf; 2643 } 2644 else { 2645 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01365) "unknown value " 2646 "\"%s\" to parameter \"encoding\" of tag set in " 2647 "%s", token, r->filename); 2648 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2649 error = 1; 2650 break; 2651 } 2652 token = apr_strtok(NULL, ", \t", &last); 2653 } 2654 2655 } 2656 2657 if (error) { 2658 break; 2659 } 2660 2661 apr_table_setn(r->subprocess_env, apr_pstrdup(p, var), 2662 apr_pstrdup(p, parsed_string)); 2663 } 2664 else { 2665 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01366) "Invalid tag for set " 2666 "directive in %s", r->filename); 2667 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2668 break; 2669 } 2670 } 2671 2672 return APR_SUCCESS; 2673} 2674 2675/* 2676 * <!--#printenv --> 2677 */ 2678static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f, 2679 apr_bucket_brigade *bb) 2680{ 2681 request_rec *r = f->r; 2682 const apr_array_header_t *arr; 2683 const apr_table_entry_t *elts; 2684 int i; 2685 2686 if (ctx->argc) { 2687 ap_log_rerror(APLOG_MARK, 2688 (ctx->flags & SSI_FLAG_PRINTING) 2689 ? APLOG_ERR : APLOG_WARNING, 2690 0, r, 2691 APLOGNO(01367) "printenv directive does not take tags in %s", 2692 r->filename); 2693 } 2694 2695 if (!(ctx->flags & SSI_FLAG_PRINTING)) { 2696 return APR_SUCCESS; 2697 } 2698 2699 if (ctx->argc) { 2700 SSI_CREATE_ERROR_BUCKET(ctx, f, bb); 2701 return APR_SUCCESS; 2702 } 2703 2704 arr = apr_table_elts(r->subprocess_env); 2705 elts = (apr_table_entry_t *)arr->elts; 2706 2707 for (i = 0; i < arr->nelts; ++i) { 2708 const char *key_text, *val_text; 2709 2710 /* get key */ 2711 key_text = ap_escape_html(ctx->dpool, elts[i].key); 2712 2713 /* get value */ 2714 val_text = elts[i].val; 2715 if (val_text == LAZY_VALUE) 2716 val_text = add_include_vars_lazy(r, elts[i].key, ctx->time_str); 2717 val_text = ap_escape_html(ctx->dpool, val_text); 2718 2719 apr_brigade_putstrs(bb, NULL, NULL, key_text, "=", val_text, "\n", 2720 NULL); 2721 } 2722 2723 ctx->flush_now = 1; 2724 return APR_SUCCESS; 2725} 2726 2727 2728/* 2729 * +-------------------------------------------------------+ 2730 * | | 2731 * | Main Includes-Filter Engine 2732 * | | 2733 * +-------------------------------------------------------+ 2734 */ 2735 2736/* This is an implementation of the BNDM search algorithm. 2737 * 2738 * Fast and Flexible String Matching by Combining Bit-parallelism and 2739 * Suffix Automata (2001) 2740 * Gonzalo Navarro, Mathieu Raffinot 2741 * 2742 * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz 2743 * 2744 * Initial code submitted by Sascha Schumann. 2745 */ 2746 2747/* Precompile the bndm_t data structure. */ 2748static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl) 2749{ 2750 unsigned int x; 2751 const char *ne = n + nl; 2752 bndm_t *t = apr_palloc(pool, sizeof(*t)); 2753 2754 memset(t->T, 0, sizeof(unsigned int) * 256); 2755 t->pattern_len = nl; 2756 2757 for (x = 1; n < ne; x <<= 1) { 2758 t->T[(unsigned char) *n++] |= x; 2759 } 2760 2761 t->x = x - 1; 2762 2763 return t; 2764} 2765 2766/* Implements the BNDM search algorithm (as described above). 2767 * 2768 * h - the string to look in 2769 * hl - length of the string to look for 2770 * t - precompiled bndm structure against the pattern 2771 * 2772 * Returns the count of character that is the first match or hl if no 2773 * match is found. 2774 */ 2775static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl) 2776{ 2777 const char *skip; 2778 const char *he, *p, *pi; 2779 unsigned int *T, x, d; 2780 apr_size_t nl; 2781 2782 he = h + hl; 2783 2784 T = t->T; 2785 x = t->x; 2786 nl = t->pattern_len; 2787 2788 pi = h - 1; /* pi: p initial */ 2789 p = pi + nl; /* compare window right to left. point to the first char */ 2790 2791 while (p < he) { 2792 skip = p; 2793 d = x; 2794 do { 2795 d &= T[(unsigned char) *p--]; 2796 if (!d) { 2797 break; 2798 } 2799 if ((d & 1)) { 2800 if (p != pi) { 2801 skip = p; 2802 } 2803 else { 2804 return p - h + 1; 2805 } 2806 } 2807 d >>= 1; 2808 } while (d); 2809 2810 pi = skip; 2811 p = pi + nl; 2812 } 2813 2814 return hl; 2815} 2816 2817/* 2818 * returns the index position of the first byte of start_seq (or the len of 2819 * the buffer as non-match) 2820 */ 2821static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data, 2822 apr_size_t len) 2823{ 2824 struct ssi_internal_ctx *intern = ctx->intern; 2825 apr_size_t slen = intern->start_seq_pat->pattern_len; 2826 apr_size_t index; 2827 const char *p, *ep; 2828 2829 if (len < slen) { 2830 p = data; /* try partial match at the end of the buffer (below) */ 2831 } 2832 else { 2833 /* try fast bndm search over the buffer 2834 * (hopefully the whole start sequence can be found in this buffer) 2835 */ 2836 index = bndm(intern->start_seq_pat, data, len); 2837 2838 /* wow, found it. ready. */ 2839 if (index < len) { 2840 intern->state = PARSE_DIRECTIVE; 2841 return index; 2842 } 2843 else { 2844 /* ok, the pattern can't be found as whole in the buffer, 2845 * check the end for a partial match 2846 */ 2847 p = data + len - slen + 1; 2848 } 2849 } 2850 2851 ep = data + len; 2852 do { 2853 while (p < ep && *p != *intern->start_seq) { 2854 ++p; 2855 } 2856 2857 index = p - data; 2858 2859 /* found a possible start_seq start */ 2860 if (p < ep) { 2861 apr_size_t pos = 1; 2862 2863 ++p; 2864 while (p < ep && *p == intern->start_seq[pos]) { 2865 ++p; 2866 ++pos; 2867 } 2868 2869 /* partial match found. Store the info for the next round */ 2870 if (p == ep) { 2871 intern->state = PARSE_HEAD; 2872 intern->parse_pos = pos; 2873 return index; 2874 } 2875 } 2876 2877 /* we must try all combinations; consider (e.g.) SSIStartTag "--->" 2878 * and a string data of "--.-" and the end of the buffer 2879 */ 2880 p = data + index + 1; 2881 } while (p < ep); 2882 2883 /* no match */ 2884 return len; 2885} 2886 2887/* 2888 * returns the first byte *after* the partial (or final) match. 2889 * 2890 * If we had to trick with the start_seq start, 'release' returns the 2891 * number of chars of the start_seq which appeared not to be part of a 2892 * full tag and may have to be passed down the filter chain. 2893 */ 2894static apr_size_t find_partial_start_sequence(include_ctx_t *ctx, 2895 const char *data, 2896 apr_size_t len, 2897 apr_size_t *release) 2898{ 2899 struct ssi_internal_ctx *intern = ctx->intern; 2900 apr_size_t pos, spos = 0; 2901 apr_size_t slen = intern->start_seq_pat->pattern_len; 2902 const char *p, *ep; 2903 2904 pos = intern->parse_pos; 2905 ep = data + len; 2906 *release = 0; 2907 2908 do { 2909 p = data; 2910 2911 while (p < ep && pos < slen && *p == intern->start_seq[pos]) { 2912 ++p; 2913 ++pos; 2914 } 2915 2916 /* full match */ 2917 if (pos == slen) { 2918 intern->state = PARSE_DIRECTIVE; 2919 return (p - data); 2920 } 2921 2922 /* the whole buffer is a partial match */ 2923 if (p == ep) { 2924 intern->parse_pos = pos; 2925 return (p - data); 2926 } 2927 2928 /* No match so far, but again: 2929 * We must try all combinations, since the start_seq is a random 2930 * user supplied string 2931 * 2932 * So: look if the first char of start_seq appears somewhere within 2933 * the current partial match. If it does, try to start a match that 2934 * begins with this offset. (This can happen, if a strange 2935 * start_seq like "---->" spans buffers) 2936 */ 2937 if (spos < intern->parse_pos) { 2938 do { 2939 ++spos; 2940 ++*release; 2941 p = intern->start_seq + spos; 2942 pos = intern->parse_pos - spos; 2943 2944 while (pos && *p != *intern->start_seq) { 2945 ++p; 2946 ++spos; 2947 ++*release; 2948 --pos; 2949 } 2950 2951 /* if a matching beginning char was found, try to match the 2952 * remainder of the old buffer. 2953 */ 2954 if (pos > 1) { 2955 apr_size_t t = 1; 2956 2957 ++p; 2958 while (t < pos && *p == intern->start_seq[t]) { 2959 ++p; 2960 ++t; 2961 } 2962 2963 if (t == pos) { 2964 /* yeah, another partial match found in the *old* 2965 * buffer, now test the *current* buffer for 2966 * continuing match 2967 */ 2968 break; 2969 } 2970 } 2971 } while (pos > 1); 2972 2973 if (pos) { 2974 continue; 2975 } 2976 } 2977 2978 break; 2979 } while (1); /* work hard to find a match ;-) */ 2980 2981 /* no match at all, release all (wrongly) matched chars so far */ 2982 *release = intern->parse_pos; 2983 intern->state = PARSE_PRE_HEAD; 2984 return 0; 2985} 2986 2987/* 2988 * returns the position after the directive 2989 */ 2990static apr_size_t find_directive(include_ctx_t *ctx, const char *data, 2991 apr_size_t len, char ***store, 2992 apr_size_t **store_len) 2993{ 2994 struct ssi_internal_ctx *intern = ctx->intern; 2995 const char *p = data; 2996 const char *ep = data + len; 2997 apr_size_t pos; 2998 2999 switch (intern->state) { 3000 case PARSE_DIRECTIVE: 3001 while (p < ep && !apr_isspace(*p)) { 3002 /* we have to consider the case of missing space between directive 3003 * and end_seq (be somewhat lenient), e.g. <!--#printenv--> 3004 */ 3005 if (*p == *intern->end_seq) { 3006 intern->state = PARSE_DIRECTIVE_TAIL; 3007 intern->parse_pos = 1; 3008 ++p; 3009 return (p - data); 3010 } 3011 ++p; 3012 } 3013 3014 if (p < ep) { /* found delimiter whitespace */ 3015 intern->state = PARSE_DIRECTIVE_POSTNAME; 3016 *store = &intern->directive; 3017 *store_len = &intern->directive_len; 3018 } 3019 3020 break; 3021 3022 case PARSE_DIRECTIVE_TAIL: 3023 pos = intern->parse_pos; 3024 3025 while (p < ep && pos < intern->end_seq_len && 3026 *p == intern->end_seq[pos]) { 3027 ++p; 3028 ++pos; 3029 } 3030 3031 /* full match, we're done */ 3032 if (pos == intern->end_seq_len) { 3033 intern->state = PARSE_DIRECTIVE_POSTTAIL; 3034 *store = &intern->directive; 3035 *store_len = &intern->directive_len; 3036 break; 3037 } 3038 3039 /* partial match, the buffer is too small to match fully */ 3040 if (p == ep) { 3041 intern->parse_pos = pos; 3042 break; 3043 } 3044 3045 /* no match. continue normal parsing */ 3046 intern->state = PARSE_DIRECTIVE; 3047 return 0; 3048 3049 case PARSE_DIRECTIVE_POSTTAIL: 3050 intern->state = PARSE_EXECUTE; 3051 intern->directive_len -= intern->end_seq_len; 3052 /* continue immediately with the next state */ 3053 3054 case PARSE_DIRECTIVE_POSTNAME: 3055 if (PARSE_DIRECTIVE_POSTNAME == intern->state) { 3056 intern->state = PARSE_PRE_ARG; 3057 } 3058 ctx->argc = 0; 3059 intern->argv = NULL; 3060 3061 if (!intern->directive_len) { 3062 intern->error = 1; 3063 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01368) "missing " 3064 "directive name in parsed document %s", 3065 ctx->r->filename); 3066 } 3067 else { 3068 char *sp = intern->directive; 3069 char *sep = intern->directive + intern->directive_len; 3070 3071 /* normalize directive name */ 3072 for (; sp < sep; ++sp) { 3073 *sp = apr_tolower(*sp); 3074 } 3075 } 3076 3077 return 0; 3078 3079 default: 3080 /* get a rid of a gcc warning about unhandled enumerations */ 3081 break; 3082 } 3083 3084 return (p - data); 3085} 3086 3087/* 3088 * find out whether the next token is (a possible) end_seq or an argument 3089 */ 3090static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data, 3091 apr_size_t len) 3092{ 3093 struct ssi_internal_ctx *intern = ctx->intern; 3094 const char *p = data; 3095 const char *ep = data + len; 3096 3097 /* skip leading WS */ 3098 while (p < ep && apr_isspace(*p)) { 3099 ++p; 3100 } 3101 3102 /* buffer doesn't consist of whitespaces only */ 3103 if (p < ep) { 3104 intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG; 3105 } 3106 3107 return (p - data); 3108} 3109 3110/* 3111 * test the stream for end_seq. If it doesn't match at all, it must be an 3112 * argument 3113 */ 3114static apr_size_t find_tail(include_ctx_t *ctx, const char *data, 3115 apr_size_t len) 3116{ 3117 struct ssi_internal_ctx *intern = ctx->intern; 3118 const char *p = data; 3119 const char *ep = data + len; 3120 apr_size_t pos = intern->parse_pos; 3121 3122 if (PARSE_TAIL == intern->state) { 3123 intern->state = PARSE_TAIL_SEQ; 3124 pos = intern->parse_pos = 0; 3125 } 3126 3127 while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) { 3128 ++p; 3129 ++pos; 3130 } 3131 3132 /* bingo, full match */ 3133 if (pos == intern->end_seq_len) { 3134 intern->state = PARSE_EXECUTE; 3135 return (p - data); 3136 } 3137 3138 /* partial match, the buffer is too small to match fully */ 3139 if (p == ep) { 3140 intern->parse_pos = pos; 3141 return (p - data); 3142 } 3143 3144 /* no match. It must be an argument string then 3145 * The caller should cleanup and rewind to the reparse point 3146 */ 3147 intern->state = PARSE_ARG; 3148 return 0; 3149} 3150 3151/* 3152 * extract name=value from the buffer 3153 * A pcre-pattern could look (similar to): 3154 * name\s*(?:=\s*(["'`]?)value\1(?>\s*))? 3155 */ 3156static apr_size_t find_argument(include_ctx_t *ctx, const char *data, 3157 apr_size_t len, char ***store, 3158 apr_size_t **store_len) 3159{ 3160 struct ssi_internal_ctx *intern = ctx->intern; 3161 const char *p = data; 3162 const char *ep = data + len; 3163 3164 switch (intern->state) { 3165 case PARSE_ARG: 3166 /* 3167 * create argument structure and append it to the current list 3168 */ 3169 intern->current_arg = apr_palloc(ctx->dpool, 3170 sizeof(*intern->current_arg)); 3171 intern->current_arg->next = NULL; 3172 3173 ++(ctx->argc); 3174 if (!intern->argv) { 3175 intern->argv = intern->current_arg; 3176 } 3177 else { 3178 arg_item_t *newarg = intern->argv; 3179 3180 while (newarg->next) { 3181 newarg = newarg->next; 3182 } 3183 newarg->next = intern->current_arg; 3184 } 3185 3186 /* check whether it's a valid one. If it begins with a quote, we 3187 * can safely assume, someone forgot the name of the argument 3188 */ 3189 switch (*p) { 3190 case '"': case '\'': case '`': 3191 *store = NULL; 3192 3193 intern->state = PARSE_ARG_VAL; 3194 intern->quote = *p++; 3195 intern->current_arg->name = NULL; 3196 intern->current_arg->name_len = 0; 3197 intern->error = 1; 3198 3199 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01369) "missing " 3200 "argument name for value to tag %s in %s", 3201 apr_pstrmemdup(ctx->r->pool, intern->directive, 3202 intern->directive_len), 3203 ctx->r->filename); 3204 3205 return (p - data); 3206 3207 default: 3208 intern->state = PARSE_ARG_NAME; 3209 } 3210 /* continue immediately with next state */ 3211 3212 case PARSE_ARG_NAME: 3213 while (p < ep && !apr_isspace(*p) && *p != '=') { 3214 ++p; 3215 } 3216 3217 if (p < ep) { 3218 intern->state = PARSE_ARG_POSTNAME; 3219 *store = &intern->current_arg->name; 3220 *store_len = &intern->current_arg->name_len; 3221 return (p - data); 3222 } 3223 break; 3224 3225 case PARSE_ARG_POSTNAME: 3226 intern->current_arg->name = apr_pstrmemdup(ctx->dpool, 3227 intern->current_arg->name, 3228 intern->current_arg->name_len); 3229 if (!intern->current_arg->name_len) { 3230 intern->error = 1; 3231 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01370) "missing " 3232 "argument name for value to tag %s in %s", 3233 apr_pstrmemdup(ctx->r->pool, intern->directive, 3234 intern->directive_len), 3235 ctx->r->filename); 3236 } 3237 else { 3238 ap_str_tolower(intern->current_arg->name); 3239 } 3240 3241 intern->state = PARSE_ARG_EQ; 3242 /* continue with next state immediately */ 3243 3244 case PARSE_ARG_EQ: 3245 *store = NULL; 3246 3247 while (p < ep && apr_isspace(*p)) { 3248 ++p; 3249 } 3250 3251 if (p < ep) { 3252 if (*p == '=') { 3253 intern->state = PARSE_ARG_PREVAL; 3254 ++p; 3255 } 3256 else { /* no value */ 3257 intern->current_arg->value = NULL; 3258 intern->state = PARSE_PRE_ARG; 3259 } 3260 3261 return (p - data); 3262 } 3263 break; 3264 3265 case PARSE_ARG_PREVAL: 3266 *store = NULL; 3267 3268 while (p < ep && apr_isspace(*p)) { 3269 ++p; 3270 } 3271 3272 /* buffer doesn't consist of whitespaces only */ 3273 if (p < ep) { 3274 intern->state = PARSE_ARG_VAL; 3275 switch (*p) { 3276 case '"': case '\'': case '`': 3277 intern->quote = *p++; 3278 break; 3279 default: 3280 intern->quote = '\0'; 3281 break; 3282 } 3283 3284 return (p - data); 3285 } 3286 break; 3287 3288 case PARSE_ARG_VAL_ESC: 3289 if (*p == intern->quote) { 3290 ++p; 3291 } 3292 intern->state = PARSE_ARG_VAL; 3293 /* continue with next state immediately */ 3294 3295 case PARSE_ARG_VAL: 3296 for (; p < ep; ++p) { 3297 if (intern->quote && *p == '\\') { 3298 ++p; 3299 if (p == ep) { 3300 intern->state = PARSE_ARG_VAL_ESC; 3301 break; 3302 } 3303 3304 if (*p != intern->quote) { 3305 --p; 3306 } 3307 } 3308 else if (intern->quote && *p == intern->quote) { 3309 ++p; 3310 *store = &intern->current_arg->value; 3311 *store_len = &intern->current_arg->value_len; 3312 intern->state = PARSE_ARG_POSTVAL; 3313 break; 3314 } 3315 else if (!intern->quote && apr_isspace(*p)) { 3316 ++p; 3317 *store = &intern->current_arg->value; 3318 *store_len = &intern->current_arg->value_len; 3319 intern->state = PARSE_ARG_POSTVAL; 3320 break; 3321 } 3322 } 3323 3324 return (p - data); 3325 3326 case PARSE_ARG_POSTVAL: 3327 /* 3328 * The value is still the raw input string. Finally clean it up. 3329 */ 3330 --(intern->current_arg->value_len); 3331 3332 /* strip quote escaping \ from the string */ 3333 if (intern->quote) { 3334 apr_size_t shift = 0; 3335 char *sp; 3336 3337 sp = intern->current_arg->value; 3338 ep = intern->current_arg->value + intern->current_arg->value_len; 3339 while (sp < ep && *sp != '\\') { 3340 ++sp; 3341 } 3342 for (; sp < ep; ++sp) { 3343 if (*sp == '\\' && sp[1] == intern->quote) { 3344 ++sp; 3345 ++shift; 3346 } 3347 if (shift) { 3348 *(sp-shift) = *sp; 3349 } 3350 } 3351 3352 intern->current_arg->value_len -= shift; 3353 } 3354 3355 intern->current_arg->value[intern->current_arg->value_len] = '\0'; 3356 intern->state = PARSE_PRE_ARG; 3357 3358 return 0; 3359 3360 default: 3361 /* get a rid of a gcc warning about unhandled enumerations */ 3362 break; 3363 } 3364 3365 return len; /* partial match of something */ 3366} 3367 3368/* 3369 * This is the main loop over the current bucket brigade. 3370 */ 3371static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb) 3372{ 3373 include_ctx_t *ctx = f->ctx; 3374 struct ssi_internal_ctx *intern = ctx->intern; 3375 request_rec *r = f->r; 3376 apr_bucket *b = APR_BRIGADE_FIRST(bb); 3377 apr_bucket_brigade *pass_bb; 3378 apr_status_t rv = APR_SUCCESS; 3379 char *magic; /* magic pointer for sentinel use */ 3380 3381 /* fast exit */ 3382 if (APR_BRIGADE_EMPTY(bb)) { 3383 return APR_SUCCESS; 3384 } 3385 3386 /* we may crash, since already cleaned up; hand over the responsibility 3387 * to the next filter;-) 3388 */ 3389 if (intern->seen_eos) { 3390 return ap_pass_brigade(f->next, bb); 3391 } 3392 3393 /* All stuff passed along has to be put into that brigade */ 3394 pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc); 3395 3396 /* initialization for this loop */ 3397 intern->bytes_read = 0; 3398 intern->error = 0; 3399 ctx->flush_now = 0; 3400 3401 /* loop over the current bucket brigade */ 3402 while (b != APR_BRIGADE_SENTINEL(bb)) { 3403 const char *data = NULL; 3404 apr_size_t len, index, release; 3405 apr_bucket *newb = NULL; 3406 char **store = &magic; 3407 apr_size_t *store_len = NULL; 3408 3409 /* handle meta buckets before reading any data */ 3410 if (APR_BUCKET_IS_METADATA(b)) { 3411 newb = APR_BUCKET_NEXT(b); 3412 3413 APR_BUCKET_REMOVE(b); 3414 3415 if (APR_BUCKET_IS_EOS(b)) { 3416 intern->seen_eos = 1; 3417 3418 /* Hit end of stream, time for cleanup ... But wait! 3419 * Perhaps we're not ready yet. We may have to loop one or 3420 * two times again to finish our work. In that case, we 3421 * just re-insert the EOS bucket to allow for an extra loop. 3422 * 3423 * PARSE_EXECUTE means, we've hit a directive just before the 3424 * EOS, which is now waiting for execution. 3425 * 3426 * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with 3427 * no argument and no space between directive and end_seq 3428 * just before the EOS. (consider <!--#printenv--> as last 3429 * or only string within the stream). This state, however, 3430 * just cleans up and turns itself to PARSE_EXECUTE, which 3431 * will be passed through within the next (and actually 3432 * last) round. 3433 */ 3434 if (PARSE_EXECUTE == intern->state || 3435 PARSE_DIRECTIVE_POSTTAIL == intern->state) { 3436 APR_BUCKET_INSERT_BEFORE(newb, b); 3437 } 3438 else { 3439 break; /* END OF STREAM */ 3440 } 3441 } 3442 else { 3443 APR_BRIGADE_INSERT_TAIL(pass_bb, b); 3444 3445 if (APR_BUCKET_IS_FLUSH(b)) { 3446 ctx->flush_now = 1; 3447 } 3448 3449 b = newb; 3450 continue; 3451 } 3452 } 3453 3454 /* enough is enough ... */ 3455 if (ctx->flush_now || 3456 intern->bytes_read > AP_MIN_BYTES_TO_WRITE) { 3457 3458 if (!APR_BRIGADE_EMPTY(pass_bb)) { 3459 rv = ap_pass_brigade(f->next, pass_bb); 3460 if (rv != APR_SUCCESS) { 3461 apr_brigade_destroy(pass_bb); 3462 return rv; 3463 } 3464 } 3465 3466 ctx->flush_now = 0; 3467 intern->bytes_read = 0; 3468 } 3469 3470 /* read the current bucket data */ 3471 len = 0; 3472 if (!intern->seen_eos) { 3473 if (intern->bytes_read > 0) { 3474 rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ); 3475 if (APR_STATUS_IS_EAGAIN(rv)) { 3476 ctx->flush_now = 1; 3477 continue; 3478 } 3479 } 3480 3481 if (!len || rv != APR_SUCCESS) { 3482 rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ); 3483 } 3484 3485 if (rv != APR_SUCCESS) { 3486 apr_brigade_destroy(pass_bb); 3487 return rv; 3488 } 3489 3490 intern->bytes_read += len; 3491 } 3492 3493 /* zero length bucket, fetch next one */ 3494 if (!len && !intern->seen_eos) { 3495 b = APR_BUCKET_NEXT(b); 3496 continue; 3497 } 3498 3499 /* 3500 * it's actually a data containing bucket, start/continue parsing 3501 */ 3502 3503 switch (intern->state) { 3504 /* no current tag; search for start sequence */ 3505 case PARSE_PRE_HEAD: 3506 index = find_start_sequence(ctx, data, len); 3507 3508 if (index < len) { 3509 apr_bucket_split(b, index); 3510 } 3511 3512 newb = APR_BUCKET_NEXT(b); 3513 if (ctx->flags & SSI_FLAG_PRINTING) { 3514 APR_BUCKET_REMOVE(b); 3515 APR_BRIGADE_INSERT_TAIL(pass_bb, b); 3516 } 3517 else { 3518 apr_bucket_delete(b); 3519 } 3520 3521 if (index < len) { 3522 /* now delete the start_seq stuff from the remaining bucket */ 3523 if (PARSE_DIRECTIVE == intern->state) { /* full match */ 3524 apr_bucket_split(newb, intern->start_seq_pat->pattern_len); 3525 ctx->flush_now = 1; /* pass pre-tag stuff */ 3526 } 3527 3528 b = APR_BUCKET_NEXT(newb); 3529 apr_bucket_delete(newb); 3530 } 3531 else { 3532 b = newb; 3533 } 3534 3535 break; 3536 3537 /* we're currently looking for the end of the start sequence */ 3538 case PARSE_HEAD: 3539 index = find_partial_start_sequence(ctx, data, len, &release); 3540 3541 /* check if we mismatched earlier and have to release some chars */ 3542 if (release && (ctx->flags & SSI_FLAG_PRINTING)) { 3543 char *to_release = apr_pmemdup(ctx->pool, intern->start_seq, release); 3544 3545 newb = apr_bucket_pool_create(to_release, release, ctx->pool, 3546 f->c->bucket_alloc); 3547 APR_BRIGADE_INSERT_TAIL(pass_bb, newb); 3548 } 3549 3550 if (index) { /* any match */ 3551 /* now delete the start_seq stuff from the remaining bucket */ 3552 if (PARSE_DIRECTIVE == intern->state) { /* final match */ 3553 apr_bucket_split(b, index); 3554 ctx->flush_now = 1; /* pass pre-tag stuff */ 3555 } 3556 newb = APR_BUCKET_NEXT(b); 3557 apr_bucket_delete(b); 3558 b = newb; 3559 } 3560 3561 break; 3562 3563 /* we're currently grabbing the directive name */ 3564 case PARSE_DIRECTIVE: 3565 case PARSE_DIRECTIVE_POSTNAME: 3566 case PARSE_DIRECTIVE_TAIL: 3567 case PARSE_DIRECTIVE_POSTTAIL: 3568 index = find_directive(ctx, data, len, &store, &store_len); 3569 3570 if (index) { 3571 apr_bucket_split(b, index); 3572 newb = APR_BUCKET_NEXT(b); 3573 } 3574 3575 if (store) { 3576 if (index) { 3577 APR_BUCKET_REMOVE(b); 3578 apr_bucket_setaside(b, r->pool); 3579 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b); 3580 b = newb; 3581 } 3582 3583 /* time for cleanup? */ 3584 if (store != &magic) { 3585 apr_brigade_pflatten(intern->tmp_bb, store, store_len, 3586 ctx->dpool); 3587 apr_brigade_cleanup(intern->tmp_bb); 3588 } 3589 } 3590 else if (index) { 3591 apr_bucket_delete(b); 3592 b = newb; 3593 } 3594 3595 break; 3596 3597 /* skip WS and find out what comes next (arg or end_seq) */ 3598 case PARSE_PRE_ARG: 3599 index = find_arg_or_tail(ctx, data, len); 3600 3601 if (index) { /* skipped whitespaces */ 3602 if (index < len) { 3603 apr_bucket_split(b, index); 3604 } 3605 newb = APR_BUCKET_NEXT(b); 3606 apr_bucket_delete(b); 3607 b = newb; 3608 } 3609 3610 break; 3611 3612 /* currently parsing name[=val] */ 3613 case PARSE_ARG: 3614 case PARSE_ARG_NAME: 3615 case PARSE_ARG_POSTNAME: 3616 case PARSE_ARG_EQ: 3617 case PARSE_ARG_PREVAL: 3618 case PARSE_ARG_VAL: 3619 case PARSE_ARG_VAL_ESC: 3620 case PARSE_ARG_POSTVAL: 3621 index = find_argument(ctx, data, len, &store, &store_len); 3622 3623 if (index) { 3624 apr_bucket_split(b, index); 3625 newb = APR_BUCKET_NEXT(b); 3626 } 3627 3628 if (store) { 3629 if (index) { 3630 APR_BUCKET_REMOVE(b); 3631 apr_bucket_setaside(b, r->pool); 3632 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b); 3633 b = newb; 3634 } 3635 3636 /* time for cleanup? */ 3637 if (store != &magic) { 3638 apr_brigade_pflatten(intern->tmp_bb, store, store_len, 3639 ctx->dpool); 3640 apr_brigade_cleanup(intern->tmp_bb); 3641 } 3642 } 3643 else if (index) { 3644 apr_bucket_delete(b); 3645 b = newb; 3646 } 3647 3648 break; 3649 3650 /* try to match end_seq at current pos. */ 3651 case PARSE_TAIL: 3652 case PARSE_TAIL_SEQ: 3653 index = find_tail(ctx, data, len); 3654 3655 switch (intern->state) { 3656 case PARSE_EXECUTE: /* full match */ 3657 apr_bucket_split(b, index); 3658 newb = APR_BUCKET_NEXT(b); 3659 apr_bucket_delete(b); 3660 b = newb; 3661 break; 3662 3663 case PARSE_ARG: /* no match */ 3664 /* PARSE_ARG must reparse at the beginning */ 3665 APR_BRIGADE_PREPEND(bb, intern->tmp_bb); 3666 b = APR_BRIGADE_FIRST(bb); 3667 break; 3668 3669 default: /* partial match */ 3670 newb = APR_BUCKET_NEXT(b); 3671 APR_BUCKET_REMOVE(b); 3672 apr_bucket_setaside(b, r->pool); 3673 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b); 3674 b = newb; 3675 break; 3676 } 3677 3678 break; 3679 3680 /* now execute the parsed directive, cleanup the space and 3681 * start again with PARSE_PRE_HEAD 3682 */ 3683 case PARSE_EXECUTE: 3684 /* if there was an error, it was already logged; just stop here */ 3685 if (intern->error) { 3686 if (ctx->flags & SSI_FLAG_PRINTING) { 3687 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb); 3688 intern->error = 0; 3689 } 3690 } 3691 else { 3692 include_handler_fn_t *handle_func; 3693 3694 handle_func = 3695 (include_handler_fn_t *)apr_hash_get(include_handlers, intern->directive, 3696 intern->directive_len); 3697 3698 if (handle_func) { 3699 DEBUG_INIT(ctx, f, pass_bb); 3700 rv = handle_func(ctx, f, pass_bb); 3701 if (rv != APR_SUCCESS) { 3702 apr_brigade_destroy(pass_bb); 3703 return rv; 3704 } 3705 } 3706 else { 3707 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01371) 3708 "unknown directive \"%s\" in parsed doc %s", 3709 apr_pstrmemdup(r->pool, intern->directive, 3710 intern->directive_len), 3711 r->filename); 3712 if (ctx->flags & SSI_FLAG_PRINTING) { 3713 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb); 3714 } 3715 } 3716 } 3717 3718 /* cleanup */ 3719 apr_pool_clear(ctx->dpool); 3720 apr_brigade_cleanup(intern->tmp_bb); 3721 3722 /* Oooof. Done here, start next round */ 3723 intern->state = PARSE_PRE_HEAD; 3724 break; 3725 3726 } /* switch(ctx->state) */ 3727 3728 } /* while(brigade) */ 3729 3730 /* End of stream. Final cleanup */ 3731 if (intern->seen_eos) { 3732 if (PARSE_HEAD == intern->state) { 3733 if (ctx->flags & SSI_FLAG_PRINTING) { 3734 char *to_release = apr_pmemdup(ctx->pool, intern->start_seq, 3735 intern->parse_pos); 3736 3737 APR_BRIGADE_INSERT_TAIL(pass_bb, 3738 apr_bucket_pool_create(to_release, 3739 intern->parse_pos, ctx->pool, 3740 f->c->bucket_alloc)); 3741 } 3742 } 3743 else if (PARSE_PRE_HEAD != intern->state) { 3744 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01372) 3745 "SSI directive was not properly finished at the end " 3746 "of parsed document %s", r->filename); 3747 if (ctx->flags & SSI_FLAG_PRINTING) { 3748 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb); 3749 } 3750 } 3751 3752 if (!(ctx->flags & SSI_FLAG_PRINTING)) { 3753 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01373) 3754 "missing closing endif directive in parsed document" 3755 " %s", r->filename); 3756 } 3757 3758 /* cleanup our temporary memory */ 3759 apr_brigade_destroy(intern->tmp_bb); 3760 apr_pool_destroy(ctx->dpool); 3761 3762 /* don't forget to finally insert the EOS bucket */ 3763 APR_BRIGADE_INSERT_TAIL(pass_bb, b); 3764 } 3765 3766 /* if something's left over, pass it along */ 3767 if (!APR_BRIGADE_EMPTY(pass_bb)) { 3768 rv = ap_pass_brigade(f->next, pass_bb); 3769 } 3770 else { 3771 rv = APR_SUCCESS; 3772 apr_brigade_destroy(pass_bb); 3773 } 3774 return rv; 3775} 3776 3777 3778/* 3779 * +-------------------------------------------------------+ 3780 * | | 3781 * | Runtime Hooks 3782 * | | 3783 * +-------------------------------------------------------+ 3784 */ 3785 3786static int includes_setup(ap_filter_t *f) 3787{ 3788 include_dir_config *conf = ap_get_module_config(f->r->per_dir_config, 3789 &include_module); 3790 3791 /* When our xbithack value isn't set to full or our platform isn't 3792 * providing group-level protection bits or our group-level bits do not 3793 * have group-execite on, we will set the no_local_copy value to 1 so 3794 * that we will not send 304s. 3795 */ 3796 if ((conf->xbithack != XBITHACK_FULL) 3797 || !(f->r->finfo.valid & APR_FINFO_GPROT) 3798 || !(f->r->finfo.protection & APR_GEXECUTE)) { 3799 f->r->no_local_copy = 1; 3800 } 3801 3802 /* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4. 3803 * We don't know if we are going to be including a file or executing 3804 * a program - in either case a strong ETag header will likely be invalid. 3805 */ 3806 if (conf->etag <= 0) { 3807 apr_table_setn(f->r->notes, "no-etag", ""); 3808 } 3809 3810 return OK; 3811} 3812 3813static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b) 3814{ 3815 request_rec *r = f->r; 3816 request_rec *parent; 3817 include_dir_config *conf = ap_get_module_config(r->per_dir_config, 3818 &include_module); 3819 3820 include_server_config *sconf= ap_get_module_config(r->server->module_config, 3821 &include_module); 3822 3823 if (!(ap_allow_options(r) & OPT_INCLUDES)) { 3824 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01374) 3825 "mod_include: Options +Includes (or IncludesNoExec) " 3826 "wasn't set, INCLUDES filter removed: %s", r->uri); 3827 ap_remove_output_filter(f); 3828 return ap_pass_brigade(f->next, b); 3829 } 3830 3831 if (!f->ctx) { 3832 struct ssi_internal_ctx *intern; 3833 include_ctx_t *ctx; 3834 3835 /* create context for this filter */ 3836 f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx)); 3837 ctx->r = r; 3838 ctx->intern = intern = apr_palloc(r->pool, sizeof(*ctx->intern)); 3839 ctx->pool = r->pool; 3840 apr_pool_create(&ctx->dpool, ctx->pool); 3841 3842 /* runtime data */ 3843 intern->tmp_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc); 3844 intern->seen_eos = 0; 3845 intern->state = PARSE_PRE_HEAD; 3846 ctx->flags = (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE); 3847 if ((ap_allow_options(r) & OPT_INC_WITH_EXEC) == 0) { 3848 ctx->flags |= SSI_FLAG_NO_EXEC; 3849 } 3850 intern->legacy_expr = (conf->legacy_expr > 0); 3851 intern->expr_eval_ctx = NULL; 3852 intern->expr_err = NULL; 3853 intern->expr_vary_this = NULL; 3854 3855 ctx->if_nesting_level = 0; 3856 intern->re = NULL; 3857 3858 ctx->error_str = conf->default_error_msg ? conf->default_error_msg : 3859 DEFAULT_ERROR_MSG; 3860 ctx->time_str = conf->default_time_fmt ? conf->default_time_fmt : 3861 DEFAULT_TIME_FORMAT; 3862 intern->start_seq = sconf->default_start_tag; 3863 intern->start_seq_pat = bndm_compile(ctx->pool, intern->start_seq, 3864 strlen(intern->start_seq)); 3865 intern->end_seq = sconf->default_end_tag; 3866 intern->end_seq_len = strlen(intern->end_seq); 3867 intern->undefined_echo = conf->undefined_echo ? conf->undefined_echo : 3868 DEFAULT_UNDEFINED_ECHO; 3869 intern->undefined_echo_len = strlen(intern->undefined_echo); 3870 } 3871 3872 if ((parent = ap_get_module_config(r->request_config, &include_module))) { 3873 /* Kludge --- for nested includes, we want to keep the subprocess 3874 * environment of the base document (for compatibility); that means 3875 * torquing our own last_modified date as well so that the 3876 * LAST_MODIFIED variable gets reset to the proper value if the 3877 * nested document resets <!--#config timefmt -->. 3878 */ 3879 r->subprocess_env = r->main->subprocess_env; 3880 apr_pool_join(r->main->pool, r->pool); 3881 r->finfo.mtime = r->main->finfo.mtime; 3882 } 3883 else { 3884 /* we're not a nested include, so we create an initial 3885 * environment */ 3886 ap_add_common_vars(r); 3887 ap_add_cgi_vars(r); 3888 add_include_vars(r); 3889 } 3890 /* Always unset the content-length. There is no way to know if 3891 * the content will be modified at some point by send_parsed_content. 3892 * It is very possible for us to not find any content in the first 3893 * 9k of the file, but still have to modify the content of the file. 3894 * If we are going to pass the file through send_parsed_content, then 3895 * the content-length should just be unset. 3896 */ 3897 apr_table_unset(f->r->headers_out, "Content-Length"); 3898 3899 /* Always unset the Last-Modified field - see RFC2616 - 13.3.4. 3900 * We don't know if we are going to be including a file or executing 3901 * a program which may change the Last-Modified header or make the 3902 * content completely dynamic. Therefore, we can't support these 3903 * headers. 3904 * 3905 * Exception: XBitHack full means we *should* set the 3906 * Last-Modified field. 3907 * 3908 * SSILastModified on means we *should* set the Last-Modified field 3909 * if not present, or respect an existing value if present. 3910 */ 3911 3912 /* Must we respect the last modified header? By default, no */ 3913 if (conf->lastmodified > 0) { 3914 3915 /* update the last modified if we have a valid time, and only if 3916 * we don't already have a valid last modified. 3917 */ 3918 if (r->finfo.valid & APR_FINFO_MTIME 3919 && !apr_table_get(f->r->headers_out, "Last-Modified")) { 3920 ap_update_mtime(r, r->finfo.mtime); 3921 ap_set_last_modified(r); 3922 } 3923 3924 } 3925 3926 /* Assure the platform supports Group protections */ 3927 else if (((conf->xbithack == XBITHACK_FULL || 3928 (conf->xbithack == XBITHACK_UNSET && 3929 DEFAULT_XBITHACK == XBITHACK_FULL)) 3930 && (r->finfo.valid & APR_FINFO_GPROT) 3931 && (r->finfo.protection & APR_GEXECUTE))) { 3932 ap_update_mtime(r, r->finfo.mtime); 3933 ap_set_last_modified(r); 3934 } 3935 else { 3936 apr_table_unset(f->r->headers_out, "Last-Modified"); 3937 } 3938 3939 /* add QUERY stuff to env cause it ain't yet */ 3940 if (r->args) { 3941 char *arg_copy = apr_pstrdup(r->pool, r->args); 3942 3943 apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args); 3944 ap_unescape_url(arg_copy); 3945 apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED", 3946 ap_escape_shell_cmd(r->pool, arg_copy)); 3947 } 3948 3949 return send_parsed_content(f, b); 3950} 3951 3952static int include_fixup(request_rec *r) 3953{ 3954 if (r->handler && (strcmp(r->handler, "server-parsed") == 0)) 3955 { 3956 if (!r->content_type || !*r->content_type) { 3957 ap_set_content_type(r, "text/html"); 3958 } 3959 r->handler = "default-handler"; 3960 } 3961 else 3962#if defined(OS2) || defined(WIN32) || defined(NETWARE) 3963 /* These OS's don't support xbithack. This is being worked on. */ 3964 { 3965 return DECLINED; 3966 } 3967#else 3968 { 3969 include_dir_config *conf = ap_get_module_config(r->per_dir_config, 3970 &include_module); 3971 3972 if (conf->xbithack == XBITHACK_OFF || 3973 (DEFAULT_XBITHACK == XBITHACK_OFF && 3974 conf->xbithack == XBITHACK_UNSET)) 3975 { 3976 return DECLINED; 3977 } 3978 3979 if (!(r->finfo.protection & APR_UEXECUTE)) { 3980 return DECLINED; 3981 } 3982 3983 if (!r->content_type || strncmp(r->content_type, "text/html", 9)) { 3984 return DECLINED; 3985 } 3986 } 3987#endif 3988 3989 /* We always return declined, because the default handler actually 3990 * serves the file. All we have to do is add the filter. 3991 */ 3992 ap_add_output_filter("INCLUDES", NULL, r, r->connection); 3993 return DECLINED; 3994} 3995 3996 3997/* 3998 * +-------------------------------------------------------+ 3999 * | | 4000 * | Configuration Handling 4001 * | | 4002 * +-------------------------------------------------------+ 4003 */ 4004 4005static void *create_includes_dir_config(apr_pool_t *p, char *dummy) 4006{ 4007 include_dir_config *result = apr_pcalloc(p, sizeof(include_dir_config)); 4008 4009 result->xbithack = XBITHACK_UNSET; 4010 result->lastmodified = UNSET; 4011 result->etag = UNSET; 4012 result->legacy_expr = UNSET; 4013 4014 return result; 4015} 4016 4017#define MERGE(b,o,n,val,unset) n->val = o->val != unset ? o->val : b->val 4018static void *merge_includes_dir_config(apr_pool_t *p, void *basev, void *overridesv) 4019{ 4020 include_dir_config *base = (include_dir_config *)basev, 4021 *over = (include_dir_config *)overridesv, 4022 *new = apr_palloc(p, sizeof(include_dir_config)); 4023 MERGE(base, over, new, default_error_msg, NULL); 4024 MERGE(base, over, new, default_time_fmt, NULL); 4025 MERGE(base, over, new, undefined_echo, NULL); 4026 MERGE(base, over, new, xbithack, XBITHACK_UNSET); 4027 MERGE(base, over, new, lastmodified, UNSET); 4028 MERGE(base, over, new, etag, UNSET); 4029 MERGE(base, over, new, legacy_expr, UNSET); 4030 return new; 4031} 4032 4033static void *create_includes_server_config(apr_pool_t *p, server_rec *server) 4034{ 4035 include_server_config *result; 4036 4037 result = apr_palloc(p, sizeof(include_server_config)); 4038 result->default_end_tag = DEFAULT_END_SEQUENCE; 4039 result->default_start_tag = DEFAULT_START_SEQUENCE; 4040 4041 return result; 4042} 4043 4044static const char *set_xbithack(cmd_parms *cmd, void *mconfig, const char *arg) 4045{ 4046 include_dir_config *conf = mconfig; 4047 4048 if (!strcasecmp(arg, "off")) { 4049 conf->xbithack = XBITHACK_OFF; 4050 } 4051 else if (!strcasecmp(arg, "on")) { 4052 conf->xbithack = XBITHACK_ON; 4053 } 4054 else if (!strcasecmp(arg, "full")) { 4055 conf->xbithack = XBITHACK_FULL; 4056 } 4057 else { 4058 return "XBitHack must be set to Off, On, or Full"; 4059 } 4060 4061 return NULL; 4062} 4063 4064static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig, 4065 const char *tag) 4066{ 4067 include_server_config *conf; 4068 const char *p = tag; 4069 4070 /* be consistent. (See below in set_default_end_tag) */ 4071 while (*p) { 4072 if (apr_isspace(*p)) { 4073 return "SSIStartTag may not contain any whitespaces"; 4074 } 4075 ++p; 4076 } 4077 4078 conf= ap_get_module_config(cmd->server->module_config , &include_module); 4079 conf->default_start_tag = tag; 4080 4081 return NULL; 4082} 4083 4084static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig, 4085 const char *tag) 4086{ 4087 include_server_config *conf; 4088 const char *p = tag; 4089 4090 /* sanity check. The parser may fail otherwise */ 4091 while (*p) { 4092 if (apr_isspace(*p)) { 4093 return "SSIEndTag may not contain any whitespaces"; 4094 } 4095 ++p; 4096 } 4097 4098 conf= ap_get_module_config(cmd->server->module_config , &include_module); 4099 conf->default_end_tag = tag; 4100 4101 return NULL; 4102} 4103 4104static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig, 4105 const char *msg) 4106{ 4107 include_dir_config *conf = mconfig; 4108 conf->undefined_echo = msg; 4109 4110 return NULL; 4111} 4112 4113static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig, 4114 const char *msg) 4115{ 4116 include_dir_config *conf = mconfig; 4117 conf->default_error_msg = msg; 4118 4119 return NULL; 4120} 4121 4122static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig, 4123 const char *fmt) 4124{ 4125 include_dir_config *conf = mconfig; 4126 conf->default_time_fmt = fmt; 4127 4128 return NULL; 4129} 4130 4131 4132/* 4133 * +-------------------------------------------------------+ 4134 * | | 4135 * | Module Initialization and Configuration 4136 * | | 4137 * +-------------------------------------------------------+ 4138 */ 4139 4140static int include_post_config(apr_pool_t *p, apr_pool_t *plog, 4141 apr_pool_t *ptemp, server_rec *s) 4142{ 4143 include_handlers = apr_hash_make(p); 4144 4145 ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler); 4146 4147 if(ssi_pfn_register) { 4148 ssi_pfn_register("if", handle_if); 4149 ssi_pfn_register("set", handle_set); 4150 ssi_pfn_register("else", handle_else); 4151 ssi_pfn_register("elif", handle_elif); 4152 ssi_pfn_register("echo", handle_echo); 4153 ssi_pfn_register("endif", handle_endif); 4154 ssi_pfn_register("fsize", handle_fsize); 4155 ssi_pfn_register("config", handle_config); 4156 ssi_pfn_register("include", handle_include); 4157 ssi_pfn_register("flastmod", handle_flastmod); 4158 ssi_pfn_register("printenv", handle_printenv); 4159 } 4160 4161 return OK; 4162} 4163 4164static const command_rec includes_cmds[] = 4165{ 4166 AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS, 4167 "Off, On, or Full"), 4168 AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL, 4169 "a string"), 4170 AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL, 4171 "a strftime(3) formatted string"), 4172 AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF, 4173 "SSI Start String Tag"), 4174 AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF, 4175 "SSI End String Tag"), 4176 AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, OR_ALL, 4177 "String to be displayed if an echoed variable is undefined"), 4178 AP_INIT_FLAG("SSILegacyExprParser", ap_set_flag_slot_char, 4179 (void *)APR_OFFSETOF(include_dir_config, legacy_expr), 4180 OR_LIMIT, 4181 "Whether to use the legacy expression parser compatible " 4182 "with <= 2.2.x. Limited to 'on' or 'off'"), 4183 AP_INIT_FLAG("SSILastModified", ap_set_flag_slot_char, 4184 (void *)APR_OFFSETOF(include_dir_config, lastmodified), 4185 OR_LIMIT, "Whether to set the last modified header or respect " 4186 "an existing header. Limited to 'on' or 'off'"), 4187 AP_INIT_FLAG("SSIEtag", ap_set_flag_slot_char, 4188 (void *)APR_OFFSETOF(include_dir_config, etag), 4189 OR_LIMIT, "Whether to allow the generation of ETags within the server. " 4190 "Existing ETags will be preserved. Limited to 'on' or 'off'"), 4191 {NULL} 4192}; 4193 4194static void ap_register_include_handler(char *tag, include_handler_fn_t *func) 4195{ 4196 apr_hash_set(include_handlers, tag, strlen(tag), (const void *)func); 4197} 4198 4199static void register_hooks(apr_pool_t *p) 4200{ 4201 APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value); 4202 APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string); 4203 APR_REGISTER_OPTIONAL_FN(ap_register_include_handler); 4204 ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST); 4205 ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST); 4206 ap_register_output_filter("INCLUDES", includes_filter, includes_setup, 4207 AP_FTYPE_RESOURCE); 4208} 4209 4210AP_DECLARE_MODULE(include) = 4211{ 4212 STANDARD20_MODULE_STUFF, 4213 create_includes_dir_config, /* dir config creater */ 4214 merge_includes_dir_config, /* dir config merger */ 4215 create_includes_server_config,/* server config */ 4216 NULL, /* merge server config */ 4217 includes_cmds, /* command apr_table_t */ 4218 register_hooks /* register hooks */ 4219}; 4220