/[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 795446 - (show annotations)
Sat Jul 18 23:21:52 2009 UTC (4 months, 1 week ago) by niq
File MIME type: text/plain
File size: 104328 byte(s)
Typo fix
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->match[idx].rm_so == re->match[idx].rm_eo) {
615 return NULL;
616 }
617 else if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) {
618 /* I don't think this can happen if have_match is true.
619 * But let's not risk a regression by dropping this
620 */
621 return NULL;
622 }
623 else if (re->nsub < idx || idx >= AP_MAX_REG_MATCH) {
624 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
625 "regex capture $%" APR_SIZE_T_FMT
626 " is out of range (last regex was: '%s') in %s",
627 idx, re->rexp, r->filename);
628 return NULL;
629 }
630
631 else {
632 val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so,
633 re->match[idx].rm_eo - re->match[idx].rm_so);
634 }
635 }
636 else {
637 val = apr_table_get(r->subprocess_env, var);
638
639 if (val == LAZY_VALUE) {
640 val = add_include_vars_lazy(r, var, ctx->time_str);
641 }
642 }
643
644 return val;
645 }
646
647 /*
648 * Do variable substitution on strings
649 *
650 * (Note: If out==NULL, this function allocs a buffer for the resulting
651 * string from ctx->pool. The return value is always the parsed string)
652 */
653 static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out,
654 apr_size_t length, int leave_name)
655 {
656 request_rec *r = ctx->intern->r;
657 result_item_t *result = NULL, *current = NULL;
658 apr_size_t outlen = 0, inlen, span;
659 char *ret = NULL, *eout = NULL;
660 const char *p;
661
662 if (out) {
663 /* sanity check, out && !length is not supported */
664 ap_assert(out && length);
665
666 ret = out;
667 eout = out + length - 1;
668 }
669
670 span = strcspn(in, "\\$");
671 inlen = strlen(in);
672
673 /* fast exit */
674 if (inlen == span) {
675 if (out) {
676 apr_cpystrn(out, in, length);
677 }
678 else {
679 ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen)
680 ? length - 1 : inlen);
681 }
682
683 return ret;
684 }
685
686 /* well, actually something to do */
687 p = in + span;
688
689 if (out) {
690 if (span) {
691 memcpy(out, in, (out+span <= eout) ? span : (eout-out));
692 out += span;
693 }
694 }
695 else {
696 current = result = apr_palloc(ctx->dpool, sizeof(*result));
697 current->next = NULL;
698 current->string = in;
699 current->len = span;
700 outlen = span;
701 }
702
703 /* loop for specials */
704 do {
705 if ((out && out >= eout) || (length && outlen >= length)) {
706 break;
707 }
708
709 /* prepare next entry */
710 if (!out && current->len) {
711 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
712 current = current->next;
713 current->next = NULL;
714 current->len = 0;
715 }
716
717 /*
718 * escaped character
719 */
720 if (*p == '\\') {
721 if (out) {
722 *out++ = (p[1] == '$') ? *++p : *p;
723 ++p;
724 }
725 else {
726 current->len = 1;
727 current->string = (p[1] == '$') ? ++p : p;
728 ++p;
729 ++outlen;
730 }
731 }
732
733 /*
734 * variable expansion
735 */
736 else { /* *p == '$' */
737 const char *newp = NULL, *ep, *key = NULL;
738
739 if (*++p == '{') {
740 ep = ap_strchr_c(++p, '}');
741 if (!ep) {
742 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Missing '}' on "
743 "variable \"%s\" in %s", p, r->filename);
744 break;
745 }
746
747 if (p < ep) {
748 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
749 newp = ep + 1;
750 }
751 p -= 2;
752 }
753 else {
754 ep = p;
755 while (*ep == '_' || apr_isalnum(*ep)) {
756 ++ep;
757 }
758
759 if (p < ep) {
760 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
761 newp = ep;
762 }
763 --p;
764 }
765
766 /* empty name results in a copy of '$' in the output string */
767 if (!key) {
768 if (out) {
769 *out++ = *p++;
770 }
771 else {
772 current->len = 1;
773 current->string = p++;
774 ++outlen;
775 }
776 }
777 else {
778 const char *val = get_include_var(key, ctx);
779 apr_size_t len = 0;
780
781 if (val) {
782 len = strlen(val);
783 }
784 else if (leave_name) {
785 val = p;
786 len = ep - p;
787 }
788
789 if (val && len) {
790 if (out) {
791 memcpy(out, val, (out+len <= eout) ? len : (eout-out));
792 out += len;
793 }
794 else {
795 current->len = len;
796 current->string = val;
797 outlen += len;
798 }
799 }
800
801 p = newp;
802 }
803 }
804
805 if ((out && out >= eout) || (length && outlen >= length)) {
806 break;
807 }
808
809 /* check the remainder */
810 if (*p && (span = strcspn(p, "\\$")) > 0) {
811 if (!out && current->len) {
812 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
813 current = current->next;
814 current->next = NULL;
815 }
816
817 if (out) {
818 memcpy(out, p, (out+span <= eout) ? span : (eout-out));
819 out += span;
820 }
821 else {
822 current->len = span;
823 current->string = p;
824 outlen += span;
825 }
826
827 p += span;
828 }
829 } while (p < in+inlen);
830
831 /* assemble result */
832 if (out) {
833 if (out > eout) {
834 *eout = '\0';
835 }
836 else {
837 *out = '\0';
838 }
839 }
840 else {
841 const char *ep;
842
843 if (length && outlen > length) {
844 outlen = length - 1;
845 }
846
847 ret = out = apr_palloc(ctx->pool, outlen + 1);
848 ep = ret + outlen;
849
850 do {
851 if (result->len) {
852 memcpy(out, result->string, (out+result->len <= ep)
853 ? result->len : (ep-out));
854 out += result->len;
855 }
856 result = result->next;
857 } while (result && out < ep);
858
859 ret[outlen] = '\0';
860 }
861
862 return ret;
863 }
864
865 static const char *ssi_parse_string(request_rec *r, const char *in)
866 {
867 include_ctx_t *ctx = ap_get_module_config(r->request_config,
868 &include_module);
869 return ap_ssi_parse_string(ctx, in, NULL, 0, SSI_EXPAND_DROP_NAME);
870 }
871 static int ssi_access(request_rec *r, ap_parse_node_t *current,
872 string_func_t parse_string)
873 {
874 request_rec *rr;
875 include_ctx_t *ctx = ap_get_module_config(r->request_config,
876 &include_module);
877
878 /* if this arg isn't -A, just return */
879 if (current->token.type != TOKEN_ACCESS || current->token.value[0] != 'A') {
880 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
881 "Unsupported option -%s in file %s",
882 current->token.value, r->filename);
883 return 1;
884 }
885 if (current->left || !current->right ||
886 (current->right->token.type != TOKEN_STRING &&
887 current->right->token.type != TOKEN_RE)) {
888 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
889 "Invalid expression in file %s: Token '-A' must be followed by a URI string.",
890 r->filename);
891 return 1; /* was_error */
892 }
893 current->right->token.value =
894 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
895 SSI_EXPAND_DROP_NAME);
896 rr = ap_sub_req_lookup_uri(current->right->token.value, r, NULL);
897 /* 400 and higher are considered access denied */
898 if (rr->status < HTTP_BAD_REQUEST) {
899 current->value = 1;
900 }
901 else {
902 current->value = 0;
903 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rr->status, r,
904 "mod_include: The tested "
905 "subrequest -A \"%s\" returned an error code.",
906 current->right->token.value);
907 }
908 ap_destroy_sub_req(rr);
909 return 0;
910 }
911
912 /*
913 * +-------------------------------------------------------+
914 * | |
915 * | Action Handlers
916 * | |
917 * +-------------------------------------------------------+
918 */
919
920 /*
921 * Extract the next tag name and value.
922 * If there are no more tags, set the tag name to NULL.
923 * The tag value is html decoded if dodecode is non-zero.
924 * The tag value may be NULL if there is no tag value..
925 */
926 static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
927 char **tag_val, int dodecode)
928 {
929 if (!ctx->intern->argv) {
930 *tag = NULL;
931 *tag_val = NULL;
932
933 return;
934 }
935
936 *tag_val = ctx->intern->argv->value;
937 *tag = ctx->intern->argv->name;
938
939 ctx->intern->argv = ctx->intern->argv->next;
940
941 if (dodecode && *tag_val) {
942 decodehtml(*tag_val);
943 }
944
945 return;
946 }
947
948 static int find_file(request_rec *r, const char *directive, const char *tag,
949 char *tag_val, apr_finfo_t *finfo)
950 {
951 char *to_send = tag_val;
952 request_rec *rr = NULL;
953 int ret=0;
954 char *error_fmt = NULL;
955 apr_status_t rv = APR_SUCCESS;
956
957 if (!strcmp(tag, "file")) {
958 char *newpath;
959
960 /* be safe; only files in this directory or below allowed */
961 rv = apr_filepath_merge(&newpath, NULL, tag_val,
962 APR_FILEPATH_SECUREROOTTEST |
963 APR_FILEPATH_NOTABSOLUTE, r->pool);
964
965 if (rv != APR_SUCCESS) {
966 error_fmt = "unable to access file \"%s\" "
967 "in parsed file %s";
968 }
969 else {
970 /* note: it is okay to pass NULL for the "next filter" since
971 we never attempt to "run" this sub request. */
972 rr = ap_sub_req_lookup_file(newpath, r, NULL);
973
974 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
975 to_send = rr->filename;
976 if ((rv = apr_stat(finfo, to_send,
977 APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
978 && rv != APR_INCOMPLETE) {
979 error_fmt = "unable to get information about \"%s\" "
980 "in parsed file %s";
981 }
982 }
983 else {
984 error_fmt = "unable to lookup information about \"%s\" "
985 "in parsed file %s";
986 }
987 }
988
989 if (error_fmt) {
990 ret = -1;
991 ap_log_rerror(APLOG_MARK, APLOG_ERR,
992 rv, r, error_fmt, to_send, r->filename);
993 }
994
995 if (rr) ap_destroy_sub_req(rr);
996
997 return ret;
998 }
999 else if (!strcmp(tag, "virtual")) {
1000 /* note: it is okay to pass NULL for the "next filter" since
1001 we never attempt to "run" this sub request. */
1002 rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1003
1004 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1005 memcpy((char *) finfo, (const char *) &rr->finfo,
1006 sizeof(rr->finfo));
1007 ap_destroy_sub_req(rr);
1008 return 0;
1009 }
1010 else {
1011 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unable to get "
1012 "information about \"%s\" in parsed file %s",
1013 tag_val, r->filename);
1014 ap_destroy_sub_req(rr);
1015 return -1;
1016 }
1017 }
1018 else {
1019 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
1020 "to tag %s in %s", tag, directive, r->filename);
1021 return -1;
1022 }
1023 }
1024
1025 /*
1026 * <!--#include virtual|file="..." [virtual|file="..."] ... -->
1027 */
1028 static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
1029 apr_bucket_brigade *bb)
1030 {
1031 request_rec *r = f->r;
1032
1033 if (!ctx->argc) {
1034 ap_log_rerror(APLOG_MARK,
1035 (ctx->flags & SSI_FLAG_PRINTING)
1036 ? APLOG_ERR : APLOG_WARNING,
1037 0, r, "missing argument for include element in %s",
1038 r->filename);
1039 }
1040
1041 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1042 return APR_SUCCESS;
1043 }
1044
1045 if (!ctx->argc) {
1046 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1047 return APR_SUCCESS;
1048 }
1049
1050 while (1) {
1051 char *tag = NULL;
1052 char *tag_val = NULL;
1053 request_rec *rr = NULL;
1054 char *error_fmt = NULL;
1055 char *parsed_string;
1056
1057 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1058 if (!tag || !tag_val) {
1059 break;
1060 }
1061
1062 if (strcmp(tag, "virtual") && strcmp(tag, "file")) {
1063 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1064 "\"%s\" to tag include in %s", tag, r->filename);
1065 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1066 break;
1067 }
1068
1069 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1070 SSI_EXPAND_DROP_NAME);
1071 if (tag[0] == 'f') {
1072 char *newpath;
1073 apr_status_t rv;
1074
1075 /* be safe; only files in this directory or below allowed */
1076 rv = apr_filepath_merge(&newpath, NULL, parsed_string,
1077 APR_FILEPATH_SECUREROOTTEST |
1078 APR_FILEPATH_NOTABSOLUTE, ctx->dpool);
1079
1080 if (rv != APR_SUCCESS) {
1081 error_fmt = "unable to include file \"%s\" in parsed file %s";
1082 }
1083 else {
1084 rr = ap_sub_req_lookup_file(newpath, r, f->next);
1085 }
1086 }
1087 else {
1088 if (r->kept_body) {
1089 rr = ap_sub_req_method_uri(r->method, parsed_string, r, f->next);
1090 }
1091 else {
1092 rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
1093 }
1094 }
1095
1096 if (!error_fmt && rr->status != HTTP_OK) {
1097 error_fmt = "unable to include \"%s\" in parsed file %s";
1098 }
1099
1100 if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) &&
1101 rr->content_type && strncmp(rr->content_type, "text/", 5)) {
1102
1103 error_fmt = "unable to include potential exec \"%s\" in parsed "
1104 "file %s";
1105 }
1106
1107 /* See the Kludge in includes_filter for why.
1108 * Basically, it puts a bread crumb in here, then looks
1109 * for the crumb later to see if its been here.
1110 */
1111 ctx->intern->kludge_child = rr;
1112
1113 if (!error_fmt && ap_run_sub_req(rr)) {
1114 error_fmt = "unable to include \"%s\" in parsed file %s";
1115 }
1116
1117 if (error_fmt) {
1118 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, tag_val,
1119 r->filename);
1120 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1121 }
1122
1123 /* Do *not* destroy the subrequest here; it may have allocated
1124 * variables in this r->subprocess_env in the subrequest's
1125 * r->pool, so that pool must survive as long as this request.
1126 * Yes, this is a memory leak. */
1127
1128 if (error_fmt) {
1129 break;
1130 }
1131 }
1132
1133 return APR_SUCCESS;
1134 }
1135
1136 /*
1137 * <!--#echo [encoding="..."] var="..." [encoding="..."] var="..." ... -->
1138 */
1139 static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
1140 apr_bucket_brigade *bb)
1141 {
1142 enum {E_NONE, E_URL, E_ENTITY} encode;
1143 request_rec *r = f->r;
1144
1145 if (!ctx->argc) {
1146 ap_log_rerror(APLOG_MARK,
1147 (ctx->flags & SSI_FLAG_PRINTING)
1148 ? APLOG_ERR : APLOG_WARNING,
1149 0, r, "missing argument for echo element in %s",
1150 r->filename);
1151 }
1152
1153 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1154 return APR_SUCCESS;
1155 }
1156
1157 if (!ctx->argc) {
1158 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1159 return APR_SUCCESS;
1160 }
1161
1162 encode = E_ENTITY;
1163
1164 while (1) {
1165 char *tag = NULL;
1166 char *tag_val = NULL;
1167
1168 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1169 if (!tag || !tag_val) {
1170 break;
1171 }
1172
1173 if (!strcmp(tag, "var")) {
1174 const char *val;
1175 const char *echo_text = NULL;
1176 apr_size_t e_len;
1177
1178 val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL,
1179 0, SSI_EXPAND_DROP_NAME),
1180 ctx);
1181
1182 if (val) {
1183 switch(encode) {
1184 case E_NONE:
1185 echo_text = val;
1186 break;
1187 case E_URL:
1188 echo_text = ap_escape_uri(ctx->dpool, val);
1189 break;
1190 case E_ENTITY:
1191 /* PR#25202: escape anything non-ascii here */
1192 echo_text = ap_escape_html2(ctx->dpool, val, 1);
1193 break;
1194 }
1195
1196 e_len = strlen(echo_text);
1197 }
1198 else {
1199 echo_text = ctx->intern->undefined_echo;
1200 e_len = ctx->intern->undefined_echo_len;
1201 }
1202
1203 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(
1204 apr_pmemdup(ctx->pool, echo_text, e_len),
1205 e_len, ctx->pool, f->c->bucket_alloc));
1206 }
1207 else if (!strcmp(tag, "encoding")) {
1208 if (!strcasecmp(tag_val, "none")) {
1209 encode = E_NONE;
1210 }
1211 else if (!strcasecmp(tag_val, "url")) {
1212 encode = E_URL;
1213 }
1214 else if (!strcasecmp(tag_val, "entity")) {
1215 encode = E_ENTITY;
1216 }
1217 else {
1218 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1219 "\"%s\" to parameter \"encoding\" of tag echo in "
1220 "%s", tag_val, r->filename);
1221 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1222 break;
1223 }
1224 }
1225 else {
1226 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1227 "\"%s\" in tag echo of %s", tag, r->filename);
1228 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1229 break;
1230 }
1231 }
1232
1233 return APR_SUCCESS;
1234 }
1235
1236 /*
1237 * <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."]
1238 * [echomsg="..."] -->
1239 */
1240 static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
1241 apr_bucket_brigade *bb)
1242 {
1243 request_rec *r = f->r;
1244 apr_table_t *env = r->subprocess_env;
1245
1246 if (!ctx->argc) {
1247 ap_log_rerror(APLOG_MARK,
1248 (ctx->flags & SSI_FLAG_PRINTING)
1249 ? APLOG_ERR : APLOG_WARNING,
1250 0, r, "missing argument for config element in %s",
1251 r->filename);
1252 }
1253
1254 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1255 return APR_SUCCESS;
1256 }
1257
1258 if (!ctx->argc) {
1259 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1260 return APR_SUCCESS;
1261 }
1262
1263 while (1) {
1264 char *tag = NULL;
1265 char *tag_val = NULL;
1266
1267 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
1268 if (!tag || !tag_val) {
1269 break;
1270 }
1271
1272 if (!strcmp(tag, "errmsg")) {
1273 ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1274 SSI_EXPAND_DROP_NAME);
1275 }
1276 else if (!strcmp(tag, "echomsg")) {
1277 ctx->intern->undefined_echo =
1278 ap_ssi_parse_string(ctx, tag_val, NULL, 0,SSI_EXPAND_DROP_NAME);
1279 ctx->intern->undefined_echo_len=strlen(ctx->intern->undefined_echo);
1280 }
1281 else if (!strcmp(tag, "timefmt")) {
1282 apr_time_t date = r->request_time;
1283
1284 ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1285 SSI_EXPAND_DROP_NAME);
1286
1287 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
1288 ctx->time_str, 0));
1289 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
1290 ctx->time_str, 1));
1291 apr_table_setn(env, "LAST_MODIFIED",
1292 ap_ht_time(r->pool, r->finfo.mtime,
1293 ctx->time_str, 0));
1294 }
1295 else if (!strcmp(tag, "sizefmt")) {
1296 char *parsed_string;
1297
1298 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1299 SSI_EXPAND_DROP_NAME);
1300 if (!strcmp(parsed_string, "bytes")) {
1301 ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
1302 }
1303 else if (!strcmp(parsed_string, "abbrev")) {
1304 ctx->flags &= SSI_FLAG_SIZE_ABBREV;
1305 }
1306 else {
1307 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1308 "\"%s\" to parameter \"sizefmt\" of tag config "
1309 "in %s", parsed_string, r->filename);
1310 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1311 break;
1312 }
1313 }
1314 else {
1315 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1316 "\"%s\" to tag config in %s", tag, r->filename);
1317 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1318 break;
1319 }
1320 }
1321
1322 return APR_SUCCESS;
1323 }
1324
1325 /*
1326 * <!--#fsize virtual|file="..." [virtual|file="..."] ... -->
1327 */
1328 static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
1329 apr_bucket_brigade *bb)
1330 {
1331 request_rec *r = f->r;
1332
1333 if (!ctx->argc) {
1334 ap_log_rerror(APLOG_MARK,
1335 (ctx->flags & SSI_FLAG_PRINTING)
1336 ? APLOG_ERR : APLOG_WARNING,
1337 0, r, "missing argument for fsize element in %s",
1338 r->filename);
1339 }
1340
1341 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1342 return APR_SUCCESS;
1343 }
1344
1345 if (!ctx->argc) {
1346 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1347 return APR_SUCCESS;
1348 }
1349
1350 while (1) {
1351 char *tag = NULL;
1352 char *tag_val = NULL;
1353 apr_finfo_t finfo;
1354 char *parsed_string;
1355
1356 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1357 if (!tag || !tag_val) {
1358 break;
1359 }
1360
1361 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1362 SSI_EXPAND_DROP_NAME);
1363
1364 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
1365 char *buf;
1366 apr_size_t len;
1367
1368 if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
1369 buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5));
1370 len = 4; /* omit the \0 terminator */
1371 }
1372 else {
1373 apr_size_t l, x, pos;
1374 char *tmp;
1375
1376 tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size);
1377 len = l = strlen(tmp);
1378
1379 for (x = 0; x < l; ++x) {
1380 if (x && !((l - x) % 3)) {
1381 ++len;
1382 }
1383 }
1384
1385 if (len == l) {
1386 buf = apr_pstrmemdup(ctx->pool, tmp, len);
1387 }
1388 else {
1389 buf = apr_palloc(ctx->pool, len);
1390
1391 for (pos = x = 0; x < l; ++x) {
1392 if (x && !((l - x) % 3)) {
1393 buf[pos++] = ',';
1394 }
1395 buf[pos++] = tmp[x];
1396 }
1397 }
1398 }
1399
1400 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len,
1401 ctx->pool, f->c->bucket_alloc));
1402 }
1403 else {
1404 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1405 break;
1406 }
1407 }
1408
1409 return APR_SUCCESS;
1410 }
1411
1412 /*
1413 * <!--#flastmod virtual|file="..." [virtual|file="..."] ... -->
1414 */
1415 static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
1416 apr_bucket_brigade *bb)
1417 {
1418 request_rec *r = f->r;
1419
1420 if (!ctx->argc) {
1421 ap_log_rerror(APLOG_MARK,
1422 (ctx->flags & SSI_FLAG_PRINTING)
1423 ? APLOG_ERR : APLOG_WARNING,
1424 0, r, "missing argument for flastmod element in %s",
1425 r->filename);
1426 }
1427
1428 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1429 return APR_SUCCESS;
1430 }
1431
1432 if (!ctx->argc) {
1433 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1434 return APR_SUCCESS;
1435 }
1436
1437 while (1) {
1438 char *tag = NULL;
1439 char *tag_val = NULL;
1440 apr_finfo_t finfo;
1441 char *parsed_string;
1442
1443 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1444 if (!tag || !tag_val) {
1445 break;
1446 }
1447
1448 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1449 SSI_EXPAND_DROP_NAME);
1450
1451 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
1452 char *t_val;
1453 apr_size_t t_len;
1454
1455 t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0);
1456 t_len = strlen(t_val);
1457
1458 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len,
1459 ctx->pool, f->c->bucket_alloc));
1460 }
1461 else {
1462 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1463 break;
1464 }
1465 }
1466
1467 return APR_SUCCESS;
1468 }
1469
1470 /*
1471 * <!--#if expr="..." -->
1472 */
1473 static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f,
1474 apr_bucket_brigade *bb)
1475 {
1476 char *tag = NULL;
1477 char *expr = NULL;
1478 request_rec *r = f->r;
1479 int expr_ret, was_error;
1480
1481 if (ctx->argc != 1) {
1482 ap_log_rerror(APLOG_MARK,
1483 (ctx->flags & SSI_FLAG_PRINTING)
1484 ? APLOG_ERR : APLOG_WARNING,
1485 0, r, (ctx->argc)
1486 ? "too many arguments for if element in %s"
1487 : "missing expr argument for if element in %s",
1488 r->filename);
1489 }
1490
1491 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1492 ++(ctx->if_nesting_level);
1493 return APR_SUCCESS;
1494 }
1495
1496 if (ctx->argc != 1) {
1497 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1498 return APR_SUCCESS;
1499 }
1500
1501 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
1502
1503 if (strcmp(tag, "expr")) {
1504 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
1505 "to tag if in %s", tag, r->filename);
1506 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1507 return APR_SUCCESS;
1508 }
1509
1510 if (!expr) {
1511 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr value for if "
1512 "element in %s", r->filename);
1513 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1514 return APR_SUCCESS;
1515 }
1516
1517 DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr));
1518
1519 expr_ret = ap_expr_evalstring(r, expr, &was_error, &ctx->intern->re,
1520 ssi_parse_string, ctx->intern->access_func);
1521
1522 if (was_error) {
1523 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1524 return APR_SUCCESS;
1525 }
1526
1527 if (expr_ret) {
1528 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
1529 }
1530 else {
1531 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
1532 }
1533
1534 DEBUG_DUMP_COND(ctx, " if");
1535
1536 ctx->if_nesting_level = 0;
1537
1538 return APR_SUCCESS;
1539 }
1540
1541 /*
1542 * <!--#elif expr="..." -->
1543 */
1544 static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f,
1545 apr_bucket_brigade *bb)
1546 {
1547 char *tag = NULL;
1548 char *expr = NULL;
1549 request_rec *r = f->r;
1550 int expr_ret, was_error;
1551
1552 if (ctx->argc != 1) {
1553 ap_log_rerror(APLOG_MARK,
1554 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
1555 0, r, (ctx->argc)
1556 ? "too many arguments for if element in %s"
1557 : "missing expr argument for if element in %s",
1558 r->filename);
1559 }
1560
1561 if (ctx->if_nesting_level) {
1562 return APR_SUCCESS;
1563 }
1564
1565 if (ctx->argc != 1) {
1566 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1567 return APR_SUCCESS;
1568 }
1569
1570 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
1571
1572 if (strcmp(tag, "expr")) {
1573 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
1574 "to tag if in %s", tag, r->filename);
1575 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1576 return APR_SUCCESS;
1577 }
1578
1579 if (!expr) {
1580 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr in elif "
1581 "statement: %s", r->filename);
1582 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1583 return APR_SUCCESS;
1584 }
1585
1586 DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr));
1587 DEBUG_DUMP_COND(ctx, " elif");
1588
1589 if (ctx->flags & SSI_FLAG_COND_TRUE) {
1590 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
1591 return APR_SUCCESS;
1592 }
1593
1594 expr_ret = ap_expr_evalstring(r, expr, &was_error, &ctx->intern->re,
1595 ssi_parse_string, ctx->intern->access_func);
1596
1597 if (was_error) {
1598 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1599 return APR_SUCCESS;
1600 }
1601
1602 if (expr_ret) {
1603 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
1604 }
1605 else {
1606 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
1607 }
1608
1609 DEBUG_DUMP_COND(ctx, " elif");
1610
1611 return APR_SUCCESS;
1612 }
1613
1614 /*
1615 * <!--#else -->
1616 */
1617 static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f,
1618 apr_bucket_brigade *bb)
1619 {
1620 request_rec *r = f->r;
1621
1622 if (ctx->argc) {
1623 ap_log_rerror(APLOG_MARK,
1624 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
1625 0, r, "else directive does not take tags in %s",
1626 r->filename);
1627 }
1628
1629 if (ctx->if_nesting_level) {
1630 return APR_SUCCESS;
1631 }
1632
1633 if (ctx->argc) {
1634 if (ctx->flags & SSI_FLAG_PRINTING) {
1635 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1636 }
1637
1638 return APR_SUCCESS;
1639 }
1640
1641 DEBUG_DUMP_COND(ctx, " else");
1642
1643 if (ctx->flags & SSI_FLAG_COND_TRUE) {
1644 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
1645 }
1646 else {
1647 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
1648 }
1649
1650 return APR_SUCCESS;
1651 }
1652
1653 /*
1654 * <!--#endif -->
1655 */
1656 static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f,
1657 apr_bucket_brigade *bb)
1658 {
1659 request_rec *r = f->r;
1660
1661 if (ctx->argc) {
1662 ap_log_rerror(APLOG_MARK,
1663 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
1664 0, r, "endif directive does not take tags in %s",
1665 r->filename);
1666 }
1667
1668 if (ctx->if_nesting_level) {
1669 --(ctx->if_nesting_level);
1670 return APR_SUCCESS;
1671 }
1672
1673 if (ctx->argc) {
1674 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1675 return APR_SUCCESS;
1676 }
1677
1678 DEBUG_DUMP_COND(ctx, "endif");
1679
1680 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
1681
1682 return APR_SUCCESS;
1683 }
1684
1685 /*
1686 * <!--#set var="..." value="..." ... -->
1687 */
1688 static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
1689 apr_bucket_brigade *bb)
1690 {
1691 char *var = NULL;
1692 request_rec *r = f->r;
1693 request_rec *sub = r->main;
1694 apr_pool_t *p = r->pool;
1695
1696 if (ctx->argc < 2) {
1697 ap_log_rerror(APLOG_MARK,
1698 (ctx->flags & SSI_FLAG_PRINTING)
1699 ? APLOG_ERR : APLOG_WARNING,
1700 0, r, "missing argument for set element in %s",
1701 r->filename);
1702 }
1703
1704 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1705 return APR_SUCCESS;
1706 }
1707
1708 if (ctx->argc < 2) {
1709 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1710 return APR_SUCCESS;
1711 }
1712
1713 /* we need to use the 'main' request pool to set notes as that is
1714 * a notes lifetime
1715 */
1716 while (sub) {
1717 p = sub->pool;
1718 sub = sub->main;
1719 }
1720
1721 while (1) {
1722 char *tag = NULL;
1723 char *tag_val = NULL;
1724
1725 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1726
1727 if (!tag || !tag_val) {
1728 break;
1729 }
1730
1731 if (!strcmp(tag, "var")) {
1732 var = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1733 SSI_EXPAND_DROP_NAME);
1734 }
1735 else if (!strcmp(tag, "value")) {
1736 char *parsed_string;
1737
1738 if (!var) {
1739 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "variable must "
1740 "precede value in set directive in %s",
1741 r->filename);
1742 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1743 break;
1744 }
1745
1746 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1747 SSI_EXPAND_DROP_NAME);
1748 apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
1749 apr_pstrdup(p, parsed_string));
1750 }
1751 else {
1752 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Invalid tag for set "
1753 "directive in %s", r->filename);
1754 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1755 break;
1756 }
1757 }
1758
1759 return APR_SUCCESS;
1760 }
1761
1762 /*
1763 * <!--#printenv -->
1764 */
1765 static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
1766 apr_bucket_brigade *bb)
1767 {
1768 request_rec *r = f->r;
1769 const apr_array_header_t *arr;
1770 const apr_table_entry_t *elts;
1771 int i;
1772
1773 if (ctx->argc) {
1774 ap_log_rerror(APLOG_MARK,
1775 (ctx->flags & SSI_FLAG_PRINTING)
1776 ? APLOG_ERR : APLOG_WARNING,
1777 0, r, "printenv directive does not take tags in %s",
1778 r->filename);
1779 }
1780
1781 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1782 return APR_SUCCESS;
1783 }
1784
1785 if (ctx->argc) {
1786 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1787 return APR_SUCCESS;
1788 }
1789
1790 arr = apr_table_elts(r->subprocess_env);
1791 elts = (apr_table_entry_t *)arr->elts;
1792
1793 for (i = 0; i < arr->nelts; ++i) {
1794 const char *key_text, *val_text;
1795 char *key_val, *next;
1796 apr_size_t k_len, v_len, kv_length;
1797
1798 /* get key */
1799 key_text = ap_escape_html(ctx->dpool, elts[i].key);
1800 k_len = strlen(key_text);
1801
1802 /* get value */
1803 val_text = elts[i].val;
1804 if (val_text == LAZY_VALUE) {
1805 val_text = add_include_vars_lazy(r, elts[i].key, ctx->time_str);
1806 }
1807 val_text = ap_escape_html(ctx->dpool, elts[i].val);
1808 v_len = strlen(val_text);
1809
1810 /* assemble result */
1811 kv_length = k_len + v_len + sizeof("=\n");
1812 key_val = apr_palloc(ctx->pool, kv_length);
1813 next = key_val;
1814
1815 memcpy(next, key_text, k_len);
1816 next += k_len;
1817 *next++ = '=';
1818 memcpy(next, val_text, v_len);
1819 next += v_len;
1820 *next++ = '\n';
1821 *next = 0;
1822
1823 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(key_val, kv_length-1,
1824 ctx->pool, f->c->bucket_alloc));
1825 }
1826
1827 ctx->flush_now = 1;
1828 return APR_SUCCESS;
1829 }
1830
1831
1832 /*
1833 * +-------------------------------------------------------+
1834 * | |
1835 * | Main Includes-Filter Engine
1836 * | |
1837 * +-------------------------------------------------------+
1838 */
1839
1840 /* This is an implementation of the BNDM search algorithm.
1841 *
1842 * Fast and Flexible String Matching by Combining Bit-parallelism and
1843 * Suffix Automata (2001)
1844 * Gonzalo Navarro, Mathieu Raffinot
1845 *
1846 * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
1847 *
1848 * Initial code submitted by Sascha Schumann.
1849 */
1850
1851 /* Precompile the bndm_t data structure. */
1852 static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl)
1853 {
1854 unsigned int x;
1855 const char *ne = n + nl;
1856 bndm_t *t = apr_palloc(pool, sizeof(*t));
1857
1858 memset(t->T, 0, sizeof(unsigned int) * 256);
1859 t->pattern_len = nl;
1860
1861 for (x = 1; n < ne; x <<= 1) {
1862 t->T[(unsigned char) *n++] |= x;
1863 }
1864
1865 t->x = x - 1;
1866
1867 return t;
1868 }
1869
1870 /* Implements the BNDM search algorithm (as described above).
1871 *
1872 * h - the string to look in
1873 * hl - length of the string to look for
1874 * t - precompiled bndm structure against the pattern
1875 *
1876 * Returns the count of character that is the first match or hl if no
1877 * match is found.
1878 */
1879 static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl)
1880 {
1881 const char *skip;
1882 const char *he, *p, *pi;
1883 unsigned int *T, x, d;
1884 apr_size_t nl;
1885
1886 he = h + hl;
1887
1888 T = t->T;
1889 x = t->x;
1890 nl = t->pattern_len;
1891
1892 pi = h - 1; /* pi: p initial */
1893 p = pi + nl; /* compare window right to left. point to the first char */
1894
1895 while (p < he) {
1896 skip = p;
1897 d = x;
1898 do {
1899 d &= T[(unsigned char) *p--];
1900 if (!d) {
1901 break;
1902 }
1903 if ((d & 1)) {
1904 if (p != pi) {
1905 skip = p;
1906 }
1907 else {
1908 return p - h + 1;
1909 }
1910 }
1911 d >>= 1;
1912 } while (d);
1913
1914 pi = skip;
1915 p = pi + nl;
1916 }
1917
1918 return hl;
1919 }
1920
1921 /*
1922 * returns the index position of the first byte of start_seq (or the len of
1923 * the buffer as non-match)
1924 */
1925 static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data,
1926 apr_size_t len)
1927 {
1928 struct ssi_internal_ctx *intern = ctx->intern;
1929 apr_size_t slen = intern->start_seq_pat->pattern_len;
1930 apr_size_t index;
1931 const char *p, *ep;
1932
1933 if (len < slen) {
1934 p = data; /* try partial match at the end of the buffer (below) */
1935 }
1936 else {
1937 /* try fast bndm search over the buffer
1938 * (hopefully the whole start sequence can be found in this buffer)
1939 */
1940 index = bndm(intern->start_seq_pat, data, len);
1941
1942 /* wow, found it. ready. */
1943 if (index < len) {
1944 intern->state = PARSE_DIRECTIVE;
1945 return index;
1946 }
1947 else {
1948 /* ok, the pattern can't be found as whole in the buffer,
1949 * check the end for a partial match
1950 */
1951 p = data + len - slen + 1;
1952 }
1953 }
1954
1955 ep = data + len;
1956 do {
1957 while (p < ep && *p != *intern->start_seq) {
1958 ++p;
1959 }
1960
1961 index = p - data;
1962
1963 /* found a possible start_seq start */
1964 if (p < ep) {
1965 apr_size_t pos = 1;
1966
1967 ++p;
1968 while (p < ep && *p == intern->start_seq[pos]) {
1969 ++p;
1970 ++pos;
1971 }
1972
1973 /* partial match found. Store the info for the next round */
1974 if (p == ep) {
1975 intern->state = PARSE_HEAD;
1976 intern->parse_pos = pos;
1977 return index;
1978 }
1979 }
1980
1981 /* we must try all combinations; consider (e.g.) SSIStartTag "--->"
1982 * and a string data of "--.-" and the end of the buffer
1983 */
1984 p = data + index + 1;
1985 } while (p < ep);
1986
1987 /* no match */
1988 return len;
1989 }
1990
1991 /*
1992 * returns the first byte *after* the partial (or final) match.
1993 *
1994 * If we had to trick with the start_seq start, 'release' returns the
1995 * number of chars of the start_seq which appeared not to be part of a
1996 * full tag and may have to be passed down the filter chain.
1997 */
1998 static apr_size_t find_partial_start_sequence(include_ctx_t *ctx,
1999 const char *data,
2000 apr_size_t len,
2001 apr_size_t *release)
2002 {
2003 struct ssi_internal_ctx *intern = ctx->intern;
2004 apr_size_t pos, spos = 0;
2005 apr_size_t slen = intern->start_seq_pat->pattern_len;
2006 const char *p, *ep;
2007
2008 pos = intern->parse_pos;
2009 ep = data + len;
2010 *release = 0;
2011
2012 do {
2013 p = data;
2014
2015 while (p < ep && pos < slen && *p == intern->start_seq[pos]) {
2016 ++p;
2017 ++pos;
2018 }
2019
2020 /* full match */
2021 if (pos == slen) {
2022 intern->state = PARSE_DIRECTIVE;
2023 return (p - data);
2024 }
2025
2026 /* the whole buffer is a partial match */
2027 if (p == ep) {
2028 intern->parse_pos = pos;
2029 return (p - data);
2030 }
2031
2032 /* No match so far, but again:
2033 * We must try all combinations, since the start_seq is a random
2034 * user supplied string
2035 *
2036 * So: look if the first char of start_seq appears somewhere within
2037 * the current partial match. If it does, try to start a match that
2038 * begins with this offset. (This can happen, if a strange
2039 * start_seq like "---->" spans buffers)
2040 */
2041 if (spos < intern->parse_pos) {
2042 do {
2043 ++spos;
2044 ++*release;
2045 p = intern->start_seq + spos;
2046 pos = intern->parse_pos - spos;
2047
2048 while (pos && *p != *intern->start_seq) {
2049 ++p;
2050 ++spos;
2051 ++*release;
2052 --pos;
2053 }
2054
2055 /* if a matching beginning char was found, try to match the
2056 * remainder of the old buffer.
2057 */
2058 if (pos > 1) {
2059 apr_size_t t = 1;
2060
2061 ++p;
2062 while (t < pos && *p == intern->start_seq[t]) {
2063 ++p;
2064 ++t;
2065 }
2066
2067 if (t == pos) {
2068 /* yeah, another partial match found in the *old*
2069 * buffer, now test the *current* buffer for
2070 * continuing match
2071 */
2072 break;
2073 }
2074 }
2075 } while (pos > 1);
2076
2077 if (pos) {
2078 continue;
2079 }
2080 }
2081
2082 break;
2083 } while (1); /* work hard to find a match ;-) */
2084
2085 /* no match at all, release all (wrongly) matched chars so far */
2086 *release = intern->parse_pos;
2087 intern->state = PARSE_PRE_HEAD;
2088 return 0;
2089 }
2090
2091 /*
2092 * returns the position after the directive
2093 */
2094 static apr_size_t find_directive(include_ctx_t *ctx, const char *data,
2095 apr_size_t len, char ***store,
2096 apr_size_t **store_len)
2097 {
2098 struct ssi_internal_ctx *intern = ctx->intern;
2099 const char *p = data;
2100 const char *ep = data + len;
2101 apr_size_t pos;
2102
2103 switch (intern->state) {
2104 case PARSE_DIRECTIVE:
2105 while (p < ep && !apr_isspace(*p)) {
2106 /* we have to consider the case of missing space between directive
2107 * and end_seq (be somewhat lenient), e.g. <!--#printenv-->
2108 */
2109 if (*p == *intern->end_seq) {
2110 intern->state = PARSE_DIRECTIVE_TAIL;
2111 intern->parse_pos = 1;
2112 ++p;
2113 return (p - data);
2114 }
2115 ++p;
2116 }
2117
2118 if (p < ep) { /* found delimiter whitespace */
2119 intern->state = PARSE_DIRECTIVE_POSTNAME;
2120 *store = &intern->directive;
2121 *store_len = &intern->directive_len;
2122 }
2123
2124 break;
2125
2126 case PARSE_DIRECTIVE_TAIL:
2127 pos = intern->parse_pos;
2128
2129 while (p < ep && pos < intern->end_seq_len &&
2130 *p == intern->end_seq[pos]) {
2131 ++p;
2132 ++pos;
2133 }
2134
2135 /* full match, we're done */
2136 if (pos == intern->end_seq_len) {
2137 intern->state = PARSE_DIRECTIVE_POSTTAIL;
2138 *store = &intern->directive;
2139 *store_len = &intern->directive_len;
2140 break;
2141 }
2142
2143 /* partial match, the buffer is too small to match fully */
2144 if (p == ep) {
2145 intern->parse_pos = pos;
2146 break;
2147 }
2148
2149 /* no match. continue normal parsing */
2150 intern->state = PARSE_DIRECTIVE;
2151 return 0;
2152
2153 case PARSE_DIRECTIVE_POSTTAIL:
2154 intern->state = PARSE_EXECUTE;
2155 intern->directive_len -= intern->end_seq_len;
2156 /* continue immediately with the next state */
2157
2158 case PARSE_DIRECTIVE_POSTNAME:
2159 if (PARSE_DIRECTIVE_POSTNAME == intern->state) {
2160 intern->state = PARSE_PRE_ARG;
2161 }
2162 ctx->argc = 0;
2163 intern->argv = NULL;
2164
2165 if (!intern->directive_len) {
2166 intern->error = 1;
2167 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2168 "directive name in parsed document %s",
2169 intern->r->filename);
2170 }
2171 else {
2172 char *sp = intern->directive;
2173 char *sep = intern->directive + intern->directive_len;
2174
2175 /* normalize directive name */
2176 for (; sp < sep; ++sp) {
2177 *sp = apr_tolower(*sp);
2178 }
2179 }
2180
2181 return 0;
2182
2183 default:
2184 /* get a rid of a gcc warning about unhandled enumerations */
2185 break;
2186 }
2187
2188 return (p - data);
2189 }
2190
2191 /*
2192 * find out whether the next token is (a possible) end_seq or an argument
2193 */
2194 static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data,
2195 apr_size_t len)
2196 {
2197 struct ssi_internal_ctx *intern = ctx->intern;
2198 const char *p = data;
2199 const char *ep = data + len;
2200
2201 /* skip leading WS */
2202 while (p < ep && apr_isspace(*p)) {
2203 ++p;
2204 }
2205
2206 /* buffer doesn't consist of whitespaces only */
2207 if (p < ep) {
2208 intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG;
2209 }
2210
2211 return (p - data);
2212 }
2213
2214 /*
2215 * test the stream for end_seq. If it doesn't match at all, it must be an
2216 * argument
2217 */
2218 static apr_size_t find_tail(include_ctx_t *ctx, const char *data,
2219 apr_size_t len)
2220 {
2221 struct ssi_internal_ctx *intern = ctx->intern;
2222 const char *p = data;
2223 const char *ep = data + len;
2224 apr_size_t pos = intern->parse_pos;
2225
2226 if (PARSE_TAIL == intern->state) {
2227 intern->state = PARSE_TAIL_SEQ;
2228 pos = intern->parse_pos = 0;
2229 }
2230
2231 while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) {
2232 ++p;
2233 ++pos;
2234 }
2235
2236 /* bingo, full match */
2237 if (pos == intern->end_seq_len) {
2238 intern->state = PARSE_EXECUTE;
2239 return (p - data);
2240 }
2241
2242 /* partial match, the buffer is too small to match fully */
2243 if (p == ep) {
2244 intern->parse_pos = pos;
2245 return (p - data);
2246 }
2247
2248 /* no match. It must be an argument string then
2249 * The caller should cleanup and rewind to the reparse point
2250 */
2251 intern->state = PARSE_ARG;
2252 return 0;
2253 }
2254
2255 /*
2256 * extract name=value from the buffer
2257 * A pcre-pattern could look (similar to):
2258 * name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
2259 */
2260 static apr_size_t find_argument(include_ctx_t *ctx, const char *data,
2261 apr_size_t len, char ***store,
2262 apr_size_t **store_len)
2263 {
2264 struct ssi_internal_ctx *intern = ctx->intern;
2265 const char *p = data;
2266 const char *ep = data + len;
2267
2268 switch (intern->state) {
2269 case PARSE_ARG:
2270 /*
2271 * create argument structure and append it to the current list
2272 */
2273 intern->current_arg = apr_palloc(ctx->dpool,
2274 sizeof(*intern->current_arg));
2275 intern->current_arg->next = NULL;
2276
2277 ++(ctx->argc);
2278 if (!intern->argv) {
2279 intern->argv = intern->current_arg;
2280 }
2281 else {
2282 arg_item_t *newarg = intern->argv;
2283
2284 while (newarg->next) {
2285 newarg = newarg->next;
2286 }
2287 newarg->next = intern->current_arg;
2288 }
2289
2290 /* check whether it's a valid one. If it begins with a quote, we
2291 * can safely assume, someone forgot the name of the argument
2292 */
2293 switch (*p) {
2294 case '"': case '\'': case '`':
2295 *store = NULL;
2296
2297 intern->state = PARSE_ARG_VAL;
2298 intern->quote = *p++;
2299 intern->current_arg->name = NULL;
2300 intern->current_arg->name_len = 0;
2301 intern->error = 1;
2302
2303 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2304 "argument name for value to tag %s in %s",
2305 apr_pstrmemdup(intern->r->pool, intern->directive,
2306 intern->directive_len),
2307 intern->r->filename);
2308
2309 return (p - data);
2310
2311 default:
2312 intern->state = PARSE_ARG_NAME;
2313 }
2314 /* continue immediately with next state */
2315
2316 case PARSE_ARG_NAME:
2317 while (p < ep && !apr_isspace(*p) && *p != '=') {
2318 ++p;
2319 }
2320
2321 if (p < ep) {
2322 intern->state = PARSE_ARG_POSTNAME;
2323 *store = &intern->current_arg->name;
2324 *store_len = &intern->current_arg->name_len;
2325 return (p - data);
2326 }
2327 break;
2328
2329 case PARSE_ARG_POSTNAME:
2330 intern->current_arg->name = apr_pstrmemdup(ctx->dpool,
2331 intern->current_arg->name,
2332 intern->current_arg->name_len);
2333 if (!intern->current_arg->name_len) {
2334 intern->error = 1;
2335 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2336 "argument name for value to tag %s in %s",
2337 apr_pstrmemdup(intern->r->pool, intern->directive,
2338 intern->directive_len),
2339 intern->r->filename);
2340 }
2341 else {
2342 char *sp = intern->current_arg->name;
2343
2344 /* normalize the name */
2345 while (*sp) {
2346 *sp = apr_tolower(*sp);
2347 ++sp;
2348 }
2349 }
2350
2351 intern->state = PARSE_ARG_EQ;
2352 /* continue with next state immediately */
2353
2354 case PARSE_ARG_EQ:
2355 *store = NULL;
2356
2357 while (p < ep && apr_isspace(*p)) {
2358 ++p;
2359 }
2360
2361 if (p < ep) {
2362 if (*p == '=') {
2363 intern->state = PARSE_ARG_PREVAL;
2364 ++p;
2365 }
2366 else { /* no value */
2367 intern->current_arg->value = NULL;
2368 intern->state = PARSE_PRE_ARG;
2369 }
2370
2371 return (p - data);
2372 }
2373 break;
2374
2375 case PARSE_ARG_PREVAL:
2376 *store = NULL;
2377
2378 while (p < ep && apr_isspace(*p)) {
2379 ++p;
2380 }
2381
2382 /* buffer doesn't consist of whitespaces only */
2383 if (p < ep) {
2384 intern->state = PARSE_ARG_VAL;
2385 switch (*p) {
2386 case '"': case '\'': case '`':
2387 intern->quote = *p++;
2388 break;
2389 default:
2390 intern->quote = '\0';
2391 break;
2392 }
2393
2394 return (p - data);
2395 }
2396 break;
2397
2398 case PARSE_ARG_VAL_ESC:
2399 if (*p == intern->quote) {
2400 ++p;
2401 }
2402 intern->state = PARSE_ARG_VAL;
2403 /* continue with next state immediately */
2404
2405 case PARSE_ARG_VAL:
2406 for (; p < ep; ++p) {
2407 if (intern->quote && *p == '\\') {
2408 ++p;
2409 if (p == ep) {
2410 intern->state = PARSE_ARG_VAL_ESC;
2411 break;
2412 }
2413
2414 if (*p != intern->quote) {
2415 --p;
2416 }
2417 }
2418 else if (intern->quote && *p == intern->quote) {
2419 ++p;
2420 *store = &intern->current_arg->value;
2421 *store_len = &intern->current_arg->value_len;
2422 intern->state = PARSE_ARG_POSTVAL;
2423 break;
2424 }
2425 else if (!intern->quote && apr_isspace(*p)) {
2426 ++p;
2427 *store = &intern->current_arg->value;
2428 *store_len = &intern->current_arg->value_len;
2429 intern->state = PARSE_ARG_POSTVAL;
2430 break;
2431 }
2432 }
2433
2434 return (p - data);
2435
2436 case PARSE_ARG_POSTVAL:
2437 /*
2438 * The value is still the raw input string. Finally clean it up.
2439 */
2440 --(intern->current_arg->value_len);
2441
2442 /* strip quote escaping \ from the string */
2443 if (intern->quote) {
2444 apr_size_t shift = 0;
2445 char *sp;
2446
2447 sp = intern->current_arg->value;
2448 ep = intern->current_arg->value + intern->current_arg->value_len;
2449 while (sp < ep && *sp != '\\') {
2450 ++sp;
2451 }
2452 for (; sp < ep; ++sp) {
2453 if (*sp == '\\' && sp[1] == intern->quote) {
2454 ++sp;
2455 ++shift;
2456 }
2457 if (shift) {
2458 *(sp-shift) = *sp;
2459 }
2460 }
2461
2462 intern->current_arg->value_len -= shift;
2463 }
2464
2465 intern->current_arg->value[intern->current_arg->value_len] = '\0';
2466 intern->state = PARSE_PRE_ARG;
2467
2468 return 0;
2469
2470 default:
2471 /* get a rid of a gcc warning about unhandled enumerations */
2472 break;
2473 }
2474
2475 return len; /* partial match of something */
2476 }
2477
2478 /*
2479 * This is the main loop over the current bucket brigade.
2480 */
2481 static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
2482 {
2483 include_ctx_t *ctx = f->ctx;
2484 struct ssi_internal_ctx *intern = ctx->intern;
2485 request_rec *r = f->r;
2486 apr_bucket *b = APR_BRIGADE_FIRST(bb);
2487 apr_bucket_brigade *pass_bb;
2488 apr_status_t rv = APR_SUCCESS;
2489 char *magic; /* magic pointer for sentinel use */
2490
2491 /* fast exit */
2492 if (APR_BRIGADE_EMPTY(bb)) {
2493 return APR_SUCCESS;
2494 }
2495
2496 /* we may crash, since already cleaned up; hand over the responsibility
2497 * to the next filter;-)
2498 */
2499 if (intern->seen_eos) {
2500 return ap_pass_brigade(f->next, bb);
2501 }
2502
2503 /* All stuff passed along has to be put into that brigade */
2504 pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
2505
2506 /* initialization for this loop */
2507 intern->bytes_read = 0;
2508 intern->error = 0;
2509 intern->r = r;
2510 ctx->flush_now = 0;
2511
2512 /* loop over the current bucket brigade */
2513 while (b != APR_BRIGADE_SENTINEL(bb)) {
2514 const char *data = NULL;
2515 apr_size_t len, index, release;
2516 apr_bucket *newb = NULL;
2517 char **store = &magic;
2518 apr_size_t *store_len = NULL;
2519
2520 /* handle meta buckets before reading any data */
2521 if (APR_BUCKET_IS_METADATA(b)) {
2522 newb = APR_BUCKET_NEXT(b);
2523
2524 APR_BUCKET_REMOVE(b);
2525
2526 if (APR_BUCKET_IS_EOS(b)) {
2527 intern->seen_eos = 1;
2528
2529 /* Hit end of stream, time for cleanup ... But wait!
2530 * Perhaps we're not ready yet. We may have to loop one or
2531 * two times again to finish our work. In that case, we
2532 * just re-insert the EOS bucket to allow for an extra loop.
2533 *
2534 * PARSE_EXECUTE means, we've hit a directive just before the
2535 * EOS, which is now waiting for execution.
2536 *
2537 * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
2538 * no argument and no space between directive and end_seq
2539 * just before the EOS. (consider <!--#printenv--> as last
2540 * or only string within the stream). This state, however,
2541 * just cleans up and turns itself to PARSE_EXECUTE, which
2542 * will be passed through within the next (and actually
2543 * last) round.
2544 */
2545 if (PARSE_EXECUTE == intern->state ||
2546 PARSE_DIRECTIVE_POSTTAIL == intern->state) {
2547 APR_BUCKET_INSERT_BEFORE(newb, b);
2548 }
2549 else {
2550 break; /* END OF STREAM */
2551 }
2552 }
2553 else {
2554 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
2555
2556 if (APR_BUCKET_IS_FLUSH(b)) {
2557 ctx->flush_now = 1;
2558 }
2559
2560 b = newb;
2561 continue;
2562 }
2563 }
2564
2565 /* enough is enough ... */
2566 if (ctx->flush_now ||
2567 intern->bytes_read > AP_MIN_BYTES_TO_WRITE) {
2568
2569 if (!APR_BRIGADE_EMPTY(pass_bb)) {
2570 rv = ap_pass_brigade(f->next, pass_bb);
2571 if (rv != APR_SUCCESS) {
2572 apr_brigade_destroy(pass_bb);
2573 return rv;
2574 }
2575 }
2576
2577 ctx->flush_now = 0;
2578 intern->bytes_read = 0;
2579 }
2580
2581 /* read the current bucket data */
2582 len = 0;
2583 if (!intern->seen_eos) {
2584 if (intern->bytes_read > 0) {
2585 rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
2586 if (APR_STATUS_IS_EAGAIN(rv)) {
2587 ctx->flush_now = 1;
2588 continue;
2589 }
2590 }
2591
2592 if (!len || rv != APR_SUCCESS) {
2593 rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
2594 }
2595
2596 if (rv != APR_SUCCESS) {
2597 apr_brigade_destroy(pass_bb);
2598 return rv;
2599 }
2600
2601 intern->bytes_read += len;
2602 }
2603
2604 /* zero length bucket, fetch next one */
2605 if (!len && !intern->seen_eos) {
2606 b = APR_BUCKET_NEXT(b);
2607 continue;
2608 }
2609
2610 /*
2611 * it's actually a data containing bucket, start/continue parsing
2612 */
2613
2614 switch (intern->state) {
2615 /* no current tag; search for start sequence */
2616 case PARSE_PRE_HEAD:
2617 index = find_start_sequence(ctx, data, len);
2618
2619 if (index < len) {
2620 apr_bucket_split(b, index);
2621 }
2622
2623 newb = APR_BUCKET_NEXT(b);
2624 if (ctx->flags & SSI_FLAG_PRINTING) {
2625 APR_BUCKET_REMOVE(b);
2626 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
2627 }
2628 else {
2629 apr_bucket_delete(b);
2630 }
2631
2632 if (index < len) {
2633 /* now delete the start_seq stuff from the remaining bucket */
2634 if (PARSE_DIRECTIVE == intern->state) { /* full match */
2635 apr_bucket_split(newb, intern->start_seq_pat->pattern_len);
2636 ctx->flush_now = 1; /* pass pre-tag stuff */
2637 }
2638
2639 b = APR_BUCKET_NEXT(newb);
2640 apr_bucket_delete(newb);
2641 }
2642 else {
2643 b = newb;
2644 }
2645
2646 break;
2647
2648 /* we're currently looking for the end of the start sequence */
2649 case PARSE_HEAD:
2650 index = find_partial_start_sequence(ctx, data, len, &release);
2651
2652 /* check if we mismatched earlier and have to release some chars */
2653 if (release && (ctx->flags & SSI_FLAG_PRINTING)) {
2654 char *to_release = apr_pmemdup(ctx->pool, intern->start_seq, release);
2655
2656 newb = apr_bucket_pool_create(to_release, release, ctx->pool,
2657 f->c->bucket_alloc);
2658 APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
2659 }
2660
2661 if (index) { /* any match */
2662 /* now delete the start_seq stuff from the remaining bucket */
2663 if (PARSE_DIRECTIVE == intern->state) { /* final match */
2664 apr_bucket_split(b, index);
2665 ctx->flush_now = 1; /* pass pre-tag stuff */
2666 }
2667 newb = APR_BUCKET_NEXT(b);
2668 apr_bucket_delete(b);
2669 b = newb;
2670 }
2671
2672 break;
2673
2674 /* we're currently grabbing the directive name */
2675 case PARSE_DIRECTIVE:
2676 case PARSE_DIRECTIVE_POSTNAME:
2677 case PARSE_DIRECTIVE_TAIL:
2678 case PARSE_DIRECTIVE_POSTTAIL:
2679 index = find_directive(ctx, data, len, &store, &store_len);
2680
2681 if (index) {
2682 apr_bucket_split(b, index);
2683 newb = APR_BUCKET_NEXT(b);
2684 }
2685
2686 if (store) {
2687 if (index) {
2688 APR_BUCKET_REMOVE(b);
2689 apr_bucket_setaside(b, r->pool);
2690 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
2691 b = newb;
2692 }
2693
2694 /* time for cleanup? */
2695 if (store != &magic) {
2696 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
2697 ctx->dpool);
2698 apr_brigade_cleanup(intern->tmp_bb);
2699 }
2700 }
2701 else if (index) {
2702 apr_bucket_delete(b);
2703 b = newb;
2704 }
2705
2706 break;
2707
2708 /* skip WS and find out what comes next (arg or end_seq) */
2709 case PARSE_PRE_ARG:
2710 index = find_arg_or_tail(ctx, data, len);
2711
2712 if (index) { /* skipped whitespaces */
2713 if (index < len) {
2714 apr_bucket_split(b, index);
2715 }
2716 newb = APR_BUCKET_NEXT(b);
2717 apr_bucket_delete(b);
2718 b = newb;
2719 }
2720
2721 break;
2722
2723 /* currently parsing name[=val] */
2724 case PARSE_ARG:
2725 case PARSE_ARG_NAME:
2726 case PARSE_ARG_POSTNAME:
2727 case PARSE_ARG_EQ:
2728 case PARSE_ARG_PREVAL:
2729 case PARSE_ARG_VAL:
2730 case PARSE_ARG_VAL_ESC:
2731 case PARSE_ARG_POSTVAL:
2732 index = find_argument(ctx, data, len, &store, &store_len);
2733
2734 if (index) {
2735 apr_bucket_split(b, index);
2736 newb = APR_BUCKET_NEXT(b);
2737 }
2738
2739 if (store) {
2740 if (index) {
2741 APR_BUCKET_REMOVE(b);
2742 apr_bucket_setaside(b, r->pool);
2743 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
2744 b = newb;
2745 }
2746
2747 /* time for cleanup? */
2748 if (store != &magic) {
2749 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
2750 ctx->dpool);
2751 apr_brigade_cleanup(intern->tmp_bb);
2752 }
2753 }
2754 else if (index) {
2755 apr_bucket_delete(b);
2756 b = newb;
2757 }
2758
2759 break;
2760
2761 /* try to match end_seq at current pos. */
2762 case PARSE_TAIL:
2763 case PARSE_TAIL_SEQ:
2764 index = find_tail(ctx, data, len);
2765
2766 switch (intern->state) {
2767 case PARSE_EXECUTE: /* full match */
2768 apr_bucket_split(b, index);
2769 newb = APR_BUCKET_NEXT(b);
2770 apr_bucket_delete(b);
2771 b = newb;
2772 break;
2773
2774 case PARSE_ARG: /* no match */
2775 /* PARSE_ARG must reparse at the beginning */
2776 APR_BRIGADE_PREPEND(bb, intern->tmp_bb);
2777 b = APR_BRIGADE_FIRST(bb);
2778 break;
2779
2780 default: /* partial match */
2781 newb = APR_BUCKET_NEXT(b);
2782 APR_BUCKET_REMOVE(b);
2783 apr_bucket_setaside(b, r->pool);
2784 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
2785 b = newb;
2786 break;
2787 }
2788
2789 break;
2790
2791 /* now execute the parsed directive, cleanup the space and
2792 * start again with PARSE_PRE_HEAD
2793 */
2794 case PARSE_EXECUTE:
2795 /* if there was an error, it was already logged; just stop here */
2796 if (intern->error) {
2797 if (ctx->flags & SSI_FLAG_PRINTING) {
2798 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
2799 intern->error = 0;
2800 }
2801 }
2802 else {
2803 include_handler_fn_t *handle_func;
2804
2805 handle_func =
2806 (include_handler_fn_t *)apr_hash_get(include_handlers, intern->directive,
2807 intern->directive_len);
2808
2809 if (handle_func) {
2810 DEBUG_INIT(ctx, f, pass_bb);
2811 rv = handle_func(ctx, f, pass_bb);
2812 if (rv != APR_SUCCESS) {
2813 apr_brigade_destroy(pass_bb);
2814 return rv;
2815 }
2816 }
2817 else {
2818 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2819 "unknown directive \"%s\" in parsed doc %s",
2820 apr_pstrmemdup(r->pool, intern->directive,
2821 intern->directive_len),
2822 r->filename);
2823 if (ctx->flags & SSI_FLAG_PRINTING) {
2824 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
2825 }
2826 }
2827 }
2828
2829 /* cleanup */
2830 apr_pool_clear(ctx->dpool);
2831 apr_brigade_cleanup(intern->tmp_bb);
2832
2833 /* Oooof. Done here, start next round */
2834 intern->state = PARSE_PRE_HEAD;
2835 break;
2836
2837 } /* switch(ctx->state) */
2838
2839 } /* while(brigade) */
2840
2841 /* End of stream. Final cleanup */
2842 if (intern->seen_eos) {
2843 if (PARSE_HEAD == intern->state) {
2844 if (ctx->flags & SSI_FLAG_PRINTING) {
2845 char *to_release = apr_pmemdup(ctx->pool, intern->start_seq,
2846 intern->parse_pos);
2847
2848 APR_BRIGADE_INSERT_TAIL(pass_bb,
2849 apr_bucket_pool_create(to_release,
2850 intern->parse_pos, ctx->pool,
2851 f->c->bucket_alloc));
2852 }
2853 }
2854 else if (PARSE_PRE_HEAD != intern->state) {
2855 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2856 "SSI directive was not properly finished at the end "
2857 "of parsed document %s", r->filename);
2858 if (ctx->flags & SSI_FLAG_PRINTING) {
2859 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
2860 }
2861 }
2862
2863 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2864 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
2865 "missing closing endif directive in parsed document"
2866 " %s", r->filename);
2867 }
2868
2869 /* cleanup our temporary memory */
2870 apr_brigade_destroy(intern->tmp_bb);
2871 apr_pool_destroy(ctx->dpool);
2872
2873 /* don't forget to finally insert the EOS bucket */
2874 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
2875 }
2876
2877 /* if something's left over, pass it along */
2878 if (!APR_BRIGADE_EMPTY(pass_bb)) {
2879 rv = ap_pass_brigade(f->next, pass_bb);
2880 }
2881 else {
2882 rv = APR_SUCCESS;
2883 apr_brigade_destroy(pass_bb);
2884 }
2885 return rv;
2886 }
2887
2888
2889 /*
2890 * +-------------------------------------------------------+
2891 * | |
2892 * | Runtime Hooks
2893 * | |
2894 * +-------------------------------------------------------+
2895 */
2896
2897 static int includes_setup(ap_filter_t *f)
2898 {
2899 include_dir_config *conf = ap_get_module_config(f->r->per_dir_config,
2900 &include_module);
2901
2902 /* When our xbithack value isn't set to full or our platform isn't
2903 * providing group-level protection bits or our group-level bits do not
2904 * have group-execite on, we will set the no_local_copy value to 1 so
2905 * that we will not send 304s.
2906 */
2907 if ((conf->xbithack != XBITHACK_FULL)
2908 || !(f->r->finfo.valid & APR_FINFO_GPROT)
2909 || !(f->r->finfo.protection & APR_GEXECUTE)) {
2910 f->r->no_local_copy = 1;
2911 }
2912
2913 /* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4.
2914 * We don't know if we are going to be including a file or executing
2915 * a program - in either case a strong ETag header will likely be invalid.
2916 */
2917 apr_table_setn(f->r->notes, "no-etag", "");
2918
2919 return OK;
2920 }
2921
2922 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
2923 {
2924 request_rec *r = f->r;
2925 include_ctx_t *ctx = f->ctx;
2926 include_dir_config *conf = ap_get_module_config(r->per_dir_config,
2927 &include_module);
2928
2929 include_server_config *sconf= ap_get_module_config(r->server->module_config,
2930 &include_module);
2931
2932 if (!(ap_allow_options(r) & OPT_INCLUDES)) {
2933 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
2934 "mod