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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * http_include.c: Handles the server-parsed HTML documents
20 * Original by Rob McCool; substantial fixups by David Robinson;
21 * incorporated into the Apache module framework by rst.
26 #include "apr_strings.h"
27 #include "apr_thread_proc.h"
31 #include "apr_optional.h"
33 #define APR_WANT_STRFUNC
34 #define APR_WANT_MEMFUNC
39 #include "ap_config.h"
40 #include "util_filter.h"
42 #include "http_config.h"
43 #include "http_core.h"
44 #include "http_request.h"
45 #include "http_core.h"
46 #include "http_protocol.h"
48 #include "http_main.h"
49 #include "util_script.h"
50 #include "http_core.h"
52 #define MOD_INCLUDE_REDESIGN
53 #include "mod_include.h"
54 #include "util_ebcdic.h"
56 module AP_MODULE_DECLARE_DATA include_module;
57 static apr_hash_t *include_hash;
58 static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
60 /*****************************************************************
62 * XBITHACK. Sigh... NB it's configurable per-directory; the compile-time
63 * option only changes the default.
67 xbithack_off, xbithack_on, xbithack_full
76 char *default_error_msg;
77 char *default_time_fmt;
78 enum xbithack *xbithack;
82 char *default_start_tag;
83 char *default_end_tag;
88 } include_server_config;
90 /* main parser states */
95 PARSE_DIRECTIVE_POSTNAME,
97 PARSE_DIRECTIVE_POSTTAIL,
112 typedef struct ssi_arg_item {
113 struct ssi_arg_item *next;
117 apr_size_t value_len;
124 char quote; /* quote character value (or \0) */
126 apr_bucket_brigade *tmp_bb;
128 apr_size_t end_seq_len;
129 char *directive; /* name of the current directive */
131 unsigned argc; /* argument counter (of the current
134 ssi_arg_item_t *argv; /* all arguments */
135 ssi_arg_item_t *current_arg; /* currently parsed argument */
137 include_ctx_t *ctx; /* public part of the context structure */
143 #define DEFAULT_XBITHACK xbithack_full
145 #define DEFAULT_XBITHACK xbithack_off
148 #define BYTE_COUNT_THRESHOLD AP_MIN_BYTES_TO_WRITE
150 #define SSI_CREATE_ERROR_BUCKET(ctx, f, bb) APR_BRIGADE_INSERT_TAIL((bb), \
151 apr_bucket_pool_create(apr_pstrdup((ctx)->pool, (ctx)->error_str), \
152 strlen((ctx)->error_str), (ctx)->pool, \
153 (f)->c->bucket_alloc))
155 /* ------------------------ Environment function -------------------------- */
157 /* Sentinel value to store in subprocess_env for items that
158 * shouldn't be evaluated until/unless they're actually used
160 static const char lazy_eval_sentinel;
161 #define LAZY_VALUE (&lazy_eval_sentinel)
163 static void add_include_vars(request_rec *r, char *timefmt)
165 apr_table_t *e = r->subprocess_env;
168 apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
169 apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
170 apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
171 apr_table_setn(e, "DOCUMENT_URI", r->uri);
172 if (r->path_info && *r->path_info) {
173 apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
175 apr_table_setn(e, "USER_NAME", LAZY_VALUE);
176 if (r->filename && (t = strrchr(r->filename, '/'))) {
177 apr_table_setn(e, "DOCUMENT_NAME", ++t);
180 apr_table_setn(e, "DOCUMENT_NAME", r->uri);
183 char *arg_copy = apr_pstrdup(r->pool, r->args);
185 ap_unescape_url(arg_copy);
186 apr_table_setn(e, "QUERY_STRING_UNESCAPED",
187 ap_escape_shell_cmd(r->pool, arg_copy));
191 static const char *add_include_vars_lazy(request_rec *r, const char *var)
194 if (!strcasecmp(var, "DATE_LOCAL")) {
195 include_dir_config *conf =
196 (include_dir_config *)ap_get_module_config(r->per_dir_config,
198 val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 0);
200 else if (!strcasecmp(var, "DATE_GMT")) {
201 include_dir_config *conf =
202 (include_dir_config *)ap_get_module_config(r->per_dir_config,
204 val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 1);
206 else if (!strcasecmp(var, "LAST_MODIFIED")) {
207 include_dir_config *conf =
208 (include_dir_config *)ap_get_module_config(r->per_dir_config,
210 val = ap_ht_time(r->pool, r->finfo.mtime, conf->default_time_fmt, 0);
212 else if (!strcasecmp(var, "USER_NAME")) {
213 if (apr_get_username(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
222 apr_table_setn(r->subprocess_env, var, val);
227 static const char *get_include_var(request_rec *r, include_ctx_t *ctx,
231 if (apr_isdigit(*var) && !var[1]) {
232 /* Handle $0 .. $9 from the last regex evaluated.
233 * The choice of returning NULL strings on not-found,
234 * v.s. empty strings on an empty match is deliberate.
236 if (!ctx->re_result || !ctx->re_string) {
241 apr_size_t len = (*ctx->re_result)[idx].rm_eo
242 - (*ctx->re_result)[idx].rm_so;
243 if ( (*ctx->re_result)[idx].rm_so < 0
244 || (*ctx->re_result)[idx].rm_eo < 0) {
247 val = apr_pstrmemdup(r->pool, ctx->re_string
248 + (*ctx->re_result)[idx].rm_so, len);
252 val = apr_table_get(r->subprocess_env, var);
254 if (val == LAZY_VALUE)
255 val = add_include_vars_lazy(r, var);
260 /* --------------------------- Parser functions --------------------------- */
262 /* This is an implementation of the BNDM search algorithm.
264 * Fast and Flexible String Matching by Combining Bit-parallelism and
265 * Suffix Automata (2001)
266 * Gonzalo Navarro, Mathieu Raffinot
268 * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
270 * Initial code submitted by Sascha Schumann.
273 /* Precompile the bndm_t data structure. */
274 static void bndm_compile(bndm_t *t, const char *n, apr_size_t nl)
277 const char *ne = n + nl;
279 memset(t->T, 0, sizeof(unsigned int) * 256);
281 for (x = 1; n < ne; x <<= 1)
282 t->T[(unsigned char) *n++] |= x;
287 /* Implements the BNDM search algorithm (as described above).
289 * n - the pattern to search for
290 * nl - length of the pattern to search for
291 * h - the string to look in
292 * hl - length of the string to look for
293 * t - precompiled bndm structure against the pattern
295 * Returns the count of character that is the first match or hl if no
298 static apr_size_t bndm(const char *n, apr_size_t nl, const char *h,
299 apr_size_t hl, bndm_t *t)
302 const char *he, *p, *pi;
303 unsigned int *T, x, d;
310 pi = h - 1; /* pi: p initial */
311 p = pi + nl; /* compare window right to left. point to the first char */
317 d &= T[(unsigned char) *p--];
338 * decodes a string containing html entities or numeric character references.
339 * 's' is overwritten with the decoded string.
340 * If 's' is syntatically incorrect, then the followed fixups will be made:
341 * unknown entities will be left undecoded;
342 * references to unused numeric characters will be deleted.
343 * In particular, � will not be decoded, but will be deleted.
348 /* maximum length of any ISO-LATIN-1 HTML entity name. */
349 #define MAXENTLEN (6)
351 /* The following is a shrinking transformation, therefore safe. */
353 static void decodehtml(char *s)
358 static const char * const entlist[MAXENTLEN + 1] =
362 "lt\074gt\076", /* 2 */
363 "amp\046ETH\320eth\360", /* 3 */
364 "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml\353\
365 iuml\357ouml\366uuml\374yuml\377", /* 4 */
366 "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc\333\
367 THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352icirc\356ocirc\364\
368 ucirc\373thorn\376", /* 5 */
369 "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311\
370 Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde\325Oslash\330\
371 Ugrave\331Uacute\332Yacute\335agrave\340aacute\341atilde\343ccedil\347\
372 egrave\350eacute\351igrave\354iacute\355ntilde\361ograve\362oacute\363\
373 otilde\365oslash\370ugrave\371uacute\372yacute\375" /* 6 */
376 /* Do a fast scan through the string until we find anything
377 * that needs more complicated handling
379 for (; *s != '&'; s++) {
385 for (p = s; *s != '\0'; s++, p++) {
390 /* find end of entity */
391 for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
395 if (s[i] == '\0') { /* treat as normal data */
400 /* is it numeric ? */
402 for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
403 val = val * 10 + s[j] - '0';
406 if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
407 (val >= 127 && val <= 160) || val >= 256) {
408 p--; /* no data to output */
411 *p = RAW_ASCII_CHAR(val);
416 if (j > MAXENTLEN || entlist[j] == NULL) {
419 continue; /* skip it */
421 for (ents = entlist[j]; *ents != '\0'; ents += i) {
422 if (strncmp(s + 1, ents, j) == 0) {
428 *p = '&'; /* unknown */
431 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
441 * Extract the next tag name and value.
442 * If there are no more tags, set the tag name to NULL.
443 * The tag value is html decoded if dodecode is non-zero.
444 * The tag value may be NULL if there is no tag value..
446 * [WS]<Tag>[WS]=[WS]['|"|`]<Value>[['|"|`|]|WS]
449 #define SKIP_TAG_WHITESPACE(ptr) while ((*ptr != '\0') && (apr_isspace (*ptr))) ptr++
451 static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
452 char **tag_val, int dodecode)
455 if (ctx->curr_tag_pos >= ctx->combined_tag + ctx->tag_length) {
460 *tag = ctx->curr_tag_pos;
464 ctx->curr_tag_pos = ctx->combined_tag + ctx->tag_length;
468 *tag_val = ap_strchr(*tag, '=');
470 ctx->curr_tag_pos = ctx->combined_tag + ctx->tag_length;
474 /* if it starts with '=' there was no tag name, just a value */
475 if (*tag_val == *tag) {
479 *(*tag_val)++ = '\0';
480 ctx->curr_tag_pos = *tag_val + strlen(*tag_val) + 1; /* skip \0 byte */
483 decodehtml(*tag_val);
489 /* initial buffer size for power-of-two allocator in ap_ssi_parse_string */
490 #define PARSE_STRING_INITIAL_SIZE 64
493 * Do variable substitution on strings
494 * (Note: If out==NULL, this function allocs a buffer for the resulting
495 * string from r->pool. The return value is the parsed string)
497 static char *ap_ssi_parse_string(request_rec *r, include_ctx_t *ctx,
498 const char *in, char *out,
499 apr_size_t length, int leave_name)
506 /* allocate an output buffer if needed */
508 out_size = PARSE_STRING_INITIAL_SIZE;
509 if (out_size > length) {
512 out = apr_palloc(r->pool, out_size);
518 /* leave room for nul terminator */
519 end_out = out + out_size - 1;
522 while ((ch = *in++) != '\0') {
525 if (next == end_out) {
526 if (out_size < length) {
527 /* double the buffer size */
528 apr_size_t new_out_size = out_size * 2;
529 apr_size_t current_length = next - out;
531 if (new_out_size > length) {
532 new_out_size = length;
534 new_out = apr_palloc(r->pool, new_out_size);
535 memcpy(new_out, out, current_length);
537 out_size = new_out_size;
538 end_out = out + out_size - 1;
539 next = out + current_length;
556 const char *start_of_var_name;
557 char *end_of_var_name; /* end of var name + 1 */
558 const char *expansion, *temp_end, *val;
562 /* guess that the expansion won't happen */
566 start_of_var_name = in;
567 in = ap_strchr_c(in, '}');
569 ap_log_rerror(APLOG_MARK, APLOG_ERR,
570 0, r, "Missing '}' on variable \"%s\"",
576 end_of_var_name = (char *)temp_end;
580 start_of_var_name = in;
581 while (apr_isalnum(*in) || *in == '_') {
585 end_of_var_name = (char *)temp_end;
587 /* what a pain, too bad there's no table_getn where you can
588 * pass a non-nul terminated string */
589 l = end_of_var_name - start_of_var_name;
591 tmp_store = *end_of_var_name;
592 *end_of_var_name = '\0';
593 val = get_include_var(r, ctx, start_of_var_name);
594 *end_of_var_name = tmp_store;
598 l = strlen(expansion);
600 else if (leave_name) {
604 /* no expansion to be done */
609 /* zero-length variable name causes just the $ to be
613 if ((next + l > end_out) && (out_size < length)) {
614 /* increase the buffer size to accommodate l more chars */
615 apr_size_t new_out_size = out_size;
616 apr_size_t current_length = next - out;
620 } while (new_out_size < current_length + l + 1); /* +1 for NUL */
621 if (new_out_size > length) {
622 new_out_size = length;
624 new_out = apr_palloc(r->pool, new_out_size);
625 memcpy(new_out, out, current_length);
627 out_size = new_out_size;
628 end_out = out + out_size - 1;
629 next = out + current_length;
631 l = ((int)l > end_out - next) ? (end_out - next) : l;
632 memcpy(next, expansion, l);
637 if (next == end_out) {
638 if (out_size < length) {
639 /* double the buffer size */
640 apr_size_t new_out_size = out_size * 2;
641 apr_size_t current_length = next - out;
643 if (new_out_size > length) {
644 new_out_size = length;
646 new_out = apr_palloc(r->pool, new_out_size);
647 memcpy(new_out, out, current_length);
649 out_size = new_out_size;
650 end_out = out + out_size - 1;
651 next = out + current_length;
667 /* --------------------------- Action handlers ---------------------------- */
669 /* ensure that path is relative, and does not contain ".." elements
670 * ensentially ensure that it does not match the regex:
671 * (^/|(^|/)\.\.(/|$))
672 * XXX: Simply replace with apr_filepath_merge
674 static int is_only_below(const char *path)
676 #ifdef HAVE_DRIVE_LETTERS
681 if (ap_strchr_c(path, ':'))
684 if (path[0] == '/') {
689 while (path[dots] == '.')
692 /* If the name is canonical this is redundant
693 * but in security, redundancy is worthwhile.
694 * Does OS2 belong here (accepts ... for ..)?
696 if (dots > 1 && (!path[dots] || path[dots] == '/'))
699 if (dots == 2 && (!path[dots] || path[dots] == '/'))
703 /* Advance to either the null byte at the end of the
704 * string or the character right after the next slash,
705 * whichever comes first
707 while (*path && (*path++ != '/')) {
714 static int handle_include(include_ctx_t *ctx, apr_bucket_brigade **bb,
715 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
716 apr_bucket **inserted_head)
719 char *tag_val = NULL;
720 apr_bucket *tmp_buck;
722 int loglevel = APLOG_ERR;
724 *inserted_head = NULL;
725 if (ctx->flags & FLAG_PRINTING) {
727 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
728 if (tag_val == NULL) {
736 if (!strcmp(tag, "virtual") || !strcmp(tag, "file")) {
737 request_rec *rr = NULL;
738 char *error_fmt = NULL;
739 apr_status_t rc = APR_SUCCESS;
741 SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next, rc);
742 if (rc != APR_SUCCESS) {
746 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL,
749 /* XXX: Port to apr_filepath_merge
750 * be safe; only files in this directory or below allowed
752 if (!is_only_below(parsed_string)) {
753 error_fmt = "unable to include file \"%s\" "
757 rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
761 rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
764 if (!error_fmt && rr->status != HTTP_OK) {
765 error_fmt = "unable to include \"%s\" in parsed file %s";
768 if (!error_fmt && (ctx->flags & FLAG_NO_EXEC) &&
770 (strncmp(rr->content_type, "text/", 5))) {
771 error_fmt = "unable to include potential exec \"%s\" "
775 /* See the Kludge in send_parsed_file for why */
776 /* Basically, it puts a bread crumb in here, then looks */
777 /* for the crumb later to see if its been here. */
779 ap_set_module_config(rr->request_config,
782 if (!error_fmt && ap_run_sub_req(rr)) {
783 error_fmt = "unable to include \"%s\" in parsed file %s";
786 ap_log_rerror(APLOG_MARK, loglevel,
787 0, r, error_fmt, tag_val, r->filename);
788 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
792 /* Do *not* destroy the subrequest here; it may have allocated
793 * variables in this r->subprocess_env in the subrequest's
794 * r->pool, so that pool must survive as long as this request.
795 * Yes, this is a memory leak. */
798 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
799 "unknown parameter \"%s\" to tag include in %s",
801 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
810 static int handle_echo(include_ctx_t *ctx, apr_bucket_brigade **bb,
811 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
812 apr_bucket **inserted_head)
815 char *tag_val = NULL;
816 const char *echo_text = NULL;
817 apr_bucket *tmp_buck;
819 enum {E_NONE, E_URL, E_ENTITY} encode;
823 *inserted_head = NULL;
824 if (ctx->flags & FLAG_PRINTING) {
826 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
827 if (tag_val == NULL) {
835 if (!strcmp(tag, "var")) {
836 conn_rec *c = r->connection;
838 get_include_var(r, ctx,
839 ap_ssi_parse_string(r, ctx, tag_val, NULL,
847 echo_text = ap_escape_uri(r->pool, val);
850 echo_text = ap_escape_html(r->pool, val);
854 e_len = strlen(echo_text);
855 tmp_buck = apr_bucket_pool_create(echo_text, e_len,
856 r->pool, c->bucket_alloc);
859 include_server_config *sconf=
860 ap_get_module_config(r->server->module_config,
862 tmp_buck = apr_bucket_pool_create(sconf->undefinedEcho,
863 sconf->undefinedEchoLen,
864 r->pool, c->bucket_alloc);
866 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
867 if (*inserted_head == NULL) {
868 *inserted_head = tmp_buck;
871 else if (!strcmp(tag, "encoding")) {
872 if (!strcasecmp(tag_val, "none")) encode = E_NONE;
873 else if (!strcasecmp(tag_val, "url")) encode = E_URL;
874 else if (!strcasecmp(tag_val, "entity")) encode = E_ENTITY;
876 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
877 "unknown value \"%s\" to parameter \"encoding\" of "
878 "tag echo in %s", tag_val, r->filename);
879 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
885 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
886 "unknown parameter \"%s\" in tag echo of %s",
888 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
897 /* error and tf must point to a string with room for at
898 * least MAX_STRING_LEN characters
900 static int handle_config(include_ctx_t *ctx, apr_bucket_brigade **bb,
901 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
902 apr_bucket **inserted_head)
905 char *tag_val = NULL;
907 apr_table_t *env = r->subprocess_env;
909 *inserted_head = NULL;
910 if (ctx->flags & FLAG_PRINTING) {
912 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
913 if (tag_val == NULL) {
915 return 0; /* Reached the end of the string. */
918 return 1; /* tags must have values. */
921 if (!strcmp(tag, "errmsg")) {
922 if (ctx->error_str_override == NULL) {
923 ctx->error_str_override = (char *)apr_palloc(ctx->pool,
925 ctx->error_str = ctx->error_str_override;
927 ap_ssi_parse_string(r, ctx, tag_val, ctx->error_str_override,
930 else if (!strcmp(tag, "timefmt")) {
931 apr_time_t date = r->request_time;
932 if (ctx->time_str_override == NULL) {
933 ctx->time_str_override = (char *)apr_palloc(ctx->pool,
935 ctx->time_str = ctx->time_str_override;
937 ap_ssi_parse_string(r, ctx, tag_val, ctx->time_str_override,
939 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
941 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
943 apr_table_setn(env, "LAST_MODIFIED",
944 ap_ht_time(r->pool, r->finfo.mtime,
947 else if (!strcmp(tag, "sizefmt")) {
948 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL,
950 decodehtml(parsed_string);
951 if (!strcmp(parsed_string, "bytes")) {
952 ctx->flags |= FLAG_SIZE_IN_BYTES;
954 else if (!strcmp(parsed_string, "abbrev")) {
955 ctx->flags &= FLAG_SIZE_ABBREV;
959 apr_bucket *tmp_buck;
961 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
962 "unknown parameter \"%s\" to tag config in %s",
964 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
973 static int find_file(request_rec *r, const char *directive, const char *tag,
974 char *tag_val, apr_finfo_t *finfo)
976 char *to_send = tag_val;
977 request_rec *rr = NULL;
979 char *error_fmt = NULL;
980 apr_status_t rv = APR_SUCCESS;
982 if (!strcmp(tag, "file")) {
983 /* XXX: Port to apr_filepath_merge
984 * be safe; only files in this directory or below allowed
986 if (!is_only_below(tag_val)) {
987 error_fmt = "unable to access file \"%s\" "
991 ap_getparents(tag_val); /* get rid of any nasties */
993 /* note: it is okay to pass NULL for the "next filter" since
994 we never attempt to "run" this sub request. */
995 rr = ap_sub_req_lookup_file(tag_val, r, NULL);
997 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
998 to_send = rr->filename;
999 if ((rv = apr_stat(finfo, to_send,
1000 APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
1001 && rv != APR_INCOMPLETE) {
1002 error_fmt = "unable to get information about \"%s\" "
1003 "in parsed file %s";
1007 error_fmt = "unable to lookup information about \"%s\" "
1008 "in parsed file %s";
1014 ap_log_rerror(APLOG_MARK, APLOG_ERR,
1015 rv, r, error_fmt, to_send, r->filename);
1018 if (rr) ap_destroy_sub_req(rr);
1022 else if (!strcmp(tag, "virtual")) {
1023 /* note: it is okay to pass NULL for the "next filter" since
1024 we never attempt to "run" this sub request. */
1025 rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1027 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1028 memcpy((char *) finfo, (const char *) &rr->finfo,
1030 ap_destroy_sub_req(rr);
1034 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1035 "unable to get information about \"%s\" "
1036 "in parsed file %s",
1037 tag_val, r->filename);
1038 ap_destroy_sub_req(rr);
1043 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1044 "unknown parameter \"%s\" to tag %s in %s",
1045 tag, directive, r->filename);
1050 static int handle_fsize(include_ctx_t *ctx, apr_bucket_brigade **bb,
1051 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
1052 apr_bucket **inserted_head)
1055 char *tag_val = NULL;
1058 apr_bucket *tmp_buck;
1059 char *parsed_string;
1061 *inserted_head = NULL;
1062 if (ctx->flags & FLAG_PRINTING) {
1064 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
1065 if (tag_val == NULL) {
1074 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL,
1076 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
1077 /* XXX: if we *know* we're going to have to copy the
1078 * thing off of the stack anyway, why not palloc buff
1079 * instead of sticking it on the stack; then we can just
1080 * use a pool bucket and skip the copy
1084 if (!(ctx->flags & FLAG_SIZE_IN_BYTES)) {
1085 apr_strfsize(finfo.size, buff);
1086 s_len = strlen (buff);
1092 apr_snprintf(tmp_buff, sizeof(tmp_buff),
1093 "%" APR_OFF_T_FMT, finfo.size);
1094 l = strlen(tmp_buff); /* grrr */
1095 for (x = 0; x < l; x++) {
1096 if (x && (!((l - x) % 3))) {
1099 buff[pos++] = tmp_buff[x];
1105 tmp_buck = apr_bucket_heap_create(buff, s_len, NULL,
1106 r->connection->bucket_alloc);
1107 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
1108 if (*inserted_head == NULL) {
1109 *inserted_head = tmp_buck;
1113 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
1123 static int handle_flastmod(include_ctx_t *ctx, apr_bucket_brigade **bb,
1124 request_rec *r, ap_filter_t *f,
1125 apr_bucket *head_ptr, apr_bucket **inserted_head)
1128 char *tag_val = NULL;
1131 apr_bucket *tmp_buck;
1132 char *parsed_string;
1134 *inserted_head = NULL;
1135 if (ctx->flags & FLAG_PRINTING) {
1137 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
1138 if (tag_val == NULL) {
1147 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL,
1149 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
1152 t_val = ap_ht_time(r->pool, finfo.mtime, ctx->time_str, 0);
1153 t_len = strlen(t_val);
1155 tmp_buck = apr_bucket_pool_create(t_val, t_len, r->pool,
1156 r->connection->bucket_alloc);
1157 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
1158 if (*inserted_head == NULL) {
1159 *inserted_head = tmp_buck;
1163 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
1173 static int re_check(request_rec *r, include_ctx_t *ctx,
1174 char *string, char *rexp)
1177 const apr_size_t nres = sizeof(*ctx->re_result) / sizeof(regmatch_t);
1180 compiled = ap_pregcomp(r->pool, rexp, REG_EXTENDED | REG_NOSUB);
1181 if (compiled == NULL) {
1182 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1183 "unable to compile pattern \"%s\"", rexp);
1186 if (!ctx->re_result) {
1187 ctx->re_result = apr_pcalloc(r->pool, sizeof(*ctx->re_result));
1189 ctx->re_string = string;
1190 regex_error = ap_regexec(compiled, string, nres, *ctx->re_result, 0);
1191 ap_pregfree(r->pool, compiled);
1192 return (!regex_error);
1196 token_string, token_re,
1197 token_and, token_or, token_not, token_eq, token_ne,
1198 token_rbrace, token_lbrace, token_group,
1199 token_ge, token_le, token_gt, token_lt
1202 enum token_type type;
1206 static const char *get_ptoken(request_rec *r, const char *string,
1207 struct token *token, int *unmatched)
1214 token->value = NULL;
1216 /* Skip leading white space */
1217 if (string == (char *) NULL) {
1218 return (char *) NULL;
1220 while ((ch = *string++)) {
1221 if (!apr_isspace(ch)) {
1226 return (char *) NULL;
1229 token->type = token_string; /* the default type */
1232 token->type = token_lbrace;
1235 token->type = token_rbrace;
1238 token->type = token_eq;
1241 if (*string == '=') {
1242 token->type = token_ne;
1243 return (string + 1);
1246 token->type = token_not;
1250 /* already token->type == token_string */
1254 token->type = token_re;
1258 if (*string == '|') {
1259 token->type = token_or;
1260 return (string + 1);
1264 if (*string == '&') {
1265 token->type = token_and;
1266 return (string + 1);
1270 if (*string == '=') {
1271 token->type = token_ge;
1272 return (string + 1);
1275 token->type = token_gt;
1279 if (*string == '=') {
1280 token->type = token_le;
1281 return (string + 1);
1284 token->type = token_lt;
1288 /* already token->type == token_string */
1291 /* We should only be here if we are in a string */
1292 token->value = apr_palloc(r->pool, strlen(string) + 2); /* 2 for ch plus
1299 * I used the ++string throughout this section so that string
1300 * ends up pointing to the next token and I can just return it
1302 for (ch = *string; ((ch != '\0') && (!tkn_fnd)); ch = *++string) {
1304 if ((ch = *++string) == '\0') {
1308 token->value[next++] = ch;
1313 if (apr_isspace(ch)) {
1327 if (*(string + 1) == '|') {
1332 if (*(string + 1) == '&') {
1338 token->value[next++] = ch;
1349 token->value[next++] = ch;
1358 /* If qs is still set, we have an unmatched quote */
1363 token->value[next] = '\0';
1369 /* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1
1370 * characters long...
1372 static int parse_expr(request_rec *r, include_ctx_t *ctx, const char *expr,
1373 int *was_error, int *was_unmatched, char *debug)
1376 struct parse_node *left, *right, *parent;
1379 } *root, *current, *new;
1383 apr_size_t debug_pos = 0;
1385 debug[debug_pos] = '\0';
1388 if ((parse = expr) == (char *) NULL) {
1391 root = current = (struct parse_node *) NULL;
1393 /* Create Parse Tree */
1395 new = (struct parse_node *) apr_palloc(r->pool,
1396 sizeof(struct parse_node));
1397 new->parent = new->left = new->right = (struct parse_node *) NULL;
1399 if ((parse = get_ptoken(r, parse, &new->token, was_unmatched)) ==
1403 switch (new->token.type) {
1406 #ifdef DEBUG_INCLUDE
1407 debug_pos += sprintf (&debug[debug_pos],
1408 " Token: string (%s)\n",
1411 if (current == (struct parse_node *) NULL) {
1412 root = current = new;
1415 switch (current->token.type) {
1417 current->token.value = apr_pstrcat(r->pool,
1418 current->token.value,
1419 current->token.value[0] ? " " : "",
1434 new->parent = current;
1435 current = current->right = new;
1438 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1439 "Invalid expression \"%s\" in file %s",
1447 #ifdef DEBUG_INCLUDE
1448 debug_pos += sprintf (&debug[debug_pos],
1449 " Token: regex (%s)\n",
1452 if (current == (struct parse_node *) NULL) {
1453 root = current = new;
1456 switch (current->token.type) {
1463 new->parent = current;
1464 current = current->right = new;
1467 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1468 "Invalid expression \"%s\" in file %s",
1477 #ifdef DEBUG_INCLUDE
1478 memcpy (&debug[debug_pos], " Token: and/or\n",
1479 sizeof (" Token: and/or\n"));
1480 debug_pos += sizeof (" Token: and/or\n");
1482 if (current == (struct parse_node *) NULL) {
1483 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1484 "Invalid expression \"%s\" in file %s",
1489 /* Percolate upwards */
1490 while (current != (struct parse_node *) NULL) {
1491 switch (current->token.type) {
1504 current = current->parent;
1509 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1510 "Invalid expression \"%s\" in file %s",
1517 if (current == (struct parse_node *) NULL) {
1519 new->left->parent = new;
1520 new->parent = (struct parse_node *) NULL;
1524 new->left = current->right;
1525 new->left->parent = new;
1526 current->right = new;
1527 new->parent = current;
1533 #ifdef DEBUG_INCLUDE
1534 memcpy(&debug[debug_pos], " Token: not\n",
1535 sizeof(" Token: not\n"));
1536 debug_pos += sizeof(" Token: not\n");
1538 if (current == (struct parse_node *) NULL) {
1539 root = current = new;
1542 /* Percolate upwards */
1543 if (current != (struct parse_node *) NULL) {
1544 switch (current->token.type) {
1557 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1558 "Invalid expression \"%s\" in file %s",
1564 if (current == (struct parse_node *) NULL) {
1566 new->left->parent = new;
1567 new->parent = (struct parse_node *) NULL;
1571 new->left = current->right;
1572 current->right = new;
1573 new->parent = current;
1584 #ifdef DEBUG_INCLUDE
1585 memcpy(&debug[debug_pos], " Token: eq/ne/ge/gt/le/lt\n",
1586 sizeof(" Token: eq/ne/ge/gt/le/lt\n"));
1587 debug_pos += sizeof(" Token: eq/ne/ge/gt/le/lt\n");
1589 if (current == (struct parse_node *) NULL) {
1590 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1591 "Invalid expression \"%s\" in file %s",
1596 /* Percolate upwards */
1597 while (current != (struct parse_node *) NULL) {
1598 switch (current->token.type) {
1602 current = current->parent;
1616 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1617 "Invalid expression \"%s\" in file %s",
1624 if (current == (struct parse_node *) NULL) {
1626 new->left->parent = new;
1627 new->parent = (struct parse_node *) NULL;
1631 new->left = current->right;
1632 new->left->parent = new;
1633 current->right = new;
1634 new->parent = current;
1640 #ifdef DEBUG_INCLUDE
1641 memcpy (&debug[debug_pos], " Token: rbrace\n",
1642 sizeof (" Token: rbrace\n"));
1643 debug_pos += sizeof (" Token: rbrace\n");
1645 while (current != (struct parse_node *) NULL) {
1646 if (current->token.type == token_lbrace) {
1647 current->token.type = token_group;
1650 current = current->parent;
1652 if (current == (struct parse_node *) NULL) {
1653 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1654 "Unmatched ')' in \"%s\" in file %s",
1662 #ifdef DEBUG_INCLUDE
1663 memcpy (&debug[debug_pos], " Token: lbrace\n",
1664 sizeof (" Token: lbrace\n"));
1665 debug_pos += sizeof (" Token: lbrace\n");
1667 if (current == (struct parse_node *) NULL) {
1668 root = current = new;
1671 /* Percolate upwards */
1672 if (current != (struct parse_node *) NULL) {
1673 switch (current->token.type) {
1689 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1690 "Invalid expression \"%s\" in file %s",
1696 if (current == (struct parse_node *) NULL) {
1698 new->left->parent = new;
1699 new->parent = (struct parse_node *) NULL;
1703 new->left = current->right;
1704 current->right = new;
1705 new->parent = current;
1714 /* Evaluate Parse Tree */
1716 while (current != (struct parse_node *) NULL) {
1717 switch (current->token.type) {
1719 #ifdef DEBUG_INCLUDE
1720 memcpy (&debug[debug_pos], " Evaluate string\n",
1721 sizeof (" Evaluate string\n"));
1722 debug_pos += sizeof (" Evaluate string\n");
1724 buffer = ap_ssi_parse_string(r, ctx, current->token.value, NULL,
1726 current->token.value = buffer;
1727 current->value = (current->token.value[0] != '\0');
1729 current = current->parent;
1733 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1734 "No operator before regex of expr \"%s\" in file %s",
1741 #ifdef DEBUG_INCLUDE
1742 memcpy(&debug[debug_pos], " Evaluate and/or\n",
1743 sizeof(" Evaluate and/or\n"));
1744 debug_pos += sizeof(" Evaluate and/or\n");
1746 if (current->left == (struct parse_node *) NULL ||
1747 current->right == (struct parse_node *) NULL) {
1748 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1749 "Invalid expression \"%s\" in file %s",
1754 if (!current->left->done) {
1755 switch (current->left->token.type) {
1757 buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
1758 NULL, MAX_STRING_LEN, 0);
1759 current->left->token.value = buffer;
1760 current->left->value =
1761 (current->left->token.value[0] != '\0');
1762 current->left->done = 1;
1765 current = current->left;
1769 if (!current->right->done) {
1770 switch (current->right->token.type) {
1772 buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
1773 NULL, MAX_STRING_LEN, 0);
1774 current->right->token.value = buffer;
1775 current->right->value =
1776 (current->right->token.value[0] != '\0');
1777 current->right->done = 1;
1780 current = current->right;
1784 #ifdef DEBUG_INCLUDE
1785 debug_pos += sprintf (&debug[debug_pos], " Left: %c\n",
1786 current->left->value ? '1' : '0');
1787 debug_pos += sprintf (&debug[debug_pos], " Right: %c\n",
1788 current->right->value ? '1' : '0');
1790 if (current->token.type == token_and) {
1791 current->value = current->left->value && current->right->value;
1794 current->value = current->left->value || current->right->value;
1796 #ifdef DEBUG_INCLUDE
1797 debug_pos += sprintf (&debug[debug_pos], " Returning %c\n",
1798 current->value ? '1' : '0');
1801 current = current->parent;
1806 #ifdef DEBUG_INCLUDE
1807 memcpy (&debug[debug_pos], " Evaluate eq/ne\n",
1808 sizeof (" Evaluate eq/ne\n"));
1809 debug_pos += sizeof (" Evaluate eq/ne\n");
1811 if ((current->left == (struct parse_node *) NULL) ||
1812 (current->right == (struct parse_node *) NULL) ||
1813 (current->left->token.type != token_string) ||
1814 ((current->right->token.type != token_string) &&
1815 (current->right->token.type != token_re))) {
1816 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1817 "Invalid expression \"%s\" in file %s",
1822 buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
1823 NULL, MAX_STRING_LEN, 0);
1824 current->left->token.value = buffer;
1825 buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
1826 NULL, MAX_STRING_LEN, 0);
1827 current->right->token.value = buffer;
1828 if (current->right->token.type == token_re) {
1829 #ifdef DEBUG_INCLUDE
1830 debug_pos += sprintf (&debug[debug_pos],
1831 " Re Compare (%s) with /%s/\n",
1832 current->left->token.value,
1833 current->right->token.value);
1836 re_check(r, ctx, current->left->token.value,
1837 current->right->token.value);
1840 #ifdef DEBUG_INCLUDE
1841 debug_pos += sprintf (&debug[debug_pos],
1842 " Compare (%s) with (%s)\n",
1843 current->left->token.value,
1844 current->right->token.value);
1847 (strcmp(current->left->token.value,
1848 current->right->token.value) == 0);
1850 if (current->token.type == token_ne) {
1851 current->value = !current->value;
1853 #ifdef DEBUG_INCLUDE
1854 debug_pos += sprintf (&debug[debug_pos], " Returning %c\n",
1855 current->value ? '1' : '0');
1858 current = current->parent;
1864 #ifdef DEBUG_INCLUDE
1865 memcpy (&debug[debug_pos], " Evaluate ge/gt/le/lt\n",
1866 sizeof (" Evaluate ge/gt/le/lt\n"));
1867 debug_pos += sizeof (" Evaluate ge/gt/le/lt\n");
1869 if ((current->left == (struct parse_node *) NULL) ||
1870 (current->right == (struct parse_node *) NULL) ||
1871 (current->left->token.type != token_string) ||
1872 (current->right->token.type != token_string)) {
1873 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1874 "Invalid expression \"%s\" in file %s",
1879 buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
1880 NULL, MAX_STRING_LEN, 0);
1881 current->left->token.value = buffer;
1882 buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
1883 NULL, MAX_STRING_LEN, 0);
1884 current->right->token.value = buffer;
1885 #ifdef DEBUG_INCLUDE
1886 debug_pos += sprintf (&debug[debug_pos],
1887 " Compare (%s) with (%s)\n",
1888 current->left->token.value,
1889 current->right->token.value);
1892 strcmp(current->left->token.value,
1893 current->right->token.value);
1894 if (current->token.type == token_ge) {
1895 current->value = current->value >= 0;
1897 else if (current->token.type == token_gt) {
1898 current->value = current->value > 0;
1900 else if (current->token.type == token_le) {
1901 current->value = current->value <= 0;
1903 else if (current->token.type == token_lt) {
1904 current->value = current->value < 0;
1907 current->value = 0; /* Don't return -1 if unknown token */
1909 #ifdef DEBUG_INCLUDE
1910 debug_pos += sprintf (&debug[debug_pos], " Returning %c\n",
1911 current->value ? '1' : '0');
1914 current = current->parent;
1918 if (current->right != (struct parse_node *) NULL) {
1919 if (!current->right->done) {
1920 current = current->right;
1923 current->value = !current->right->value;
1928 #ifdef DEBUG_INCLUDE
1929 debug_pos += sprintf (&debug[debug_pos], " Evaluate !: %c\n",
1930 current->value ? '1' : '0');
1933 current = current->parent;
1937 if (current->right != (struct parse_node *) NULL) {
1938 if (!current->right->done) {
1939 current = current->right;
1942 current->value = current->right->value;
1947 #ifdef DEBUG_INCLUDE
1948 debug_pos += sprintf (&debug[debug_pos], " Evaluate (): %c\n",
1949 current->value ? '1' : '0');
1952 current = current->parent;
1956 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1957 "Unmatched '(' in \"%s\" in file %s",
1963 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1964 "Unmatched ')' in \"%s\" in file %s",
1970 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1977 retval = (root == (struct parse_node *) NULL) ? 0 : root->value;
1981 /*-------------------------------------------------------------------------*/
1982 #ifdef DEBUG_INCLUDE
1984 #define MAX_DEBUG_SIZE MAX_STRING_LEN
1985 #define LOG_COND_STATUS(cntx, t_buck, h_ptr, ins_head, tag_text) \
1987 char cond_txt[] = "**** X conditional_status=\"0\"\n"; \
1989 if (cntx->flags & FLAG_COND_TRUE) { \
1990 cond_txt[31] = '1'; \
1992 memcpy(&cond_txt[5], tag_text, sizeof(tag_text)-1); \
1993 t_buck = apr_bucket_heap_create(cond_txt, sizeof(cond_txt)-1, \
1994 NULL, h_ptr->list); \
1995 APR_BUCKET_INSERT_BEFORE(h_ptr, t_buck); \
1997 if (ins_head == NULL) { \
1998 ins_head = t_buck; \
2001 #define DUMP_PARSE_EXPR_DEBUG(t_buck, h_ptr, d_buf, ins_head) \
2003 if (d_buf[0] != '\0') { \
2004 t_buck = apr_bucket_heap_create(d_buf, strlen(d_buf), \
2005 NULL, h_ptr->list); \
2006 APR_BUCKET_INSERT_BEFORE(h_ptr, t_buck); \
2008 if (ins_head == NULL) { \
2009 ins_head = t_buck; \
2015 #define MAX_DEBUG_SIZE 10
2016 #define LOG_COND_STATUS(cntx, t_buck, h_ptr, ins_head, tag_text)
2017 #define DUMP_PARSE_EXPR_DEBUG(t_buck, h_ptr, d_buf, ins_head)
2020 /*-------------------------------------------------------------------------*/
2022 /* pjr - These seem to allow expr="fred" expr="joe" where joe overwrites fred. */
2023 static int handle_if(include_ctx_t *ctx, apr_bucket_brigade **bb,
2024 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
2025 apr_bucket **inserted_head)
2028 char *tag_val = NULL;
2030 int expr_ret, was_error, was_unmatched;
2031 apr_bucket *tmp_buck;
2032 char debug_buf[MAX_DEBUG_SIZE];
2034 *inserted_head = NULL;
2035 if (!(ctx->flags & FLAG_PRINTING)) {
2036 ctx->if_nesting_level++;
2040 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
2043 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2044 "missing expr in if statement: %s",
2046 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
2050 expr_ret = parse_expr(r, ctx, expr, &was_error,
2051 &was_unmatched, debug_buf);
2053 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
2057 if (was_unmatched) {
2058 DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr,
2059 "\nUnmatched '\n", *inserted_head);
2061 DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, debug_buf,
2065 ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2068 ctx->flags &= FLAG_CLEAR_PRINT_COND;
2070 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head,
2072 ctx->if_nesting_level = 0;
2075 else if (!strcmp(tag, "expr")) {
2077 #ifdef DEBUG_INCLUDE
2079 apr_size_t d_len = 0;
2080 d_len = sprintf(debug_buf, "**** if expr=\"%s\"\n", expr);
2081 tmp_buck = apr_bucket_heap_create(debug_buf, d_len, NULL,
2082 r->connection->bucket_alloc);
2083 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2085 if (*inserted_head == NULL) {
2086 *inserted_head = tmp_buck;
2092 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2093 "unknown parameter \"%s\" to tag if in %s", tag,
2095 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2104 static int handle_elif(include_ctx_t *ctx, apr_bucket_brigade **bb,
2105 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
2106 apr_bucket **inserted_head)
2109 char *tag_val = NULL;
2111 int expr_ret, was_error, was_unmatched;
2112 apr_bucket *tmp_buck;
2113 char debug_buf[MAX_DEBUG_SIZE];
2115 *inserted_head = NULL;
2116 if (!ctx->if_nesting_level) {
2118 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
2120 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head,
2123 if (ctx->flags & FLAG_COND_TRUE) {
2124 ctx->flags &= FLAG_CLEAR_PRINTING;
2128 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2129 "missing expr in elif statement: %s",
2131 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
2135 expr_ret = parse_expr(r, ctx, expr, &was_error,
2136 &was_unmatched, debug_buf);
2138 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
2142 if (was_unmatched) {
2143 DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr,
2144 "\nUnmatched '\n", *inserted_head);
2146 DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, debug_buf,
2150 ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2153 ctx->flags &= FLAG_CLEAR_PRINT_COND;
2155 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head,
2159 else if (!strcmp(tag, "expr")) {
2161 #ifdef DEBUG_INCLUDE
2163 apr_size_t d_len = 0;
2164 d_len = sprintf(debug_buf, "**** elif expr=\"%s\"\n", expr);
2165 tmp_buck = apr_bucket_heap_create(debug_buf, d_len, NULL,
2166 r->connection->bucket_alloc);
2167 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2169 if (*inserted_head == NULL) {
2170 *inserted_head = tmp_buck;
2176 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2177 "unknown parameter \"%s\" to tag if in %s", tag,
2179 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2187 static int handle_else(include_ctx_t *ctx, apr_bucket_brigade **bb,
2188 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
2189 apr_bucket **inserted_head)
2192 char *tag_val = NULL;
2193 apr_bucket *tmp_buck;
2195 *inserted_head = NULL;
2196 if (!ctx->if_nesting_level) {
2197 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2198 if ((tag != NULL) || (tag_val != NULL)) {
2199 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2200 "else directive does not take tags in %s", r->filename);
2201 if (ctx->flags & FLAG_PRINTING) {
2202 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2207 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, " else");
2209 if (ctx->flags & FLAG_COND_TRUE) {
2210 ctx->flags &= FLAG_CLEAR_PRINTING;
2213 ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2221 static int handle_endif(include_ctx_t *ctx, apr_bucket_brigade **bb,
2222 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
2223 apr_bucket **inserted_head)
2226 char *tag_val = NULL;
2227 apr_bucket *tmp_buck;
2229 *inserted_head = NULL;
2230 if (!ctx->if_nesting_level) {
2231 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2232 if ((tag != NULL) || (tag_val != NULL)) {
2233 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2234 "endif directive does not take tags in %s", r->filename);
2235 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2239 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, "endif");
2240 ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2245 ctx->if_nesting_level--;
2250 static int handle_set(include_ctx_t *ctx, apr_bucket_brigade **bb,
2251 request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
2252 apr_bucket **inserted_head)
2255 char *tag_val = NULL;
2257 apr_bucket *tmp_buck;
2258 char *parsed_string;
2259 request_rec *sub = r->main;
2260 apr_pool_t *p = r->pool;
2262 /* we need to use the 'main' request pool to set notes as that is
2270 *inserted_head = NULL;
2271 if (ctx->flags & FLAG_PRINTING) {
2273 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2274 if ((tag == NULL) && (tag_val == NULL)) {
2277 else if (tag_val == NULL) {
2280 else if (!strcmp(tag, "var")) {
2281 var = ap_ssi_parse_string(r, ctx, tag_val, NULL,
2284 else if (!strcmp(tag, "value")) {
2285 if (var == (char *) NULL) {
2286 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2287 "variable must precede value in set directive in %s",
2289 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
2293 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL,
2295 apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
2296 apr_pstrdup(p, parsed_string));
2299 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2300 "Invalid tag for set directive in %s", r->filename);
2301 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2309 static int handle_printenv(include_ctx_t *ctx, apr_bucket_brigade **bb,
2310 request_rec *r, ap_filter_t *f,
2311 apr_bucket *head_ptr, apr_bucket **inserted_head)
2314 char *tag_val = NULL;
2315 apr_bucket *tmp_buck;
2317 if (ctx->flags & FLAG_PRINTING) {
2318 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2319 if ((tag == NULL) && (tag_val == NULL)) {
2320 const apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
2321 const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
2323 const char *key_text, *val_text;
2324 char *key_val, *next;
2325 apr_size_t k_len, v_len, kv_length;
2327 *inserted_head = NULL;
2328 for (i = 0; i < arr->nelts; ++i) {
2329 key_text = ap_escape_html(r->pool, elts[i].key);
2330 val_text = elts[i].val;
2331 if (val_text == LAZY_VALUE) {
2332 val_text = add_include_vars_lazy(r, elts[i].key);
2334 val_text = ap_escape_html(r->pool, elts[i].val);
2335 k_len = strlen(key_text);
2336 v_len = strlen(val_text);
2337 kv_length = k_len + v_len + sizeof("=\n");
2338 key_val = apr_palloc(r->pool, kv_length);
2340 memcpy(next, key_text, k_len);
2343 memcpy(next, val_text, v_len);
2347 tmp_buck = apr_bucket_pool_create(key_val, kv_length - 1,
2349 r->connection->bucket_alloc);
2350 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2351 if (*inserted_head == NULL) {
2352 *inserted_head = tmp_buck;
2358 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2359 "printenv directive does not take tags in %s",
2361 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2368 /* -------------------------- The main function --------------------------- */
2371 * returns the index position of the first byte of start_seq (or the len of
2372 * the buffer as non-match)
2374 static apr_size_t find_start_sequence(ssi_ctx_t *ctx, const char *data,
2377 apr_size_t slen = ctx->ctx->start_seq_len;
2382 p = data; /* try partial match at the end of the buffer (below) */
2385 /* try fast bndm search over the buffer
2386 * (hopefully the whole start sequence can be found in this buffer)
2388 index = bndm(ctx->ctx->start_seq, ctx->ctx->start_seq_len, data, len,
2389 ctx->ctx->start_seq_pat);
2391 /* wow, found it. ready. */
2393 ctx->state = PARSE_DIRECTIVE;
2397 /* ok, the pattern can't be found as whole in the buffer,
2398 * check the end for a partial match
2400 p = data + len - slen + 1;
2406 while (p < ep && *p != *ctx->ctx->start_seq) {
2412 /* found a possible start_seq start */
2417 while (p < ep && *p == ctx->ctx->start_seq[pos]) {
2422 /* partial match found. Store the info for the next round */
2424 ctx->state = PARSE_HEAD;
2425 ctx->ctx->parse_pos = pos;
2430 /* we must try all combinations; consider (e.g.) SSIStartTag "--->"
2431 * and a string data of "--.-" and the end of the buffer
2433 p = data + index + 1;
2441 * returns the first byte *after* the partial (or final) match.
2443 * If we had to trick with the start_seq start, 'release' returns the
2444 * number of chars of the start_seq which appeared not to be part of a
2445 * full tag and may have to be passed down the filter chain.
2447 static apr_size_t find_partial_start_sequence(ssi_ctx_t *ctx,
2450 apr_size_t *release)
2452 apr_size_t pos, spos = 0;
2453 apr_size_t slen = ctx->ctx->start_seq_len;
2456 pos = ctx->ctx->parse_pos;
2463 while (p < ep && pos < slen && *p == ctx->ctx->start_seq[pos]) {
2470 ctx->state = PARSE_DIRECTIVE;
2474 /* the whole buffer is a partial match */
2476 ctx->ctx->parse_pos = pos;
2480 /* No match so far, but again:
2481 * We must try all combinations, since the start_seq is a random
2482 * user supplied string
2484 * So: look if the first char of start_seq appears somewhere within
2485 * the current partial match. If it does, try to start a match that
2486 * begins with this offset. (This can happen, if a strange
2487 * start_seq like "---->" spans buffers)
2489 if (spos < ctx->ctx->parse_pos) {
2493 p = ctx->ctx->start_seq + spos;
2494 pos = ctx->ctx->parse_pos - spos;
2496 while (pos && *p != *ctx->ctx->start_seq) {
2503 /* if a matching beginning char was found, try to match the
2504 * remainder of the old buffer.
2510 while (t < pos && *p == ctx->ctx->start_seq[t]) {
2516 /* yeah, another partial match found in the *old*
2517 * buffer, now test the *current* buffer for
2531 } while (1); /* work hard to find a match ;-) */
2533 /* no match at all, release all (wrongly) matched chars so far */
2534 *release = ctx->ctx->parse_pos;
2535 ctx->state = PARSE_PRE_HEAD;
2540 * returns the position after the directive
2542 static apr_size_t find_directive(ssi_ctx_t *ctx, const char *data,
2543 apr_size_t len, char ***store,
2544 apr_size_t **store_len)
2546 const char *p = data;
2547 const char *ep = data + len;
2550 switch (ctx->state) {
2551 case PARSE_DIRECTIVE:
2552 while (p < ep && !apr_isspace(*p)) {
2553 /* we have to consider the case of missing space between directive
2554 * and end_seq (be somewhat lenient), e.g. <!--#printenv-->
2556 if (*p == *ctx->ctx->end_seq) {
2557 ctx->state = PARSE_DIRECTIVE_TAIL;
2558 ctx->ctx->parse_pos = 1;
2565 if (p < ep) { /* found delimiter whitespace */
2566 ctx->state = PARSE_DIRECTIVE_POSTNAME;
2567 *store = &ctx->directive;
2568 *store_len = &ctx->ctx->directive_length;
2573 case PARSE_DIRECTIVE_TAIL:
2574 pos = ctx->ctx->parse_pos;
2576 while (p < ep && pos < ctx->end_seq_len &&
2577 *p == ctx->ctx->end_seq[pos]) {
2582 /* full match, we're done */
2583 if (pos == ctx->end_seq_len) {
2584 ctx->state = PARSE_DIRECTIVE_POSTTAIL;
2585 *store = &ctx->directive;
2586 *store_len = &ctx->ctx->directive_length;
2590 /* partial match, the buffer is too small to match fully */
2592 ctx->ctx->parse_pos = pos;
2596 /* no match. continue normal parsing */
2597 ctx->state = PARSE_DIRECTIVE;
2600 case PARSE_DIRECTIVE_POSTTAIL:
2601 ctx->state = PARSE_EXECUTE;
2602 ctx->ctx->directive_length -= ctx->end_seq_len;
2603 /* continue immediately with the next state */
2605 case PARSE_DIRECTIVE_POSTNAME:
2606 if (PARSE_DIRECTIVE_POSTNAME == ctx->state) {
2607 ctx->state = PARSE_PRE_ARG;
2612 if (!ctx->ctx->directive_length) {
2614 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "missing directive "
2615 "name in parsed document %s", ctx->r->filename);
2618 char *sp = ctx->directive;
2619 char *sep = ctx->directive + ctx->ctx->directive_length;
2621 /* normalize directive name */
2622 for (; sp < sep; ++sp) {
2623 *sp = apr_tolower(*sp);
2630 /* get a rid of a gcc warning about unhandled enumerations */
2638 * find out whether the next token is (a possible) end_seq or an argument
2640 static apr_size_t find_arg_or_tail(ssi_ctx_t *ctx, const char *data,
2643 const char *p = data;
2644 const char *ep = data + len;
2646 /* skip leading WS */
2647 while (p < ep && apr_isspace(*p)) {
2651 /* buffer doesn't consist of whitespaces only */
2653 ctx->state = (*p == *ctx->ctx->end_seq) ? PARSE_TAIL : PARSE_ARG;
2660 * test the stream for end_seq. If it doesn't match at all, it must be an
2663 static apr_size_t find_tail(ssi_ctx_t *ctx, const char *data,
2666 const char *p = data;
2667 const char *ep = data + len;
2668 apr_size_t pos = ctx->ctx->parse_pos;
2670 if (PARSE_TAIL == ctx->state) {
2671 ctx->state = PARSE_TAIL_SEQ;
2672 pos = ctx->ctx->parse_pos = 0;
2675 while (p < ep && pos < ctx->end_seq_len && *p == ctx->ctx->end_seq[pos]) {
2680 /* bingo, full match */
2681 if (pos == ctx->end_seq_len) {
2682 ctx->state = PARSE_EXECUTE;
2686 /* partial match, the buffer is too small to match fully */
2688 ctx->ctx->parse_pos = pos;
2692 /* no match. It must be an argument string then */
2693 ctx->state = PARSE_ARG;
2698 * extract name=value from the buffer
2699 * A pcre-pattern could look (similar to):
2700 * name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
2702 static apr_size_t find_argument(ssi_ctx_t *ctx, const char *data,
2703 apr_size_t len, char ***store,
2704 apr_size_t **store_len)
2706 const char *p = data;
2707 const char *ep = data + len;
2709 switch (ctx->state) {
2712 * create argument structure and append it to the current list
2714 ctx->current_arg = apr_palloc(ctx->dpool,
2715 sizeof(*ctx->current_arg));
2716 ctx->current_arg->next = NULL;
2720 ctx->argv = ctx->current_arg;
2723 ssi_arg_item_t *newarg = ctx->argv;
2725 while (newarg->next) {
2726 newarg = newarg->next;
2728 newarg->next = ctx->current_arg;
2731 /* check whether it's a valid one. If it begins with a quote, we
2732 * can safely assume, someone forgot the name of the argument
2735 case '"': case '\'': case '`':
2738 ctx->state = PARSE_ARG_VAL;
2740 ctx->current_arg->name = NULL;
2741 ctx->current_arg->name_len = 0;
2744 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "missing argument "
2745 "name for value to tag %s in %s",
2746 apr_pstrmemdup(ctx->r->pool, ctx->directive,
2747 ctx->ctx->directive_length),
2753 ctx->state = PARSE_ARG_NAME;
2755 /* continue immediately with next state */
2757 case PARSE_ARG_NAME:
2758 while (p < ep && !apr_isspace(*p) && *p != '=') {
2763 ctx->state = PARSE_ARG_POSTNAME;
2764 *store = &ctx->current_arg->name;
2765 *store_len = &ctx->current_arg->name_len;
2770 case PARSE_ARG_POSTNAME:
2771 ctx->current_arg->name = apr_pstrmemdup(ctx->dpool,
2772 ctx->current_arg->name,
2773 ctx->current_arg->name_len);
2774 if (!ctx->current_arg->name_len) {
2776 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "missing argument "
2777 "name for value to tag %s in %s",
2778 apr_pstrmemdup(ctx->r->pool, ctx->directive,
2779 ctx->ctx->directive_length),
2783 char *sp = ctx->current_arg->name;
2785 /* normalize the name */
2787 *sp = apr_tolower(*sp);
2792 ctx->state = PARSE_ARG_EQ;
2793 /* continue with next state immediately */
2798 while (p < ep && apr_isspace(*p)) {
2804 ctx->state = PARSE_ARG_PREVAL;
2807 else { /* no value */
2808 ctx->current_arg->value = NULL;
2809 ctx->state = PARSE_PRE_ARG;
2816 case PARSE_ARG_PREVAL:
2819 while (p < ep && apr_isspace(*p)) {
2823 /* buffer doesn't consist of whitespaces only */
2825 ctx->state = PARSE_ARG_VAL;
2827 case '"': case '\'': case '`':
2839 case PARSE_ARG_VAL_ESC:
2840 if (*p == ctx->quote) {
2843 ctx->state = PARSE_ARG_VAL;
2844 /* continue with next state immediately */
2847 for (; p < ep; ++p) {
2848 if (ctx->quote && *p == '\\') {
2851 ctx->state = PARSE_ARG_VAL_ESC;
2855 if (*p != ctx->quote) {
2859 else if (ctx->quote && *p == ctx->quote) {
2861 *store = &ctx->current_arg->value;
2862 *store_len = &ctx->current_arg->value_len;
2863 ctx->state = PARSE_ARG_POSTVAL;
2866 else if (!ctx->quote && apr_isspace(*p)) {
2868 *store = &ctx->current_arg->value;
2869 *store_len = &ctx->current_arg->value_len;
2870 ctx->state = PARSE_ARG_POSTVAL;
2877 case PARSE_ARG_POSTVAL:
2879 * The value is still the raw input string. Finally clean it up.
2881 --(ctx->current_arg->value_len);
2883 /* strip quote escaping \ from the string */
2885 apr_size_t shift = 0;
2888 sp = ctx->current_arg->value;
2889 ep = ctx->current_arg->value + ctx->current_arg->value_len;
2890 while (sp < ep && *sp != '\\') {
2893 for (; sp < ep; ++sp) {
2894 if (*sp == '\\' && sp[1] == ctx->quote) {
2903 ctx->current_arg->value_len -= shift;
2906 ctx->current_arg->value[ctx->current_arg->value_len] = '\0';
2907 ctx->state = PARSE_PRE_ARG;
2912 /* get a rid of a gcc warning about unhandled enumerations */
2916 return len; /* partial match of something */
2920 * This is the main loop over the current bucket brigade.
2922 static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
2924 ssi_ctx_t *ctx = f->ctx;
2925 request_rec *r = f->r;
2926 apr_bucket *b = APR_BRIGADE_FIRST(bb);
2927 apr_bucket_brigade *pass_bb;
2928 apr_status_t rv = APR_SUCCESS;
2929 char *magic; /* magic pointer for sentinel use */
2932 if (APR_BRIGADE_EMPTY(bb)) {
2936 /* we may crash, since already cleaned up; hand over the responsibility
2937 * to the next filter;-)
2939 if (ctx->seen_eos) {
2940 return ap_pass_brigade(f->next, bb);
2943 /* All stuff passed along has to be put into that brigade */
2944 pass_bb = apr_brigade_create(ctx->ctx->pool, f->c->bucket_alloc);
2945 ctx->ctx->bytes_parsed = 0;
2946 ctx->ctx->output_now = 0;
2949 /* loop over the current bucket brigade */
2950 while (b != APR_BRIGADE_SENTINEL(bb)) {
2951 const char *data = NULL;
2952 apr_size_t len, index, release;
2953 apr_bucket *newb = NULL;
2954 char **store = &magic;
2955 apr_size_t *store_len;
2957 /* handle meta buckets before reading any data */
2958 if (APR_BUCKET_IS_METADATA(b)) {
2959 newb = APR_BUCKET_NEXT(b);
2961 APR_BUCKET_REMOVE(b);
2963 if (APR_BUCKET_IS_EOS(b)) {
2966 /* Hit end of stream, time for cleanup ... But wait!
2967 * Perhaps we're not ready yet. We may have to loop one or
2968 * two times again to finish our work. In that case, we
2969 * just re-insert the EOS bucket to allow for an extra loop.
2971 * PARSE_EXECUTE means, we've hit a directive just before the
2972 * EOS, which is now waiting for execution.
2974 * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
2975 * no argument and no space between directive and end_seq
2976 * just before the EOS. (consider <!--#printenv--> as last
2977 * or only string within the stream). This state, however,
2978 * just cleans up and turns itself to PARSE_EXECUTE, which
2979 * will be passed through within the next (and actually
2982 if (PARSE_EXECUTE == ctx->state ||
2983 PARSE_DIRECTIVE_POSTTAIL == ctx->state) {
2984 APR_BUCKET_INSERT_BEFORE(newb, b);
2987 break; /* END OF STREAM */
2991 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
2993 if (APR_BUCKET_IS_FLUSH(b)) {
2994 ctx->ctx->output_now = 1;
3002 /* enough is enough ... */
3003 if (ctx->ctx->output_now ||
3004 ctx->ctx->bytes_parsed > AP_MIN_BYTES_TO_WRITE) {
3006 if (!APR_BRIGADE_EMPTY(pass_bb)) {
3007 rv = ap_pass_brigade(f->next, pass_bb);
3008 if (!APR_STATUS_IS_SUCCESS(rv)) {
3009 apr_brigade_destroy(pass_bb);
3014 ctx->ctx->output_now = 0;
3015 ctx->ctx->bytes_parsed = 0;
3018 /* read the current bucket data */
3020 if (!ctx->seen_eos) {
3021 if (ctx->ctx->bytes_parsed > 0) {
3022 rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
3023 if (APR_STATUS_IS_EAGAIN(rv)) {
3024 ctx->ctx->output_now = 1;
3029 if (!len || !APR_STATUS_IS_SUCCESS(rv)) {
3030 rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
3033 if (!APR_STATUS_IS_SUCCESS(rv)) {
3034 apr_brigade_destroy(pass_bb);
3038 ctx->ctx->bytes_parsed += len;
3041 /* zero length bucket, fetch next one */
3042 if (!len && !ctx->seen_eos) {
3043 b = APR_BUCKET_NEXT(b);
3048 * it's actually a data containing bucket, start/continue parsing
3051 switch (ctx->state) {
3052 /* no current tag; search for start sequence */
3053 case PARSE_PRE_HEAD:
3054 index = find_start_sequence(ctx, data, len);
3057 apr_bucket_split(b, index);
3060 newb = APR_BUCKET_NEXT(b);
3061 if (ctx->ctx->flags & FLAG_PRINTING) {
3062 APR_BUCKET_REMOVE(b);
3063 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3066 apr_bucket_delete(b);
3070 /* now delete the start_seq stuff from the remaining bucket */
3071 if (PARSE_DIRECTIVE == ctx->state) { /* full match */
3072 apr_bucket_split(newb, ctx->ctx->start_seq_len);
3073 ctx->ctx->output_now = 1; /* pass pre-tag stuff */
3076 b = APR_BUCKET_NEXT(newb);
3077 apr_bucket_delete(newb);
3085 /* we're currently looking for the end of the start sequence */
3087 index = find_partial_start_sequence(ctx, data, len, &release);
3089 /* check if we mismatched earlier and have to release some chars */
3090 if (release && (ctx->ctx->flags & FLAG_PRINTING)) {
3091 char *to_release = apr_palloc(ctx->ctx->pool, release);
3093 memcpy(to_release, ctx->ctx->start_seq, release);
3094 newb = apr_bucket_pool_create(to_release, release,
3096 f->c->bucket_alloc);
3097 APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
3100 if (index) { /* any match */
3101 /* now delete the start_seq stuff from the remaining bucket */
3102 if (PARSE_DIRECTIVE == ctx->state) { /* final match */
3103 apr_bucket_split(b, index);
3104 ctx->ctx->output_now = 1; /* pass pre-tag stuff */
3106 newb = APR_BUCKET_NEXT(b);
3107 apr_bucket_delete(b);
3113 /* we're currently grabbing the directive name */
3114 case PARSE_DIRECTIVE:
3115 case PARSE_DIRECTIVE_POSTNAME:
3116 case PARSE_DIRECTIVE_TAIL:
3117 case PARSE_DIRECTIVE_POSTTAIL:
3118 index = find_directive(ctx, data, len, &store, &store_len);
3121 apr_bucket_split(b, index);
3122 newb = APR_BUCKET_NEXT(b);
3127 APR_BUCKET_REMOVE(b);
3128 APR_BRIGADE_INSERT_TAIL(ctx->tmp_bb, b);
3132 /* time for cleanup? */
3133 if (store != &magic) {
3134 apr_brigade_pflatten(ctx->tmp_bb, store, store_len,
3136 apr_brigade_cleanup(ctx->tmp_bb);
3140 apr_bucket_delete(b);
3146 /* skip WS and find out what comes next (arg or end_seq) */
3148 index = find_arg_or_tail(ctx, data, len);
3150 if (index) { /* skipped whitespaces */
3152 apr_bucket_split(b, index);
3154 newb = APR_BUCKET_NEXT(b);
3155 apr_bucket_delete(b);
3161 /* currently parsing name[=val] */
3163 case PARSE_ARG_NAME:
3164 case PARSE_ARG_POSTNAME:
3166 case PARSE_ARG_PREVAL:
3168 case PARSE_ARG_VAL_ESC:
3169 case PARSE_ARG_POSTVAL:
3170 index = find_argument(ctx, data, len, &store, &store_len);
3173 apr_bucket_split(b, index);
3174 newb = APR_BUCKET_NEXT(b);
3179 APR_BUCKET_REMOVE(b);
3180 APR_BRIGADE_INSERT_TAIL(ctx->tmp_bb, b);
3184 /* time for cleanup? */
3185 if (store != &magic) {
3186 apr_brigade_pflatten(ctx->tmp_bb, store, store_len,
3188 apr_brigade_cleanup(ctx->tmp_bb);
3192 apr_bucket_delete(b);
3198 /* try to match end_seq at current pos. */
3200 case PARSE_TAIL_SEQ:
3201 index = find_tail(ctx, data, len);
3203 switch (ctx->state) {
3204 case PARSE_EXECUTE: /* full match */
3205 apr_bucket_split(b, index);
3206 newb = APR_BUCKET_NEXT(b);
3207 apr_bucket_delete(b);
3211 case PARSE_ARG: /* no match */
3212 /* PARSE_ARG must reparse at the beginning */
3213 APR_BRIGADE_PREPEND(bb, ctx->tmp_bb);
3214 b = APR_BRIGADE_FIRST(bb);
3217 default: /* partial match */
3218 newb = APR_BUCKET_NEXT(b);
3219 APR_BUCKET_REMOVE(b);
3220 APR_BRIGADE_INSERT_TAIL(ctx->tmp_bb, b);
3227 /* now execute the parsed directive, cleanup the space and
3228 * start again with PARSE_PRE_HEAD
3231 /* if there was an error, it was already logged; just stop here */
3233 if (ctx->ctx->flags & FLAG_PRINTING) {
3234 SSI_CREATE_ERROR_BUCKET(ctx->ctx, f, pass_bb);
3239 include_handler_fn_t *handle_func;
3242 (include_handler_fn_t *) apr_hash_get(include_hash,
3244 ctx->ctx->directive_length);
3248 apr_size_t tag_len = 0;
3249 ssi_arg_item_t *carg = ctx->argv;
3251 /* legacy wrapper code */
3253 /* +1 \0 byte (either after tag or value)
3254 * +1 = byte (before value)
3256 tag_len += (carg->name ? carg->name_len : 0) +
3257 (carg->value ? carg->value_len + 1 : 0) + 1;
3261 tag = ctx->ctx->combined_tag = ctx->ctx->curr_tag_pos =
3262 apr_palloc(ctx->dpool, tag_len);
3267 memcpy(tag, carg->name, carg->name_len);
3268 tag += carg->name_len;
3272 memcpy(tag, carg->value, carg->value_len);
3273 tag += carg->value_len;
3278 ctx->ctx->tag_length = tag_len;
3280 /* create dummy buckets for backards compat */
3281 ctx->ctx->head_start_bucket =
3282 apr_bucket_pool_create(apr_pmemdup(ctx->ctx->pool,
3283 ctx->ctx->start_seq,
3284 ctx->ctx->start_seq_len),
3285 ctx->ctx->start_seq_len,
3287 f->c->bucket_alloc);
3288 APR_BRIGADE_INSERT_TAIL(ctx->ctx->ssi_tag_brigade,
3289 ctx->ctx->head_start_bucket);
3290 ctx->ctx->tag_start_bucket =
3291 apr_bucket_pool_create(apr_pmemdup(ctx->ctx->pool,
3292 ctx->ctx->combined_tag,
3293 ctx->ctx->tag_length),
3294 ctx->ctx->tag_length,
3296 f->c->bucket_alloc);
3297 APR_BRIGADE_INSERT_TAIL(ctx->ctx->ssi_tag_brigade,
3298 ctx->ctx->tag_start_bucket);
3299 ctx->ctx->tail_start_bucket =
3300 apr_bucket_pool_create(apr_pmemdup(ctx->ctx->pool,
3305 f->c->bucket_alloc);
3306 APR_BRIGADE_INSERT_TAIL(ctx->ctx->ssi_tag_brigade,
3307 ctx->ctx->tail_start_bucket);
3309 rv = handle_func(ctx->ctx, &bb, r, f, b, &dummy);
3311 apr_brigade_cleanup(ctx->ctx->ssi_tag_brigade);
3313 if (rv != 0 && rv != 1 && rv != -1) {
3314 apr_brigade_destroy(pass_bb);
3319 apr_bucket_brigade *remain;
3321 remain = apr_brigade_split(bb, b);
3322 APR_BRIGADE_CONCAT(pass_bb, bb);
3327 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3328 "unknown directive \"%s\" in parsed doc %s",
3329 apr_pstrmemdup(r->pool, ctx->directive,
3330 ctx->ctx->directive_length),
3332 if (ctx->ctx->flags & FLAG_PRINTING) {
3333 SSI_CREATE_ERROR_BUCKET(ctx->ctx, f, pass_bb);
3339 apr_pool_clear(ctx->dpool);
3340 apr_brigade_cleanup(ctx->tmp_bb);
3342 /* Oooof. Done here, start next round */
3343 ctx->state = PARSE_PRE_HEAD;
3347 } /* while (brigade) */
3349 /* End of stream. Final cleanup */
3350 if (ctx->seen_eos) {
3351 if (PARSE_HEAD == ctx->state) {
3352 if (ctx->ctx->flags & FLAG_PRINTING) {
3353 char *to_release = apr_palloc(ctx->ctx->pool,
3354 ctx->ctx->parse_pos);
3356 memcpy(to_release, ctx->ctx->start_seq, ctx->ctx->parse_pos);
3357 APR_BRIGADE_INSERT_TAIL(pass_bb,
3358 apr_bucket_pool_create(to_release,
3359 ctx->ctx->parse_pos, ctx->ctx->pool,
3360 f->c->bucket_alloc));
3363 else if (PARSE_PRE_HEAD != ctx->state) {
3364 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3365 "SSI directive was not properly finished at the end "
3366 "of parsed document %s", r->filename);
3367 if (ctx->ctx->flags & FLAG_PRINTING) {
3368 SSI_CREATE_ERROR_BUCKET(ctx->ctx, f, pass_bb);
3372 if (!(ctx->ctx->flags & FLAG_PRINTING)) {
3373 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
3374 "missing closing endif directive in parsed document"
3375 " %s", r->filename);
3378 /* cleanup our temporary memory */
3379 apr_brigade_destroy(ctx->tmp_bb);
3380 apr_pool_destroy(ctx->dpool);
3382 /* don't forget to finally insert the EOS bucket */
3383 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3386 /* if something's left over, pass it along */
3387 if (!APR_BRIGADE_EMPTY(pass_bb)) {
3388 rv = ap_pass_brigade(f->next, pass_bb);
3394 apr_brigade_destroy(pass_bb);
3398 static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
3400 include_dir_config *result =
3401 (include_dir_config *)apr_palloc(p, sizeof(include_dir_config));
3402 enum xbithack *xbh = (enum xbithack *) apr_palloc(p, sizeof(enum xbithack));
3403 *xbh = DEFAULT_XBITHACK;
3404 result->default_error_msg = DEFAULT_ERROR_MSG;
3405 result->default_time_fmt = DEFAULT_TIME_FORMAT;
3406 result->xbithack = xbh;
3410 static void *create_includes_server_config(apr_pool_t*p, server_rec *server)
3412 include_server_config *result =
3413 (include_server_config *)apr_palloc(p, sizeof(include_server_config));
3414 result->default_end_tag = ENDING_SEQUENCE;
3415 result->default_start_tag =STARTING_SEQUENCE;
3416 result->start_tag_len = sizeof(STARTING_SEQUENCE)-1;
3417 /* compile the pattern used by find_start_sequence */
3418 bndm_compile(&result->start_seq_pat, result->default_start_tag,
3419 result->start_tag_len);
3421 result->undefinedEcho = apr_pstrdup(p,"(none)");
3422 result->undefinedEchoLen = strlen( result->undefinedEcho);
3425 static const char *set_xbithack(cmd_parms *cmd, void *xbp, const char *arg)
3427 include_dir_config *conf = (include_dir_config *)xbp;
3429 if (!strcasecmp(arg, "off")) {
3430 *conf->xbithack = xbithack_off;
3432 else if (!strcasecmp(arg, "on")) {
3433 *conf->xbithack = xbithack_on;
3435 else if (!strcasecmp(arg, "full")) {
3436 *conf->xbithack = xbithack_full;
3439 return "XBitHack must be set to Off, On, or Full";
3445 static int includes_setup(ap_filter_t *f)
3447 include_dir_config *conf =
3448 (include_dir_config *)ap_get_module_config(f->r->per_dir_config,
3451 /* When our xbithack value isn't set to full or our platform isn't
3452 * providing group-level protection bits or our group-level bits do not
3453 * have group-execite on, we will set the no_local_copy value to 1 so
3454 * that we will not send 304s.
3456 if ((*conf->xbithack != xbithack_full)
3457 || !(f->r->finfo.valid & APR_FINFO_GPROT)
3458 || !(f->r->finfo.protection & APR_GEXECUTE)) {
3459 f->r->no_local_copy = 1;
3462 /* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4.
3463 * We don't know if we are going to be including a file or executing
3464 * a program - in either case a strong ETag header will likely be invalid.
3466 apr_table_setn(f->r->notes, "no-etag", "");
3471 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
3473 request_rec *r = f->r;
3474 ssi_ctx_t *ctx = f->ctx;
3475 request_rec *parent;
3476 include_dir_config *conf =
3477 (include_dir_config *)ap_get_module_config(r->per_dir_config,
3480 include_server_config *sconf= ap_get_module_config(r->server->module_config,
3483 if (!(ap_allow_options(r) & OPT_INCLUDES)) {
3484 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
3485 "mod_include: Options +Includes (or IncludesNoExec) "
3486 "wasn't set, INCLUDES filter removed");
3487 ap_remove_output_filter(f);
3488 return ap_pass_brigade(f->next, b);
3492 /* create context for this filter */
3493 f->ctx = ctx = apr_palloc(f->c->pool, sizeof(*ctx));
3494 ctx->ctx = apr_pcalloc(f->c->pool, sizeof(*ctx->ctx));
3495 ctx->ctx->pool = f->r->pool;
3496 apr_pool_create(&ctx->dpool, ctx->ctx->pool);
3498 /* configuration data */
3499 ctx->end_seq_len = strlen(sconf->default_end_tag);
3503 ctx->tmp_bb = apr_brigade_create(ctx->ctx->pool, f->c->bucket_alloc);
3505 ctx->state = PARSE_PRE_HEAD;
3506 ctx->ctx->flags = (FLAG_PRINTING | FLAG_COND_TRUE);
3507 if (ap_allow_options(f->r) & OPT_INCNOEXEC) {
3508 ctx->ctx->flags |= FLAG_NO_EXEC;
3510 ctx->ctx->if_nesting_level = 0;
3511 ctx->ctx->re_string = NULL;
3512 ctx->ctx->error_str_override = NULL;
3513 ctx->ctx->time_str_override = NULL;
3515 ctx->ctx->error_str = conf->default_error_msg;
3516 ctx->ctx->time_str = conf->default_time_fmt;
3517 ctx->ctx->start_seq_pat = &sconf->start_seq_pat;
3518 ctx->ctx->start_seq = sconf->default_start_tag;
3519 ctx->ctx->start_seq_len = sconf->start_tag_len;
3520 ctx->ctx->end_seq = sconf->default_end_tag;
3522 /* legacy compat stuff */
3523 ctx->ctx->state = PARSED; /* dummy */
3524 ctx->ctx->ssi_tag_brigade = apr_brigade_create(f->c->pool,
3525 f->c->bucket_alloc);
3526 ctx->ctx->status = APR_SUCCESS;
3527 ctx->ctx->head_start_index = 0;
3528 ctx->ctx->tag_start_index = 0;
3529 ctx->ctx->tail_start_index = 0;
3532 ctx->ctx->bytes_parsed = 0;
3535 if ((parent = ap_get_module_config(r->request_config, &include_module))) {
3536 /* Kludge --- for nested includes, we want to keep the subprocess
3537 * environment of the base document (for compatibility); that means
3538 * torquing our own last_modified date as well so that the
3539 * LAST_MODIFIED variable gets reset to the proper value if the
3540 * nested document resets <!--#config timefmt -->.
3542 r->subprocess_env = r->main->subprocess_env;
3543 apr_pool_join(r->main->pool, r->pool);
3544 r->finfo.mtime = r->main->finfo.mtime;
3547 /* we're not a nested include, so we create an initial
3549 ap_add_common_vars(r);
3551 add_include_vars(r, conf->default_time_fmt);
3553 /* Always unset the content-length. There is no way to know if
3554 * the content will be modified at some point by send_parsed_content.
3555 * It is very possible for us to not find any content in the first
3556 * 9k of the file, but still have to modify the content of the file.
3557 * If we are going to pass the file through send_parsed_content, then
3558 * the content-length should just be unset.
3560 apr_table_unset(f->r->headers_out, "Content-Length");
3562 /* Always unset the Last-Modified field - see RFC2616 - 13.3.4.
3563 * We don't know if we are going to be including a file or executing
3564 * a program which may change the Last-Modified header or make the
3565 * content completely dynamic. Therefore, we can't support these
3567 * Exception: XBitHack full means we *should* set the Last-Modified field.
3570 /* Assure the platform supports Group protections */
3571 if ((*conf->xbithack == xbithack_full)
3572 && (r->finfo.valid & APR_FINFO_GPROT)
3573 && (r->finfo.protection & APR_GEXECUTE)) {
3574 ap_update_mtime(r, r->finfo.mtime);
3575 ap_set_last_modified(r);
3578 apr_table_unset(f->r->headers_out, "Last-Modified");
3581 /* add QUERY stuff to env cause it ain't yet */
3583 char *arg_copy = apr_pstrdup(r->pool, r->args);
3585 apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
3586 ap_unescape_url(arg_copy);
3587 apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
3588 ap_escape_shell_cmd(r->pool, arg_copy));
3591 return send_parsed_content(f, b);
3594 static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
3596 apr_hash_set(include_hash, tag, strlen(tag), (const void *)func);
3599 static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
3600 apr_pool_t *ptemp, server_rec *s)
3602 include_hash = apr_hash_make(p);
3604 ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
3606 if(ssi_pfn_register) {
3607 ssi_pfn_register("if", handle_if);
3608 ssi_pfn_register("set", handle_set);
3609 ssi_pfn_register("else", handle_else);
3610 ssi_pfn_register("elif", handle_elif);
3611 ssi_pfn_register("echo", handle_echo);
3612 ssi_pfn_register("endif", handle_endif);
3613 ssi_pfn_register("fsize", handle_fsize);
3614 ssi_pfn_register("config", handle_config);
3615 ssi_pfn_register("include", handle_include);
3616 ssi_pfn_register("flastmod", handle_flastmod);
3617 ssi_pfn_register("printenv", handle_printenv);
3622 static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig, const char *msg)
3624 include_dir_config *conf = (include_dir_config *)mconfig;
3625 conf->default_error_msg = apr_pstrdup(cmd->pool, msg);
3629 static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig, const char *msg)
3631 include_server_config *conf;
3632 conf= ap_get_module_config(cmd->server->module_config , &include_module);
3633 conf->default_start_tag = apr_pstrdup(cmd->pool, msg);
3634 conf->start_tag_len = strlen(conf->default_start_tag );
3635 bndm_compile(&conf->start_seq_pat, conf->default_start_tag,
3636 conf->start_tag_len);
3640 static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig, const char *msg)
3642 include_server_config *conf;
3643 conf = ap_get_module_config(cmd->server->module_config, &include_module);
3644 conf->undefinedEcho = apr_pstrdup(cmd->pool, msg);
3645 conf->undefinedEchoLen = strlen(msg);
3651 static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig, const char *msg)
3653 include_server_config *conf;
3654 conf= ap_get_module_config(cmd->server->module_config , &include_module);
3655 conf->default_end_tag = apr_pstrdup(cmd->pool, msg);
3660 static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig, const char *fmt)
3662 include_dir_config *conf = (include_dir_config *)mconfig;
3663 conf->default_time_fmt = apr_pstrdup(cmd->pool, fmt);
3668 * Module definition and configuration data structs...
3670 static const command_rec includes_cmds[] =
3672 AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS,
3673 "Off, On, or Full"),
3674 AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL,
3676 AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
3677 "a strftime(3) formatted string"),
3678 AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF,
3679 "SSI Start String Tag"),
3680 AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF,
3681 "SSI End String Tag"),
3682 AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, RSRC_CONF,
3683 "SSI Start String Tag"),
3688 static int include_fixup(request_rec *r)
3690 include_dir_config *conf;
3692 conf = (include_dir_config *) ap_get_module_config(r->per_dir_config,
3695 if (r->handler && (strcmp(r->handler, "server-parsed") == 0))
3697 if (!r->content_type || !*r->content_type) {
3698 ap_set_content_type(r, "text/html");
3700 r->handler = "default-handler";
3703 #if defined(OS2) || defined(WIN32) || defined(NETWARE)
3704 /* These OS's don't support xbithack. This is being worked on. */
3710 if (*conf->xbithack == xbithack_off) {
3714 if (!(r->finfo.protection & APR_UEXECUTE)) {
3718 if (!r->content_type || strcmp(r->content_type, "text/html")) {
3724 /* We always return declined, because the default handler actually
3725 * serves the file. All we have to do is add the filter.
3727 ap_add_output_filter("INCLUDES", NULL, r, r->connection);
3731 static void register_hooks(apr_pool_t *p)
3733 APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
3734 APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
3735 APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
3736 ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
3737 ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST);
3738 ap_register_output_filter("INCLUDES", includes_filter, includes_setup,
3742 module AP_MODULE_DECLARE_DATA include_module =
3744 STANDARD20_MODULE_STUFF,
3745 create_includes_dir_config, /* dir config creater */
3746 NULL, /* dir merger --- default is to override */
3747 create_includes_server_config,/* server config */
3748 NULL, /* merge server config */
3749 includes_cmds, /* command apr_table_t */
3750 register_hooks /* register hooks */