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