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