/[Apache-SVN]/httpd/httpd/trunk/modules/filters/mod_include.c
ViewVC logotype

Contents of /httpd/httpd/trunk/modules/filters/mod_include.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 832886 - (show annotations)
Wed Nov 4 22:26:23 2009 UTC (3 weeks ago) by jorton
File MIME type: text/plain
File size: 104241 byte(s)
* modules/filters/mod_include.c (handle_printenv): Fix handling of
  lazy variables, courtesy of LLVM scan-build.
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "apr.h"
18 #include "apr_strings.h"
19 #include "apr_thread_proc.h"
20 #include "apr_hash.h"
21 #include "apr_user.h"
22 #include "apr_lib.h"
23 #include "apr_optional.h"
24
25 #define APR_WANT_STRFUNC
26 #define APR_WANT_MEMFUNC
27 #include "apr_want.h"
28
29 #include "ap_config.h"
30 #include "util_filter.h"
31 #include "httpd.h"
32 #include "http_config.h"
33 #include "http_core.h"
34 #include "http_request.h"
35 #include "http_core.h"
36 #include "http_protocol.h"
37 #include "http_log.h"
38 #include "http_main.h"
39 #include "util_script.h"
40 #include "http_core.h"
41 #include "mod_include.h"
42 #include "ap_expr.h"
43
44 /* helper for Latin1 <-> entity encoding */
45 #if APR_CHARSET_EBCDIC
46 #include "util_ebcdic.h"
47 #define RAW_ASCII_CHAR(ch) apr_xlate_conv_byte(ap_hdrs_from_ascii, \
48 (unsigned char)ch)
49 #else /* APR_CHARSET_EBCDIC */
50 #define RAW_ASCII_CHAR(ch) (ch)
51 #endif /* !APR_CHARSET_EBCDIC */
52
53
54 /*
55 * +-------------------------------------------------------+
56 * | |
57 * | Types and Structures
58 * | |
59 * +-------------------------------------------------------+
60 */
61
62 /* sll used for string expansion */
63 typedef struct result_item {
64 struct result_item *next;
65 apr_size_t len;
66 const char *string;
67 } result_item_t;
68
69 typedef enum {
70 XBITHACK_OFF,
71 XBITHACK_ON,
72 XBITHACK_FULL
73 } xbithack_t;
74
75 typedef struct {
76 const char *default_error_msg;
77 const char *default_time_fmt;
78 const char *undefined_echo;
79 xbithack_t xbithack;
80 const int accessenable;
81 } include_dir_config;
82
83 typedef struct {
84 const char *default_start_tag;
85 const char *default_end_tag;
86 } include_server_config;
87
88 /* main parser states */
89 typedef enum {
90 PARSE_PRE_HEAD,
91 PARSE_HEAD,
92 PARSE_DIRECTIVE,
93 PARSE_DIRECTIVE_POSTNAME,
94 PARSE_DIRECTIVE_TAIL,
95 PARSE_DIRECTIVE_POSTTAIL,
96 PARSE_PRE_ARG,
97 PARSE_ARG,
98 PARSE_ARG_NAME,
99 PARSE_ARG_POSTNAME,
100 PARSE_ARG_EQ,
101 PARSE_ARG_PREVAL,
102 PARSE_ARG_VAL,
103 PARSE_ARG_VAL_ESC,
104 PARSE_ARG_POSTVAL,
105 PARSE_TAIL,
106 PARSE_TAIL_SEQ,
107 PARSE_EXECUTE
108 } parse_state_t;
109
110 typedef struct arg_item {
111 struct arg_item *next;
112 char *name;
113 apr_size_t name_len;
114 char *value;
115 apr_size_t value_len;
116 } arg_item_t;
117
118 typedef struct {
119 unsigned int T[256];
120 unsigned int x;
121 apr_size_t pattern_len;
122 } bndm_t;
123
124 struct ssi_internal_ctx {
125 parse_state_t state;
126 int seen_eos;
127 int error;
128 char quote; /* quote character value (or \0) */
129 apr_size_t parse_pos; /* parse position of partial matches */
130 apr_size_t bytes_read;
131
132 apr_bucket_brigade *tmp_bb;
133
134 request_rec *r;
135 const char *start_seq;
136 bndm_t *start_seq_pat;
137 const char *end_seq;
138 apr_size_t end_seq_len;
139 char *directive; /* name of the current directive */
140 apr_size_t directive_len; /* length of the current directive name */
141
142 arg_item_t *current_arg; /* currently parsed argument */
143 arg_item_t *argv; /* all arguments */
144
145 backref_t *re; /* NULL if there wasn't a regex yet */
146
147 const char *undefined_echo;
148 apr_size_t undefined_echo_len;
149
150 opt_func_t access_func; /* is using the access tests allowed? */
151
152 /* breadcrumb to track whether child request should have parent's env */
153 request_rec *kludge_child;
154 #ifdef DEBUG_INCLUDE
155 struct {
156 ap_filter_t *f;
157 apr_bucket_brigade *bb;
158 } debug;
159 #endif
160 };
161
162
163 /*
164 * +-------------------------------------------------------+
165 * | |
166 * | Debugging Utilities
167 * | |
168 * +-------------------------------------------------------+
169 */
170
171 #ifdef DEBUG_INCLUDE
172
173 #define TYPE_TOKEN(token, ttype) do { \
174 (token)->type = ttype; \
175 (token)->s = #ttype; \
176 } while(0)
177
178 #define CREATE_NODE(ctx, name) do { \
179 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
180 (name)->parent = (name)->left = (name)->right = NULL; \
181 (name)->done = 0; \
182 (name)->dump_done = 0; \
183 } while(0)
184
185 static void debug_printf(include_ctx_t *ctx, const char *fmt, ...)
186 {
187 va_list ap;
188 char *debug__str;
189
190 va_start(ap, fmt);
191 debug__str = apr_pvsprintf(ctx->pool, fmt, ap);
192 va_end(ap);
193
194 APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create(
195 debug__str, strlen(debug__str), ctx->pool,
196 ctx->intern->debug.f->c->bucket_alloc));
197 }
198
199 #define DUMP__CHILD(ctx, is, node, child) if (1) { \
200 parse_node_t *d__c = node->child; \
201 if (d__c) { \
202 if (!d__c->dump_done) { \
203 if (d__c->parent != node) { \
204 debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \
205 if (!d__c->parent) { \
206 debug_printf(ctx, "Parent of " #child " child node is " \
207 "NULL.\n"); \
208 } \
209 else { \
210 debug_printf(ctx, "Parent of " #child " child node " \
211 "points to another node (of type %s)!\n", \
212 d__c->parent->token.s); \
213 } \
214 return; \
215 } \
216 node = d__c; \
217 continue; \
218 } \
219 } \
220 else { \
221 debug_printf(ctx, "%s(missing)\n", is); \
222 } \
223 }
224
225 static void debug_dump_tree(include_ctx_t *ctx, parse_node_t *root)
226 {
227 parse_node_t *current;
228 char *is;
229
230 if (!root) {
231 debug_printf(ctx, " -- Parse Tree empty --\n\n");
232 return;
233 }
234
235 debug_printf(ctx, " ----- Parse Tree -----\n");
236 current = root;
237 is = " ";
238
239 while (current) {
240 switch (current->token.type) {
241 case TOKEN_STRING:
242 case TOKEN_RE:
243 debug_printf(ctx, "%s%s (%s)\n", is, current->token.s,
244 current->token.value);
245 current->dump_done = 1;
246 current = current->parent;
247 continue;
248
249 case TOKEN_NOT:
250 case TOKEN_GROUP:
251 case TOKEN_RBRACE:
252 case TOKEN_LBRACE:
253 if (!current->dump_done) {
254 debug_printf(ctx, "%s%s\n", is, current->token.s);
255 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
256 current->dump_done = 1;
257 }
258
259 DUMP__CHILD(ctx, is, current, right)
260
261 if (!current->right || current->right->dump_done) {
262 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
263 if (current->right) current->right->dump_done = 0;
264 current = current->parent;
265 }
266 continue;
267
268 default:
269 if (!current->dump_done) {
270 debug_printf(ctx, "%s%s\n", is, current->token.s);
271 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
272 current->dump_done = 1;
273 }
274
275 DUMP__CHILD(ctx, is, current, left)
276 DUMP__CHILD(ctx, is, current, right)
277
278 if ((!current->left || current->left->dump_done) &&
279 (!current->right || current->right->dump_done)) {
280
281 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
282 if (current->left) current->left->dump_done = 0;
283 if (current->right) current->right->dump_done = 0;
284 current = current->parent;
285 }
286 continue;
287 }
288 }
289
290 /* it is possible to call this function within the parser loop, to see
291 * how the tree is built. That way, we must cleanup after us to dump
292 * always the whole tree
293 */
294 root->dump_done = 0;
295 if (root->left) root->left->dump_done = 0;
296 if (root->right) root->right->dump_done = 0;
297
298 debug_printf(ctx, " --- End Parse Tree ---\n\n");
299
300 return;
301 }
302
303 #define DEBUG_INIT(ctx, filter, brigade) do { \
304 (ctx)->intern->debug.f = filter; \
305 (ctx)->intern->debug.bb = brigade; \
306 } while(0)
307
308 #define DEBUG_PRINTF(arg) debug_printf arg
309
310 #define DEBUG_DUMP_TOKEN(ctx, token) do { \
311 token_t *d__t = (token); \
312 \
313 if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \
314 DEBUG_PRINTF(((ctx), " Found: %s (%s)\n", d__t->s, d__t->value)); \
315 } \
316 else { \
317 DEBUG_PRINTF((ctx, " Found: %s\n", d__t->s)); \
318 } \
319 } while(0)
320
321 #define DEBUG_DUMP_EVAL(ctx, node) do { \
322 char c = '"'; \
323 switch ((node)->token.type) { \
324 case TOKEN_STRING: \
325 debug_printf((ctx), " Evaluate: %s (%s) -> %c\n", (node)->token.s,\
326 (node)->token.value, ((node)->value) ? '1':'0'); \
327 break; \
328 case TOKEN_AND: \
329 case TOKEN_OR: \
330 debug_printf((ctx), " Evaluate: %s (Left: %s; Right: %s) -> %c\n",\
331 (node)->token.s, \
332 (((node)->left->done) ? ((node)->left->value ?"1":"0") \
333 : "short circuited"), \
334 (((node)->right->done) ? ((node)->right->value?"1":"0") \
335 : "short circuited"), \
336 (node)->value ? '1' : '0'); \
337 break; \
338 case TOKEN_EQ: \
339 case TOKEN_NE: \
340 case TOKEN_GT: \
341 case TOKEN_GE: \
342 case TOKEN_LT: \
343 case TOKEN_LE: \
344 if ((node)->right->token.type == TOKEN_RE) c = '/'; \
345 debug_printf((ctx), " Compare: %s (\"%s\" with %c%s%c) -> %c\n", \
346 (node)->token.s, \
347 (node)->left->token.value, \
348 c, (node)->right->token.value, c, \
349 (node)->value ? '1' : '0'); \
350 break; \
351 default: \
352 debug_printf((ctx), " Evaluate: %s -> %c\n", (node)->token.s, \
353 (node)->value ? '1' : '0'); \
354 break; \
355 } \
356 } while(0)
357
358 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched) do { \
359 if (unmatched) { \
360 DEBUG_PRINTF(((ctx), " Unmatched %c\n", (char)(unmatched))); \
361 } \
362 } while(0)
363
364 #define DEBUG_DUMP_COND(ctx, text) \
365 DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \
366 ((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0'))
367
368 #define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root)
369
370 #else /* DEBUG_INCLUDE */
371
372 #define TYPE_TOKEN(token, ttype) (token)->type = ttype
373
374 #define CREATE_NODE(ctx, name) do { \
375 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
376 (name)->parent = (name)->left = (name)->right = NULL; \
377 (name)->done = 0; \
378 } while(0)
379
380 #define DEBUG_INIT(ctx, f, bb)
381 #define DEBUG_PRINTF(arg)
382 #define DEBUG_DUMP_TOKEN(ctx, token)
383 #define DEBUG_DUMP_EVAL(ctx, node)
384 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched)
385 #define DEBUG_DUMP_COND(ctx, text)
386 #define DEBUG_DUMP_TREE(ctx, root)
387
388 #endif /* !DEBUG_INCLUDE */
389
390
391 /*
392 * +-------------------------------------------------------+
393 * | |
394 * | Static Module Data
395 * | |
396 * +-------------------------------------------------------+
397 */
398
399 /* global module structure */
400 module AP_MODULE_DECLARE_DATA include_module;
401
402 /* function handlers for include directives */
403 static apr_hash_t *include_handlers;
404
405 /* forward declaration of handler registry */
406 static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
407
408 /* Sentinel value to store in subprocess_env for items that
409 * shouldn't be evaluated until/unless they're actually used
410 */
411 static const char lazy_eval_sentinel;
412 #define LAZY_VALUE (&lazy_eval_sentinel)
413
414 /* default values */
415 #define DEFAULT_START_SEQUENCE "<!--#"
416 #define DEFAULT_END_SEQUENCE "-->"
417 #define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
418 #define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
419 #define DEFAULT_UNDEFINED_ECHO "(none)"
420
421 #ifdef XBITHACK
422 #define DEFAULT_XBITHACK XBITHACK_FULL
423 #else
424 #define DEFAULT_XBITHACK XBITHACK_OFF
425 #endif
426
427
428 /*
429 * +-------------------------------------------------------+
430 * | |
431 * | Environment/Expansion Functions
432 * | |
433 * +-------------------------------------------------------+
434 */
435
436 /*
437 * decodes a string containing html entities or numeric character references.
438 * 's' is overwritten with the decoded string.
439 * If 's' is syntatically incorrect, then the followed fixups will be made:
440 * unknown entities will be left undecoded;
441 * references to unused numeric characters will be deleted.
442 * In particular, &#00; will not be decoded, but will be deleted.
443 */
444
445 /* maximum length of any ISO-LATIN-1 HTML entity name. */
446 #define MAXENTLEN (6)
447
448 /* The following is a shrinking transformation, therefore safe. */
449
450 static void decodehtml(char *s)
451 {
452 int val, i, j;
453 char *p;
454 const char *ents;
455 static const char * const entlist[MAXENTLEN + 1] =
456 {
457 NULL, /* 0 */
458 NULL, /* 1 */
459 "lt\074gt\076", /* 2 */
460 "amp\046ETH\320eth\360", /* 3 */
461 "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml"
462 "\353iuml\357ouml\366uuml\374yuml\377", /* 4 */
463
464 "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc"
465 "\333THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352"
466 "icirc\356ocirc\364ucirc\373thorn\376", /* 5 */
467
468 "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311"
469 "Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde"
470 "\325Oslash\330Ugrave\331Uacute\332Yacute\335agrave\340"
471 "aacute\341atilde\343ccedil\347egrave\350eacute\351igrave"
472 "\354iacute\355ntilde\361ograve\362oacute\363otilde\365"
473 "oslash\370ugrave\371uacute\372yacute\375" /* 6 */
474 };
475
476 /* Do a fast scan through the string until we find anything
477 * that needs more complicated handling
478 */
479 for (; *s != '&'; s++) {
480 if (*s == '\0') {
481 return;
482 }
483 }
484
485 for (p = s; *s != '\0'; s++, p++) {
486 if (*s != '&') {
487 *p = *s;
488 continue;
489 }
490 /* find end of entity */
491 for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
492 continue;
493 }
494
495 if (s[i] == '\0') { /* treat as normal data */
496 *p = *s;
497 continue;
498 }
499
500 /* is it numeric ? */
501 if (s[1] == '#') {
502 for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
503 val = val * 10 + s[j] - '0';
504 }
505 s += i;
506 if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
507 (val >= 127 && val <= 160) || val >= 256) {
508 p--; /* no data to output */
509 }
510 else {
511 *p = RAW_ASCII_CHAR(val);
512 }
513 }
514 else {
515 j = i - 1;
516 if (j > MAXENTLEN || entlist[j] == NULL) {
517 /* wrong length */
518 *p = '&';
519 continue; /* skip it */
520 }
521 for (ents = entlist[j]; *ents != '\0'; ents += i) {
522 if (strncmp(s + 1, ents, j) == 0) {
523 break;
524 }
525 }
526
527 if (*ents == '\0') {
528 *p = '&'; /* unknown */
529 }
530 else {
531 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
532 s += i;
533 }
534 }
535 }
536
537 *p = '\0';
538 }
539
540 static void add_include_vars(request_rec *r)
541 {
542 apr_table_t *e = r->subprocess_env;
543 char *t;
544
545 apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
546 apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
547 apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
548 apr_table_setn(e, "DOCUMENT_URI", r->uri);
549 if (r->path_info && *r->path_info) {
550 apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
551 }
552 apr_table_setn(e, "USER_NAME", LAZY_VALUE);
553 if (r->filename && (t = strrchr(r->filename, '/'))) {
554 apr_table_setn(e, "DOCUMENT_NAME", ++t);
555 }
556 else {
557 apr_table_setn(e, "DOCUMENT_NAME", r->uri);
558 }
559 if (r->args) {
560 char *arg_copy = apr_pstrdup(r->pool, r->args);
561
562 ap_unescape_url(arg_copy);
563 apr_table_setn(e, "QUERY_STRING_UNESCAPED",
564 ap_escape_shell_cmd(r->pool, arg_copy));
565 }
566 }
567
568 static const char *add_include_vars_lazy(request_rec *r, const char *var, const char *timefmt)
569 {
570 char *val;
571 if (!strcasecmp(var, "DATE_LOCAL")) {
572 val = ap_ht_time(r->pool, r->request_time, timefmt, 0);
573 }
574 else if (!strcasecmp(var, "DATE_GMT")) {
575 val = ap_ht_time(r->pool, r->request_time, timefmt, 1);
576 }
577 else if (!strcasecmp(var, "LAST_MODIFIED")) {
578 val = ap_ht_time(r->pool, r->finfo.mtime, timefmt, 0);
579 }
580 else if (!strcasecmp(var, "USER_NAME")) {
581 if (apr_uid_name_get(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
582 val = "<unknown>";
583 }
584 }
585 else {
586 val = NULL;
587 }
588
589 if (val) {
590 apr_table_setn(r->subprocess_env, var, val);
591 }
592 return val;
593 }
594
595 static const char *get_include_var(const char *var, include_ctx_t *ctx)
596 {
597 const char *val;
598 request_rec *r = ctx->intern->r;
599
600 if (apr_isdigit(*var) && !var[1]) {
601 apr_size_t idx = *var - '0';
602 backref_t *re = ctx->intern->re;
603
604 /* Handle $0 .. $9 from the last regex evaluated.
605 * The choice of returning NULL strings on not-found,
606 * v.s. empty strings on an empty match is deliberate.
607 */
608 if (!re || !re->have_match) {
609 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
610 "regex capture $%" APR_SIZE_T_FMT " refers to no regex in %s",
611 idx, r->filename);
612 return NULL;
613 }
614 else if (re->nsub < idx || idx >= AP_MAX_REG_MATCH) {
615 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
616 "regex capture $%" APR_SIZE_T_FMT
617 " is out of range (last regex was: '%s') in %s",
618 idx, re->rexp, r->filename);
619 return NULL;
620 }
621 else if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) {
622 /* I don't think this can happen if have_match is true.
623 * But let's not risk a regression by dropping this
624 */
625 return NULL;
626 }
627
628 else {
629 val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so,
630 re->match[idx].rm_eo - re->match[idx].rm_so);
631 }
632 }
633 else {
634 val = apr_table_get(r->subprocess_env, var);
635
636 if (val == LAZY_VALUE) {
637 val = add_include_vars_lazy(r, var, ctx->time_str);
638 }
639 }
640
641 return val;
642 }
643
644 /*
645 * Do variable substitution on strings
646 *
647 * (Note: If out==NULL, this function allocs a buffer for the resulting
648 * string from ctx->pool. The return value is always the parsed string)
649 */
650 static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out,
651 apr_size_t length, int leave_name)
652 {
653 request_rec *r = ctx->intern->r;
654 result_item_t *result = NULL, *current = NULL;
655 apr_size_t outlen = 0, inlen, span;
656 char *ret = NULL, *eout = NULL;
657 const char *p;
658
659 if (out) {
660 /* sanity check, out && !length is not supported */
661 ap_assert(out && length);
662
663 ret = out;
664 eout = out + length - 1;
665 }
666
667 span = strcspn(in, "\\$");
668 inlen = strlen(in);
669
670 /* fast exit */
671 if (inlen == span) {
672 if (out) {
673 apr_cpystrn(out, in, length);
674 }
675 else {
676 ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen)
677 ? length - 1 : inlen);
678 }
679
680 return ret;
681 }
682
683 /* well, actually something to do */
684 p = in + span;
685
686 if (out) {
687 if (span) {
688 memcpy(out, in, (out+span <= eout) ? span : (eout-out));
689 out += span;
690 }
691 }
692 else {
693 current = result = apr_palloc(ctx->dpool, sizeof(*result));
694 current->next = NULL;
695 current->string = in;
696 current->len = span;
697 outlen = span;
698 }
699
700 /* loop for specials */
701 do {
702 if ((out && out >= eout) || (length && outlen >= length)) {
703 break;
704 }
705
706 /* prepare next entry */
707 if (!out && current->len) {
708 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
709 current = current->next;
710 current->next = NULL;
711 current->len = 0;
712 }
713
714 /*
715 * escaped character
716 */
717 if (*p == '\\') {
718 if (out) {
719 *out++ = (p[1] == '$') ? *++p : *p;
720 ++p;
721 }
722 else {
723 current->len = 1;
724 current->string = (p[1] == '$') ? ++p : p;
725 ++p;
726 ++outlen;
727 }
728 }
729
730 /*
731 * variable expansion
732 */
733 else { /* *p == '$' */
734 const char *newp = NULL, *ep, *key = NULL;
735
736 if (*++p == '{') {
737 ep = ap_strchr_c(++p, '}');
738 if (!ep) {
739 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Missing '}' on "
740 "variable \"%s\" in %s", p, r->filename);
741 break;
742 }
743
744 if (p < ep) {
745 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
746 newp = ep + 1;
747 }
748 p -= 2;
749 }
750 else {
751 ep = p;
752 while (*ep == '_' || apr_isalnum(*ep)) {
753 ++ep;
754 }
755
756 if (p < ep) {
757 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
758 newp = ep;
759 }
760 --p;
761 }
762
763 /* empty name results in a copy of '$' in the output string */
764 if (!key) {
765 if (out) {
766 *out++ = *p++;
767 }
768 else {
769 current->len = 1;
770 current->string = p++;
771 ++outlen;
772 }
773 }
774 else {
775 const char *val = get_include_var(key, ctx);
776 apr_size_t len = 0;
777
778 if (val) {
779 len = strlen(val);
780 }
781 else if (leave_name) {
782 val = p;
783 len = ep - p;
784 }
785
786 if (val && len) {
787 if (out) {
788 memcpy(out, val, (out+len <= eout) ? len : (eout-out));
789 out += len;
790 }
791 else {
792 current->len = len;
793 current->string = val;
794 outlen += len;
795 }
796 }
797
798 p = newp;
799 }
800 }
801
802 if ((out && out >= eout) || (length && outlen >= length)) {
803 break;
804 }
805
806 /* check the remainder */
807 if (*p && (span = strcspn(p, "\\$")) > 0) {
808 if (!out && current->len) {
809 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
810 current = current->next;
811 current->next = NULL;
812 }
813
814 if (out) {
815 memcpy(out, p, (out+span <= eout) ? span : (eout-out));
816 out += span;
817 }
818 else {
819 current->len = span;
820 current->string = p;
821 outlen += span;
822 }
823
824 p += span;
825 }
826 } while (p < in+inlen);
827
828 /* assemble result */
829 if (out) {
830 if (out > eout) {
831 *eout = '\0';
832 }
833 else {
834 *out = '\0';
835 }
836 }
837 else {
838 const char *ep;
839
840 if (length && outlen > length) {
841 outlen = length - 1;
842 }
843
844 ret = out = apr_palloc(ctx->pool, outlen + 1);
845 ep = ret + outlen;
846
847 do {
848 if (result->len) {
849 memcpy(out, result->string, (out+result->len <= ep)
850 ? result->len : (ep-out));
851 out += result->len;
852 }
853 result = result->next;
854 } while (result && out < ep);
855
856 ret[outlen] = '\0';
857 }
858
859 return ret;
860 }
861
862 static const char *ssi_parse_string(request_rec *r, const char *in)
863 {
864 include_ctx_t *ctx = ap_get_module_config(r->request_config,
865 &include_module);
866 return ap_ssi_parse_string(ctx, in, NULL, 0, SSI_EXPAND_DROP_NAME);
867 }
868 static int ssi_access(request_rec *r, ap_parse_node_t *current,
869 string_func_t parse_string)
870 {
871 request_rec *rr;
872 include_ctx_t *ctx = ap_get_module_config(r->request_config,
873 &include_module);
874
875 /* if this arg isn't -A, just return */
876 if (current->token.type != TOKEN_ACCESS || current->token.value[0] != 'A') {
877 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
878 "Unsupported option -%s in file %s",
879 current->token.value, r->filename);
880 return 1;
881 }
882 if (current->left || !current->right ||
883 (current->right->token.type != TOKEN_STRING &&
884 current->right->token.type != TOKEN_RE)) {
885 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
886 "Invalid expression in file %s: Token '-A' must be followed by a URI string.",
887 r->filename);
888 return 1; /* was_error */
889 }
890 current->right->token.value =
891 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
892 SSI_EXPAND_DROP_NAME);
893 rr = ap_sub_req_lookup_uri(current->right->token.value, r, NULL);
894 /* 400 and higher are considered access denied */
895 if (rr->status < HTTP_BAD_REQUEST) {
896 current->value = 1;
897 }
898 else {
899 current->value = 0;
900 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rr->status, r,
901 "mod_include: The tested "
902 "subrequest -A \"%s\" returned an error code.",
903 current->right->token.value);
904 }
905 ap_destroy_sub_req(rr);
906 return 0;
907 }
908
909 /*
910 * +-------------------------------------------------------+
911 * | |
912 * | Action Handlers
913 * | |
914 * +-------------------------------------------------------+
915 */
916
917 /*
918 * Extract the next tag name and value.
919 * If there are no more tags, set the tag name to NULL.
920 * The tag value is html decoded if dodecode is non-zero.
921 * The tag value may be NULL if there is no tag value..
922 */
923 static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
924 char **tag_val, int dodecode)
925 {
926 if (!ctx->intern->argv) {
927 *tag = NULL;
928 *tag_val = NULL;
929
930 return;
931 }
932
933 *tag_val = ctx->intern->argv->value;
934 *tag = ctx->intern->argv->name;
935
936 ctx->intern->argv = ctx->intern->argv->next;
937
938 if (dodecode && *tag_val) {
939 decodehtml(*tag_val);
940 }
941
942 return;
943 }
944
945 static int find_file(request_rec *r, const char *directive, const char *tag,
946 char *tag_val, apr_finfo_t *finfo)
947 {
948 char *to_send = tag_val;
949 request_rec *rr = NULL;
950 int ret=0;
951 char *error_fmt = NULL;
952 apr_status_t rv = APR_SUCCESS;
953
954 if (!strcmp(tag, "file")) {
955 char *newpath;
956
957 /* be safe; only files in this directory or below allowed */
958 rv = apr_filepath_merge(&newpath, NULL, tag_val,
959 APR_FILEPATH_SECUREROOTTEST |
960 APR_FILEPATH_NOTABSOLUTE, r->pool);
961
962 if (rv != APR_SUCCESS) {
963 error_fmt = "unable to access file \"%s\" "
964 "in parsed file %s";
965 }
966 else {
967 /* note: it is okay to pass NULL for the "next filter" since
968 we never attempt to "run" this sub request. */
969 rr = ap_sub_req_lookup_file(newpath, r, NULL);
970
971 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
972 to_send = rr->filename;
973 if ((rv = apr_stat(finfo, to_send,
974 APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
975 && rv != APR_INCOMPLETE) {
976 error_fmt = "unable to get information about \"%s\" "
977 "in parsed file %s";
978 }
979 }
980 else {
981 error_fmt = "unable to lookup information about \"%s\" "
982 "in parsed file %s";
983 }
984 }
985
986 if (error_fmt) {
987 ret = -1;
988 ap_log_rerror(APLOG_MARK, APLOG_ERR,
989 rv, r, error_fmt, to_send, r->filename);
990 }
991
992 if (rr) ap_destroy_sub_req(rr);
993
994 return ret;
995 }
996 else if (!strcmp(tag, "virtual")) {
997 /* note: it is okay to pass NULL for the "next filter" since
998 we never attempt to "run" this sub request. */
999 rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1000
1001 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1002 memcpy((char *) finfo, (const char *) &rr->finfo,
1003 sizeof(rr->finfo));
1004 ap_destroy_sub_req(rr);
1005 return 0;
1006 }
1007 else {
1008 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unable to get "
1009 "information about \"%s\" in parsed file %s",
1010 tag_val, r->filename);
1011 ap_destroy_sub_req(rr);
1012 return -1;
1013 }
1014 }
1015 else {
1016 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
1017 "to tag %s in %s", tag, directive, r->filename);
1018 return -1;
1019 }
1020 }
1021
1022 /*
1023 * <!--#include virtual|file="..." [virtual|file="..."] ... -->
1024 */
1025 static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
1026 apr_bucket_brigade *bb)
1027 {
1028 request_rec *r = f->r;
1029
1030 if (!ctx->argc) {
1031 ap_log_rerror(APLOG_MARK,
1032 (ctx->flags & SSI_FLAG_PRINTING)
1033 ? APLOG_ERR : APLOG_WARNING,
1034 0, r, "missing argument for include element in %s",
1035 r->filename);
1036 }
1037
1038 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1039 return APR_SUCCESS;
1040 }
1041
1042 if (!ctx->argc) {
1043 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1044 return APR_SUCCESS;
1045 }
1046
1047 while (1) {
1048 char *tag = NULL;
1049 char *tag_val = NULL;
1050 request_rec *rr = NULL;
1051 char *error_fmt = NULL;
1052 char *parsed_string;
1053
1054 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1055 if (!tag || !tag_val) {
1056 break;
1057 }
1058
1059 if (strcmp(tag, "virtual") && strcmp(tag, "file")) {
1060 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1061 "\"%s\" to tag include in %s", tag, r->filename);
1062 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1063 break;
1064 }
1065
1066 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1067 SSI_EXPAND_DROP_NAME);
1068 if (tag[0] == 'f') {
1069 char *newpath;
1070 apr_status_t rv;
1071
1072 /* be safe; only files in this directory or below allowed */
1073 rv = apr_filepath_merge(&newpath, NULL, parsed_string,
1074 APR_FILEPATH_SECUREROOTTEST |
1075 APR_FILEPATH_NOTABSOLUTE, ctx->dpool);
1076
1077 if (rv != APR_SUCCESS) {
1078 error_fmt = "unable to include file \"%s\" in parsed file %s";
1079 }
1080 else {
1081 rr = ap_sub_req_lookup_file(newpath, r, f->next);
1082 }
1083 }
1084 else {
1085 if (r->kept_body) {
1086 rr = ap_sub_req_method_uri(r->method, parsed_string, r, f->next);
1087 }
1088 else {
1089 rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
1090 }
1091 }
1092
1093 if (!error_fmt && rr->status != HTTP_OK) {
1094 error_fmt = "unable to include \"%s\" in parsed file %s";
1095 }
1096
1097 if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) &&
1098 rr->content_type && strncmp(rr->content_type, "text/", 5)) {
1099
1100 error_fmt = "unable to include potential exec \"%s\" in parsed "
1101 "file %s";
1102 }
1103
1104 /* See the Kludge in includes_filter for why.
1105 * Basically, it puts a bread crumb in here, then looks
1106 * for the crumb later to see if its been here.
1107 */
1108 ctx->intern->kludge_child = rr;
1109
1110 if (!error_fmt && ap_run_sub_req(rr)) {
1111 error_fmt = "unable to include \"%s\" in parsed file %s";
1112 }
1113
1114 if (error_fmt) {
1115 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, tag_val,
1116 r->filename);
1117 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1118 }
1119
1120 /* Do *not* destroy the subrequest here; it may have allocated
1121 * variables in this r->subprocess_env in the subrequest's
1122 * r->pool, so that pool must survive as long as this request.
1123 * Yes, this is a memory leak. */
1124
1125 if (error_fmt) {
1126 break;
1127 }
1128 }
1129
1130 return APR_SUCCESS;
1131 }
1132
1133 /*
1134 * <!--#echo [encoding="..."] var="..." [encoding="..."] var="..." ... -->
1135 */
1136 static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
1137 apr_bucket_brigade *bb)
1138 {
1139 enum {E_NONE, E_URL, E_ENTITY} encode;
1140 request_rec *r = f->r;
1141
1142 if (!ctx->argc) {
1143 ap_log_rerror(APLOG_MARK,
1144 (ctx->flags & SSI_FLAG_PRINTING)
1145 ? APLOG_ERR : APLOG_WARNING,
1146 0, r, "missing argument for echo element in %s",
1147 r->filename);
1148 }
1149
1150 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1151 return APR_SUCCESS;
1152 }
1153
1154 if (!ctx->argc) {
1155 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1156 return APR_SUCCESS;
1157 }
1158
1159 encode = E_ENTITY;
1160
1161 while (1) {
1162 char *tag = NULL;
1163 char *tag_val = NULL;
1164
1165 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1166 if (!tag || !tag_val) {
1167 break;
1168 }
1169
1170 if (!strcmp(tag, "var")) {
1171 const char *val;
1172 const char *echo_text = NULL;
1173 apr_size_t e_len;
1174
1175 val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL,
1176 0, SSI_EXPAND_DROP_NAME),
1177 ctx);
1178
1179 if (val) {
1180 switch(encode) {
1181 case E_NONE:
1182 echo_text = val;
1183 break;
1184 case E_URL:
1185 echo_text = ap_escape_uri(ctx->dpool, val);
1186 break;
1187 case E_ENTITY:
1188 /* PR#25202: escape anything non-ascii here */
1189 echo_text = ap_escape_html2(ctx->dpool, val, 1);
1190 break;
1191 }
1192
1193 e_len = strlen(echo_text);
1194 }
1195 else {
1196 echo_text = ctx->intern->undefined_echo;
1197 e_len = ctx->intern->undefined_echo_len;
1198 }
1199
1200 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(
1201 apr_pmemdup(ctx->pool, echo_text, e_len),
1202 e_len, ctx->pool, f->c->bucket_alloc));
1203 }
1204 else if (!strcmp(tag, "encoding")) {
1205 if (!strcasecmp(tag_val, "none")) {
1206 encode = E_NONE;
1207 }
1208 else if (!strcasecmp(tag_val, "url")) {
1209 encode = E_URL;
1210 }
1211 else if (!strcasecmp(tag_val, "entity")) {
1212 encode = E_ENTITY;
1213 }
1214 else {
1215 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1216 "\"%s\" to parameter \"encoding\" of tag echo in "
1217 "%s", tag_val, r->filename);
1218 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1219 break;
1220 }
1221 }
1222 else {
1223 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1224 "\"%s\" in tag echo of %s", tag, r->filename);
1225 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1226 break;
1227 }
1228 }
1229
1230 return APR_SUCCESS;
1231 }
1232
1233 /*
1234 * <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."]
1235 * [echomsg="..."] -->
1236 */
1237 static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
1238 apr_bucket_brigade *bb)
1239 {
1240 request_rec *r = f->r;
1241 apr_table_t *env = r->subprocess_env;
1242
1243 if (!ctx->argc) {
1244 ap_log_rerror(APLOG_MARK,
1245 (ctx->flags & SSI_FLAG_PRINTING)
1246 ? APLOG_ERR : APLOG_WARNING,
1247 0, r, "missing argument for config element in %s",
1248 r->filename);
1249 }
1250
1251 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1252 return APR_SUCCESS;
1253 }
1254
1255 if (!ctx->argc) {
1256 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1257 return APR_SUCCESS;
1258 }
1259
1260 while (1) {
1261 char *tag = NULL;
1262 char *tag_val = NULL;
1263
1264 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
1265 if (!tag || !tag_val) {
1266 break;
1267 }
1268
1269 if (!strcmp(tag, "errmsg")) {
1270 ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1271 SSI_EXPAND_DROP_NAME);
1272 }
1273 else if (!strcmp(tag, "echomsg")) {
1274 ctx->intern->undefined_echo =
1275 ap_ssi_parse_string(ctx, tag_val, NULL, 0,SSI_EXPAND_DROP_NAME);
1276 ctx->intern->undefined_echo_len=strlen(ctx->intern->undefined_echo);
1277 }
1278 else if (!strcmp(tag, "timefmt")) {
1279 apr_time_t date = r->request_time;
1280
1281 ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1282 SSI_EXPAND_DROP_NAME);
1283
1284 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
1285 ctx->time_str, 0));
1286 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
1287 ctx->time_str, 1));
1288 apr_table_setn(env, "LAST_MODIFIED",
1289 ap_ht_time(r->pool, r->finfo.mtime,
1290 ctx->time_str, 0));
1291 }
1292 else if (!strcmp(tag, "sizefmt")) {
1293 char *parsed_string;
1294
1295 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1296 SSI_EXPAND_DROP_NAME);
1297 if (!strcmp(parsed_string, "bytes")) {
1298 ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
1299 }
1300 else if (!strcmp(parsed_string, "abbrev")) {
1301 ctx->flags &= SSI_FLAG_SIZE_ABBREV;
1302 }
1303 else {
1304 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1305 "\"%s\" to parameter \"sizefmt\" of tag config "
1306 "in %s", parsed_string, r->filename);
1307 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1308 break;
1309 }
1310 }
1311 else {
1312 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1313 "\"%s\" to tag config in %s", tag, r->filename);
1314 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1315 break;
1316 }
1317 }
1318
1319 return APR_SUCCESS;
1320 }
1321
1322 /*
1323 * <!--#fsize virtual|file="..." [virtual|file="..."] ... -->
1324 */
1325 static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
1326 apr_bucket_brigade *bb)
1327 {
1328 request_rec *r = f->r;
1329
1330 if (!ctx->argc) {
1331 ap_log_rerror(APLOG_MARK,
1332 (ctx->flags & SSI_FLAG_PRINTING)
1333 ? APLOG_ERR : APLOG_WARNING,
1334 0, r, "missing argument for fsize element in %s",
1335 r->filename);
1336 }
1337
1338 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1339 return APR_SUCCESS;
1340 }
1341
1342 if (!ctx->argc) {
1343 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1344 return APR_SUCCESS;
1345 }
1346
1347 while (1) {
1348 char *tag = NULL;
1349 char *tag_val = NULL;
1350 apr_finfo_t finfo;
1351 char *parsed_string;
1352
1353 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1354 if (!tag || !tag_val) {
1355 break;
1356 }
1357
1358 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1359 SSI_EXPAND_DROP_NAME);
1360
1361 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
1362 char *buf;
1363 apr_size_t len;
1364
1365 if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
1366 buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5));
1367 len = 4; /* omit the \0 terminator */
1368 }
1369 else {
1370 apr_size_t l, x, pos;
1371 char *tmp;
1372
1373 tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size);
1374 len = l = strlen(tmp);
1375
1376 for (x = 0; x < l; ++x) {
1377 if (x && !((l - x) % 3)) {
1378 ++len;
1379 }
1380 }
1381
1382 if (len == l) {
1383 buf = apr_pstrmemdup(ctx->pool, tmp, len);
1384 }
1385 else {
1386 buf = apr_palloc(ctx->pool, len);
1387
1388 for (pos = x = 0; x < l; ++x) {
1389 if (x && !((l - x) % 3)) {
1390 buf[pos++] = ',';
1391 }
1392 buf[pos++] = tmp[x];
1393 }
1394 }
1395 }
1396
1397 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len,
1398 ctx->pool, f->c->bucket_alloc));
1399 }
1400 else {
1401 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1402 break;
1403 }
1404 }
1405
1406 return APR_SUCCESS;
1407 }
1408
1409 /*
1410 * <!--#flastmod virtual|file="..." [virtual|file="..."] ... -->
1411 */
1412 static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
1413 apr_bucket_brigade *bb)
1414 {
1415 request_rec *r = f->r;
1416
1417 if (!ctx->argc) {
1418 ap_log_rerror(APLOG_MARK,
1419 (ctx->flags & SSI_FLAG_PRINTING)
1420 ? APLOG_ERR : APLOG_WARNING,
1421 0, r, "missing argument for flastmod element in %s",
1422 r->filename);
1423 }
1424
1425 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1426 return APR_SUCCESS;
1427 }
1428
1429 if (!ctx->argc) {
1430 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1431 return APR_SUCCESS;
1432 }
1433
1434 while (1) {
1435 char *tag = NULL;
1436 char *tag_val = NULL;
1437 apr_finfo_t finfo;
1438 char *parsed_string;
1439
1440 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1441 if (!tag || !tag_val) {
1442 break;
1443 }
1444
1445 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1446 SSI_EXPAND_DROP_NAME);
1447
1448 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
1449 char *t_val;
1450 apr_size_t t_len;
1451
1452 t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0);
1453 t_len = strlen(t_val);
1454
1455 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len,
1456 ctx->pool, f->c->bucket_alloc));
1457 }
1458 else {
1459 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1460 break;
1461 }
1462 }
1463
1464 return APR_SUCCESS;
1465 }
1466
1467 /*
1468 * <!--#if expr="..." -->
1469 */
1470 static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f,
1471 apr_bucket_brigade *bb)
1472 {
1473 char *tag = NULL;
1474 char *expr = NULL;
1475 request_rec *r = f->r;
1476 int expr_ret, was_error;
1477
1478 if (ctx->argc != 1) {
1479 ap_log_rerror(APLOG_MARK,
1480 (ctx->flags & SSI_FLAG_PRINTING)
1481 ? APLOG_ERR : APLOG_WARNING,
1482 0, r, (ctx->argc)
1483 ? "too many arguments for if element in %s"
1484 : "missing expr argument for if element in %s",
1485 r->filename);
1486 }
1487
1488 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1489 ++(ctx->if_nesting_level);
1490 return APR_SUCCESS;
1491 }
1492
1493 if (ctx->argc != 1) {
1494 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1495 return APR_SUCCESS;
1496 }
1497
1498 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
1499
1500 if (strcmp(tag, "expr")) {
1501 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
1502 "to tag if in %s", tag, r->filename);
1503 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1504 return APR_SUCCESS;
1505 }
1506
1507 if (!expr) {
1508 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr value for if "
1509 "element in %s", r->filename);
1510 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1511 return APR_SUCCESS;
1512 }
1513
1514 DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr));
1515
1516 expr_ret = ap_expr_evalstring(r, expr, &was_error, &ctx->intern->re,
1517 ssi_parse_string, ctx->intern->access_func);
1518
1519 if (was_error) {
1520 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1521 return APR_SUCCESS;
1522 }
1523
1524 if (expr_ret) {
1525 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
1526 }
1527 else {
1528 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
1529 }
1530
1531 DEBUG_DUMP_COND(ctx, " if");
1532
1533 ctx->if_nesting_level = 0;
1534
1535 return APR_SUCCESS;
1536 }
1537
1538 /*
1539 * <!--#elif expr="..." -->
1540 */
1541 static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f,
1542 apr_bucket_brigade *bb)
1543 {
1544 char *tag = NULL;
1545 char *expr = NULL;
1546 request_rec *r = f->r;
1547 int expr_ret, was_error;
1548
1549 if (ctx->argc != 1) {
1550 ap_log_rerror(APLOG_MARK,
1551 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
1552 0, r, (ctx->argc)
1553 ? "too many arguments for if element in %s"
1554 : "missing expr argument for if element in %s",
1555 r->filename);
1556 }
1557
1558 if (ctx->if_nesting_level) {
1559 return APR_SUCCESS;
1560 }
1561
1562 if (ctx->argc != 1) {
1563 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1564 return APR_SUCCESS;
1565 }
1566
1567 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
1568
1569 if (strcmp(tag, "expr")) {
1570 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
1571 "to tag if in %s", tag, r->filename);
1572 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1573 return APR_SUCCESS;
1574 }
1575
1576 if (!expr) {
1577 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr in elif "
1578 "statement: %s", r->filename);
1579 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1580 return APR_SUCCESS;
1581 }
1582
1583 DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr));
1584 DEBUG_DUMP_COND(ctx, " elif");
1585
1586 if (ctx->flags & SSI_FLAG_COND_TRUE) {
1587 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
1588 return APR_SUCCESS;
1589 }
1590
1591 expr_ret = ap_expr_evalstring(r, expr, &was_error, &ctx->intern->re,
1592 ssi_parse_string, ctx->intern->access_func);
1593
1594 if (was_error) {
1595 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1596 return APR_SUCCESS;
1597 }
1598
1599 if (expr_ret) {
1600 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
1601 }
1602 else {
1603 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
1604 }
1605
1606 DEBUG_DUMP_COND(ctx, " elif");
1607
1608 return APR_SUCCESS;
1609 }
1610
1611 /*
1612 * <!--#else -->
1613 */
1614 static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f,
1615 apr_bucket_brigade *bb)
1616 {
1617 request_rec *r = f->r;
1618
1619 if (ctx->argc) {
1620 ap_log_rerror(APLOG_MARK,
1621 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
1622 0, r, "else directive does not take tags in %s",
1623 r->filename);
1624 }
1625
1626 if (ctx->if_nesting_level) {
1627 return APR_SUCCESS;
1628 }
1629
1630 if (ctx->argc) {
1631 if (ctx->flags & SSI_FLAG_PRINTING) {
1632 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1633 }
1634
1635 return APR_SUCCESS;
1636 }
1637
1638 DEBUG_DUMP_COND(ctx, " else");
1639
1640 if (ctx->flags & SSI_FLAG_COND_TRUE) {
1641 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
1642 }
1643 else {
1644 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
1645 }
1646
1647 return APR_SUCCESS;
1648 }
1649
1650 /*
1651 * <!--#endif -->
1652 */
1653 static apr_status_t handle_endif(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->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
1661 0, r, "endif directive does not take tags in %s",
1662 r->filename);
1663 }
1664
1665 if (ctx->if_nesting_level) {
1666 --(ctx->if_nesting_level);
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 DEBUG_DUMP_COND(ctx, "endif");
1676
1677 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
1678
1679 return APR_SUCCESS;
1680 }
1681
1682 /*
1683 * <!--#set var="..." value="..." ... -->
1684 */
1685 static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
1686 apr_bucket_brigade *bb)
1687 {
1688 char *var = NULL;
1689 request_rec *r = f->r;
1690 request_rec *sub = r->main;
1691 apr_pool_t *p = r->pool;
1692
1693 if (ctx->argc < 2) {
1694 ap_log_rerror(APLOG_MARK,
1695 (ctx->flags & SSI_FLAG_PRINTING)
1696 ? APLOG_ERR : APLOG_WARNING,
1697 0, r, "missing argument for set element in %s",
1698 r->filename);
1699 }
1700
1701 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1702 return APR_SUCCESS;
1703 }
1704
1705 if (ctx->argc < 2) {
1706 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1707 return APR_SUCCESS;
1708 }
1709
1710 /* we need to use the 'main' request pool to set notes as that is
1711 * a notes lifetime
1712 */
1713 while (sub) {
1714 p = sub->pool;
1715 sub = sub->main;
1716 }
1717
1718 while (1) {
1719 char *tag = NULL;
1720 char *tag_val = NULL;
1721
1722 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1723
1724 if (!tag || !tag_val) {
1725 break;
1726 }
1727
1728 if (!strcmp(tag, "var")) {
1729 var = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1730 SSI_EXPAND_DROP_NAME);
1731 }
1732 else if (!strcmp(tag, "value")) {
1733 char *parsed_string;
1734
1735 if (!var) {
1736 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "variable must "
1737 "precede value in set directive in %s",
1738 r->filename);
1739 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1740 break;
1741 }
1742
1743 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1744 SSI_EXPAND_DROP_NAME);
1745 apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
1746 apr_pstrdup(p, parsed_string));
1747 }
1748 else {
1749 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Invalid tag for set "
1750 "directive in %s", r->filename);
1751 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1752 break;
1753 }
1754 }
1755
1756 return APR_SUCCESS;
1757 }
1758
1759 /*
1760 * <!--#printenv -->
1761 */
1762 static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
1763 apr_bucket_brigade *bb)
1764 {
1765 request_rec *r = f->r;
1766 const apr_array_header_t *arr;
1767 const apr_table_entry_t *elts;
1768 int i;
1769
1770 if (ctx->argc) {
1771 ap_log_rerror(APLOG_MARK,
1772 (ctx->flags & SSI_FLAG_PRINTING)
1773 ? APLOG_ERR : APLOG_WARNING,
1774 0, r, "printenv directive does not take tags in %s",
1775 r->filename);
1776 }
1777
1778 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1779 return APR_SUCCESS;
1780 }
1781
1782 if (ctx->argc) {
1783 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1784 return APR_SUCCESS;
1785 }
1786
1787 arr = apr_table_elts(r->subprocess_env);
1788 elts = (apr_table_entry_t *)arr->elts;
1789
1790 for (i = 0; i < arr->nelts; ++i) {
1791 const char *key_text, *val_text;
1792 char *key_val, *next;
1793 apr_size_t k_len, v_len, kv_length;
1794
1795 /* get key */
1796 key_text = ap_escape_html(ctx->dpool, elts[i].key);
1797 k_len = strlen(key_text);
1798
1799 /* get value */
1800 val_text = elts[i].val;
1801 if (val_text == LAZY_VALUE) {
1802 val_text = add_include_vars_lazy(r, elts[i].key, ctx->time_str);
1803 }
1804 val_text = ap_escape_html(ctx->dpool, val_text);
1805 v_len = strlen(val_text);
1806
1807 /* assemble result */
1808 kv_length = k_len + v_len + sizeof("=\n");
1809 key_val = apr_palloc(ctx->pool, kv_length);
1810 next = key_val;
1811
1812 memcpy(next, key_text, k_len);
1813 next += k_len;
1814 *next++ = '=';
1815 memcpy(next, val_text, v_len);
1816 next += v_len;
1817 *next++ = '\n';
1818 *next = 0;
1819
1820 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(key_val, kv_length-1,
1821 ctx->pool, f->c->bucket_alloc));
1822 }
1823
1824 ctx->flush_now = 1;
1825 return APR_SUCCESS;
1826 }
1827
1828
1829 /*
1830 * +-------------------------------------------------------+
1831 * | |
1832 * | Main Includes-Filter Engine
1833 * | |
1834 * +-------------------------------------------------------+
1835 */
1836
1837 /* This is an implementation of the BNDM search algorithm.
1838 *
1839 * Fast and Flexible String Matching by Combining Bit-parallelism and
1840 * Suffix Automata (2001)
1841 * Gonzalo Navarro, Mathieu Raffinot
1842 *
1843 * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
1844 *
1845 * Initial code submitted by Sascha Schumann.
1846 */
1847
1848 /* Precompile the bndm_t data structure. */
1849 static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl)
1850 {
1851 unsigned int x;
1852 const char *ne = n + nl;
1853 bndm_t *t = apr_palloc(pool, sizeof(*t));
1854
1855 memset(t->T, 0, sizeof(unsigned int) * 256);
1856 t->pattern_len = nl;
1857
1858 for (x = 1; n < ne; x <<= 1) {
1859 t->T[(unsigned char) *n++] |= x;
1860 }
1861
1862 t->x = x - 1;
1863
1864 return t;
1865 }
1866
1867 /* Implements the BNDM search algorithm (as described above).
1868 *
1869 * h - the string to look in
1870 * hl - length of the string to look for
1871 * t - precompiled bndm structure against the pattern
1872 *
1873 * Returns the count of character that is the first match or hl if no
1874 * match is found.
1875 */
1876 static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl)
1877 {
1878 const char *skip;
1879 const char *he, *p, *pi;
1880 unsigned int *T, x, d;
1881 apr_size_t nl;
1882
1883 he = h + hl;
1884
1885 T = t->T;
1886 x = t->x;
1887 nl = t->pattern_len;
1888
1889 pi = h - 1; /* pi: p initial */
1890 p = pi + nl; /* compare window right to left. point to the first char */
1891
1892 while (p < he) {
1893 skip = p;
1894 d = x;
1895 do {
1896 d &= T[(unsigned char) *p--];
1897 if (!d) {
1898 break;
1899 }
1900 if ((d & 1)) {
1901 if (p != pi) {
1902 skip = p;
1903 }
1904 else {
1905 return p - h + 1;
1906 }
1907 }
1908 d >>= 1;
1909 } while (d);
1910
1911 pi = skip;
1912 p = pi + nl;
1913 }
1914
1915 return hl;
1916 }
1917
1918 /*
1919 * returns the index position of the first byte of start_seq (or the len of
1920 * the buffer as non-match)
1921 */
1922 static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data,
1923 apr_size_t len)
1924 {
1925 struct ssi_internal_ctx *intern = ctx->intern;
1926 apr_size_t slen = intern->start_seq_pat->pattern_len;
1927 apr_size_t index;
1928 const char *p, *ep;
1929
1930 if (len < slen) {
1931 p = data; /* try partial match at the end of the buffer (below) */
1932 }
1933 else {
1934 /* try fast bndm search over the buffer
1935 * (hopefully the whole start sequence can be found in this buffer)
1936 */
1937 index = bndm(intern->start_seq_pat, data, len);
1938
1939 /* wow, found it. ready. */
1940 if (index < len) {
1941 intern->state = PARSE_DIRECTIVE;
1942 return index;
1943 }
1944 else {
1945 /* ok, the pattern can't be found as whole in the buffer,
1946 * check the end for a partial match
1947 */
1948 p = data + len - slen + 1;
1949 }
1950 }
1951
1952 ep = data + len;
1953 do {
1954 while (p < ep && *p != *intern->start_seq) {
1955 ++p;
1956 }
1957
1958 index = p - data;
1959
1960 /* found a possible start_seq start */
1961 if (p < ep) {
1962 apr_size_t pos = 1;
1963
1964 ++p;
1965 while (p < ep && *p == intern->start_seq[pos]) {
1966 ++p;
1967 ++pos;
1968 }
1969
1970 /* partial match found. Store the info for the next round */
1971 if (p == ep) {
1972 intern->state = PARSE_HEAD;
1973 intern->parse_pos = pos;
1974 return index;
1975 }
1976 }
1977
1978 /* we must try all combinations; consider (e.g.) SSIStartTag "--->"
1979 * and a string data of "--.-" and the end of the buffer
1980 */
1981 p = data + index + 1;
1982 } while (p < ep);
1983
1984 /* no match */
1985 return len;
1986 }
1987
1988 /*
1989 * returns the first byte *after* the partial (or final) match.
1990 *
1991 * If we had to trick with the start_seq start, 'release' returns the
1992 * number of chars of the start_seq which appeared not to be part of a
1993 * full tag and may have to be passed down the filter chain.
1994 */
1995 static apr_size_t find_partial_start_sequence(include_ctx_t *ctx,
1996 const char *data,
1997 apr_size_t len,
1998 apr_size_t *release)
1999 {
2000 struct ssi_internal_ctx *intern = ctx->intern;
2001 apr_size_t pos, spos = 0;
2002 apr_size_t slen = intern->start_seq_pat->pattern_len;
2003 const char *p, *ep;
2004
2005 pos = intern->parse_pos;
2006 ep = data + len;
2007 *release = 0;
2008
2009 do {
2010 p = data;
2011
2012 while (p < ep && pos < slen && *p == intern->start_seq[pos]) {
2013 ++p;
2014 ++pos;
2015 }
2016
2017 /* full match */
2018 if (pos == slen) {
2019 intern->state = PARSE_DIRECTIVE;
2020 return (p - data);
2021 }
2022
2023 /* the whole buffer is a partial match */
2024 if (p == ep) {
2025 intern->parse_pos = pos;
2026 return (p - data);
2027 }
2028
2029 /* No match so far, but again:
2030 * We must try all combinations, since the start_seq is a random
2031 * user supplied string
2032 *
2033 * So: look if the first char of start_seq appears somewhere within
2034 * the current partial match. If it does, try to start a match that
2035 * begins with this offset. (This can happen, if a strange
2036 * start_seq like "---->" spans buffers)
2037 */
2038 if (spos < intern->parse_pos) {
2039 do {
2040 ++spos;
2041 ++*release;
2042 p = intern->start_seq + spos;
2043 pos = intern->parse_pos - spos;
2044
2045 while (pos && *p != *intern->start_seq) {
2046 ++p;
2047 ++spos;
2048 ++*release;
2049 --pos;
2050 }
2051
2052 /* if a matching beginning char was found, try to match the
2053 * remainder of the old buffer.
2054 */
2055 if (pos > 1) {
2056 apr_size_t t = 1;
2057
2058 ++p;
2059 while (t < pos && *p == intern->start_seq[t]) {
2060 ++p;
2061 ++t;
2062 }
2063
2064 if (t == pos) {
2065 /* yeah, another partial match found in the *old*
2066 * buffer, now test the *current* buffer for
2067 * continuing match
2068 */
2069 break;
2070 }
2071 }
2072 } while (pos > 1);
2073
2074 if (pos) {
2075 continue;
2076 }
2077 }
2078
2079 break;
2080 } while (1); /* work hard to find a match ;-) */
2081
2082 /* no match at all, release all (wrongly) matched chars so far */
2083 *release = intern->parse_pos;
2084 intern->state = PARSE_PRE_HEAD;
2085 return 0;
2086 }
2087
2088 /*
2089 * returns the position after the directive
2090 */
2091 static apr_size_t find_directive(include_ctx_t *ctx, const char *data,
2092 apr_size_t len, char ***store,
2093 apr_size_t **store_len)
2094 {
2095 struct ssi_internal_ctx *intern = ctx->intern;
2096 const char *p = data;
2097 const char *ep = data + len;
2098 apr_size_t pos;
2099
2100 switch (intern->state) {
2101 case PARSE_DIRECTIVE:
2102 while (p < ep && !apr_isspace(*p)) {
2103 /* we have to consider the case of missing space between directive
2104 * and end_seq (be somewhat lenient), e.g. <!--#printenv-->
2105 */
2106 if (*p == *intern->end_seq) {
2107 intern->state = PARSE_DIRECTIVE_TAIL;
2108 intern->parse_pos = 1;
2109 ++p;
2110 return (p - data);
2111 }
2112 ++p;
2113 }
2114
2115 if (p < ep) { /* found delimiter whitespace */
2116 intern->state = PARSE_DIRECTIVE_POSTNAME;
2117 *store = &intern->directive;
2118 *store_len = &intern->directive_len;
2119 }
2120
2121 break;
2122
2123 case PARSE_DIRECTIVE_TAIL:
2124 pos = intern->parse_pos;
2125
2126 while (p < ep && pos < intern->end_seq_len &&
2127 *p == intern->end_seq[pos]) {
2128 ++p;
2129 ++pos;
2130 }
2131
2132 /* full match, we're done */
2133 if (pos == intern->end_seq_len) {
2134 intern->state = PARSE_DIRECTIVE_POSTTAIL;
2135 *store = &intern->directive;
2136 *store_len = &intern->directive_len;
2137 break;
2138 }
2139
2140 /* partial match, the buffer is too small to match fully */
2141 if (p == ep) {
2142 intern->parse_pos = pos;
2143 break;
2144 }
2145
2146 /* no match. continue normal parsing */
2147 intern->state = PARSE_DIRECTIVE;
2148 return 0;
2149
2150 case PARSE_DIRECTIVE_POSTTAIL:
2151 intern->state = PARSE_EXECUTE;
2152 intern->directive_len -= intern->end_seq_len;
2153 /* continue immediately with the next state */
2154
2155 case PARSE_DIRECTIVE_POSTNAME:
2156 if (PARSE_DIRECTIVE_POSTNAME == intern->state) {
2157 intern->state = PARSE_PRE_ARG;
2158 }
2159 ctx->argc = 0;
2160 intern->argv = NULL;
2161
2162 if (!intern->directive_len) {
2163 intern->error = 1;
2164 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2165 "directive name in parsed document %s",
2166 intern->r->filename);
2167 }
2168 else {
2169 char *sp = intern->directive;
2170 char *sep = intern->directive + intern->directive_len;
2171
2172 /* normalize directive name */
2173 for (; sp < sep; ++sp) {
2174 *sp = apr_tolower(*sp);
2175 }
2176 }
2177
2178 return 0;
2179
2180 default:
2181 /* get a rid of a gcc warning about unhandled enumerations */
2182 break;
2183 }
2184
2185 return (p - data);
2186 }
2187
2188 /*
2189 * find out whether the next token is (a possible) end_seq or an argument
2190 */
2191 static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data,
2192 apr_size_t len)
2193 {
2194 struct ssi_internal_ctx *intern = ctx->intern;
2195 const char *p = data;
2196 const char *ep = data + len;
2197
2198 /* skip leading WS */
2199 while (p < ep && apr_isspace(*p)) {
2200 ++p;
2201 }
2202
2203 /* buffer doesn't consist of whitespaces only */
2204 if (p < ep) {
2205 intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG;
2206 }
2207
2208 return (p - data);
2209 }
2210
2211 /*
2212 * test the stream for end_seq. If it doesn't match at all, it must be an
2213 * argument
2214 */
2215 static apr_size_t find_tail(include_ctx_t *ctx, const char *data,
2216 apr_size_t len)
2217 {
2218 struct ssi_internal_ctx *intern = ctx->intern;
2219 const char *p = data;
2220 const char *ep = data + len;
2221 apr_size_t pos = intern->parse_pos;
2222
2223 if (PARSE_TAIL == intern->state) {
2224 intern->state = PARSE_TAIL_SEQ;
2225 pos = intern->parse_pos = 0;
2226 }
2227
2228 while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) {
2229 ++p;
2230 ++pos;
2231 }
2232
2233 /* bingo, full match */
2234 if (pos == intern->end_seq_len) {
2235 intern->state = PARSE_EXECUTE;
2236 return (p - data);
2237 }
2238
2239 /* partial match, the buffer is too small to match fully */
2240 if (p == ep) {
2241 intern->parse_pos = pos;
2242 return (p - data);
2243 }
2244
2245 /* no match. It must be an argument string then
2246 * The caller should cleanup and rewind to the reparse point
2247 */
2248 intern->state = PARSE_ARG;
2249 return 0;
2250 }
2251
2252 /*
2253 * extract name=value from the buffer
2254 * A pcre-pattern could look (similar to):
2255 * name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
2256 */
2257 static apr_size_t find_argument(include_ctx_t *ctx, const char *data,
2258 apr_size_t len, char ***store,
2259 apr_size_t **store_len)
2260 {
2261 struct ssi_internal_ctx *intern = ctx->intern;
2262 const char *p = data;
2263 const char *ep = data + len;
2264
2265 switch (intern->state) {
2266 case PARSE_ARG:
2267 /*
2268 * create argument structure and append it to the current list
2269 */
2270 intern->current_arg = apr_palloc(ctx->dpool,
2271 sizeof(*intern->current_arg));
2272 intern->current_arg->next = NULL;
2273
2274 ++(ctx->argc);
2275 if (!intern->argv) {
2276 intern->argv = intern->current_arg;
2277 }
2278 else {
2279 arg_item_t *newarg = intern->argv;
2280
2281 while (newarg->next) {
2282 newarg = newarg->next;
2283 }
2284 newarg->next = intern->current_arg;
2285 }
2286
2287 /* check whether it's a valid one. If it begins with a quote, we
2288 * can safely assume, someone forgot the name of the argument
2289 */
2290 switch (*p) {
2291 case '"': case '\'': case '`':
2292 *store = NULL;
2293
2294 intern->state = PARSE_ARG_VAL;
2295 intern->quote = *p++;
2296 intern->current_arg->name = NULL;
2297 intern->current_arg->name_len = 0;
2298 intern->error = 1;
2299
2300 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2301 "argument name for value to tag %s in %s",
2302 apr_pstrmemdup(intern->r->pool, intern->directive,
2303 intern->directive_len),
2304 intern->r->filename);
2305
2306 return (p - data);
2307
2308 default:
2309 intern->state = PARSE_ARG_NAME;
2310 }
2311 /* continue immediately with next state */
2312
2313 case PARSE_ARG_NAME:
2314 while (p < ep && !apr_isspace(*p) && *p != '=') {
2315 ++p;
2316 }
2317
2318 if (p < ep) {
2319 intern->state = PARSE_ARG_POSTNAME;
2320 *store = &intern->current_arg->name;
2321 *store_len = &intern->current_arg->name_len;
2322 return (p - data);
2323 }
2324 break;
2325
2326 case PARSE_ARG_POSTNAME:
2327 intern->current_arg->name = apr_pstrmemdup(ctx->dpool,
2328 intern->current_arg->name,
2329 intern->current_arg->name_len);
2330 if (!intern->current_arg->name_len) {
2331 intern->error = 1;
2332 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2333 "argument name for value to tag %s in %s",
2334 apr_pstrmemdup(intern->r->pool, intern->directive,
2335 intern->directive_len),
2336 intern->r->filename);
2337 }
2338 else {
2339 char *sp = intern->current_arg->name;
2340
2341 /* normalize the name */
2342 while (*sp) {
2343 *sp = apr_tolower(*sp);
2344 ++sp;
2345 }
2346 }
2347
2348 intern->state = PARSE_ARG_EQ;
2349 /* continue with next state immediately */
2350
2351 case PARSE_ARG_EQ:
2352 *store = NULL;
2353
2354 while (p < ep && apr_isspace(*p)) {
2355 ++p;
2356 }
2357
2358 if (p < ep) {
2359 if (*p == '=') {
2360 intern->state = PARSE_ARG_PREVAL;
2361 ++p;
2362 }
2363 else { /* no value */
2364 intern->current_arg->value = NULL;
2365 intern->state = PARSE_PRE_ARG;
2366 }
2367
2368 return (p - data);
2369 }
2370 break;
2371
2372 case PARSE_ARG_PREVAL:
2373 *store = NULL;
2374
2375 while (p < ep && apr_isspace(*p)) {
2376 ++p;
2377 }
2378
2379 /* buffer doesn't consist of whitespaces only */
2380 if (p < ep) {
2381 intern->state = PARSE_ARG_VAL;
2382 switch (*p) {
2383 case '"': case '\'': case '`':
2384 intern->quote = *p++;
2385 break;
2386 default:
2387 intern->quote = '\0';
2388 break;
2389 }
2390
2391 return (p - data);
2392 }
2393 break;
2394
2395 case PARSE_ARG_VAL_ESC:
2396 if (*p == intern->quote) {
2397 ++p;
2398 }
2399 intern->state = PARSE_ARG_VAL;
2400 /* continue with next state immediately */
2401
2402 case PARSE_ARG_VAL:
2403 for (; p < ep; ++p) {
2404 if (intern->quote && *p == '\\') {
2405 ++p;
2406 if (p == ep) {
2407 intern->state = PARSE_ARG_VAL_ESC;
2408 break;
2409 }
2410
2411 if (*p != intern->quote) {
2412 --p;
2413 }
2414 }
2415 else if (intern->quote && *p == intern->quote) {
2416 ++p;
2417 *store = &intern->current_arg->value;
2418 *store_len = &intern->current_arg->value_len;
2419 intern->state = PARSE_ARG_POSTVAL;
2420 break;
2421 }
2422 else if (!intern->quote && apr_isspace(*p)) {
2423 ++p;
2424 *store = &intern->current_arg->value;
2425 *store_len = &intern->current_arg->value_len;
2426 intern->state = PARSE_ARG_POSTVAL;
2427 break;
2428 }
2429 }
2430
2431 return (p - data);
2432
2433 case PARSE_ARG_POSTVAL:
2434 /*
2435 * The value is still the raw input string. Finally clean it up.
2436 */
2437 --(intern->current_arg->value_len);
2438
2439 /* strip quote escaping \ from the string */
2440 if (intern->quote) {
2441 apr_size_t shift = 0;
2442 char *sp;
2443
2444 sp = intern->current_arg->value;
2445 ep = intern->current_arg->value + intern->current_arg->value_len;
2446 while (sp < ep && *sp != '\\') {
2447 ++sp;
2448 }
2449 for (; sp < ep; ++sp) {
2450 if (*sp == '\\' && sp[1] == intern->quote) {
2451 ++sp;
2452 ++shift;
2453 }
2454 if (shift) {
2455 *(sp-shift) = *sp;
2456 }
2457 }
2458
2459 intern->current_arg->value_len -= shift;
2460 }
2461
2462 intern->current_arg->value[intern->current_arg->value_len] = '\0';
2463 intern->state = PARSE_PRE_ARG;
2464
2465 return 0;
2466
2467 default:
2468 /* get a rid of a gcc warning about unhandled enumerations */
2469 break;
2470 }
2471
2472 return len; /* partial match of something */
2473 }
2474
2475 /*
2476 * This is the main loop over the current bucket brigade.
2477 */
2478 static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
2479 {
2480 include_ctx_t *ctx = f->ctx;
2481 struct ssi_internal_ctx *intern = ctx->intern;
2482 request_rec *r = f->r;
2483 apr_bucket *b = APR_BRIGADE_FIRST(bb);
2484 apr_bucket_brigade *pass_bb;
2485 apr_status_t rv = APR_SUCCESS;
2486 char *magic; /* magic pointer for sentinel use */
2487
2488 /* fast exit */
2489 if (APR_BRIGADE_EMPTY(bb)) {
2490 return APR_SUCCESS;
2491 }
2492
2493 /* we may crash, since already cleaned up; hand over the responsibility
2494 * to the next filter;-)
2495 */
2496 if (intern->seen_eos) {
2497 return ap_pass_brigade(f->next, bb);
2498 }
2499
2500 /* All stuff passed along has to be put into that brigade */
2501 pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
2502
2503 /* initialization for this loop */
2504 intern->bytes_read = 0;
2505 intern->error = 0;
2506 intern->r = r;
2507 ctx->flush_now = 0;
2508
2509 /* loop over the current bucket brigade */
2510 while (b != APR_BRIGADE_SENTINEL(bb)) {
2511 const char *data = NULL;
2512 apr_size_t len, index, release;
2513 apr_bucket *newb = NULL;
2514 char **store = &magic;
2515 apr_size_t *store_len = NULL;
2516
2517 /* handle meta buckets before reading any data */
2518 if (APR_BUCKET_IS_METADATA(b)) {
2519 newb = APR_BUCKET_NEXT(b);
2520
2521 APR_BUCKET_REMOVE(b);
2522
2523 if (APR_BUCKET_IS_EOS(b)) {
2524 intern->seen_eos = 1;
2525
2526 /* Hit end of stream, time for cleanup ... But wait!
2527 * Perhaps we're not ready yet. We may have to loop one or
2528 * two times again to finish our work. In that case, we
2529 * just re-insert the EOS bucket to allow for an extra loop.
2530 *
2531 * PARSE_EXECUTE means, we've hit a directive just before the
2532 * EOS, which is now waiting for execution.
2533 *
2534 * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
2535 * no argument and no space between directive and end_seq
2536 * just before the EOS. (consider <!--#printenv--> as last
2537 * or only string within the stream). This state, however,
2538 * just cleans up and turns itself to PARSE_EXECUTE, which
2539 * will be passed through within the next (and actually
2540 * last) round.
2541 */
2542 if (PARSE_EXECUTE == intern->state ||
2543 PARSE_DIRECTIVE_POSTTAIL == intern->state) {
2544 APR_BUCKET_INSERT_BEFORE(newb, b);
2545 }
2546 else {
2547 break; /* END OF STREAM */
2548 }
2549 }
2550 else {
2551 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
2552
2553 if (APR_BUCKET_IS_FLUSH(b)) {
2554 ctx->flush_now = 1;
2555 }
2556
2557 b = newb;
2558 continue;
2559 }
2560 }
2561
2562 /* enough is enough ... */
2563 if (ctx->flush_now ||
2564 intern->bytes_read > AP_MIN_BYTES_TO_WRITE) {
2565
2566 if (!APR_BRIGADE_EMPTY(pass_bb)) {
2567 rv = ap_pass_brigade(f->next, pass_bb);
2568 if (rv != APR_SUCCESS) {
2569 apr_brigade_destroy(pass_bb);
2570 return rv;
2571 }
2572 }
2573
2574 ctx->flush_now = 0;
2575 intern->bytes_read = 0;
2576 }
2577
2578 /* read the current bucket data */
2579 len = 0;
2580 if (!intern->seen_eos) {
2581 if (intern->bytes_read > 0) {
2582 rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
2583 if (APR_STATUS_IS_EAGAIN(rv)) {
2584 ctx->flush_now = 1;
2585 continue;
2586 }
2587 }
2588
2589 if (!len || rv != APR_SUCCESS) {
2590 rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
2591 }
2592
2593 if (rv != APR_SUCCESS) {
2594 apr_brigade_destroy(pass_bb);
2595 return rv;
2596 }
2597
2598 intern->bytes_read += len;
2599 }
2600
2601 /* zero length bucket, fetch next one */
2602 if (!len && !intern->seen_eos) {
2603 b = APR_BUCKET_NEXT(b);
2604 continue;
2605 }
2606
2607 /*
2608 * it's actually a data containing bucket, start/continue parsing
2609 */
2610
2611 switch (intern->state) {
2612 /* no current tag; search for start sequence */
2613 case PARSE_PRE_HEAD:
2614 index = find_start_sequence(ctx, data, len);
2615
2616 if (index < len) {
2617 apr_bucket_split(b, index);
2618 }
2619
2620 newb = APR_BUCKET_NEXT(b);
2621 if (ctx->flags & SSI_FLAG_PRINTING) {
2622 APR_BUCKET_REMOVE(b);
2623 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
2624 }
2625 else {
2626 apr_bucket_delete(b);
2627 }
2628
2629 if (index < len) {
2630 /* now delete the start_seq stuff from the remaining bucket */
2631 if (PARSE_DIRECTIVE == intern->state) { /* full match */
2632 apr_bucket_split(newb, intern->start_seq_pat->pattern_len);
2633 ctx->flush_now = 1; /* pass pre-tag stuff */
2634 }
2635
2636 b = APR_BUCKET_NEXT(newb);
2637 apr_bucket_delete(newb);
2638 }
2639 else {
2640 b = newb;
2641 }
2642
2643 break;
2644
2645 /* we're currently looking for the end of the start sequence */
2646 case PARSE_HEAD:
2647 index = find_partial_start_sequence(ctx, data, len, &release);
2648
2649 /* check if we mismatched earlier and have to release some chars */
2650 if (release && (ctx->flags & SSI_FLAG_PRINTING)) {
2651 char *to_release = apr_pmemdup(ctx->pool, intern->start_seq, release);
2652
2653 newb = apr_bucket_pool_create(to_release, release, ctx->pool,
2654 f->c->bucket_alloc);
2655 APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
2656 }
2657
2658 if (index) { /* any match */
2659 /* now delete the start_seq stuff from the remaining bucket */
2660 if (PARSE_DIRECTIVE == intern->state) { /* final match */
2661 apr_bucket_split(b, index);
2662 ctx->flush_now = 1; /* pass pre-tag stuff */
2663 }
2664 newb = APR_BUCKET_NEXT(b);
2665 apr_bucket_delete(b);
2666 b = newb;
2667 }
2668
2669 break;
2670
2671 /* we're currently grabbing the directive name */
2672 case PARSE_DIRECTIVE:
2673 case PARSE_DIRECTIVE_POSTNAME:
2674 case PARSE_DIRECTIVE_TAIL:
2675 case PARSE_DIRECTIVE_POSTTAIL:
2676 index = find_directive(ctx, data, len, &store, &store_len);
2677
2678 if (index) {
2679 apr_bucket_split(b, index);
2680 newb = APR_BUCKET_NEXT(b);
2681 }
2682
2683 if (store) {
2684 if (index) {
2685 APR_BUCKET_REMOVE(b);
2686 apr_bucket_setaside(b, r->pool);
2687 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
2688 b = newb;
2689 }
2690
2691 /* time for cleanup? */
2692 if (store != &magic) {
2693 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
2694 ctx->dpool);
2695 apr_brigade_cleanup(intern->tmp_bb);
2696 }
2697 }
2698 else if (index) {
2699 apr_bucket_delete(b);
2700 b = newb;
2701 }
2702
2703 break;
2704
2705 /* skip WS and find out what comes next (arg or end_seq) */
2706 case PARSE_PRE_ARG:
2707 index = find_arg_or_tail(ctx, data, len);
2708
2709 if (index) { /* skipped whitespaces */
2710 if (index < len) {
2711 apr_bucket_split(b, index);
2712 }
2713 newb = APR_BUCKET_NEXT(b);
2714 apr_bucket_delete(b);
2715 b = newb;
2716 }
2717
2718 break;
2719
2720 /* currently parsing name[=val] */
2721 case PARSE_ARG:
2722 case PARSE_ARG_NAME:
2723 case PARSE_ARG_POSTNAME:
2724 case PARSE_ARG_EQ:
2725 case PARSE_ARG_PREVAL:
2726 case PARSE_ARG_VAL:
2727 case PARSE_ARG_VAL_ESC:
2728 case PARSE_ARG_POSTVAL:
2729 index = find_argument(ctx, data, len, &store, &store_len);
2730
2731 if (index) {
2732 apr_bucket_split(b, index);
2733 newb = APR_BUCKET_NEXT(b);
2734 }
2735
2736 if (store) {
2737 if (index) {
2738 APR_BUCKET_REMOVE(b);
2739 apr_bucket_setaside(b, r->pool);
2740 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
2741 b = newb;
2742 }
2743
2744 /* time for cleanup? */
2745 if (store != &magic) {
2746 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
2747 ctx->dpool);
2748 apr_brigade_cleanup(intern->tmp_bb);
2749 }
2750 }
2751 else if (index) {
2752 apr_bucket_delete(b);
2753 b = newb;
2754 }
2755
2756 break;
2757
2758 /* try to match end_seq at current pos. */
2759 case PARSE_TAIL:
2760 case PARSE_TAIL_SEQ:
2761 index = find_tail(ctx, data, len);
2762
2763 switch (intern->state) {
2764 case PARSE_EXECUTE: /* full match */
2765 apr_bucket_split(b, index);
2766 newb = APR_BUCKET_NEXT(b);
2767 apr_bucket_delete(b);
2768 b = newb;
2769 break;
2770
2771 case PARSE_ARG: /* no match */
2772 /* PARSE_ARG must reparse at the beginning */
2773 APR_BRIGADE_PREPEND(bb, intern->tmp_bb);
2774 b = APR_BRIGADE_FIRST(bb);
2775 break;
2776
2777 default: /* partial match */
2778 newb = APR_BUCKET_NEXT(b);
2779 APR_BUCKET_REMOVE(b);
2780 apr_bucket_setaside(b, r->pool);
2781 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
2782 b = newb;
2783 break;
2784 }
2785
2786 break;
2787
2788 /* now execute the parsed directive, cleanup the space and
2789 * start again with PARSE_PRE_HEAD
2790 */
2791 case PARSE_EXECUTE:
2792 /* if there was an error, it was already logged; just stop here */
2793 if (intern->error) {
2794 if (ctx->flags & SSI_FLAG_PRINTING) {
2795 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
2796 intern->error = 0;
2797 }
2798 }
2799 else {
2800 include_handler_fn_t *handle_func;
2801
2802 handle_func =
2803 (include_handler_fn_t *)apr_hash_get(include_handlers, intern->directive,
2804 intern->directive_len);
2805
2806 if (handle_func) {
2807 DEBUG_INIT(ctx, f, pass_bb);
2808 rv = handle_func(ctx, f, pass_bb);
2809 if (rv != APR_SUCCESS) {
2810 apr_brigade_destroy(pass_bb);
2811 return rv;
2812 }
2813 }
2814 else {
2815 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2816 "unknown directive \"%s\" in parsed doc %s",
2817 apr_pstrmemdup(r->pool, intern->directive,
2818 intern->directive_len),
2819 r->filename);
2820 if (ctx->flags & SSI_FLAG_PRINTING) {
2821 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
2822 }
2823 }
2824 }
2825
2826 /* cleanup */
2827 apr_pool_clear(ctx->dpool);
2828 apr_brigade_cleanup(intern->tmp_bb);
2829
2830 /* Oooof. Done here, start next round */
2831 intern->state = PARSE_PRE_HEAD;
2832 break;
2833
2834 } /* switch(ctx->state) */
2835
2836 } /* while(brigade) */
2837
2838 /* End of stream. Final cleanup */
2839 if (intern->seen_eos) {
2840 if (PARSE_HEAD == intern->state) {
2841 if (ctx->flags & SSI_FLAG_PRINTING) {
2842 char *to_release = apr_pmemdup(ctx->pool, intern->start_seq,
2843 intern->parse_pos);
2844
2845 APR_BRIGADE_INSERT_TAIL(pass_bb,
2846 apr_bucket_pool_create(to_release,
2847 intern->parse_pos, ctx->pool,
2848 f->c->bucket_alloc));
2849 }
2850 }
2851 else if (PARSE_PRE_HEAD != intern->state) {
2852 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2853 "SSI directive was not properly finished at the end "
2854 "of parsed document %s", r->filename);
2855 if (ctx->flags & SSI_FLAG_PRINTING) {
2856 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
2857 }
2858 }
2859
2860 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2861 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
2862 "missing closing endif directive in parsed document"
2863 " %s", r->filename);
2864 }
2865
2866 /* cleanup our temporary memory */
2867 apr_brigade_destroy(intern->tmp_bb);
2868 apr_pool_destroy(ctx->dpool);
2869
2870 /* don't forget to finally insert the EOS bucket */
2871 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
2872 }
2873
2874 /* if something's left over, pass it along */
2875 if (!APR_BRIGADE_EMPTY(pass_bb)) {
2876 rv = ap_pass_brigade(f->next, pass_bb);
2877 }
2878 else {
2879 rv = APR_SUCCESS;
2880 apr_brigade_destroy(pass_bb);
2881 }
2882 return rv;
2883 }
2884
2885
2886 /*
2887 * +-------------------------------------------------------+
2888 * | |
2889 * | Runtime Hooks
2890 * | |
2891 * +-------------------------------------------------------+
2892 */
2893
2894 static int includes_setup(ap_filter_t *f)
2895 {
2896 include_dir_config *conf = ap_get_module_config(f->r->per_dir_config,
2897 &include_module);
2898
2899 /* When our xbithack value isn't set to full or our platform isn't
2900 * providing group-level protection bits or our group-level bits do not
2901 * have group-execite on, we will set the no_local_copy value to 1 so
2902 * that we will not send 304s.
2903 */
2904 if ((conf->xbithack != XBITHACK_FULL)
2905 || !(f->r->finfo.valid & APR_FINFO_GPROT)
2906 || !(f->r->finfo.protection & APR_GEXECUTE)) {
2907 f->r->no_local_copy = 1;
2908 }
2909
2910 /* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4.
2911 * We don't know if we are going to be including a file or executing
2912 * a program - in either case a strong ETag header will likely be invalid.
2913 */
2914 apr_table_setn(f->r->notes, "no-etag", "");
2915
2916 return OK;
2917 }
2918
2919 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
2920 {
2921 request_rec *r = f->r;
2922 include_ctx_t *ctx = f->ctx;
2923 include_dir_config *conf = ap_get_module_config(r->per_dir_config,
2924 &include_module);
2925
2926 include_server_config *sconf= ap_get_module_config(r->server->module_config,
2927 &include_module);
2928
2929 if (!(ap_allow_options(r) & OPT_INCLUDES)) {
2930 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
2931 "mod_include: Options +Includes (or IncludesNoExec) "
2932 "wasn't set, INCLUDES filter removed");
2933 ap_remove_output_filter(f);