1 /* Copyright 1999-2004 The Apache Software Foundation
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
17 #include "apr_strings.h"
18 #include "apr_thread_proc.h"
22 #include "apr_optional.h"
24 #define APR_WANT_STRFUNC
25 #define APR_WANT_MEMFUNC
28 #include "ap_config.h"
29 #include "util_filter.h"
31 #include "http_config.h"
32 #include "http_core.h"
33 #include "http_request.h"
34 #include "http_core.h"
35 #include "http_protocol.h"
37 #include "http_main.h"
38 #include "util_script.h"
39 #include "http_core.h"
40 #include "mod_include.h"
42 /* helper for Latin1 <-> entity encoding */
43 #if APR_CHARSET_EBCDIC
44 #include "util_ebcdic.h"
45 #define RAW_ASCII_CHAR(ch) apr_xlate_conv_byte(ap_hdrs_from_ascii, \
47 #else /* APR_CHARSET_EBCDIC */
48 #define RAW_ASCII_CHAR(ch) (ch)
49 #endif /* !APR_CHARSET_EBCDIC */
53 * +-------------------------------------------------------+
55 * | Types and Structures
57 * +-------------------------------------------------------+
60 /* sll used for string expansion */
61 typedef struct result_item {
62 struct result_item *next;
67 /* conditional expression parser stuff */
93 typedef struct parse_node {
94 struct parse_node *parent;
95 struct parse_node *left;
96 struct parse_node *right;
112 const char *default_error_msg;
113 const char *default_time_fmt;
114 const char *undefined_echo;
116 } include_dir_config;
119 const char *default_start_tag;
120 const char *default_end_tag;
121 } include_server_config;
123 /* main parser states */
128 PARSE_DIRECTIVE_POSTNAME,
129 PARSE_DIRECTIVE_TAIL,
130 PARSE_DIRECTIVE_POSTTAIL,
145 typedef struct arg_item {
146 struct arg_item *next;
150 apr_size_t value_len;
157 regmatch_t match[AP_MAX_REG_MATCH];
163 apr_size_t pattern_len;
166 struct ssi_internal_ctx {
170 char quote; /* quote character value (or \0) */
171 apr_size_t parse_pos; /* parse position of partial matches */
172 apr_size_t bytes_read;
174 apr_bucket_brigade *tmp_bb;
177 const char *start_seq;
178 bndm_t *start_seq_pat;
180 apr_size_t end_seq_len;
181 char *directive; /* name of the current directive */
182 apr_size_t directive_len; /* length of the current directive name */
184 arg_item_t *current_arg; /* currently parsed argument */
185 arg_item_t *argv; /* all arguments */
187 backref_t *re; /* NULL if there wasn't a regex yet */
189 const char *undefined_echo;
190 apr_size_t undefined_echo_len;
195 apr_bucket_brigade *bb;
202 * +-------------------------------------------------------+
204 * | Debugging Utilities
206 * +-------------------------------------------------------+
211 #define TYPE_TOKEN(token, ttype) do { \
212 (token)->type = ttype; \
213 (token)->s = #ttype; \
216 #define CREATE_NODE(ctx, name) do { \
217 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
218 (name)->parent = (name)->left = (name)->right = NULL; \
220 (name)->dump_done = 0; \
223 static void debug_printf(include_ctx_t *ctx, const char *fmt, ...)
229 debug__str = apr_pvsprintf(ctx->pool, fmt, ap);
232 APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create(
233 debug__str, strlen(debug__str), ctx->pool,
234 ctx->intern->debug.f->c->bucket_alloc));
237 #define DUMP__CHILD(ctx, is, node, child) if (1) { \
238 parse_node_t *d__c = node->child; \
240 if (!d__c->dump_done) { \
241 if (d__c->parent != node) { \
242 debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \
243 if (!d__c->parent) { \
244 debug_printf(ctx, "Parent of " #child " child node is " \
248 debug_printf(ctx, "Parent of " #child " child node " \
249 "points to another node (of type %s)!\n", \
250 d__c->parent->token.s); \
259 debug_printf(ctx, "%s(missing)\n", is); \
263 static void debug_dump_tree(include_ctx_t *ctx, parse_node_t *root)
265 parse_node_t *current;
269 debug_printf(ctx, " -- Parse Tree empty --\n\n");
273 debug_printf(ctx, " ----- Parse Tree -----\n");
278 switch (current->token.type) {
281 debug_printf(ctx, "%s%s (%s)\n", is, current->token.s,
282 current->token.value);
283 current->dump_done = 1;
284 current = current->parent;
291 if (!current->dump_done) {
292 debug_printf(ctx, "%s%s\n", is, current->token.s);
293 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
294 current->dump_done = 1;
297 DUMP__CHILD(ctx, is, current, right)
299 if (!current->right || current->right->dump_done) {
300 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
301 if (current->right) current->right->dump_done = 0;
302 current = current->parent;
307 if (!current->dump_done) {
308 debug_printf(ctx, "%s%s\n", is, current->token.s);
309 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
310 current->dump_done = 1;
313 DUMP__CHILD(ctx, is, current, left)
314 DUMP__CHILD(ctx, is, current, right)
316 if ((!current->left || current->left->dump_done) &&
317 (!current->right || current->right->dump_done)) {
319 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
320 if (current->left) current->left->dump_done = 0;
321 if (current->right) current->right->dump_done = 0;
322 current = current->parent;
328 /* it is possible to call this function within the parser loop, to see
329 * how the tree is built. That way, we must cleanup after us to dump
330 * always the whole tree
333 if (root->left) root->left->dump_done = 0;
334 if (root->right) root->right->dump_done = 0;
336 debug_printf(ctx, " --- End Parse Tree ---\n\n");
341 #define DEBUG_INIT(ctx, filter, brigade) do { \
342 (ctx)->intern->debug.f = filter; \
343 (ctx)->intern->debug.bb = brigade; \
346 #define DEBUG_PRINTF(arg) debug_printf arg
348 #define DEBUG_DUMP_TOKEN(ctx, token) do { \
349 token_t *d__t = (token); \
351 if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \
352 DEBUG_PRINTF(((ctx), " Found: %s (%s)\n", d__t->s, d__t->value)); \
355 DEBUG_PRINTF((ctx, " Found: %s\n", d__t->s)); \
359 #define DEBUG_DUMP_EVAL(ctx, node) do { \
361 switch ((node)->token.type) { \
363 debug_printf((ctx), " Evaluate: %s (%s) -> %c\n", (node)->token.s,\
364 (node)->token.value, ((node)->value) ? '1':'0'); \
368 debug_printf((ctx), " Evaluate: %s (Left: %s; Right: %s) -> %c\n",\
370 (((node)->left->done) ? ((node)->left->value ?"1":"0") \
371 : "short circuited"), \
372 (((node)->right->done) ? ((node)->right->value?"1":"0") \
373 : "short circuited"), \
374 (node)->value ? '1' : '0'); \
382 if ((node)->right->token.type == TOKEN_RE) c = '/'; \
383 debug_printf((ctx), " Compare: %s (\"%s\" with %c%s%c) -> %c\n", \
385 (node)->left->token.value, \
386 c, (node)->right->token.value, c, \
387 (node)->value ? '1' : '0'); \
390 debug_printf((ctx), " Evaluate: %s -> %c\n", (node)->token.s, \
391 (node)->value ? '1' : '0'); \
396 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched) do { \
398 DEBUG_PRINTF(((ctx), " Unmatched %c\n", (char)(unmatched))); \
402 #define DEBUG_DUMP_COND(ctx, text) \
403 DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \
404 ((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0'))
406 #define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root)
408 #else /* DEBUG_INCLUDE */
410 #define TYPE_TOKEN(token, ttype) (token)->type = ttype
412 #define CREATE_NODE(ctx, name) do { \
413 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
414 (name)->parent = (name)->left = (name)->right = NULL; \
418 #define DEBUG_INIT(ctx, f, bb)
419 #define DEBUG_PRINTF(arg)
420 #define DEBUG_DUMP_TOKEN(ctx, token)
421 #define DEBUG_DUMP_EVAL(ctx, node)
422 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched)
423 #define DEBUG_DUMP_COND(ctx, text)
424 #define DEBUG_DUMP_TREE(ctx, root)
426 #endif /* !DEBUG_INCLUDE */
430 * +-------------------------------------------------------+
432 * | Static Module Data
434 * +-------------------------------------------------------+
437 /* global module structure */
438 module AP_MODULE_DECLARE_DATA include_module;
440 /* function handlers for include directives */
441 static apr_hash_t *include_handlers;
443 /* forward declaration of handler registry */
444 static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
446 /* Sentinel value to store in subprocess_env for items that
447 * shouldn't be evaluated until/unless they're actually used
449 static const char lazy_eval_sentinel;
450 #define LAZY_VALUE (&lazy_eval_sentinel)
453 #define DEFAULT_START_SEQUENCE "<!--#"
454 #define DEFAULT_END_SEQUENCE "-->"
455 #define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
456 #define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
457 #define DEFAULT_UNDEFINED_ECHO "(none)"
460 #define DEFAULT_XBITHACK XBITHACK_FULL
462 #define DEFAULT_XBITHACK XBITHACK_OFF
467 * +-------------------------------------------------------+
469 * | Environment/Expansion Functions
471 * +-------------------------------------------------------+
475 * decodes a string containing html entities or numeric character references.
476 * 's' is overwritten with the decoded string.
477 * If 's' is syntatically incorrect, then the followed fixups will be made:
478 * unknown entities will be left undecoded;
479 * references to unused numeric characters will be deleted.
480 * In particular, � will not be decoded, but will be deleted.
483 /* maximum length of any ISO-LATIN-1 HTML entity name. */
484 #define MAXENTLEN (6)
486 /* The following is a shrinking transformation, therefore safe. */
488 static void decodehtml(char *s)
493 static const char * const entlist[MAXENTLEN + 1] =
497 "lt\074gt\076", /* 2 */
498 "amp\046ETH\320eth\360", /* 3 */
499 "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml"
500 "\353iuml\357ouml\366uuml\374yuml\377", /* 4 */
502 "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc"
503 "\333THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352"
504 "icirc\356ocirc\364ucirc\373thorn\376", /* 5 */
506 "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311"
507 "Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde"
508 "\325Oslash\330Ugrave\331Uacute\332Yacute\335agrave\340"
509 "aacute\341atilde\343ccedil\347egrave\350eacute\351igrave"
510 "\354iacute\355ntilde\361ograve\362oacute\363otilde\365"
511 "oslash\370ugrave\371uacute\372yacute\375" /* 6 */
514 /* Do a fast scan through the string until we find anything
515 * that needs more complicated handling
517 for (; *s != '&'; s++) {
523 for (p = s; *s != '\0'; s++, p++) {
528 /* find end of entity */
529 for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
533 if (s[i] == '\0') { /* treat as normal data */
538 /* is it numeric ? */
540 for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
541 val = val * 10 + s[j] - '0';
544 if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
545 (val >= 127 && val <= 160) || val >= 256) {
546 p--; /* no data to output */
549 *p = RAW_ASCII_CHAR(val);
554 if (j > MAXENTLEN || entlist[j] == NULL) {
557 continue; /* skip it */
559 for (ents = entlist[j]; *ents != '\0'; ents += i) {
560 if (strncmp(s + 1, ents, j) == 0) {
566 *p = '&'; /* unknown */
569 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
578 static void add_include_vars(request_rec *r, const char *timefmt)
580 apr_table_t *e = r->subprocess_env;
583 apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
584 apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
585 apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
586 apr_table_setn(e, "DOCUMENT_URI", r->uri);
587 if (r->path_info && *r->path_info) {
588 apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
590 apr_table_setn(e, "USER_NAME", LAZY_VALUE);
591 if (r->filename && (t = strrchr(r->filename, '/'))) {
592 apr_table_setn(e, "DOCUMENT_NAME", ++t);
595 apr_table_setn(e, "DOCUMENT_NAME", r->uri);
598 char *arg_copy = apr_pstrdup(r->pool, r->args);
600 ap_unescape_url(arg_copy);
601 apr_table_setn(e, "QUERY_STRING_UNESCAPED",
602 ap_escape_shell_cmd(r->pool, arg_copy));
606 static const char *add_include_vars_lazy(request_rec *r, const char *var)
609 if (!strcasecmp(var, "DATE_LOCAL")) {
610 include_dir_config *conf =
611 (include_dir_config *)ap_get_module_config(r->per_dir_config,
613 val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 0);
615 else if (!strcasecmp(var, "DATE_GMT")) {
616 include_dir_config *conf =
617 (include_dir_config *)ap_get_module_config(r->per_dir_config,
619 val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 1);
621 else if (!strcasecmp(var, "LAST_MODIFIED")) {
622 include_dir_config *conf =
623 (include_dir_config *)ap_get_module_config(r->per_dir_config,
625 val = ap_ht_time(r->pool, r->finfo.mtime, conf->default_time_fmt, 0);
627 else if (!strcasecmp(var, "USER_NAME")) {
628 if (apr_uid_name_get(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
637 apr_table_setn(r->subprocess_env, var, val);
642 static const char *get_include_var(const char *var, include_ctx_t *ctx)
645 request_rec *r = ctx->intern->r;
647 if (apr_isdigit(*var) && !var[1]) {
648 apr_size_t idx = *var - '0';
649 backref_t *re = ctx->intern->re;
651 /* Handle $0 .. $9 from the last regex evaluated.
652 * The choice of returning NULL strings on not-found,
653 * v.s. empty strings on an empty match is deliberate.
656 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
657 "regex capture $%" APR_SIZE_T_FMT " refers to no regex in %s",
662 if (re->nsub < idx || idx >= AP_MAX_REG_MATCH) {
663 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
664 "regex capture $%" APR_SIZE_T_FMT
665 " is out of range (last regex was: '%s') in %s",
666 idx, re->rexp, r->filename);
670 if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) {
674 val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so,
675 re->match[idx].rm_eo - re->match[idx].rm_so);
679 val = apr_table_get(r->subprocess_env, var);
681 if (val == LAZY_VALUE) {
682 val = add_include_vars_lazy(r, var);
690 * Do variable substitution on strings
692 * (Note: If out==NULL, this function allocs a buffer for the resulting
693 * string from ctx->pool. The return value is always the parsed string)
695 static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out,
696 apr_size_t length, int leave_name)
698 request_rec *r = ctx->intern->r;
699 result_item_t *result = NULL, *current = NULL;
700 apr_size_t outlen = 0, inlen, span;
701 char *ret = NULL, *eout = NULL;
705 /* sanity check, out && !length is not supported */
706 ap_assert(out && length);
709 eout = out + length - 1;
712 span = strcspn(in, "\\$");
718 apr_cpystrn(out, in, length);
721 ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen)
722 ? length - 1 : inlen);
728 /* well, actually something to do */
733 memcpy(out, in, (out+span <= eout) ? span : (eout-out));
738 current = result = apr_palloc(ctx->dpool, sizeof(*result));
739 current->next = NULL;
740 current->string = in;
745 /* loop for specials */
747 if ((out && out >= eout) || (length && outlen >= length)) {
751 /* prepare next entry */
752 if (!out && current->len) {
753 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
754 current = current->next;
755 current->next = NULL;
764 *out++ = (p[1] == '$') ? *++p : *p;
769 current->string = (p[1] == '$') ? ++p : p;
778 else { /* *p == '$' */
779 const char *newp = NULL, *ep, *key = NULL;
782 ep = ap_strchr_c(++p, '}');
784 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Missing '}' on "
785 "variable \"%s\" in %s", p, r->filename);
790 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
797 while (*ep == '_' || apr_isalnum(*ep)) {
802 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
808 /* empty name results in a copy of '$' in the output string */
815 current->string = p++;
820 const char *val = get_include_var(key, ctx);
826 else if (leave_name) {
833 memcpy(out, val, (out+len <= eout) ? len : (eout-out));
838 current->string = val;
847 if ((out && out >= eout) || (length && outlen >= length)) {
851 /* check the remainder */
852 if (*p && (span = strcspn(p, "\\$")) > 0) {
853 if (!out && current->len) {
854 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
855 current = current->next;
856 current->next = NULL;
860 memcpy(out, p, (out+span <= eout) ? span : (eout-out));
871 } while (p < in+inlen);
873 /* assemble result */
885 if (length && outlen > length) {
889 ret = out = apr_palloc(ctx->pool, outlen + 1);
894 memcpy(out, result->string, (out+result->len <= ep)
895 ? result->len : (ep-out));
898 result = result->next;
899 } while (result && out < ep);
909 * +-------------------------------------------------------+
911 * | Conditional Expression Parser
913 * +-------------------------------------------------------+
916 static APR_INLINE int re_check(include_ctx_t *ctx, const char *string,
920 backref_t *re = ctx->intern->re;
923 compiled = ap_pregcomp(ctx->dpool, rexp, REG_EXTENDED);
925 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->intern->r, "unable to "
926 "compile pattern \"%s\"", rexp);
931 re = ctx->intern->re = apr_palloc(ctx->pool, sizeof(*re));
934 re->source = apr_pstrdup(ctx->pool, string);
935 re->rexp = apr_pstrdup(ctx->pool, rexp);
936 re->nsub = compiled->re_nsub;
937 rc = !ap_regexec(compiled, string, AP_MAX_REG_MATCH, re->match, 0);
939 ap_pregfree(ctx->dpool, compiled);
943 static int get_ptoken(apr_pool_t *pool, const char **parse, token_t *token)
955 /* Skip leading white space */
956 while (apr_isspace(**parse)) {
965 TYPE_TOKEN(token, TOKEN_STRING); /* the default type */
969 switch (*(*parse)++) {
971 TYPE_TOKEN(token, TOKEN_LBRACE);
974 TYPE_TOKEN(token, TOKEN_RBRACE);
977 if (**parse == '=') ++*parse;
978 TYPE_TOKEN(token, TOKEN_EQ);
981 if (**parse == '=') {
982 TYPE_TOKEN(token, TOKEN_NE);
986 TYPE_TOKEN(token, TOKEN_NOT);
992 TYPE_TOKEN(token, TOKEN_RE);
996 if (**parse == '|') {
997 TYPE_TOKEN(token, TOKEN_OR);
1003 if (**parse == '&') {
1004 TYPE_TOKEN(token, TOKEN_AND);
1010 if (**parse == '=') {
1011 TYPE_TOKEN(token, TOKEN_GE);
1015 TYPE_TOKEN(token, TOKEN_GT);
1018 if (**parse == '=') {
1019 TYPE_TOKEN(token, TOKEN_LE);
1023 TYPE_TOKEN(token, TOKEN_LT);
1027 /* It's a string or regex token
1028 * Now search for the next token, which finishes this string
1031 p = *parse = token->value = unmatched ? *parse : p;
1033 for (; **parse; p = ++*parse) {
1034 if (**parse == '\\') {
1044 if (**parse == unmatched) {
1049 } else if (apr_isspace(**parse)) {
1067 if ((*parse)[1] == **parse) {
1081 token->value = apr_pstrdup(pool, "");
1084 apr_size_t len = p - token->value - shift;
1085 char *c = apr_palloc(pool, len + 1);
1091 const char *e = ap_strchr_c(p, '\\');
1109 static int parse_expr(include_ctx_t *ctx, const char *expr, int *was_error)
1111 parse_node_t *new, *root = NULL, *current = NULL;
1112 request_rec *r = ctx->intern->r;
1113 const char *error = "Invalid expression \"%s\" in file %s";
1114 const char *parse = expr;
1115 int was_unmatched = 0;
1124 /* Create Parse Tree */
1126 /* uncomment this to see how the tree a built:
1128 * DEBUG_DUMP_TREE(ctx, root);
1130 CREATE_NODE(ctx, new);
1132 was_unmatched = get_ptoken(ctx->dpool, &parse, &new->token);
1137 DEBUG_DUMP_UNMATCHED(ctx, was_unmatched);
1138 DEBUG_DUMP_TOKEN(ctx, &new->token);
1141 switch (new->token.type) {
1145 root = current = new;
1149 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr,
1156 switch (new->token.type) {
1158 switch (current->token.type) {
1160 current->token.value =
1161 apr_pstrcat(ctx->dpool, current->token.value,
1162 *current->token.value ? " " : "",
1163 new->token.value, NULL);
1172 new->parent = current;
1173 current = current->right = new;
1179 switch (current->token.type) {
1182 new->parent = current;
1183 current = current->right = new;
1194 switch (current->token.type) {
1198 current = current->parent;
1201 switch (current->token.type) {
1208 current = current->parent;
1217 current = root = new;
1221 new->left = current->right;
1222 new->left->parent = new;
1223 new->parent = current;
1224 current = current->right = new;
1238 if (current->token.type == TOKEN_STRING) {
1239 current = current->parent;
1244 current = root = new;
1248 switch (current->token.type) {
1252 new->left = current->right;
1253 new->left->parent = new;
1254 new->parent = current;
1255 current = current->right = new;
1265 while (current && current->token.type != TOKEN_LBRACE) {
1266 current = current->parent;
1270 TYPE_TOKEN(¤t->token, TOKEN_GROUP);
1274 error = "Unmatched ')' in \"%s\" in file %s";
1279 switch (current->token.type) {
1287 current->right = new;
1288 new->parent = current;
1298 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename);
1303 DEBUG_DUMP_TREE(ctx, root);
1305 /* Evaluate Parse Tree */
1309 switch (current->token.type) {
1311 current->token.value =
1312 ap_ssi_parse_string(ctx, current->token.value, NULL, 0,
1313 SSI_EXPAND_DROP_NAME);
1314 current->value = !!*current->token.value;
1319 if (!current->left || !current->right) {
1320 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1321 "Invalid expression \"%s\" in file %s",
1327 if (!current->left->done) {
1328 switch (current->left->token.type) {
1330 current->left->token.value =
1331 ap_ssi_parse_string(ctx, current->left->token.value,
1332 NULL, 0, SSI_EXPAND_DROP_NAME);
1333 current->left->value = !!*current->left->token.value;
1334 DEBUG_DUMP_EVAL(ctx, current->left);
1335 current->left->done = 1;
1339 current = current->left;
1344 /* short circuit evaluation */
1345 if (!current->right->done && !regex &&
1346 ((current->token.type == TOKEN_AND && !current->left->value) ||
1347 (current->token.type == TOKEN_OR && current->left->value))) {
1348 current->value = current->left->value;
1351 if (!current->right->done) {
1352 switch (current->right->token.type) {
1354 current->right->token.value =
1355 ap_ssi_parse_string(ctx,current->right->token.value,
1356 NULL, 0, SSI_EXPAND_DROP_NAME);
1357 current->right->value = !!*current->right->token.value;
1358 DEBUG_DUMP_EVAL(ctx, current->right);
1359 current->right->done = 1;
1363 current = current->right;
1368 if (current->token.type == TOKEN_AND) {
1369 current->value = current->left->value &&
1370 current->right->value;
1373 current->value = current->left->value ||
1374 current->right->value;
1381 if (!current->left || !current->right ||
1382 current->left->token.type != TOKEN_STRING ||
1383 (current->right->token.type != TOKEN_STRING &&
1384 current->right->token.type != TOKEN_RE)) {
1385 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1386 "Invalid expression \"%s\" in file %s",
1391 current->left->token.value =
1392 ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
1393 SSI_EXPAND_DROP_NAME);
1394 current->right->token.value =
1395 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
1396 SSI_EXPAND_DROP_NAME);
1398 if (current->right->token.type == TOKEN_RE) {
1399 current->value = re_check(ctx, current->left->token.value,
1400 current->right->token.value);
1404 current->value = !strcmp(current->left->token.value,
1405 current->right->token.value);
1408 if (current->token.type == TOKEN_NE) {
1409 current->value = !current->value;
1417 if (!current->left || !current->right ||
1418 current->left->token.type != TOKEN_STRING ||
1419 current->right->token.type != TOKEN_STRING) {
1420 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1421 "Invalid expression \"%s\" in file %s",
1427 current->left->token.value =
1428 ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
1429 SSI_EXPAND_DROP_NAME);
1430 current->right->token.value =
1431 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
1432 SSI_EXPAND_DROP_NAME);
1434 current->value = strcmp(current->left->token.value,
1435 current->right->token.value);
1437 switch (current->token.type) {
1438 case TOKEN_GE: current->value = current->value >= 0; break;
1439 case TOKEN_GT: current->value = current->value > 0; break;
1440 case TOKEN_LE: current->value = current->value <= 0; break;
1441 case TOKEN_LT: current->value = current->value < 0; break;
1442 default: current->value = 0; break; /* should not happen */
1448 if (current->right) {
1449 if (!current->right->done) {
1450 current = current->right;
1453 current->value = current->right->value;
1459 if (current->token.type == TOKEN_NOT) {
1460 current->value = !current->value;
1466 error = "No operator before regex in expr \"%s\" in file %s";
1470 error = "Unmatched '(' in \"%s\" in file %s";
1474 error = "internal parser error in \"%s\" in file %s";
1477 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr,r->filename);
1482 DEBUG_DUMP_EVAL(ctx, current);
1484 current = current->parent;
1487 return (root ? root->value : 0);
1492 * +-------------------------------------------------------+
1496 * +-------------------------------------------------------+
1500 * Extract the next tag name and value.
1501 * If there are no more tags, set the tag name to NULL.
1502 * The tag value is html decoded if dodecode is non-zero.
1503 * The tag value may be NULL if there is no tag value..
1505 static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
1506 char **tag_val, int dodecode)
1508 if (!ctx->intern->argv) {
1515 *tag_val = ctx->intern->argv->value;
1516 *tag = ctx->intern->argv->name;
1518 ctx->intern->argv = ctx->intern->argv->next;
1520 if (dodecode && *tag_val) {
1521 decodehtml(*tag_val);
1527 static int find_file(request_rec *r, const char *directive, const char *tag,
1528 char *tag_val, apr_finfo_t *finfo)
1530 char *to_send = tag_val;
1531 request_rec *rr = NULL;
1533 char *error_fmt = NULL;
1534 apr_status_t rv = APR_SUCCESS;
1536 if (!strcmp(tag, "file")) {
1539 /* be safe; only files in this directory or below allowed */
1540 rv = apr_filepath_merge(&newpath, NULL, tag_val,
1541 APR_FILEPATH_NOTABOVEROOT |
1542 APR_FILEPATH_SECUREROOTTEST |
1543 APR_FILEPATH_NOTABSOLUTE, r->pool);
1545 if (rv != APR_SUCCESS) {
1546 error_fmt = "unable to access file \"%s\" "
1547 "in parsed file %s";
1550 /* note: it is okay to pass NULL for the "next filter" since
1551 we never attempt to "run" this sub request. */
1552 rr = ap_sub_req_lookup_file(newpath, r, NULL);
1554 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1555 to_send = rr->filename;
1556 if ((rv = apr_stat(finfo, to_send,
1557 APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
1558 && rv != APR_INCOMPLETE) {
1559 error_fmt = "unable to get information about \"%s\" "
1560 "in parsed file %s";
1564 error_fmt = "unable to lookup information about \"%s\" "
1565 "in parsed file %s";
1571 ap_log_rerror(APLOG_MARK, APLOG_ERR,
1572 rv, r, error_fmt, to_send, r->filename);
1575 if (rr) ap_destroy_sub_req(rr);
1579 else if (!strcmp(tag, "virtual")) {
1580 /* note: it is okay to pass NULL for the "next filter" since
1581 we never attempt to "run" this sub request. */
1582 rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1584 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1585 memcpy((char *) finfo, (const char *) &rr->finfo,
1587 ap_destroy_sub_req(rr);
1591 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unable to get "
1592 "information about \"%s\" in parsed file %s",
1593 tag_val, r->filename);
1594 ap_destroy_sub_req(rr);
1599 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
1600 "to tag %s in %s", tag, directive, r->filename);
1606 * <!--#include virtual|file="..." [virtual|file="..."] ... -->
1608 static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
1609 apr_bucket_brigade *bb)
1611 request_rec *r = f->r;
1614 ap_log_rerror(APLOG_MARK,
1615 (ctx->flags & SSI_FLAG_PRINTING)
1616 ? APLOG_ERR : APLOG_WARNING,
1617 0, r, "missing argument for include element in %s",
1621 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1626 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1632 char *tag_val = NULL;
1633 request_rec *rr = NULL;
1634 char *error_fmt = NULL;
1635 char *parsed_string;
1637 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1638 if (!tag || !tag_val) {
1642 if (strcmp(tag, "virtual") && strcmp(tag, "file")) {
1643 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1644 "\"%s\" to tag include in %s", tag, r->filename);
1645 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1649 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1650 SSI_EXPAND_DROP_NAME);
1651 if (tag[0] == 'f') {
1655 /* be safe; only files in this directory or below allowed */
1656 rv = apr_filepath_merge(&newpath, NULL, parsed_string,
1657 APR_FILEPATH_NOTABOVEROOT |
1658 APR_FILEPATH_SECUREROOTTEST |
1659 APR_FILEPATH_NOTABSOLUTE, ctx->dpool);
1661 if (rv != APR_SUCCESS) {
1662 error_fmt = "unable to include file \"%s\" in parsed file %s";
1665 rr = ap_sub_req_lookup_file(newpath, r, f->next);
1669 rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
1672 if (!error_fmt && rr->status != HTTP_OK) {
1673 error_fmt = "unable to include \"%s\" in parsed file %s";
1676 if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) &&
1677 rr->content_type && strncmp(rr->content_type, "text/", 5)) {
1679 error_fmt = "unable to include potential exec \"%s\" in parsed "
1683 /* See the Kludge in includes_filter for why.
1684 * Basically, it puts a bread crumb in here, then looks
1685 * for the crumb later to see if its been here.
1688 ap_set_module_config(rr->request_config, &include_module, r);
1691 if (!error_fmt && ap_run_sub_req(rr)) {
1692 error_fmt = "unable to include \"%s\" in parsed file %s";
1696 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, tag_val,
1698 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1701 /* destroy the sub request */
1703 ap_destroy_sub_req(rr);
1715 * <!--#echo [encoding="..."] var="..." [encoding="..."] var="..." ... -->
1717 static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
1718 apr_bucket_brigade *bb)
1720 enum {E_NONE, E_URL, E_ENTITY} encode;
1721 request_rec *r = f->r;
1724 ap_log_rerror(APLOG_MARK,
1725 (ctx->flags & SSI_FLAG_PRINTING)
1726 ? APLOG_ERR : APLOG_WARNING,
1727 0, r, "missing argument for echo element in %s",
1731 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1736 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1744 char *tag_val = NULL;
1746 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1747 if (!tag || !tag_val) {
1751 if (!strcmp(tag, "var")) {
1753 const char *echo_text = NULL;
1756 val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL,
1757 0, SSI_EXPAND_DROP_NAME),
1766 echo_text = ap_escape_uri(ctx->dpool, val);
1769 echo_text = ap_escape_html(ctx->dpool, val);
1773 e_len = strlen(echo_text);
1776 echo_text = ctx->intern->undefined_echo;
1777 e_len = ctx->intern->undefined_echo_len;
1780 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(
1781 apr_pmemdup(ctx->pool, echo_text, e_len),
1782 e_len, ctx->pool, f->c->bucket_alloc));
1784 else if (!strcmp(tag, "encoding")) {
1785 if (!strcasecmp(tag_val, "none")) {
1788 else if (!strcasecmp(tag_val, "url")) {
1791 else if (!strcasecmp(tag_val, "entity")) {
1795 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1796 "\"%s\" to parameter \"encoding\" of tag echo in "
1797 "%s", tag_val, r->filename);
1798 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1803 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1804 "\"%s\" in tag echo of %s", tag, r->filename);
1805 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1814 * <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."]
1815 * [echomsg="..."] -->
1817 static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
1818 apr_bucket_brigade *bb)
1820 request_rec *r = f->r;
1821 apr_table_t *env = r->subprocess_env;
1824 ap_log_rerror(APLOG_MARK,
1825 (ctx->flags & SSI_FLAG_PRINTING)
1826 ? APLOG_ERR : APLOG_WARNING,
1827 0, r, "missing argument for config element in %s",
1831 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1836 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1842 char *tag_val = NULL;
1844 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
1845 if (!tag || !tag_val) {
1849 if (!strcmp(tag, "errmsg")) {
1850 ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1851 SSI_EXPAND_DROP_NAME);
1853 else if (!strcmp(tag, "echomsg")) {
1854 ctx->intern->undefined_echo =
1855 ap_ssi_parse_string(ctx, tag_val, NULL, 0,SSI_EXPAND_DROP_NAME);
1856 ctx->intern->undefined_echo_len=strlen(ctx->intern->undefined_echo);
1858 else if (!strcmp(tag, "timefmt")) {
1859 apr_time_t date = r->request_time;
1861 ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1862 SSI_EXPAND_DROP_NAME);
1864 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
1866 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
1868 apr_table_setn(env, "LAST_MODIFIED",
1869 ap_ht_time(r->pool, r->finfo.mtime,
1872 else if (!strcmp(tag, "sizefmt")) {
1873 char *parsed_string;
1875 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1876 SSI_EXPAND_DROP_NAME);
1877 if (!strcmp(parsed_string, "bytes")) {
1878 ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
1880 else if (!strcmp(parsed_string, "abbrev")) {
1881 ctx->flags &= SSI_FLAG_SIZE_ABBREV;
1884 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1885 "\"%s\" to parameter \"sizefmt\" of tag config "
1886 "in %s", parsed_string, r->filename);
1887 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1892 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1893 "\"%s\" to tag config in %s", tag, r->filename);
1894 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1903 * <!--#fsize virtual|file="..." [virtual|file="..."] ... -->
1905 static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
1906 apr_bucket_brigade *bb)
1908 request_rec *r = f->r;
1911 ap_log_rerror(APLOG_MARK,
1912 (ctx->flags & SSI_FLAG_PRINTING)
1913 ? APLOG_ERR : APLOG_WARNING,
1914 0, r, "missing argument for fsize element in %s",
1918 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1923 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1929 char *tag_val = NULL;
1931 char *parsed_string;
1933 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1934 if (!tag || !tag_val) {
1938 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1939 SSI_EXPAND_DROP_NAME);
1941 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
1945 if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
1946 buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5));
1947 len = 4; /* omit the \0 terminator */
1950 apr_size_t l, x, pos;
1953 tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size);
1954 len = l = strlen(tmp);
1956 for (x = 0; x < l; ++x) {
1957 if (x && !((l - x) % 3)) {
1963 buf = apr_pstrmemdup(ctx->pool, tmp, len);
1966 buf = apr_palloc(ctx->pool, len);
1968 for (pos = x = 0; x < l; ++x) {
1969 if (x && !((l - x) % 3)) {
1972 buf[pos++] = tmp[x];
1977 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len,
1978 ctx->pool, f->c->bucket_alloc));
1981 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1990 * <!--#flastmod virtual|file="..." [virtual|file="..."] ... -->
1992 static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
1993 apr_bucket_brigade *bb)
1995 request_rec *r = f->r;
1998 ap_log_rerror(APLOG_MARK,
1999 (ctx->flags & SSI_FLAG_PRINTING)
2000 ? APLOG_ERR : APLOG_WARNING,
2001 0, r, "missing argument for flastmod element in %s",
2005 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2010 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2016 char *tag_val = NULL;
2018 char *parsed_string;
2020 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2021 if (!tag || !tag_val) {
2025 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2026 SSI_EXPAND_DROP_NAME);
2028 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
2032 t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0);
2033 t_len = strlen(t_val);
2035 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len,
2036 ctx->pool, f->c->bucket_alloc));
2039 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2048 * <!--#if expr="..." -->
2050 static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f,
2051 apr_bucket_brigade *bb)
2055 request_rec *r = f->r;
2056 int expr_ret, was_error;
2058 if (ctx->argc != 1) {
2059 ap_log_rerror(APLOG_MARK,
2060 (ctx->flags & SSI_FLAG_PRINTING)
2061 ? APLOG_ERR : APLOG_WARNING,
2063 ? "too many arguments for if element in %s"
2064 : "missing expr argument for if element in %s",
2068 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2069 ++(ctx->if_nesting_level);
2073 if (ctx->argc != 1) {
2074 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2078 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2080 if (strcmp(tag, "expr")) {
2081 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
2082 "to tag if in %s", tag, r->filename);
2083 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2088 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr value for if "
2089 "element in %s", r->filename);
2090 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2094 DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr));
2096 expr_ret = parse_expr(ctx, expr, &was_error);
2099 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2104 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2107 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2110 DEBUG_DUMP_COND(ctx, " if");
2112 ctx->if_nesting_level = 0;
2118 * <!--#elif expr="..." -->
2120 static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f,
2121 apr_bucket_brigade *bb)
2125 request_rec *r = f->r;
2126 int expr_ret, was_error;
2128 if (ctx->argc != 1) {
2129 ap_log_rerror(APLOG_MARK,
2130 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2132 ? "too many arguments for if element in %s"
2133 : "missing expr argument for if element in %s",
2137 if (ctx->if_nesting_level) {
2141 if (ctx->argc != 1) {
2142 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2146 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2148 if (strcmp(tag, "expr")) {
2149 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
2150 "to tag if in %s", tag, r->filename);
2151 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2156 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr in elif "
2157 "statement: %s", r->filename);
2158 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2162 DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr));
2163 DEBUG_DUMP_COND(ctx, " elif");
2165 if (ctx->flags & SSI_FLAG_COND_TRUE) {
2166 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2170 expr_ret = parse_expr(ctx, expr, &was_error);
2173 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2178 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2181 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2184 DEBUG_DUMP_COND(ctx, " elif");
2192 static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f,
2193 apr_bucket_brigade *bb)
2195 request_rec *r = f->r;
2198 ap_log_rerror(APLOG_MARK,
2199 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2200 0, r, "else directive does not take tags in %s",
2204 if (ctx->if_nesting_level) {
2209 if (ctx->flags & SSI_FLAG_PRINTING) {
2210 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2216 DEBUG_DUMP_COND(ctx, " else");
2218 if (ctx->flags & SSI_FLAG_COND_TRUE) {
2219 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2222 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2231 static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f,
2232 apr_bucket_brigade *bb)
2234 request_rec *r = f->r;
2237 ap_log_rerror(APLOG_MARK,
2238 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2239 0, r, "endif directive does not take tags in %s",
2243 if (ctx->if_nesting_level) {
2244 --(ctx->if_nesting_level);
2249 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2253 DEBUG_DUMP_COND(ctx, "endif");
2255 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2261 * <!--#set var="..." value="..." ... -->
2263 static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
2264 apr_bucket_brigade *bb)
2267 request_rec *r = f->r;
2268 request_rec *sub = r->main;
2269 apr_pool_t *p = r->pool;
2271 if (ctx->argc < 2) {
2272 ap_log_rerror(APLOG_MARK,
2273 (ctx->flags & SSI_FLAG_PRINTING)
2274 ? APLOG_ERR : APLOG_WARNING,
2275 0, r, "missing argument for set element in %s",
2279 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2283 if (ctx->argc < 2) {
2284 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2288 /* we need to use the 'main' request pool to set notes as that is
2298 char *tag_val = NULL;
2300 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2302 if (!tag || !tag_val) {
2306 if (!strcmp(tag, "var")) {
2307 var = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2308 SSI_EXPAND_DROP_NAME);
2310 else if (!strcmp(tag, "value")) {
2311 char *parsed_string;
2314 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "variable must "
2315 "precede value in set directive in %s",
2317 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2321 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2322 SSI_EXPAND_DROP_NAME);
2323 apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
2324 apr_pstrdup(p, parsed_string));
2327 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Invalid tag for set "
2328 "directive in %s", r->filename);
2329 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2340 static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
2341 apr_bucket_brigade *bb)
2343 request_rec *r = f->r;
2344 const apr_array_header_t *arr;
2345 const apr_table_entry_t *elts;
2349 ap_log_rerror(APLOG_MARK,
2350 (ctx->flags & SSI_FLAG_PRINTING)
2351 ? APLOG_ERR : APLOG_WARNING,
2352 0, r, "printenv directive does not take tags in %s",
2356 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2361 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2365 arr = apr_table_elts(r->subprocess_env);
2366 elts = (apr_table_entry_t *)arr->elts;
2368 for (i = 0; i < arr->nelts; ++i) {
2369 const char *key_text, *val_text;
2370 char *key_val, *next;
2371 apr_size_t k_len, v_len, kv_length;
2374 key_text = ap_escape_html(ctx->dpool, elts[i].key);
2375 k_len = strlen(key_text);
2378 val_text = elts[i].val;
2379 if (val_text == LAZY_VALUE) {
2380 val_text = add_include_vars_lazy(r, elts[i].key);
2382 val_text = ap_escape_html(ctx->dpool, elts[i].val);
2383 v_len = strlen(val_text);
2385 /* assemble result */
2386 kv_length = k_len + v_len + sizeof("=\n");
2387 key_val = apr_palloc(ctx->pool, kv_length);
2390 memcpy(next, key_text, k_len);
2393 memcpy(next, val_text, v_len);
2398 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(key_val, kv_length-1,
2399 ctx->pool, f->c->bucket_alloc));
2408 * +-------------------------------------------------------+
2410 * | Main Includes-Filter Engine
2412 * +-------------------------------------------------------+
2415 /* This is an implementation of the BNDM search algorithm.
2417 * Fast and Flexible String Matching by Combining Bit-parallelism and
2418 * Suffix Automata (2001)
2419 * Gonzalo Navarro, Mathieu Raffinot
2421 * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
2423 * Initial code submitted by Sascha Schumann.
2426 /* Precompile the bndm_t data structure. */
2427 static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl)
2430 const char *ne = n + nl;
2431 bndm_t *t = apr_palloc(pool, sizeof(*t));
2433 memset(t->T, 0, sizeof(unsigned int) * 256);
2434 t->pattern_len = nl;
2436 for (x = 1; n < ne; x <<= 1) {
2437 t->T[(unsigned char) *n++] |= x;
2445 /* Implements the BNDM search algorithm (as described above).
2447 * h - the string to look in
2448 * hl - length of the string to look for
2449 * t - precompiled bndm structure against the pattern
2451 * Returns the count of character that is the first match or hl if no
2454 static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl)
2457 const char *he, *p, *pi;
2458 unsigned int *T, x, d;
2465 nl = t->pattern_len;
2467 pi = h - 1; /* pi: p initial */
2468 p = pi + nl; /* compare window right to left. point to the first char */
2474 d &= T[(unsigned char) *p--];
2497 * returns the index position of the first byte of start_seq (or the len of
2498 * the buffer as non-match)
2500 static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data,
2503 struct ssi_internal_ctx *intern = ctx->intern;
2504 apr_size_t slen = intern->start_seq_pat->pattern_len;
2509 p = data; /* try partial match at the end of the buffer (below) */
2512 /* try fast bndm search over the buffer
2513 * (hopefully the whole start sequence can be found in this buffer)
2515 index = bndm(intern->start_seq_pat, data, len);
2517 /* wow, found it. ready. */
2519 intern->state = PARSE_DIRECTIVE;
2523 /* ok, the pattern can't be found as whole in the buffer,
2524 * check the end for a partial match
2526 p = data + len - slen + 1;
2532 while (p < ep && *p != *intern->start_seq) {
2538 /* found a possible start_seq start */
2543 while (p < ep && *p == intern->start_seq[pos]) {
2548 /* partial match found. Store the info for the next round */
2550 intern->state = PARSE_HEAD;
2551 intern->parse_pos = pos;
2556 /* we must try all combinations; consider (e.g.) SSIStartTag "--->"
2557 * and a string data of "--.-" and the end of the buffer
2559 p = data + index + 1;
2567 * returns the first byte *after* the partial (or final) match.
2569 * If we had to trick with the start_seq start, 'release' returns the
2570 * number of chars of the start_seq which appeared not to be part of a
2571 * full tag and may have to be passed down the filter chain.
2573 static apr_size_t find_partial_start_sequence(include_ctx_t *ctx,
2576 apr_size_t *release)
2578 struct ssi_internal_ctx *intern = ctx->intern;
2579 apr_size_t pos, spos = 0;
2580 apr_size_t slen = intern->start_seq_pat->pattern_len;
2583 pos = intern->parse_pos;
2590 while (p < ep && pos < slen && *p == intern->start_seq[pos]) {
2597 intern->state = PARSE_DIRECTIVE;
2601 /* the whole buffer is a partial match */
2603 intern->parse_pos = pos;
2607 /* No match so far, but again:
2608 * We must try all combinations, since the start_seq is a random
2609 * user supplied string
2611 * So: look if the first char of start_seq appears somewhere within
2612 * the current partial match. If it does, try to start a match that
2613 * begins with this offset. (This can happen, if a strange
2614 * start_seq like "---->" spans buffers)
2616 if (spos < intern->parse_pos) {
2620 p = intern->start_seq + spos;
2621 pos = intern->parse_pos - spos;
2623 while (pos && *p != *intern->start_seq) {
2630 /* if a matching beginning char was found, try to match the
2631 * remainder of the old buffer.
2637 while (t < pos && *p == intern->start_seq[t]) {
2643 /* yeah, another partial match found in the *old*
2644 * buffer, now test the *current* buffer for
2658 } while (1); /* work hard to find a match ;-) */
2660 /* no match at all, release all (wrongly) matched chars so far */
2661 *release = intern->parse_pos;
2662 intern->state = PARSE_PRE_HEAD;
2667 * returns the position after the directive
2669 static apr_size_t find_directive(include_ctx_t *ctx, const char *data,
2670 apr_size_t len, char ***store,
2671 apr_size_t **store_len)
2673 struct ssi_internal_ctx *intern = ctx->intern;
2674 const char *p = data;
2675 const char *ep = data + len;
2678 switch (intern->state) {
2679 case PARSE_DIRECTIVE:
2680 while (p < ep && !apr_isspace(*p)) {
2681 /* we have to consider the case of missing space between directive
2682 * and end_seq (be somewhat lenient), e.g. <!--#printenv-->
2684 if (*p == *intern->end_seq) {
2685 intern->state = PARSE_DIRECTIVE_TAIL;
2686 intern->parse_pos = 1;
2693 if (p < ep) { /* found delimiter whitespace */
2694 intern->state = PARSE_DIRECTIVE_POSTNAME;
2695 *store = &intern->directive;
2696 *store_len = &intern->directive_len;
2701 case PARSE_DIRECTIVE_TAIL:
2702 pos = intern->parse_pos;
2704 while (p < ep && pos < intern->end_seq_len &&
2705 *p == intern->end_seq[pos]) {
2710 /* full match, we're done */
2711 if (pos == intern->end_seq_len) {
2712 intern->state = PARSE_DIRECTIVE_POSTTAIL;
2713 *store = &intern->directive;
2714 *store_len = &intern->directive_len;
2718 /* partial match, the buffer is too small to match fully */
2720 intern->parse_pos = pos;
2724 /* no match. continue normal parsing */
2725 intern->state = PARSE_DIRECTIVE;
2728 case PARSE_DIRECTIVE_POSTTAIL:
2729 intern->state = PARSE_EXECUTE;
2730 intern->directive_len -= intern->end_seq_len;
2731 /* continue immediately with the next state */
2733 case PARSE_DIRECTIVE_POSTNAME:
2734 if (PARSE_DIRECTIVE_POSTNAME == intern->state) {
2735 intern->state = PARSE_PRE_ARG;
2738 intern->argv = NULL;
2740 if (!intern->directive_len) {
2742 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2743 "directive name in parsed document %s",
2744 intern->r->filename);
2747 char *sp = intern->directive;
2748 char *sep = intern->directive + intern->directive_len;
2750 /* normalize directive name */
2751 for (; sp < sep; ++sp) {
2752 *sp = apr_tolower(*sp);
2759 /* get a rid of a gcc warning about unhandled enumerations */
2767 * find out whether the next token is (a possible) end_seq or an argument
2769 static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data,
2772 struct ssi_internal_ctx *intern = ctx->intern;
2773 const char *p = data;
2774 const char *ep = data + len;
2776 /* skip leading WS */
2777 while (p < ep && apr_isspace(*p)) {
2781 /* buffer doesn't consist of whitespaces only */
2783 intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG;
2790 * test the stream for end_seq. If it doesn't match at all, it must be an
2793 static apr_size_t find_tail(include_ctx_t *ctx, const char *data,
2796 struct ssi_internal_ctx *intern = ctx->intern;
2797 const char *p = data;
2798 const char *ep = data + len;
2799 apr_size_t pos = intern->parse_pos;
2801 if (PARSE_TAIL == intern->state) {
2802 intern->state = PARSE_TAIL_SEQ;
2803 pos = intern->parse_pos = 0;
2806 while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) {
2811 /* bingo, full match */
2812 if (pos == intern->end_seq_len) {
2813 intern->state = PARSE_EXECUTE;
2817 /* partial match, the buffer is too small to match fully */
2819 intern->parse_pos = pos;
2823 /* no match. It must be an argument string then
2824 * The caller should cleanup and rewind to the reparse point
2826 intern->state = PARSE_ARG;
2831 * extract name=value from the buffer
2832 * A pcre-pattern could look (similar to):
2833 * name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
2835 static apr_size_t find_argument(include_ctx_t *ctx, const char *data,
2836 apr_size_t len, char ***store,
2837 apr_size_t **store_len)
2839 struct ssi_internal_ctx *intern = ctx->intern;
2840 const char *p = data;
2841 const char *ep = data + len;
2843 switch (intern->state) {
2846 * create argument structure and append it to the current list
2848 intern->current_arg = apr_palloc(ctx->dpool,
2849 sizeof(*intern->current_arg));
2850 intern->current_arg->next = NULL;
2853 if (!intern->argv) {
2854 intern->argv = intern->current_arg;
2857 arg_item_t *newarg = intern->argv;
2859 while (newarg->next) {
2860 newarg = newarg->next;
2862 newarg->next = intern->current_arg;
2865 /* check whether it's a valid one. If it begins with a quote, we
2866 * can safely assume, someone forgot the name of the argument
2869 case '"': case '\'': case '`':
2872 intern->state = PARSE_ARG_VAL;
2873 intern->quote = *p++;
2874 intern->current_arg->name = NULL;
2875 intern->current_arg->name_len = 0;
2878 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2879 "argument name for value to tag %s in %s",
2880 apr_pstrmemdup(intern->r->pool, intern->directive,
2881 intern->directive_len),
2882 intern->r->filename);
2887 intern->state = PARSE_ARG_NAME;
2889 /* continue immediately with next state */
2891 case PARSE_ARG_NAME:
2892 while (p < ep && !apr_isspace(*p) && *p != '=') {
2897 intern->state = PARSE_ARG_POSTNAME;
2898 *store = &intern->current_arg->name;
2899 *store_len = &intern->current_arg->name_len;
2904 case PARSE_ARG_POSTNAME:
2905 intern->current_arg->name = apr_pstrmemdup(ctx->dpool,
2906 intern->current_arg->name,
2907 intern->current_arg->name_len);
2908 if (!intern->current_arg->name_len) {
2910 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2911 "argument name for value to tag %s in %s",
2912 apr_pstrmemdup(intern->r->pool, intern->directive,
2913 intern->directive_len),
2914 intern->r->filename);
2917 char *sp = intern->current_arg->name;
2919 /* normalize the name */
2921 *sp = apr_tolower(*sp);
2926 intern->state = PARSE_ARG_EQ;
2927 /* continue with next state immediately */
2932 while (p < ep && apr_isspace(*p)) {
2938 intern->state = PARSE_ARG_PREVAL;
2941 else { /* no value */
2942 intern->current_arg->value = NULL;
2943 intern->state = PARSE_PRE_ARG;
2950 case PARSE_ARG_PREVAL:
2953 while (p < ep && apr_isspace(*p)) {
2957 /* buffer doesn't consist of whitespaces only */
2959 intern->state = PARSE_ARG_VAL;
2961 case '"': case '\'': case '`':
2962 intern->quote = *p++;
2965 intern->quote = '\0';
2973 case PARSE_ARG_VAL_ESC:
2974 if (*p == intern->quote) {
2977 intern->state = PARSE_ARG_VAL;
2978 /* continue with next state immediately */
2981 for (; p < ep; ++p) {
2982 if (intern->quote && *p == '\\') {
2985 intern->state = PARSE_ARG_VAL_ESC;
2989 if (*p != intern->quote) {
2993 else if (intern->quote && *p == intern->quote) {
2995 *store = &intern->current_arg->value;
2996 *store_len = &intern->current_arg->value_len;
2997 intern->state = PARSE_ARG_POSTVAL;
3000 else if (!intern->quote && apr_isspace(*p)) {
3002 *store = &intern->current_arg->value;
3003 *store_len = &intern->current_arg->value_len;
3004 intern->state = PARSE_ARG_POSTVAL;
3011 case PARSE_ARG_POSTVAL:
3013 * The value is still the raw input string. Finally clean it up.
3015 --(intern->current_arg->value_len);
3017 /* strip quote escaping \ from the string */
3018 if (intern->quote) {
3019 apr_size_t shift = 0;
3022 sp = intern->current_arg->value;
3023 ep = intern->current_arg->value + intern->current_arg->value_len;
3024 while (sp < ep && *sp != '\\') {
3027 for (; sp < ep; ++sp) {
3028 if (*sp == '\\' && sp[1] == intern->quote) {
3037 intern->current_arg->value_len -= shift;
3040 intern->current_arg->value[intern->current_arg->value_len] = '\0';
3041 intern->state = PARSE_PRE_ARG;
3046 /* get a rid of a gcc warning about unhandled enumerations */
3050 return len; /* partial match of something */
3054 * This is the main loop over the current bucket brigade.
3056 static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
3058 include_ctx_t *ctx = f->ctx;
3059 struct ssi_internal_ctx *intern = ctx->intern;
3060 request_rec *r = f->r;
3061 apr_bucket *b = APR_BRIGADE_FIRST(bb);
3062 apr_bucket_brigade *pass_bb;
3063 apr_status_t rv = APR_SUCCESS;
3064 char *magic; /* magic pointer for sentinel use */
3067 if (APR_BRIGADE_EMPTY(bb)) {
3071 /* we may crash, since already cleaned up; hand over the responsibility
3072 * to the next filter;-)
3074 if (intern->seen_eos) {
3075 return ap_pass_brigade(f->next, bb);
3078 /* All stuff passed along has to be put into that brigade */
3079 pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3081 /* initialization for this loop */
3082 intern->bytes_read = 0;
3087 /* loop over the current bucket brigade */
3088 while (b != APR_BRIGADE_SENTINEL(bb)) {
3089 const char *data = NULL;
3090 apr_size_t len, index, release;
3091 apr_bucket *newb = NULL;
3092 char **store = &magic;
3093 apr_size_t *store_len;
3095 /* handle meta buckets before reading any data */
3096 if (APR_BUCKET_IS_METADATA(b)) {
3097 newb = APR_BUCKET_NEXT(b);
3099 APR_BUCKET_REMOVE(b);
3101 if (APR_BUCKET_IS_EOS(b)) {
3102 intern->seen_eos = 1;
3104 /* Hit end of stream, time for cleanup ... But wait!
3105 * Perhaps we're not ready yet. We may have to loop one or
3106 * two times again to finish our work. In that case, we
3107 * just re-insert the EOS bucket to allow for an extra loop.
3109 * PARSE_EXECUTE means, we've hit a directive just before the
3110 * EOS, which is now waiting for execution.
3112 * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
3113 * no argument and no space between directive and end_seq
3114 * just before the EOS. (consider <!--#printenv--> as last
3115 * or only string within the stream). This state, however,
3116 * just cleans up and turns itself to PARSE_EXECUTE, which
3117 * will be passed through within the next (and actually
3120 if (PARSE_EXECUTE == intern->state ||
3121 PARSE_DIRECTIVE_POSTTAIL == intern->state) {
3122 APR_BUCKET_INSERT_BEFORE(newb, b);
3125 break; /* END OF STREAM */
3129 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3131 if (APR_BUCKET_IS_FLUSH(b)) {
3140 /* enough is enough ... */
3141 if (ctx->flush_now ||
3142 intern->bytes_read > AP_MIN_BYTES_TO_WRITE) {
3144 if (!APR_BRIGADE_EMPTY(pass_bb)) {
3145 rv = ap_pass_brigade(f->next, pass_bb);
3146 if (rv != APR_SUCCESS) {
3147 apr_brigade_destroy(pass_bb);
3153 intern->bytes_read = 0;
3156 /* read the current bucket data */
3158 if (!intern->seen_eos) {
3159 if (intern->bytes_read > 0) {
3160 rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
3161 if (APR_STATUS_IS_EAGAIN(rv)) {
3167 if (!len || rv != APR_SUCCESS) {
3168 rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
3171 if (rv != APR_SUCCESS) {
3172 apr_brigade_destroy(pass_bb);
3176 intern->bytes_read += len;
3179 /* zero length bucket, fetch next one */
3180 if (!len && !intern->seen_eos) {
3181 b = APR_BUCKET_NEXT(b);
3186 * it's actually a data containing bucket, start/continue parsing
3189 switch (intern->state) {
3190 /* no current tag; search for start sequence */
3191 case PARSE_PRE_HEAD:
3192 index = find_start_sequence(ctx, data, len);
3195 apr_bucket_split(b, index);
3198 newb = APR_BUCKET_NEXT(b);
3199 if (ctx->flags & SSI_FLAG_PRINTING) {
3200 APR_BUCKET_REMOVE(b);
3201 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3204 apr_bucket_delete(b);
3208 /* now delete the start_seq stuff from the remaining bucket */
3209 if (PARSE_DIRECTIVE == intern->state) { /* full match */
3210 apr_bucket_split(newb, intern->start_seq_pat->pattern_len);
3211 ctx->flush_now = 1; /* pass pre-tag stuff */
3214 b = APR_BUCKET_NEXT(newb);
3215 apr_bucket_delete(newb);
3223 /* we're currently looking for the end of the start sequence */
3225 index = find_partial_start_sequence(ctx, data, len, &release);
3227 /* check if we mismatched earlier and have to release some chars */
3228 if (release && (ctx->flags & SSI_FLAG_PRINTING)) {
3229 char *to_release = apr_palloc(ctx->pool, release);
3231 memcpy(to_release, intern->start_seq, release);
3232 newb = apr_bucket_pool_create(to_release, release, ctx->pool,
3233 f->c->bucket_alloc);
3234 APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
3237 if (index) { /* any match */
3238 /* now delete the start_seq stuff from the remaining bucket */
3239 if (PARSE_DIRECTIVE == intern->state) { /* final match */
3240 apr_bucket_split(b, index);
3241 ctx->flush_now = 1; /* pass pre-tag stuff */
3243 newb = APR_BUCKET_NEXT(b);
3244 apr_bucket_delete(b);
3250 /* we're currently grabbing the directive name */
3251 case PARSE_DIRECTIVE:
3252 case PARSE_DIRECTIVE_POSTNAME:
3253 case PARSE_DIRECTIVE_TAIL:
3254 case PARSE_DIRECTIVE_POSTTAIL:
3255 index = find_directive(ctx, data, len, &store, &store_len);
3258 apr_bucket_split(b, index);
3259 newb = APR_BUCKET_NEXT(b);
3264 APR_BUCKET_REMOVE(b);
3265 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3269 /* time for cleanup? */
3270 if (store != &magic) {
3271 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3273 apr_brigade_cleanup(intern->tmp_bb);
3277 apr_bucket_delete(b);
3283 /* skip WS and find out what comes next (arg or end_seq) */
3285 index = find_arg_or_tail(ctx, data, len);
3287 if (index) { /* skipped whitespaces */
3289 apr_bucket_split(b, index);
3291 newb = APR_BUCKET_NEXT(b);
3292 apr_bucket_delete(b);
3298 /* currently parsing name[=val] */
3300 case PARSE_ARG_NAME:
3301 case PARSE_ARG_POSTNAME:
3303 case PARSE_ARG_PREVAL:
3305 case PARSE_ARG_VAL_ESC:
3306 case PARSE_ARG_POSTVAL:
3307 index = find_argument(ctx, data, len, &store, &store_len);
3310 apr_bucket_split(b, index);
3311 newb = APR_BUCKET_NEXT(b);
3316 APR_BUCKET_REMOVE(b);
3317 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3321 /* time for cleanup? */
3322 if (store != &magic) {
3323 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3325 apr_brigade_cleanup(intern->tmp_bb);
3329 apr_bucket_delete(b);
3335 /* try to match end_seq at current pos. */
3337 case PARSE_TAIL_SEQ:
3338 index = find_tail(ctx, data, len);
3340 switch (intern->state) {
3341 case PARSE_EXECUTE: /* full match */
3342 apr_bucket_split(b, index);
3343 newb = APR_BUCKET_NEXT(b);
3344 apr_bucket_delete(b);
3348 case PARSE_ARG: /* no match */
3349 /* PARSE_ARG must reparse at the beginning */
3350 APR_BRIGADE_PREPEND(bb, intern->tmp_bb);
3351 b = APR_BRIGADE_FIRST(bb);
3354 default: /* partial match */
3355 newb = APR_BUCKET_NEXT(b);
3356 APR_BUCKET_REMOVE(b);
3357 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3364 /* now execute the parsed directive, cleanup the space and
3365 * start again with PARSE_PRE_HEAD
3368 /* if there was an error, it was already logged; just stop here */
3369 if (intern->error) {
3370 if (ctx->flags & SSI_FLAG_PRINTING) {
3371 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3376 include_handler_fn_t *handle_func;
3379 (include_handler_fn_t *)apr_hash_get(include_handlers, intern->directive,
3380 intern->directive_len);
3383 DEBUG_INIT(ctx, f, pass_bb);
3384 rv = handle_func(ctx, f, pass_bb);
3385 if (rv != APR_SUCCESS) {
3386 apr_brigade_destroy(pass_bb);
3391 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3392 "unknown directive \"%s\" in parsed doc %s",
3393 apr_pstrmemdup(r->pool, intern->directive,
3394 intern->directive_len),
3396 if (ctx->flags & SSI_FLAG_PRINTING) {
3397 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3403 apr_pool_clear(ctx->dpool);
3404 apr_brigade_cleanup(intern->tmp_bb);
3406 /* Oooof. Done here, start next round */
3407 intern->state = PARSE_PRE_HEAD;
3410 } /* switch(ctx->state) */
3412 } /* while(brigade) */
3414 /* End of stream. Final cleanup */
3415 if (intern->seen_eos) {
3416 if (PARSE_HEAD == intern->state) {
3417 if (ctx->flags & SSI_FLAG_PRINTING) {
3418 char *to_release = apr_palloc(ctx->pool, intern->parse_pos);
3420 memcpy(to_release, intern->start_seq, intern->parse_pos);
3421 APR_BRIGADE_INSERT_TAIL(pass_bb,
3422 apr_bucket_pool_create(to_release,
3423 intern->parse_pos, ctx->pool,
3424 f->c->bucket_alloc));
3427 else if (PARSE_PRE_HEAD != intern->state) {
3428 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3429 "SSI directive was not properly finished at the end "
3430 "of parsed document %s", r->filename);
3431 if (ctx->flags & SSI_FLAG_PRINTING) {
3432 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3436 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
3437 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
3438 "missing closing endif directive in parsed document"
3439 " %s", r->filename);
3442 /* cleanup our temporary memory */
3443 apr_brigade_destroy(intern->tmp_bb);
3444 apr_pool_destroy(ctx->dpool);
3446 /* don't forget to finally insert the EOS bucket */
3447 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3450 /* if something's left over, pass it along */
3451 if (!APR_BRIGADE_EMPTY(pass_bb)) {
3452 rv = ap_pass_brigade(f->next, pass_bb);
3456 apr_brigade_destroy(pass_bb);
3463 * +-------------------------------------------------------+
3467 * +-------------------------------------------------------+
3470 static int includes_setup(ap_filter_t *f)
3472 include_dir_config *conf = ap_get_module_config(f->r->per_dir_config,
3475 /* When our xbithack value isn't set to full or our platform isn't
3476 * providing group-level protection bits or our group-level bits do not
3477 * have group-execite on, we will set the no_local_copy value to 1 so
3478 * that we will not send 304s.
3480 if ((conf->xbithack != XBITHACK_FULL)
3481 || !(f->r->finfo.valid & APR_FINFO_GPROT)
3482 || !(f->r->finfo.protection & APR_GEXECUTE)) {
3483 f->r->no_local_copy = 1;
3486 /* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4.
3487 * We don't know if we are going to be including a file or executing
3488 * a program - in either case a strong ETag header will likely be invalid.
3490 apr_table_setn(f->r->notes, "no-etag", "");
3495 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
3497 request_rec *r = f->r;
3498 include_ctx_t *ctx = f->ctx;
3499 request_rec *parent;
3500 include_dir_config *conf = ap_get_module_config(r->per_dir_config,
3503 include_server_config *sconf= ap_get_module_config(r->server->module_config,
3506 if (!(ap_allow_options(r) & OPT_INCLUDES)) {
3507 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
3508 "mod_include: Options +Includes (or IncludesNoExec) "
3509 "wasn't set, INCLUDES filter removed");
3510 ap_remove_output_filter(f);
3511 return ap_pass_brigade(f->next, b);
3515 struct ssi_internal_ctx *intern;
3517 /* create context for this filter */
3518 f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx));
3519 ctx->intern = intern = apr_palloc(r->pool, sizeof(*ctx->intern));
3520 ctx->pool = r->pool;
3521 apr_pool_create(&ctx->dpool, ctx->pool);
3524 intern->tmp_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3525 intern->seen_eos = 0;
3526 intern->state = PARSE_PRE_HEAD;
3527 ctx->flags = (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
3528 if (ap_allow_options(r) & OPT_INCNOEXEC) {
3529 ctx->flags |= SSI_FLAG_NO_EXEC;
3532 ctx->if_nesting_level = 0;
3535 ctx->error_str = conf->default_error_msg;
3536 ctx->time_str = conf->default_time_fmt;
3537 intern->start_seq = sconf->default_start_tag;
3538 intern->start_seq_pat = bndm_compile(ctx->pool, intern->start_seq,
3539 strlen(intern->start_seq));
3540 intern->end_seq = sconf->default_end_tag;
3541 intern->end_seq_len = strlen(intern->end_seq);
3542 intern->undefined_echo = conf->undefined_echo;
3543 intern->undefined_echo_len = strlen(conf->undefined_echo);
3546 if ((parent = ap_get_module_config(r->request_config, &include_module))) {
3547 /* Kludge --- for nested includes, we want to keep the subprocess
3548 * environment of the base document (for compatibility); that means
3549 * torquing our own last_modified date as well so that the
3550 * LAST_MODIFIED variable gets reset to the proper value if the
3551 * nested document resets <!--#config timefmt -->.
3553 r->subprocess_env = r->main->subprocess_env;
3554 apr_pool_join(r->main->pool, r->pool);
3555 r->finfo.mtime = r->main->finfo.mtime;
3558 /* we're not a nested include, so we create an initial
3560 ap_add_common_vars(r);
3562 add_include_vars(r, conf->default_time_fmt);
3564 /* Always unset the content-length. There is no way to know if
3565 * the content will be modified at some point by send_parsed_content.
3566 * It is very possible for us to not find any content in the first
3567 * 9k of the file, but still have to modify the content of the file.
3568 * If we are going to pass the file through send_parsed_content, then
3569 * the content-length should just be unset.
3571 apr_table_unset(f->r->headers_out, "Content-Length");
3573 /* Always unset the Last-Modified field - see RFC2616 - 13.3.4.
3574 * We don't know if we are going to be including a file or executing
3575 * a program which may change the Last-Modified header or make the
3576 * content completely dynamic. Therefore, we can't support these
3578 * Exception: XBitHack full means we *should* set the Last-Modified field.
3581 /* Assure the platform supports Group protections */
3582 if ((conf->xbithack == XBITHACK_FULL)
3583 && (r->finfo.valid & APR_FINFO_GPROT)
3584 && (r->finfo.protection & APR_GEXECUTE)) {
3585 ap_update_mtime(r, r->finfo.mtime);
3586 ap_set_last_modified(r);
3589 apr_table_unset(f->r->headers_out, "Last-Modified");
3592 /* add QUERY stuff to env cause it ain't yet */
3594 char *arg_copy = apr_pstrdup(r->pool, r->args);
3596 apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
3597 ap_unescape_url(arg_copy);
3598 apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
3599 ap_escape_shell_cmd(r->pool, arg_copy));
3602 return send_parsed_content(f, b);
3605 static int include_fixup(request_rec *r)
3607 include_dir_config *conf;
3609 conf = ap_get_module_config(r->per_dir_config, &include_module);
3611 if (r->handler && (strcmp(r->handler, "server-parsed") == 0))
3613 if (!r->content_type || !*r->content_type) {
3614 ap_set_content_type(r, "text/html");
3616 r->handler = "default-handler";
3619 #if defined(OS2) || defined(WIN32) || defined(NETWARE)
3620 /* These OS's don't support xbithack. This is being worked on. */
3626 if (conf->xbithack == XBITHACK_OFF) {
3630 if (!(r->finfo.protection & APR_UEXECUTE)) {
3634 if (!r->content_type || strcmp(r->content_type, "text/html")) {
3640 /* We always return declined, because the default handler actually
3641 * serves the file. All we have to do is add the filter.
3643 ap_add_output_filter("INCLUDES", NULL, r, r->connection);
3649 * +-------------------------------------------------------+
3651 * | Configuration Handling
3653 * +-------------------------------------------------------+
3656 static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
3658 include_dir_config *result = apr_palloc(p, sizeof(include_dir_config));
3660 result->default_error_msg = DEFAULT_ERROR_MSG;
3661 result->default_time_fmt = DEFAULT_TIME_FORMAT;
3662 result->undefined_echo = DEFAULT_UNDEFINED_ECHO;
3663 result->xbithack = DEFAULT_XBITHACK;
3668 static void *create_includes_server_config(apr_pool_t *p, server_rec *server)
3670 include_server_config *result;
3672 result = apr_palloc(p, sizeof(include_server_config));
3673 result->default_end_tag = DEFAULT_END_SEQUENCE;
3674 result->default_start_tag = DEFAULT_START_SEQUENCE;
3679 static const char *set_xbithack(cmd_parms *cmd, void *mconfig, const char *arg)
3681 include_dir_config *conf = mconfig;
3683 if (!strcasecmp(arg, "off")) {
3684 conf->xbithack = XBITHACK_OFF;
3686 else if (!strcasecmp(arg, "on")) {
3687 conf->xbithack = XBITHACK_ON;
3689 else if (!strcasecmp(arg, "full")) {
3690 conf->xbithack = XBITHACK_FULL;
3693 return "XBitHack must be set to Off, On, or Full";
3699 static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig,
3702 include_server_config *conf;
3703 const char *p = tag;
3705 /* be consistent. (See below in set_default_end_tag) */
3707 if (apr_isspace(*p)) {
3708 return "SSIStartTag may not contain any whitespaces";
3713 conf= ap_get_module_config(cmd->server->module_config , &include_module);
3714 conf->default_start_tag = tag;
3719 static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig,
3722 include_server_config *conf;
3723 const char *p = tag;
3725 /* sanity check. The parser may fail otherwise */
3727 if (apr_isspace(*p)) {
3728 return "SSIEndTag may not contain any whitespaces";
3733 conf= ap_get_module_config(cmd->server->module_config , &include_module);
3734 conf->default_end_tag = tag;
3739 static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig,
3742 include_dir_config *conf = mconfig;
3743 conf->undefined_echo = msg;
3748 static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig,
3751 include_dir_config *conf = mconfig;
3752 conf->default_error_msg = msg;
3757 static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig,
3760 include_dir_config *conf = mconfig;
3761 conf->default_time_fmt = fmt;
3768 * +-------------------------------------------------------+
3770 * | Module Initialization and Configuration
3772 * +-------------------------------------------------------+
3775 static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
3776 apr_pool_t *ptemp, server_rec *s)
3778 include_handlers = apr_hash_make(p);
3780 ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
3782 if(ssi_pfn_register) {
3783 ssi_pfn_register("if", handle_if);
3784 ssi_pfn_register("set", handle_set);
3785 ssi_pfn_register("else", handle_else);
3786 ssi_pfn_register("elif", handle_elif);
3787 ssi_pfn_register("echo", handle_echo);
3788 ssi_pfn_register("endif", handle_endif);
3789 ssi_pfn_register("fsize", handle_fsize);
3790 ssi_pfn_register("config", handle_config);
3791 ssi_pfn_register("include", handle_include);
3792 ssi_pfn_register("flastmod", handle_flastmod);
3793 ssi_pfn_register("printenv", handle_printenv);
3799 static const command_rec includes_cmds[] =
3801 AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS,
3802 "Off, On, or Full"),
3803 AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL,
3805 AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
3806 "a strftime(3) formatted string"),
3807 AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF,
3808 "SSI Start String Tag"),
3809 AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF,
3810 "SSI End String Tag"),
3811 AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, OR_ALL,
3812 "String to be displayed if an echoed variable is undefined"),
3816 static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
3818 apr_hash_set(include_handlers, tag, strlen(tag), (const void *)func);
3821 static void register_hooks(apr_pool_t *p)
3823 APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
3824 APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
3825 APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
3826 ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
3827 ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST);
3828 ap_register_output_filter("INCLUDES", includes_filter, includes_setup,
3832 module AP_MODULE_DECLARE_DATA include_module =
3834 STANDARD20_MODULE_STUFF,
3835 create_includes_dir_config, /* dir config creater */
3836 NULL, /* dir merger --- default is to override */
3837 create_includes_server_config,/* server config */
3838 NULL, /* merge server config */
3839 includes_cmds, /* command apr_table_t */
3840 register_hooks /* register hooks */