/[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 642978 - (show annotations)
Mon Mar 31 12:29:05 2008 UTC (19 months, 3 weeks ago) by niq
File MIME type: text/plain
File size: 104611 byte(s)
Update mod_include for ap_expr update
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 echo_text = ap_escape_html(ctx->dpool, val);
1196 break;
1197 }
1198
1199 e_len = strlen(echo_text);
1200 }
1201 else {
1202 echo_text = ctx->intern->undefined_echo;
1203 e_len = ctx->intern->undefined_echo_len;
1204 }
1205
1206 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(
1207 apr_pmemdup(ctx->pool, echo_text, e_len),
1208 e_len, ctx->pool, f->c->bucket_alloc));
1209 }
1210 else if (!strcmp(tag, "encoding")) {
1211 if (!strcasecmp(tag_val, "none")) {
1212 encode = E_NONE;
1213 }
1214 else if (!strcasecmp(tag_val, "url")) {
1215 encode = E_URL;
1216 }
1217 else if (!strcasecmp(tag_val, "entity")) {
1218 encode = E_ENTITY;
1219 }
1220 else {
1221 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1222 "\"%s\" to parameter \"encoding\" of tag echo in "
1223 "%s", tag_val, r->filename);
1224 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1225 break;
1226 }
1227 }
1228 else {
1229 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1230 "\"%s\" in tag echo of %s", tag, r->filename);
1231 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1232 break;
1233 }
1234 }
1235
1236 return APR_SUCCESS;
1237 }
1238
1239 /*
1240 * <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."]
1241 * [echomsg="..."] -->
1242 */
1243 static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
1244 apr_bucket_brigade *bb)
1245 {
1246 request_rec *r = f->r;
1247 apr_table_t *env = r->subprocess_env;
1248
1249 if (!ctx->argc) {
1250 ap_log_rerror(APLOG_MARK,
1251 (ctx->flags & SSI_FLAG_PRINTING)
1252 ? APLOG_ERR : APLOG_WARNING,
1253 0, r, "missing argument for config element in %s",
1254 r->filename);
1255 }
1256
1257 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1258 return APR_SUCCESS;
1259 }
1260
1261 if (!ctx->argc) {
1262 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1263 return APR_SUCCESS;
1264 }
1265
1266 while (1) {
1267 char *tag = NULL;
1268 char *tag_val = NULL;
1269
1270 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
1271 if (!tag || !tag_val) {
1272 break;
1273 }
1274
1275 if (!strcmp(tag, "errmsg")) {
1276 ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1277 SSI_EXPAND_DROP_NAME);
1278 }
1279 else if (!strcmp(tag, "echomsg")) {
1280 ctx->intern->undefined_echo =
1281 ap_ssi_parse_string(ctx, tag_val, NULL, 0,SSI_EXPAND_DROP_NAME);
1282 ctx->intern->undefined_echo_len=strlen(ctx->intern->undefined_echo);
1283 }
1284 else if (!strcmp(tag, "timefmt")) {
1285 apr_time_t date = r->request_time;
1286
1287 ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1288 SSI_EXPAND_DROP_NAME);
1289
1290 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
1291 ctx->time_str, 0));
1292 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
1293 ctx->time_str, 1));
1294 apr_table_setn(env, "LAST_MODIFIED",
1295 ap_ht_time(r->pool, r->finfo.mtime,
1296 ctx->time_str, 0));
1297 }
1298 else if (!strcmp(tag, "sizefmt")) {
1299 char *parsed_string;
1300
1301 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1302 SSI_EXPAND_DROP_NAME);
1303 if (!strcmp(parsed_string, "bytes")) {
1304 ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
1305 }
1306 else if (!strcmp(parsed_string, "abbrev")) {
1307 ctx->flags &= SSI_FLAG_SIZE_ABBREV;
1308 }
1309 else {
1310 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1311 "\"%s\" to parameter \"sizefmt\" of tag config "
1312 "in %s", parsed_string, r->filename);
1313 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1314 break;
1315 }
1316 }
1317 else {
1318 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1319 "\"%s\" to tag config in %s", tag, r->filename);
1320 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1321 break;
1322 }
1323 }
1324
1325 return APR_SUCCESS;
1326 }
1327
1328 /*
1329 * <!--#fsize virtual|file="..." [virtual|file="..."] ... -->
1330 */
1331 static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
1332 apr_bucket_brigade *bb)
1333 {
1334 request_rec *r = f->r;
1335
1336 if (!ctx->argc) {
1337 ap_log_rerror(APLOG_MARK,
1338 (ctx->flags & SSI_FLAG_PRINTING)
1339 ? APLOG_ERR : APLOG_WARNING,
1340 0, r, "missing argument for fsize element in %s",
1341 r->filename);
1342 }
1343
1344 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1345 return APR_SUCCESS;
1346 }
1347
1348 if (!ctx->argc) {
1349 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1350 return APR_SUCCESS;
1351 }
1352
1353 while (1) {
1354 char *tag = NULL;
1355 char *tag_val = NULL;
1356 apr_finfo_t finfo;
1357 char *parsed_string;
1358
1359 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1360 if (!tag || !tag_val) {
1361 break;
1362 }
1363
1364 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1365 SSI_EXPAND_DROP_NAME);
1366
1367 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
1368 char *buf;
1369 apr_size_t len;
1370
1371 if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
1372 buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5));
1373 len = 4; /* omit the \0 terminator */
1374 }
1375 else {
1376 apr_size_t l, x, pos;
1377 char *tmp;
1378
1379 tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size);
1380 len = l = strlen(tmp);
1381
1382 for (x = 0; x < l; ++x) {
1383 if (x && !((l - x) % 3)) {
1384 ++len;
1385 }
1386 }
1387
1388 if (len == l) {
1389 buf = apr_pstrmemdup(ctx->pool, tmp, len);
1390 }
1391 else {
1392 buf = apr_palloc(ctx->pool, len);
1393
1394 for (pos = x = 0; x < l; ++x) {
1395 if (x && !((l - x) % 3)) {
1396 buf[pos++] = ',';
1397 }
1398 buf[pos++] = tmp[x];
1399 }
1400 }
1401 }
1402
1403 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len,
1404 ctx->pool, f->c->bucket_alloc));
1405 }
1406 else {
1407 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1408 break;
1409 }
1410 }
1411
1412 return APR_SUCCESS;
1413 }
1414
1415 /*
1416 * <!--#flastmod virtual|file="..." [virtual|file="..."] ... -->
1417 */
1418 static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
1419 apr_bucket_brigade *bb)
1420 {
1421 request_rec *r = f->r;
1422
1423 if (!ctx->argc) {
1424 ap_log_rerror(APLOG_MARK,
1425 (ctx->flags & SSI_FLAG_PRINTING)
1426 ? APLOG_ERR : APLOG_WARNING,
1427 0, r, "missing argument for flastmod element in %s",
1428 r->filename);
1429 }
1430
1431 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1432 return APR_SUCCESS;
1433 }
1434
1435 if (!ctx->argc) {
1436 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1437 return APR_SUCCESS;
1438 }
1439
1440 while (1) {
1441 char *tag = NULL;
1442 char *tag_val = NULL;
1443 apr_finfo_t finfo;
1444 char *parsed_string;
1445
1446 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1447 if (!tag || !tag_val) {
1448 break;
1449 }
1450
1451 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1452 SSI_EXPAND_DROP_NAME);
1453
1454 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
1455 char *t_val;
1456 apr_size_t t_len;
1457
1458 t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0);
1459 t_len = strlen(t_val);
1460
1461 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len,
1462 ctx->pool, f->c->bucket_alloc));
1463 }
1464 else {
1465 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1466 break;
1467 }
1468 }
1469
1470 return APR_SUCCESS;
1471 }
1472
1473 /*
1474 * <!--#if expr="..." -->
1475 */
1476 static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f,
1477 apr_bucket_brigade *bb)
1478 {
1479 char *tag = NULL;
1480 char *expr = NULL;
1481 request_rec *r = f->r;
1482 int expr_ret, was_error;
1483
1484 if (ctx->argc != 1) {
1485 ap_log_rerror(APLOG_MARK,
1486 (ctx->flags & SSI_FLAG_PRINTING)
1487 ? APLOG_ERR : APLOG_WARNING,
1488 0, r, (ctx->argc)
1489 ? "too many arguments for if element in %s"
1490 : "missing expr argument for if element in %s",
1491 r->filename);
1492 }
1493
1494 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1495 ++(ctx->if_nesting_level);
1496 return APR_SUCCESS;
1497 }
1498
1499 if (ctx->argc != 1) {
1500 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1501 return APR_SUCCESS;
1502 }
1503
1504 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
1505
1506 if (strcmp(tag, "expr")) {
1507 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
1508 "to tag if in %s", tag, r->filename);
1509 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1510 return APR_SUCCESS;
1511 }
1512
1513 if (!expr) {
1514 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr value for if "
1515 "element in %s", r->filename);
1516 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1517 return APR_SUCCESS;
1518 }
1519
1520 DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr));
1521
1522 expr_ret = ap_expr_evalstring(r, expr, &was_error, &ctx->intern->re,
1523 ssi_parse_string, ctx->intern->access_func);
1524
1525 if (was_error) {
1526 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1527 return APR_SUCCESS;
1528 }
1529
1530 if (expr_ret) {
1531 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
1532 }
1533 else {
1534 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
1535 }
1536
1537 DEBUG_DUMP_COND(ctx, " if");
1538
1539 ctx->if_nesting_level = 0;
1540
1541 return APR_SUCCESS;
1542 }
1543
1544 /*
1545 * <!--#elif expr="..." -->
1546 */
1547 static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f,
1548 apr_bucket_brigade *bb)
1549 {
1550 char *tag = NULL;
1551 char *expr = NULL;
1552 request_rec *r = f->r;
1553 int expr_ret, was_error;
1554
1555 if (ctx->argc != 1) {
1556 ap_log_rerror(APLOG_MARK,
1557 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
1558 0, r, (ctx->argc)
1559 ? "too many arguments for if element in %s"
1560 : "missing expr argument for if element in %s",
1561 r->filename);
1562 }
1563
1564 if (ctx->if_nesting_level) {
1565 return APR_SUCCESS;
1566 }
1567
1568 if (ctx->argc != 1) {
1569 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1570 return APR_SUCCESS;
1571 }
1572
1573 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
1574
1575 if (strcmp(tag, "expr")) {
1576 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
1577 "to tag if in %s", tag, r->filename);
1578 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1579 return APR_SUCCESS;
1580 }
1581
1582 if (!expr) {
1583 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr in elif "
1584 "statement: %s", r->filename);
1585 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1586 return APR_SUCCESS;
1587 }
1588
1589 DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr));
1590 DEBUG_DUMP_COND(ctx, " elif");
1591
1592 if (ctx->flags & SSI_FLAG_COND_TRUE) {
1593 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
1594 return APR_SUCCESS;
1595 }
1596
1597 expr_ret = ap_expr_evalstring(r, expr, &was_error, &ctx->intern->re,
1598 ssi_parse_string, ctx->intern->access_func);
1599
1600 if (was_error) {
1601 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1602 return APR_SUCCESS;
1603 }
1604
1605 if (expr_ret) {
1606 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
1607 }
1608 else {
1609 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
1610 }
1611
1612 DEBUG_DUMP_COND(ctx, " elif");
1613
1614 return APR_SUCCESS;
1615 }
1616
1617 /*
1618 * <!--#else -->
1619 */
1620 static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f,
1621 apr_bucket_brigade *bb)
1622 {
1623 request_rec *r = f->r;
1624
1625 if (ctx->argc) {
1626 ap_log_rerror(APLOG_MARK,
1627 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
1628 0, r, "else directive does not take tags in %s",
1629 r->filename);
1630 }
1631
1632 if (ctx->if_nesting_level) {
1633 return APR_SUCCESS;
1634 }
1635
1636 if (ctx->argc) {
1637 if (ctx->flags & SSI_FLAG_PRINTING) {
1638 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1639 }
1640
1641 return APR_SUCCESS;
1642 }
1643
1644 DEBUG_DUMP_COND(ctx, " else");
1645
1646 if (ctx->flags & SSI_FLAG_COND_TRUE) {
1647 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
1648 }
1649 else {
1650 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
1651 }
1652
1653 return APR_SUCCESS;
1654 }
1655
1656 /*
1657 * <!--#endif -->
1658 */
1659 static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f,
1660 apr_bucket_brigade *bb)
1661 {
1662 request_rec *r = f->r;
1663
1664 if (ctx->argc) {
1665 ap_log_rerror(APLOG_MARK,
1666 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
1667 0, r, "endif directive does not take tags in %s",
1668 r->filename);
1669 }
1670
1671 if (ctx->if_nesting_level) {
1672 --(ctx->if_nesting_level);
1673 return APR_SUCCESS;
1674 }
1675
1676 if (ctx->argc) {
1677 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1678 return APR_SUCCESS;
1679 }
1680
1681 DEBUG_DUMP_COND(ctx, "endif");
1682
1683 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
1684
1685 return APR_SUCCESS;
1686 }
1687
1688 /*
1689 * <!--#set var="..." value="..." ... -->
1690 */
1691 static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
1692 apr_bucket_brigade *bb)
1693 {
1694 char *var = NULL;
1695 request_rec *r = f->r;
1696 request_rec *sub = r->main;
1697 apr_pool_t *p = r->pool;
1698
1699 if (ctx->argc < 2) {
1700 ap_log_rerror(APLOG_MARK,
1701 (ctx->flags & SSI_FLAG_PRINTING)
1702 ? APLOG_ERR : APLOG_WARNING,
1703 0, r, "missing argument for set element in %s",
1704 r->filename);
1705 }
1706
1707 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1708 return APR_SUCCESS;
1709 }
1710
1711 if (ctx->argc < 2) {
1712 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1713 return APR_SUCCESS;
1714 }
1715
1716 /* we need to use the 'main' request pool to set notes as that is
1717 * a notes lifetime
1718 */
1719 while (sub) {
1720 p = sub->pool;
1721 sub = sub->main;
1722 }
1723
1724 while (1) {
1725 char *tag = NULL;
1726 char *tag_val = NULL;
1727
1728 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1729
1730 if (!tag || !tag_val) {
1731 break;
1732 }
1733
1734 if (!strcmp(tag, "var")) {
1735 var = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1736 SSI_EXPAND_DROP_NAME);
1737 }
1738 else if (!strcmp(tag, "value")) {
1739 char *parsed_string;
1740
1741 if (!var) {
1742 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "variable must "
1743 "precede value in set directive in %s",
1744 r->filename);
1745 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1746 break;
1747 }
1748
1749 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1750 SSI_EXPAND_DROP_NAME);
1751 apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
1752 apr_pstrdup(p, parsed_string));
1753 }
1754 else {
1755 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Invalid tag for set "
1756 "directive in %s", r->filename);
1757 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1758 break;
1759 }
1760 }
1761
1762 return APR_SUCCESS;
1763 }
1764
1765 /*
1766 * <!--#printenv -->
1767 */
1768 static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
1769 apr_bucket_brigade *bb)
1770 {
1771 request_rec *r = f->r;
1772 const apr_array_header_t *arr;
1773 const apr_table_entry_t *elts;
1774 int i;
1775
1776 if (ctx->argc) {
1777 ap_log_rerror(APLOG_MARK,
1778 (ctx->flags & SSI_FLAG_PRINTING)
1779 ? APLOG_ERR : APLOG_WARNING,
1780 0, r, "printenv directive does not take tags in %s",
1781 r->filename);
1782 }
1783
1784 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1785 return APR_SUCCESS;
1786 }
1787
1788 if (ctx->argc) {
1789 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1790 return APR_SUCCESS;
1791 }
1792
1793 arr = apr_table_elts(r->subprocess_env);
1794 elts = (apr_table_entry_t *)arr->elts;
1795
1796 for (i = 0; i < arr->nelts; ++i) {
1797 const char *key_text, *val_text;
1798 char *key_val, *next;
1799 apr_size_t k_len, v_len, kv_length;
1800
1801 /* get key */
1802 key_text = ap_escape_html(ctx->dpool, elts[i].key);
1803 k_len = strlen(key_text);
1804
1805 /* get value */
1806 val_text = elts[i].val;
1807 if (val_text == LAZY_VALUE) {
1808 val_text = add_include_vars_lazy(r, elts[i].key);
1809 }
1810 val_text = ap_escape_html(ctx->dpool, elts[i].val);
1811 v_len = strlen(val_text);
1812
1813 /* assemble result */
1814 kv_length = k_len + v_len + sizeof("=\n");
1815 key_val = apr_palloc(ctx->pool, kv_length);
1816 next = key_val;
1817
1818 memcpy(next, key_text, k_len);
1819 next += k_len;
1820 *next++ = '=';
1821 memcpy(next, val_text, v_len);
1822 next += v_len;
1823 *next++ = '\n';
1824 *next = 0;
1825
1826 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(key_val, kv_length-1,
1827 ctx->pool, f->c->bucket_alloc));
1828 }
1829
1830 ctx->flush_now = 1;
1831 return APR_SUCCESS;
1832 }
1833
1834
1835 /*
1836 * +-------------------------------------------------------+
1837 * | |
1838 * | Main Includes-Filter Engine
1839 * | |
1840 * +-------------------------------------------------------+
1841 */
1842
1843 /* This is an implementation of the BNDM search algorithm.
1844 *
1845 * Fast and Flexible String Matching by Combining Bit-parallelism and
1846 * Suffix Automata (2001)
1847 * Gonzalo Navarro, Mathieu Raffinot
1848 *
1849 * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
1850 *
1851 * Initial code submitted by Sascha Schumann.
1852 */
1853
1854 /* Precompile the bndm_t data structure. */
1855 static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl)
1856 {
1857 unsigned int x;
1858 const char *ne = n + nl;
1859 bndm_t *t = apr_palloc(pool, sizeof(*t));
1860
1861 memset(t->T, 0, sizeof(unsigned int) * 256);
1862 t->pattern_len = nl;
1863
1864 for (x = 1; n < ne; x <<= 1) {
1865 t->T[(unsigned char) *n++] |= x;
1866 }
1867
1868 t->x = x - 1;
1869
1870 return t;
1871 }
1872
1873 /* Implements the BNDM search algorithm (as described above).
1874 *
1875 * h - the string to look in
1876 * hl - length of the string to look for
1877 * t - precompiled bndm structure against the pattern
1878 *
1879 * Returns the count of character that is the first match or hl if no
1880 * match is found.
1881 */
1882 static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl)
1883 {
1884 const char *skip;
1885 const char *he, *p, *pi;
1886 unsigned int *T, x, d;
1887 apr_size_t nl;
1888
1889 he = h + hl;
1890
1891 T = t->T;
1892 x = t->x;
1893 nl = t->pattern_len;
1894
1895 pi = h - 1; /* pi: p initial */
1896 p = pi + nl; /* compare window right to left. point to the first char */
1897
1898 while (p < he) {
1899 skip = p;
1900 d = x;
1901 do {
1902 d &= T[(unsigned char) *p--];
1903 if (!d) {
1904 break;
1905 }
1906 if ((d & 1)) {
1907 if (p != pi) {
1908 skip = p;
1909 }
1910 else {
1911 return p - h + 1;
1912 }
1913 }
1914 d >>= 1;
1915 } while (d);
1916
1917 pi = skip;
1918 p = pi + nl;
1919 }
1920
1921 return hl;
1922 }
1923
1924 /*
1925 * returns the index position of the first byte of start_seq (or the len of
1926 * the buffer as non-match)
1927 */
1928 static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data,
1929 apr_size_t len)
1930 {
1931 struct ssi_internal_ctx *intern = ctx->intern;
1932 apr_size_t slen = intern->start_seq_pat->pattern_len;
1933 apr_size_t index;
1934 const char *p, *ep;
1935
1936 if (len < slen) {
1937 p = data; /* try partial match at the end of the buffer (below) */
1938 }
1939 else {
1940 /* try fast bndm search over the buffer
1941 * (hopefully the whole start sequence can be found in this buffer)
1942 */
1943 index = bndm(intern->start_seq_pat, data, len);
1944
1945 /* wow, found it. ready. */
1946 if (index < len) {
1947 intern->state = PARSE_DIRECTIVE;
1948 return index;
1949 }
1950 else {
1951 /* ok, the pattern can't be found as whole in the buffer,
1952 * check the end for a partial match
1953 */
1954 p = data + len - slen + 1;
1955 }
1956 }
1957
1958 ep = data + len;
1959 do {
1960 while (p < ep && *p != *intern->start_seq) {
1961 ++p;
1962 }
1963
1964 index = p - data;
1965
1966 /* found a possible start_seq start */
1967 if (p < ep) {
1968 apr_size_t pos = 1;
1969
1970 ++p;
1971 while (p < ep && *p == intern->start_seq[pos]) {
1972 ++p;
1973 ++pos;
1974 }
1975
1976 /* partial match found. Store the info for the next round */
1977 if (p == ep) {
1978 intern->state = PARSE_HEAD;
1979 intern->parse_pos = pos;
1980 return index;
1981 }
1982 }
1983
1984 /* we must try all combinations; consider (e.g.) SSIStartTag "--->"
1985 * and a string data of "--.-" and the end of the buffer
1986 */
1987 p = data + index + 1;
1988 } while (p < ep);
1989
1990 /* no match */
1991 return len;
1992 }
1993
1994 /*
1995 * returns the first byte *after* the partial (or final) match.
1996 *
1997 * If we had to trick with the start_seq start, 'release' returns the
1998 * number of chars of the start_seq which appeared not to be part of a
1999 * full tag and may have to be passed down the filter chain.
2000 */
2001 static apr_size_t find_partial_start_sequence(include_ctx_t *ctx,
2002 const char *data,
2003 apr_size_t len,
2004 apr_size_t *release)
2005 {
2006 struct ssi_internal_ctx *intern = ctx->intern;
2007 apr_size_t pos, spos = 0;
2008 apr_size_t slen = intern->start_seq_pat->pattern_len;
2009 const char *p, *ep;
2010
2011 pos = intern->parse_pos;
2012 ep = data + len;
2013 *release = 0;
2014
2015 do {
2016 p = data;
2017
2018 while (p < ep && pos < slen && *p == intern->start_seq[pos]) {
2019 ++p;
2020 ++pos;
2021 }
2022
2023 /* full match */
2024 if (pos == slen) {
2025 intern->state = PARSE_DIRECTIVE;
2026 return (p - data);
2027 }
2028
2029 /* the whole buffer is a partial match */
2030 if (p == ep) {
2031 intern->parse_pos = pos;
2032 return (p - data);
2033 }
2034
2035 /* No match so far, but again:
2036 * We must try all combinations, since the start_seq is a random
2037 * user supplied string
2038 *
2039 * So: look if the first char of start_seq appears somewhere within
2040 * the current partial match. If it does, try to start a match that
2041 * begins with this offset. (This can happen, if a strange
2042 * start_seq like "---->" spans buffers)
2043 */
2044 if (spos < intern->parse_pos) {
2045 do {
2046 ++spos;
2047 ++*release;
2048 p = intern->start_seq + spos;
2049 pos = intern->parse_pos - spos;
2050
2051 while (pos && *p != *intern->start_seq) {
2052 ++p;
2053 ++spos;
2054 ++*release;
2055 --pos;
2056 }
2057
2058 /* if a matching beginning char was found, try to match the
2059 * remainder of the old buffer.
2060 */
2061 if (pos > 1) {
2062 apr_size_t t = 1;
2063
2064 ++p;
2065 while (t < pos && *p == intern->start_seq[t]) {
2066 ++p;
2067 ++t;
2068 }
2069
2070 if (t == pos) {
2071 /* yeah, another partial match found in the *old*
2072 * buffer, now test the *current* buffer for
2073 * continuing match
2074 */
2075 break;
2076 }
2077 }
2078 } while (pos > 1);
2079
2080 if (pos) {
2081 continue;
2082 }
2083 }
2084
2085 break;
2086 } while (1); /* work hard to find a match ;-) */
2087
2088 /* no match at all, release all (wrongly) matched chars so far */
2089 *release = intern->parse_pos;
2090 intern->state = PARSE_PRE_HEAD;
2091 return 0;
2092 }
2093
2094 /*
2095 * returns the position after the directive
2096 */
2097 static apr_size_t find_directive(include_ctx_t *ctx, const char *data,
2098 apr_size_t len, char ***store,
2099 apr_size_t **store_len)
2100 {
2101 struct ssi_internal_ctx *intern = ctx->intern;
2102 const char *p = data;
2103 const char *ep = data + len;
2104 apr_size_t pos;
2105
2106 switch (intern->state) {
2107 case PARSE_DIRECTIVE:
2108 while (p < ep && !apr_isspace(*p)) {
2109 /* we have to consider the case of missing space between directive
2110 * and end_seq (be somewhat lenient), e.g. <!--#printenv-->
2111 */
2112 if (*p == *intern->end_seq) {
2113 intern->state = PARSE_DIRECTIVE_TAIL;
2114 intern->parse_pos = 1;
2115 ++p;
2116 return (p - data);
2117 }
2118 ++p;
2119 }
2120
2121 if (p < ep) { /* found delimiter whitespace */
2122 intern->state = PARSE_DIRECTIVE_POSTNAME;
2123 *store = &intern->directive;
2124 *store_len = &intern->directive_len;
2125 }
2126
2127 break;
2128
2129 case PARSE_DIRECTIVE_TAIL:
2130 pos = intern->parse_pos;
2131
2132 while (p < ep && pos < intern->end_seq_len &&
2133 *p == intern->end_seq[pos]) {
2134 ++p;
2135 ++pos;
2136 }
2137
2138 /* full match, we're done */
2139 if (pos == intern->end_seq_len) {
2140 intern->state = PARSE_DIRECTIVE_POSTTAIL;
2141 *store = &intern->directive;
2142 *store_len = &intern->directive_len;
2143 break;
2144 }
2145
2146 /* partial match, the buffer is too small to match fully */
2147 if (p == ep) {
2148 intern->parse_pos = pos;
2149 break;
2150 }
2151
2152 /* no match. continue normal parsing */
2153 intern->state = PARSE_DIRECTIVE;
2154 return 0;
2155
2156 case PARSE_DIRECTIVE_POSTTAIL:
2157 intern->state = PARSE_EXECUTE;
2158 intern->directive_len -= intern->end_seq_len;
2159 /* continue immediately with the next state */
2160
2161 case PARSE_DIRECTIVE_POSTNAME:
2162 if (PARSE_DIRECTIVE_POSTNAME == intern->state) {
2163 intern->state = PARSE_PRE_ARG;
2164 }
2165 ctx->argc = 0;
2166 intern->argv = NULL;
2167
2168 if (!intern->directive_len) {
2169 intern->error = 1;
2170 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2171 "directive name in parsed document %s",
2172 intern->r->filename);
2173 }
2174 else {
2175 char *sp = intern->directive;
2176 char *sep = intern->directive + intern->directive_len;
2177
2178 /* normalize directive name */
2179 for (; sp < sep; ++sp) {
2180 *sp = apr_tolower(*sp);
2181 }
2182 }
2183
2184 return 0;
2185
2186 default:
2187 /* get a rid of a gcc warning about unhandled enumerations */
2188 break;
2189 }
2190
2191 return (p - data);
2192 }
2193
2194 /*
2195 * find out whether the next token is (a possible) end_seq or an argument
2196 */
2197 static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data,
2198 apr_size_t len)
2199 {
2200 struct ssi_internal_ctx *intern = ctx->intern;
2201 const char *p = data;
2202 const char *ep = data + len;
2203
2204 /* skip leading WS */
2205 while (p < ep && apr_isspace(*p)) {
2206 ++p;
2207 }
2208
2209 /* buffer doesn't consist of whitespaces only */
2210 if (p < ep) {
2211 intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG;
2212 }
2213
2214 return (p - data);
2215 }
2216
2217 /*
2218 * test the stream for end_seq. If it doesn't match at all, it must be an
2219 * argument
2220 */
2221 static apr_size_t find_tail(include_ctx_t *ctx, const char *data,
2222 apr_size_t len)
2223 {
2224 struct ssi_internal_ctx *intern = ctx->intern;
2225 const char *p = data;
2226 const char *ep = data + len;
2227 apr_size_t pos = intern->parse_pos;
2228
2229 if (PARSE_TAIL == intern->state) {
2230 intern->state = PARSE_TAIL_SEQ;
2231 pos = intern->parse_pos = 0;
2232 }
2233
2234 while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) {
2235 ++p;
2236 ++pos;
2237 }
2238
2239 /* bingo, full match */
2240 if (pos == intern->end_seq_len) {
2241 intern->state = PARSE_EXECUTE;
2242 return (p - data);
2243 }
2244
2245 /* partial match, the buffer is too small to match fully */
2246 if (p == ep) {
2247 intern->parse_pos = pos;
2248 return (p - data);
2249 }
2250
2251 /* no match. It must be an argument string then
2252 * The caller should cleanup and rewind to the reparse point
2253 */
2254 intern->state = PARSE_ARG;
2255 return 0;
2256 }
2257
2258 /*
2259 * extract name=value from the buffer
2260 * A pcre-pattern could look (similar to):
2261 * name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
2262 */
2263 static apr_size_t find_argument(include_ctx_t *ctx, const char *data,
2264 apr_size_t len, char ***store,
2265 apr_size_t **store_len)
2266 {
2267 struct ssi_internal_ctx *intern = ctx->intern;
2268 const char *p = data;
2269 const char *ep = data + len;
2270
2271 switch (intern->state) {
2272 case PARSE_ARG:
2273 /*
2274 * create argument structure and append it to the current list
2275 */
2276 intern->current_arg = apr_palloc(ctx->dpool,
2277 sizeof(*intern->current_arg));
2278 intern->current_arg->next = NULL;
2279
2280 ++(ctx->argc);
2281 if (!intern->argv) {
2282 intern->argv = intern->current_arg;
2283 }
2284 else {
2285 arg_item_t *newarg = intern->argv;
2286
2287 while (newarg->next) {
2288 newarg = newarg->next;
2289 }
2290 newarg->next = intern->current_arg;
2291 }
2292
2293 /* check whether it's a valid one. If it begins with a quote, we
2294 * can safely assume, someone forgot the name of the argument
2295 */
2296 switch (*p) {
2297 case '"': case '\'': case '`':
2298 *store = NULL;
2299
2300 intern->state = PARSE_ARG_VAL;
2301 intern->quote = *p++;
2302 intern->current_arg->name = NULL;
2303 intern->current_arg->name_len = 0;
2304 intern->error = 1;
2305
2306 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2307 "argument name for value to tag %s in %s",
2308 apr_pstrmemdup(intern->r->pool, intern->directive,
2309 intern->directive_len),
2310 intern->r->filename);
2311
2312 return (p - data);
2313
2314 default:
2315 intern->state = PARSE_ARG_NAME;
2316 }
2317 /* continue immediately with next state */
2318
2319 case PARSE_ARG_NAME:
2320 while (p < ep && !apr_isspace(*p) && *p != '=') {
2321 ++p;
2322 }
2323
2324 if (p < ep) {
2325 intern->state = PARSE_ARG_POSTNAME;
2326 *store = &intern->current_arg->name;
2327 *store_len = &intern->current_arg->name_len;
2328 return (p - data);
2329 }
2330 break;
2331
2332 case PARSE_ARG_POSTNAME:
2333 intern->current_arg->name = apr_pstrmemdup(ctx->dpool,
2334 intern->current_arg->name,
2335 intern->current_arg->name_len);
2336 if (!intern->current_arg->name_len) {
2337 intern->error = 1;
2338 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2339 "argument name for value to tag %s in %s",
2340 apr_pstrmemdup(intern->r->pool, intern->directive,
2341 intern->directive_len),
2342 intern->r->filename);
2343 }
2344 else {
2345 char *sp = intern->current_arg->name;
2346
2347 /* normalize the name */
2348 while (*sp) {
2349 *sp = apr_tolower(*sp);
2350 ++sp;
2351 }
2352 }
2353
2354 intern->state = PARSE_ARG_EQ;
2355 /* continue with next state immediately */
2356
2357 case PARSE_ARG_EQ:
2358 *store = NULL;
2359
2360 while (p < ep && apr_isspace(*p)) {
2361 ++p;
2362 }
2363
2364 if (p < ep) {
2365 if (*p == '=') {
2366 intern->state = PARSE_ARG_PREVAL;
2367 ++p;
2368 }
2369 else { /* no value */
2370 intern->current_arg->value = NULL;
2371 intern->state = PARSE_PRE_ARG;
2372 }
2373
2374 return (p - data);
2375 }
2376 break;
2377
2378 case PARSE_ARG_PREVAL:
2379 *store = NULL;
2380
2381 while (p < ep && apr_isspace(*p)) {
2382 ++p;
2383 }
2384
2385 /* buffer doesn't consist of whitespaces only */
2386 if (p < ep) {
2387 intern->state = PARSE_ARG_VAL;
2388 switch (*p) {
2389 case '"': case '\'': case '`':
2390 intern->quote = *p++;
2391 break;
2392 default:
2393 intern->quote = '\0';
2394 break;
2395 }
2396
2397 return (p - data);
2398 }
2399 break;
2400
2401 case PARSE_ARG_VAL_ESC:
2402 if (*p == intern->quote) {
2403 ++p;
2404 }
2405 intern->state = PARSE_ARG_VAL;
2406 /* continue with next state immediately */
2407
2408 case PARSE_ARG_VAL:
2409 for (; p < ep; ++p) {
2410 if (intern->quote && *p == '\\') {
2411 ++p;
2412 if (p == ep) {
2413 intern->state = PARSE_ARG_VAL_ESC;
2414 break;
2415 }
2416
2417 if (*p != intern->quote) {
2418 --p;
2419 }
2420 }
2421 else if (intern->quote && *p == intern->quote) {
2422 ++p;
2423 *store = &intern->current_arg->value;
2424 *store_len = &intern->current_arg->value_len;
2425 intern->state = PARSE_ARG_POSTVAL;
2426 break;
2427 }
2428 else if (!intern->quote && apr_isspace(*p)) {
2429 ++p;
2430 *store = &intern->current_arg->value;
2431 *store_len = &intern->current_arg->value_len;
2432 intern->state = PARSE_ARG_POSTVAL;
2433 break;
2434 }
2435 }
2436
2437 return (p - data);
2438
2439 case PARSE_ARG_POSTVAL:
2440 /*
2441 * The value is still the raw input string. Finally clean it up.
2442 */
2443 --(intern->current_arg->value_len);
2444
2445 /* strip quote escaping \ from the string */
2446 if (intern->quote) {
2447 apr_size_t shift = 0;
2448 char *sp;
2449
2450 sp = intern->current_arg->value;
2451 ep = intern->current_arg->value + intern->current_arg->value_len;
2452 while (sp < ep && *sp != '\\') {
2453 ++sp;
2454 }
2455 for (; sp < ep; ++sp) {
2456 if (*sp == '\\' && sp[1] == intern->quote) {
2457 ++sp;
2458 ++shift;
2459 }
2460 if (shift) {
2461 *(sp-shift) = *sp;
2462 }
2463 }
2464
2465 intern->current_arg->value_len -= shift;
2466 }
2467
2468 intern->current_arg->value[intern->current_arg->value_len] = '\0';
2469 intern->state = PARSE_PRE_ARG;
2470
2471 return 0;
2472
2473 default:
2474 /* get a rid of a gcc warning about unhandled enumerations */
2475 break;
2476 }
2477
2478 return len; /* partial match of something */
2479 }
2480
2481 /*
2482 * This is the main loop over the current bucket brigade.
2483 */
2484 static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
2485 {
2486 include_ctx_t *ctx = f->ctx;
2487 struct ssi_internal_ctx *intern = ctx->intern;
2488 request_rec *r = f->r;
2489 apr_bucket *b = APR_BRIGADE_FIRST(bb);
2490 apr_bucket_brigade *pass_bb;
2491 apr_status_t rv = APR_SUCCESS;
2492 char *magic; /* magic pointer for sentinel use */
2493
2494 /* fast exit */
2495 if (APR_BRIGADE_EMPTY(bb)) {
2496 return APR_SUCCESS;
2497 }
2498
2499 /* we may crash, since already cleaned up; hand over the responsibility
2500 * to the next filter;-)
2501 */
2502 if (intern->seen_eos) {
2503 return ap_pass_brigade(f->next, bb);
2504 }
2505
2506 /* All stuff passed along has to be put into that brigade */
2507 pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
2508
2509 /* initialization for this loop */
2510 intern->bytes_read = 0;
2511 intern->error = 0;
2512 intern->r = r;
2513 ctx->flush_now = 0;
2514
2515 /* loop over the current bucket brigade */
2516 while (b != APR_BRIGADE_SENTINEL(bb)) {
2517 const char *data = NULL;
2518 apr_size_t len, index, release;
2519 apr_bucket *newb = NULL;
2520 char **store = &magic;
2521 apr_size_t *store_len = NULL;
2522
2523 /* handle meta buckets before reading any data */
2524 if (APR_BUCKET_IS_METADATA(b)) {
2525 newb = APR_BUCKET_NEXT(b);
2526
2527 APR_BUCKET_REMOVE(b);
2528
2529 if (APR_BUCKET_IS_EOS(b)) {
2530 intern->seen_eos = 1;
2531
2532 /* Hit end of stream, time for cleanup ... But wait!
2533 * Perhaps we're not ready yet. We may have to loop one or
2534 * two times again to finish our work. In that case, we
2535 * just re-insert the EOS bucket to allow for an extra loop.
2536 *
2537 * PARSE_EXECUTE means, we've hit a directive just before the
2538 * EOS, which is now waiting for execution.
2539 *
2540 * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
2541 * no argument and no space between directive and end_seq
2542 * just before the EOS. (consider <!--#printenv--> as last
2543 * or only string within the stream). This state, however,
2544 * just cleans up and turns itself to PARSE_EXECUTE, which
2545 * will be passed through within the next (and actually
2546 * last) round.
2547 */
2548 if (PARSE_EXECUTE == intern->state ||
2549 PARSE_DIRECTIVE_POSTTAIL == intern->state) {
2550 APR_BUCKET_INSERT_BEFORE(newb, b);
2551 }
2552 else {
2553 break; /* END OF STREAM */
2554 }
2555 }
2556 else {
2557 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
2558
2559 if (APR_BUCKET_IS_FLUSH(b)) {
2560 ctx->flush_now = 1;
2561 }
2562
2563 b = newb;
2564 continue;
2565 }
2566 }
2567
2568 /* enough is enough ... */
2569 if (ctx->flush_now ||
2570 intern->bytes_read > AP_MIN_BYTES_TO_WRITE) {
2571
2572 if (!APR_BRIGADE_EMPTY(pass_bb)) {
2573 rv = ap_pass_brigade(f->next, pass_bb);
2574 if (rv != APR_SUCCESS) {
2575 apr_brigade_destroy(pass_bb);
2576 return rv;
2577 }
2578 }
2579
2580 ctx->flush_now = 0;
2581 intern->bytes_read = 0;
2582 }
2583
2584 /* read the current bucket data */
2585 len = 0;
2586 if (!intern->seen_eos) {
2587 if (intern->bytes_read > 0) {
2588 rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
2589 if (APR_STATUS_IS_EAGAIN(rv)) {
2590 ctx->flush_now = 1;
2591 continue;
2592 }
2593 }
2594
2595 if (!len || rv != APR_SUCCESS) {
2596 rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
2597 }
2598
2599 if (rv != APR_SUCCESS) {
2600 apr_brigade_destroy(pass_bb);
2601 return rv;
2602 }
2603
2604 intern->bytes_read += len;
2605 }
2606
2607 /* zero length bucket, fetch next one */
2608 if (!len && !intern->seen_eos) {
2609 b = APR_BUCKET_NEXT(b);
2610 continue;
2611 }
2612
2613 /*
2614 * it's actually a data containing bucket, start/continue parsing
2615 */
2616
2617 switch (intern->state) {
2618 /* no current tag; search for start sequence */
2619 case PARSE_PRE_HEAD:
2620 index = find_start_sequence(ctx, data, len);
2621
2622 if (index < len) {
2623 apr_bucket_split(b, index);
2624 }
2625
2626 newb = APR_BUCKET_NEXT(b);
2627 if (ctx->flags & SSI_FLAG_PRINTING) {
2628 APR_BUCKET_REMOVE(b);
2629 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
2630 }
2631 else {
2632 apr_bucket_delete(b);
2633 }
2634
2635 if (index < len) {
2636 /* now delete the start_seq stuff from the remaining bucket */
2637 if (PARSE_DIRECTIVE == intern->state) { /* full match */
2638 apr_bucket_split(newb, intern->start_seq_pat->pattern_len);
2639 ctx->flush_now = 1; /* pass pre-tag stuff */
2640 }
2641
2642 b = APR_BUCKET_NEXT(newb);
2643 apr_bucket_delete(newb);
2644 }
2645 else {
2646 b = newb;
2647 }
2648
2649 break;
2650
2651 /* we're currently looking for the end of the start sequence */
2652 case PARSE_HEAD:
2653 index = find_partial_start_sequence(ctx, data, len, &release);
2654
2655 /* check if we mismatched earlier and have to release some chars */
2656 if (release && (ctx->flags & SSI_FLAG_PRINTING)) {
2657 char *to_release = apr_pmemdup(ctx->pool, intern->start_seq, release);
2658
2659 newb = apr_bucket_pool_create(to_release, release, ctx->pool,
2660 f->c->bucket_alloc);
2661 APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
2662 }
2663
2664 if (index) { /* any match */
2665 /* now delete the start_seq stuff from the remaining bucket */
2666 if (PARSE_DIRECTIVE == intern->state) { /* final match */
2667 apr_bucket_split(b, index);
2668 ctx->flush_now = 1; /* pass pre-tag stuff */
2669 }
2670 newb = APR_BUCKET_NEXT(b);
2671 apr_bucket_delete(b);
2672 b = newb;
2673 }
2674
2675 break;
2676
2677 /* we're currently grabbing the directive name */
2678 case PARSE_DIRECTIVE:
2679 case PARSE_DIRECTIVE_POSTNAME:
2680 case PARSE_DIRECTIVE_TAIL:
2681 case PARSE_DIRECTIVE_POSTTAIL:
2682 index = find_directive(ctx, data, len, &store, &store_len);
2683
2684 if (index) {
2685 apr_bucket_split(b, index);
2686 newb = APR_BUCKET_NEXT(b);
2687 }
2688
2689 if (store) {
2690 if (index) {
2691 APR_BUCKET_REMOVE(b);
2692 apr_bucket_setaside(b, r->pool);
2693 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
2694 b = newb;
2695 }
2696
2697 /* time for cleanup? */
2698 if (store != &magic) {
2699 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
2700 ctx->dpool);
2701 apr_brigade_cleanup(intern->tmp_bb);
2702 }
2703 }
2704 else if (index) {
2705 apr_bucket_delete(b);
2706 b = newb;
2707 }
2708
2709 break;
2710
2711 /* skip WS and find out what comes next (arg or end_seq) */
2712 case PARSE_PRE_ARG:
2713 index = find_arg_or_tail(ctx, data, len);
2714
2715 if (index) { /* skipped whitespaces */
2716 if (index < len) {
2717 apr_bucket_split(b, index);
2718 }
2719 newb = APR_BUCKET_NEXT(b);
2720 apr_bucket_delete(b);
2721 b = newb;
2722 }
2723
2724 break;
2725
2726 /* currently parsing name[=val] */
2727 case PARSE_ARG:
2728 case PARSE_ARG_NAME:
2729 case PARSE_ARG_POSTNAME:
2730 case PARSE_ARG_EQ:
2731 case PARSE_ARG_PREVAL:
2732 case PARSE_ARG_VAL:
2733 case PARSE_ARG_VAL_ESC:
2734 case PARSE_ARG_POSTVAL:
2735 index = find_argument(ctx, data, len, &store, &store_len);
2736
2737 if (index) {
2738 apr_bucket_split(b, index);
2739 newb = APR_BUCKET_NEXT(b);
2740 }
2741
2742 if (store) {
2743 if (index) {
2744 APR_BUCKET_REMOVE(b);
2745 apr_bucket_setaside(b, r->pool);
2746 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
2747 b = newb;
2748 }
2749
2750 /* time for cleanup? */
2751 if (store != &magic) {
2752 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
2753 ctx->dpool);
2754 apr_brigade_cleanup(intern->tmp_bb);
2755 }
2756 }
2757 else if (index) {
2758 apr_bucket_delete(b);
2759 b = newb;
2760 }
2761
2762 break;
2763
2764 /* try to match end_seq at current pos. */
2765 case PARSE_TAIL:
2766 case PARSE_TAIL_SEQ:
2767 index = find_tail(ctx, data, len);
2768
2769 switch (intern->state) {
2770 case PARSE_EXECUTE: /* full match */
2771 apr_bucket_split(b, index);
2772 newb = APR_BUCKET_NEXT(b);
2773 apr_bucket_delete(b);
2774 b = newb;
2775 break;
2776
2777 case PARSE_ARG: /* no match */
2778 /* PARSE_ARG must reparse at the beginning */
2779 APR_BRIGADE_PREPEND(bb, intern->tmp_bb);
2780 b = APR_BRIGADE_FIRST(bb);
2781 break;
2782
2783 default: /* partial match */
2784 newb = APR_BUCKET_NEXT(b);
2785 APR_BUCKET_REMOVE(b);
2786 apr_bucket_setaside(b, r->pool);
2787 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
2788 b = newb;
2789 break;
2790 }
2791
2792 break;
2793
2794 /* now execute the parsed directive, cleanup the space and
2795 * start again with PARSE_PRE_HEAD
2796 */
2797 case PARSE_EXECUTE:
2798 /* if there was an error, it was already logged; just stop here */
2799 if (intern->error) {
2800 if (ctx->flags & SSI_FLAG_PRINTING) {
2801 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
2802 intern->error = 0;
2803 }
2804 }
2805 else {
2806 include_handler_fn_t *handle_func;
2807
2808 handle_func =
2809 (include_handler_fn_t *)apr_hash_get(include_handlers, intern->directive,
2810 intern->directive_len);
2811
2812 if (handle_func) {
2813 DEBUG_INIT(ctx, f, pass_bb);
2814 rv = handle_func(ctx, f, pass_bb);
2815 if (rv != APR_SUCCESS) {
2816 apr_brigade_destroy(pass_bb);
2817 return rv;
2818 }
2819 }
2820 else {
2821 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2822 "unknown directive \"%s\" in parsed doc %s",
2823 apr_pstrmemdup(r->pool, intern->directive,
2824 intern->directive_len),
2825 r->filename);
2826 if (ctx->flags & SSI_FLAG_PRINTING) {
2827 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
2828 }
2829 }
2830 }
2831
2832 /* cleanup */
2833 apr_pool_clear(ctx->dpool);
2834 apr_brigade_cleanup(intern->tmp_bb);
2835
2836 /* Oooof. Done here, start next round */
2837 intern->state = PARSE_PRE_HEAD;
2838 break;
2839
2840 } /* switch(ctx->state) */
2841
2842 } /* while(brigade) */
2843
2844 /* End of stream. Final cleanup */
2845 if (intern->seen_eos) {
2846 if (PARSE_HEAD == intern->state) {
2847 if (ctx->flags & SSI_FLAG_PRINTING) {
2848 char *to_release = apr_pmemdup(ctx->pool, intern->start_seq,
2849 intern->parse_pos);
2850
2851 APR_BRIGADE_INSERT_TAIL(pass_bb,
2852 apr_bucket_pool_create(to_release,
2853 intern->parse_pos, ctx->pool,
2854 f->c->bucket_alloc));
2855 }
2856 }
2857 else if (PARSE_PRE_HEAD != intern->state) {
2858 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2859 "SSI directive was not properly finished at the end "
2860 "of parsed document %s", r->filename);
2861 if (ctx->flags & SSI_FLAG_PRINTING) {
2862 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
2863 }
2864 }
2865
2866 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2867 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
2868 "missing closing endif directive in parsed document"
2869 " %s", r->filename);
2870 }
2871
2872 /* cleanup our temporary memory */
2873 apr_brigade_destroy(intern->tmp_bb);
2874 apr_pool_destroy(ctx->dpool);
2875
2876 /* don't forget to finally insert the EOS bucket */
2877 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
2878 }
2879
2880 /* if something's left over, pass it along */
2881 if (!APR_BRIGADE_EMPTY(pass_bb)) {
2882 rv = ap_pass_brigade(f->next, pass_bb);
2883 }
2884 else {
2885 rv = APR_SUCCESS;
2886 apr_brigade_destroy(pass_bb);
2887 }
2888 return rv;
2889 }
2890
2891
2892 /*
2893 * +-------------------------------------------------------+
2894 * | |
2895 * | Runtime Hooks
2896 * | |
2897 * +-------------------------------------------------------+
2898 */
2899
2900 static int includes_setup(ap_filter_t *f)
2901 {
2902 include_dir_config *conf = ap_get_module_config(f->r->per_dir_config,
2903 &include_module);
2904
2905 /* When our xbithack value isn't set to full or our platform isn't
2906 * providing group-level protection bits or our group-level bits do not
2907 * have group-execite on, we will set the no_local_copy value to 1 so
2908 * that we will not send 304s.
2909 */
2910 if ((conf->xbithack != XBITHACK_FULL)
2911 || !(f->r->finfo.valid & APR_FINFO_GPROT)
2912 || !(f->r->finfo.protection & APR_GEXECUTE)) {
2913 f->r->no_local_copy = 1;
2914 }
2915
2916 /* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4.
2917 * We don't know if we are going to be including a file or executing
2918 * a program - in either case a strong ETag header will likely be invalid.
2919 */
2920 apr_table_setn(f->r->notes, "no-etag", "");
2921
2922 return OK;
2923 }
2924
2925 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
2926 {
2927 request_rec *r = f->r;
2928 include_ctx_t *ctx = f->ctx;
2929 include_dir_config *conf = ap_get_module_config(r->per_dir_config,
2930 &include_module);
2931
2932 include_server_config *sconf= ap_get_module_config(r->server->module_config,
2933 &include_module);
2934
2935