1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000-2002 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
54 * Portions of this software are based upon public domain software
55 * originally written at the National Center for Supercomputing Applications,
56 * University of Illinois, Urbana-Champaign.
60 * http_include.c: Handles the server-parsed HTML documents
62 * Original by Rob McCool; substantial fixups by David Robinson;
63 * incorporated into the Apache module framework by rst.
68 #include "apr_strings.h"
69 #include "apr_thread_proc.h"
73 #include "apr_optional.h"
75 #define APR_WANT_STRFUNC
80 #include "ap_config.h"
81 #include "util_filter.h"
83 #include "http_config.h"
84 #include "http_core.h"
85 #include "http_request.h"
86 #include "http_core.h"
87 #include "http_protocol.h"
89 #include "http_main.h"
90 #include "util_script.h"
91 #include "http_core.h"
92 #include "mod_include.h"
93 #include "util_ebcdic.h"
95 module AP_MODULE_DECLARE_DATA include_module;
96 static apr_hash_t *include_hash;
97 static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
99 /*****************************************************************
101 * XBITHACK. Sigh... NB it's configurable per-directory; the compile-time
102 * option only changes the default.
106 xbithack_off, xbithack_on, xbithack_full
115 char *default_error_msg;
116 char *default_time_fmt;
117 enum xbithack *xbithack;
118 } include_dir_config;
121 char *default_start_tag;
122 char *default_end_tag;
124 bndm_t start_seq_pat;
126 int undefinedEchoLen;
127 } include_server_config;
130 #define DEFAULT_XBITHACK xbithack_full
132 #define DEFAULT_XBITHACK xbithack_off
135 #define BYTE_COUNT_THRESHOLD AP_MIN_BYTES_TO_WRITE
137 /* ------------------------ Environment function -------------------------- */
139 /* Sentinel value to store in subprocess_env for items that
140 * shouldn't be evaluated until/unless they're actually used
142 static const char lazy_eval_sentinel;
143 #define LAZY_VALUE (&lazy_eval_sentinel)
145 static void add_include_vars(request_rec *r, char *timefmt)
147 apr_table_t *e = r->subprocess_env;
150 apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
151 apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
152 apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
153 apr_table_setn(e, "DOCUMENT_URI", r->uri);
154 if (r->path_info && *r->path_info) {
155 apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
157 apr_table_setn(e, "USER_NAME", LAZY_VALUE);
158 if ((t = strrchr(r->filename, '/'))) {
159 apr_table_setn(e, "DOCUMENT_NAME", ++t);
162 apr_table_setn(e, "DOCUMENT_NAME", r->uri);
165 char *arg_copy = apr_pstrdup(r->pool, r->args);
167 ap_unescape_url(arg_copy);
168 apr_table_setn(e, "QUERY_STRING_UNESCAPED",
169 ap_escape_shell_cmd(r->pool, arg_copy));
173 static const char *add_include_vars_lazy(request_rec *r, const char *var)
176 if (!strcasecmp(var, "DATE_LOCAL")) {
177 include_dir_config *conf =
178 (include_dir_config *)ap_get_module_config(r->per_dir_config,
180 val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 0);
182 else if (!strcasecmp(var, "DATE_GMT")) {
183 include_dir_config *conf =
184 (include_dir_config *)ap_get_module_config(r->per_dir_config,
186 val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 1);
188 else if (!strcasecmp(var, "LAST_MODIFIED")) {
189 include_dir_config *conf =
190 (include_dir_config *)ap_get_module_config(r->per_dir_config,
192 val = ap_ht_time(r->pool, r->finfo.mtime, conf->default_time_fmt, 0);
194 else if (!strcasecmp(var, "USER_NAME")) {
195 if (apr_get_username(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
204 apr_table_setn(r->subprocess_env, var, val);
209 static const char *get_include_var(request_rec *r, include_ctx_t *ctx,
213 if (apr_isdigit(*var) && !var[1]) {
214 /* Handle $0 .. $9 from the last regex evaluated.
215 * The choice of returning NULL strings on not-found,
216 * v.s. empty strings on an empty match is deliberate.
218 if (!ctx->re_result || !ctx->re_string) {
223 apr_size_t len = (*ctx->re_result)[idx].rm_eo
224 - (*ctx->re_result)[idx].rm_so;
225 if ( (*ctx->re_result)[idx].rm_so < 0
226 || (*ctx->re_result)[idx].rm_eo < 0) {
229 val = apr_pstrmemdup(r->pool, ctx->re_string
230 + (*ctx->re_result)[idx].rm_so, len);
234 val = apr_table_get(r->subprocess_env, var);
236 if (val == LAZY_VALUE)
237 val = add_include_vars_lazy(r, var);
242 /* --------------------------- Parser functions --------------------------- */
244 /* This is an implementation of the BNDM search algorithm.
246 * Fast and Flexible String Matching by Combining Bit-parallelism and
247 * Suffix Automata (2001)
248 * Gonzalo Navarro, Mathieu Raffinot
250 * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
252 * Initial code submitted by Sascha Schumann.
255 /* Precompile the bndm_t data structure. */
256 static void bndm_compile(bndm_t *t, const char *n, apr_size_t nl)
259 const char *ne = n + nl;
261 memset(t->T, 0, sizeof(unsigned int) * 256);
263 for (x = 1; n < ne; x <<= 1)
264 t->T[(unsigned char) *n++] |= x;
269 /* Implements the BNDM search algorithm (as described above).
271 * n - the pattern to search for
272 * nl - length of the pattern to search for
273 * h - the string to look in
274 * hl - length of the string to look for
275 * t - precompiled bndm structure against the pattern
277 * Returns the count of character that is the first match or hl if no
280 static apr_size_t bndm(const char *n, apr_size_t nl, const char *h,
281 apr_size_t hl, bndm_t *t)
284 const char *he, *p, *pi;
285 unsigned int *T, x, d;
292 pi = h - 1; /* pi: p initial */
293 p = pi + nl; /* compare window right to left. point to the first char */
299 d &= T[(unsigned char) *p--];
319 /* We've now found a start sequence tag... */
320 static apr_bucket* found_start_sequence(apr_bucket *dptr,
324 /* We want to split the bucket at the '<'. */
325 ctx->state = PARSE_DIRECTIVE;
328 ctx->tag_start_bucket = dptr;
329 ctx->tag_start_index = tagStart;
330 if (ctx->head_start_index > 0) {
333 /* Split the bucket with the start of the tag in it */
334 apr_bucket_split(ctx->head_start_bucket, ctx->head_start_index);
335 tmp_bkt = APR_BUCKET_NEXT(ctx->head_start_bucket);
336 /* If it was a one bucket match */
337 if (dptr == ctx->head_start_bucket) {
338 ctx->tag_start_bucket = tmp_bkt;
339 ctx->tag_start_index = tagStart - ctx->head_start_index;
341 ctx->head_start_bucket = tmp_bkt;
342 ctx->head_start_index = 0;
344 return ctx->head_start_bucket;
347 /* This function returns either a pointer to the split bucket containing the
348 * first byte of the BEGINNING_SEQUENCE (after finding a complete match) or it
349 * returns NULL if no match found.
351 static apr_bucket *find_start_sequence(apr_bucket *dptr, include_ctx_t *ctx,
352 apr_bucket_brigade *bb, int *do_cleanup)
357 const char *str = ctx->start_seq ;
358 apr_size_t slen = ctx->start_seq_len;
367 if (APR_BUCKET_IS_EOS(dptr)) {
372 /* XXX the bucket flush support is commented out for now
373 * because it was causing a segfault */
374 if (APR_BUCKET_IS_FLUSH(dptr)) {
375 apr_bucket *old = dptr;
376 dptr = APR_BUCKET_NEXT(old);
377 APR_BUCKET_REMOVE(old);
379 ctx->output_flush = 1;
383 if (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD) {
386 else if (ctx->bytes_parsed > 0) {
387 rv = apr_bucket_read(dptr, &buf, &len, APR_NONBLOCK_READ);
389 if (APR_STATUS_IS_EAGAIN(rv)) {
394 if (ctx->output_now) {
395 apr_bucket *start_bucket;
396 if (ctx->head_start_index > 0) {
397 start_bucket = ctx->head_start_bucket;
398 apr_bucket_split(start_bucket, ctx->head_start_index);
399 start_bucket = APR_BUCKET_NEXT(start_bucket);
400 ctx->head_start_index = 0;
401 ctx->head_start_bucket = start_bucket;
403 ctx->state = PRE_HEAD;
412 rv = apr_bucket_read(dptr, &buf, &len, APR_BLOCK_READ);
414 if (!APR_STATUS_IS_SUCCESS(rv)) {
419 if (len == 0) { /* end of pipe? */
423 /* Set our buffer to use. */
426 /* The last bucket had a left over partial match that we need to
429 if (ctx->state == PARSE_HEAD)
432 tmpLen = (len < (slen - 1)) ? len : (slen - 1);
434 while (c < buf + tmpLen && *c == str[ctx->parse_pos])
440 if (str[ctx->parse_pos] == '\0')
442 ctx->bytes_parsed += c - buf;
443 return found_start_sequence(dptr, ctx, c - buf);
445 else if (c == buf + tmpLen) {
446 dptr = APR_BUCKET_NEXT(dptr);
452 APR_BRIGADE_PREPEND(bb, ctx->ssi_tag_brigade);
454 ctx->state = PRE_HEAD;
459 pos = bndm(str, slen, c, len, ctx->start_seq_pat);
462 ctx->head_start_bucket = dptr;
463 ctx->head_start_index = pos;
464 ctx->bytes_parsed += pos + slen;
465 return found_start_sequence(dptr, ctx, pos + slen);
469 /* Consider the case where we have <!-- at the end of the bucket. */
471 ctx->bytes_parsed += (len - slen);
472 c = buf + len - slen;
479 while (c < buf + len)
481 if (*c == str[ctx->parse_pos]) {
482 if (ctx->state == PRE_HEAD) {
483 ctx->state = PARSE_HEAD;
484 ctx->head_start_bucket = dptr;
485 ctx->head_start_index = c - buf;
489 else if (ctx->parse_pos != 0)
491 /* The reason for this, is that we need to make sure
492 * that we catch cases like <<!--#. This makes the
493 * second check after the original check fails.
494 * If parse_pos was already 0 then we already checked this.
500 ctx->head_start_index = c - buf;
504 ctx->state = PRE_HEAD;
505 ctx->head_start_bucket = NULL;
506 ctx->head_start_index = 0;
512 dptr = APR_BUCKET_NEXT(dptr);
513 } while (dptr != APR_BRIGADE_SENTINEL(bb));
519 static apr_bucket *find_end_sequence(apr_bucket *dptr, include_ctx_t *ctx,
520 apr_bucket_brigade *bb)
525 const char *str = ctx->end_seq;
532 if (APR_BUCKET_IS_EOS(dptr)) {
536 /* XXX the bucket flush support is commented out for now
537 * because it was causing a segfault */
538 if (APR_BUCKET_IS_FLUSH(dptr)) {
539 apr_bucket *old = dptr;
540 dptr = APR_BUCKET_NEXT(old);
541 APR_BUCKET_REMOVE(old);
543 ctx->output_flush = 1;
547 if (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD) {
550 else if (ctx->bytes_parsed > 0) {
551 rv = apr_bucket_read(dptr, &buf, &len, APR_NONBLOCK_READ);
553 if (APR_STATUS_IS_EAGAIN(rv)) {
558 if (ctx->output_now) {
559 if (ctx->state == PARSE_DIRECTIVE) {
560 /* gonna start over parsing the directive next time through */
561 ctx->directive_length = 0;
568 rv = apr_bucket_read(dptr, &buf, &len, APR_BLOCK_READ);
570 if (!APR_STATUS_IS_SUCCESS(rv)) {
575 if (len == 0) { /* end of pipe? */
578 if (dptr == ctx->tag_start_bucket) {
579 c = buf + ctx->tag_start_index;
585 while (c < buf + len) {
586 if (*c == str[ctx->parse_pos]) {
587 if (ctx->state != PARSE_TAIL) {
588 ctx->state = PARSE_TAIL;
589 ctx->tail_start_bucket = dptr;
590 ctx->tail_start_index = c - buf;
593 if (str[ctx->parse_pos] == '\0') {
594 apr_bucket *tmp_buck = dptr;
596 /* We want to split the bucket at the '>'. The
597 * end of the END_SEQUENCE is in the current bucket.
598 * The beginning might be in a previous bucket.
601 ctx->bytes_parsed += (c - start);
603 apr_bucket_split(dptr, c - buf);
604 tmp_buck = APR_BUCKET_NEXT(dptr);
609 if (ctx->state == PARSE_DIRECTIVE) {
610 if (ctx->tag_length == 0) {
611 if (!apr_isspace(*c)) {
613 ctx->tag_start_bucket = dptr;
614 ctx->tag_start_index = c - buf;
617 } while ((c < buf + len) && !apr_isspace(*c) &&
619 ctx->tag_length = ctx->directive_length = c - tmp;
624 if (!apr_isspace(*c)) {
625 ctx->directive_length++;
628 ctx->state = PARSE_TAG;
633 else if (ctx->state == PARSE_TAG) {
637 } while ((c < buf + len) && (*c != *str));
638 ctx->tag_length += (c - tmp);
642 if (ctx->parse_pos != 0) {
643 /* The reason for this, is that we need to make sure
644 * that we catch cases like --->. This makes the
645 * second check after the original check fails.
646 * If parse_pos was already 0 then we already checked
649 ctx->tag_length += ctx->parse_pos;
652 ctx->state = PARSE_TAIL;
653 ctx->tail_start_bucket = dptr;
654 ctx->tail_start_index = c - buf;
659 if (ctx->tag_length > ctx->directive_length) {
660 ctx->state = PARSE_TAG;
663 ctx->state = PARSE_DIRECTIVE;
664 ctx->directive_length += ctx->parse_pos;
666 ctx->tail_start_bucket = NULL;
667 ctx->tail_start_index = 0;
675 ctx->bytes_parsed += (c - start);
676 dptr = APR_BUCKET_NEXT(dptr);
677 } while (dptr != APR_BRIGADE_SENTINEL(bb));
681 /* This function culls through the buckets that have been set aside in the
682 * ssi_tag_brigade and copies just the directive part of the SSI tag (none
683 * of the start and end delimiter bytes are copied).
685 static apr_status_t get_combined_directive (include_ctx_t *ctx,
687 apr_bucket_brigade *bb,
689 apr_size_t tmp_buf_size)
693 const char *tmp_from;
694 apr_size_t tmp_from_len;
696 /* If the tag length is longer than the tmp buffer, allocate space. */
697 if (ctx->tag_length > tmp_buf_size-1) {
698 if ((ctx->combined_tag = apr_pcalloc(r->pool,
699 ctx->tag_length + 1)) == NULL) {
702 } /* Else, just use the temp buffer. */
704 ctx->combined_tag = tmp_buf;
707 /* Prime the pump. Start at the beginning of the tag... */
708 dptr = ctx->tag_start_bucket;
709 /* Read the bucket... */
710 apr_bucket_read (dptr, &tmp_from, &tmp_from_len, 0);
712 /* Adjust the pointer to start at the tag within the bucket... */
713 if (dptr == ctx->tail_start_bucket) {
714 tmp_from_len -= (tmp_from_len - ctx->tail_start_index);
716 tmp_from = &tmp_from[ctx->tag_start_index];
717 tmp_from_len -= ctx->tag_start_index;
718 ctx->curr_tag_pos = ctx->combined_tag;
720 /* Loop through the buckets from the tag_start_bucket until before
721 * the tail_start_bucket copying the contents into the buffer.
724 memcpy (ctx->curr_tag_pos, tmp_from, tmp_from_len);
725 ctx->curr_tag_pos += tmp_from_len;
727 if (dptr == ctx->tail_start_bucket) {
731 dptr = APR_BUCKET_NEXT (dptr);
732 apr_bucket_read (dptr, &tmp_from, &tmp_from_len, 0);
733 /* Adjust the count to stop at the beginning of the tail. */
734 if (dptr == ctx->tail_start_bucket) {
735 tmp_from_len -= (tmp_from_len - ctx->tail_start_index);
739 (ctx->curr_tag_pos < ctx->combined_tag + ctx->tag_length));
741 ctx->combined_tag[ctx->tag_length] = '\0';
742 ctx->curr_tag_pos = ctx->combined_tag;
744 return (APR_SUCCESS);
748 * decodes a string containing html entities or numeric character references.
749 * 's' is overwritten with the decoded string.
750 * If 's' is syntatically incorrect, then the followed fixups will be made:
751 * unknown entities will be left undecoded;
752 * references to unused numeric characters will be deleted.
753 * In particular, � will not be decoded, but will be deleted.
758 /* maximum length of any ISO-LATIN-1 HTML entity name. */
759 #define MAXENTLEN (6)
761 /* The following is a shrinking transformation, therefore safe. */
763 static void decodehtml(char *s)
768 static const char * const entlist[MAXENTLEN + 1] =
772 "lt\074gt\076", /* 2 */
773 "amp\046ETH\320eth\360", /* 3 */
774 "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml\353\
775 iuml\357ouml\366uuml\374yuml\377", /* 4 */
776 "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc\333\
777 THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352icirc\356ocirc\364\
778 ucirc\373thorn\376", /* 5 */
779 "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311\
780 Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde\325Oslash\330\
781 Ugrave\331Uacute\332Yacute\335agrave\340aacute\341atilde\343ccedil\347\
782 egrave\350eacute\351igrave\354iacute\355ntilde\361ograve\362oacute\363\
783 otilde\365oslash\370ugrave\371uacute\372yacute\375" /* 6 */
786 /* Do a fast scan through the string until we find anything
787 * that needs more complicated handling
789 for (; *s != '&'; s++) {
795 for (p = s; *s != '\0'; s++, p++) {
800 /* find end of entity */
801 for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
805 if (s[i] == '\0') { /* treat as normal data */
810 /* is it numeric ? */
812 for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
813 val = val * 10 + s[j] - '0';
816 if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
817 (val >= 127 && val <= 160) || val >= 256) {
818 p--; /* no data to output */
821 *p = RAW_ASCII_CHAR(val);
826 if (j > MAXENTLEN || entlist[j] == NULL) {
829 continue; /* skip it */
831 for (ents = entlist[j]; *ents != '\0'; ents += i) {
832 if (strncmp(s + 1, ents, j) == 0) {
838 *p = '&'; /* unknown */
841 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
851 * Extract the next tag name and value.
852 * If there are no more tags, set the tag name to NULL.
853 * The tag value is html decoded if dodecode is non-zero.
854 * The tag value may be NULL if there is no tag value..
856 * [WS]<Tag>[WS]=[WS]['|"|`]<Value>[['|"|`|]|WS]
859 #define SKIP_TAG_WHITESPACE(ptr) while ((*ptr != '\0') && (apr_isspace (*ptr))) ptr++
861 static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
862 char **tag_val, int dodecode)
864 char *c = ctx->curr_tag_pos;
869 if (ctx->curr_tag_pos > ctx->combined_tag + ctx->tag_length) {
873 SKIP_TAG_WHITESPACE(c);
874 *tag = c; /* First non-whitespace character (could be NULL). */
876 while (apr_islower(*c)) {
877 c++; /* Optimization for the common case where the tag */
878 } /* is already lowercase */
880 while ((*c != '=') && (!apr_isspace(*c)) && (*c != '\0')) {
881 *c = apr_tolower(*c); /* find end of tag, lowercasing as we go... */
885 if ((*c == '\0') || (**tag == '=')) {
886 if ((**tag == '\0') || (**tag == '=')) {
889 ctx->curr_tag_pos = c;
890 return; /* We have found the end of the buffer. */
891 } /* We might have a tag, but definitely no value. */
894 *c++ = '\0'; /* Overwrite the '=' with a terminating byte after tag. */
896 else { /* Try skipping WS to find the '='. */
897 *c++ = '\0'; /* Terminate the tag... */
898 SKIP_TAG_WHITESPACE(c);
900 /* There needs to be an equal sign if there's a value. */
902 ctx->curr_tag_pos = c;
903 return; /* There apparently was no value. */
906 c++; /* Skip the equals sign. */
910 SKIP_TAG_WHITESPACE(c);
911 if (*c == '"' || *c == '\'' || *c == '`') {
912 /* Allow quoted values for space inclusion.
913 * NOTE: This does not pass the quotes on return.
920 while (!apr_isspace(*c) && (*c != '\0')) {
925 while ((*c != term) && (*c != '\0') && (*c != '\\')) {
926 /* Quickly scan past the string until we reach
927 * either the end of the tag or a backslash. If
928 * we find a backslash, we have to switch to the
929 * more complicated parser loop that follows.
935 /* Accept \" (or ' or `) as valid quotation of string.
938 /* Overwrite the "\" during the embedded
939 * escape sequence of '"'. "\'" or '`'.
940 * Shift bytes from here to next delimiter.
958 } while ((*c != term) && (*c != '\0'));
962 *(c-shift_val) = '\0'; /* Overwrites delimiter (term or WS) with NULL. */
963 ctx->curr_tag_pos = ++c;
965 decodehtml(*tag_val);
971 /* initial buffer size for power-of-two allocator in ap_ssi_parse_string */
972 #define PARSE_STRING_INITIAL_SIZE 64
975 * Do variable substitution on strings
976 * (Note: If out==NULL, this function allocs a buffer for the resulting
977 * string from r->pool. The return value is the parsed string)
979 static char *ap_ssi_parse_string(request_rec *r, include_ctx_t *ctx,
980 const char *in, char *out,
981 apr_size_t length, int leave_name)
988 /* allocate an output buffer if needed */
990 out_size = PARSE_STRING_INITIAL_SIZE;
991 if (out_size > length) {
994 out = apr_palloc(r->pool, out_size);
1000 /* leave room for nul terminator */
1001 end_out = out + out_size - 1;
1004 while ((ch = *in++) != '\0') {
1007 if (next == end_out) {
1008 if (out_size < length) {
1009 /* double the buffer size */
1010 apr_size_t new_out_size = out_size * 2;
1011 apr_size_t current_length = next - out;
1013 if (new_out_size > length) {
1014 new_out_size = length;
1016 new_out = apr_palloc(r->pool, new_out_size);
1017 memcpy(new_out, out, current_length);
1019 out_size = new_out_size;
1020 end_out = out + out_size - 1;
1021 next = out + current_length;
1038 const char *start_of_var_name;
1039 char *end_of_var_name; /* end of var name + 1 */
1040 const char *expansion, *temp_end, *val;
1044 /* guess that the expansion won't happen */
1048 start_of_var_name = in;
1049 in = ap_strchr_c(in, '}');
1051 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
1052 0, r, "Missing '}' on variable \"%s\"",
1058 end_of_var_name = (char *)temp_end;
1062 start_of_var_name = in;
1063 while (apr_isalnum(*in) || *in == '_') {
1067 end_of_var_name = (char *)temp_end;
1069 /* what a pain, too bad there's no table_getn where you can
1070 * pass a non-nul terminated string */
1071 l = end_of_var_name - start_of_var_name;
1073 tmp_store = *end_of_var_name;
1074 *end_of_var_name = '\0';
1075 val = get_include_var(r, ctx, start_of_var_name);
1076 *end_of_var_name = tmp_store;
1080 l = strlen(expansion);
1082 else if (leave_name) {
1086 /* no expansion to be done */
1091 /* zero-length variable name causes just the $ to be
1095 if ((next + l > end_out) && (out_size < length)) {
1096 /* increase the buffer size to accommodate l more chars */
1097 apr_size_t new_out_size = out_size;
1098 apr_size_t current_length = next - out;
1102 } while (new_out_size < current_length + l);
1103 if (new_out_size > length) {
1104 new_out_size = length;
1106 new_out = apr_palloc(r->pool, new_out_size);
1107 memcpy(new_out, out, current_length);
1109 out_size = new_out_size;
1110 end_out = out + out_size - 1;
1111 next = out + current_length;
1113 l = ((int)l > end_out - next) ? (end_out - next) : l;
1114 memcpy(next, expansion, l);
1119 if (next == end_out) {
1120 if (out_size < length) {
1121 /* double the buffer size */
1122 apr_size_t new_out_size = out_size * 2;
1123 apr_size_t current_length = next - out;
1125 if (new_out_size > length) {
1126 new_out_size = length;
1128 new_out = apr_palloc(r->pool, new_out_size);
1129 memcpy(new_out, out, current_length);
1131 out_size = new_out_size;
1132 end_out = out + out_size - 1;
1133 next = out + current_length;
1149 /* --------------------------- Action handlers ---------------------------- */
1151 /* ensure that path is relative, and does not contain ".." elements
1152 * ensentially ensure that it does not match the regex:
1153 * (^/|(^|/)\.\.(/|$))
1154 * XXX: Simply replace with apr_filepath_merge
1156 static int is_only_below(const char *path)
1158 #ifdef HAVE_DRIVE_LETTERS
1163 if (strchr(path, ':'))
1166 if (path[0] == '/') {
1171 while (path[dots] == '.')
1174 /* If the name is canonical this is redundant
1175 * but in security, redundancy is worthwhile.
1176 * Does OS2 belong here (accepts ... for ..)?
1178 if (dots > 1 && (!path[dots] || path[dots] == '/'))
1181 if (dots == 2 && (!path[dots] || path[dots] == '/'))
1185 /* Advance to either the null byte at the end of the
1186 * string or the character right after the next slash,
1187 * whichever comes first
1189 while (*path && (*path++ != '/')) {
1196 static int handle_include(include_ctx_t *ctx, apr_bucket_brigade **bb,
1197 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
1198 apr_bucket **inserted_head)
1201 char *tag_val = NULL;
1202 apr_bucket *tmp_buck;
1203 char *parsed_string;
1204 int loglevel = APLOG_ERR;
1206 *inserted_head = NULL;
1207 if (ctx->flags & FLAG_PRINTING) {
1209 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
1210 if (tag_val == NULL) {
1218 if (!strcmp(tag, "virtual") || !strcmp(tag, "file")) {
1219 request_rec *rr = NULL;
1220 char *error_fmt = NULL;
1221 apr_status_t rc = APR_SUCCESS;
1223 SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next, rc);
1224 if (rc != APR_SUCCESS) {
1228 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL,
1230 if (tag[0] == 'f') {
1231 /* XXX: Port to apr_filepath_merge
1232 * be safe; only files in this directory or below allowed
1234 if (!is_only_below(parsed_string)) {
1235 error_fmt = "unable to include file \"%s\" "
1236 "in parsed file %s";
1239 rr = ap_sub_req_lookup_file(parsed_string, r, f->next);
1243 rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
1246 if (!error_fmt && rr->status != HTTP_OK) {
1247 error_fmt = "unable to include \"%s\" in parsed file %s";
1250 if (!error_fmt && (ctx->flags & FLAG_NO_EXEC) &&
1252 (strncmp(rr->content_type, "text/", 5))) {
1253 error_fmt = "unable to include potential exec \"%s\" "
1254 "in parsed file %s";
1256 if (error_fmt == NULL) {
1257 /* try to avoid recursive includes. We do this by walking
1258 * up the r->main list of subrequests, and at each level
1259 * walking back through any internal redirects. At each
1260 * step, we compare the filenames and the URIs.
1262 * The filename comparison catches a recursive include
1263 * with an ever-changing URL, eg.
1264 * <!--#include virtual=
1265 * "$REQUEST_URI/$QUERY_STRING?$QUERY_STRING/x" -->
1266 * which, although they would eventually be caught because
1267 * we have a limit on the length of files, etc., can
1268 * recurse for a while.
1270 * The URI comparison catches the case where the filename
1271 * is changed while processing the request, so the
1272 * current name is never the same as any previous one.
1273 * This can happen with "DocumentRoot /foo" when you
1274 * request "/" on the server and it includes "/".
1275 * This only applies to modules such as mod_dir that
1276 * (somewhat improperly) mess with r->filename outside
1277 * of a filename translation phase.
1281 for (p = r; p != NULL && !founddupe; p = p->main) {
1283 for (q = p; q != NULL; q = q->prev) {
1284 if ((q->filename && rr->filename &&
1285 (strcmp(q->filename, rr->filename) == 0)) ||
1286 ((*q->uri == '/') &&
1287 (strcmp(q->uri, rr->uri) == 0)))
1296 error_fmt = "Recursive include of \"%s\" "
1297 "in parsed file %s";
1301 /* See the Kludge in send_parsed_file for why */
1302 /* Basically, it puts a bread crumb in here, then looks */
1303 /* for the crumb later to see if its been here. */
1305 ap_set_module_config(rr->request_config,
1306 &include_module, r);
1308 if (!error_fmt && ap_run_sub_req(rr)) {
1309 error_fmt = "unable to include \"%s\" in parsed file %s";
1312 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|loglevel,
1313 0, r, error_fmt, tag_val, r->filename);
1314 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
1318 /* destroy the sub request */
1320 ap_destroy_sub_req(rr);
1324 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1325 "unknown parameter \"%s\" to tag include in %s",
1327 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
1335 static int handle_echo(include_ctx_t *ctx, apr_bucket_brigade **bb,
1336 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
1337 apr_bucket **inserted_head)
1340 char *tag_val = NULL;
1341 const char *echo_text = NULL;
1342 apr_bucket *tmp_buck;
1344 enum {E_NONE, E_URL, E_ENTITY} encode;
1348 *inserted_head = NULL;
1349 if (ctx->flags & FLAG_PRINTING) {
1351 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
1352 if (tag_val == NULL) {
1360 if (!strcmp(tag, "var")) {
1361 conn_rec *c = r->connection;
1363 get_include_var(r, ctx,
1364 ap_ssi_parse_string(r, ctx, tag_val, NULL,
1365 MAX_STRING_LEN, 0));
1372 echo_text = ap_escape_uri(r->pool, val);
1375 echo_text = ap_escape_html(r->pool, val);
1379 e_len = strlen(echo_text);
1380 tmp_buck = apr_bucket_pool_create(echo_text, e_len,
1381 r->pool, c->bucket_alloc);
1384 include_server_config *sconf=
1385 ap_get_module_config(r->server->module_config,
1387 tmp_buck = apr_bucket_pool_create(sconf->undefinedEcho,
1388 sconf->undefinedEchoLen,
1389 r->pool, c->bucket_alloc);
1391 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
1392 if (*inserted_head == NULL) {
1393 *inserted_head = tmp_buck;
1396 else if (!strcmp(tag, "encoding")) {
1397 if (!strcasecmp(tag_val, "none")) encode = E_NONE;
1398 else if (!strcasecmp(tag_val, "url")) encode = E_URL;
1399 else if (!strcasecmp(tag_val, "entity")) encode = E_ENTITY;
1401 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1402 "unknown value \"%s\" to parameter \"encoding\" of "
1403 "tag echo in %s", tag_val, r->filename);
1404 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
1409 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1410 "unknown parameter \"%s\" in tag echo of %s",
1412 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
1420 /* error and tf must point to a string with room for at
1421 * least MAX_STRING_LEN characters
1423 static int handle_config(include_ctx_t *ctx, apr_bucket_brigade **bb,
1424 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
1425 apr_bucket **inserted_head)
1428 char *tag_val = NULL;
1429 char *parsed_string;
1430 apr_table_t *env = r->subprocess_env;
1432 *inserted_head = NULL;
1433 if (ctx->flags & FLAG_PRINTING) {
1435 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
1436 if (tag_val == NULL) {
1438 return 0; /* Reached the end of the string. */
1441 return 1; /* tags must have values. */
1444 if (!strcmp(tag, "errmsg")) {
1445 if (ctx->error_str_override == NULL) {
1446 ctx->error_str_override = (char *)apr_palloc(ctx->pool,
1448 ctx->error_str = ctx->error_str_override;
1450 ap_ssi_parse_string(r, ctx, tag_val, ctx->error_str_override,
1453 else if (!strcmp(tag, "timefmt")) {
1454 apr_time_t date = r->request_time;
1455 if (ctx->time_str_override == NULL) {
1456 ctx->time_str_override = (char *)apr_palloc(ctx->pool,
1458 ctx->time_str = ctx->time_str_override;
1460 ap_ssi_parse_string(r, ctx, tag_val, ctx->time_str_override,
1462 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
1464 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
1466 apr_table_setn(env, "LAST_MODIFIED",
1467 ap_ht_time(r->pool, r->finfo.mtime,
1470 else if (!strcmp(tag, "sizefmt")) {
1471 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL,
1473 decodehtml(parsed_string);
1474 if (!strcmp(parsed_string, "bytes")) {
1475 ctx->flags |= FLAG_SIZE_IN_BYTES;
1477 else if (!strcmp(parsed_string, "abbrev")) {
1478 ctx->flags &= FLAG_SIZE_ABBREV;
1482 apr_bucket *tmp_buck;
1484 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1485 "unknown parameter \"%s\" to tag config in %s",
1487 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
1495 static int find_file(request_rec *r, const char *directive, const char *tag,
1496 char *tag_val, apr_finfo_t *finfo)
1498 char *to_send = tag_val;
1499 request_rec *rr = NULL;
1501 char *error_fmt = NULL;
1502 apr_status_t rv = APR_SUCCESS;
1504 if (!strcmp(tag, "file")) {
1505 /* XXX: Port to apr_filepath_merge
1506 * be safe; only files in this directory or below allowed
1508 if (!is_only_below(tag_val)) {
1509 error_fmt = "unable to access file \"%s\" "
1510 "in parsed file %s";
1513 ap_getparents(tag_val); /* get rid of any nasties */
1515 /* note: it is okay to pass NULL for the "next filter" since
1516 we never attempt to "run" this sub request. */
1517 rr = ap_sub_req_lookup_file(tag_val, r, NULL);
1519 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1520 to_send = rr->filename;
1521 if ((rv = apr_stat(finfo, to_send,
1522 APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
1523 && rv != APR_INCOMPLETE) {
1524 error_fmt = "unable to get information about \"%s\" "
1525 "in parsed file %s";
1529 error_fmt = "unable to lookup information about \"%s\" "
1530 "in parsed file %s";
1536 ap_log_rerror(APLOG_MARK, APLOG_ERR | (rv ? 0 : APLOG_NOERRNO),
1537 rv, r, error_fmt, to_send, r->filename);
1540 if (rr) ap_destroy_sub_req(rr);
1544 else if (!strcmp(tag, "virtual")) {
1545 /* note: it is okay to pass NULL for the "next filter" since
1546 we never attempt to "run" this sub request. */
1547 rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1549 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1550 memcpy((char *) finfo, (const char *) &rr->finfo,
1552 ap_destroy_sub_req(rr);
1556 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1557 "unable to get information about \"%s\" "
1558 "in parsed file %s",
1559 tag_val, r->filename);
1560 ap_destroy_sub_req(rr);
1565 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1566 "unknown parameter \"%s\" to tag %s in %s",
1567 tag, directive, r->filename);
1572 static int handle_fsize(include_ctx_t *ctx, apr_bucket_brigade **bb,
1573 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
1574 apr_bucket **inserted_head)
1577 char *tag_val = NULL;
1580 apr_bucket *tmp_buck;
1581 char *parsed_string;
1583 *inserted_head = NULL;
1584 if (ctx->flags & FLAG_PRINTING) {
1586 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
1587 if (tag_val == NULL) {
1596 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL,
1598 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
1599 /* XXX: if we *know* we're going to have to copy the
1600 * thing off of the stack anyway, why not palloc buff
1601 * instead of sticking it on the stack; then we can just
1602 * use a pool bucket and skip the copy
1606 if (!(ctx->flags & FLAG_SIZE_IN_BYTES)) {
1607 apr_strfsize(finfo.size, buff);
1608 s_len = strlen (buff);
1614 apr_snprintf(tmp_buff, sizeof(tmp_buff),
1615 "%" APR_OFF_T_FMT, finfo.size);
1616 l = strlen(tmp_buff); /* grrr */
1617 for (x = 0; x < l; x++) {
1618 if (x && (!((l - x) % 3))) {
1621 buff[pos++] = tmp_buff[x];
1627 tmp_buck = apr_bucket_heap_create(buff, s_len, NULL,
1628 r->connection->bucket_alloc);
1629 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
1630 if (*inserted_head == NULL) {
1631 *inserted_head = tmp_buck;
1635 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
1644 static int handle_flastmod(include_ctx_t *ctx, apr_bucket_brigade **bb,
1645 request_rec *r, ap_filter_t *f,
1646 apr_bucket *head_ptr, apr_bucket **inserted_head)
1649 char *tag_val = NULL;
1652 apr_bucket *tmp_buck;
1653 char *parsed_string;
1655 *inserted_head = NULL;
1656 if (ctx->flags & FLAG_PRINTING) {
1658 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
1659 if (tag_val == NULL) {
1668 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL,
1670 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
1673 t_val = ap_ht_time(r->pool, finfo.mtime, ctx->time_str, 0);
1674 t_len = strlen(t_val);
1676 tmp_buck = apr_bucket_pool_create(t_val, t_len, r->pool,
1677 r->connection->bucket_alloc);
1678 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
1679 if (*inserted_head == NULL) {
1680 *inserted_head = tmp_buck;
1684 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
1693 static int re_check(request_rec *r, include_ctx_t *ctx,
1694 char *string, char *rexp)
1697 const apr_size_t nres = sizeof(*ctx->re_result) / sizeof(regmatch_t);
1700 compiled = ap_pregcomp(r->pool, rexp, REG_EXTENDED | REG_NOSUB);
1701 if (compiled == NULL) {
1702 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1703 "unable to compile pattern \"%s\"", rexp);
1706 if (!ctx->re_result) {
1707 ctx->re_result = apr_pcalloc(r->pool, sizeof(*ctx->re_result));
1709 ctx->re_string = string;
1710 regex_error = ap_regexec(compiled, string, nres, *ctx->re_result, 0);
1711 ap_pregfree(r->pool, compiled);
1712 return (!regex_error);
1716 token_string, token_re,
1717 token_and, token_or, token_not, token_eq, token_ne,
1718 token_rbrace, token_lbrace, token_group,
1719 token_ge, token_le, token_gt, token_lt
1722 enum token_type type;
1726 static const char *get_ptoken(request_rec *r, const char *string,
1727 struct token *token, int *unmatched)
1734 token->value = NULL;
1736 /* Skip leading white space */
1737 if (string == (char *) NULL) {
1738 return (char *) NULL;
1740 while ((ch = *string++)) {
1741 if (!apr_isspace(ch)) {
1746 return (char *) NULL;
1749 token->type = token_string; /* the default type */
1752 token->type = token_lbrace;
1755 token->type = token_rbrace;
1758 token->type = token_eq;
1761 if (*string == '=') {
1762 token->type = token_ne;
1763 return (string + 1);
1766 token->type = token_not;
1770 /* already token->type == token_string */
1774 token->type = token_re;
1778 if (*string == '|') {
1779 token->type = token_or;
1780 return (string + 1);
1784 if (*string == '&') {
1785 token->type = token_and;
1786 return (string + 1);
1790 if (*string == '=') {
1791 token->type = token_ge;
1792 return (string + 1);
1795 token->type = token_gt;
1799 if (*string == '=') {
1800 token->type = token_le;
1801 return (string + 1);
1804 token->type = token_lt;
1808 /* already token->type == token_string */
1811 /* We should only be here if we are in a string */
1812 token->value = apr_palloc(r->pool, strlen(string) + 2); /* 2 for ch plus
1815 token->value[next++] = ch;
1819 * I used the ++string throughout this section so that string
1820 * ends up pointing to the next token and I can just return it
1822 for (ch = *string; ((ch != '\0') && (!tkn_fnd)); ch = *++string) {
1824 if ((ch = *++string) == '\0') {
1828 token->value[next++] = ch;
1833 if (apr_isspace(ch)) {
1847 if (*(string + 1) == '|') {
1852 if (*(string + 1) == '&') {
1858 token->value[next++] = ch;
1868 token->value[next++] = ch;
1874 /* If qs is still set, we have an unmatched quote */
1879 token->value[next] = '\0';
1885 /* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1
1886 * characters long...
1888 static int parse_expr(request_rec *r, include_ctx_t *ctx, const char *expr,
1889 int *was_error, int *was_unmatched, char *debug)
1892 struct parse_node *left, *right, *parent;
1895 } *root, *current, *new;
1899 apr_size_t debug_pos = 0;
1901 debug[debug_pos] = '\0';
1904 if ((parse = expr) == (char *) NULL) {
1907 root = current = (struct parse_node *) NULL;
1909 /* Create Parse Tree */
1911 new = (struct parse_node *) apr_palloc(r->pool,
1912 sizeof(struct parse_node));
1913 new->parent = new->left = new->right = (struct parse_node *) NULL;
1915 if ((parse = get_ptoken(r, parse, &new->token, was_unmatched)) ==
1919 switch (new->token.type) {
1922 #ifdef DEBUG_INCLUDE
1923 debug_pos += sprintf (&debug[debug_pos],
1924 " Token: string (%s)\n",
1927 if (current == (struct parse_node *) NULL) {
1928 root = current = new;
1931 switch (current->token.type) {
1933 current->token.value = apr_pstrcat(r->pool,
1934 current->token.value,
1935 current->token.value[0] ? " " : "",
1950 new->parent = current;
1951 current = current->right = new;
1954 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1955 "Invalid expression \"%s\" in file %s",
1963 #ifdef DEBUG_INCLUDE
1964 debug_pos += sprintf (&debug[debug_pos],
1965 " Token: regex (%s)\n",
1968 if (current == (struct parse_node *) NULL) {
1969 root = current = new;
1972 switch (current->token.type) {
1979 new->parent = current;
1980 current = current->right = new;
1983 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1984 "Invalid expression \"%s\" in file %s",
1993 #ifdef DEBUG_INCLUDE
1994 memcpy (&debug[debug_pos], " Token: and/or\n",
1995 sizeof (" Token: and/or\n"));
1996 debug_pos += sizeof (" Token: and/or\n");
1998 if (current == (struct parse_node *) NULL) {
1999 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2000 "Invalid expression \"%s\" in file %s",
2005 /* Percolate upwards */
2006 while (current != (struct parse_node *) NULL) {
2007 switch (current->token.type) {
2020 current = current->parent;
2025 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2026 "Invalid expression \"%s\" in file %s",
2033 if (current == (struct parse_node *) NULL) {
2035 new->left->parent = new;
2036 new->parent = (struct parse_node *) NULL;
2040 new->left = current->right;
2041 current->right = new;
2042 new->parent = current;
2048 #ifdef DEBUG_INCLUDE
2049 memcpy(&debug[debug_pos], " Token: not\n",
2050 sizeof(" Token: not\n"));
2051 debug_pos += sizeof(" Token: not\n");
2053 if (current == (struct parse_node *) NULL) {
2054 root = current = new;
2057 /* Percolate upwards */
2058 while (current != (struct parse_node *) NULL) {
2059 switch (current->token.type) {
2072 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2073 "Invalid expression \"%s\" in file %s",
2080 if (current == (struct parse_node *) NULL) {
2082 new->left->parent = new;
2083 new->parent = (struct parse_node *) NULL;
2087 new->left = current->right;
2088 current->right = new;
2089 new->parent = current;
2100 #ifdef DEBUG_INCLUDE
2101 memcpy(&debug[debug_pos], " Token: eq/ne/ge/gt/le/lt\n",
2102 sizeof(" Token: eq/ne/ge/gt/le/lt\n"));
2103 debug_pos += sizeof(" Token: eq/ne/ge/gt/le/lt\n");
2105 if (current == (struct parse_node *) NULL) {
2106 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2107 "Invalid expression \"%s\" in file %s",
2112 /* Percolate upwards */
2113 while (current != (struct parse_node *) NULL) {
2114 switch (current->token.type) {
2118 current = current->parent;
2132 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2133 "Invalid expression \"%s\" in file %s",
2140 if (current == (struct parse_node *) NULL) {
2142 new->left->parent = new;
2143 new->parent = (struct parse_node *) NULL;
2147 new->left = current->right;
2148 current->right = new;
2149 new->parent = current;
2155 #ifdef DEBUG_INCLUDE
2156 memcpy (&debug[debug_pos], " Token: rbrace\n",
2157 sizeof (" Token: rbrace\n"));
2158 debug_pos += sizeof (" Token: rbrace\n");
2160 while (current != (struct parse_node *) NULL) {
2161 if (current->token.type == token_lbrace) {
2162 current->token.type = token_group;
2165 current = current->parent;
2167 if (current == (struct parse_node *) NULL) {
2168 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2169 "Unmatched ')' in \"%s\" in file %s",
2177 #ifdef DEBUG_INCLUDE
2178 memcpy (&debug[debug_pos], " Token: lbrace\n",
2179 sizeof (" Token: lbrace\n"));
2180 debug_pos += sizeof (" Token: lbrace\n");
2182 if (current == (struct parse_node *) NULL) {
2183 root = current = new;
2186 /* Percolate upwards */
2187 while (current != (struct parse_node *) NULL) {
2188 switch (current->token.type) {
2204 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2205 "Invalid expression \"%s\" in file %s",
2212 if (current == (struct parse_node *) NULL) {
2214 new->left->parent = new;
2215 new->parent = (struct parse_node *) NULL;
2219 new->left = current->right;
2220 current->right = new;
2221 new->parent = current;
2230 /* Evaluate Parse Tree */
2232 while (current != (struct parse_node *) NULL) {
2233 switch (current->token.type) {
2235 #ifdef DEBUG_INCLUDE
2236 memcpy (&debug[debug_pos], " Evaluate string\n",
2237 sizeof (" Evaluate string\n"));
2238 debug_pos += sizeof (" Evaluate string\n");
2240 buffer = ap_ssi_parse_string(r, ctx, current->token.value, NULL,
2242 current->token.value = buffer;
2243 current->value = (current->token.value[0] != '\0');
2245 current = current->parent;
2249 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2250 "No operator before regex of expr \"%s\" in file %s",
2257 #ifdef DEBUG_INCLUDE
2258 memcpy(&debug[debug_pos], " Evaluate and/or\n",
2259 sizeof(" Evaluate and/or\n"));
2260 debug_pos += sizeof(" Evaluate and/or\n");
2262 if (current->left == (struct parse_node *) NULL ||
2263 current->right == (struct parse_node *) NULL) {
2264 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2265 "Invalid expression \"%s\" in file %s",
2270 if (!current->left->done) {
2271 switch (current->left->token.type) {
2273 buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
2274 NULL, MAX_STRING_LEN, 0);
2275 current->left->token.value = buffer;
2276 current->left->value =
2277 (current->left->token.value[0] != '\0');
2278 current->left->done = 1;
2281 current = current->left;
2285 if (!current->right->done) {
2286 switch (current->right->token.type) {
2288 buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
2289 NULL, MAX_STRING_LEN, 0);
2290 current->right->token.value = buffer;
2291 current->right->value =
2292 (current->right->token.value[0] != '\0');
2293 current->right->done = 1;
2296 current = current->right;
2300 #ifdef DEBUG_INCLUDE
2301 debug_pos += sprintf (&debug[debug_pos], " Left: %c\n",
2302 current->left->value ? '1' : '0');
2303 debug_pos += sprintf (&debug[debug_pos], " Right: %c\n",
2304 current->right->value ? '1' : '0');
2306 if (current->token.type == token_and) {
2307 current->value = current->left->value && current->right->value;
2310 current->value = current->left->value || current->right->value;
2312 #ifdef DEBUG_INCLUDE
2313 debug_pos += sprintf (&debug[debug_pos], " Returning %c\n",
2314 current->value ? '1' : '0');
2317 current = current->parent;
2322 #ifdef DEBUG_INCLUDE
2323 memcpy (&debug[debug_pos], " Evaluate eq/ne\n",
2324 sizeof (" Evaluate eq/ne\n"));
2325 debug_pos += sizeof (" Evaluate eq/ne\n");
2327 if ((current->left == (struct parse_node *) NULL) ||
2328 (current->right == (struct parse_node *) NULL) ||
2329 (current->left->token.type != token_string) ||
2330 ((current->right->token.type != token_string) &&
2331 (current->right->token.type != token_re))) {
2332 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2333 "Invalid expression \"%s\" in file %s",
2338 buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
2339 NULL, MAX_STRING_LEN, 0);
2340 current->left->token.value = buffer;
2341 buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
2342 NULL, MAX_STRING_LEN, 0);
2343 current->right->token.value = buffer;
2344 if (current->right->token.type == token_re) {
2345 #ifdef DEBUG_INCLUDE
2346 debug_pos += sprintf (&debug[debug_pos],
2347 " Re Compare (%s) with /%s/\n",
2348 current->left->token.value,
2349 current->right->token.value);
2352 re_check(r, ctx, current->left->token.value,
2353 current->right->token.value);
2356 #ifdef DEBUG_INCLUDE
2357 debug_pos += sprintf (&debug[debug_pos],
2358 " Compare (%s) with (%s)\n",
2359 current->left->token.value,
2360 current->right->token.value);
2363 (strcmp(current->left->token.value,
2364 current->right->token.value) == 0);
2366 if (current->token.type == token_ne) {
2367 current->value = !current->value;
2369 #ifdef DEBUG_INCLUDE
2370 debug_pos += sprintf (&debug[debug_pos], " Returning %c\n",
2371 current->value ? '1' : '0');
2374 current = current->parent;
2380 #ifdef DEBUG_INCLUDE
2381 memcpy (&debug[debug_pos], " Evaluate ge/gt/le/lt\n",
2382 sizeof (" Evaluate ge/gt/le/lt\n"));
2383 debug_pos += sizeof (" Evaluate ge/gt/le/lt\n");
2385 if ((current->left == (struct parse_node *) NULL) ||
2386 (current->right == (struct parse_node *) NULL) ||
2387 (current->left->token.type != token_string) ||
2388 (current->right->token.type != token_string)) {
2389 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2390 "Invalid expression \"%s\" in file %s",
2395 buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
2396 NULL, MAX_STRING_LEN, 0);
2397 current->left->token.value = buffer;
2398 buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
2399 NULL, MAX_STRING_LEN, 0);
2400 current->right->token.value = buffer;
2401 #ifdef DEBUG_INCLUDE
2402 debug_pos += sprintf (&debug[debug_pos],
2403 " Compare (%s) with (%s)\n",
2404 current->left->token.value,
2405 current->right->token.value);
2408 strcmp(current->left->token.value,
2409 current->right->token.value);
2410 if (current->token.type == token_ge) {
2411 current->value = current->value >= 0;
2413 else if (current->token.type == token_gt) {
2414 current->value = current->value > 0;
2416 else if (current->token.type == token_le) {
2417 current->value = current->value <= 0;
2419 else if (current->token.type == token_lt) {
2420 current->value = current->value < 0;
2423 current->value = 0; /* Don't return -1 if unknown token */
2425 #ifdef DEBUG_INCLUDE
2426 debug_pos += sprintf (&debug[debug_pos], " Returning %c\n",
2427 current->value ? '1' : '0');
2430 current = current->parent;
2434 if (current->right != (struct parse_node *) NULL) {
2435 if (!current->right->done) {
2436 current = current->right;
2439 current->value = !current->right->value;
2444 #ifdef DEBUG_INCLUDE
2445 debug_pos += sprintf (&debug[debug_pos], " Evaluate !: %c\n",
2446 current->value ? '1' : '0');
2449 current = current->parent;
2453 if (current->right != (struct parse_node *) NULL) {
2454 if (!current->right->done) {
2455 current = current->right;
2458 current->value = current->right->value;
2463 #ifdef DEBUG_INCLUDE
2464 debug_pos += sprintf (&debug[debug_pos], " Evaluate (): %c\n",
2465 current->value ? '1' : '0');
2468 current = current->parent;
2472 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2473 "Unmatched '(' in \"%s\" in file %s",
2479 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2480 "Unmatched ')' in \"%s\" in file %s",
2486 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2493 retval = (root == (struct parse_node *) NULL) ? 0 : root->value;
2497 /*-------------------------------------------------------------------------*/
2498 #ifdef DEBUG_INCLUDE
2500 /* XXX overlaying the static string pointed to by cond_txt isn't cool */
2502 #define MAX_DEBUG_SIZE MAX_STRING_LEN
2503 #define LOG_COND_STATUS(cntx, t_buck, h_ptr, ins_head, tag_text) \
2505 char *cond_txt = "**** X conditional_status=\"0\"\n"; \
2507 if (cntx->flags & FLAG_COND_TRUE) { \
2508 cond_txt[31] = '1'; \
2510 memcpy(&cond_txt[5], tag_text, sizeof(tag_text)-1); \
2511 t_buck = apr_bucket_heap_create(cond_txt, sizeof(cond_txt)-1, \
2512 NULL, h_ptr->list); \
2513 APR_BUCKET_INSERT_BEFORE(h_ptr, t_buck); \
2515 if (ins_head == NULL) { \
2516 ins_head = t_buck; \
2519 #define DUMP_PARSE_EXPR_DEBUG(t_buck, h_ptr, d_buf, ins_head) \
2521 if (d_buf[0] != '\0') { \
2522 t_buck = apr_bucket_heap_create(d_buf, strlen(d_buf), \
2523 NULL, h_ptr->list); \
2524 APR_BUCKET_INSERT_BEFORE(h_ptr, t_buck); \
2526 if (ins_head == NULL) { \
2527 ins_head = t_buck; \
2533 #define MAX_DEBUG_SIZE 10
2534 #define LOG_COND_STATUS(cntx, t_buck, h_ptr, ins_head, tag_text)
2535 #define DUMP_PARSE_EXPR_DEBUG(t_buck, h_ptr, d_buf, ins_head)
2538 /*-------------------------------------------------------------------------*/
2540 /* pjr - These seem to allow expr="fred" expr="joe" where joe overwrites fred. */
2541 static int handle_if(include_ctx_t *ctx, apr_bucket_brigade **bb,
2542 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
2543 apr_bucket **inserted_head)
2546 char *tag_val = NULL;
2548 int expr_ret, was_error, was_unmatched;
2549 apr_bucket *tmp_buck;
2550 char debug_buf[MAX_DEBUG_SIZE];
2552 *inserted_head = NULL;
2553 if (!ctx->flags & FLAG_PRINTING) {
2554 ctx->if_nesting_level++;
2558 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
2561 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2562 "missing expr in if statement: %s",
2564 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
2568 expr_ret = parse_expr(r, ctx, expr, &was_error,
2569 &was_unmatched, debug_buf);
2571 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
2575 if (was_unmatched) {
2576 DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr,
2577 "\nUnmatched '\n", *inserted_head);
2579 DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, debug_buf,
2583 ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2586 ctx->flags &= FLAG_CLEAR_PRINT_COND;
2588 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head,
2590 ctx->if_nesting_level = 0;
2593 else if (!strcmp(tag, "expr")) {
2595 #ifdef DEBUG_INCLUDE
2597 apr_size_t d_len = 0;
2598 d_len = sprintf(debug_buf, "**** if expr=\"%s\"\n", expr);
2599 tmp_buck = apr_bucket_heap_create(debug_buf, d_len, NULL,
2600 r->connection->bucket_alloc);
2601 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2603 if (*inserted_head == NULL) {
2604 *inserted_head = tmp_buck;
2610 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2611 "unknown parameter \"%s\" to tag if in %s", tag,
2613 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2621 static int handle_elif(include_ctx_t *ctx, apr_bucket_brigade **bb,
2622 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
2623 apr_bucket **inserted_head)
2626 char *tag_val = NULL;
2628 int expr_ret, was_error, was_unmatched;
2629 apr_bucket *tmp_buck;
2630 char debug_buf[MAX_DEBUG_SIZE];
2632 *inserted_head = NULL;
2633 if (!ctx->if_nesting_level) {
2635 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
2637 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head,
2640 if (ctx->flags & FLAG_COND_TRUE) {
2641 ctx->flags &= FLAG_CLEAR_PRINTING;
2645 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2646 "missing expr in elif statement: %s",
2648 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
2652 expr_ret = parse_expr(r, ctx, expr, &was_error,
2653 &was_unmatched, debug_buf);
2655 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
2659 if (was_unmatched) {
2660 DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr,
2661 "\nUnmatched '\n", *inserted_head);
2663 DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, debug_buf,
2667 ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2670 ctx->flags &= FLAG_CLEAR_PRINT_COND;
2672 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head,
2676 else if (!strcmp(tag, "expr")) {
2678 #ifdef DEBUG_INCLUDE
2680 apr_size_t d_len = 0;
2681 d_len = sprintf(debug_buf, "**** elif expr=\"%s\"\n", expr);
2682 tmp_buck = apr_bucket_heap_create(debug_buf, d_len, NULL,
2683 r->connection->bucket_alloc);
2684 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2686 if (*inserted_head == NULL) {
2687 *inserted_head = tmp_buck;
2693 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2694 "unknown parameter \"%s\" to tag if in %s", tag,
2696 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2703 static int handle_else(include_ctx_t *ctx, apr_bucket_brigade **bb,
2704 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
2705 apr_bucket **inserted_head)
2708 char *tag_val = NULL;
2709 apr_bucket *tmp_buck;
2711 *inserted_head = NULL;
2712 if (!ctx->if_nesting_level) {
2713 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2714 if ((tag != NULL) || (tag_val != NULL)) {
2715 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2716 "else directive does not take tags in %s", r->filename);
2717 if (ctx->flags & FLAG_PRINTING) {
2718 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2723 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, " else");
2725 if (ctx->flags & FLAG_COND_TRUE) {
2726 ctx->flags &= FLAG_CLEAR_PRINTING;
2729 ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2737 static int handle_endif(include_ctx_t *ctx, apr_bucket_brigade **bb,
2738 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
2739 apr_bucket **inserted_head)
2742 char *tag_val = NULL;
2743 apr_bucket *tmp_buck;
2745 *inserted_head = NULL;
2746 if (!ctx->if_nesting_level) {
2747 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2748 if ((tag != NULL) || (tag_val != NULL)) {
2749 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2750 "endif directive does not take tags in %s", r->filename);
2751 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2755 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, "endif");
2756 ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2761 ctx->if_nesting_level--;
2766 static int handle_set(include_ctx_t *ctx, apr_bucket_brigade **bb,
2767 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
2768 apr_bucket **inserted_head)
2771 char *tag_val = NULL;
2773 apr_bucket *tmp_buck;
2774 char *parsed_string;
2775 request_rec *sub = r->main;
2776 apr_pool_t *p = r->pool;
2778 /* we need to use the 'main' request pool to set notes as that is
2786 *inserted_head = NULL;
2787 if (ctx->flags & FLAG_PRINTING) {
2789 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2790 if ((tag == NULL) && (tag_val == NULL)) {
2793 else if (tag_val == NULL) {
2796 else if (!strcmp(tag, "var")) {
2797 var = ap_ssi_parse_string(r, ctx, tag_val, NULL,
2800 else if (!strcmp(tag, "value")) {
2801 if (var == (char *) NULL) {
2802 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2803 "variable must precede value in set directive in %s",
2805 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
2809 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL,
2811 apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
2812 apr_pstrdup(p, parsed_string));
2815 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2816 "Invalid tag for set directive in %s", r->filename);
2817 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2825 static int handle_printenv(include_ctx_t *ctx, apr_bucket_brigade **bb,
2826 request_rec *r, ap_filter_t *f,
2827 apr_bucket *head_ptr, apr_bucket **inserted_head)
2830 char *tag_val = NULL;
2831 apr_bucket *tmp_buck;
2833 if (ctx->flags & FLAG_PRINTING) {
2834 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2835 if ((tag == NULL) && (tag_val == NULL)) {
2836 const apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
2837 const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
2839 const char *key_text, *val_text;
2840 char *key_val, *next;
2841 apr_size_t k_len, v_len, kv_length;
2843 *inserted_head = NULL;
2844 for (i = 0; i < arr->nelts; ++i) {
2845 key_text = ap_escape_html(r->pool, elts[i].key);
2846 val_text = elts[i].val;
2847 if (val_text == LAZY_VALUE) {
2848 val_text = add_include_vars_lazy(r, elts[i].key);
2850 val_text = ap_escape_html(r->pool, elts[i].val);
2851 k_len = strlen(key_text);
2852 v_len = strlen(val_text);
2853 kv_length = k_len + v_len + sizeof("=\n");
2854 key_val = apr_palloc(r->pool, kv_length);
2856 memcpy(next, key_text, k_len);
2859 memcpy(next, val_text, v_len);
2863 tmp_buck = apr_bucket_pool_create(key_val, kv_length - 1,
2865 r->connection->bucket_alloc);
2866 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2867 if (*inserted_head == NULL) {
2868 *inserted_head = tmp_buck;
2874 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2875 "printenv directive does not take tags in %s",
2877 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2884 /* -------------------------- The main function --------------------------- */
2886 static apr_status_t send_parsed_content(apr_bucket_brigade **bb,
2887 request_rec *r, ap_filter_t *f)
2889 include_ctx_t *ctx = f->ctx;
2890 apr_bucket *dptr = APR_BRIGADE_FIRST(*bb);
2891 apr_bucket *tmp_dptr;
2892 apr_bucket_brigade *tag_and_after;
2893 apr_status_t rv = APR_SUCCESS;
2895 if (r->args) { /* add QUERY stuff to env cause it ain't yet */
2896 char *arg_copy = apr_pstrdup(r->pool, r->args);
2898 apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
2899 ap_unescape_url(arg_copy);
2900 apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
2901 ap_escape_shell_cmd(r->pool, arg_copy));
2904 while (dptr != APR_BRIGADE_SENTINEL(*bb) && !APR_BUCKET_IS_EOS(dptr)) {
2905 /* State to check for the STARTING_SEQUENCE. */
2906 if ((ctx->state == PRE_HEAD) || (ctx->state == PARSE_HEAD)) {
2908 apr_size_t cleanup_bytes = ctx->parse_pos;
2910 tmp_dptr = find_start_sequence(dptr, ctx, *bb, &do_cleanup);
2911 if (!APR_STATUS_IS_SUCCESS(ctx->status)) {
2915 /* The few bytes stored in the ssi_tag_brigade turned out not to
2916 * be a tag after all. This can only happen if the starting
2917 * tag actually spans brigades. This should be very rare.
2919 if ((do_cleanup) && (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade))) {
2920 apr_bucket *tmp_bkt;
2922 tmp_bkt = apr_bucket_immortal_create(ctx->start_seq,
2924 r->connection->bucket_alloc);
2925 APR_BRIGADE_INSERT_HEAD(*bb, tmp_bkt);
2926 apr_brigade_cleanup(ctx->ssi_tag_brigade);
2929 /* If I am inside a conditional (if, elif, else) that is false
2930 * then I need to throw away anything contained in it.
2932 if ((!(ctx->flags & FLAG_PRINTING)) && (tmp_dptr != NULL) &&
2933 (dptr != APR_BRIGADE_SENTINEL(*bb))) {
2934 while ((dptr != APR_BRIGADE_SENTINEL(*bb)) &&
2935 (dptr != tmp_dptr)) {
2936 apr_bucket *free_bucket = dptr;
2938 dptr = APR_BUCKET_NEXT (dptr);
2939 apr_bucket_delete(free_bucket);
2943 /* Adjust the current bucket position based on what was found... */
2944 if ((tmp_dptr != NULL) && (ctx->state == PARSE_DIRECTIVE)) {
2945 if (ctx->tag_start_bucket != NULL) {
2946 dptr = ctx->tag_start_bucket;
2949 dptr = APR_BRIGADE_SENTINEL(*bb);
2952 else if ((tmp_dptr != NULL) &&
2954 (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD))) {
2955 /* Send the large chunk of pre-tag bytes... */
2956 tag_and_after = apr_brigade_split(*bb, tmp_dptr);
2957 if (ctx->output_flush) {
2958 APR_BRIGADE_INSERT_TAIL(*bb, apr_bucket_flush_create((*bb)->bucket_alloc));
2961 rv = ap_pass_brigade(f->next, *bb);
2962 if (rv != APR_SUCCESS) {
2965 *bb = tag_and_after;
2967 ctx->output_flush = 0;
2968 ctx->bytes_parsed = 0;
2969 ctx->output_now = 0;
2971 else if (tmp_dptr == NULL) {
2972 /* There was no possible SSI tag in the
2973 * remainder of this brigade... */
2974 dptr = APR_BRIGADE_SENTINEL(*bb);
2978 /* State to check for the ENDING_SEQUENCE. */
2979 if (((ctx->state == PARSE_DIRECTIVE) ||
2980 (ctx->state == PARSE_TAG) ||
2981 (ctx->state == PARSE_TAIL)) &&
2982 (dptr != APR_BRIGADE_SENTINEL(*bb))) {
2983 tmp_dptr = find_end_sequence(dptr, ctx, *bb);
2984 if (!APR_STATUS_IS_SUCCESS(ctx->status)) {
2988 if (tmp_dptr != NULL) {
2989 dptr = tmp_dptr; /* Adjust bucket pos... */
2991 /* If some of the tag has already been set aside then set
2992 * aside remainder of tag. Now the full tag is in
2994 * If none has yet been set aside, then leave it all where it
2996 * In any event after this the entire set of tag buckets will
2997 * be in one place or another.
2999 if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
3000 tag_and_after = apr_brigade_split(*bb, dptr);
3001 APR_BRIGADE_CONCAT(ctx->ssi_tag_brigade, *bb);
3002 *bb = tag_and_after;
3004 else if (ctx->output_now ||
3005 (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD)) {
3006 SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next, rv);
3007 if (rv != APR_SUCCESS) {
3010 ctx->output_flush = 0;
3011 ctx->output_now = 0;
3015 /* remainder of this brigade... */
3016 dptr = APR_BRIGADE_SENTINEL(*bb);
3020 /* State to processed the directive... */
3021 if (ctx->state == PARSED) {
3022 apr_bucket *content_head = NULL, *tmp_bkt;
3024 char tmp_buf[TMP_BUF_SIZE];
3025 int (*handle_func)(include_ctx_t *, apr_bucket_brigade **,
3026 request_rec *, ap_filter_t *, apr_bucket *,
3029 /* By now the full tag (all buckets) should either be set aside into
3030 * ssi_tag_brigade or contained within the current bb. All tag
3031 * processing from here on can assume that.
3034 /* At this point, everything between ctx->head_start_bucket and
3035 * ctx->tail_start_bucket is an SSI
3036 * directive, we just have to deal with it now.
3038 if (get_combined_directive(ctx, r, *bb, tmp_buf,
3039 TMP_BUF_SIZE) != APR_SUCCESS) {
3040 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
3041 "mod_include: error copying directive in %s",
3043 CREATE_ERROR_BUCKET(ctx, tmp_bkt, dptr, content_head);
3045 /* DO CLEANUP HERE!!!!! */
3046 tmp_dptr = ctx->head_start_bucket;
3047 if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
3048 apr_brigade_cleanup(ctx->ssi_tag_brigade);
3053 tmp_dptr = APR_BUCKET_NEXT (tmp_dptr);
3054 apr_bucket_delete(tmp_bkt);
3055 } while ((tmp_dptr != dptr) &&
3056 (tmp_dptr != APR_BRIGADE_SENTINEL(*bb)));
3062 /* Can't destroy the tag buckets until I'm done processing
3063 * because the combined_tag might just be pointing to
3064 * the contents of a single bucket!
3067 /* Retrieve the handler function to be called for this directive
3068 * from the functions registered in the hash table.
3069 * Need to lower case the directive for proper matching. Also need
3070 * to have it NULL terminated for proper hash matching.
3072 for (tmp_i = 0; tmp_i < ctx->directive_length; tmp_i++) {
3073 ctx->combined_tag[tmp_i] =
3074 apr_tolower(ctx->combined_tag[tmp_i]);
3076 ctx->combined_tag[ctx->directive_length] = '\0';
3077 ctx->curr_tag_pos = &ctx->combined_tag[ctx->directive_length+1];
3080 (include_handler_fn_t *)apr_hash_get(include_hash,
3082 ctx->directive_length);
3083 if (handle_func != NULL) {
3084 rv = (*handle_func)(ctx, bb, r, f, dptr, &content_head);
3085 if ((rv != 0) && (rv != 1)) {
3090 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
3091 "unknown directive \"%s\" in parsed doc %s",
3092 ctx->combined_tag, r->filename);
3093 CREATE_ERROR_BUCKET(ctx, tmp_bkt, dptr, content_head);
3096 /* This chunk of code starts at the first bucket in the chain
3097 * of tag buckets (assuming that by this point the bucket for
3098 * the STARTING_SEQUENCE has been split) and loops through to
3099 * the end of the tag buckets freeing them all.
3101 * Remember that some part of this may have been set aside
3102 * into the ssi_tag_brigade and the remainder (possibly as
3103 * little as one byte) will be in the current brigade.
3105 * The value of dptr should have been set during the
3106 * PARSE_TAIL state to the first bucket after the
3109 * The value of content_head may have been set during processing
3110 * of the directive. If so, the content was inserted in front
3111 * of the dptr bucket. The inserted buckets should not be thrown
3112 * away here, but they should also not be parsed later.
3114 if (content_head == NULL) {
3115 content_head = dptr;
3117 tmp_dptr = ctx->head_start_bucket;
3118 if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
3119 apr_brigade_cleanup(ctx->ssi_tag_brigade);
3124 tmp_dptr = APR_BUCKET_NEXT (tmp_dptr);
3125 apr_bucket_delete(tmp_bkt);
3126 } while ((tmp_dptr != content_head) &&
3127 (tmp_dptr != APR_BRIGADE_SENTINEL(*bb)));
3129 if (ctx->combined_tag == tmp_buf) {
3130 ctx->combined_tag = NULL;
3133 /* Don't reset the flags or the nesting level!!! */
3135 ctx->head_start_bucket = NULL;
3136 ctx->head_start_index = 0;
3137 ctx->tag_start_bucket = NULL;
3138 ctx->tag_start_index = 0;
3139 ctx->tail_start_bucket = NULL;
3140 ctx->tail_start_index = 0;
3141 ctx->curr_tag_pos = NULL;
3142 ctx->tag_length = 0;
3143 ctx->directive_length = 0;
3145 if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
3146 apr_brigade_cleanup(ctx->ssi_tag_brigade);
3149 ctx->state = PRE_HEAD;
3153 /* We have nothing more to send, stop now. */
3154 if (dptr != APR_BRIGADE_SENTINEL(*bb) &&
3155 APR_BUCKET_IS_EOS(dptr)) {
3156 /* We might have something saved that we never completed, but send
3157 * down unparsed. This allows for <!-- at the end of files to be
3158 * sent correctly. */
3159 if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
3160 APR_BRIGADE_CONCAT(ctx->ssi_tag_brigade, *bb);
3161 return ap_pass_brigade(f->next, ctx->ssi_tag_brigade);
3163 return ap_pass_brigade(f->next, *bb);
3166 /* If I am in the middle of parsing an SSI tag then I need to set aside
3167 * the pertinent trailing buckets and pass on the initial part of the
3168 * brigade. The pertinent parts of the next brigades will be added to
3169 * these set aside buckets to form the whole tag and will be processed
3170 * once the whole tag has been found.
3172 if (ctx->state == PRE_HEAD) {
3173 /* Inside a false conditional (if, elif, else), so toss it all... */
3174 if ((dptr != APR_BRIGADE_SENTINEL(*bb)) &&
3175 (!(ctx->flags & FLAG_PRINTING))) {
3176 apr_bucket *free_bucket;
3179 dptr = APR_BUCKET_NEXT (dptr);
3180 apr_bucket_delete(free_bucket);
3181 } while (dptr != APR_BRIGADE_SENTINEL(*bb));
3184 /* Otherwise pass it along...
3185 * No SSI tags in this brigade... */
3186 rv = ap_pass_brigade(f->next, *bb);
3187 if (rv != APR_SUCCESS) {
3190 ctx->bytes_parsed = 0;
3193 else if (ctx->state == PARSED) { /* Invalid internal condition... */
3194 apr_bucket *content_head = NULL, *tmp_bkt;
3195 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
3196 "Invalid mod_include state during file %s", r->filename);
3197 CREATE_ERROR_BUCKET(ctx, tmp_bkt, APR_BRIGADE_FIRST(*bb), content_head);
3199 else { /* Entire brigade is middle chunk of SSI tag... */
3200 if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
3201 APR_BRIGADE_CONCAT(ctx->ssi_tag_brigade, *bb);
3203 else { /* End of brigade contains part of SSI tag... */
3204 if (ctx->head_start_index > 0) {
3205 apr_bucket_split(ctx->head_start_bucket, ctx->head_start_index);
3206 ctx->head_start_bucket =
3207 APR_BUCKET_NEXT(ctx->head_start_bucket);
3208 ctx->head_start_index = 0;
3210 /* Set aside tag, pass pre-tag... */
3211 tag_and_after = apr_brigade_split(*bb, ctx->head_start_bucket);
3212 ap_save_brigade(f, &ctx->ssi_tag_brigade, &tag_and_after, r->pool);
3213 rv = ap_pass_brigade(f->next, *bb);
3214 if (rv != APR_SUCCESS) {
3217 ctx->bytes_parsed = 0;
3223 static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
3225 include_dir_config *result =
3226 (include_dir_config *)apr_palloc(p, sizeof(include_dir_config));
3227 enum xbithack *xbh = (enum xbithack *) apr_palloc(p, sizeof(enum xbithack));
3228 *xbh = DEFAULT_XBITHACK;
3229 result->default_error_msg = DEFAULT_ERROR_MSG;
3230 result->default_time_fmt = DEFAULT_TIME_FORMAT;
3231 result->xbithack = xbh;
3235 static void *create_includes_server_config(apr_pool_t*p, server_rec *server)
3237 include_server_config *result =
3238 (include_server_config *)apr_palloc(p, sizeof(include_server_config));
3239 result->default_end_tag = ENDING_SEQUENCE;
3240 result->default_start_tag =STARTING_SEQUENCE;
3241 result->start_tag_len = sizeof(STARTING_SEQUENCE)-1;
3242 /* compile the pattern used by find_start_sequence */
3243 bndm_compile(&result->start_seq_pat, result->default_start_tag,
3244 result->start_tag_len);
3246 result->undefinedEcho = apr_pstrdup(p,"(none)");
3247 result->undefinedEchoLen = strlen( result->undefinedEcho);
3250 static const char *set_xbithack(cmd_parms *cmd, void *xbp, const char *arg)
3252 include_dir_config *conf = (include_dir_config *)xbp;
3254 if (!strcasecmp(arg, "off")) {
3255 *conf->xbithack = xbithack_off;
3257 else if (!strcasecmp(arg, "on")) {
3258 *conf->xbithack = xbithack_on;
3260 else if (!strcasecmp(arg, "full")) {
3261 *conf->xbithack = xbithack_full;
3264 return "XBitHack must be set to Off, On, or Full";
3270 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
3272 request_rec *r = f->r;
3273 include_ctx_t *ctx = f->ctx;
3274 request_rec *parent;
3275 include_dir_config *conf =
3276 (include_dir_config *)ap_get_module_config(r->per_dir_config,
3279 include_server_config *sconf= ap_get_module_config(r->server->module_config,
3282 if (!(ap_allow_options(r) & OPT_INCLUDES)) {
3283 return ap_pass_brigade(f->next, b);
3287 f->ctx = ctx = apr_pcalloc(f->c->pool, sizeof(*ctx));
3288 ctx->state = PRE_HEAD;
3289 ctx->flags = (FLAG_PRINTING | FLAG_COND_TRUE);
3290 if (ap_allow_options(r) & OPT_INCNOEXEC) {
3291 ctx->flags |= FLAG_NO_EXEC;
3293 ctx->ssi_tag_brigade = apr_brigade_create(f->c->pool,
3294 f->c->bucket_alloc);
3295 ctx->status = APR_SUCCESS;
3297 ctx->error_str = conf->default_error_msg;
3298 ctx->time_str = conf->default_time_fmt;
3299 ctx->pool = f->c->pool;
3300 ctx->start_seq_pat = &sconf->start_seq_pat;
3301 ctx->start_seq = sconf->default_start_tag;
3302 ctx->start_seq_len = sconf->start_tag_len;
3303 ctx->end_seq = sconf->default_end_tag;
3306 ctx->bytes_parsed = 0;
3309 if ((parent = ap_get_module_config(r->request_config, &include_module))) {
3310 /* Kludge --- for nested includes, we want to keep the subprocess
3311 * environment of the base document (for compatibility); that means
3312 * torquing our own last_modified date as well so that the
3313 * LAST_MODIFIED variable gets reset to the proper value if the
3314 * nested document resets <!--#config timefmt -->.
3316 r->subprocess_env = r->main->subprocess_env;
3317 apr_pool_join(r->main->pool, r->pool);
3318 r->finfo.mtime = r->main->finfo.mtime;
3321 /* we're not a nested include, so we create an initial
3323 ap_add_common_vars(r);
3325 add_include_vars(r, conf->default_time_fmt);
3327 /* XXX: this is bogus, at some point we're going to do a subrequest,
3328 * and when we do it we're going to be subjecting code that doesn't
3329 * expect to be signal-ready to SIGALRM. There is no clean way to
3330 * fix this, except to put alarm support into BUFF. -djg
3333 /* Always unset the content-length. There is no way to know if
3334 * the content will be modified at some point by send_parsed_content.
3335 * It is very possible for us to not find any content in the first
3336 * 9k of the file, but still have to modify the content of the file.
3337 * If we are going to pass the file through send_parsed_content, then
3338 * the content-length should just be unset.
3340 apr_table_unset(f->r->headers_out, "Content-Length");
3342 /* Always unset the ETag/Last-Modified fields - see RFC2616 - 13.3.4.
3343 * We don't know if we are going to be including a file or executing
3344 * a program which may change the Last-Modified header or make the
3345 * content completely dynamic. Therefore, we can't support these
3347 * Exception: XBitHack full means we *should* set the Last-Modified field.
3349 apr_table_unset(f->r->headers_out, "ETag");
3351 /* Assure the platform supports Group protections */
3352 if ((*conf->xbithack == xbithack_full)
3353 && (r->finfo.valid & APR_FINFO_GPROT)
3354 && (r->finfo.protection & APR_GEXECUTE)) {
3355 ap_update_mtime(r, r->finfo.mtime);
3356 ap_set_last_modified(r);
3359 apr_table_unset(f->r->headers_out, "Last-Modified");
3362 return send_parsed_content(&b, r, f);
3365 static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
3367 apr_hash_set(include_hash, tag, strlen(tag), (const void *)func);
3370 static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
3371 apr_pool_t *ptemp, server_rec *s)
3373 include_hash = apr_hash_make(p);
3375 ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
3377 if(ssi_pfn_register) {
3378 ssi_pfn_register("if", handle_if);
3379 ssi_pfn_register("set", handle_set);
3380 ssi_pfn_register("else", handle_else);
3381 ssi_pfn_register("elif", handle_elif);
3382 ssi_pfn_register("echo", handle_echo);
3383 ssi_pfn_register("endif", handle_endif);
3384 ssi_pfn_register("fsize", handle_fsize);
3385 ssi_pfn_register("config", handle_config);
3386 ssi_pfn_register("include", handle_include);
3387 ssi_pfn_register("flastmod", handle_flastmod);
3388 ssi_pfn_register("printenv", handle_printenv);
3393 static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig, const char *msg)
3395 include_dir_config *conf = (include_dir_config *)mconfig;
3396 conf->default_error_msg = apr_pstrdup(cmd->pool, msg);
3400 static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig, const char *msg)
3402 include_server_config *conf;
3403 conf= ap_get_module_config(cmd->server->module_config , &include_module);
3404 conf->default_start_tag = apr_pstrdup(cmd->pool, msg);
3405 conf->start_tag_len = strlen(conf->default_start_tag );
3406 bndm_compile(&conf->start_seq_pat, conf->default_start_tag,
3407 conf->start_tag_len);
3411 static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig, const char *msg)
3413 include_server_config *conf;
3414 conf = ap_get_module_config(cmd->server->module_config, &include_module);
3415 conf->undefinedEcho = apr_pstrdup(cmd->pool, msg);
3416 conf->undefinedEchoLen = strlen(msg);
3422 static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig, const char *msg)
3424 include_server_config *conf;
3425 conf= ap_get_module_config(cmd->server->module_config , &include_module);
3426 conf->default_end_tag = apr_pstrdup(cmd->pool, msg);
3431 static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig, const char *fmt)
3433 include_dir_config *conf = (include_dir_config *)mconfig;
3434 conf->default_time_fmt = apr_pstrdup(cmd->pool, fmt);
3439 * Module definition and configuration data structs...
3441 static const command_rec includes_cmds[] =
3443 AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS,
3444 "Off, On, or Full"),
3445 AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL,
3447 AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
3448 "a strftime(3) formatted string"),
3449 AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF,
3450 "SSI Start String Tag"),
3451 AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF,
3452 "SSI End String Tag"),
3453 AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, RSRC_CONF,
3454 "SSI Start String Tag"),
3459 static int include_fixup(request_rec *r)
3461 include_dir_config *conf;
3463 conf = (include_dir_config *) ap_get_module_config(r->per_dir_config,
3466 if (r->handler && (strcmp(r->handler, "server-parsed") == 0))
3468 if (!r->content_type || !*r->content_type) {
3469 ap_set_content_type(r, "text/html");
3471 r->handler = "default-handler";
3474 #if defined(OS2) || defined(WIN32) || defined(NETWARE)
3475 /* These OS's don't support xbithack. This is being worked on. */
3481 if (*conf->xbithack == xbithack_off) {
3485 if (!(r->finfo.protection & APR_UEXECUTE)) {
3489 if (!r->content_type || strcmp(r->content_type, "text/html")) {
3495 /* We always return declined, because the default handler actually
3496 * serves the file. All we have to do is add the filter.
3498 ap_add_output_filter("INCLUDES", NULL, r, r->connection);
3502 static void register_hooks(apr_pool_t *p)
3504 APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
3505 APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
3506 APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
3507 ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
3508 ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST);
3509 ap_register_output_filter("INCLUDES", includes_filter, AP_FTYPE_RESOURCE);
3512 module AP_MODULE_DECLARE_DATA include_module =
3514 STANDARD20_MODULE_STUFF,
3515 create_includes_dir_config, /* dir config creater */
3516 NULL, /* dir merger --- default is to override */
3517 create_includes_server_config,/* server config */
3518 NULL, /* merge server config */
3519 includes_cmds, /* command apr_table_t */
3520 register_hooks /* register hooks */