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 /* conditional expression parser stuff */
70 typedef enum {
71 TOKEN_STRING,
72 TOKEN_RE,
73 TOKEN_AND,
74 TOKEN_OR,
75 TOKEN_NOT,
76 TOKEN_EQ,
77 TOKEN_NE,
78 TOKEN_RBRACE,
79 TOKEN_LBRACE,
80 TOKEN_GROUP,
81 TOKEN_GE,
82 TOKEN_LE,
83 TOKEN_GT,
84 TOKEN_LT,
85 TOKEN_ACCESS
86 } token_type_t;
87
88 typedef struct {
89 token_type_t type;
90 const char *value;
91 #ifdef DEBUG_INCLUDE
92 const char *s;
93 #endif
94 } token_t;
95
96 typedef struct parse_node {
97 struct parse_node *parent;
98 struct parse_node *left;
99 struct parse_node *right;
100 token_t token;
101 int value;
102 int done;
103 #ifdef DEBUG_INCLUDE
104 int dump_done;
105 #endif
106 } parse_node_t;
107
108 typedef enum {
109 XBITHACK_OFF,
110 XBITHACK_ON,
111 XBITHACK_FULL,
112 XBITHACK_UNSET
113 } xbithack_t;
114
115 typedef struct {
116 const char *default_error_msg;
117 const char *default_time_fmt;
118 const char *undefined_echo;
119 xbithack_t xbithack;
120 signed char lastmodified;
121 signed char etag;
122 signed char legacy_expr;
123 } include_dir_config;
124
125 typedef struct {
126 const char *default_start_tag;
127 const char *default_end_tag;
128 } include_server_config;
129
130 /* main parser states */
131 typedef enum {
132 PARSE_PRE_HEAD,
133 PARSE_HEAD,
134 PARSE_DIRECTIVE,
135 PARSE_DIRECTIVE_POSTNAME,
136 PARSE_DIRECTIVE_TAIL,
137 PARSE_DIRECTIVE_POSTTAIL,
138 PARSE_PRE_ARG,
139 PARSE_ARG,
140 PARSE_ARG_NAME,
141 PARSE_ARG_POSTNAME,
142 PARSE_ARG_EQ,
143 PARSE_ARG_PREVAL,
144 PARSE_ARG_VAL,
145 PARSE_ARG_VAL_ESC,
146 PARSE_ARG_POSTVAL,
147 PARSE_TAIL,
148 PARSE_TAIL_SEQ,
149 PARSE_EXECUTE
150 } parse_state_t;
151
152 typedef struct arg_item {
153 struct arg_item *next;
154 char *name;
155 apr_size_t name_len;
156 char *value;
157 apr_size_t value_len;
158 } arg_item_t;
159
160 typedef struct {
161 const char *source;
162 const char *rexp;
163 apr_size_t nsub;
164 ap_regmatch_t match[AP_MAX_REG_MATCH];
165 int have_match;
166 } backref_t;
167
168 typedef struct {
169 unsigned int T[256];
170 unsigned int x;
171 apr_size_t pattern_len;
172 } bndm_t;
173
174 struct ssi_internal_ctx {
175 parse_state_t state;
176 int seen_eos;
177 int error;
178 char quote; /* quote character value (or \0) */
179 apr_size_t parse_pos; /* parse position of partial matches */
180 apr_size_t bytes_read;
181
182 apr_bucket_brigade *tmp_bb;
183
184 const char *start_seq;
185 bndm_t *start_seq_pat;
186 const char *end_seq;
187 apr_size_t end_seq_len;
188 char *directive; /* name of the current directive */
189 apr_size_t directive_len; /* length of the current directive name */
190
191 arg_item_t *current_arg; /* currently parsed argument */
192 arg_item_t *argv; /* all arguments */
193
194 backref_t *re; /* NULL if there wasn't a regex yet */
195
196 const char *undefined_echo;
197 apr_size_t undefined_echo_len;
198
199 char legacy_expr; /* use ap_expr or legacy mod_include
200 expression parser? */
201
202 ap_expr_eval_ctx_t *expr_eval_ctx; /* NULL if there wasn't an ap_expr yet */
203 const char *expr_vary_this; /* for use by ap_expr_eval_ctx */
204 const char *expr_err; /* for use by ap_expr_eval_ctx */
205 #ifdef DEBUG_INCLUDE
206 struct {
207 ap_filter_t *f;
208 apr_bucket_brigade *bb;
209 } debug;
210 #endif
211 };
212
213
214 /*
215 * +-------------------------------------------------------+
216 * | |
217 * | Debugging Utilities
218 * | |
219 * +-------------------------------------------------------+
220 */
221
222 #ifdef DEBUG_INCLUDE
223
224 #define TYPE_TOKEN(token, ttype) do { \
225 (token)->type = ttype; \
226 (token)->s = #ttype; \
227 } while(0)
228
229 #define CREATE_NODE(ctx, name) do { \
230 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
231 (name)->parent = (name)->left = (name)->right = NULL; \
232 (name)->done = 0; \
233 (name)->dump_done = 0; \
234 } while(0)
235
debug_printf(include_ctx_t * ctx,const char * fmt,...)236 static void debug_printf(include_ctx_t *ctx, const char *fmt, ...)
237 {
238 va_list ap;
239 char *debug__str;
240
241 va_start(ap, fmt);
242 debug__str = apr_pvsprintf(ctx->pool, fmt, ap);
243 va_end(ap);
244
245 APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create(
246 debug__str, strlen(debug__str), ctx->pool,
247 ctx->intern->debug.f->c->bucket_alloc));
248 }
249
250 #define DUMP__CHILD(ctx, is, node, child) if (1) { \
251 parse_node_t *d__c = node->child; \
252 if (d__c) { \
253 if (!d__c->dump_done) { \
254 if (d__c->parent != node) { \
255 debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \
256 if (!d__c->parent) { \
257 debug_printf(ctx, "Parent of " #child " child node is " \
258 "NULL.\n"); \
259 } \
260 else { \
261 debug_printf(ctx, "Parent of " #child " child node " \
262 "points to another node (of type %s)!\n", \
263 d__c->parent->token.s); \
264 } \
265 return; \
266 } \
267 node = d__c; \
268 continue; \
269 } \
270 } \
271 else { \
272 debug_printf(ctx, "%s(missing)\n", is); \
273 } \
274 }
275
debug_dump_tree(include_ctx_t * ctx,parse_node_t * root)276 static void debug_dump_tree(include_ctx_t *ctx, parse_node_t *root)
277 {
278 parse_node_t *current;
279 char *is;
280
281 if (!root) {
282 debug_printf(ctx, " -- Parse Tree empty --\n\n");
283 return;
284 }
285
286 debug_printf(ctx, " ----- Parse Tree -----\n");
287 current = root;
288 is = " ";
289
290 while (current) {
291 switch (current->token.type) {
292 case TOKEN_STRING:
293 case TOKEN_RE:
294 debug_printf(ctx, "%s%s (%s)\n", is, current->token.s,
295 current->token.value);
296 current->dump_done = 1;
297 current = current->parent;
298 continue;
299
300 case TOKEN_NOT:
301 case TOKEN_GROUP:
302 case TOKEN_RBRACE:
303 case TOKEN_LBRACE:
304 if (!current->dump_done) {
305 debug_printf(ctx, "%s%s\n", is, current->token.s);
306 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
307 current->dump_done = 1;
308 }
309
310 DUMP__CHILD(ctx, is, current, right)
311
312 if (!current->right || current->right->dump_done) {
313 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
314 if (current->right) current->right->dump_done = 0;
315 current = current->parent;
316 }
317 continue;
318
319 default:
320 if (!current->dump_done) {
321 debug_printf(ctx, "%s%s\n", is, current->token.s);
322 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
323 current->dump_done = 1;
324 }
325
326 DUMP__CHILD(ctx, is, current, left)
327 DUMP__CHILD(ctx, is, current, right)
328
329 if ((!current->left || current->left->dump_done) &&
330 (!current->right || current->right->dump_done)) {
331
332 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
333 if (current->left) current->left->dump_done = 0;
334 if (current->right) current->right->dump_done = 0;
335 current = current->parent;
336 }
337 continue;
338 }
339 }
340
341 /* it is possible to call this function within the parser loop, to see
342 * how the tree is built. That way, we must cleanup after us to dump
343 * always the whole tree
344 */
345 root->dump_done = 0;
346 if (root->left) root->left->dump_done = 0;
347 if (root->right) root->right->dump_done = 0;
348
349 debug_printf(ctx, " --- End Parse Tree ---\n\n");
350 }
351
352 #define DEBUG_INIT(ctx, filter, brigade) do { \
353 (ctx)->intern->debug.f = filter; \
354 (ctx)->intern->debug.bb = brigade; \
355 } while(0)
356
357 #define DEBUG_PRINTF(arg) debug_printf arg
358
359 #define DEBUG_DUMP_TOKEN(ctx, token) do { \
360 token_t *d__t = (token); \
361 \
362 if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \
363 DEBUG_PRINTF(((ctx), " Found: %s (%s)\n", d__t->s, d__t->value)); \
364 } \
365 else { \
366 DEBUG_PRINTF((ctx, " Found: %s\n", d__t->s)); \
367 } \
368 } while(0)
369
370 #define DEBUG_DUMP_EVAL(ctx, node) do { \
371 char c = '"'; \
372 switch ((node)->token.type) { \
373 case TOKEN_STRING: \
374 debug_printf((ctx), " Evaluate: %s (%s) -> %c\n", (node)->token.s,\
375 (node)->token.value, ((node)->value) ? '1':'0'); \
376 break; \
377 case TOKEN_AND: \
378 case TOKEN_OR: \
379 debug_printf((ctx), " Evaluate: %s (Left: %s; Right: %s) -> %c\n",\
380 (node)->token.s, \
381 (((node)->left->done) ? ((node)->left->value ?"1":"0") \
382 : "short circuited"), \
383 (((node)->right->done) ? ((node)->right->value?"1":"0") \
384 : "short circuited"), \
385 (node)->value ? '1' : '0'); \
386 break; \
387 case TOKEN_EQ: \
388 case TOKEN_NE: \
389 case TOKEN_GT: \
390 case TOKEN_GE: \
391 case TOKEN_LT: \
392 case TOKEN_LE: \
393 if ((node)->right->token.type == TOKEN_RE) c = '/'; \
394 debug_printf((ctx), " Compare: %s (\"%s\" with %c%s%c) -> %c\n", \
395 (node)->token.s, \
396 (node)->left->token.value, \
397 c, (node)->right->token.value, c, \
398 (node)->value ? '1' : '0'); \
399 break; \
400 default: \
401 debug_printf((ctx), " Evaluate: %s -> %c\n", (node)->token.s, \
402 (node)->value ? '1' : '0'); \
403 break; \
404 } \
405 } while(0)
406
407 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched) do { \
408 if (unmatched) { \
409 DEBUG_PRINTF(((ctx), " Unmatched %c\n", (char)(unmatched))); \
410 } \
411 } while(0)
412
413 #define DEBUG_DUMP_COND(ctx, text) \
414 DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \
415 ((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0'))
416
417 #define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root)
418
419 #else /* DEBUG_INCLUDE */
420
421 #define TYPE_TOKEN(token, ttype) (token)->type = ttype
422
423 #define CREATE_NODE(ctx, name) do { \
424 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
425 (name)->parent = (name)->left = (name)->right = NULL; \
426 (name)->done = 0; \
427 } while(0)
428
429 #define DEBUG_INIT(ctx, f, bb)
430 #define DEBUG_PRINTF(arg)
431 #define DEBUG_DUMP_TOKEN(ctx, token)
432 #define DEBUG_DUMP_EVAL(ctx, node)
433 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched)
434 #define DEBUG_DUMP_COND(ctx, text)
435 #define DEBUG_DUMP_TREE(ctx, root)
436
437 #endif /* !DEBUG_INCLUDE */
438
439
440 /*
441 * +-------------------------------------------------------+
442 * | |
443 * | Static Module Data
444 * | |
445 * +-------------------------------------------------------+
446 */
447
448 /* global module structure */
449 module AP_MODULE_DECLARE_DATA include_module;
450
451 /* function handlers for include directives */
452 static apr_hash_t *include_handlers;
453
454 /* forward declaration of handler registry */
455 static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
456
457 /* Sentinel value to store in subprocess_env for items that
458 * shouldn't be evaluated until/unless they're actually used
459 */
460 static const char lazy_eval_sentinel = '\0';
461 #define LAZY_VALUE (&lazy_eval_sentinel)
462
463 /* default values */
464 #define DEFAULT_START_SEQUENCE "<!--#"
465 #define DEFAULT_END_SEQUENCE "-->"
466 #define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
467 #define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
468 #define DEFAULT_UNDEFINED_ECHO "(none)"
469
470 #define UNSET -1
471
472 #ifdef XBITHACK
473 #define DEFAULT_XBITHACK XBITHACK_FULL
474 #else
475 #define DEFAULT_XBITHACK XBITHACK_OFF
476 #endif
477
478
479 /*
480 * +-------------------------------------------------------+
481 * | |
482 * | Environment/Expansion Functions
483 * | |
484 * +-------------------------------------------------------+
485 */
486
487 /*
488 * decodes a string containing html entities or numeric character references.
489 * 's' is overwritten with the decoded string.
490 * If 's' is syntatically incorrect, then the followed fixups will be made:
491 * unknown entities will be left undecoded;
492 * references to unused numeric characters will be deleted.
493 * In particular, � will not be decoded, but will be deleted.
494 */
495
496 /* maximum length of any ISO-LATIN-1 HTML entity name. */
497 #define MAXENTLEN (6)
498
499 /* The following is a shrinking transformation, therefore safe. */
500
501 /* Note: this function is deprecated in favour of apr_unescape_entity() in APR */
decodehtml(char * s)502 static void decodehtml(char *s)
503 {
504 int val, i, j;
505 char *p;
506 const char *ents;
507 static const char * const entlist[MAXENTLEN + 1] =
508 {
509 NULL, /* 0 */
510 NULL, /* 1 */
511 "lt\074gt\076", /* 2 */
512 "amp\046ETH\320eth\360", /* 3 */
513 "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml"
514 "\353iuml\357ouml\366uuml\374yuml\377", /* 4 */
515
516 "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc"
517 "\333THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352"
518 "icirc\356ocirc\364ucirc\373thorn\376", /* 5 */
519
520 "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311"
521 "Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde"
522 "\325Oslash\330Ugrave\331Uacute\332Yacute\335agrave\340"
523 "aacute\341atilde\343ccedil\347egrave\350eacute\351igrave"
524 "\354iacute\355ntilde\361ograve\362oacute\363otilde\365"
525 "oslash\370ugrave\371uacute\372yacute\375" /* 6 */
526 };
527
528 /* Do a fast scan through the string until we find anything
529 * that needs more complicated handling
530 */
531 for (; *s != '&'; s++) {
532 if (*s == '\0') {
533 return;
534 }
535 }
536
537 for (p = s; *s != '\0'; s++, p++) {
538 if (*s != '&') {
539 *p = *s;
540 continue;
541 }
542 /* find end of entity */
543 for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
544 continue;
545 }
546
547 if (s[i] == '\0') { /* treat as normal data */
548 *p = *s;
549 continue;
550 }
551
552 /* is it numeric ? */
553 if (s[1] == '#') {
554 for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
555 val = val * 10 + s[j] - '0';
556 }
557 s += i;
558 if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
559 (val >= 127 && val <= 160) || val >= 256) {
560 p--; /* no data to output */
561 }
562 else {
563 *p = RAW_ASCII_CHAR(val);
564 }
565 }
566 else {
567 j = i - 1;
568 if (j > MAXENTLEN || entlist[j] == NULL) {
569 /* wrong length */
570 *p = '&';
571 continue; /* skip it */
572 }
573 for (ents = entlist[j]; *ents != '\0'; ents += i) {
574 if (strncmp(s + 1, ents, j) == 0) {
575 break;
576 }
577 }
578
579 if (*ents == '\0') {
580 *p = '&'; /* unknown */
581 }
582 else {
583 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
584 s += i;
585 }
586 }
587 }
588
589 *p = '\0';
590 }
591
add_include_vars(request_rec * r)592 static void add_include_vars(request_rec *r)
593 {
594 apr_table_t *e = r->subprocess_env;
595 char *t;
596
597 apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
598 apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
599 apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
600 apr_table_setn(e, "DOCUMENT_URI", r->uri);
601 apr_table_setn(e, "DOCUMENT_ARGS", r->args ? r->args : "");
602 if (r->path_info && *r->path_info) {
603 apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
604 }
605 apr_table_setn(e, "USER_NAME", LAZY_VALUE);
606 if (r->filename && (t = strrchr(r->filename, '/'))) {
607 apr_table_setn(e, "DOCUMENT_NAME", ++t);
608 }
609 else {
610 apr_table_setn(e, "DOCUMENT_NAME", r->uri);
611 }
612 if (r->args) {
613 char *arg_copy = apr_pstrdup(r->pool, r->args);
614
615 ap_unescape_url(arg_copy);
616 apr_table_setn(e, "QUERY_STRING_UNESCAPED",
617 ap_escape_shell_cmd(r->pool, arg_copy));
618 }
619 }
620
add_include_vars_lazy(request_rec * r,const char * var,const char * timefmt)621 static const char *add_include_vars_lazy(request_rec *r, const char *var, const char *timefmt)
622 {
623 char *val;
624 if (!strcasecmp(var, "DATE_LOCAL")) {
625 val = ap_ht_time(r->pool, r->request_time, timefmt, 0);
626 }
627 else if (!strcasecmp(var, "DATE_GMT")) {
628 val = ap_ht_time(r->pool, r->request_time, timefmt, 1);
629 }
630 else if (!strcasecmp(var, "LAST_MODIFIED")) {
631 val = ap_ht_time(r->pool, r->finfo.mtime, timefmt, 0);
632 }
633 else if (!strcasecmp(var, "USER_NAME")) {
634 if (apr_uid_name_get(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
635 val = "<unknown>";
636 }
637 }
638 else {
639 val = NULL;
640 }
641
642 if (val) {
643 apr_table_setn(r->subprocess_env, var, val);
644 }
645 return val;
646 }
647
get_include_var(const char * var,include_ctx_t * ctx)648 static const char *get_include_var(const char *var, include_ctx_t *ctx)
649 {
650 const char *val;
651 request_rec *r = ctx->r;
652
653 if (apr_isdigit(*var) && !var[1]) {
654 apr_size_t idx = *var - '0';
655 backref_t *re = ctx->intern->re;
656
657 /* Handle $0 .. $9 from the last regex evaluated.
658 * The choice of returning NULL strings on not-found,
659 * v.s. empty strings on an empty match is deliberate.
660 */
661 if (!re || !re->have_match) {
662 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01329)
663 "regex capture $%" APR_SIZE_T_FMT " refers to no regex in %s",
664 idx, r->filename);
665 return NULL;
666 }
667 else if (re->nsub < idx || idx >= AP_MAX_REG_MATCH) {
668 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01330)
669 "regex capture $%" APR_SIZE_T_FMT
670 " is out of range (last regex was: '%s') in %s",
671 idx, re->rexp, r->filename);
672 return NULL;
673 }
674 else if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) {
675 /* This particular subpattern was not used by the regex */
676 return NULL;
677 }
678 else {
679 val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so,
680 re->match[idx].rm_eo - re->match[idx].rm_so);
681 }
682 }
683 else {
684 val = apr_table_get(r->subprocess_env, var);
685
686 if (val == LAZY_VALUE) {
687 val = add_include_vars_lazy(r, var, ctx->time_str);
688 }
689 }
690
691 return val;
692 }
693
include_expr_var_fn(ap_expr_eval_ctx_t * eval_ctx,const void * data,const char * arg)694 static const char *include_expr_var_fn(ap_expr_eval_ctx_t *eval_ctx,
695 const void *data,
696 const char *arg)
697 {
698 const char *res, *name = data;
699 include_ctx_t *ctx = eval_ctx->data;
700 if ((name[0] == 'e') || (name[0] == 'E')) {
701 /* keep legacy "env" semantics */
702 if ((res = apr_table_get(ctx->r->notes, arg)) != NULL)
703 return res;
704 else if ((res = get_include_var(arg, ctx)) != NULL)
705 return res;
706 else
707 return getenv(arg);
708 }
709 else {
710 return get_include_var(arg, ctx);
711 }
712 }
713
include_expr_lookup(ap_expr_lookup_parms * parms)714 static int include_expr_lookup(ap_expr_lookup_parms *parms)
715 {
716 switch (parms->type) {
717 case AP_EXPR_FUNC_STRING:
718 if (strcasecmp(parms->name, "v") == 0 ||
719 strcasecmp(parms->name, "reqenv") == 0 ||
720 strcasecmp(parms->name, "env") == 0) {
721 *parms->func = include_expr_var_fn;
722 *parms->data = parms->name;
723 return OK;
724 }
725 break;
726 /*
727 * We could also make the SSI vars available as %{...} style variables
728 * (AP_EXPR_FUNC_VAR), but this would create problems if we ever want
729 * to cache parsed expressions for performance reasons.
730 */
731 }
732 return ap_run_expr_lookup(parms);
733 }
734
735
736 /*
737 * Do variable substitution on strings
738 *
739 * (Note: If out==NULL, this function allocs a buffer for the resulting
740 * string from ctx->pool. The return value is always the parsed string)
741 */
ap_ssi_parse_string(include_ctx_t * ctx,const char * in,char * out,apr_size_t length,int leave_name)742 static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out,
743 apr_size_t length, int leave_name)
744 {
745 request_rec *r = ctx->r;
746 result_item_t *result = NULL, *current = NULL;
747 apr_size_t outlen = 0, inlen, span;
748 char *ret = NULL, *eout = NULL;
749 const char *p;
750
751 if (out) {
752 /* sanity check, out && !length is not supported */
753 ap_assert(out && length);
754
755 ret = out;
756 eout = out + length - 1;
757 }
758
759 span = strcspn(in, "\\$");
760 inlen = strlen(in);
761
762 /* fast exit */
763 if (inlen == span) {
764 if (out) {
765 apr_cpystrn(out, in, length);
766 }
767 else {
768 ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen)
769 ? length - 1 : inlen);
770 }
771
772 return ret;
773 }
774
775 /* well, actually something to do */
776 p = in + span;
777
778 if (out) {
779 if (span) {
780 memcpy(out, in, (out+span <= eout) ? span : (eout-out));
781 out += span;
782 }
783 }
784 else {
785 current = result = apr_palloc(ctx->dpool, sizeof(*result));
786 current->next = NULL;
787 current->string = in;
788 current->len = span;
789 outlen = span;
790 }
791
792 /* loop for specials */
793 do {
794 if ((out && out >= eout) || (length && outlen >= length)) {
795 break;
796 }
797
798 /* prepare next entry */
799 if (!out && current->len) {
800 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
801 current = current->next;
802 current->next = NULL;
803 current->len = 0;
804 }
805
806 /*
807 * escaped character
808 */
809 if (*p == '\\') {
810 if (out) {
811 *out++ = (p[1] == '$') ? *++p : *p;
812 ++p;
813 }
814 else {
815 current->len = 1;
816 current->string = (p[1] == '$') ? ++p : p;
817 ++p;
818 ++outlen;
819 }
820 }
821
822 /*
823 * variable expansion
824 */
825 else { /* *p == '$' */
826 const char *newp = NULL, *ep, *key = NULL;
827
828 if (*++p == '{') {
829 ep = ap_strchr_c(++p, '}');
830 if (!ep) {
831 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01331) "Missing '}' on "
832 "variable \"%s\" in %s", p, r->filename);
833 break;
834 }
835
836 if (p < ep) {
837 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
838 newp = ep + 1;
839 }
840 p -= 2;
841 }
842 else {
843 ep = p;
844 while (*ep == '_' || apr_isalnum(*ep)) {
845 ++ep;
846 }
847
848 if (p < ep) {
849 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
850 newp = ep;
851 }
852 --p;
853 }
854
855 /* empty name results in a copy of '$' in the output string */
856 if (!key) {
857 if (out) {
858 *out++ = *p++;
859 }
860 else {
861 current->len = 1;
862 current->string = p++;
863 ++outlen;
864 }
865 }
866 else {
867 const char *val = get_include_var(key, ctx);
868 apr_size_t len = 0;
869
870 if (val) {
871 len = strlen(val);
872 }
873 else if (leave_name) {
874 val = p;
875 len = ep - p;
876 }
877
878 if (val && len) {
879 if (out) {
880 memcpy(out, val, (out+len <= eout) ? len : (eout-out));
881 out += len;
882 }
883 else {
884 current->len = len;
885 current->string = val;
886 outlen += len;
887 }
888 }
889
890 p = newp;
891 }
892 }
893
894 if ((out && out >= eout) || (length && outlen >= length)) {
895 break;
896 }
897
898 /* check the remainder */
899 if (*p && (span = strcspn(p, "\\$")) > 0) {
900 if (!out && current->len) {
901 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
902 current = current->next;
903 current->next = NULL;
904 }
905
906 if (out) {
907 memcpy(out, p, (out+span <= eout) ? span : (eout-out));
908 out += span;
909 }
910 else {
911 current->len = span;
912 current->string = p;
913 outlen += span;
914 }
915
916 p += span;
917 }
918 } while (p < in+inlen);
919
920 /* assemble result */
921 if (out) {
922 if (out > eout) {
923 *eout = '\0';
924 }
925 else {
926 *out = '\0';
927 }
928 }
929 else {
930 const char *ep;
931
932 if (length && outlen > length) {
933 outlen = length - 1;
934 }
935
936 ret = out = apr_palloc(ctx->pool, outlen + 1);
937 ep = ret + outlen;
938
939 do {
940 if (result->len) {
941 memcpy(out, result->string, (out+result->len <= ep)
942 ? result->len : (ep-out));
943 out += result->len;
944 }
945 result = result->next;
946 } while (result && out < ep);
947
948 ret[outlen] = '\0';
949 }
950
951 return ret;
952 }
953
954
955 /*
956 * +-------------------------------------------------------+
957 * | |
958 * | Conditional Expression Parser
959 * | |
960 * +-------------------------------------------------------+
961 */
962
re_check(include_ctx_t * ctx,const char * string,const char * rexp)963 static APR_INLINE int re_check(include_ctx_t *ctx, const char *string,
964 const char *rexp)
965 {
966 ap_regex_t *compiled;
967 backref_t *re = ctx->intern->re;
968
969 compiled = ap_pregcomp(ctx->dpool, rexp, AP_REG_EXTENDED);
970 if (!compiled) {
971 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02667)
972 "unable to compile pattern \"%s\"", rexp);
973 return -1;
974 }
975
976 if (!re) {
977 re = ctx->intern->re = apr_palloc(ctx->pool, sizeof(*re));
978 }
979
980 re->source = apr_pstrdup(ctx->pool, string);
981 re->rexp = apr_pstrdup(ctx->pool, rexp);
982 re->nsub = compiled->re_nsub;
983 re->have_match = !ap_regexec(compiled, string, AP_MAX_REG_MATCH,
984 re->match, 0);
985
986 ap_pregfree(ctx->dpool, compiled);
987 return re->have_match;
988 }
989
get_ptoken(include_ctx_t * ctx,const char ** parse,token_t * token,token_t * previous)990 static int get_ptoken(include_ctx_t *ctx, const char **parse, token_t *token, token_t *previous)
991 {
992 const char *p;
993 apr_size_t shift;
994 int unmatched;
995
996 token->value = NULL;
997
998 if (!*parse) {
999 return 0;
1000 }
1001
1002 /* Skip leading white space */
1003 while (apr_isspace(**parse)) {
1004 ++*parse;
1005 }
1006
1007 if (!**parse) {
1008 *parse = NULL;
1009 return 0;
1010 }
1011
1012 TYPE_TOKEN(token, TOKEN_STRING); /* the default type */
1013 p = *parse;
1014 unmatched = 0;
1015
1016 switch (*(*parse)++) {
1017 case '(':
1018 TYPE_TOKEN(token, TOKEN_LBRACE);
1019 return 0;
1020 case ')':
1021 TYPE_TOKEN(token, TOKEN_RBRACE);
1022 return 0;
1023 case '=':
1024 if (**parse == '=') ++*parse;
1025 TYPE_TOKEN(token, TOKEN_EQ);
1026 return 0;
1027 case '!':
1028 if (**parse == '=') {
1029 TYPE_TOKEN(token, TOKEN_NE);
1030 ++*parse;
1031 return 0;
1032 }
1033 TYPE_TOKEN(token, TOKEN_NOT);
1034 return 0;
1035 case '\'':
1036 unmatched = '\'';
1037 break;
1038 case '/':
1039 /* if last token was ACCESS, this token is STRING */
1040 if (previous != NULL && TOKEN_ACCESS == previous->type) {
1041 break;
1042 }
1043 TYPE_TOKEN(token, TOKEN_RE);
1044 unmatched = '/';
1045 break;
1046 case '|':
1047 if (**parse == '|') {
1048 TYPE_TOKEN(token, TOKEN_OR);
1049 ++*parse;
1050 return 0;
1051 }
1052 break;
1053 case '&':
1054 if (**parse == '&') {
1055 TYPE_TOKEN(token, TOKEN_AND);
1056 ++*parse;
1057 return 0;
1058 }
1059 break;
1060 case '>':
1061 if (**parse == '=') {
1062 TYPE_TOKEN(token, TOKEN_GE);
1063 ++*parse;
1064 return 0;
1065 }
1066 TYPE_TOKEN(token, TOKEN_GT);
1067 return 0;
1068 case '<':
1069 if (**parse == '=') {
1070 TYPE_TOKEN(token, TOKEN_LE);
1071 ++*parse;
1072 return 0;
1073 }
1074 TYPE_TOKEN(token, TOKEN_LT);
1075 return 0;
1076 case '-':
1077 if (**parse == 'A') {
1078 TYPE_TOKEN(token, TOKEN_ACCESS);
1079 ++*parse;
1080 return 0;
1081 }
1082 break;
1083 }
1084
1085 /* It's a string or regex token
1086 * Now search for the next token, which finishes this string
1087 */
1088 shift = 0;
1089 p = *parse = token->value = unmatched ? *parse : p;
1090
1091 for (; **parse; p = ++*parse) {
1092 if (**parse == '\\') {
1093 if (!*(++*parse)) {
1094 p = *parse;
1095 break;
1096 }
1097
1098 ++shift;
1099 }
1100 else {
1101 if (unmatched) {
1102 if (**parse == unmatched) {
1103 unmatched = 0;
1104 ++*parse;
1105 break;
1106 }
1107 } else if (apr_isspace(**parse)) {
1108 break;
1109 }
1110 else {
1111 int found = 0;
1112
1113 switch (**parse) {
1114 case '(':
1115 case ')':
1116 case '=':
1117 case '!':
1118 case '<':
1119 case '>':
1120 ++found;
1121 break;
1122
1123 case '|':
1124 case '&':
1125 if ((*parse)[1] == **parse) {
1126 ++found;
1127 }
1128 break;
1129 }
1130
1131 if (found) {
1132 break;
1133 }
1134 }
1135 }
1136 }
1137
1138 if (unmatched) {
1139 token->value = apr_pstrdup(ctx->dpool, "");
1140 }
1141 else {
1142 apr_size_t len = p - token->value - shift;
1143 char *c = apr_palloc(ctx->dpool, len + 1);
1144
1145 p = token->value;
1146 token->value = c;
1147
1148 while (shift--) {
1149 const char *e = ap_strchr_c(p, '\\');
1150
1151 memcpy(c, p, e-p);
1152 c += e-p;
1153 *c++ = *++e;
1154 len -= e-p;
1155 p = e+1;
1156 }
1157
1158 if (len) {
1159 memcpy(c, p, len);
1160 }
1161 c[len] = '\0';
1162 }
1163
1164 return unmatched;
1165 }
1166
parse_expr(include_ctx_t * ctx,const char * expr,int * was_error)1167 static int parse_expr(include_ctx_t *ctx, const char *expr, int *was_error)
1168 {
1169 parse_node_t *new, *root = NULL, *current = NULL;
1170 request_rec *r = ctx->r;
1171 request_rec *rr = NULL;
1172 const char *error = APLOGNO(03188) "Invalid expression \"%s\" in file %s";
1173 const char *parse = expr;
1174 unsigned regex = 0;
1175
1176 *was_error = 0;
1177
1178 if (!parse) {
1179 return 0;
1180 }
1181
1182 /* Create Parse Tree */
1183 while (1) {
1184 /* uncomment this to see how the tree a built:
1185 *
1186 * DEBUG_DUMP_TREE(ctx, root);
1187 */
1188 CREATE_NODE(ctx, new);
1189
1190 {
1191 #ifdef DEBUG_INCLUDE
1192 int was_unmatched =
1193 #endif
1194 get_ptoken(ctx, &parse, &new->token,
1195 (current != NULL ? ¤t->token : NULL));
1196 if (!parse)
1197 break;
1198
1199 DEBUG_DUMP_UNMATCHED(ctx, was_unmatched);
1200 DEBUG_DUMP_TOKEN(ctx, &new->token);
1201 }
1202
1203 if (!current) {
1204 switch (new->token.type) {
1205 case TOKEN_STRING:
1206 case TOKEN_NOT:
1207 case TOKEN_ACCESS:
1208 case TOKEN_LBRACE:
1209 root = current = new;
1210 continue;
1211
1212 default:
1213 /* Intentional no APLOGNO */
1214 /* error text provides APLOGNO */
1215 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr,
1216 r->filename);
1217 *was_error = 1;
1218 return 0;
1219 }
1220 }
1221
1222 switch (new->token.type) {
1223 case TOKEN_STRING:
1224 switch (current->token.type) {
1225 case TOKEN_STRING:
1226 current->token.value =
1227 apr_pstrcat(ctx->dpool, current->token.value,
1228 *current->token.value ? " " : "",
1229 new->token.value, NULL);
1230 continue;
1231
1232 case TOKEN_RE:
1233 case TOKEN_RBRACE:
1234 case TOKEN_GROUP:
1235 break;
1236
1237 default:
1238 new->parent = current;
1239 current = current->right = new;
1240 continue;
1241 }
1242 break;
1243
1244 case TOKEN_RE:
1245 switch (current->token.type) {
1246 case TOKEN_EQ:
1247 case TOKEN_NE:
1248 new->parent = current;
1249 current = current->right = new;
1250 ++regex;
1251 continue;
1252
1253 default:
1254 break;
1255 }
1256 break;
1257
1258 case TOKEN_AND:
1259 case TOKEN_OR:
1260 switch (current->token.type) {
1261 case TOKEN_STRING:
1262 case TOKEN_RE:
1263 case TOKEN_GROUP:
1264 current = current->parent;
1265
1266 while (current) {
1267 switch (current->token.type) {
1268 case TOKEN_AND:
1269 case TOKEN_OR:
1270 case TOKEN_LBRACE:
1271 break;
1272
1273 default:
1274 current = current->parent;
1275 continue;
1276 }
1277 break;
1278 }
1279
1280 if (!current) {
1281 new->left = root;
1282 root->parent = new;
1283 current = root = new;
1284 continue;
1285 }
1286
1287 new->left = current->right;
1288 new->left->parent = new;
1289 new->parent = current;
1290 current = current->right = new;
1291 continue;
1292
1293 default:
1294 break;
1295 }
1296 break;
1297
1298 case TOKEN_EQ:
1299 case TOKEN_NE:
1300 case TOKEN_GE:
1301 case TOKEN_GT:
1302 case TOKEN_LE:
1303 case TOKEN_LT:
1304 if (current->token.type == TOKEN_STRING) {
1305 current = current->parent;
1306
1307 if (!current) {
1308 new->left = root;
1309 root->parent = new;
1310 current = root = new;
1311 continue;
1312 }
1313
1314 switch (current->token.type) {
1315 case TOKEN_LBRACE:
1316 case TOKEN_AND:
1317 case TOKEN_OR:
1318 new->left = current->right;
1319 new->left->parent = new;
1320 new->parent = current;
1321 current = current->right = new;
1322 continue;
1323
1324 default:
1325 break;
1326 }
1327 }
1328 break;
1329
1330 case TOKEN_RBRACE:
1331 while (current && current->token.type != TOKEN_LBRACE) {
1332 current = current->parent;
1333 }
1334
1335 if (current) {
1336 TYPE_TOKEN(¤t->token, TOKEN_GROUP);
1337 continue;
1338 }
1339
1340 error = APLOGNO(03189) "Unmatched ')' in \"%s\" in file %s";
1341 break;
1342
1343 case TOKEN_NOT:
1344 case TOKEN_ACCESS:
1345 case TOKEN_LBRACE:
1346 switch (current->token.type) {
1347 case TOKEN_STRING:
1348 case TOKEN_RE:
1349 case TOKEN_RBRACE:
1350 case TOKEN_GROUP:
1351 break;
1352
1353 default:
1354 current->right = new;
1355 new->parent = current;
1356 current = new;
1357 continue;
1358 }
1359 break;
1360
1361 default:
1362 break;
1363 }
1364
1365 /* Intentional no APLOGNO */
1366 /* error text provides APLOGNO */
1367 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename);
1368 *was_error = 1;
1369 return 0;
1370 }
1371
1372 DEBUG_DUMP_TREE(ctx, root);
1373
1374 /* Evaluate Parse Tree */
1375 current = root;
1376 error = NULL;
1377 while (current) {
1378 switch (current->token.type) {
1379 case TOKEN_STRING:
1380 current->token.value =
1381 ap_ssi_parse_string(ctx, current->token.value, NULL, 0,
1382 SSI_EXPAND_DROP_NAME);
1383 current->value = !!*current->token.value;
1384 break;
1385
1386 case TOKEN_AND:
1387 case TOKEN_OR:
1388 if (!current->left || !current->right) {
1389 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01332)
1390 "Invalid expression \"%s\" in file %s",
1391 expr, r->filename);
1392 *was_error = 1;
1393 return 0;
1394 }
1395
1396 if (!current->left->done) {
1397 switch (current->left->token.type) {
1398 case TOKEN_STRING:
1399 current->left->token.value =
1400 ap_ssi_parse_string(ctx, current->left->token.value,
1401 NULL, 0, SSI_EXPAND_DROP_NAME);
1402 current->left->value = !!*current->left->token.value;
1403 DEBUG_DUMP_EVAL(ctx, current->left);
1404 current->left->done = 1;
1405 break;
1406
1407 default:
1408 current = current->left;
1409 continue;
1410 }
1411 }
1412
1413 /* short circuit evaluation */
1414 if (!current->right->done && !regex &&
1415 ((current->token.type == TOKEN_AND && !current->left->value) ||
1416 (current->token.type == TOKEN_OR && current->left->value))) {
1417 current->value = current->left->value;
1418 }
1419 else {
1420 if (!current->right->done) {
1421 switch (current->right->token.type) {
1422 case TOKEN_STRING:
1423 current->right->token.value =
1424 ap_ssi_parse_string(ctx,current->right->token.value,
1425 NULL, 0, SSI_EXPAND_DROP_NAME);
1426 current->right->value = !!*current->right->token.value;
1427 DEBUG_DUMP_EVAL(ctx, current->right);
1428 current->right->done = 1;
1429 break;
1430
1431 default:
1432 current = current->right;
1433 continue;
1434 }
1435 }
1436
1437 if (current->token.type == TOKEN_AND) {
1438 current->value = current->left->value &&
1439 current->right->value;
1440 }
1441 else {
1442 current->value = current->left->value ||
1443 current->right->value;
1444 }
1445 }
1446 break;
1447
1448 case TOKEN_EQ:
1449 case TOKEN_NE:
1450 if (!current->left || !current->right ||
1451 current->left->token.type != TOKEN_STRING ||
1452 (current->right->token.type != TOKEN_STRING &&
1453 current->right->token.type != TOKEN_RE)) {
1454 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01333)
1455 "Invalid expression \"%s\" in file %s",
1456 expr, r->filename);
1457 *was_error = 1;
1458 return 0;
1459 }
1460 current->left->token.value =
1461 ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
1462 SSI_EXPAND_DROP_NAME);
1463 current->right->token.value =
1464 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
1465 SSI_EXPAND_DROP_NAME);
1466
1467 if (current->right->token.type == TOKEN_RE) {
1468 current->value = re_check(ctx, current->left->token.value,
1469 current->right->token.value);
1470 --regex;
1471 }
1472 else {
1473 current->value = !strcmp(current->left->token.value,
1474 current->right->token.value);
1475 }
1476
1477 if (current->token.type == TOKEN_NE) {
1478 current->value = !current->value;
1479 }
1480 break;
1481
1482 case TOKEN_GE:
1483 case TOKEN_GT:
1484 case TOKEN_LE:
1485 case TOKEN_LT:
1486 if (!current->left || !current->right ||
1487 current->left->token.type != TOKEN_STRING ||
1488 current->right->token.type != TOKEN_STRING) {
1489 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01334)
1490 "Invalid expression \"%s\" in file %s",
1491 expr, r->filename);
1492 *was_error = 1;
1493 return 0;
1494 }
1495
1496 current->left->token.value =
1497 ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
1498 SSI_EXPAND_DROP_NAME);
1499 current->right->token.value =
1500 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
1501 SSI_EXPAND_DROP_NAME);
1502
1503 current->value = strcmp(current->left->token.value,
1504 current->right->token.value);
1505
1506 switch (current->token.type) {
1507 case TOKEN_GE: current->value = current->value >= 0; break;
1508 case TOKEN_GT: current->value = current->value > 0; break;
1509 case TOKEN_LE: current->value = current->value <= 0; break;
1510 case TOKEN_LT: current->value = current->value < 0; break;
1511 default: current->value = 0; break; /* should not happen */
1512 }
1513 break;
1514
1515 case TOKEN_NOT:
1516 case TOKEN_GROUP:
1517 if (current->right) {
1518 if (!current->right->done) {
1519 current = current->right;
1520 continue;
1521 }
1522 current->value = current->right->value;
1523 }
1524 else {
1525 current->value = 1;
1526 }
1527
1528 if (current->token.type == TOKEN_NOT) {
1529 current->value = !current->value;
1530 }
1531 break;
1532
1533 case TOKEN_ACCESS:
1534 if (current->left || !current->right ||
1535 (current->right->token.type != TOKEN_STRING &&
1536 current->right->token.type != TOKEN_RE)) {
1537 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01335)
1538 "Invalid expression \"%s\" in file %s: Token '-A' must be followed by a URI string.",
1539 expr, r->filename);
1540 *was_error = 1;
1541 return 0;
1542 }
1543 current->right->token.value =
1544 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
1545 SSI_EXPAND_DROP_NAME);
1546 rr = ap_sub_req_lookup_uri(current->right->token.value, r, NULL);
1547 /* 400 and higher are considered access denied */
1548 if (rr->status < HTTP_BAD_REQUEST) {
1549 current->value = 1;
1550 }
1551 else {
1552 current->value = 0;
1553 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rr->status, r, APLOGNO(01336)
1554 "mod_include: The tested "
1555 "subrequest -A \"%s\" returned an error code.",
1556 current->right->token.value);
1557 }
1558 ap_destroy_sub_req(rr);
1559 break;
1560
1561 case TOKEN_RE:
1562 if (!error) {
1563 error = APLOGNO(03190) "No operator before regex in expr \"%s\" in file %s";
1564 }
1565 case TOKEN_LBRACE:
1566 if (!error) {
1567 error = APLOGNO(03191) "Unmatched '(' in \"%s\" in file %s";
1568 }
1569 default:
1570 if (!error) {
1571 error = APLOGNO(03192) "internal parser error in \"%s\" in file %s";
1572 }
1573
1574 /* Intentional no APLOGNO */
1575 /* error text provides APLOGNO */
1576 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename);
1577 *was_error = 1;
1578 return 0;
1579 }
1580
1581 DEBUG_DUMP_EVAL(ctx, current);
1582 current->done = 1;
1583 current = current->parent;
1584 }
1585
1586 return (root ? root->value : 0);
1587 }
1588
1589 /* same as above, but use common ap_expr syntax / API */
parse_ap_expr(include_ctx_t * ctx,const char * expr,int * was_error)1590 static int parse_ap_expr(include_ctx_t *ctx, const char *expr, int *was_error)
1591 {
1592 ap_expr_info_t *expr_info = apr_pcalloc(ctx->pool, sizeof (*expr_info));
1593 const char *err;
1594 int ret;
1595 backref_t *re = ctx->intern->re;
1596 ap_expr_eval_ctx_t *eval_ctx = ctx->intern->expr_eval_ctx;
1597
1598 expr_info->filename = ctx->r->filename;
1599 expr_info->line_number = 0;
1600 expr_info->module_index = APLOG_MODULE_INDEX;
1601 expr_info->flags = AP_EXPR_FLAG_RESTRICTED;
1602 err = ap_expr_parse(ctx->r->pool, ctx->r->pool, expr_info, expr,
1603 include_expr_lookup);
1604 if (err) {
1605 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01337)
1606 "Could not parse expr \"%s\" in %s: %s", expr,
1607 ctx->r->filename, err);
1608 *was_error = 1;
1609 return 0;
1610 }
1611
1612 if (!re) {
1613 ctx->intern->re = re = apr_pcalloc(ctx->pool, sizeof(*re));
1614 }
1615 else {
1616 /* ap_expr_exec_ctx() does not care about re->have_match but only about
1617 * re->source
1618 */
1619 if (!re->have_match)
1620 re->source = NULL;
1621 }
1622
1623 if (!eval_ctx) {
1624 eval_ctx = apr_pcalloc(ctx->pool, sizeof(*eval_ctx));
1625 ctx->intern->expr_eval_ctx = eval_ctx;
1626 eval_ctx->r = ctx->r;
1627 eval_ctx->c = ctx->r->connection;
1628 eval_ctx->s = ctx->r->server;
1629 eval_ctx->p = ctx->r->pool;
1630 eval_ctx->data = ctx;
1631 eval_ctx->err = &ctx->intern->expr_err;
1632 eval_ctx->vary_this = &ctx->intern->expr_vary_this;
1633 eval_ctx->re_nmatch = AP_MAX_REG_MATCH;
1634 eval_ctx->re_pmatch = re->match;
1635 eval_ctx->re_source = &re->source;
1636 }
1637
1638 eval_ctx->info = expr_info;
1639 ret = ap_expr_exec_ctx(eval_ctx);
1640 if (ret < 0) {
1641 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01338)
1642 "Could not evaluate expr \"%s\" in %s: %s", expr,
1643 ctx->r->filename, ctx->intern->expr_err);
1644 *was_error = 1;
1645 return 0;
1646 }
1647 *was_error = 0;
1648 if (re->source)
1649 re->have_match = 1;
1650 return ret;
1651 }
1652
1653 /*
1654 * +-------------------------------------------------------+
1655 * | |
1656 * | Action Handlers
1657 * | |
1658 * +-------------------------------------------------------+
1659 */
1660
1661 /*
1662 * Extract the next tag name and value.
1663 * If there are no more tags, set the tag name to NULL.
1664 * The tag value is html decoded if dodecode is non-zero.
1665 * The tag value may be NULL if there is no tag value..
1666 */
ap_ssi_get_tag_and_value(include_ctx_t * ctx,char ** tag,char ** tag_val,int dodecode)1667 static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
1668 char **tag_val, int dodecode)
1669 {
1670 if (!ctx->intern->argv) {
1671 *tag = NULL;
1672 *tag_val = NULL;
1673
1674 return;
1675 }
1676
1677 *tag_val = ctx->intern->argv->value;
1678 *tag = ctx->intern->argv->name;
1679
1680 ctx->intern->argv = ctx->intern->argv->next;
1681
1682 if (dodecode && *tag_val) {
1683 decodehtml(*tag_val);
1684 }
1685 }
1686
find_file(request_rec * r,const char * directive,const char * tag,char * tag_val,apr_finfo_t * finfo)1687 static int find_file(request_rec *r, const char *directive, const char *tag,
1688 char *tag_val, apr_finfo_t *finfo)
1689 {
1690 char *to_send = tag_val;
1691 request_rec *rr = NULL;
1692 int ret=0;
1693 char *error_fmt = NULL;
1694 apr_status_t rv = APR_SUCCESS;
1695
1696 if (!strcmp(tag, "file")) {
1697 char *newpath;
1698
1699 /* be safe; only files in this directory or below allowed */
1700 rv = apr_filepath_merge(&newpath, NULL, tag_val,
1701 APR_FILEPATH_SECUREROOTTEST |
1702 APR_FILEPATH_NOTABSOLUTE, r->pool);
1703
1704 if (rv != APR_SUCCESS) {
1705 error_fmt = APLOGNO(02668) "unable to access file \"%s\" "
1706 "in parsed file %s";
1707 }
1708 else {
1709 /* note: it is okay to pass NULL for the "next filter" since
1710 we never attempt to "run" this sub request. */
1711 rr = ap_sub_req_lookup_file(newpath, r, NULL);
1712
1713 if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) {
1714 to_send = rr->filename;
1715 if ((rv = apr_stat(finfo, to_send,
1716 APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
1717 && rv != APR_INCOMPLETE) {
1718 error_fmt = APLOGNO(02669) "unable to get information "
1719 "about \"%s\" in parsed file %s";
1720 }
1721 }
1722 else {
1723 error_fmt = APLOGNO(02670) "unable to lookup information "
1724 "about \"%s\" in parsed file %s";
1725 }
1726 }
1727
1728 if (error_fmt) {
1729 ret = -1;
1730 /* Intentional no APLOGNO */
1731 /* error_fmt provides APLOGNO */
1732 ap_log_rerror(APLOG_MARK, APLOG_ERR,
1733 rv, r, error_fmt, to_send, r->filename);
1734 }
1735
1736 if (rr) ap_destroy_sub_req(rr);
1737
1738 return ret;
1739 }
1740 else if (!strcmp(tag, "virtual")) {
1741 /* note: it is okay to pass NULL for the "next filter" since
1742 we never attempt to "run" this sub request. */
1743 rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1744
1745 if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) {
1746 memcpy((char *) finfo, (const char *) &rr->finfo,
1747 sizeof(rr->finfo));
1748 ap_destroy_sub_req(rr);
1749 return 0;
1750 }
1751 else {
1752 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01339) "unable to get "
1753 "information about \"%s\" in parsed file %s",
1754 tag_val, r->filename);
1755 ap_destroy_sub_req(rr);
1756 return -1;
1757 }
1758 }
1759 else {
1760 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01340) "unknown parameter \"%s\" "
1761 "to tag %s in %s", tag, directive, r->filename);
1762 return -1;
1763 }
1764 }
1765
1766 /*
1767 * <!--#comment blah blah blah ... -->
1768 */
handle_comment(include_ctx_t * ctx,ap_filter_t * f,apr_bucket_brigade * bb)1769 static apr_status_t handle_comment(include_ctx_t *ctx, ap_filter_t *f,
1770 apr_bucket_brigade *bb)
1771 {
1772 return APR_SUCCESS;
1773 }
1774
1775 /*
1776 * <!--#include virtual|file="..." [onerror|virtual|file="..."] ... -->
1777 *
1778 * Output each file/virtual in turn until one of them returns an error.
1779 * On error, ignore all further file/virtual attributes until we reach
1780 * an onerror attribute, where we make an attempt to serve the onerror
1781 * virtual url. If onerror fails, or no onerror is present, the default
1782 * error string is inserted into the stream.
1783 */
handle_include(include_ctx_t * ctx,ap_filter_t * f,apr_bucket_brigade * bb)1784 static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
1785 apr_bucket_brigade *bb)
1786 {
1787 request_rec *r = f->r;
1788 char *last_error;
1789
1790 if (!ctx->argc) {
1791 ap_log_rerror(APLOG_MARK,
1792 (ctx->flags & SSI_FLAG_PRINTING)
1793 ? APLOG_ERR : APLOG_WARNING,
1794 0, r, APLOGNO(01341)
1795 "missing argument for include element in %s",
1796 r->filename);
1797 }
1798
1799 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1800 return APR_SUCCESS;
1801 }
1802
1803 if (!ctx->argc) {
1804 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1805 return APR_SUCCESS;
1806 }
1807
1808 last_error = NULL;
1809 while (1) {
1810 char *tag = NULL;
1811 char *tag_val = NULL;
1812 request_rec *rr = NULL;
1813 char *error_fmt = NULL;
1814 char *parsed_string;
1815 apr_status_t rv = APR_SUCCESS;
1816 int status = 0;
1817
1818 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1819 if (!tag || !tag_val) {
1820 break;
1821 }
1822
1823 if (strcmp(tag, "virtual") && strcmp(tag, "file") && strcmp(tag,
1824 "onerror")) {
1825 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01342) "unknown parameter "
1826 "\"%s\" to tag include in %s", tag, r->filename);
1827 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1828 break;
1829 }
1830
1831 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1832 SSI_EXPAND_DROP_NAME);
1833 if (tag[0] == 'f') {
1834 char *newpath;
1835
1836 /* be safe; only files in this directory or below allowed */
1837 rv = apr_filepath_merge(&newpath, NULL, parsed_string,
1838 APR_FILEPATH_SECUREROOTTEST |
1839 APR_FILEPATH_NOTABSOLUTE, ctx->dpool);
1840
1841 if (rv != APR_SUCCESS) {
1842 error_fmt = "unable to include file \"%s\" in parsed file %s";
1843 }
1844 else {
1845 rr = ap_sub_req_lookup_file(newpath, r, f->next);
1846 }
1847 }
1848 else if ((tag[0] == 'v' && !last_error)
1849 || (tag[0] == 'o' && last_error)) {
1850 if (r->kept_body) {
1851 rr = ap_sub_req_method_uri(r->method, parsed_string, r, f->next);
1852 }
1853 else {
1854 rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
1855 }
1856 }
1857 else {
1858 continue;
1859 }
1860
1861 if (!error_fmt && rr->status != HTTP_OK) {
1862 error_fmt = "unable to include \"%s\" in parsed file %s, subrequest setup returned %d";
1863 }
1864
1865 if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) &&
1866 rr->content_type && strncmp(rr->content_type, "text/", 5)) {
1867
1868 error_fmt = "unable to include potential exec \"%s\" in parsed "
1869 "file %s, content type not text/*";
1870 }
1871
1872 /* See the Kludge in includes_filter for why.
1873 * Basically, it puts a bread crumb in here, then looks
1874 * for the crumb later to see if its been here.
1875 */
1876 if (rr) {
1877 ap_set_module_config(rr->request_config, &include_module, r);
1878 }
1879
1880 if (!error_fmt && ((status = ap_run_sub_req(rr)))) {
1881 error_fmt = "unable to include \"%s\" in parsed file %s, subrequest returned %d";
1882 }
1883
1884 if (error_fmt) {
1885 /* Intentional no APLOGNO */
1886 /* error text is also sent to client */
1887 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, error_fmt, tag_val,
1888 r->filename, status ? status : rr ? rr->status : 0);
1889 if (last_error) {
1890 /* onerror threw an error, give up completely */
1891 break;
1892 }
1893 last_error = error_fmt;
1894 }
1895 else {
1896 last_error = NULL;
1897 }
1898
1899 /* Do *not* destroy the subrequest here; it may have allocated
1900 * variables in this r->subprocess_env in the subrequest's
1901 * r->pool, so that pool must survive as long as this request.
1902 * Yes, this is a memory leak. */
1903
1904 }
1905
1906 if (last_error) {
1907 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1908 }
1909
1910 return APR_SUCCESS;
1911 }
1912
1913 /*
1914 * <!--#echo [decoding="..."] [encoding="..."] var="..." [decoding="..."]
1915 * [encoding="..."] var="..." ... -->
1916 */
handle_echo(include_ctx_t * ctx,ap_filter_t * f,apr_bucket_brigade * bb)1917 static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
1918 apr_bucket_brigade *bb)
1919 {
1920 const char *encoding = "entity", *decoding = "none";
1921 request_rec *r = f->r;
1922 int error = 0;
1923
1924 if (!ctx->argc) {
1925 ap_log_rerror(APLOG_MARK,
1926 (ctx->flags & SSI_FLAG_PRINTING)
1927 ? APLOG_ERR : APLOG_WARNING,
1928 0, r, APLOGNO(01343)
1929 "missing argument for echo element in %s",
1930 r->filename);
1931 }
1932
1933 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1934 return APR_SUCCESS;
1935 }
1936
1937 if (!ctx->argc) {
1938 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1939 return APR_SUCCESS;
1940 }
1941
1942 while (1) {
1943 char *tag = NULL;
1944 char *tag_val = NULL;
1945
1946 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1947 if (!tag || !tag_val) {
1948 break;
1949 }
1950
1951 if (!strcmp(tag, "var")) {
1952 const char *val;
1953 const char *echo_text = NULL;
1954 apr_size_t e_len;
1955
1956 val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL,
1957 0, SSI_EXPAND_DROP_NAME),
1958 ctx);
1959
1960 if (val) {
1961 char *last = NULL;
1962 char *e, *d, *token;
1963
1964 echo_text = val;
1965
1966 d = apr_pstrdup(ctx->pool, decoding);
1967 token = apr_strtok(d, ", \t", &last);
1968
1969 while (token) {
1970 if (!ap_cstr_casecmp(token, "none")) {
1971 /* do nothing */
1972 }
1973 else if (!ap_cstr_casecmp(token, "url")) {
1974 char *buf = apr_pstrdup(ctx->pool, echo_text);
1975 ap_unescape_url(buf);
1976 echo_text = buf;
1977 }
1978 else if (!ap_cstr_casecmp(token, "urlencoded")) {
1979 char *buf = apr_pstrdup(ctx->pool, echo_text);
1980 ap_unescape_urlencoded(buf);
1981 echo_text = buf;
1982 }
1983 else if (!ap_cstr_casecmp(token, "entity")) {
1984 char *buf = apr_pstrdup(ctx->pool, echo_text);
1985 decodehtml(buf);
1986 echo_text = buf;
1987 }
1988 else if (!ap_cstr_casecmp(token, "base64")) {
1989 echo_text = ap_pbase64decode(ctx->dpool, echo_text);
1990 }
1991 else {
1992 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01344) "unknown value "
1993 "\"%s\" to parameter \"decoding\" of tag echo in "
1994 "%s", token, r->filename);
1995 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1996 error = 1;
1997 break;
1998 }
1999 token = apr_strtok(NULL, ", \t", &last);
2000 }
2001
2002 e = apr_pstrdup(ctx->pool, encoding);
2003 token = apr_strtok(e, ", \t", &last);
2004
2005 while (token) {
2006 if (!ap_cstr_casecmp(token, "none")) {
2007 /* do nothing */
2008 }
2009 else if (!ap_cstr_casecmp(token, "url")) {
2010 echo_text = ap_escape_uri(ctx->dpool, echo_text);
2011 }
2012 else if (!ap_cstr_casecmp(token, "urlencoded")) {
2013 echo_text = ap_escape_urlencoded(ctx->dpool, echo_text);
2014 }
2015 else if (!ap_cstr_casecmp(token, "entity")) {
2016 echo_text = ap_escape_html2(ctx->dpool, echo_text, 0);
2017 }
2018 else if (!ap_cstr_casecmp(token, "base64")) {
2019 char *buf;
2020 buf = ap_pbase64encode(ctx->dpool, (char *)echo_text);
2021 echo_text = buf;
2022 }
2023 else {
2024 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01345) "unknown value "
2025 "\"%s\" to parameter \"encoding\" of tag echo in "
2026 "%s", token, r->filename);
2027 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2028 error = 1;
2029 break;
2030 }
2031 token = apr_strtok(NULL, ", \t", &last);
2032 }
2033
2034 e_len = strlen(echo_text);
2035 }
2036 else {
2037 echo_text = ctx->intern->undefined_echo;
2038 e_len = ctx->intern->undefined_echo_len;
2039 }
2040
2041 if (error) {
2042 break;
2043 }
2044
2045 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(
2046 apr_pmemdup(ctx->pool, echo_text, e_len),
2047 e_len, ctx->pool, f->c->bucket_alloc));
2048 }
2049 else if (!strcmp(tag, "decoding")) {
2050 decoding = tag_val;
2051 }
2052 else if (!strcmp(tag, "encoding")) {
2053 encoding = tag_val;
2054 }
2055 else {
2056 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01346) "unknown parameter "
2057 "\"%s\" in tag echo of %s", tag, r->filename);
2058 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2059 break;
2060 }
2061 }
2062
2063 return APR_SUCCESS;
2064 }
2065
2066 /*
2067 * <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."]
2068 * [echomsg="..."] -->
2069 */
handle_config(include_ctx_t * ctx,ap_filter_t * f,apr_bucket_brigade * bb)2070 static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
2071 apr_bucket_brigade *bb)
2072 {
2073 request_rec *r = f->r;
2074 apr_table_t *env = r->subprocess_env;
2075
2076 if (!ctx->argc) {
2077 ap_log_rerror(APLOG_MARK,
2078 (ctx->flags & SSI_FLAG_PRINTING)
2079 ? APLOG_ERR : APLOG_WARNING,
2080 0, r, APLOGNO(01347)
2081 "missing argument for config element in %s",
2082 r->filename);
2083 }
2084
2085 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2086 return APR_SUCCESS;
2087 }
2088
2089 if (!ctx->argc) {
2090 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2091 return APR_SUCCESS;
2092 }
2093
2094 while (1) {
2095 char *tag = NULL;
2096 char *tag_val = NULL;
2097
2098 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
2099 if (!tag || !tag_val) {
2100 break;
2101 }
2102
2103 if (!strcmp(tag, "errmsg")) {
2104 ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2105 SSI_EXPAND_DROP_NAME);
2106 }
2107 else if (!strcmp(tag, "echomsg")) {
2108 ctx->intern->undefined_echo =
2109 ap_ssi_parse_string(ctx, tag_val, NULL, 0,SSI_EXPAND_DROP_NAME);
2110 ctx->intern->undefined_echo_len=strlen(ctx->intern->undefined_echo);
2111 }
2112 else if (!strcmp(tag, "timefmt")) {
2113 apr_time_t date = r->request_time;
2114
2115 ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2116 SSI_EXPAND_DROP_NAME);
2117
2118 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
2119 ctx->time_str, 0));
2120 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
2121 ctx->time_str, 1));
2122 apr_table_setn(env, "LAST_MODIFIED",
2123 ap_ht_time(r->pool, r->finfo.mtime,
2124 ctx->time_str, 0));
2125 }
2126 else if (!strcmp(tag, "sizefmt")) {
2127 char *parsed_string;
2128
2129 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2130 SSI_EXPAND_DROP_NAME);
2131 if (!strcmp(parsed_string, "bytes")) {
2132 ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
2133 }
2134 else if (!strcmp(parsed_string, "abbrev")) {
2135 ctx->flags &= SSI_FLAG_SIZE_ABBREV;
2136 }
2137 else {
2138 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01348) "unknown value "
2139 "\"%s\" to parameter \"sizefmt\" of tag config "
2140 "in %s", parsed_string, r->filename);
2141 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2142 break;
2143 }
2144 }
2145 else {
2146 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01349) "unknown parameter "
2147 "\"%s\" to tag config in %s", tag, r->filename);
2148 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2149 break;
2150 }
2151 }
2152
2153 return APR_SUCCESS;
2154 }
2155
2156 /*
2157 * <!--#fsize virtual|file="..." [virtual|file="..."] ... -->
2158 */
handle_fsize(include_ctx_t * ctx,ap_filter_t * f,apr_bucket_brigade * bb)2159 static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
2160 apr_bucket_brigade *bb)
2161 {
2162 request_rec *r = f->r;
2163
2164 if (!ctx->argc) {
2165 ap_log_rerror(APLOG_MARK,
2166 (ctx->flags & SSI_FLAG_PRINTING)
2167 ? APLOG_ERR : APLOG_WARNING,
2168 0, r, APLOGNO(01350)
2169 "missing argument for fsize element in %s",
2170 r->filename);
2171 }
2172
2173 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2174 return APR_SUCCESS;
2175 }
2176
2177 if (!ctx->argc) {
2178 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2179 return APR_SUCCESS;
2180 }
2181
2182 while (1) {
2183 char *tag = NULL;
2184 char *tag_val = NULL;
2185 apr_finfo_t finfo;
2186 char *parsed_string;
2187
2188 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2189 if (!tag || !tag_val) {
2190 break;
2191 }
2192
2193 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2194 SSI_EXPAND_DROP_NAME);
2195
2196 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
2197 char *buf;
2198 apr_size_t len;
2199
2200 if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
2201 buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5));
2202 len = 4; /* omit the \0 terminator */
2203 }
2204 else {
2205 apr_size_t l, x, pos;
2206 char *tmp;
2207
2208 tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size);
2209 len = l = strlen(tmp);
2210
2211 for (x = 0; x < l; ++x) {
2212 if (x && !((l - x) % 3)) {
2213 ++len;
2214 }
2215 }
2216
2217 if (len == l) {
2218 buf = apr_pstrmemdup(ctx->pool, tmp, len);
2219 }
2220 else {
2221 buf = apr_palloc(ctx->pool, len);
2222
2223 for (pos = x = 0; x < l; ++x) {
2224 if (x && !((l - x) % 3)) {
2225 buf[pos++] = ',';
2226 }
2227 buf[pos++] = tmp[x];
2228 }
2229 }
2230 }
2231
2232 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len,
2233 ctx->pool, f->c->bucket_alloc));
2234 }
2235 else {
2236 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2237 break;
2238 }
2239 }
2240
2241 return APR_SUCCESS;
2242 }
2243
2244 /*
2245 * <!--#flastmod virtual|file="..." [virtual|file="..."] ... -->
2246 */
handle_flastmod(include_ctx_t * ctx,ap_filter_t * f,apr_bucket_brigade * bb)2247 static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
2248 apr_bucket_brigade *bb)
2249 {
2250 request_rec *r = f->r;
2251
2252 if (!ctx->argc) {
2253 ap_log_rerror(APLOG_MARK,
2254 (ctx->flags & SSI_FLAG_PRINTING)
2255 ? APLOG_ERR : APLOG_WARNING,
2256 0, r, APLOGNO(01351)
2257 "missing argument for flastmod element in %s",
2258 r->filename);
2259 }
2260
2261 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2262 return APR_SUCCESS;
2263 }
2264
2265 if (!ctx->argc) {
2266 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2267 return APR_SUCCESS;
2268 }
2269
2270 while (1) {
2271 char *tag = NULL;
2272 char *tag_val = NULL;
2273 apr_finfo_t finfo;
2274 char *parsed_string;
2275
2276 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2277 if (!tag || !tag_val) {
2278 break;
2279 }
2280
2281 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2282 SSI_EXPAND_DROP_NAME);
2283
2284 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
2285 char *t_val;
2286 apr_size_t t_len;
2287
2288 t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0);
2289 t_len = strlen(t_val);
2290
2291 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len,
2292 ctx->pool, f->c->bucket_alloc));
2293 }
2294 else {
2295 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2296 break;
2297 }
2298 }
2299
2300 return APR_SUCCESS;
2301 }
2302
2303 /*
2304 * <!--#if expr="..." -->
2305 */
handle_if(include_ctx_t * ctx,ap_filter_t * f,apr_bucket_brigade * bb)2306 static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f,
2307 apr_bucket_brigade *bb)
2308 {
2309 char *tag = NULL;
2310 char *expr = NULL;
2311 request_rec *r = f->r;
2312 int expr_ret, was_error;
2313
2314 if (ctx->argc != 1) {
2315 ap_log_rerror(APLOG_MARK,
2316 (ctx->flags & SSI_FLAG_PRINTING)
2317 ? APLOG_ERR : APLOG_WARNING,
2318 0, r,
2319 (ctx->argc)
2320 ? APLOGNO(01352) "too many arguments for if element in %s"
2321 : APLOGNO(01353) "missing expr argument for if element in %s",
2322 r->filename);
2323 }
2324
2325 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2326 ++(ctx->if_nesting_level);
2327 return APR_SUCCESS;
2328 }
2329
2330 if (ctx->argc != 1) {
2331 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2332 return APR_SUCCESS;
2333 }
2334
2335 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2336
2337 if (strcmp(tag, "expr")) {
2338 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01354) "unknown parameter \"%s\" "
2339 "to tag if in %s", tag, r->filename);
2340 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2341 return APR_SUCCESS;
2342 }
2343
2344 if (!expr) {
2345 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01355) "missing expr value for if "
2346 "element in %s", r->filename);
2347 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2348 return APR_SUCCESS;
2349 }
2350
2351 DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr));
2352
2353 if (ctx->intern->legacy_expr)
2354 expr_ret = parse_expr(ctx, expr, &was_error);
2355 else
2356 expr_ret = parse_ap_expr(ctx, expr, &was_error);
2357
2358 if (was_error) {
2359 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2360 return APR_SUCCESS;
2361 }
2362
2363 if (expr_ret) {
2364 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2365 }
2366 else {
2367 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2368 }
2369
2370 DEBUG_DUMP_COND(ctx, " if");
2371
2372 ctx->if_nesting_level = 0;
2373
2374 return APR_SUCCESS;
2375 }
2376
2377 /*
2378 * <!--#elif expr="..." -->
2379 */
handle_elif(include_ctx_t * ctx,ap_filter_t * f,apr_bucket_brigade * bb)2380 static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f,
2381 apr_bucket_brigade *bb)
2382 {
2383 char *tag = NULL;
2384 char *expr = NULL;
2385 request_rec *r = f->r;
2386 int expr_ret, was_error;
2387
2388 if (ctx->argc != 1) {
2389 ap_log_rerror(APLOG_MARK,
2390 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2391 0, r,
2392 (ctx->argc)
2393 ? APLOGNO(01356) "too many arguments for if element in %s"
2394 : APLOGNO(01357) "missing expr argument for if element in %s",
2395 r->filename);
2396 }
2397
2398 if (ctx->if_nesting_level) {
2399 return APR_SUCCESS;
2400 }
2401
2402 if (ctx->argc != 1) {
2403 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2404 return APR_SUCCESS;
2405 }
2406
2407 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2408
2409 if (strcmp(tag, "expr")) {
2410 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01358) "unknown parameter \"%s\" "
2411 "to tag if in %s", tag, r->filename);
2412 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2413 return APR_SUCCESS;
2414 }
2415
2416 if (!expr) {
2417 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01359) "missing expr in elif "
2418 "statement: %s", r->filename);
2419 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2420 return APR_SUCCESS;
2421 }
2422
2423 DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr));
2424 DEBUG_DUMP_COND(ctx, " elif");
2425
2426 if (ctx->flags & SSI_FLAG_COND_TRUE) {
2427 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2428 return APR_SUCCESS;
2429 }
2430
2431 if (ctx->intern->legacy_expr)
2432 expr_ret = parse_expr(ctx, expr, &was_error);
2433 else
2434 expr_ret = parse_ap_expr(ctx, expr, &was_error);
2435
2436 if (was_error) {
2437 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2438 return APR_SUCCESS;
2439 }
2440
2441 if (expr_ret) {
2442 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2443 }
2444 else {
2445 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2446 }
2447
2448 DEBUG_DUMP_COND(ctx, " elif");
2449
2450 return APR_SUCCESS;
2451 }
2452
2453 /*
2454 * <!--#else -->
2455 */
handle_else(include_ctx_t * ctx,ap_filter_t * f,apr_bucket_brigade * bb)2456 static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f,
2457 apr_bucket_brigade *bb)
2458 {
2459 request_rec *r = f->r;
2460
2461 if (ctx->argc) {
2462 ap_log_rerror(APLOG_MARK,
2463 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2464 0, r, APLOGNO(01360)
2465 "else directive does not take tags in %s",
2466 r->filename);
2467 }
2468
2469 if (ctx->if_nesting_level) {
2470 return APR_SUCCESS;
2471 }
2472
2473 if (ctx->argc) {
2474 if (ctx->flags & SSI_FLAG_PRINTING) {
2475 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2476 }
2477
2478 return APR_SUCCESS;
2479 }
2480
2481 DEBUG_DUMP_COND(ctx, " else");
2482
2483 if (ctx->flags & SSI_FLAG_COND_TRUE) {
2484 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2485 }
2486 else {
2487 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2488 }
2489
2490 return APR_SUCCESS;
2491 }
2492
2493 /*
2494 * <!--#endif -->
2495 */
handle_endif(include_ctx_t * ctx,ap_filter_t * f,apr_bucket_brigade * bb)2496 static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f,
2497 apr_bucket_brigade *bb)
2498 {
2499 request_rec *r = f->r;
2500
2501 if (ctx->argc) {
2502 ap_log_rerror(APLOG_MARK,
2503 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2504 0, r, APLOGNO(01361)
2505 "endif directive does not take tags in %s",
2506 r->filename);
2507 }
2508
2509 if (ctx->if_nesting_level) {
2510 --(ctx->if_nesting_level);
2511 return APR_SUCCESS;
2512 }
2513
2514 if (ctx->argc) {
2515 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2516 return APR_SUCCESS;
2517 }
2518
2519 DEBUG_DUMP_COND(ctx, "endif");
2520
2521 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2522
2523 return APR_SUCCESS;
2524 }
2525
2526 /*
2527 * <!--#set var="..." value="..." ... -->
2528 */
handle_set(include_ctx_t * ctx,ap_filter_t * f,apr_bucket_brigade * bb)2529 static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
2530 apr_bucket_brigade *bb)
2531 {
2532 const char *encoding = "none", *decoding = "none";
2533 char *var = NULL;
2534 request_rec *r = f->r;
2535 request_rec *sub = r->main;
2536 apr_pool_t *p = r->pool;
2537 int error = 0;
2538
2539 if (ctx->argc < 2) {
2540 ap_log_rerror(APLOG_MARK,
2541 (ctx->flags & SSI_FLAG_PRINTING)
2542 ? APLOG_ERR : APLOG_WARNING,
2543 0, r,
2544 APLOGNO(01362) "missing argument for set element in %s",
2545 r->filename);
2546 }
2547
2548 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2549 return APR_SUCCESS;
2550 }
2551
2552 if (ctx->argc < 2) {
2553 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2554 return APR_SUCCESS;
2555 }
2556
2557 /* we need to use the 'main' request pool to set notes as that is
2558 * a notes lifetime
2559 */
2560 while (sub) {
2561 p = sub->pool;
2562 sub = sub->main;
2563 }
2564
2565 while (1) {
2566 char *tag = NULL;
2567 char *tag_val = NULL;
2568
2569 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
2570
2571 if (!tag || !tag_val) {
2572 break;
2573 }
2574
2575 if (!strcmp(tag, "var")) {
2576 decodehtml(tag_val);
2577 var = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2578 SSI_EXPAND_DROP_NAME);
2579 }
2580 else if (!strcmp(tag, "decoding")) {
2581 decoding = tag_val;
2582 }
2583 else if (!strcmp(tag, "encoding")) {
2584 encoding = tag_val;
2585 }
2586 else if (!strcmp(tag, "value")) {
2587 char *parsed_string;
2588
2589 if (!var) {
2590 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01363) "variable must "
2591 "precede value in set directive in %s",
2592 r->filename);
2593 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2594 break;
2595 }
2596
2597 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2598 SSI_EXPAND_DROP_NAME);
2599
2600 if (parsed_string) {
2601 char *last = NULL;
2602 char *e, *d, *token;
2603
2604 d = apr_pstrdup(ctx->pool, decoding);
2605 token = apr_strtok(d, ", \t", &last);
2606
2607 while (token) {
2608 if (!ap_cstr_casecmp(token, "none")) {
2609 /* do nothing */
2610 }
2611 else if (!ap_cstr_casecmp(token, "url")) {
2612 char *buf = apr_pstrdup(ctx->pool, parsed_string);
2613 ap_unescape_url(buf);
2614 parsed_string = buf;
2615 }
2616 else if (!ap_cstr_casecmp(token, "urlencoded")) {
2617 char *buf = apr_pstrdup(ctx->pool, parsed_string);
2618 ap_unescape_urlencoded(buf);
2619 parsed_string = buf;
2620 }
2621 else if (!ap_cstr_casecmp(token, "entity")) {
2622 char *buf = apr_pstrdup(ctx->pool, parsed_string);
2623 decodehtml(buf);
2624 parsed_string = buf;
2625 }
2626 else if (!ap_cstr_casecmp(token, "base64")) {
2627 parsed_string = ap_pbase64decode(ctx->dpool, parsed_string);
2628 }
2629 else {
2630 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01364) "unknown value "
2631 "\"%s\" to parameter \"decoding\" of tag set in "
2632 "%s", token, r->filename);
2633 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2634 error = 1;
2635 break;
2636 }
2637 token = apr_strtok(NULL, ", \t", &last);
2638 }
2639
2640 e = apr_pstrdup(ctx->pool, encoding);
2641 token = apr_strtok(e, ", \t", &last);
2642
2643 while (token) {
2644 if (!ap_cstr_casecmp(token, "none")) {
2645 /* do nothing */
2646 }
2647 else if (!ap_cstr_casecmp(token, "url")) {
2648 parsed_string = ap_escape_uri(ctx->dpool, parsed_string);
2649 }
2650 else if (!ap_cstr_casecmp(token, "urlencoded")) {
2651 parsed_string = ap_escape_urlencoded(ctx->dpool, parsed_string);
2652 }
2653 else if (!ap_cstr_casecmp(token, "entity")) {
2654 parsed_string = ap_escape_html2(ctx->dpool, parsed_string, 0);
2655 }
2656 else if (!ap_cstr_casecmp(token, "base64")) {
2657 char *buf;
2658 buf = ap_pbase64encode(ctx->dpool, (char *)parsed_string);
2659 parsed_string = buf;
2660 }
2661 else {
2662 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01365) "unknown value "
2663 "\"%s\" to parameter \"encoding\" of tag set in "
2664 "%s", token, r->filename);
2665 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2666 error = 1;
2667 break;
2668 }
2669 token = apr_strtok(NULL, ", \t", &last);
2670 }
2671
2672 }
2673
2674 if (error) {
2675 break;
2676 }
2677
2678 apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
2679 apr_pstrdup(p, parsed_string));
2680 }
2681 else {
2682 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01366) "Invalid tag for set "
2683 "directive in %s", r->filename);
2684 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2685 break;
2686 }
2687 }
2688
2689 return APR_SUCCESS;
2690 }
2691
2692 /*
2693 * <!--#printenv -->
2694 */
handle_printenv(include_ctx_t * ctx,ap_filter_t * f,apr_bucket_brigade * bb)2695 static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
2696 apr_bucket_brigade *bb)
2697 {
2698 request_rec *r = f->r;
2699 const apr_array_header_t *arr;
2700 const apr_table_entry_t *elts;
2701 int i;
2702
2703 if (ctx->argc) {
2704 ap_log_rerror(APLOG_MARK,
2705 (ctx->flags & SSI_FLAG_PRINTING)
2706 ? APLOG_ERR : APLOG_WARNING,
2707 0, r,
2708 APLOGNO(01367) "printenv directive does not take tags in %s",
2709 r->filename);
2710 }
2711
2712 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2713 return APR_SUCCESS;
2714 }
2715
2716 if (ctx->argc) {
2717 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2718 return APR_SUCCESS;
2719 }
2720
2721 arr = apr_table_elts(r->subprocess_env);
2722 elts = (apr_table_entry_t *)arr->elts;
2723
2724 for (i = 0; i < arr->nelts; ++i) {
2725 const char *key_text, *val_text;
2726
2727 /* get key */
2728 key_text = ap_escape_html(ctx->dpool, elts[i].key);
2729
2730 /* get value */
2731 val_text = elts[i].val;
2732 if (val_text == LAZY_VALUE)
2733 val_text = add_include_vars_lazy(r, elts[i].key, ctx->time_str);
2734 val_text = ap_escape_html(ctx->dpool, val_text);
2735
2736 apr_brigade_putstrs(bb, NULL, NULL, key_text, "=", val_text, "\n",
2737 NULL);
2738 }
2739
2740 ctx->flush_now = 1;
2741 return APR_SUCCESS;
2742 }
2743
2744
2745 /*
2746 * +-------------------------------------------------------+
2747 * | |
2748 * | Main Includes-Filter Engine
2749 * | |
2750 * +-------------------------------------------------------+
2751 */
2752
2753 /* This is an implementation of the BNDM search algorithm.
2754 *
2755 * Fast and Flexible String Matching by Combining Bit-parallelism and
2756 * Suffix Automata (2001)
2757 * Gonzalo Navarro, Mathieu Raffinot
2758 *
2759 * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
2760 *
2761 * Initial code submitted by Sascha Schumann.
2762 */
2763
2764 /* Precompile the bndm_t data structure. */
bndm_compile(apr_pool_t * pool,const char * n,apr_size_t nl)2765 static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl)
2766 {
2767 unsigned int x;
2768 const char *ne = n + nl;
2769 bndm_t *t = apr_palloc(pool, sizeof(*t));
2770
2771 memset(t->T, 0, sizeof(unsigned int) * 256);
2772 t->pattern_len = nl;
2773
2774 for (x = 1; n < ne; x <<= 1) {
2775 t->T[(unsigned char) *n++] |= x;
2776 }
2777
2778 t->x = x - 1;
2779
2780 return t;
2781 }
2782
2783 /* Implements the BNDM search algorithm (as described above).
2784 *
2785 * h - the string to look in
2786 * hl - length of the string to look for
2787 * t - precompiled bndm structure against the pattern
2788 *
2789 * Returns the count of character that is the first match or hl if no
2790 * match is found.
2791 */
bndm(bndm_t * t,const char * h,apr_size_t hl)2792 static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl)
2793 {
2794 const char *skip;
2795 const char *he, *p, *pi;
2796 unsigned int *T, x, d;
2797 apr_size_t nl;
2798
2799 he = h + hl;
2800
2801 T = t->T;
2802 x = t->x;
2803 nl = t->pattern_len;
2804
2805 pi = h - 1; /* pi: p initial */
2806 p = pi + nl; /* compare window right to left. point to the first char */
2807
2808 while (p < he) {
2809 skip = p;
2810 d = x;
2811 do {
2812 d &= T[(unsigned char) *p--];
2813 if (!d) {
2814 break;
2815 }
2816 if ((d & 1)) {
2817 if (p != pi) {
2818 skip = p;
2819 }
2820 else {
2821 return p - h + 1;
2822 }
2823 }
2824 d >>= 1;
2825 } while (d);
2826
2827 pi = skip;
2828 p = pi + nl;
2829 }
2830
2831 return hl;
2832 }
2833
2834 /*
2835 * returns the index position of the first byte of start_seq (or the len of
2836 * the buffer as non-match)
2837 */
find_start_sequence(include_ctx_t * ctx,const char * data,apr_size_t len)2838 static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data,
2839 apr_size_t len)
2840 {
2841 struct ssi_internal_ctx *intern = ctx->intern;
2842 apr_size_t slen = intern->start_seq_pat->pattern_len;
2843 apr_size_t index;
2844 const char *p, *ep;
2845
2846 if (len < slen) {
2847 p = data; /* try partial match at the end of the buffer (below) */
2848 }
2849 else {
2850 /* try fast bndm search over the buffer
2851 * (hopefully the whole start sequence can be found in this buffer)
2852 */
2853 index = bndm(intern->start_seq_pat, data, len);
2854
2855 /* wow, found it. ready. */
2856 if (index < len) {
2857 intern->state = PARSE_DIRECTIVE;
2858 return index;
2859 }
2860 else {
2861 /* ok, the pattern can't be found as whole in the buffer,
2862 * check the end for a partial match
2863 */
2864 p = data + len - slen + 1;
2865 }
2866 }
2867
2868 ep = data + len;
2869 do {
2870 while (p < ep && *p != *intern->start_seq) {
2871 ++p;
2872 }
2873
2874 index = p - data;
2875
2876 /* found a possible start_seq start */
2877 if (p < ep) {
2878 apr_size_t pos = 1;
2879
2880 ++p;
2881 while (p < ep && *p == intern->start_seq[pos]) {
2882 ++p;
2883 ++pos;
2884 }
2885
2886 /* partial match found. Store the info for the next round */
2887 if (p == ep) {
2888 intern->state = PARSE_HEAD;
2889 intern->parse_pos = pos;
2890 return index;
2891 }
2892 }
2893
2894 /* we must try all combinations; consider (e.g.) SSIStartTag "--->"
2895 * and a string data of "--.-" and the end of the buffer
2896 */
2897 p = data + index + 1;
2898 } while (p < ep);
2899
2900 /* no match */
2901 return len;
2902 }
2903
2904 /*
2905 * returns the first byte *after* the partial (or final) match.
2906 *
2907 * If we had to trick with the start_seq start, 'release' returns the
2908 * number of chars of the start_seq which appeared not to be part of a
2909 * full tag and may have to be passed down the filter chain.
2910 */
find_partial_start_sequence(include_ctx_t * ctx,const char * data,apr_size_t len,apr_size_t * release)2911 static apr_size_t find_partial_start_sequence(include_ctx_t *ctx,
2912 const char *data,
2913 apr_size_t len,
2914 apr_size_t *release)
2915 {
2916 struct ssi_internal_ctx *intern = ctx->intern;
2917 apr_size_t pos, spos = 0;
2918 apr_size_t slen = intern->start_seq_pat->pattern_len;
2919 const char *p, *ep;
2920
2921 pos = intern->parse_pos;
2922 ep = data + len;
2923 *release = 0;
2924
2925 do {
2926 p = data;
2927
2928 while (p < ep && pos < slen && *p == intern->start_seq[pos]) {
2929 ++p;
2930 ++pos;
2931 }
2932
2933 /* full match */
2934 if (pos == slen) {
2935 intern->state = PARSE_DIRECTIVE;
2936 return (p - data);
2937 }
2938
2939 /* the whole buffer is a partial match */
2940 if (p == ep) {
2941 intern->parse_pos = pos;
2942 return (p - data);
2943 }
2944
2945 /* No match so far, but again:
2946 * We must try all combinations, since the start_seq is a random
2947 * user supplied string
2948 *
2949 * So: look if the first char of start_seq appears somewhere within
2950 * the current partial match. If it does, try to start a match that
2951 * begins with this offset. (This can happen, if a strange
2952 * start_seq like "---->" spans buffers)
2953 */
2954 if (spos < intern->parse_pos) {
2955 do {
2956 ++spos;
2957 ++*release;
2958 p = intern->start_seq + spos;
2959 pos = intern->parse_pos - spos;
2960
2961 while (pos && *p != *intern->start_seq) {
2962 ++p;
2963 ++spos;
2964 ++*release;
2965 --pos;
2966 }
2967
2968 /* if a matching beginning char was found, try to match the
2969 * remainder of the old buffer.
2970 */
2971 if (pos > 1) {
2972 apr_size_t t = 1;
2973
2974 ++p;
2975 while (t < pos && *p == intern->start_seq[t]) {
2976 ++p;
2977 ++t;
2978 }
2979
2980 if (t == pos) {
2981 /* yeah, another partial match found in the *old*
2982 * buffer, now test the *current* buffer for
2983 * continuing match
2984 */
2985 break;
2986 }
2987 }
2988 } while (pos > 1);
2989
2990 if (pos) {
2991 continue;
2992 }
2993 }
2994
2995 break;
2996 } while (1); /* work hard to find a match ;-) */
2997
2998 /* no match at all, release all (wrongly) matched chars so far */
2999 *release = intern->parse_pos;
3000 intern->state = PARSE_PRE_HEAD;
3001 return 0;
3002 }
3003
3004 /*
3005 * returns the position after the directive
3006 */
find_directive(include_ctx_t * ctx,const char * data,apr_size_t len,char *** store,apr_size_t ** store_len)3007 static apr_size_t find_directive(include_ctx_t *ctx, const char *data,
3008 apr_size_t len, char ***store,
3009 apr_size_t **store_len)
3010 {
3011 struct ssi_internal_ctx *intern = ctx->intern;
3012 const char *p = data;
3013 const char *ep = data + len;
3014 apr_size_t pos;
3015
3016 switch (intern->state) {
3017 case PARSE_DIRECTIVE:
3018 while (p < ep && !apr_isspace(*p)) {
3019 /* we have to consider the case of missing space between directive
3020 * and end_seq (be somewhat lenient), e.g. <!--#printenv-->
3021 */
3022 if (*p == *intern->end_seq) {
3023 intern->state = PARSE_DIRECTIVE_TAIL;
3024 intern->parse_pos = 1;
3025 ++p;
3026 return (p - data);
3027 }
3028 ++p;
3029 }
3030
3031 if (p < ep) { /* found delimiter whitespace */
3032 intern->state = PARSE_DIRECTIVE_POSTNAME;
3033 *store = &intern->directive;
3034 *store_len = &intern->directive_len;
3035 }
3036
3037 break;
3038
3039 case PARSE_DIRECTIVE_TAIL:
3040 pos = intern->parse_pos;
3041
3042 while (p < ep && pos < intern->end_seq_len &&
3043 *p == intern->end_seq[pos]) {
3044 ++p;
3045 ++pos;
3046 }
3047
3048 /* full match, we're done */
3049 if (pos == intern->end_seq_len) {
3050 intern->state = PARSE_DIRECTIVE_POSTTAIL;
3051 *store = &intern->directive;
3052 *store_len = &intern->directive_len;
3053 break;
3054 }
3055
3056 /* partial match, the buffer is too small to match fully */
3057 if (p == ep) {
3058 intern->parse_pos = pos;
3059 break;
3060 }
3061
3062 /* no match. continue normal parsing */
3063 intern->state = PARSE_DIRECTIVE;
3064 return 0;
3065
3066 case PARSE_DIRECTIVE_POSTTAIL:
3067 intern->state = PARSE_EXECUTE;
3068 intern->directive_len -= intern->end_seq_len;
3069 /* continue immediately with the next state */
3070
3071 case PARSE_DIRECTIVE_POSTNAME:
3072 if (PARSE_DIRECTIVE_POSTNAME == intern->state) {
3073 intern->state = PARSE_PRE_ARG;
3074 }
3075 ctx->argc = 0;
3076 intern->argv = NULL;
3077
3078 if (!intern->directive_len) {
3079 intern->error = 1;
3080 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01368) "missing "
3081 "directive name in parsed document %s",
3082 ctx->r->filename);
3083 }
3084 else {
3085 char *sp = intern->directive;
3086 char *sep = intern->directive + intern->directive_len;
3087
3088 /* normalize directive name */
3089 for (; sp < sep; ++sp) {
3090 *sp = apr_tolower(*sp);
3091 }
3092 }
3093
3094 return 0;
3095
3096 default:
3097 /* get a rid of a gcc warning about unhandled enumerations */
3098 break;
3099 }
3100
3101 return (p - data);
3102 }
3103
3104 /*
3105 * find out whether the next token is (a possible) end_seq or an argument
3106 */
find_arg_or_tail(include_ctx_t * ctx,const char * data,apr_size_t len)3107 static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data,
3108 apr_size_t len)
3109 {
3110 struct ssi_internal_ctx *intern = ctx->intern;
3111 const char *p = data;
3112 const char *ep = data + len;
3113
3114 /* skip leading WS */
3115 while (p < ep && apr_isspace(*p)) {
3116 ++p;
3117 }
3118
3119 /* buffer doesn't consist of whitespaces only */
3120 if (p < ep) {
3121 intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG;
3122 }
3123
3124 return (p - data);
3125 }
3126
3127 /*
3128 * test the stream for end_seq. If it doesn't match at all, it must be an
3129 * argument
3130 */
find_tail(include_ctx_t * ctx,const char * data,apr_size_t len)3131 static apr_size_t find_tail(include_ctx_t *ctx, const char *data,
3132 apr_size_t len)
3133 {
3134 struct ssi_internal_ctx *intern = ctx->intern;
3135 const char *p = data;
3136 const char *ep = data + len;
3137 apr_size_t pos = intern->parse_pos;
3138
3139 if (PARSE_TAIL == intern->state) {
3140 intern->state = PARSE_TAIL_SEQ;
3141 pos = intern->parse_pos = 0;
3142 }
3143
3144 while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) {
3145 ++p;
3146 ++pos;
3147 }
3148
3149 /* bingo, full match */
3150 if (pos == intern->end_seq_len) {
3151 intern->state = PARSE_EXECUTE;
3152 return (p - data);
3153 }
3154
3155 /* partial match, the buffer is too small to match fully */
3156 if (p == ep) {
3157 intern->parse_pos = pos;
3158 return (p - data);
3159 }
3160
3161 /* no match. It must be an argument string then
3162 * The caller should cleanup and rewind to the reparse point
3163 */
3164 intern->state = PARSE_ARG;
3165 return 0;
3166 }
3167
3168 /*
3169 * extract name=value from the buffer
3170 * A pcre-pattern could look (similar to):
3171 * name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
3172 */
find_argument(include_ctx_t * ctx,const char * data,apr_size_t len,char *** store,apr_size_t ** store_len)3173 static apr_size_t find_argument(include_ctx_t *ctx, const char *data,
3174 apr_size_t len, char ***store,
3175 apr_size_t **store_len)
3176 {
3177 struct ssi_internal_ctx *intern = ctx->intern;
3178 const char *p = data;
3179 const char *ep = data + len;
3180
3181 switch (intern->state) {
3182 case PARSE_ARG:
3183 /*
3184 * create argument structure and append it to the current list
3185 */
3186 intern->current_arg = apr_palloc(ctx->dpool,
3187 sizeof(*intern->current_arg));
3188 intern->current_arg->next = NULL;
3189
3190 ++(ctx->argc);
3191 if (!intern->argv) {
3192 intern->argv = intern->current_arg;
3193 }
3194 else {
3195 arg_item_t *newarg = intern->argv;
3196
3197 while (newarg->next) {
3198 newarg = newarg->next;
3199 }
3200 newarg->next = intern->current_arg;
3201 }
3202
3203 /* check whether it's a valid one. If it begins with a quote, we
3204 * can safely assume, someone forgot the name of the argument
3205 */
3206 switch (*p) {
3207 case '"': case '\'': case '`':
3208 *store = NULL;
3209
3210 intern->state = PARSE_ARG_VAL;
3211 intern->quote = *p++;
3212 intern->current_arg->name = NULL;
3213 intern->current_arg->name_len = 0;
3214 intern->error = 1;
3215
3216 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01369) "missing "
3217 "argument name for value to tag %s in %s",
3218 apr_pstrmemdup(ctx->r->pool, intern->directive,
3219 intern->directive_len),
3220 ctx->r->filename);
3221
3222 return (p - data);
3223
3224 default:
3225 intern->state = PARSE_ARG_NAME;
3226 }
3227 /* continue immediately with next state */
3228
3229 case PARSE_ARG_NAME:
3230 while (p < ep && !apr_isspace(*p) && *p != '=') {
3231 ++p;
3232 }
3233
3234 if (p < ep) {
3235 intern->state = PARSE_ARG_POSTNAME;
3236 *store = &intern->current_arg->name;
3237 *store_len = &intern->current_arg->name_len;
3238 return (p - data);
3239 }
3240 break;
3241
3242 case PARSE_ARG_POSTNAME:
3243 intern->current_arg->name = apr_pstrmemdup(ctx->dpool,
3244 intern->current_arg->name,
3245 intern->current_arg->name_len);
3246 if (!intern->current_arg->name_len) {
3247 intern->error = 1;
3248 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01370) "missing "
3249 "argument name for value to tag %s in %s",
3250 apr_pstrmemdup(ctx->r->pool, intern->directive,
3251 intern->directive_len),
3252 ctx->r->filename);
3253 }
3254 else {
3255 ap_str_tolower(intern->current_arg->name);
3256 }
3257
3258 intern->state = PARSE_ARG_EQ;
3259 /* continue with next state immediately */
3260
3261 case PARSE_ARG_EQ:
3262 *store = NULL;
3263
3264 while (p < ep && apr_isspace(*p)) {
3265 ++p;
3266 }
3267
3268 if (p < ep) {
3269 if (*p == '=') {
3270 intern->state = PARSE_ARG_PREVAL;
3271 ++p;
3272 }
3273 else { /* no value */
3274 intern->current_arg->value = NULL;
3275 intern->state = PARSE_PRE_ARG;
3276 }
3277
3278 return (p - data);
3279 }
3280 break;
3281
3282 case PARSE_ARG_PREVAL:
3283 *store = NULL;
3284
3285 while (p < ep && apr_isspace(*p)) {
3286 ++p;
3287 }
3288
3289 /* buffer doesn't consist of whitespaces only */
3290 if (p < ep) {
3291 intern->state = PARSE_ARG_VAL;
3292 switch (*p) {
3293 case '"': case '\'': case '`':
3294 intern->quote = *p++;
3295 break;
3296 default:
3297 intern->quote = '\0';
3298 break;
3299 }
3300
3301 return (p - data);
3302 }
3303 break;
3304
3305 case PARSE_ARG_VAL_ESC:
3306 if (*p == intern->quote) {
3307 ++p;
3308 }
3309 intern->state = PARSE_ARG_VAL;
3310 /* continue with next state immediately */
3311
3312 case PARSE_ARG_VAL:
3313 for (; p < ep; ++p) {
3314 if (intern->quote && *p == '\\') {
3315 ++p;
3316 if (p == ep) {
3317 intern->state = PARSE_ARG_VAL_ESC;
3318 break;
3319 }
3320
3321 if (*p != intern->quote) {
3322 --p;
3323 }
3324 }
3325 else if (intern->quote && *p == intern->quote) {
3326 ++p;
3327 *store = &intern->current_arg->value;
3328 *store_len = &intern->current_arg->value_len;
3329 intern->state = PARSE_ARG_POSTVAL;
3330 break;
3331 }
3332 else if (!intern->quote && apr_isspace(*p)) {
3333 ++p;
3334 *store = &intern->current_arg->value;
3335 *store_len = &intern->current_arg->value_len;
3336 intern->state = PARSE_ARG_POSTVAL;
3337 break;
3338 }
3339 }
3340
3341 return (p - data);
3342
3343 case PARSE_ARG_POSTVAL:
3344 /*
3345 * The value is still the raw input string. Finally clean it up.
3346 */
3347 --(intern->current_arg->value_len);
3348
3349 /* strip quote escaping \ from the string */
3350 if (intern->quote) {
3351 apr_size_t shift = 0;
3352 char *sp;
3353
3354 sp = intern->current_arg->value;
3355 ep = intern->current_arg->value + intern->current_arg->value_len;
3356 while (sp < ep && *sp != '\\') {
3357 ++sp;
3358 }
3359 for (; sp < ep; ++sp) {
3360 if (*sp == '\\' && sp[1] == intern->quote) {
3361 ++sp;
3362 ++shift;
3363 }
3364 if (shift) {
3365 *(sp-shift) = *sp;
3366 }
3367 }
3368
3369 intern->current_arg->value_len -= shift;
3370 }
3371
3372 intern->current_arg->value[intern->current_arg->value_len] = '\0';
3373 intern->state = PARSE_PRE_ARG;
3374
3375 return 0;
3376
3377 default:
3378 /* get a rid of a gcc warning about unhandled enumerations */
3379 break;
3380 }
3381
3382 return len; /* partial match of something */
3383 }
3384
3385 /*
3386 * This is the main loop over the current bucket brigade.
3387 */
send_parsed_content(ap_filter_t * f,apr_bucket_brigade * bb)3388 static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
3389 {
3390 include_ctx_t *ctx = f->ctx;
3391 struct ssi_internal_ctx *intern = ctx->intern;
3392 request_rec *r = f->r;
3393 apr_bucket *b = APR_BRIGADE_FIRST(bb);
3394 apr_bucket_brigade *pass_bb;
3395 apr_status_t rv = APR_SUCCESS;
3396 char *magic; /* magic pointer for sentinel use */
3397
3398 /* fast exit */
3399 if (APR_BRIGADE_EMPTY(bb)) {
3400 return APR_SUCCESS;
3401 }
3402
3403 /* we may crash, since already cleaned up; hand over the responsibility
3404 * to the next filter;-)
3405 */
3406 if (intern->seen_eos) {
3407 return ap_pass_brigade(f->next, bb);
3408 }
3409
3410 /* All stuff passed along has to be put into that brigade */
3411 pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3412
3413 /* initialization for this loop */
3414 intern->bytes_read = 0;
3415 intern->error = 0;
3416 ctx->flush_now = 0;
3417
3418 /* loop over the current bucket brigade */
3419 while (b != APR_BRIGADE_SENTINEL(bb)) {
3420 const char *data = NULL;
3421 apr_size_t len, index, release;
3422 apr_bucket *newb = NULL;
3423 char **store = &magic;
3424 apr_size_t *store_len = NULL;
3425
3426 /* handle meta buckets before reading any data */
3427 if (APR_BUCKET_IS_METADATA(b)) {
3428 newb = APR_BUCKET_NEXT(b);
3429
3430 APR_BUCKET_REMOVE(b);
3431
3432 if (APR_BUCKET_IS_EOS(b)) {
3433 intern->seen_eos = 1;
3434
3435 /* Hit end of stream, time for cleanup ... But wait!
3436 * Perhaps we're not ready yet. We may have to loop one or
3437 * two times again to finish our work. In that case, we
3438 * just re-insert the EOS bucket to allow for an extra loop.
3439 *
3440 * PARSE_EXECUTE means, we've hit a directive just before the
3441 * EOS, which is now waiting for execution.
3442 *
3443 * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
3444 * no argument and no space between directive and end_seq
3445 * just before the EOS. (consider <!--#printenv--> as last
3446 * or only string within the stream). This state, however,
3447 * just cleans up and turns itself to PARSE_EXECUTE, which
3448 * will be passed through within the next (and actually
3449 * last) round.
3450 */
3451 if (PARSE_EXECUTE == intern->state ||
3452 PARSE_DIRECTIVE_POSTTAIL == intern->state) {
3453 APR_BUCKET_INSERT_BEFORE(newb, b);
3454 }
3455 else {
3456 break; /* END OF STREAM */
3457 }
3458 }
3459 else {
3460 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3461
3462 if (APR_BUCKET_IS_FLUSH(b)) {
3463 ctx->flush_now = 1;
3464 }
3465
3466 b = newb;
3467 continue;
3468 }
3469 }
3470
3471 /* enough is enough ... */
3472 if (ctx->flush_now ||
3473 intern->bytes_read > AP_MIN_BYTES_TO_WRITE) {
3474
3475 if (!APR_BRIGADE_EMPTY(pass_bb)) {
3476 rv = ap_pass_brigade(f->next, pass_bb);
3477 if (rv != APR_SUCCESS) {
3478 apr_brigade_destroy(pass_bb);
3479 return rv;
3480 }
3481 }
3482
3483 ctx->flush_now = 0;
3484 intern->bytes_read = 0;
3485 }
3486
3487 /* read the current bucket data */
3488 len = 0;
3489 if (!intern->seen_eos) {
3490 if (intern->bytes_read > 0) {
3491 rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
3492 if (APR_STATUS_IS_EAGAIN(rv)) {
3493 ctx->flush_now = 1;
3494 continue;
3495 }
3496 }
3497
3498 if (!len || rv != APR_SUCCESS) {
3499 rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
3500 }
3501
3502 if (rv != APR_SUCCESS) {
3503 apr_brigade_destroy(pass_bb);
3504 return rv;
3505 }
3506
3507 intern->bytes_read += len;
3508 }
3509
3510 /* zero length bucket, fetch next one */
3511 if (!len && !intern->seen_eos) {
3512 b = APR_BUCKET_NEXT(b);
3513 continue;
3514 }
3515
3516 /*
3517 * it's actually a data containing bucket, start/continue parsing
3518 */
3519
3520 switch (intern->state) {
3521 /* no current tag; search for start sequence */
3522 case PARSE_PRE_HEAD:
3523 index = find_start_sequence(ctx, data, len);
3524
3525 if (index < len) {
3526 apr_bucket_split(b, index);
3527 }
3528
3529 newb = APR_BUCKET_NEXT(b);
3530 if (ctx->flags & SSI_FLAG_PRINTING) {
3531 APR_BUCKET_REMOVE(b);
3532 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3533 }
3534 else {
3535 apr_bucket_delete(b);
3536 }
3537
3538 if (index < len) {
3539 /* now delete the start_seq stuff from the remaining bucket */
3540 if (PARSE_DIRECTIVE == intern->state) { /* full match */
3541 apr_bucket_split(newb, intern->start_seq_pat->pattern_len);
3542 ctx->flush_now = 1; /* pass pre-tag stuff */
3543 }
3544
3545 b = APR_BUCKET_NEXT(newb);
3546 apr_bucket_delete(newb);
3547 }
3548 else {
3549 b = newb;
3550 }
3551
3552 break;
3553
3554 /* we're currently looking for the end of the start sequence */
3555 case PARSE_HEAD:
3556 index = find_partial_start_sequence(ctx, data, len, &release);
3557
3558 /* check if we mismatched earlier and have to release some chars */
3559 if (release && (ctx->flags & SSI_FLAG_PRINTING)) {
3560 char *to_release = apr_pmemdup(ctx->pool, intern->start_seq, release);
3561
3562 newb = apr_bucket_pool_create(to_release, release, ctx->pool,
3563 f->c->bucket_alloc);
3564 APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
3565 }
3566
3567 if (index) { /* any match */
3568 /* now delete the start_seq stuff from the remaining bucket */
3569 if (PARSE_DIRECTIVE == intern->state) { /* final match */
3570 apr_bucket_split(b, index);
3571 ctx->flush_now = 1; /* pass pre-tag stuff */
3572 }
3573 newb = APR_BUCKET_NEXT(b);
3574 apr_bucket_delete(b);
3575 b = newb;
3576 }
3577
3578 break;
3579
3580 /* we're currently grabbing the directive name */
3581 case PARSE_DIRECTIVE:
3582 case PARSE_DIRECTIVE_POSTNAME:
3583 case PARSE_DIRECTIVE_TAIL:
3584 case PARSE_DIRECTIVE_POSTTAIL:
3585 index = find_directive(ctx, data, len, &store, &store_len);
3586
3587 if (index) {
3588 apr_bucket_split(b, index);
3589 newb = APR_BUCKET_NEXT(b);
3590 }
3591
3592 if (store) {
3593 if (index) {
3594 APR_BUCKET_REMOVE(b);
3595 apr_bucket_setaside(b, r->pool);
3596 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3597 b = newb;
3598 }
3599
3600 /* time for cleanup? */
3601 if (store != &magic) {
3602 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3603 ctx->dpool);
3604 apr_brigade_cleanup(intern->tmp_bb);
3605 }
3606 }
3607 else if (index) {
3608 apr_bucket_delete(b);
3609 b = newb;
3610 }
3611
3612 break;
3613
3614 /* skip WS and find out what comes next (arg or end_seq) */
3615 case PARSE_PRE_ARG:
3616 index = find_arg_or_tail(ctx, data, len);
3617
3618 if (index) { /* skipped whitespaces */
3619 if (index < len) {
3620 apr_bucket_split(b, index);
3621 }
3622 newb = APR_BUCKET_NEXT(b);
3623 apr_bucket_delete(b);
3624 b = newb;
3625 }
3626
3627 break;
3628
3629 /* currently parsing name[=val] */
3630 case PARSE_ARG:
3631 case PARSE_ARG_NAME:
3632 case PARSE_ARG_POSTNAME:
3633 case PARSE_ARG_EQ:
3634 case PARSE_ARG_PREVAL:
3635 case PARSE_ARG_VAL:
3636 case PARSE_ARG_VAL_ESC:
3637 case PARSE_ARG_POSTVAL:
3638 index = find_argument(ctx, data, len, &store, &store_len);
3639
3640 if (index) {
3641 apr_bucket_split(b, index);
3642 newb = APR_BUCKET_NEXT(b);
3643 }
3644
3645 if (store) {
3646 if (index) {
3647 APR_BUCKET_REMOVE(b);
3648 apr_bucket_setaside(b, r->pool);
3649 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3650 b = newb;
3651 }
3652
3653 /* time for cleanup? */
3654 if (store != &magic) {
3655 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3656 ctx->dpool);
3657 apr_brigade_cleanup(intern->tmp_bb);
3658 }
3659 }
3660 else if (index) {
3661 apr_bucket_delete(b);
3662 b = newb;
3663 }
3664
3665 break;
3666
3667 /* try to match end_seq at current pos. */
3668 case PARSE_TAIL:
3669 case PARSE_TAIL_SEQ:
3670 index = find_tail(ctx, data, len);
3671
3672 switch (intern->state) {
3673 case PARSE_EXECUTE: /* full match */
3674 apr_bucket_split(b, index);
3675 newb = APR_BUCKET_NEXT(b);
3676 apr_bucket_delete(b);
3677 b = newb;
3678 break;
3679
3680 case PARSE_ARG: /* no match */
3681 /* PARSE_ARG must reparse at the beginning */
3682 APR_BRIGADE_PREPEND(bb, intern->tmp_bb);
3683 b = APR_BRIGADE_FIRST(bb);
3684 break;
3685
3686 default: /* partial match */
3687 newb = APR_BUCKET_NEXT(b);
3688 APR_BUCKET_REMOVE(b);
3689 apr_bucket_setaside(b, r->pool);
3690 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3691 b = newb;
3692 break;
3693 }
3694
3695 break;
3696
3697 /* now execute the parsed directive, cleanup the space and
3698 * start again with PARSE_PRE_HEAD
3699 */
3700 case PARSE_EXECUTE:
3701 /* if there was an error, it was already logged; just stop here */
3702 if (intern->error) {
3703 if (ctx->flags & SSI_FLAG_PRINTING) {
3704 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3705 intern->error = 0;
3706 }
3707 }
3708 else {
3709 include_handler_fn_t *handle_func;
3710
3711 handle_func =
3712 (include_handler_fn_t *)apr_hash_get(include_handlers, intern->directive,
3713 intern->directive_len);
3714
3715 if (handle_func) {
3716 DEBUG_INIT(ctx, f, pass_bb);
3717 rv = handle_func(ctx, f, pass_bb);
3718 if (rv != APR_SUCCESS) {
3719 apr_brigade_destroy(pass_bb);
3720 return rv;
3721 }
3722 }
3723 else {
3724 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01371)
3725 "unknown directive \"%s\" in parsed doc %s",
3726 apr_pstrmemdup(r->pool, intern->directive,
3727 intern->directive_len),
3728 r->filename);
3729 if (ctx->flags & SSI_FLAG_PRINTING) {
3730 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3731 }
3732 }
3733 }
3734
3735 /* cleanup */
3736 apr_pool_clear(ctx->dpool);
3737 apr_brigade_cleanup(intern->tmp_bb);
3738
3739 /* Oooof. Done here, start next round */
3740 intern->state = PARSE_PRE_HEAD;
3741 break;
3742
3743 } /* switch(ctx->state) */
3744
3745 } /* while (brigade) */
3746
3747 /* End of stream. Final cleanup */
3748 if (intern->seen_eos) {
3749 if (PARSE_HEAD == intern->state) {
3750 if (ctx->flags & SSI_FLAG_PRINTING) {
3751 char *to_release = apr_pmemdup(ctx->pool, intern->start_seq,
3752 intern->parse_pos);
3753
3754 APR_BRIGADE_INSERT_TAIL(pass_bb,
3755 apr_bucket_pool_create(to_release,
3756 intern->parse_pos, ctx->pool,
3757 f->c->bucket_alloc));
3758 }
3759 }
3760 else if (PARSE_PRE_HEAD != intern->state) {
3761 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01372)
3762 "SSI directive was not properly finished at the end "
3763 "of parsed document %s", r->filename);
3764 if (ctx->flags & SSI_FLAG_PRINTING) {
3765 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3766 }
3767 }
3768
3769 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
3770 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01373)
3771 "missing closing endif directive in parsed document"
3772 " %s", r->filename);
3773 }
3774
3775 /* cleanup our temporary memory */
3776 apr_brigade_destroy(intern->tmp_bb);
3777 apr_pool_destroy(ctx->dpool);
3778
3779 /* don't forget to finally insert the EOS bucket */
3780 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3781 }
3782
3783 /* if something's left over, pass it along */
3784 if (!APR_BRIGADE_EMPTY(pass_bb)) {
3785 rv = ap_pass_brigade(f->next, pass_bb);
3786 }
3787 else {
3788 rv = APR_SUCCESS;
3789 apr_brigade_destroy(pass_bb);
3790 }
3791 return rv;
3792 }
3793
3794
3795 /*
3796 * +-------------------------------------------------------+
3797 * | |
3798 * | Runtime Hooks
3799 * | |
3800 * +-------------------------------------------------------+
3801 */
3802
includes_setup(ap_filter_t * f)3803 static int includes_setup(ap_filter_t *f)
3804 {
3805 include_dir_config *conf = ap_get_module_config(f->r->per_dir_config,
3806 &include_module);
3807
3808 /* When our xbithack value isn't set to full or our platform isn't
3809 * providing group-level protection bits or our group-level bits do not
3810 * have group-execite on, we will set the no_local_copy value to 1 so
3811 * that we will not send 304s.
3812 */
3813 if ((conf->xbithack != XBITHACK_FULL)
3814 || !(f->r->finfo.valid & APR_FINFO_GPROT)
3815 || !(f->r->finfo.protection & APR_GEXECUTE)) {
3816 f->r->no_local_copy = 1;
3817 }
3818
3819 /* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4.
3820 * We don't know if we are going to be including a file or executing
3821 * a program - in either case a strong ETag header will likely be invalid.
3822 */
3823 if (conf->etag <= 0) {
3824 apr_table_setn(f->r->notes, "no-etag", "");
3825 }
3826
3827 return OK;
3828 }
3829
includes_filter(ap_filter_t * f,apr_bucket_brigade * b)3830 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
3831 {
3832 request_rec *r = f->r;
3833 request_rec *parent;
3834 include_dir_config *conf = ap_get_module_config(r->per_dir_config,
3835 &include_module);
3836
3837 include_server_config *sconf= ap_get_module_config(r->server->module_config,
3838 &include_module);
3839
3840 if (!(ap_allow_options(r) & OPT_INCLUDES)) {
3841 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01374)
3842 "mod_include: Options +Includes (or IncludesNoExec) "
3843 "wasn't set, INCLUDES filter removed: %s", r->uri);
3844 ap_remove_output_filter(f);
3845 return ap_pass_brigade(f->next, b);
3846 }
3847
3848 if (!f->ctx) {
3849 struct ssi_internal_ctx *intern;
3850 include_ctx_t *ctx;
3851
3852 /* create context for this filter */
3853 f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx));
3854 ctx->r = r;
3855 ctx->intern = intern = apr_palloc(r->pool, sizeof(*ctx->intern));
3856 ctx->pool = r->pool;
3857 apr_pool_create(&ctx->dpool, ctx->pool);
3858 apr_pool_tag(ctx->dpool, "includes_dpool");
3859
3860 /* runtime data */
3861 intern->tmp_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3862 intern->seen_eos = 0;
3863 intern->state = PARSE_PRE_HEAD;
3864 ctx->flags = (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
3865 if ((ap_allow_options(r) & OPT_INC_WITH_EXEC) == 0) {
3866 ctx->flags |= SSI_FLAG_NO_EXEC;
3867 }
3868 intern->legacy_expr = (conf->legacy_expr > 0);
3869 intern->expr_eval_ctx = NULL;
3870 intern->expr_err = NULL;
3871 intern->expr_vary_this = NULL;
3872
3873 ctx->if_nesting_level = 0;
3874 intern->re = NULL;
3875
3876 ctx->error_str = conf->default_error_msg ? conf->default_error_msg :
3877 DEFAULT_ERROR_MSG;
3878 ctx->time_str = conf->default_time_fmt ? conf->default_time_fmt :
3879 DEFAULT_TIME_FORMAT;
3880 intern->start_seq = sconf->default_start_tag;
3881 intern->start_seq_pat = bndm_compile(ctx->pool, intern->start_seq,
3882 strlen(intern->start_seq));
3883 intern->end_seq = sconf->default_end_tag;
3884 intern->end_seq_len = strlen(intern->end_seq);
3885 intern->undefined_echo = conf->undefined_echo ? conf->undefined_echo :
3886 DEFAULT_UNDEFINED_ECHO;
3887 intern->undefined_echo_len = strlen(intern->undefined_echo);
3888 }
3889
3890 if ((parent = ap_get_module_config(r->request_config, &include_module))) {
3891 /* Kludge --- for nested includes, we want to keep the subprocess
3892 * environment of the base document (for compatibility); that means
3893 * torquing our own last_modified date as well so that the
3894 * LAST_MODIFIED variable gets reset to the proper value if the
3895 * nested document resets <!--#config timefmt -->.
3896 */
3897 r->subprocess_env = r->main->subprocess_env;
3898 apr_pool_join(r->main->pool, r->pool);
3899 r->finfo.mtime = r->main->finfo.mtime;
3900 }
3901 else {
3902 /* we're not a nested include, so we create an initial
3903 * environment */
3904 ap_add_common_vars(r);
3905 ap_add_cgi_vars(r);
3906 add_include_vars(r);
3907 }
3908 /* Always unset the content-length. There is no way to know if
3909 * the content will be modified at some point by send_parsed_content.
3910 * It is very possible for us to not find any content in the first
3911 * 9k of the file, but still have to modify the content of the file.
3912 * If we are going to pass the file through send_parsed_content, then
3913 * the content-length should just be unset.
3914 */
3915 apr_table_unset(f->r->headers_out, "Content-Length");
3916
3917 /* Always unset the Last-Modified field - see RFC2616 - 13.3.4.
3918 * We don't know if we are going to be including a file or executing
3919 * a program which may change the Last-Modified header or make the
3920 * content completely dynamic. Therefore, we can't support these
3921 * headers.
3922 *
3923 * Exception: XBitHack full means we *should* set the
3924 * Last-Modified field.
3925 *
3926 * SSILastModified on means we *should* set the Last-Modified field
3927 * if not present, or respect an existing value if present.
3928 */
3929
3930 /* Must we respect the last modified header? By default, no */
3931 if (conf->lastmodified > 0) {
3932
3933 /* update the last modified if we have a valid time, and only if
3934 * we don't already have a valid last modified.
3935 */
3936 if (r->finfo.valid & APR_FINFO_MTIME
3937 && !apr_table_get(f->r->headers_out, "Last-Modified")) {
3938 ap_update_mtime(r, r->finfo.mtime);
3939 ap_set_last_modified(r);
3940 }
3941
3942 }
3943
3944 /* Assure the platform supports Group protections */
3945 else if (((conf->xbithack == XBITHACK_FULL ||
3946 (conf->xbithack == XBITHACK_UNSET &&
3947 DEFAULT_XBITHACK == XBITHACK_FULL))
3948 && (r->finfo.valid & APR_FINFO_GPROT)
3949 && (r->finfo.protection & APR_GEXECUTE))) {
3950 ap_update_mtime(r, r->finfo.mtime);
3951 ap_set_last_modified(r);
3952 }
3953 else {
3954 apr_table_unset(f->r->headers_out, "Last-Modified");
3955 }
3956
3957 /* add QUERY stuff to env cause it ain't yet */
3958 if (r->args) {
3959 char *arg_copy = apr_pstrdup(r->pool, r->args);
3960
3961 apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
3962 ap_unescape_url(arg_copy);
3963 apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
3964 ap_escape_shell_cmd(r->pool, arg_copy));
3965 }
3966
3967 return send_parsed_content(f, b);
3968 }
3969
include_fixup(request_rec * r)3970 static int include_fixup(request_rec *r)
3971 {
3972 if (r->handler && (strcmp(r->handler, "server-parsed") == 0))
3973 {
3974 if (!r->content_type || !*r->content_type) {
3975 ap_set_content_type(r, "text/html");
3976 }
3977 r->handler = "default-handler";
3978 }
3979 else
3980 #if defined(OS2) || defined(WIN32) || defined(NETWARE)
3981 /* These OS's don't support xbithack. This is being worked on. */
3982 {
3983 return DECLINED;
3984 }
3985 #else
3986 {
3987 include_dir_config *conf = ap_get_module_config(r->per_dir_config,
3988 &include_module);
3989
3990 if (conf->xbithack == XBITHACK_OFF ||
3991 (DEFAULT_XBITHACK == XBITHACK_OFF &&
3992 conf->xbithack == XBITHACK_UNSET))
3993 {
3994 return DECLINED;
3995 }
3996
3997 if (!(r->finfo.protection & APR_UEXECUTE)) {
3998 return DECLINED;
3999 }
4000
4001 if (!r->content_type || strncmp(r->content_type, "text/html", 9)) {
4002 return DECLINED;
4003 }
4004 }
4005 #endif
4006
4007 /* We always return declined, because the default handler actually
4008 * serves the file. All we have to do is add the filter.
4009 */
4010 ap_add_output_filter("INCLUDES", NULL, r, r->connection);
4011 return DECLINED;
4012 }
4013
4014
4015 /*
4016 * +-------------------------------------------------------+
4017 * | |
4018 * | Configuration Handling
4019 * | |
4020 * +-------------------------------------------------------+
4021 */
4022
create_includes_dir_config(apr_pool_t * p,char * dummy)4023 static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
4024 {
4025 include_dir_config *result = apr_pcalloc(p, sizeof(include_dir_config));
4026
4027 result->xbithack = XBITHACK_UNSET;
4028 result->lastmodified = UNSET;
4029 result->etag = UNSET;
4030 result->legacy_expr = UNSET;
4031
4032 return result;
4033 }
4034
4035 #define MERGE(b,o,n,val,unset) n->val = o->val != unset ? o->val : b->val
merge_includes_dir_config(apr_pool_t * p,void * basev,void * overridesv)4036 static void *merge_includes_dir_config(apr_pool_t *p, void *basev, void *overridesv)
4037 {
4038 include_dir_config *base = (include_dir_config *)basev,
4039 *over = (include_dir_config *)overridesv,
4040 *new = apr_palloc(p, sizeof(include_dir_config));
4041 MERGE(base, over, new, default_error_msg, NULL);
4042 MERGE(base, over, new, default_time_fmt, NULL);
4043 MERGE(base, over, new, undefined_echo, NULL);
4044 MERGE(base, over, new, xbithack, XBITHACK_UNSET);
4045 MERGE(base, over, new, lastmodified, UNSET);
4046 MERGE(base, over, new, etag, UNSET);
4047 MERGE(base, over, new, legacy_expr, UNSET);
4048 return new;
4049 }
4050
create_includes_server_config(apr_pool_t * p,server_rec * server)4051 static void *create_includes_server_config(apr_pool_t *p, server_rec *server)
4052 {
4053 include_server_config *result;
4054
4055 result = apr_palloc(p, sizeof(include_server_config));
4056 result->default_end_tag = DEFAULT_END_SEQUENCE;
4057 result->default_start_tag = DEFAULT_START_SEQUENCE;
4058
4059 return result;
4060 }
4061
set_xbithack(cmd_parms * cmd,void * mconfig,const char * arg)4062 static const char *set_xbithack(cmd_parms *cmd, void *mconfig, const char *arg)
4063 {
4064 include_dir_config *conf = mconfig;
4065
4066 if (!strcasecmp(arg, "off")) {
4067 conf->xbithack = XBITHACK_OFF;
4068 }
4069 else if (!strcasecmp(arg, "on")) {
4070 conf->xbithack = XBITHACK_ON;
4071 }
4072 else if (!strcasecmp(arg, "full")) {
4073 conf->xbithack = XBITHACK_FULL;
4074 }
4075 else {
4076 return "XBitHack must be set to Off, On, or Full";
4077 }
4078
4079 return NULL;
4080 }
4081
set_default_start_tag(cmd_parms * cmd,void * mconfig,const char * tag)4082 static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig,
4083 const char *tag)
4084 {
4085 include_server_config *conf;
4086 const char *p = tag;
4087
4088 /* be consistent. (See below in set_default_end_tag) */
4089 while (*p) {
4090 if (apr_isspace(*p)) {
4091 return "SSIStartTag may not contain any whitespaces";
4092 }
4093 ++p;
4094 }
4095
4096 conf= ap_get_module_config(cmd->server->module_config , &include_module);
4097 conf->default_start_tag = tag;
4098
4099 return NULL;
4100 }
4101
set_default_end_tag(cmd_parms * cmd,void * mconfig,const char * tag)4102 static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig,
4103 const char *tag)
4104 {
4105 include_server_config *conf;
4106 const char *p = tag;
4107
4108 /* sanity check. The parser may fail otherwise */
4109 while (*p) {
4110 if (apr_isspace(*p)) {
4111 return "SSIEndTag may not contain any whitespaces";
4112 }
4113 ++p;
4114 }
4115
4116 conf= ap_get_module_config(cmd->server->module_config , &include_module);
4117 conf->default_end_tag = tag;
4118
4119 return NULL;
4120 }
4121
set_undefined_echo(cmd_parms * cmd,void * mconfig,const char * msg)4122 static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig,
4123 const char *msg)
4124 {
4125 include_dir_config *conf = mconfig;
4126 conf->undefined_echo = msg;
4127
4128 return NULL;
4129 }
4130
set_default_error_msg(cmd_parms * cmd,void * mconfig,const char * msg)4131 static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig,
4132 const char *msg)
4133 {
4134 include_dir_config *conf = mconfig;
4135 conf->default_error_msg = msg;
4136
4137 return NULL;
4138 }
4139
set_default_time_fmt(cmd_parms * cmd,void * mconfig,const char * fmt)4140 static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig,
4141 const char *fmt)
4142 {
4143 include_dir_config *conf = mconfig;
4144 conf->default_time_fmt = fmt;
4145
4146 return NULL;
4147 }
4148
4149
4150 /*
4151 * +-------------------------------------------------------+
4152 * | |
4153 * | Module Initialization and Configuration
4154 * | |
4155 * +-------------------------------------------------------+
4156 */
4157
include_post_config(apr_pool_t * p,apr_pool_t * plog,apr_pool_t * ptemp,server_rec * s)4158 static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
4159 apr_pool_t *ptemp, server_rec *s)
4160 {
4161 include_handlers = apr_hash_make(p);
4162
4163 ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
4164
4165 if (ssi_pfn_register) {
4166 ssi_pfn_register("if", handle_if);
4167 ssi_pfn_register("set", handle_set);
4168 ssi_pfn_register("else", handle_else);
4169 ssi_pfn_register("elif", handle_elif);
4170 ssi_pfn_register("echo", handle_echo);
4171 ssi_pfn_register("endif", handle_endif);
4172 ssi_pfn_register("fsize", handle_fsize);
4173 ssi_pfn_register("config", handle_config);
4174 ssi_pfn_register("comment", handle_comment);
4175 ssi_pfn_register("include", handle_include);
4176 ssi_pfn_register("flastmod", handle_flastmod);
4177 ssi_pfn_register("printenv", handle_printenv);
4178 }
4179
4180 return OK;
4181 }
4182
4183 static const command_rec includes_cmds[] =
4184 {
4185 AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS,
4186 "Off, On, or Full"),
4187 AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL,
4188 "a string"),
4189 AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
4190 "a strftime(3) formatted string"),
4191 AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF,
4192 "SSI Start String Tag"),
4193 AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF,
4194 "SSI End String Tag"),
4195 AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, OR_ALL,
4196 "String to be displayed if an echoed variable is undefined"),
4197 AP_INIT_FLAG("SSILegacyExprParser", ap_set_flag_slot_char,
4198 (void *)APR_OFFSETOF(include_dir_config, legacy_expr),
4199 OR_LIMIT,
4200 "Whether to use the legacy expression parser compatible "
4201 "with <= 2.2.x. Limited to 'on' or 'off'"),
4202 AP_INIT_FLAG("SSILastModified", ap_set_flag_slot_char,
4203 (void *)APR_OFFSETOF(include_dir_config, lastmodified),
4204 OR_LIMIT, "Whether to set the last modified header or respect "
4205 "an existing header. Limited to 'on' or 'off'"),
4206 AP_INIT_FLAG("SSIEtag", ap_set_flag_slot_char,
4207 (void *)APR_OFFSETOF(include_dir_config, etag),
4208 OR_LIMIT, "Whether to allow the generation of ETags within the server. "
4209 "Existing ETags will be preserved. Limited to 'on' or 'off'"),
4210 {NULL}
4211 };
4212
ap_register_include_handler(char * tag,include_handler_fn_t * func)4213 static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
4214 {
4215 apr_hash_set(include_handlers, tag, strlen(tag), (const void *)func);
4216 }
4217
register_hooks(apr_pool_t * p)4218 static void register_hooks(apr_pool_t *p)
4219 {
4220 APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
4221 APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
4222 APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
4223 ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
4224 ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST);
4225 ap_register_output_filter("INCLUDES", includes_filter, includes_setup,
4226 AP_FTYPE_RESOURCE);
4227 }
4228
4229 AP_DECLARE_MODULE(include) =
4230 {
4231 STANDARD20_MODULE_STUFF,
4232 create_includes_dir_config, /* dir config creater */
4233 merge_includes_dir_config, /* dir config merger */
4234 create_includes_server_config,/* server config */
4235 NULL, /* merge server config */
4236 includes_cmds, /* command apr_table_t */
4237 register_hooks /* register hooks */
4238 };
4239