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
#include "apr_strings.h"
19
#include "apr_thread_proc.h"
23
#include "apr_optional.h"
25
#define APR_WANT_STRFUNC
26
#define APR_WANT_MEMFUNC
29
#include "ap_config.h"
30
#include "util_filter.h"
32
#include "http_config.h"
33
#include "http_core.h"
34
#include "http_request.h"
35
#include "http_core.h"
36
#include "http_protocol.h"
38
#include "http_main.h"
39
#include "util_script.h"
40
#include "http_core.h"
41
#include "mod_include.h"
43
/* helper for Latin1 <-> entity encoding */
44
#if APR_CHARSET_EBCDIC
45
#include "util_ebcdic.h"
46
#define RAW_ASCII_CHAR(ch) apr_xlate_conv_byte(ap_hdrs_from_ascii, \
48
#else /* APR_CHARSET_EBCDIC */
49
#define RAW_ASCII_CHAR(ch) (ch)
50
#endif /* !APR_CHARSET_EBCDIC */
54
* +-------------------------------------------------------+
56
* | Types and Structures
58
* +-------------------------------------------------------+
61
/* sll used for string expansion */
62
typedef struct result_item {
63
struct result_item *next;
68
/* conditional expression parser stuff */
94
typedef struct parse_node {
95
struct parse_node *parent;
96
struct parse_node *left;
97
struct parse_node *right;
113
const char *default_error_msg;
114
const char *default_time_fmt;
115
const char *undefined_echo;
117
} include_dir_config;
120
const char *default_start_tag;
121
const char *default_end_tag;
122
} include_server_config;
124
/* main parser states */
129
PARSE_DIRECTIVE_POSTNAME,
130
PARSE_DIRECTIVE_TAIL,
131
PARSE_DIRECTIVE_POSTTAIL,
146
typedef struct arg_item {
147
struct arg_item *next;
151
apr_size_t value_len;
158
ap_regmatch_t match[AP_MAX_REG_MATCH];
164
apr_size_t pattern_len;
167
struct ssi_internal_ctx {
171
char quote; /* quote character value (or \0) */
172
apr_size_t parse_pos; /* parse position of partial matches */
173
apr_size_t bytes_read;
175
apr_bucket_brigade *tmp_bb;
178
const char *start_seq;
179
bndm_t *start_seq_pat;
181
apr_size_t end_seq_len;
182
char *directive; /* name of the current directive */
183
apr_size_t directive_len; /* length of the current directive name */
185
arg_item_t *current_arg; /* currently parsed argument */
186
arg_item_t *argv; /* all arguments */
188
backref_t *re; /* NULL if there wasn't a regex yet */
190
const char *undefined_echo;
191
apr_size_t undefined_echo_len;
196
apr_bucket_brigade *bb;
203
* +-------------------------------------------------------+
205
* | Debugging Utilities
207
* +-------------------------------------------------------+
212
#define TYPE_TOKEN(token, ttype) do { \
213
(token)->type = ttype; \
214
(token)->s = #ttype; \
217
#define CREATE_NODE(ctx, name) do { \
218
(name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
219
(name)->parent = (name)->left = (name)->right = NULL; \
221
(name)->dump_done = 0; \
224
static void debug_printf(include_ctx_t *ctx, const char *fmt, ...)
230
debug__str = apr_pvsprintf(ctx->pool, fmt, ap);
233
APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create(
234
debug__str, strlen(debug__str), ctx->pool,
235
ctx->intern->debug.f->c->bucket_alloc));
238
#define DUMP__CHILD(ctx, is, node, child) if (1) { \
239
parse_node_t *d__c = node->child; \
241
if (!d__c->dump_done) { \
242
if (d__c->parent != node) { \
243
debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \
244
if (!d__c->parent) { \
245
debug_printf(ctx, "Parent of " #child " child node is " \
249
debug_printf(ctx, "Parent of " #child " child node " \
250
"points to another node (of type %s)!\n", \
251
d__c->parent->token.s); \
260
debug_printf(ctx, "%s(missing)\n", is); \
264
static void debug_dump_tree(include_ctx_t *ctx, parse_node_t *root)
266
parse_node_t *current;
270
debug_printf(ctx, " -- Parse Tree empty --\n\n");
274
debug_printf(ctx, " ----- Parse Tree -----\n");
279
switch (current->token.type) {
282
debug_printf(ctx, "%s%s (%s)\n", is, current->token.s,
283
current->token.value);
284
current->dump_done = 1;
285
current = current->parent;
292
if (!current->dump_done) {
293
debug_printf(ctx, "%s%s\n", is, current->token.s);
294
is = apr_pstrcat(ctx->dpool, is, " ", NULL);
295
current->dump_done = 1;
298
DUMP__CHILD(ctx, is, current, right)
300
if (!current->right || current->right->dump_done) {
301
is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
302
if (current->right) current->right->dump_done = 0;
303
current = current->parent;
308
if (!current->dump_done) {
309
debug_printf(ctx, "%s%s\n", is, current->token.s);
310
is = apr_pstrcat(ctx->dpool, is, " ", NULL);
311
current->dump_done = 1;
314
DUMP__CHILD(ctx, is, current, left)
315
DUMP__CHILD(ctx, is, current, right)
317
if ((!current->left || current->left->dump_done) &&
318
(!current->right || current->right->dump_done)) {
320
is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
321
if (current->left) current->left->dump_done = 0;
322
if (current->right) current->right->dump_done = 0;
323
current = current->parent;
329
/* it is possible to call this function within the parser loop, to see
330
* how the tree is built. That way, we must cleanup after us to dump
331
* always the whole tree
334
if (root->left) root->left->dump_done = 0;
335
if (root->right) root->right->dump_done = 0;
337
debug_printf(ctx, " --- End Parse Tree ---\n\n");
342
#define DEBUG_INIT(ctx, filter, brigade) do { \
343
(ctx)->intern->debug.f = filter; \
344
(ctx)->intern->debug.bb = brigade; \
347
#define DEBUG_PRINTF(arg) debug_printf arg
349
#define DEBUG_DUMP_TOKEN(ctx, token) do { \
350
token_t *d__t = (token); \
352
if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \
353
DEBUG_PRINTF(((ctx), " Found: %s (%s)\n", d__t->s, d__t->value)); \
356
DEBUG_PRINTF((ctx, " Found: %s\n", d__t->s)); \
360
#define DEBUG_DUMP_EVAL(ctx, node) do { \
362
switch ((node)->token.type) { \
364
debug_printf((ctx), " Evaluate: %s (%s) -> %c\n", (node)->token.s,\
365
(node)->token.value, ((node)->value) ? '1':'0'); \
369
debug_printf((ctx), " Evaluate: %s (Left: %s; Right: %s) -> %c\n",\
371
(((node)->left->done) ? ((node)->left->value ?"1":"0") \
372
: "short circuited"), \
373
(((node)->right->done) ? ((node)->right->value?"1":"0") \
374
: "short circuited"), \
375
(node)->value ? '1' : '0'); \
383
if ((node)->right->token.type == TOKEN_RE) c = '/'; \
384
debug_printf((ctx), " Compare: %s (\"%s\" with %c%s%c) -> %c\n", \
386
(node)->left->token.value, \
387
c, (node)->right->token.value, c, \
388
(node)->value ? '1' : '0'); \
391
debug_printf((ctx), " Evaluate: %s -> %c\n", (node)->token.s, \
392
(node)->value ? '1' : '0'); \
397
#define DEBUG_DUMP_UNMATCHED(ctx, unmatched) do { \
399
DEBUG_PRINTF(((ctx), " Unmatched %c\n", (char)(unmatched))); \
403
#define DEBUG_DUMP_COND(ctx, text) \
404
DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \
405
((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0'))
407
#define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root)
409
#else /* DEBUG_INCLUDE */
411
#define TYPE_TOKEN(token, ttype) (token)->type = ttype
413
#define CREATE_NODE(ctx, name) do { \
414
(name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
415
(name)->parent = (name)->left = (name)->right = NULL; \
419
#define DEBUG_INIT(ctx, f, bb)
420
#define DEBUG_PRINTF(arg)
421
#define DEBUG_DUMP_TOKEN(ctx, token)
422
#define DEBUG_DUMP_EVAL(ctx, node)
423
#define DEBUG_DUMP_UNMATCHED(ctx, unmatched)
424
#define DEBUG_DUMP_COND(ctx, text)
425
#define DEBUG_DUMP_TREE(ctx, root)
427
#endif /* !DEBUG_INCLUDE */
431
* +-------------------------------------------------------+
433
* | Static Module Data
435
* +-------------------------------------------------------+
438
/* global module structure */
439
module AP_MODULE_DECLARE_DATA include_module;
441
/* function handlers for include directives */
442
static apr_hash_t *include_handlers;
444
/* forward declaration of handler registry */
445
static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
447
/* Sentinel value to store in subprocess_env for items that
448
* shouldn't be evaluated until/unless they're actually used
450
static const char lazy_eval_sentinel;
451
#define LAZY_VALUE (&lazy_eval_sentinel)
454
#define DEFAULT_START_SEQUENCE "<!--#"
455
#define DEFAULT_END_SEQUENCE "-->"
456
#define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
457
#define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
458
#define DEFAULT_UNDEFINED_ECHO "(none)"
461
#define DEFAULT_XBITHACK XBITHACK_FULL
463
#define DEFAULT_XBITHACK XBITHACK_OFF
468
* +-------------------------------------------------------+
470
* | Environment/Expansion Functions
472
* +-------------------------------------------------------+
476
* decodes a string containing html entities or numeric character references.
477
* 's' is overwritten with the decoded string.
478
* If 's' is syntatically incorrect, then the followed fixups will be made:
479
* unknown entities will be left undecoded;
480
* references to unused numeric characters will be deleted.
481
* In particular, � will not be decoded, but will be deleted.
484
/* maximum length of any ISO-LATIN-1 HTML entity name. */
485
#define MAXENTLEN (6)
487
/* The following is a shrinking transformation, therefore safe. */
489
static void decodehtml(char *s)
494
static const char * const entlist[MAXENTLEN + 1] =
498
"lt\074gt\076", /* 2 */
499
"amp\046ETH\320eth\360", /* 3 */
500
"quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml"
501
"\353iuml\357ouml\366uuml\374yuml\377", /* 4 */
503
"Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc"
504
"\333THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352"
505
"icirc\356ocirc\364ucirc\373thorn\376", /* 5 */
507
"Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311"
508
"Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde"
509
"\325Oslash\330Ugrave\331Uacute\332Yacute\335agrave\340"
510
"aacute\341atilde\343ccedil\347egrave\350eacute\351igrave"
511
"\354iacute\355ntilde\361ograve\362oacute\363otilde\365"
512
"oslash\370ugrave\371uacute\372yacute\375" /* 6 */
515
/* Do a fast scan through the string until we find anything
516
* that needs more complicated handling
518
for (; *s != '&'; s++) {
524
for (p = s; *s != '\0'; s++, p++) {
529
/* find end of entity */
530
for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
534
if (s[i] == '\0') { /* treat as normal data */
539
/* is it numeric ? */
541
for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
542
val = val * 10 + s[j] - '0';
545
if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
546
(val >= 127 && val <= 160) || val >= 256) {
547
p--; /* no data to output */
550
*p = RAW_ASCII_CHAR(val);
555
if (j > MAXENTLEN || entlist[j] == NULL) {
558
continue; /* skip it */
560
for (ents = entlist[j]; *ents != '\0'; ents += i) {
561
if (strncmp(s + 1, ents, j) == 0) {
567
*p = '&'; /* unknown */
570
*p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
579
static void add_include_vars(request_rec *r, const char *timefmt)
581
apr_table_t *e = r->subprocess_env;
584
apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
585
apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
586
apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
587
apr_table_setn(e, "DOCUMENT_URI", r->uri);
588
if (r->path_info && *r->path_info) {
589
apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
591
apr_table_setn(e, "USER_NAME", LAZY_VALUE);
592
if (r->filename && (t = strrchr(r->filename, '/'))) {
593
apr_table_setn(e, "DOCUMENT_NAME", ++t);
596
apr_table_setn(e, "DOCUMENT_NAME", r->uri);
599
char *arg_copy = apr_pstrdup(r->pool, r->args);
601
ap_unescape_url(arg_copy);
602
apr_table_setn(e, "QUERY_STRING_UNESCAPED",
603
ap_escape_shell_cmd(r->pool, arg_copy));
607
static const char *add_include_vars_lazy(request_rec *r, const char *var)
610
if (!strcasecmp(var, "DATE_LOCAL")) {
611
include_dir_config *conf =
612
(include_dir_config *)ap_get_module_config(r->per_dir_config,
614
val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 0);
616
else if (!strcasecmp(var, "DATE_GMT")) {
617
include_dir_config *conf =
618
(include_dir_config *)ap_get_module_config(r->per_dir_config,
620
val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 1);
622
else if (!strcasecmp(var, "LAST_MODIFIED")) {
623
include_dir_config *conf =
624
(include_dir_config *)ap_get_module_config(r->per_dir_config,
626
val = ap_ht_time(r->pool, r->finfo.mtime, conf->default_time_fmt, 0);
628
else if (!strcasecmp(var, "USER_NAME")) {
629
if (apr_uid_name_get(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
638
apr_table_setn(r->subprocess_env, var, val);
643
static const char *get_include_var(const char *var, include_ctx_t *ctx)
646
request_rec *r = ctx->intern->r;
648
if (apr_isdigit(*var) && !var[1]) {
649
apr_size_t idx = *var - '0';
650
backref_t *re = ctx->intern->re;
652
/* Handle $0 .. $9 from the last regex evaluated.
653
* The choice of returning NULL strings on not-found,
654
* v.s. empty strings on an empty match is deliberate.
657
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
658
"regex capture $%" APR_SIZE_T_FMT " refers to no regex in %s",
663
if (re->nsub < idx || idx >= AP_MAX_REG_MATCH) {
664
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
665
"regex capture $%" APR_SIZE_T_FMT
666
" is out of range (last regex was: '%s') in %s",
667
idx, re->rexp, r->filename);
671
if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) {
675
val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so,
676
re->match[idx].rm_eo - re->match[idx].rm_so);
680
val = apr_table_get(r->subprocess_env, var);
682
if (val == LAZY_VALUE) {
683
val = add_include_vars_lazy(r, var);
691
* Do variable substitution on strings
693
* (Note: If out==NULL, this function allocs a buffer for the resulting
694
* string from ctx->pool. The return value is always the parsed string)
696
static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out,
697
apr_size_t length, int leave_name)
699
request_rec *r = ctx->intern->r;
700
result_item_t *result = NULL, *current = NULL;
701
apr_size_t outlen = 0, inlen, span;
702
char *ret = NULL, *eout = NULL;
706
/* sanity check, out && !length is not supported */
707
ap_assert(out && length);
710
eout = out + length - 1;
713
span = strcspn(in, "\\$");
719
apr_cpystrn(out, in, length);
722
ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen)
723
? length - 1 : inlen);
729
/* well, actually something to do */
734
memcpy(out, in, (out+span <= eout) ? span : (eout-out));
739
current = result = apr_palloc(ctx->dpool, sizeof(*result));
740
current->next = NULL;
741
current->string = in;
746
/* loop for specials */
748
if ((out && out >= eout) || (length && outlen >= length)) {
752
/* prepare next entry */
753
if (!out && current->len) {
754
current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
755
current = current->next;
756
current->next = NULL;
765
*out++ = (p[1] == '$') ? *++p : *p;
770
current->string = (p[1] == '$') ? ++p : p;
779
else { /* *p == '$' */
780
const char *newp = NULL, *ep, *key = NULL;
783
ep = ap_strchr_c(++p, '}');
785
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Missing '}' on "
786
"variable \"%s\" in %s", p, r->filename);
791
key = apr_pstrmemdup(ctx->dpool, p, ep - p);
798
while (*ep == '_' || apr_isalnum(*ep)) {
803
key = apr_pstrmemdup(ctx->dpool, p, ep - p);
809
/* empty name results in a copy of '$' in the output string */
816
current->string = p++;
821
const char *val = get_include_var(key, ctx);
827
else if (leave_name) {
834
memcpy(out, val, (out+len <= eout) ? len : (eout-out));
839
current->string = val;
848
if ((out && out >= eout) || (length && outlen >= length)) {
852
/* check the remainder */
853
if (*p && (span = strcspn(p, "\\$")) > 0) {
854
if (!out && current->len) {
855
current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
856
current = current->next;
857
current->next = NULL;
861
memcpy(out, p, (out+span <= eout) ? span : (eout-out));
872
} while (p < in+inlen);
874
/* assemble result */
886
if (length && outlen > length) {
890
ret = out = apr_palloc(ctx->pool, outlen + 1);
895
memcpy(out, result->string, (out+result->len <= ep)
896
? result->len : (ep-out));
899
result = result->next;
900
} while (result && out < ep);
910
* +-------------------------------------------------------+
912
* | Conditional Expression Parser
914
* +-------------------------------------------------------+
917
static APR_INLINE int re_check(include_ctx_t *ctx, const char *string,
920
ap_regex_t *compiled;
921
backref_t *re = ctx->intern->re;
924
compiled = ap_pregcomp(ctx->dpool, rexp, AP_REG_EXTENDED);
926
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->intern->r, "unable to "
927
"compile pattern \"%s\"", rexp);
932
re = ctx->intern->re = apr_palloc(ctx->pool, sizeof(*re));
935
re->source = apr_pstrdup(ctx->pool, string);
936
re->rexp = apr_pstrdup(ctx->pool, rexp);
937
re->nsub = compiled->re_nsub;
938
rc = !ap_regexec(compiled, string, AP_MAX_REG_MATCH, re->match, 0);
940
ap_pregfree(ctx->dpool, compiled);
944
static int get_ptoken(apr_pool_t *pool, const char **parse, token_t *token)
956
/* Skip leading white space */
957
while (apr_isspace(**parse)) {
966
TYPE_TOKEN(token, TOKEN_STRING); /* the default type */
970
switch (*(*parse)++) {
972
TYPE_TOKEN(token, TOKEN_LBRACE);
975
TYPE_TOKEN(token, TOKEN_RBRACE);
978
if (**parse == '=') ++*parse;
979
TYPE_TOKEN(token, TOKEN_EQ);
982
if (**parse == '=') {
983
TYPE_TOKEN(token, TOKEN_NE);
987
TYPE_TOKEN(token, TOKEN_NOT);
993
TYPE_TOKEN(token, TOKEN_RE);
997
if (**parse == '|') {
998
TYPE_TOKEN(token, TOKEN_OR);
1004
if (**parse == '&') {
1005
TYPE_TOKEN(token, TOKEN_AND);
1011
if (**parse == '=') {
1012
TYPE_TOKEN(token, TOKEN_GE);
1016
TYPE_TOKEN(token, TOKEN_GT);
1019
if (**parse == '=') {
1020
TYPE_TOKEN(token, TOKEN_LE);
1024
TYPE_TOKEN(token, TOKEN_LT);
1028
/* It's a string or regex token
1029
* Now search for the next token, which finishes this string
1032
p = *parse = token->value = unmatched ? *parse : p;
1034
for (; **parse; p = ++*parse) {
1035
if (**parse == '\\') {
1045
if (**parse == unmatched) {
1050
} else if (apr_isspace(**parse)) {
1068
if ((*parse)[1] == **parse) {
1082
token->value = apr_pstrdup(pool, "");
1085
apr_size_t len = p - token->value - shift;
1086
char *c = apr_palloc(pool, len + 1);
1092
const char *e = ap_strchr_c(p, '\\');
1110
static int parse_expr(include_ctx_t *ctx, const char *expr, int *was_error)
1112
parse_node_t *new, *root = NULL, *current = NULL;
1113
request_rec *r = ctx->intern->r;
1114
const char *error = "Invalid expression \"%s\" in file %s";
1115
const char *parse = expr;
1116
int was_unmatched = 0;
1125
/* Create Parse Tree */
1127
/* uncomment this to see how the tree a built:
1129
* DEBUG_DUMP_TREE(ctx, root);
1131
CREATE_NODE(ctx, new);
1133
was_unmatched = get_ptoken(ctx->dpool, &parse, &new->token);
1138
DEBUG_DUMP_UNMATCHED(ctx, was_unmatched);
1139
DEBUG_DUMP_TOKEN(ctx, &new->token);
1142
switch (new->token.type) {
1146
root = current = new;
1150
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr,
1157
switch (new->token.type) {
1159
switch (current->token.type) {
1161
current->token.value =
1162
apr_pstrcat(ctx->dpool, current->token.value,
1163
*current->token.value ? " " : "",
1164
new->token.value, NULL);
1173
new->parent = current;
1174
current = current->right = new;
1180
switch (current->token.type) {
1183
new->parent = current;
1184
current = current->right = new;
1195
switch (current->token.type) {
1199
current = current->parent;
1202
switch (current->token.type) {
1209
current = current->parent;
1218
current = root = new;
1222
new->left = current->right;
1223
new->left->parent = new;
1224
new->parent = current;
1225
current = current->right = new;
1239
if (current->token.type == TOKEN_STRING) {
1240
current = current->parent;
1245
current = root = new;
1249
switch (current->token.type) {
1253
new->left = current->right;
1254
new->left->parent = new;
1255
new->parent = current;
1256
current = current->right = new;
1266
while (current && current->token.type != TOKEN_LBRACE) {
1267
current = current->parent;
1271
TYPE_TOKEN(¤t->token, TOKEN_GROUP);
1275
error = "Unmatched ')' in \"%s\" in file %s";
1280
switch (current->token.type) {
1288
current->right = new;
1289
new->parent = current;
1299
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename);
1304
DEBUG_DUMP_TREE(ctx, root);
1306
/* Evaluate Parse Tree */
1310
switch (current->token.type) {
1312
current->token.value =
1313
ap_ssi_parse_string(ctx, current->token.value, NULL, 0,
1314
SSI_EXPAND_DROP_NAME);
1315
current->value = !!*current->token.value;
1320
if (!current->left || !current->right) {
1321
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1322
"Invalid expression \"%s\" in file %s",
1328
if (!current->left->done) {
1329
switch (current->left->token.type) {
1331
current->left->token.value =
1332
ap_ssi_parse_string(ctx, current->left->token.value,
1333
NULL, 0, SSI_EXPAND_DROP_NAME);
1334
current->left->value = !!*current->left->token.value;
1335
DEBUG_DUMP_EVAL(ctx, current->left);
1336
current->left->done = 1;
1340
current = current->left;
1345
/* short circuit evaluation */
1346
if (!current->right->done && !regex &&
1347
((current->token.type == TOKEN_AND && !current->left->value) ||
1348
(current->token.type == TOKEN_OR && current->left->value))) {
1349
current->value = current->left->value;
1352
if (!current->right->done) {
1353
switch (current->right->token.type) {
1355
current->right->token.value =
1356
ap_ssi_parse_string(ctx,current->right->token.value,
1357
NULL, 0, SSI_EXPAND_DROP_NAME);
1358
current->right->value = !!*current->right->token.value;
1359
DEBUG_DUMP_EVAL(ctx, current->right);
1360
current->right->done = 1;
1364
current = current->right;
1369
if (current->token.type == TOKEN_AND) {
1370
current->value = current->left->value &&
1371
current->right->value;
1374
current->value = current->left->value ||
1375
current->right->value;
1382
if (!current->left || !current->right ||
1383
current->left->token.type != TOKEN_STRING ||
1384
(current->right->token.type != TOKEN_STRING &&
1385
current->right->token.type != TOKEN_RE)) {
1386
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1387
"Invalid expression \"%s\" in file %s",
1392
current->left->token.value =
1393
ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
1394
SSI_EXPAND_DROP_NAME);
1395
current->right->token.value =
1396
ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
1397
SSI_EXPAND_DROP_NAME);
1399
if (current->right->token.type == TOKEN_RE) {
1400
current->value = re_check(ctx, current->left->token.value,
1401
current->right->token.value);
1405
current->value = !strcmp(current->left->token.value,
1406
current->right->token.value);
1409
if (current->token.type == TOKEN_NE) {
1410
current->value = !current->value;
1418
if (!current->left || !current->right ||
1419
current->left->token.type != TOKEN_STRING ||
1420
current->right->token.type != TOKEN_STRING) {
1421
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1422
"Invalid expression \"%s\" in file %s",
1428
current->left->token.value =
1429
ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
1430
SSI_EXPAND_DROP_NAME);
1431
current->right->token.value =
1432
ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
1433
SSI_EXPAND_DROP_NAME);
1435
current->value = strcmp(current->left->token.value,
1436
current->right->token.value);
1438
switch (current->token.type) {
1439
case TOKEN_GE: current->value = current->value >= 0; break;
1440
case TOKEN_GT: current->value = current->value > 0; break;
1441
case TOKEN_LE: current->value = current->value <= 0; break;
1442
case TOKEN_LT: current->value = current->value < 0; break;
1443
default: current->value = 0; break; /* should not happen */
1449
if (current->right) {
1450
if (!current->right->done) {
1451
current = current->right;
1454
current->value = current->right->value;
1460
if (current->token.type == TOKEN_NOT) {
1461
current->value = !current->value;
1467
error = "No operator before regex in expr \"%s\" in file %s";
1471
error = "Unmatched '(' in \"%s\" in file %s";
1475
error = "internal parser error in \"%s\" in file %s";
1478
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr,r->filename);
1483
DEBUG_DUMP_EVAL(ctx, current);
1485
current = current->parent;
1488
return (root ? root->value : 0);
1493
* +-------------------------------------------------------+
1497
* +-------------------------------------------------------+
1501
* Extract the next tag name and value.
1502
* If there are no more tags, set the tag name to NULL.
1503
* The tag value is html decoded if dodecode is non-zero.
1504
* The tag value may be NULL if there is no tag value..
1506
static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
1507
char **tag_val, int dodecode)
1509
if (!ctx->intern->argv) {
1516
*tag_val = ctx->intern->argv->value;
1517
*tag = ctx->intern->argv->name;
1519
ctx->intern->argv = ctx->intern->argv->next;
1521
if (dodecode && *tag_val) {
1522
decodehtml(*tag_val);
1528
static int find_file(request_rec *r, const char *directive, const char *tag,
1529
char *tag_val, apr_finfo_t *finfo)
1531
char *to_send = tag_val;
1532
request_rec *rr = NULL;
1534
char *error_fmt = NULL;
1535
apr_status_t rv = APR_SUCCESS;
1537
if (!strcmp(tag, "file")) {
1540
/* be safe; only files in this directory or below allowed */
1541
rv = apr_filepath_merge(&newpath, NULL, tag_val,
1542
APR_FILEPATH_SECUREROOTTEST |
1543
APR_FILEPATH_NOTABSOLUTE, r->pool);
1545
if (rv != APR_SUCCESS) {
1546
error_fmt = "unable to access file \"%s\" "
1547
"in parsed file %s";
1550
/* note: it is okay to pass NULL for the "next filter" since
1551
we never attempt to "run" this sub request. */
1552
rr = ap_sub_req_lookup_file(newpath, r, NULL);
1554
if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1555
to_send = rr->filename;
1556
if ((rv = apr_stat(finfo, to_send,
1557
APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
1558
&& rv != APR_INCOMPLETE) {
1559
error_fmt = "unable to get information about \"%s\" "
1560
"in parsed file %s";
1564
error_fmt = "unable to lookup information about \"%s\" "
1565
"in parsed file %s";
1571
ap_log_rerror(APLOG_MARK, APLOG_ERR,
1572
rv, r, error_fmt, to_send, r->filename);
1575
if (rr) ap_destroy_sub_req(rr);
1579
else if (!strcmp(tag, "virtual")) {
1580
/* note: it is okay to pass NULL for the "next filter" since
1581
we never attempt to "run" this sub request. */
1582
rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1584
if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1585
memcpy((char *) finfo, (const char *) &rr->finfo,
1587
ap_destroy_sub_req(rr);
1591
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unable to get "
1592
"information about \"%s\" in parsed file %s",
1593
tag_val, r->filename);
1594
ap_destroy_sub_req(rr);
1599
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
1600
"to tag %s in %s", tag, directive, r->filename);
1606
* <!--#include virtual|file="..." [virtual|file="..."] ... -->
1608
static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
1609
apr_bucket_brigade *bb)
1611
request_rec *r = f->r;
1614
ap_log_rerror(APLOG_MARK,
1615
(ctx->flags & SSI_FLAG_PRINTING)
1616
? APLOG_ERR : APLOG_WARNING,
1617
0, r, "missing argument for include element in %s",
1621
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1626
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1632
char *tag_val = NULL;
1633
request_rec *rr = NULL;
1634
char *error_fmt = NULL;
1635
char *parsed_string;
1637
ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1638
if (!tag || !tag_val) {
1642
if (strcmp(tag, "virtual") && strcmp(tag, "file")) {
1643
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1644
"\"%s\" to tag include in %s", tag, r->filename);
1645
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1649
parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1650
SSI_EXPAND_DROP_NAME);
1651
if (tag[0] == 'f') {
1655
/* be safe; only files in this directory or below allowed */
1656
rv = apr_filepath_merge(&newpath, NULL, parsed_string,
1657
APR_FILEPATH_SECUREROOTTEST |
1658
APR_FILEPATH_NOTABSOLUTE, ctx->dpool);
1660
if (rv != APR_SUCCESS) {
1661
error_fmt = "unable to include file \"%s\" in parsed file %s";
1664
rr = ap_sub_req_lookup_file(newpath, r, f->next);
1668
rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
1671
if (!error_fmt && rr->status != HTTP_OK) {
1672
error_fmt = "unable to include \"%s\" in parsed file %s";
1675
if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) &&
1676
rr->content_type && strncmp(rr->content_type, "text/", 5)) {
1678
error_fmt = "unable to include potential exec \"%s\" in parsed "
1682
/* See the Kludge in includes_filter for why.
1683
* Basically, it puts a bread crumb in here, then looks
1684
* for the crumb later to see if its been here.
1687
ap_set_module_config(rr->request_config, &include_module, r);
1690
if (!error_fmt && ap_run_sub_req(rr)) {
1691
error_fmt = "unable to include \"%s\" in parsed file %s";
1695
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, tag_val,
1697
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1700
/* Do *not* destroy the subrequest here; it may have allocated
1701
* variables in this r->subprocess_env in the subrequest's
1702
* r->pool, so that pool must survive as long as this request.
1703
* Yes, this is a memory leak. */
1714
* <!--#echo [encoding="..."] var="..." [encoding="..."] var="..." ... -->
1716
static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
1717
apr_bucket_brigade *bb)
1719
enum {E_NONE, E_URL, E_ENTITY} encode;
1720
request_rec *r = f->r;
1723
ap_log_rerror(APLOG_MARK,
1724
(ctx->flags & SSI_FLAG_PRINTING)
1725
? APLOG_ERR : APLOG_WARNING,
1726
0, r, "missing argument for echo element in %s",
1730
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1735
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1743
char *tag_val = NULL;
1745
ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1746
if (!tag || !tag_val) {
1750
if (!strcmp(tag, "var")) {
1752
const char *echo_text = NULL;
1755
val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL,
1756
0, SSI_EXPAND_DROP_NAME),
1765
echo_text = ap_escape_uri(ctx->dpool, val);
1768
echo_text = ap_escape_html(ctx->dpool, val);
1772
e_len = strlen(echo_text);
1775
echo_text = ctx->intern->undefined_echo;
1776
e_len = ctx->intern->undefined_echo_len;
1779
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(
1780
apr_pmemdup(ctx->pool, echo_text, e_len),
1781
e_len, ctx->pool, f->c->bucket_alloc));
1783
else if (!strcmp(tag, "encoding")) {
1784
if (!strcasecmp(tag_val, "none")) {
1787
else if (!strcasecmp(tag_val, "url")) {
1790
else if (!strcasecmp(tag_val, "entity")) {
1794
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1795
"\"%s\" to parameter \"encoding\" of tag echo in "
1796
"%s", tag_val, r->filename);
1797
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1802
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1803
"\"%s\" in tag echo of %s", tag, r->filename);
1804
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1813
* <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."]
1814
* [echomsg="..."] -->
1816
static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
1817
apr_bucket_brigade *bb)
1819
request_rec *r = f->r;
1820
apr_table_t *env = r->subprocess_env;
1823
ap_log_rerror(APLOG_MARK,
1824
(ctx->flags & SSI_FLAG_PRINTING)
1825
? APLOG_ERR : APLOG_WARNING,
1826
0, r, "missing argument for config element in %s",
1830
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1835
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1841
char *tag_val = NULL;
1843
ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
1844
if (!tag || !tag_val) {
1848
if (!strcmp(tag, "errmsg")) {
1849
ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1850
SSI_EXPAND_DROP_NAME);
1852
else if (!strcmp(tag, "echomsg")) {
1853
ctx->intern->undefined_echo =
1854
ap_ssi_parse_string(ctx, tag_val, NULL, 0,SSI_EXPAND_DROP_NAME);
1855
ctx->intern->undefined_echo_len=strlen(ctx->intern->undefined_echo);
1857
else if (!strcmp(tag, "timefmt")) {
1858
apr_time_t date = r->request_time;
1860
ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1861
SSI_EXPAND_DROP_NAME);
1863
apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
1865
apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
1867
apr_table_setn(env, "LAST_MODIFIED",
1868
ap_ht_time(r->pool, r->finfo.mtime,
1871
else if (!strcmp(tag, "sizefmt")) {
1872
char *parsed_string;
1874
parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1875
SSI_EXPAND_DROP_NAME);
1876
if (!strcmp(parsed_string, "bytes")) {
1877
ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
1879
else if (!strcmp(parsed_string, "abbrev")) {
1880
ctx->flags &= SSI_FLAG_SIZE_ABBREV;
1883
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1884
"\"%s\" to parameter \"sizefmt\" of tag config "
1885
"in %s", parsed_string, r->filename);
1886
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1891
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1892
"\"%s\" to tag config in %s", tag, r->filename);
1893
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1902
* <!--#fsize virtual|file="..." [virtual|file="..."] ... -->
1904
static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
1905
apr_bucket_brigade *bb)
1907
request_rec *r = f->r;
1910
ap_log_rerror(APLOG_MARK,
1911
(ctx->flags & SSI_FLAG_PRINTING)
1912
? APLOG_ERR : APLOG_WARNING,
1913
0, r, "missing argument for fsize element in %s",
1917
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1922
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1928
char *tag_val = NULL;
1930
char *parsed_string;
1932
ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1933
if (!tag || !tag_val) {
1937
parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1938
SSI_EXPAND_DROP_NAME);
1940
if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
1944
if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
1945
buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5));
1946
len = 4; /* omit the \0 terminator */
1949
apr_size_t l, x, pos;
1952
tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size);
1953
len = l = strlen(tmp);
1955
for (x = 0; x < l; ++x) {
1956
if (x && !((l - x) % 3)) {
1962
buf = apr_pstrmemdup(ctx->pool, tmp, len);
1965
buf = apr_palloc(ctx->pool, len);
1967
for (pos = x = 0; x < l; ++x) {
1968
if (x && !((l - x) % 3)) {
1971
buf[pos++] = tmp[x];
1976
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len,
1977
ctx->pool, f->c->bucket_alloc));
1980
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1989
* <!--#flastmod virtual|file="..." [virtual|file="..."] ... -->
1991
static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
1992
apr_bucket_brigade *bb)
1994
request_rec *r = f->r;
1997
ap_log_rerror(APLOG_MARK,
1998
(ctx->flags & SSI_FLAG_PRINTING)
1999
? APLOG_ERR : APLOG_WARNING,
2000
0, r, "missing argument for flastmod element in %s",
2004
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2009
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2015
char *tag_val = NULL;
2017
char *parsed_string;
2019
ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2020
if (!tag || !tag_val) {
2024
parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2025
SSI_EXPAND_DROP_NAME);
2027
if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
2031
t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0);
2032
t_len = strlen(t_val);
2034
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len,
2035
ctx->pool, f->c->bucket_alloc));
2038
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2047
* <!--#if expr="..." -->
2049
static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f,
2050
apr_bucket_brigade *bb)
2054
request_rec *r = f->r;
2055
int expr_ret, was_error;
2057
if (ctx->argc != 1) {
2058
ap_log_rerror(APLOG_MARK,
2059
(ctx->flags & SSI_FLAG_PRINTING)
2060
? APLOG_ERR : APLOG_WARNING,
2062
? "too many arguments for if element in %s"
2063
: "missing expr argument for if element in %s",
2067
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2068
++(ctx->if_nesting_level);
2072
if (ctx->argc != 1) {
2073
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2077
ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2079
if (strcmp(tag, "expr")) {
2080
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
2081
"to tag if in %s", tag, r->filename);
2082
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2087
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr value for if "
2088
"element in %s", r->filename);
2089
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2093
DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr));
2095
expr_ret = parse_expr(ctx, expr, &was_error);
2098
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2103
ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2106
ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2109
DEBUG_DUMP_COND(ctx, " if");
2111
ctx->if_nesting_level = 0;
2117
* <!--#elif expr="..." -->
2119
static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f,
2120
apr_bucket_brigade *bb)
2124
request_rec *r = f->r;
2125
int expr_ret, was_error;
2127
if (ctx->argc != 1) {
2128
ap_log_rerror(APLOG_MARK,
2129
(!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2131
? "too many arguments for if element in %s"
2132
: "missing expr argument for if element in %s",
2136
if (ctx->if_nesting_level) {
2140
if (ctx->argc != 1) {
2141
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2145
ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2147
if (strcmp(tag, "expr")) {
2148
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
2149
"to tag if in %s", tag, r->filename);
2150
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2155
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr in elif "
2156
"statement: %s", r->filename);
2157
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2161
DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr));
2162
DEBUG_DUMP_COND(ctx, " elif");
2164
if (ctx->flags & SSI_FLAG_COND_TRUE) {
2165
ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2169
expr_ret = parse_expr(ctx, expr, &was_error);
2172
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2177
ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2180
ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2183
DEBUG_DUMP_COND(ctx, " elif");
2191
static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f,
2192
apr_bucket_brigade *bb)
2194
request_rec *r = f->r;
2197
ap_log_rerror(APLOG_MARK,
2198
(!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2199
0, r, "else directive does not take tags in %s",
2203
if (ctx->if_nesting_level) {
2208
if (ctx->flags & SSI_FLAG_PRINTING) {
2209
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2215
DEBUG_DUMP_COND(ctx, " else");
2217
if (ctx->flags & SSI_FLAG_COND_TRUE) {
2218
ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2221
ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2230
static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f,
2231
apr_bucket_brigade *bb)
2233
request_rec *r = f->r;
2236
ap_log_rerror(APLOG_MARK,
2237
(!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2238
0, r, "endif directive does not take tags in %s",
2242
if (ctx->if_nesting_level) {
2243
--(ctx->if_nesting_level);
2248
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2252
DEBUG_DUMP_COND(ctx, "endif");
2254
ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2260
* <!--#set var="..." value="..." ... -->
2262
static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
2263
apr_bucket_brigade *bb)
2266
request_rec *r = f->r;
2267
request_rec *sub = r->main;
2268
apr_pool_t *p = r->pool;
2270
if (ctx->argc < 2) {
2271
ap_log_rerror(APLOG_MARK,
2272
(ctx->flags & SSI_FLAG_PRINTING)
2273
? APLOG_ERR : APLOG_WARNING,
2274
0, r, "missing argument for set element in %s",
2278
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2282
if (ctx->argc < 2) {
2283
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2287
/* we need to use the 'main' request pool to set notes as that is
2297
char *tag_val = NULL;
2299
ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2301
if (!tag || !tag_val) {
2305
if (!strcmp(tag, "var")) {
2306
var = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2307
SSI_EXPAND_DROP_NAME);
2309
else if (!strcmp(tag, "value")) {
2310
char *parsed_string;
2313
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "variable must "
2314
"precede value in set directive in %s",
2316
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2320
parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2321
SSI_EXPAND_DROP_NAME);
2322
apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
2323
apr_pstrdup(p, parsed_string));
2326
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Invalid tag for set "
2327
"directive in %s", r->filename);
2328
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2339
static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
2340
apr_bucket_brigade *bb)
2342
request_rec *r = f->r;
2343
const apr_array_header_t *arr;
2344
const apr_table_entry_t *elts;
2348
ap_log_rerror(APLOG_MARK,
2349
(ctx->flags & SSI_FLAG_PRINTING)
2350
? APLOG_ERR : APLOG_WARNING,
2351
0, r, "printenv directive does not take tags in %s",
2355
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2360
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2364
arr = apr_table_elts(r->subprocess_env);
2365
elts = (apr_table_entry_t *)arr->elts;
2367
for (i = 0; i < arr->nelts; ++i) {
2368
const char *key_text, *val_text;
2369
char *key_val, *next;
2370
apr_size_t k_len, v_len, kv_length;
2373
key_text = ap_escape_html(ctx->dpool, elts[i].key);
2374
k_len = strlen(key_text);
2377
val_text = elts[i].val;
2378
if (val_text == LAZY_VALUE) {
2379
val_text = add_include_vars_lazy(r, elts[i].key);
2381
val_text = ap_escape_html(ctx->dpool, elts[i].val);
2382
v_len = strlen(val_text);
2384
/* assemble result */
2385
kv_length = k_len + v_len + sizeof("=\n");
2386
key_val = apr_palloc(ctx->pool, kv_length);
2389
memcpy(next, key_text, k_len);
2392
memcpy(next, val_text, v_len);
2397
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(key_val, kv_length-1,
2398
ctx->pool, f->c->bucket_alloc));
2407
* +-------------------------------------------------------+
2409
* | Main Includes-Filter Engine
2411
* +-------------------------------------------------------+
2414
/* This is an implementation of the BNDM search algorithm.
2416
* Fast and Flexible String Matching by Combining Bit-parallelism and
2417
* Suffix Automata (2001)
2418
* Gonzalo Navarro, Mathieu Raffinot
2420
* http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
2422
* Initial code submitted by Sascha Schumann.
2425
/* Precompile the bndm_t data structure. */
2426
static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl)
2429
const char *ne = n + nl;
2430
bndm_t *t = apr_palloc(pool, sizeof(*t));
2432
memset(t->T, 0, sizeof(unsigned int) * 256);
2433
t->pattern_len = nl;
2435
for (x = 1; n < ne; x <<= 1) {
2436
t->T[(unsigned char) *n++] |= x;
2444
/* Implements the BNDM search algorithm (as described above).
2446
* h - the string to look in
2447
* hl - length of the string to look for
2448
* t - precompiled bndm structure against the pattern
2450
* Returns the count of character that is the first match or hl if no
2453
static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl)
2456
const char *he, *p, *pi;
2457
unsigned int *T, x, d;
2464
nl = t->pattern_len;
2466
pi = h - 1; /* pi: p initial */
2467
p = pi + nl; /* compare window right to left. point to the first char */
2473
d &= T[(unsigned char) *p--];
2496
* returns the index position of the first byte of start_seq (or the len of
2497
* the buffer as non-match)
2499
static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data,
2502
struct ssi_internal_ctx *intern = ctx->intern;
2503
apr_size_t slen = intern->start_seq_pat->pattern_len;
2508
p = data; /* try partial match at the end of the buffer (below) */
2511
/* try fast bndm search over the buffer
2512
* (hopefully the whole start sequence can be found in this buffer)
2514
index = bndm(intern->start_seq_pat, data, len);
2516
/* wow, found it. ready. */
2518
intern->state = PARSE_DIRECTIVE;
2522
/* ok, the pattern can't be found as whole in the buffer,
2523
* check the end for a partial match
2525
p = data + len - slen + 1;
2531
while (p < ep && *p != *intern->start_seq) {
2537
/* found a possible start_seq start */
2542
while (p < ep && *p == intern->start_seq[pos]) {
2547
/* partial match found. Store the info for the next round */
2549
intern->state = PARSE_HEAD;
2550
intern->parse_pos = pos;
2555
/* we must try all combinations; consider (e.g.) SSIStartTag "--->"
2556
* and a string data of "--.-" and the end of the buffer
2558
p = data + index + 1;
2566
* returns the first byte *after* the partial (or final) match.
2568
* If we had to trick with the start_seq start, 'release' returns the
2569
* number of chars of the start_seq which appeared not to be part of a
2570
* full tag and may have to be passed down the filter chain.
2572
static apr_size_t find_partial_start_sequence(include_ctx_t *ctx,
2575
apr_size_t *release)
2577
struct ssi_internal_ctx *intern = ctx->intern;
2578
apr_size_t pos, spos = 0;
2579
apr_size_t slen = intern->start_seq_pat->pattern_len;
2582
pos = intern->parse_pos;
2589
while (p < ep && pos < slen && *p == intern->start_seq[pos]) {
2596
intern->state = PARSE_DIRECTIVE;
2600
/* the whole buffer is a partial match */
2602
intern->parse_pos = pos;
2606
/* No match so far, but again:
2607
* We must try all combinations, since the start_seq is a random
2608
* user supplied string
2610
* So: look if the first char of start_seq appears somewhere within
2611
* the current partial match. If it does, try to start a match that
2612
* begins with this offset. (This can happen, if a strange
2613
* start_seq like "---->" spans buffers)
2615
if (spos < intern->parse_pos) {
2619
p = intern->start_seq + spos;
2620
pos = intern->parse_pos - spos;
2622
while (pos && *p != *intern->start_seq) {
2629
/* if a matching beginning char was found, try to match the
2630
* remainder of the old buffer.
2636
while (t < pos && *p == intern->start_seq[t]) {
2642
/* yeah, another partial match found in the *old*
2643
* buffer, now test the *current* buffer for
2657
} while (1); /* work hard to find a match ;-) */
2659
/* no match at all, release all (wrongly) matched chars so far */
2660
*release = intern->parse_pos;
2661
intern->state = PARSE_PRE_HEAD;
2666
* returns the position after the directive
2668
static apr_size_t find_directive(include_ctx_t *ctx, const char *data,
2669
apr_size_t len, char ***store,
2670
apr_size_t **store_len)
2672
struct ssi_internal_ctx *intern = ctx->intern;
2673
const char *p = data;
2674
const char *ep = data + len;
2677
switch (intern->state) {
2678
case PARSE_DIRECTIVE:
2679
while (p < ep && !apr_isspace(*p)) {
2680
/* we have to consider the case of missing space between directive
2681
* and end_seq (be somewhat lenient), e.g. <!--#printenv-->
2683
if (*p == *intern->end_seq) {
2684
intern->state = PARSE_DIRECTIVE_TAIL;
2685
intern->parse_pos = 1;
2692
if (p < ep) { /* found delimiter whitespace */
2693
intern->state = PARSE_DIRECTIVE_POSTNAME;
2694
*store = &intern->directive;
2695
*store_len = &intern->directive_len;
2700
case PARSE_DIRECTIVE_TAIL:
2701
pos = intern->parse_pos;
2703
while (p < ep && pos < intern->end_seq_len &&
2704
*p == intern->end_seq[pos]) {
2709
/* full match, we're done */
2710
if (pos == intern->end_seq_len) {
2711
intern->state = PARSE_DIRECTIVE_POSTTAIL;
2712
*store = &intern->directive;
2713
*store_len = &intern->directive_len;
2717
/* partial match, the buffer is too small to match fully */
2719
intern->parse_pos = pos;
2723
/* no match. continue normal parsing */
2724
intern->state = PARSE_DIRECTIVE;
2727
case PARSE_DIRECTIVE_POSTTAIL:
2728
intern->state = PARSE_EXECUTE;
2729
intern->directive_len -= intern->end_seq_len;
2730
/* continue immediately with the next state */
2732
case PARSE_DIRECTIVE_POSTNAME:
2733
if (PARSE_DIRECTIVE_POSTNAME == intern->state) {
2734
intern->state = PARSE_PRE_ARG;
2737
intern->argv = NULL;
2739
if (!intern->directive_len) {
2741
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2742
"directive name in parsed document %s",
2743
intern->r->filename);
2746
char *sp = intern->directive;
2747
char *sep = intern->directive + intern->directive_len;
2749
/* normalize directive name */
2750
for (; sp < sep; ++sp) {
2751
*sp = apr_tolower(*sp);
2758
/* get a rid of a gcc warning about unhandled enumerations */
2766
* find out whether the next token is (a possible) end_seq or an argument
2768
static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data,
2771
struct ssi_internal_ctx *intern = ctx->intern;
2772
const char *p = data;
2773
const char *ep = data + len;
2775
/* skip leading WS */
2776
while (p < ep && apr_isspace(*p)) {
2780
/* buffer doesn't consist of whitespaces only */
2782
intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG;
2789
* test the stream for end_seq. If it doesn't match at all, it must be an
2792
static apr_size_t find_tail(include_ctx_t *ctx, const char *data,
2795
struct ssi_internal_ctx *intern = ctx->intern;
2796
const char *p = data;
2797
const char *ep = data + len;
2798
apr_size_t pos = intern->parse_pos;
2800
if (PARSE_TAIL == intern->state) {
2801
intern->state = PARSE_TAIL_SEQ;
2802
pos = intern->parse_pos = 0;
2805
while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) {
2810
/* bingo, full match */
2811
if (pos == intern->end_seq_len) {
2812
intern->state = PARSE_EXECUTE;
2816
/* partial match, the buffer is too small to match fully */
2818
intern->parse_pos = pos;
2822
/* no match. It must be an argument string then
2823
* The caller should cleanup and rewind to the reparse point
2825
intern->state = PARSE_ARG;
2830
* extract name=value from the buffer
2831
* A pcre-pattern could look (similar to):
2832
* name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
2834
static apr_size_t find_argument(include_ctx_t *ctx, const char *data,
2835
apr_size_t len, char ***store,
2836
apr_size_t **store_len)
2838
struct ssi_internal_ctx *intern = ctx->intern;
2839
const char *p = data;
2840
const char *ep = data + len;
2842
switch (intern->state) {
2845
* create argument structure and append it to the current list
2847
intern->current_arg = apr_palloc(ctx->dpool,
2848
sizeof(*intern->current_arg));
2849
intern->current_arg->next = NULL;
2852
if (!intern->argv) {
2853
intern->argv = intern->current_arg;
2856
arg_item_t *newarg = intern->argv;
2858
while (newarg->next) {
2859
newarg = newarg->next;
2861
newarg->next = intern->current_arg;
2864
/* check whether it's a valid one. If it begins with a quote, we
2865
* can safely assume, someone forgot the name of the argument
2868
case '"': case '\'': case '`':
2871
intern->state = PARSE_ARG_VAL;
2872
intern->quote = *p++;
2873
intern->current_arg->name = NULL;
2874
intern->current_arg->name_len = 0;
2877
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2878
"argument name for value to tag %s in %s",
2879
apr_pstrmemdup(intern->r->pool, intern->directive,
2880
intern->directive_len),
2881
intern->r->filename);
2886
intern->state = PARSE_ARG_NAME;
2888
/* continue immediately with next state */
2890
case PARSE_ARG_NAME:
2891
while (p < ep && !apr_isspace(*p) && *p != '=') {
2896
intern->state = PARSE_ARG_POSTNAME;
2897
*store = &intern->current_arg->name;
2898
*store_len = &intern->current_arg->name_len;
2903
case PARSE_ARG_POSTNAME:
2904
intern->current_arg->name = apr_pstrmemdup(ctx->dpool,
2905
intern->current_arg->name,
2906
intern->current_arg->name_len);
2907
if (!intern->current_arg->name_len) {
2909
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2910
"argument name for value to tag %s in %s",
2911
apr_pstrmemdup(intern->r->pool, intern->directive,
2912
intern->directive_len),
2913
intern->r->filename);
2916
char *sp = intern->current_arg->name;
2918
/* normalize the name */
2920
*sp = apr_tolower(*sp);
2925
intern->state = PARSE_ARG_EQ;
2926
/* continue with next state immediately */
2931
while (p < ep && apr_isspace(*p)) {
2937
intern->state = PARSE_ARG_PREVAL;
2940
else { /* no value */
2941
intern->current_arg->value = NULL;
2942
intern->state = PARSE_PRE_ARG;
2949
case PARSE_ARG_PREVAL:
2952
while (p < ep && apr_isspace(*p)) {
2956
/* buffer doesn't consist of whitespaces only */
2958
intern->state = PARSE_ARG_VAL;
2960
case '"': case '\'': case '`':
2961
intern->quote = *p++;
2964
intern->quote = '\0';
2972
case PARSE_ARG_VAL_ESC:
2973
if (*p == intern->quote) {
2976
intern->state = PARSE_ARG_VAL;
2977
/* continue with next state immediately */
2980
for (; p < ep; ++p) {
2981
if (intern->quote && *p == '\\') {
2984
intern->state = PARSE_ARG_VAL_ESC;
2988
if (*p != intern->quote) {
2992
else if (intern->quote && *p == intern->quote) {
2994
*store = &intern->current_arg->value;
2995
*store_len = &intern->current_arg->value_len;
2996
intern->state = PARSE_ARG_POSTVAL;
2999
else if (!intern->quote && apr_isspace(*p)) {
3001
*store = &intern->current_arg->value;
3002
*store_len = &intern->current_arg->value_len;
3003
intern->state = PARSE_ARG_POSTVAL;
3010
case PARSE_ARG_POSTVAL:
3012
* The value is still the raw input string. Finally clean it up.
3014
--(intern->current_arg->value_len);
3016
/* strip quote escaping \ from the string */
3017
if (intern->quote) {
3018
apr_size_t shift = 0;
3021
sp = intern->current_arg->value;
3022
ep = intern->current_arg->value + intern->current_arg->value_len;
3023
while (sp < ep && *sp != '\\') {
3026
for (; sp < ep; ++sp) {
3027
if (*sp == '\\' && sp[1] == intern->quote) {
3036
intern->current_arg->value_len -= shift;
3039
intern->current_arg->value[intern->current_arg->value_len] = '\0';
3040
intern->state = PARSE_PRE_ARG;
3045
/* get a rid of a gcc warning about unhandled enumerations */
3049
return len; /* partial match of something */
3053
* This is the main loop over the current bucket brigade.
3055
static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
3057
include_ctx_t *ctx = f->ctx;
3058
struct ssi_internal_ctx *intern = ctx->intern;
3059
request_rec *r = f->r;
3060
apr_bucket *b = APR_BRIGADE_FIRST(bb);
3061
apr_bucket_brigade *pass_bb;
3062
apr_status_t rv = APR_SUCCESS;
3063
char *magic; /* magic pointer for sentinel use */
3066
if (APR_BRIGADE_EMPTY(bb)) {
3070
/* we may crash, since already cleaned up; hand over the responsibility
3071
* to the next filter;-)
3073
if (intern->seen_eos) {
3074
return ap_pass_brigade(f->next, bb);
3077
/* All stuff passed along has to be put into that brigade */
3078
pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3080
/* initialization for this loop */
3081
intern->bytes_read = 0;
3086
/* loop over the current bucket brigade */
3087
while (b != APR_BRIGADE_SENTINEL(bb)) {
3088
const char *data = NULL;
3089
apr_size_t len, index, release;
3090
apr_bucket *newb = NULL;
3091
char **store = &magic;
3092
apr_size_t *store_len = NULL;
3094
/* handle meta buckets before reading any data */
3095
if (APR_BUCKET_IS_METADATA(b)) {
3096
newb = APR_BUCKET_NEXT(b);
3098
APR_BUCKET_REMOVE(b);
3100
if (APR_BUCKET_IS_EOS(b)) {
3101
intern->seen_eos = 1;
3103
/* Hit end of stream, time for cleanup ... But wait!
3104
* Perhaps we're not ready yet. We may have to loop one or
3105
* two times again to finish our work. In that case, we
3106
* just re-insert the EOS bucket to allow for an extra loop.
3108
* PARSE_EXECUTE means, we've hit a directive just before the
3109
* EOS, which is now waiting for execution.
3111
* PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
3112
* no argument and no space between directive and end_seq
3113
* just before the EOS. (consider <!--#printenv--> as last
3114
* or only string within the stream). This state, however,
3115
* just cleans up and turns itself to PARSE_EXECUTE, which
3116
* will be passed through within the next (and actually
3119
if (PARSE_EXECUTE == intern->state ||
3120
PARSE_DIRECTIVE_POSTTAIL == intern->state) {
3121
APR_BUCKET_INSERT_BEFORE(newb, b);
3124
break; /* END OF STREAM */
3128
APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3130
if (APR_BUCKET_IS_FLUSH(b)) {
3139
/* enough is enough ... */
3140
if (ctx->flush_now ||
3141
intern->bytes_read > AP_MIN_BYTES_TO_WRITE) {
3143
if (!APR_BRIGADE_EMPTY(pass_bb)) {
3144
rv = ap_pass_brigade(f->next, pass_bb);
3145
if (rv != APR_SUCCESS) {
3146
apr_brigade_destroy(pass_bb);
3152
intern->bytes_read = 0;
3155
/* read the current bucket data */
3157
if (!intern->seen_eos) {
3158
if (intern->bytes_read > 0) {
3159
rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
3160
if (APR_STATUS_IS_EAGAIN(rv)) {
3166
if (!len || rv != APR_SUCCESS) {
3167
rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
3170
if (rv != APR_SUCCESS) {
3171
apr_brigade_destroy(pass_bb);
3175
intern->bytes_read += len;
3178
/* zero length bucket, fetch next one */
3179
if (!len && !intern->seen_eos) {
3180
b = APR_BUCKET_NEXT(b);
3185
* it's actually a data containing bucket, start/continue parsing
3188
switch (intern->state) {
3189
/* no current tag; search for start sequence */
3190
case PARSE_PRE_HEAD:
3191
index = find_start_sequence(ctx, data, len);
3194
apr_bucket_split(b, index);
3197
newb = APR_BUCKET_NEXT(b);
3198
if (ctx->flags & SSI_FLAG_PRINTING) {
3199
APR_BUCKET_REMOVE(b);
3200
APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3203
apr_bucket_delete(b);
3207
/* now delete the start_seq stuff from the remaining bucket */
3208
if (PARSE_DIRECTIVE == intern->state) { /* full match */
3209
apr_bucket_split(newb, intern->start_seq_pat->pattern_len);
3210
ctx->flush_now = 1; /* pass pre-tag stuff */
3213
b = APR_BUCKET_NEXT(newb);
3214
apr_bucket_delete(newb);
3222
/* we're currently looking for the end of the start sequence */
3224
index = find_partial_start_sequence(ctx, data, len, &release);
3226
/* check if we mismatched earlier and have to release some chars */
3227
if (release && (ctx->flags & SSI_FLAG_PRINTING)) {
3228
char *to_release = apr_palloc(ctx->pool, release);
3230
memcpy(to_release, intern->start_seq, release);
3231
newb = apr_bucket_pool_create(to_release, release, ctx->pool,
3232
f->c->bucket_alloc);
3233
APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
3236
if (index) { /* any match */
3237
/* now delete the start_seq stuff from the remaining bucket */
3238
if (PARSE_DIRECTIVE == intern->state) { /* final match */
3239
apr_bucket_split(b, index);
3240
ctx->flush_now = 1; /* pass pre-tag stuff */
3242
newb = APR_BUCKET_NEXT(b);
3243
apr_bucket_delete(b);
3249
/* we're currently grabbing the directive name */
3250
case PARSE_DIRECTIVE:
3251
case PARSE_DIRECTIVE_POSTNAME:
3252
case PARSE_DIRECTIVE_TAIL:
3253
case PARSE_DIRECTIVE_POSTTAIL:
3254
index = find_directive(ctx, data, len, &store, &store_len);
3257
apr_bucket_split(b, index);
3258
newb = APR_BUCKET_NEXT(b);
3263
APR_BUCKET_REMOVE(b);
3264
APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3268
/* time for cleanup? */
3269
if (store != &magic) {
3270
apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3272
apr_brigade_cleanup(intern->tmp_bb);
3276
apr_bucket_delete(b);
3282
/* skip WS and find out what comes next (arg or end_seq) */
3284
index = find_arg_or_tail(ctx, data, len);
3286
if (index) { /* skipped whitespaces */
3288
apr_bucket_split(b, index);
3290
newb = APR_BUCKET_NEXT(b);
3291
apr_bucket_delete(b);
3297
/* currently parsing name[=val] */
3299
case PARSE_ARG_NAME:
3300
case PARSE_ARG_POSTNAME:
3302
case PARSE_ARG_PREVAL:
3304
case PARSE_ARG_VAL_ESC:
3305
case PARSE_ARG_POSTVAL:
3306
index = find_argument(ctx, data, len, &store, &store_len);
3309
apr_bucket_split(b, index);
3310
newb = APR_BUCKET_NEXT(b);
3315
APR_BUCKET_REMOVE(b);
3316
APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3320
/* time for cleanup? */
3321
if (store != &magic) {
3322
apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3324
apr_brigade_cleanup(intern->tmp_bb);
3328
apr_bucket_delete(b);
3334
/* try to match end_seq at current pos. */
3336
case PARSE_TAIL_SEQ:
3337
index = find_tail(ctx, data, len);
3339
switch (intern->state) {
3340
case PARSE_EXECUTE: /* full match */
3341
apr_bucket_split(b, index);
3342
newb = APR_BUCKET_NEXT(b);
3343
apr_bucket_delete(b);
3347
case PARSE_ARG: /* no match */
3348
/* PARSE_ARG must reparse at the beginning */
3349
APR_BRIGADE_PREPEND(bb, intern->tmp_bb);
3350
b = APR_BRIGADE_FIRST(bb);
3353
default: /* partial match */
3354
newb = APR_BUCKET_NEXT(b);
3355
APR_BUCKET_REMOVE(b);
3356
APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3363
/* now execute the parsed directive, cleanup the space and
3364
* start again with PARSE_PRE_HEAD
3367
/* if there was an error, it was already logged; just stop here */
3368
if (intern->error) {
3369
if (ctx->flags & SSI_FLAG_PRINTING) {
3370
SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3375
include_handler_fn_t *handle_func;
3378
(include_handler_fn_t *)apr_hash_get(include_handlers, intern->directive,
3379
intern->directive_len);
3382
DEBUG_INIT(ctx, f, pass_bb);
3383
rv = handle_func(ctx, f, pass_bb);
3384
if (rv != APR_SUCCESS) {
3385
apr_brigade_destroy(pass_bb);
3390
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3391
"unknown directive \"%s\" in parsed doc %s",
3392
apr_pstrmemdup(r->pool, intern->directive,
3393
intern->directive_len),
3395
if (ctx->flags & SSI_FLAG_PRINTING) {
3396
SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3402
apr_pool_clear(ctx->dpool);
3403
apr_brigade_cleanup(intern->tmp_bb);
3405
/* Oooof. Done here, start next round */
3406
intern->state = PARSE_PRE_HEAD;
3409
} /* switch(ctx->state) */
3411
} /* while(brigade) */
3413
/* End of stream. Final cleanup */
3414
if (intern->seen_eos) {
3415
if (PARSE_HEAD == intern->state) {
3416
if (ctx->flags & SSI_FLAG_PRINTING) {
3417
char *to_release = apr_palloc(ctx->pool, intern->parse_pos);
3419
memcpy(to_release, intern->start_seq, intern->parse_pos);
3420
APR_BRIGADE_INSERT_TAIL(pass_bb,
3421
apr_bucket_pool_create(to_release,
3422
intern->parse_pos, ctx->pool,
3423
f->c->bucket_alloc));
3426
else if (PARSE_PRE_HEAD != intern->state) {
3427
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3428
"SSI directive was not properly finished at the end "
3429
"of parsed document %s", r->filename);
3430
if (ctx->flags & SSI_FLAG_PRINTING) {
3431
SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3435
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
3436
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
3437
"missing closing endif directive in parsed document"
3438
" %s", r->filename);
3441
/* cleanup our temporary memory */
3442
apr_brigade_destroy(intern->tmp_bb);
3443
apr_pool_destroy(ctx->dpool);
3445
/* don't forget to finally insert the EOS bucket */
3446
APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3449
/* if something's left over, pass it along */
3450
if (!APR_BRIGADE_EMPTY(pass_bb)) {
3451
rv = ap_pass_brigade(f->next, pass_bb);
3455
apr_brigade_destroy(pass_bb);
3462
* +-------------------------------------------------------+
3466
* +-------------------------------------------------------+
3469
static int includes_setup(ap_filter_t *f)
3471
include_dir_config *conf = ap_get_module_config(f->r->per_dir_config,
3474
/* When our xbithack value isn't set to full or our platform isn't
3475
* providing group-level protection bits or our group-level bits do not
3476
* have group-execite on, we will set the no_local_copy value to 1 so
3477
* that we will not send 304s.
3479
if ((conf->xbithack != XBITHACK_FULL)
3480
|| !(f->r->finfo.valid & APR_FINFO_GPROT)
3481
|| !(f->r->finfo.protection & APR_GEXECUTE)) {
3482
f->r->no_local_copy = 1;
3485
/* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4.
3486
* We don't know if we are going to be including a file or executing
3487
* a program - in either case a strong ETag header will likely be invalid.
3489
apr_table_setn(f->r->notes, "no-etag", "");
3494
static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
3496
request_rec *r = f->r;
3497
include_ctx_t *ctx = f->ctx;
3498
request_rec *parent;
3499
include_dir_config *conf = ap_get_module_config(r->per_dir_config,
3502
include_server_config *sconf= ap_get_module_config(r->server->module_config,
3505
if (!(ap_allow_options(r) & OPT_INCLUDES)) {
3506
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
3507
"mod_include: Options +Includes (or IncludesNoExec) "
3508
"wasn't set, INCLUDES filter removed");
3509
ap_remove_output_filter(f);
3510
return ap_pass_brigade(f->next, b);
3514
struct ssi_internal_ctx *intern;
3516
/* create context for this filter */
3517
f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx));
3518
ctx->intern = intern = apr_palloc(r->pool, sizeof(*ctx->intern));
3519
ctx->pool = r->pool;
3520
apr_pool_create(&ctx->dpool, ctx->pool);
3523
intern->tmp_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3524
intern->seen_eos = 0;
3525
intern->state = PARSE_PRE_HEAD;
3526
ctx->flags = (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
3527
if (ap_allow_options(r) & OPT_INCNOEXEC) {
3528
ctx->flags |= SSI_FLAG_NO_EXEC;
3531
ctx->if_nesting_level = 0;
3534
ctx->error_str = conf->default_error_msg;
3535
ctx->time_str = conf->default_time_fmt;
3536
intern->start_seq = sconf->default_start_tag;
3537
intern->start_seq_pat = bndm_compile(ctx->pool, intern->start_seq,
3538
strlen(intern->start_seq));
3539
intern->end_seq = sconf->default_end_tag;
3540
intern->end_seq_len = strlen(intern->end_seq);
3541
intern->undefined_echo = conf->undefined_echo;
3542
intern->undefined_echo_len = strlen(conf->undefined_echo);
3545
if ((parent = ap_get_module_config(r->request_config, &include_module))) {
3546
/* Kludge --- for nested includes, we want to keep the subprocess
3547
* environment of the base document (for compatibility); that means
3548
* torquing our own last_modified date as well so that the
3549
* LAST_MODIFIED variable gets reset to the proper value if the
3550
* nested document resets <!--#config timefmt -->.
3552
r->subprocess_env = r->main->subprocess_env;
3553
apr_pool_join(r->main->pool, r->pool);
3554
r->finfo.mtime = r->main->finfo.mtime;
3557
/* we're not a nested include, so we create an initial
3559
ap_add_common_vars(r);
3561
add_include_vars(r, conf->default_time_fmt);
3563
/* Always unset the content-length. There is no way to know if
3564
* the content will be modified at some point by send_parsed_content.
3565
* It is very possible for us to not find any content in the first
3566
* 9k of the file, but still have to modify the content of the file.
3567
* If we are going to pass the file through send_parsed_content, then
3568
* the content-length should just be unset.
3570
apr_table_unset(f->r->headers_out, "Content-Length");
3572
/* Always unset the Last-Modified field - see RFC2616 - 13.3.4.
3573
* We don't know if we are going to be including a file or executing
3574
* a program which may change the Last-Modified header or make the
3575
* content completely dynamic. Therefore, we can't support these
3577
* Exception: XBitHack full means we *should* set the Last-Modified field.
3580
/* Assure the platform supports Group protections */
3581
if ((conf->xbithack == XBITHACK_FULL)
3582
&& (r->finfo.valid & APR_FINFO_GPROT)
3583
&& (r->finfo.protection & APR_GEXECUTE)) {
3584
ap_update_mtime(r, r->finfo.mtime);
3585
ap_set_last_modified(r);
3588
apr_table_unset(f->r->headers_out, "Last-Modified");
3591
/* add QUERY stuff to env cause it ain't yet */
3593
char *arg_copy = apr_pstrdup(r->pool, r->args);
3595
apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
3596
ap_unescape_url(arg_copy);
3597
apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
3598
ap_escape_shell_cmd(r->pool, arg_copy));
3601
return send_parsed_content(f, b);
3604
static int include_fixup(request_rec *r)
3606
include_dir_config *conf;
3608
conf = ap_get_module_config(r->per_dir_config, &include_module);
3610
if (r->handler && (strcmp(r->handler, "server-parsed") == 0))
3612
if (!r->content_type || !*r->content_type) {
3613
ap_set_content_type(r, "text/html");
3615
r->handler = "default-handler";
3618
#if defined(OS2) || defined(WIN32) || defined(NETWARE)
3619
/* These OS's don't support xbithack. This is being worked on. */
3625
if (conf->xbithack == XBITHACK_OFF) {
3629
if (!(r->finfo.protection & APR_UEXECUTE)) {
3633
if (!r->content_type || strcmp(r->content_type, "text/html")) {
3639
/* We always return declined, because the default handler actually
3640
* serves the file. All we have to do is add the filter.
3642
ap_add_output_filter("INCLUDES", NULL, r, r->connection);
3648
* +-------------------------------------------------------+
3650
* | Configuration Handling
3652
* +-------------------------------------------------------+
3655
static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
3657
include_dir_config *result = apr_palloc(p, sizeof(include_dir_config));
3659
result->default_error_msg = DEFAULT_ERROR_MSG;
3660
result->default_time_fmt = DEFAULT_TIME_FORMAT;
3661
result->undefined_echo = DEFAULT_UNDEFINED_ECHO;
3662
result->xbithack = DEFAULT_XBITHACK;
3667
static void *create_includes_server_config(apr_pool_t *p, server_rec *server)
3669
include_server_config *result;
3671
result = apr_palloc(p, sizeof(include_server_config));
3672
result->default_end_tag = DEFAULT_END_SEQUENCE;
3673
result->default_start_tag = DEFAULT_START_SEQUENCE;
3678
static const char *set_xbithack(cmd_parms *cmd, void *mconfig, const char *arg)
3680
include_dir_config *conf = mconfig;
3682
if (!strcasecmp(arg, "off")) {
3683
conf->xbithack = XBITHACK_OFF;
3685
else if (!strcasecmp(arg, "on")) {
3686
conf->xbithack = XBITHACK_ON;
3688
else if (!strcasecmp(arg, "full")) {
3689
conf->xbithack = XBITHACK_FULL;
3692
return "XBitHack must be set to Off, On, or Full";
3698
static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig,
3701
include_server_config *conf;
3702
const char *p = tag;
3704
/* be consistent. (See below in set_default_end_tag) */
3706
if (apr_isspace(*p)) {
3707
return "SSIStartTag may not contain any whitespaces";
3712
conf= ap_get_module_config(cmd->server->module_config , &include_module);
3713
conf->default_start_tag = tag;
3718
static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig,
3721
include_server_config *conf;
3722
const char *p = tag;
3724
/* sanity check. The parser may fail otherwise */
3726
if (apr_isspace(*p)) {
3727
return "SSIEndTag may not contain any whitespaces";
3732
conf= ap_get_module_config(cmd->server->module_config , &include_module);
3733
conf->default_end_tag = tag;
3738
static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig,
3741
include_dir_config *conf = mconfig;
3742
conf->undefined_echo = msg;
3747
static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig,
3750
include_dir_config *conf = mconfig;
3751
conf->default_error_msg = msg;
3756
static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig,
3759
include_dir_config *conf = mconfig;
3760
conf->default_time_fmt = fmt;
3767
* +-------------------------------------------------------+
3769
* | Module Initialization and Configuration
3771
* +-------------------------------------------------------+
3774
static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
3775
apr_pool_t *ptemp, server_rec *s)
3777
include_handlers = apr_hash_make(p);
3779
ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
3781
if(ssi_pfn_register) {
3782
ssi_pfn_register("if", handle_if);
3783
ssi_pfn_register("set", handle_set);
3784
ssi_pfn_register("else", handle_else);
3785
ssi_pfn_register("elif", handle_elif);
3786
ssi_pfn_register("echo", handle_echo);
3787
ssi_pfn_register("endif", handle_endif);
3788
ssi_pfn_register("fsize", handle_fsize);
3789
ssi_pfn_register("config", handle_config);
3790
ssi_pfn_register("include", handle_include);
3791
ssi_pfn_register("flastmod", handle_flastmod);
3792
ssi_pfn_register("printenv", handle_printenv);
3798
static const command_rec includes_cmds[] =
3800
AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS,
3801
"Off, On, or Full"),
3802
AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL,
3804
AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
3805
"a strftime(3) formatted string"),
3806
AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF,
3807
"SSI Start String Tag"),
3808
AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF,
3809
"SSI End String Tag"),
3810
AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, OR_ALL,
3811
"String to be displayed if an echoed variable is undefined"),
3815
static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
3817
apr_hash_set(include_handlers, tag, strlen(tag), (const void *)func);
3820
static void register_hooks(apr_pool_t *p)
3822
APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
3823
APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
3824
APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
3825
ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
3826
ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST);
3827
ap_register_output_filter("INCLUDES", includes_filter, includes_setup,
3831
module AP_MODULE_DECLARE_DATA include_module =
3833
STANDARD20_MODULE_STUFF,
3834
create_includes_dir_config, /* dir config creater */
3835
NULL, /* dir merger --- default is to override */
3836
create_includes_server_config,/* server config */
3837
NULL, /* merge server config */
3838
includes_cmds, /* command apr_table_t */
3839
register_hooks /* register hooks */